Skip to content

Instantly share code, notes, and snippets.

@romero126
Last active October 23, 2024 02:45
Show Gist options
  • Save romero126/9e07a397b0fad5d6f33d58838b3c8017 to your computer and use it in GitHub Desktop.
Save romero126/9e07a397b0fad5d6f33d58838b3c8017 to your computer and use it in GitHub Desktop.
Sixel Demo
class FrameBuffer : IDisposable {
[System.Int32] $Width
[System.Int32] $Height
[System.Boolean] $Disposed
hidden [System.Int32[]] $buffer
hidden [System.Runtime.InteropServices.GCHandle] $bufferHandle
hidden [System.Drawing.Image] $bitmap
FrameBuffer([System.Int32]$w, [System.Int32]$h) {
$this.Width = $w
$this.Height = $h
# Bind the image to the buffer
$this.buffer = [System.Int32[]]::new($this.Width * $this.Height)
$this.bufferHandle = [System.Runtime.InteropServices.GCHandle]::Alloc($this.buffer, [System.Runtime.InteropServices.GCHandleType]::Pinned)
$this.bitmap = [System.Drawing.Bitmap]::new($this.Width, $this.Height, $this.Width * 4, [System.Drawing.Imaging.PixelFormat]::Format32bppPArgb, $this.bufferHandle.AddrOfPinnedObject())
}
FrameBuffer([System.Drawing.Image]$image) {
$this.Width = $image.Width
$this.Height = $image.Height
# Bind the image to the buffer
$this.buffer = [System.Int32[]]::new($this.Width * $this.Height)
$this.bufferHandle = [System.Runtime.InteropServices.GCHandle]::Alloc($this.buffer, [System.Runtime.InteropServices.GCHandleType]::Pinned)
$this.bitmap = [System.Drawing.Bitmap]::new($this.Width, $this.Height, $this.Width * 4, [System.Drawing.Imaging.PixelFormat]::Format32bppPArgb, $this.bufferHandle.AddrOfPinnedObject())
$gfx = [System.Drawing.Graphics]::FromImage($this.bitmap)
$gfx.DrawImage($image, 0, 0, $image.Width, $image.Height)
$gfx.Dispose()
}
FrameBuffer([System.String]$path) {
$image = [System.Drawing.Image]::FromFile($path)
$this.Width = $image.Width
$this.Height = $image.Height
# Bind the image to the buffer
$this.buffer = [System.Int32[]]::new($this.Width * $this.Height)
$this.bufferHandle = [System.Runtime.InteropServices.GCHandle]::Alloc($this.buffer, [System.Runtime.InteropServices.GCHandleType]::Pinned)
$this.bitmap = [System.Drawing.Bitmap]::new($this.Width, $this.Height, $this.Width * 4, [System.Drawing.Imaging.PixelFormat]::Format32bppPArgb, $this.bufferHandle.AddrOfPinnedObject())
$this.Clear()
$this.DrawImage($image, 0, 0, $image.Width, $image.Height)
}
static [FrameBuffer] Create($path) {
$_fromBitmap = [System.Drawing.Image]::FromFile($path)
$result = [FrameBuffer]::new($_fromBitmap)
return $result
}
[System.Drawing.Color] GetPixel($i) {
if ($i -gt $this.buffer.Length) {
throw "Index out of bounds"
}
$col = $this.buffer[$i]
$result = [System.Drawing.Color]::FromArgb($col)
return $result
}
[System.Drawing.Color] GetPixel($x, $y) {
if ($x -gt $this.Width -or $y -gt $this.Height) {
throw "Index out of bounds"
}
$i = $x + ($y * $this.Width)
$col = $this.buffer[$i]
$result = [System.Drawing.Color]::FromArgb($col)
return $result
}
[System.Void] Clear() {
$defaultColorName = [System.Console]::BackgroundColor.ToString()
$defaultColor = [System.Drawing.Color]::FromName($defaultColorName)
$this.Clear($defaultColor)
}
[System.Void] Clear([System.Drawing.Color] $color) {
$gfx = [System.Drawing.Graphics]::FromImage($this.bitmap)
$gfx.CompositingMode = ([System.Drawing.Drawing2D.CompositingMode]::SourceOver)
$gfx.CompositingQuality = ([System.Drawing.Drawing2D.CompositingQuality]::Default)
$gfx.Clear($color)
$gfx.Flush()
$gfx.Dispose()
}
SaveImage($path) {
$this.bitmap.Save($path)
}
[System.Void] DrawImage([System.Drawing.Image]$image, [System.Int32]$x, [System.Int32]$y) {
$this.DrawImage($image, $x, $y, $image.Width, $image.Height)
}
[System.Void] DrawImage([System.Drawing.Image]$image, [System.Int32]$x, [System.Int32]$y, [System.Int32]$w, [System.Int32]$h) {
$gfx = [System.Drawing.Graphics]::FromImage($this.bitmap)
$gfx.CompositingMode = ([System.Drawing.Drawing2D.CompositingMode]::SourceOver)
$gfx.CompositingQuality = ([System.Drawing.Drawing2D.CompositingQuality]::HighQuality)
$image.MakeTransparent()
$gfx.DrawImage($image, $x, $y, $w, $h)
$gfx.Flush()
$gfx.Dispose()
}
[System.String] ToString([System.String]$format) {
if ($format -ne "Sixel") {
throw "Invalid Format"
}
$stringBuilder = [System.Text.StringBuilder]::new()
$sixelColorMap = @{}
$stringBuilder.Append([char]0x1b + "P0;1q")
# Force 1-1 Scale
$stringBuilder.AppendFormat("`"1;1;{0};{1};", $this.Width, $this.Height)
$stringBuilder.Append("#0;2;0;0;0") # This is a weird color
for ($i = 0; $i -le $this.buffer.Length-1; $i++) {
# Lets always look at the colors
$argb = $this.buffer[$i]
$A = $argb -shr 24 -band 0xFF
$R = $argb -shr 16 -band 0xFF
$G = $argb -shr 8 -band 0xFF
$B = $argb -band 0xFF
# Normalize the colors to match 255 Total colors
# Shrink the color indexes by a factor of 3
if ($R % 41 -ne 0) { $R = $R - ($R % 41) }
if ($G % 41 -ne 0) { $G = $G - ($G % 41) }
if ($B % 41 -ne 0) { $B = $B - ($B % 41) }
# Calculate new ARGB value
$newARGB = ($A -shl 24) -bor ($R -shl 16) -bor ($G -shl 8) -bor $B
if (-not $sixelColorMap.ContainsKey($newARGB)) {
$R = [int]([Math]::Round($R / 255 * 100))
$G = [int]([Math]::Round($G / 255 * 100))
$B = [int]([Math]::Round($B / 255 * 100))
$count = $SixelColorMap.Count+1
$SixelColorMap.Add($newARGB, $count)
$stringBuilder.AppendFormat("#{0};2;{1};{2};{3}", $count, $R, $G, $B)
}
# It might be faster to set x and y as incremental variables instead of using math
$x = ($i) % ($this.Width)
$y = [math]::Floor(($i) / ($this.Width))
$sixelPos = $y % 6
if ($newARGB -eq -16777216) {
$stringBuilder.Append("#0?" )
} else {
$stringBuilder.AppendFormat("#{0}{1}", $SixelColorMap[$newARGB], [char][int](63+[math]::pow(2, $sixelPos)) )
}
if ($sixelPos -eq 5 -and $x -eq $this.Width-1) {
$stringBuilder.Append('-')
} elseif ($x -eq $this.Width-1) {
$stringBuilder.Append('$')
}
}
$stringBuilder.Append([char]0x1b + "\")
return $stringBuilder.ToString()
}
Dispose() {
if (-not $this.Disposed) {
$this.Disposed = $true
$this.bufferHandle.Free()
$this.bitmap.Dispose()
}
}
}
#[FrameBuffer]::new(100, 100)
[CmdletBinding()]
param(
[parameter(Mandatory = $true)]
[ArgumentCompleter({
$path = Get-ChildItem -Path $PWD -Filter "*.png" -Recurse
$path | ForEach-Object { $_.Name }
})]
[System.IO.FileInfo]$path
)
# Load dependencies
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
# Import the Class Object
. .\ConsolePresentationFramework.ps1
$path = (Get-Item -Path $path).FullName
$frameBuffer = [FrameBuffer]::new($path)
$frameBuffer.SaveImage("$PSScriptRoot\buffer.png")
Measure-Command -Expression {
$sixel = $frameBuffer.ToString("Sixel")
}
Write-Host "Sixel Width: $($frameBuffer.Width) Height: $($frameBuffer.Height)"
$sixel
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment