Files
Futonizer/generate_icon.ps1

175 lines
6.9 KiB
PowerShell

Add-Type -AssemblyName System.Drawing
function New-AnimeEyeBitmap {
param([int]$Size)
$bmp = New-Object System.Drawing.Bitmap($Size, $Size)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
$g.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
$g.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighQuality
$g.Clear([System.Drawing.Color]::Transparent)
$s = $Size
$rect = New-Object System.Drawing.Rectangle(0, 0, $s, $s)
$radius = [Math]::Max(2, [int]($s * 0.22))
# --- rounded-square background gradient (indigo -> violet -> magenta, anime-ish) ---
$path = New-Object System.Drawing.Drawing2D.GraphicsPath
$d = $radius * 2
$path.AddArc(0, 0, $d, $d, 180, 90)
$path.AddArc($s - $d, 0, $d, $d, 270, 90)
$path.AddArc($s - $d, $s - $d, $d, $d, 0, 90)
$path.AddArc(0, $s - $d, $d, $d, 90, 90)
$path.CloseFigure()
$bgBrush = New-Object System.Drawing.Drawing2D.LinearGradientBrush(
$rect,
[System.Drawing.Color]::FromArgb(255, 40, 20, 90),
[System.Drawing.Color]::FromArgb(255, 150, 40, 130),
[System.Drawing.Drawing2D.LinearGradientMode]::ForwardDiagonal)
$g.FillPath($bgBrush, $path)
# subtle top highlight
$topRect = New-Object System.Drawing.Rectangle(0, 0, $s, [int]($s * 0.55))
$topBrush = New-Object System.Drawing.Drawing2D.LinearGradientBrush(
$topRect,
[System.Drawing.Color]::FromArgb(60, 255, 255, 255),
[System.Drawing.Color]::FromArgb(0, 255, 255, 255),
[System.Drawing.Drawing2D.LinearGradientMode]::Vertical)
$oldClip = $g.Clip
$g.SetClip($path)
$g.FillRectangle($topBrush, $topRect)
$g.Clip = $oldClip
# --- anime eye ---
$cx = $s * 0.5
$cy = $s * 0.46
$eyeW = $s * 0.72
$eyeH = $s * 0.46
# white of the eye
$eyePath = New-Object System.Drawing.Drawing2D.GraphicsPath
$eyeRect = New-Object System.Drawing.RectangleF(($cx - $eyeW/2), ($cy - $eyeH/2), $eyeW, $eyeH)
$eyePath.AddEllipse($eyeRect)
$whiteBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(250, 255, 255, 255))
$g.FillPath($whiteBrush, $eyePath)
# upper eyelid line (thick, sweeping) + eyelash accent
$lidPen = New-Object System.Drawing.Pen([System.Drawing.Color]::FromArgb(255, 25, 15, 40), [Math]::Max(1.0, $s * 0.045))
$lidPen.StartCap = [System.Drawing.Drawing2D.LineCap]::Round
$lidPen.EndCap = [System.Drawing.Drawing2D.LineCap]::Round
$lidRect = New-Object System.Drawing.RectangleF(($cx - $eyeW/2), ($cy - $eyeH/2 - $s*0.02), $eyeW, $eyeH*1.1)
$g.DrawArc($lidPen, $lidRect, 195, 150)
$lashX = $cx + $eyeW/2 * 0.92
$lashY = $cy - $eyeH/2 * 0.55
$g.DrawLine($lidPen, $lashX, $lashY, $lashX + $s*0.06, $lashY - $s*0.10)
# iris (gradient blue -> violet with highlight)
$irisSize = $eyeH * 0.95
$irisRect = New-Object System.Drawing.RectangleF(($cx - $irisSize/2), ($cy - $irisSize/2), $irisSize, $irisSize)
$irisBrush = New-Object System.Drawing.Drawing2D.LinearGradientBrush(
$irisRect,
[System.Drawing.Color]::FromArgb(255, 60, 190, 255),
[System.Drawing.Color]::FromArgb(255, 130, 60, 220),
[System.Drawing.Drawing2D.LinearGradientMode]::Vertical)
$g.FillEllipse($irisBrush, $irisRect)
# pupil
$pupilSize = $irisSize * 0.42
$pupilRect = New-Object System.Drawing.RectangleF(($cx - $pupilSize/2), ($cy - $pupilSize/2), $pupilSize, $pupilSize)
$g.FillEllipse([System.Drawing.Brushes]::Black, $pupilRect)
# ring accent inside iris
$ringPen = New-Object System.Drawing.Pen([System.Drawing.Color]::FromArgb(150, 20, 20, 40), [Math]::Max(1.0, $s*0.012))
$ringSize = $irisSize * 0.72
$ringRect = New-Object System.Drawing.RectangleF(($cx - $ringSize/2), ($cy - $ringSize/2), $ringSize, $ringSize)
$g.DrawEllipse($ringPen, $ringRect)
# big specular highlight
$hlSize = $irisSize * 0.32
$hlRect = New-Object System.Drawing.RectangleF(($cx - $irisSize*0.28), ($cy - $irisSize*0.32), $hlSize, $hlSize)
$g.FillEllipse([System.Drawing.Brushes]::White, $hlRect)
# small secondary sparkle
$sp2Size = $irisSize * 0.14
$sp2Rect = New-Object System.Drawing.RectangleF(($cx + $irisSize*0.10), ($cy + $irisSize*0.18), $sp2Size, $sp2Size)
$g.FillEllipse([System.Drawing.Brushes]::White, $sp2Rect)
# --- "subtitle bar" accent motif near the bottom ---
$barY = $s * 0.80
$barH = [Math]::Max(1.0, $s * 0.07)
$barW1 = $s * 0.5
$bar1 = New-Object System.Drawing.RectangleF(($cx - $barW1/2), $barY, $barW1, $barH)
$barBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(235, 255, 255, 255))
$g.FillRectangle($barBrush, $bar1)
$g.Dispose()
return $bmp
}
function Get-PngBytes($bmp) {
$ms = New-Object System.IO.MemoryStream
$bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
return [byte[]]$ms.ToArray()
}
$sizes = @(16, 32, 48, 256)
$pngList = @()
foreach ($sz in $sizes) {
$bmp = New-AnimeEyeBitmap -Size $sz
$bytes = Get-PngBytes $bmp
$pngList += ,@{ Size = $sz; Bytes = $bytes }
$bmp.Dispose()
}
# Save the 256px version separately as a standalone PNG for use as ui:ImageIcon source.
$png256 = ($pngList | Where-Object { $_.Size -eq 256 }).Bytes
[System.IO.File]::WriteAllBytes("Assets/app.png", [byte[]]$png256)
# --- Manually pack a multi-size .ico (ICONDIR + ICONDIRENTRY headers wrapping raw PNG bytes) ---
$icoPath = "Assets/app.ico"
$fs = New-Object System.IO.FileStream($icoPath, [System.IO.FileMode]::Create)
$bw = New-Object System.IO.BinaryWriter($fs)
# ICONDIR: reserved(2)=0, type(2)=1 (icon), count(2)
$bw.Write([UInt16]0)
$bw.Write([UInt16]1)
$bw.Write([UInt16]$pngList.Count)
$headerSize = 6
$entrySize = 16
$offset = $headerSize + ($entrySize * $pngList.Count)
foreach ($entry in $pngList) {
$sz = $entry.Size
$byteLen = $entry.Bytes.Length
$widthByte = if ($sz -ge 256) { 0 } else { $sz }
$heightByte = if ($sz -ge 256) { 0 } else { $sz }
$bw.Write([byte]$widthByte) # width (0 = 256)
$bw.Write([byte]$heightByte) # height (0 = 256)
$bw.Write([byte]0) # color palette
$bw.Write([byte]0) # reserved
$bw.Write([UInt16]1) # color planes
$bw.Write([UInt16]32) # bits per pixel
$bw.Write([UInt32]$byteLen) # size of image data
$bw.Write([UInt32]$offset) # offset of image data
$offset += $byteLen
}
# NOTE: the explicit [byte[]] cast below is required - without it, PowerShell's
# method-overload binding can silently resolve BinaryWriter.Write() to the wrong
# overload and write garbage instead of the actual PNG bytes.
foreach ($entry in $pngList) {
$bw.Write([byte[]]$entry.Bytes)
}
$bw.Flush()
$bw.Close()
$fs.Close()
Write-Output "Icon generated: $icoPath ($((Get-Item $icoPath).Length) bytes)"
Write-Output "PNG generated: Assets/app.png"