
PowerShell ANSI Art
Recent versions of the Windows console support RGB ANSI colors. Here’s a script that outputs an image to the console, useful for when you feel the need to beautify the user experience of some backup script.
It repeatedly samples parts of an image, averages the colour of the sampled area, and outputs a space with the background colour set to the derived colour. It assumes the console is using the default font, which is 8 x 16 pixels. The script ignores alpha, and I make no claim of it working with all image types.
It runs slowly. The obvious solution would be to not evaluate every pixel, but a random sample of pixels within the sample area. The trade-off would be speed at the expense of accuracy.
param( [string] $imgFile ) # Needed for working with images [void][System.Reflection.Assembly]::LoadWithPartialName( "System.Drawing" ) # Assume console default font size: 8 x 16 pixels. Set-Variable -Name FONT_W_PX -Value ([int]8) -Option Constant Set-Variable -Name FONT_H_PX -Value ([int]16) -Option Constant # Get the average color of an area. # Return an ANSI RGB color spec string # to print a space with background color function getColorChar( [System.Drawing.Bitmap] $img, [int] $xPos, [int] $yPos, [int] $width, [int] $height ) { [int]$xEnd = $xPos + $width - 1 [int]$yEnd = $yPos + $height - 1 [long] $r = [long] $g = [long] $b = 0 ForEach( $x in $xPos .. $xEnd ) { ForEach( $y in $yPos .. $yEnd ) { $pixel = $img.GetPixel( $x, $y ) $r += $pixel.R $g += $pixel.G $b += $pixel.B } } [int]$sampleCount = $width * $height return "{0}[48;2;{1};{2};{3}m " -f [char]0x1B, [math]::Floor( $r / $sampleCount ), [math]::Floor( $g / $sampleCount ), [math]::Floor( $b / $sampleCount ) } if( -not (Test-Path $imgFile)) { Throw "File not found: $imgFile" } [System.Drawing.Image]$img = [System.Drawing.Bitmap]::FromFile( $imgFile ) $imgW = $img.Width $imgH = $img.Height Write-Host "Image: $imgW x $imgH pixels" # Get console size, in pixels $consW = ((Get-Host).UI.RawUI.WindowSize.Width - 1) * $FONT_W_PX $consH = ((Get-Host).UI.RawUI.WindowSize.Height - 4) * $FONT_H_PX Write-Host "Console: $consW x $consH pixels" # Compare image against console size $ratioW = $consW / $imgW $ratioH = $consH / $imgH # Fit image to console size # Case: image is smaller than console if(($ratioW -gt 1) -and ($ratioH -gt 1)) { $targW = $imgW $targH = $imgH } else { # Case: Image is larger than console if( $ratioH -gt $ratioW ) { $targW = $consW $targH = [math]::Floor( $imgH * $ratioW ) } else { $targH = $consH $targW = [math]::Floor( $imgW * $ratioH ) } } # Adjust target width and height to multiples # of console character sizes $targW = $targW - ($targW % $FONT_W_PX ) $targH = $targH - ($targH % $FONT_H_PX ) Write-Host "Target Size: $targW x $targH pixels" # Target size, in characters $targWch = $targW / $FONT_W_PX $targHch = $targH / $FONT_H_PX Write-Host "Target Size: $targWch x $targHch chars" # Derive the bounds of the sample area. # Drive from long edge of the image. $sampleWidth = [math]::Floor( $imgW / $targWch ) $sampleHeight = [math]::Floor( $imgH / $targHch ) Write-Host "Sample size: $sampleWidth x $sampleHeight pixels" $numSamplesX = [math]::Floor( $imgW / $sampleWidth ) $numSamplesY = [math]::Floor( $imgH / $sampleHeight ) Write-Host "Horiz Samples: $numSamplesX" Write-Host "Vert Samples: $numSamplesY" # Output image line by line $line = $Null ForEach( $y in 0..($numSamplesY - 1)) { $yPos = $y * $sampleHeight ForEach( $x in 0..($numSamplesX - 1)) { $xPos = $x * $sampleWidth $line += getColorChar $img $xPos $yPos $sampleWidth $sampleHeight } Write-Output $line $line = "" } # Reset terminal output to default Write-Output ("{0}[39;49m" -f [char]0x1B)
Looking good, Mona.