#requires -Version 5 # One-shot helper: produces a multi-resolution icon.ico from icon.png. # Run only when the source icon changes; commit the resulting icon.ico. Add-Type -AssemblyName System.Drawing Add-Type -AssemblyName PresentationCore Add-Type -AssemblyName WindowsBase $here = Split-Path -Parent $MyInvocation.MyCommand.Path $srcPath = Join-Path $here 'icon.png' $icoPath = Join-Path $here 'icon.ico' if (-not (Test-Path $srcPath)) { throw "icon.png not found at $srcPath" } # Detect if the file is actually a different format renamed to .png (e.g. WebP from AI tools). # If so, transcode via WPF's WIC pipeline to a real PNG before feeding GDI+. $head = [System.IO.File]::ReadAllBytes($srcPath)[0..3] $isPng = $head[0] -eq 0x89 -and $head[1] -eq 0x50 -and $head[2] -eq 0x4E -and $head[3] -eq 0x47 if (-not $isPng) { Write-Host "Source file is not a PNG (likely WebP from AI tool). Transcoding via WIC..." $bytes = [System.IO.File]::ReadAllBytes($srcPath) $stream = New-Object System.IO.MemoryStream(,$bytes) $decoder = [System.Windows.Media.Imaging.BitmapDecoder]::Create( $stream, [System.Windows.Media.Imaging.BitmapCreateOptions]::PreservePixelFormat, [System.Windows.Media.Imaging.BitmapCacheOption]::OnLoad) $frame = $decoder.Frames[0] # Force BGRA32 so GDI+ can later handle it cleanly with alpha $converted = New-Object System.Windows.Media.Imaging.FormatConvertedBitmap( $frame, [System.Windows.Media.PixelFormats]::Bgra32, $null, 0) $encoder = New-Object System.Windows.Media.Imaging.PngBitmapEncoder $encoder.Frames.Add([System.Windows.Media.Imaging.BitmapFrame]::Create($converted)) $outStream = New-Object System.IO.MemoryStream $encoder.Save($outStream) [System.IO.File]::WriteAllBytes($srcPath, $outStream.ToArray()) $outStream.Dispose() $stream.Dispose() Write-Host "Transcoded to real PNG ($($outStream.Length) bytes)." } $sizes = 16, 32, 48, 64, 128, 256 $src = [System.Drawing.Image]::FromFile($srcPath) $frames = @{} foreach ($size in $sizes) { $bmp = New-Object System.Drawing.Bitmap $size, $size, ([System.Drawing.Imaging.PixelFormat]::Format32bppArgb) $g = [System.Drawing.Graphics]::FromImage($bmp) $g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic $g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality $g.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality $g.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighQuality $g.DrawImage($src, 0, 0, $size, $size) $g.Dispose() $ms = New-Object System.IO.MemoryStream $bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png) $frames[$size] = $ms.ToArray() $bmp.Dispose() $ms.Dispose() } $out = New-Object System.IO.MemoryStream $bw = New-Object System.IO.BinaryWriter($out) # ICONDIR header $bw.Write([UInt16]0) $bw.Write([UInt16]1) $bw.Write([UInt16]$sizes.Count) $dataOffset = 6 + (16 * $sizes.Count) foreach ($size in $sizes) { $bytes = $frames[$size] $w = if ($size -ge 256) { [byte]0 } else { [byte]$size } $h = if ($size -ge 256) { [byte]0 } else { [byte]$size } $bw.Write([byte]$w) $bw.Write([byte]$h) $bw.Write([byte]0) $bw.Write([byte]0) $bw.Write([UInt16]1) $bw.Write([UInt16]32) $bw.Write([UInt32]$bytes.Length) $bw.Write([UInt32]$dataOffset) $dataOffset += $bytes.Length } foreach ($size in $sizes) { $bw.Write($frames[$size]) } [System.IO.File]::WriteAllBytes($icoPath, $out.ToArray()) $bw.Dispose() $out.Dispose() $src.Dispose() "Wrote: $icoPath ($((Get-Item $icoPath).Length) bytes, $($sizes.Count) sizes)"