public/PwshSpectreConsoleImageSharp.ps1

function Get-SpectreImageExperimental {
    param (
        [string] $ImagePath,
        [int] $MaxWidth,
        [switch] $Repeat,
        [ValidateSet("Bicubic", "NearestNeighbor")]
        [string] $Resampler = "Bicubic"
    )

    $backgroundColor = [System.Drawing.Color]::FromName([Console]::BackgroundColor)
    
    $image = [SixLabors.ImageSharp.Image]::Load($ImagePath)
    $scaledHeight = [int]($image.Height * ($MaxWidth / $image.Width))
    
    if($image.Width -gt $MaxWidth) {
        [SixLabors.ImageSharp.Processing.ProcessingExtensions]::Mutate($image, {
            param($context)
            [SixLabors.ImageSharp.Processing.ResizeExtensions]::Resize(
                $context,
                $MaxWidth,
                $scaledHeight,
                [SixLabors.ImageSharp.Processing.KnownResamplers]::$Resampler
            )
        })
    }

    $frames = @()
    $buffer = [System.Text.StringBuilder]::new($MaxWidth * $scaledHeight * 2)

    foreach($frame in $image.Frames) {
        $frameDelayMilliseconds = 1000
        try {
            $frameMetadata = [SixLabors.ImageSharp.MetadataExtensions]::GetGifMetadata($frame.Metadata)
            if($frameMetadata.FrameDelay) {
                # The delay is supposed to be in milliseconds and imagesharp seems to be a bit out when it decodes it
                $frameDelayMilliseconds = $frameMetadata.FrameDelay * 10
            }
        } catch {
            # Don't care
        }
        $buffer.Clear() | Out-Null
        for($y = 0; $y -lt $scaledHeight; $y += 2) {
            for($x = 0; $x -lt $MaxWidth; $x++) {
                $currentPixel = $frame[$x,$y]
                if($null -ne $currentPixel.A) {
                    # Quick-hack blending the foreground with the terminal background color. This could be done in imagesharp
                    $foregroundMultiplier = $currentPixel.A / 255
                    $backgroundMultiplier = 100 - $foregroundMultiplier
                    $currentPixelRgb = @{
                        R = [math]::Min(255, ($currentPixel.R * $foregroundMultiplier + $backgroundColor.R * $backgroundMultiplier))
                        G = [math]::Min(255, ($currentPixel.G * $foregroundMultiplier + $backgroundColor.G * $backgroundMultiplier))
                        B = [math]::Min(255, ($currentPixel.B * $foregroundMultiplier + $backgroundColor.B * $backgroundMultiplier))
                    }
                } else {
                    $currentPixelRgb = @{
                        R = $currentPixel.R
                        G = $currentPixel.G
                        B = $currentPixel.B
                    }
                }

                # Parse the image 2 vertical pixels at a time and use the lower half block character with varying foreground and background colors to
                # make it appear as two pixels within one character space
                if($image.Height -ge ($y + 1)) {
                    $pixelBelow = $frame[$x,($y + 1)]

                    if($null -ne $pixelBelow.A) {
                        # Quick-hack blending the foreground with the terminal background color. This could be done in imagesharp
                        $foregroundMultiplier = $pixelBelow.A / 255
                        $backgroundMultiplier = 100 - $foregroundMultiplier
                        $pixelBelowRgb = @{
                            R = [math]::Min(255, ($pixelBelow.R * $foregroundMultiplier + $backgroundColor.R * $backgroundMultiplier))
                            G = [math]::Min(255, ($pixelBelow.G * $foregroundMultiplier + $backgroundColor.G * $backgroundMultiplier))
                            B = [math]::Min(255, ($pixelBelow.B * $foregroundMultiplier + $backgroundColor.B * $backgroundMultiplier))
                        }
                    } else {
                        $pixelBelowRgb = @{
                            R = $pixelBelow.R
                            G = $pixelBelow.G
                            B = $pixelBelow.B
                        }
                    }

                    $buffer.Append(("$([Char]27)[38;2;{0};{1};{2}m" -f
                        $pixelBelowRgb.R,
                        $pixelBelowRgb.G,
                        $pixelBelowRgb.B
                    )) | Out-Null
                }

                $buffer.Append(("$([Char]27)[48;2;{0};{1};{2}m$([Char]0x2584)$([Char]27)[0m" -f
                    $currentPixelRgb.R,
                    $currentPixelRgb.G,
                    $currentPixelRgb.B
                )) | Out-Null
            }
            $buffer.AppendLine() | Out-Null
        }

        $frames += @{
            FrameDelayMilliseconds = $frameDelayMilliseconds
            Frame = $buffer.ToString().Trim()
        }
    }

    $topLeft = $Host.UI.RawUI.CursorPosition
    [Console]::CursorVisible = $false
    do {
        foreach($frame in $frames) {
            [Console]::SetCursorPosition($topLeft.X, $topLeft.Y)
            Write-Host $frame.Frame
            Start-Sleep -Milliseconds $frame.FrameDelayMilliseconds
        }
    } while ($Repeat)
    [Console]::CursorVisible = $true
}

function Get-SpectreImage {
    param (
        [string] $ImagePath,
        [int] $MaxWidth
    )
    $image = [Spectre.Console.CanvasImage]::new($ImagePath)
    if($MaxWidth) {
        $image.MaxWidth = $MaxWidth
    }
    [Spectre.Console.AnsiConsole]::Write($image)
}