[CmdletBinding()] param( [string] $Root = (Split-Path -Parent $PSScriptRoot) ) $ErrorActionPreference = 'Stop' Add-Type -AssemblyName System.Drawing $sourcePath = Join-Path $Root 'assets\icons\app_icon.png' if (-not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) { throw "App icon source was not found: $sourcePath" } function New-ResizedBitmap { param( [Drawing.Image] $Source, [int] $Width, [int] $Height, [int] $Padding = 0, [Drawing.Color] $Background = [Drawing.Color]::Transparent, [switch] $SharpenSmallAlpha ) $bitmap = [Drawing.Bitmap]::new($Width, $Height, [Drawing.Imaging.PixelFormat]::Format32bppArgb) $graphics = [Drawing.Graphics]::FromImage($bitmap) try { $graphics.CompositingMode = [Drawing.Drawing2D.CompositingMode]::SourceCopy $graphics.CompositingQuality = [Drawing.Drawing2D.CompositingQuality]::HighQuality $graphics.InterpolationMode = [Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic $graphics.PixelOffsetMode = [Drawing.Drawing2D.PixelOffsetMode]::HighQuality $graphics.SmoothingMode = [Drawing.Drawing2D.SmoothingMode]::HighQuality $graphics.Clear($Background) $availableWidth = $Width - (2 * $Padding) $availableHeight = $Height - (2 * $Padding) $scale = [Math]::Min($availableWidth / $Source.Width, $availableHeight / $Source.Height) $drawWidth = [Math]::Max(1, [int][Math]::Round($Source.Width * $scale)) $drawHeight = [Math]::Max(1, [int][Math]::Round($Source.Height * $scale)) $drawX = [int][Math]::Floor(($Width - $drawWidth) / 2) $drawY = [int][Math]::Floor(($Height - $drawHeight) / 2) $attributes = [Drawing.Imaging.ImageAttributes]::new() try { $attributes.SetWrapMode([Drawing.Drawing2D.WrapMode]::TileFlipXY) $graphics.DrawImage( $Source, [Drawing.Rectangle]::new($drawX, $drawY, $drawWidth, $drawHeight), 0, 0, $Source.Width, $Source.Height, [Drawing.GraphicsUnit]::Pixel, $attributes) } finally { $attributes.Dispose() } } finally { $graphics.Dispose() } if ($SharpenSmallAlpha) { for ($y = 0; $y -lt $Height; $y++) { for ($x = 0; $x -lt $Width; $x++) { $color = $bitmap.GetPixel($x, $y) if ($color.A -le 10) { $bitmap.SetPixel($x, $y, [Drawing.Color]::Transparent) } else { $alpha = if ($color.A -ge 244) { 255 } else { [Math]::Min(255, [int](($color.A - 10) * 255 / 234)) } $bitmap.SetPixel($x, $y, [Drawing.Color]::FromArgb($alpha, 0, 114, 255)) } } } } return $bitmap } function Get-PngBytes([Drawing.Bitmap] $Bitmap) { $stream = [IO.MemoryStream]::new() try { $Bitmap.Save($stream, [Drawing.Imaging.ImageFormat]::Png) return $stream.ToArray() } finally { $stream.Dispose() } } function Write-PngIconFile([string] $Path, [hashtable] $Frames) { $orderedSizes = @($Frames.Keys | ForEach-Object { [int] $_ } | Sort-Object) $headerSize = 6 + (16 * $orderedSizes.Count) $offset = $headerSize $stream = [IO.MemoryStream]::new() $writer = [IO.BinaryWriter]::new($stream) try { $writer.Write([uint16] 0) $writer.Write([uint16] 1) $writer.Write([uint16] $orderedSizes.Count) foreach ($size in $orderedSizes) { $bytes = [byte[]] $Frames[$size] $writer.Write([byte] $(if ($size -eq 256) { 0 } else { $size })) $writer.Write([byte] $(if ($size -eq 256) { 0 } else { $size })) $writer.Write([byte] 0) $writer.Write([byte] 0) $writer.Write([uint16] 1) $writer.Write([uint16] 32) $writer.Write([uint32] $bytes.Length) $writer.Write([uint32] $offset) $offset += $bytes.Length } foreach ($size in $orderedSizes) { $writer.Write([byte[]] $Frames[$size]) } $writer.Flush() $directory = Split-Path -Parent $Path if ($directory) { [IO.Directory]::CreateDirectory($directory) | Out-Null } [IO.File]::WriteAllBytes($Path, $stream.ToArray()) } finally { $writer.Dispose() $stream.Dispose() } } function Save-Logo { param( [Drawing.Image] $Source, [string] $Path, [int] $Width, [int] $Height, [int] $Padding, [Drawing.Color] $Background = [Drawing.Color]::Transparent ) $bitmap = New-ResizedBitmap $Source $Width $Height $Padding $Background try { [IO.Directory]::CreateDirectory((Split-Path -Parent $Path)) | Out-Null $bitmap.Save($Path, [Drawing.Imaging.ImageFormat]::Png) } finally { $bitmap.Dispose() } } $loaded = [Drawing.Bitmap]::FromFile($sourcePath) try { $master = [Drawing.Bitmap]::new($loaded) } finally { $loaded.Dispose() } try { $frames = @{} foreach ($size in @(16, 20, 24, 32, 40, 48, 64, 96, 128, 256)) { $frame = New-ResizedBitmap $master $size $size 0 ([Drawing.Color]::Transparent) -SharpenSmallAlpha:($size -le 24) try { $frames[$size] = Get-PngBytes $frame } finally { $frame.Dispose() } } $iconTargets = @( (Join-Path $Root 'assets\icon.ico'), (Join-Path $Root 'assets\icons\app_icon.ico'), (Join-Path $Root 'src\box-winUI\Assets\app_icon.ico'), (Join-Path $Root 'src\YMhut.Box.PluginTauriHost\src-tauri\icons\icon.ico') ) foreach ($target in $iconTargets) { Write-PngIconFile $target $frames } $normalizedSource = Join-Path ([IO.Path]::GetTempPath()) ("ymhut-app-icon-{0}.png" -f [Guid]::NewGuid().ToString('N')) try { $master.Save($normalizedSource, [Drawing.Imaging.ImageFormat]::Png) Copy-Item -LiteralPath $normalizedSource -Destination (Join-Path $Root 'assets\icons\app_icon.png') -Force Copy-Item -LiteralPath $normalizedSource -Destination (Join-Path $Root 'assets\icons\icon.png') -Force } finally { Remove-Item -LiteralPath $normalizedSource -Force -ErrorAction SilentlyContinue } $projectAssets = Join-Path $Root 'src\box-winUI\Assets' Save-Logo $master (Join-Path $projectAssets 'StoreLogo.png') 50 50 5 Save-Logo $master (Join-Path $projectAssets 'Square44x44Logo.png') 44 44 4 Save-Logo $master (Join-Path $projectAssets 'Square150x150Logo.png') 150 150 16 Save-Logo $master (Join-Path $projectAssets 'LockScreenLogo.png') 70 70 8 Save-Logo $master (Join-Path $projectAssets 'Wide310x150Logo.png') 310 150 24 ([Drawing.Color]::FromArgb(255, 245, 246, 247)) } finally { $master.Dispose() } Write-Host 'Generated DPI-aware YMhut Box application icons.'