Add-Type -AssemblyName System.Drawing $resourcesDir = Join-Path $PSScriptRoot "..\resources" $resourcesDir = (Resolve-Path $resourcesDir).Path function New-AppIcon([int]$size, [string]$path) { $bmp = New-Object System.Drawing.Bitmap $size, $size $g = [System.Drawing.Graphics]::FromImage($bmp) $g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias $g.Clear([System.Drawing.Color]::Transparent) # Rounded square background with gradient (blue → teal) $rect = New-Object System.Drawing.RectangleF (0, 0, $size, $size) $path1 = New-Object System.Drawing.Drawing2D.GraphicsPath $r = $size * 0.22 $path1.AddArc($rect.X, $rect.Y, $r * 2, $r * 2, 180, 90) $path1.AddArc($rect.X + $rect.Width - $r * 2, $rect.Y, $r * 2, $r * 2, 270, 90) $path1.AddArc($rect.X + $rect.Width - $r * 2, $rect.Y + $rect.Height - $r * 2, $r * 2, $r * 2, 0, 90) $path1.AddArc($rect.X, $rect.Y + $rect.Height - $r * 2, $r * 2, $r * 2, 90, 90) $path1.CloseFigure() $color1 = [System.Drawing.Color]::FromArgb(255, 91, 141, 239) $color2 = [System.Drawing.Color]::FromArgb(255, 41, 199, 184) $brush = New-Object System.Drawing.Drawing2D.LinearGradientBrush $rect, $color1, $color2, 45 $g.FillPath($brush, $path1) # Inner heartbeat / activity line $pen = New-Object System.Drawing.Pen ([System.Drawing.Color]::White), ($size * 0.07) $pen.LineJoin = [System.Drawing.Drawing2D.LineJoin]::Round $pen.StartCap = [System.Drawing.Drawing2D.LineCap]::Round $pen.EndCap = [System.Drawing.Drawing2D.LineCap]::Round $mid = [single]($size / 2.0) $points = [System.Drawing.PointF[]]@( [System.Drawing.PointF]::new([single]($size * 0.22), $mid), [System.Drawing.PointF]::new([single]($size * 0.36), $mid), [System.Drawing.PointF]::new([single]($size * 0.44), [single]($mid - $size * 0.22)), [System.Drawing.PointF]::new([single]($size * 0.56), [single]($mid + $size * 0.22)), [System.Drawing.PointF]::new([single]($size * 0.64), $mid), [System.Drawing.PointF]::new([single]($size * 0.78), $mid) ) $g.DrawLines($pen, $points) $pen.Dispose() $brush.Dispose() $path1.Dispose() $g.Dispose() $bmp.Save($path, [System.Drawing.Imaging.ImageFormat]::Png) $bmp.Dispose() } function New-TrayIcon([int]$size, [string]$path) { $bmp = New-Object System.Drawing.Bitmap $size, $size $g = [System.Drawing.Graphics]::FromImage($bmp) $g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias $g.Clear([System.Drawing.Color]::Transparent) # Monochrome white heartbeat line $pen = New-Object System.Drawing.Pen ([System.Drawing.Color]::White), ($size * 0.13) $pen.LineJoin = [System.Drawing.Drawing2D.LineJoin]::Round $pen.StartCap = [System.Drawing.Drawing2D.LineCap]::Round $pen.EndCap = [System.Drawing.Drawing2D.LineCap]::Round $mid = [single]($size / 2.0) $points = [System.Drawing.PointF[]]@( [System.Drawing.PointF]::new([single]($size * 0.08), $mid), [System.Drawing.PointF]::new([single]($size * 0.30), $mid), [System.Drawing.PointF]::new([single]($size * 0.40), [single]($mid - $size * 0.30)), [System.Drawing.PointF]::new([single]($size * 0.55), [single]($mid + $size * 0.30)), [System.Drawing.PointF]::new([single]($size * 0.65), $mid), [System.Drawing.PointF]::new([single]($size * 0.92), $mid) ) $g.DrawLines($pen, $points) $pen.Dispose() $g.Dispose() $bmp.Save($path, [System.Drawing.Imaging.ImageFormat]::Png) $bmp.Dispose() } # Write tray PNGs (16, 32) New-TrayIcon 16 (Join-Path $resourcesDir "tray.png") New-TrayIcon 32 (Join-Path $resourcesDir "tray@2x.png") # Write app PNGs (multiple sizes for ICO assembly) $sizes = @(16, 24, 32, 48, 64, 128, 256) $pngPaths = @() foreach ($s in $sizes) { $p = Join-Path $resourcesDir ("icon-$s.png") New-AppIcon $s $p $pngPaths += $p } # Assemble multi-resolution ICO function Write-Ico([string[]]$pngFiles, [string]$icoPath) { $entries = @() $data = New-Object System.IO.MemoryStream foreach ($pf in $pngFiles) { $bytes = [System.IO.File]::ReadAllBytes($pf) $bmp = [System.Drawing.Image]::FromFile($pf) $entries += [pscustomobject]@{ Width = $bmp.Width Height = $bmp.Height Size = $bytes.Length Bytes = $bytes } $bmp.Dispose() } $headerSize = 6 + 16 * $entries.Count $offset = $headerSize foreach ($e in $entries) { $e | Add-Member -NotePropertyName Offset -NotePropertyValue $offset -Force $offset += $e.Size } $ms = New-Object System.IO.MemoryStream $bw = New-Object System.IO.BinaryWriter $ms $bw.Write([uint16]0) # Reserved $bw.Write([uint16]1) # Type: ICO $bw.Write([uint16]$entries.Count) foreach ($e in $entries) { $bw.Write([byte]($(if ($e.Width -ge 256) { 0 } else { $e.Width }))) $bw.Write([byte]($(if ($e.Height -ge 256) { 0 } else { $e.Height }))) $bw.Write([byte]0) # ColorCount $bw.Write([byte]0) # Reserved $bw.Write([uint16]1) # ColorPlanes $bw.Write([uint16]32) # BitsPerPixel $bw.Write([uint32]$e.Size) $bw.Write([uint32]$e.Offset) } foreach ($e in $entries) { $bw.Write($e.Bytes) } $bw.Flush() [System.IO.File]::WriteAllBytes($icoPath, $ms.ToArray()) $bw.Dispose() $ms.Dispose() } Write-Ico $pngPaths (Join-Path $resourcesDir "icon.ico") # Keep a 256 PNG as 'icon.png' for in-app use, drop the smaller intermediates. Copy-Item (Join-Path $resourcesDir "icon-256.png") (Join-Path $resourcesDir "icon.png") -Force foreach ($s in $sizes) { Remove-Item (Join-Path $resourcesDir ("icon-$s.png")) -Force -ErrorAction SilentlyContinue } Write-Host "Generated icons in $resourcesDir" Get-ChildItem $resourcesDir | Format-Table Name, Length