param( [string]$OutputPath = "" ) $ErrorActionPreference = "Stop" $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path if ([string]::IsNullOrWhiteSpace($OutputPath)) { $OutputPath = Join-Path $repoRoot "companion\src\Lumi.Companion.App\Assets\Lumi.Companion.ico" } $OutputPath = [System.IO.Path]::GetFullPath($OutputPath) $sizes = @(16, 32, 48, 256) function New-LumiIconFrame { param([int]$Size) $radius = [Math]::Max(3, [Math]::Round($Size * 0.22)) $center = ($Size - 1) / 2.0 $diamondRadius = $Size * 0.36 $maskStride = [Math]::Ceiling($Size / 32.0) * 4 $pixels = New-Object byte[] ($Size * $Size * 4) for ($y = 0; $y -lt $Size; $y++) { for ($x = 0; $x -lt $Size; $x++) { $inside = $true if (!(($x -ge $radius -and $x -lt $Size - $radius) -or ($y -ge $radius -and $y -lt $Size - $radius))) { $cornerX = if ($x -lt $radius) { $radius } else { $Size - $radius - 1 } $cornerY = if ($y -lt $radius) { $radius } else { $Size - $radius - 1 } $dx = $x - $cornerX $dy = $y - $cornerY $inside = (($dx * $dx) + ($dy * $dy)) -le ($radius * $radius) } $diamond = (([Math]::Abs($x - $center) / $diamondRadius) + ([Math]::Abs($y - $center) / $diamondRadius)) -le 1 $target = ((($Size - 1 - $y) * $Size) + $x) * 4 $pixels[$target] = if ($diamond -and $inside) { 255 } else { 200 } $pixels[$target + 1] = if ($diamond -and $inside) { 255 } else { 183 } $pixels[$target + 2] = if ($diamond -and $inside) { 255 } else { 40 } $pixels[$target + 3] = if ($inside) { 255 } else { 0 } } } $stream = [System.IO.MemoryStream]::new() $writer = [System.IO.BinaryWriter]::new($stream) $writer.Write([int]40) $writer.Write([int]$Size) $writer.Write([int]($Size * 2)) $writer.Write([uint16]1) $writer.Write([uint16]32) $writer.Write([int]0) $writer.Write([int]$pixels.Length) $writer.Write([int]0) $writer.Write([int]0) $writer.Write([int]0) $writer.Write([int]0) $writer.Write($pixels) $writer.Write((New-Object byte[] ($maskStride * $Size))) $writer.Flush() $result = $stream.ToArray() $writer.Dispose() $stream.Dispose() return ,$result } [byte[][]]$frames = @($sizes | ForEach-Object { ,(New-LumiIconFrame -Size $_) }) $directorySize = 6 + ($frames.Count * 16) $offset = $directorySize $output = [System.IO.MemoryStream]::new() $binary = [System.IO.BinaryWriter]::new($output) $binary.Write([uint16]0) $binary.Write([uint16]1) $binary.Write([uint16]$frames.Count) for ($index = 0; $index -lt $frames.Count; $index++) { $size = $sizes[$index] $frame = $frames[$index] $encodedSize = if ($size -eq 256) { 0 } else { $size } $binary.Write([byte]$encodedSize) $binary.Write([byte]$encodedSize) $binary.Write([byte]0) $binary.Write([byte]0) $binary.Write([uint16]1) $binary.Write([uint16]32) $binary.Write([int]$frame.Length) $binary.Write([int]$offset) $offset += $frame.Length } foreach ($frame in $frames) { $binary.Write($frame) } $binary.Flush() $directory = Split-Path -Parent $OutputPath [System.IO.Directory]::CreateDirectory($directory) | Out-Null [System.IO.File]::WriteAllBytes($OutputPath, $output.ToArray()) $binary.Dispose() $output.Dispose() Write-Host "Generated Lumi Companion icon: $OutputPath"