| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- # Generate Android launcher icons & splash from resources/icon.svg
- # Requires: Edge (headless SVG rendering) + .NET System.Drawing (scale/clip)
- # Usage: powershell -ExecutionPolicy Bypass -File generate-android-icons.ps1
- # Re-run this script after modifying icon.svg to refresh all Android assets.
- $ErrorActionPreference = 'Stop'
- Add-Type -AssemblyName System.Drawing
- $root = Split-Path -Parent $MyInvocation.MyCommand.Path # admin-h5/resources
- $android = Join-Path $root '..\android\app\src\main\res'
- $edge = 'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
- $tmp = Join-Path $env:TEMP 'yeswash_icons'
- New-Item -ItemType Directory -Force $tmp | Out-Null
- # Text-only master (same layout as icon.svg, no background)
- # Rendered as BLACK text on TRANSPARENT background; converted to white-on-transparent below
- $textSvg = @'
- <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 512 512">
- <g transform="translate(54.4,0) skewX(-12)">
- <text x="256" y="300" text-anchor="middle" font-family="'Arial Black',Arial,sans-serif" font-weight="900" font-size="123" fill="#000000" textLength="428" lengthAdjust="spacingAndGlyphs">Yeswash</text>
- </g>
- </svg>
- '@
- Set-Content -Path (Join-Path $tmp 'text.svg') -Value $textSvg -Encoding ASCII
- Copy-Item (Join-Path $root 'icon.svg') (Join-Path $tmp 'icon.svg') -Force
- function Wait-File($path, $sec = 20) {
- $deadline = (Get-Date).AddSeconds($sec)
- while ((Get-Date) -lt $deadline) {
- if ((Test-Path $path) -and ((Get-Item $path).Length -gt 0)) { return $true }
- Start-Sleep -Milliseconds 500
- }
- return $false
- }
- Remove-Item "$tmp\icon.png", "$tmp\text.png" -Force -ErrorAction SilentlyContinue
- & $edge --headless=new --disable-gpu --hide-scrollbars --default-background-color=00000000 --user-data-dir="$tmp\edge1" --window-size=512,512 --screenshot="$tmp\icon.png" "file:///$(($tmp -replace '\\','/'))/icon.svg" 2>$null
- if (-not (Wait-File "$tmp\icon.png")) { throw 'icon master render failed' }
- & $edge --headless=new --disable-gpu --hide-scrollbars --default-background-color=00000000 --user-data-dir="$tmp\edge2" --window-size=1024,1024 --screenshot="$tmp\text.png" "file:///$(($tmp -replace '\\','/'))/text.svg" 2>$null
- if (-not (Wait-File "$tmp\text.png")) { throw 'text master render failed' }
- Start-Sleep -Milliseconds 500
- $iconBmp = [System.Drawing.Bitmap]::FromFile("$tmp\icon.png") # 512 full logo on red
- $textBmp = [System.Drawing.Bitmap]::FromFile("$tmp\text.png") # 1024 black text on white (text width 856/1024)
- # Sanity check: master must contain black text pixels (guards against stale/blank renders)
- $probe = New-Object System.Drawing.Bitmap($textBmp, 128, 128)
- $hasInk = $false
- for ($y = 0; $y -lt 128 -and -not $hasInk; $y++) {
- for ($x = 0; $x -lt 128; $x++) {
- if ($probe.GetPixel($x, $y).R -lt 100) { $hasInk = $true; break }
- }
- }
- $probe.Dispose()
- if (-not $hasInk) { throw 'text master is blank - headless render failed' }
- # Convert black-on-white master to white-on-transparent (alpha = 255 - luminance)
- # Pixel-level conversion via inline C# (LockBits) - deterministic, no ColorMatrix quirks
- Add-Type -TypeDefinition @"
- using System;
- using System.Drawing;
- using System.Drawing.Imaging;
- using System.Runtime.InteropServices;
- public static class MaskUtil {
- public static Bitmap WhiteMask(Bitmap src) {
- Bitmap outBmp = new Bitmap(src.Width, src.Height, PixelFormat.Format32bppArgb);
- Rectangle rect = new Rectangle(0, 0, src.Width, src.Height);
- BitmapData data = src.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
- BitmapData odata = outBmp.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
- int len = data.Stride * src.Height;
- byte[] buf = new byte[len];
- Marshal.Copy(data.Scan0, buf, 0, len);
- for (int i = 0; i < len; i += 4) { // BGRA little-endian
- int lum = (buf[i+2]*299 + buf[i+1]*587 + buf[i]*114) / 1000;
- int a = buf[i+3] * (255 - lum) / 255; // source alpha * (1 - luminance)
- buf[i] = 255; buf[i+1] = 255; buf[i+2] = 255;
- buf[i+3] = (byte)a; // text -> opaque white, any background -> transparent
- }
- Marshal.Copy(buf, 0, odata.Scan0, len);
- src.UnlockBits(data);
- outBmp.UnlockBits(odata);
- return outBmp;
- }
- }
- "@ -ReferencedAssemblies System.Drawing
- $textMask = [MaskUtil]::WhiteMask($textBmp)
- $textBmp.Dispose()
- $textBmp = $textMask # 1024 white text on transparent (text width 856/1024)
- function New-Canvas([int]$w, [int]$h) {
- $bmp = New-Object System.Drawing.Bitmap($w, $h)
- $g = [System.Drawing.Graphics]::FromImage($bmp)
- $g.InterpolationMode = 'HighQualityBicubic'
- $g.SmoothingMode = 'HighQuality'
- $g.PixelOffsetMode = 'HighQuality'
- return @($bmp, $g)
- }
- function Save-Png($bmp, $path) {
- $dir = Split-Path -Parent $path
- if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Force $dir | Out-Null }
- $bmp.Save($path, [System.Drawing.Imaging.ImageFormat]::Png)
- }
- # 1) Square launcher icons (full logo)
- $launcher = @{ 'mipmap-mdpi'=48; 'mipmap-hdpi'=72; 'mipmap-xhdpi'=96; 'mipmap-xxhdpi'=144; 'mipmap-xxxhdpi'=192 }
- foreach ($k in $launcher.Keys) {
- $s = $launcher[$k]
- $bmp, $g = New-Canvas $s $s
- $g.DrawImage($iconBmp, 0, 0, $s, $s)
- $g.Dispose()
- Save-Png $bmp (Join-Path $android "$k\ic_launcher.png")
- $bmp.Dispose()
- Write-Output "ic_launcher: $k ${s}x${s}"
- }
- # 2) Round launcher icons (ellipse brush clip, anti-aliased edge)
- foreach ($k in $launcher.Keys) {
- $s = $launcher[$k]
- $bmp, $g = New-Canvas $s $s
- $g.Clear([System.Drawing.Color]::Transparent)
- $mid = New-Object System.Drawing.Bitmap($s, $s)
- $mg = [System.Drawing.Graphics]::FromImage($mid)
- $mg.InterpolationMode = 'HighQualityBicubic'
- $mg.DrawImage($iconBmp, 0, 0, $s, $s)
- $mg.Dispose()
- $brush = New-Object System.Drawing.TextureBrush($mid)
- $brush.WrapMode = 'Clamp'
- $g.SmoothingMode = 'HighQuality'
- $g.FillEllipse($brush, 0, 0, $s, $s)
- $brush.Dispose(); $mid.Dispose(); $g.Dispose()
- Save-Png $bmp (Join-Path $android "$k\ic_launcher_round.png")
- $bmp.Dispose()
- Write-Output "ic_launcher_round: $k ${s}x${s}"
- }
- # 3) Adaptive icon foreground (transparent, text scaled into safe zone ~60% of canvas)
- $fg = @{ 'mipmap-mdpi'=108; 'mipmap-hdpi'=162; 'mipmap-xhdpi'=216; 'mipmap-xxhdpi'=324; 'mipmap-xxxhdpi'=432 }
- foreach ($k in $fg.Keys) {
- $s = $fg[$k]
- $sf = (0.60 * $s) / 856.0
- $d = [int][Math]::Round(1024 * $sf)
- $off = [int][Math]::Round(($s - $d) / 2.0)
- $bmp, $g = New-Canvas $s $s
- $g.DrawImage($textBmp, $off, $off, $d, $d)
- $g.Dispose()
- Save-Png $bmp (Join-Path $android "$k\ic_launcher_foreground.png")
- $bmp.Dispose()
- Write-Output "ic_launcher_foreground: $k ${s}x${s}"
- }
- # 4) Splash screens (red background + white text ~58% of short side, keep original sizes)
- $splashDirs = Get-ChildItem $android -Directory | Where-Object { $_.Name -eq 'drawable' -or $_.Name -like 'drawable-land-*' -or $_.Name -like 'drawable-port-*' }
- foreach ($dir in $splashDirs) {
- $f = Join-Path $dir.FullName 'splash.png'
- if (-not (Test-Path $f)) { continue }
- $probe = [System.Drawing.Image]::FromFile($f); $w = $probe.Width; $h = $probe.Height; $probe.Dispose()
- $bmp, $g = New-Canvas $w $h
- $g.Clear([System.Drawing.Color]::FromArgb(0xF3, 0x1C, 0x17))
- $sf = (0.58 * [Math]::Min($w, $h)) / 856.0
- $d = [int][Math]::Round(1024 * $sf)
- $offX = [int][Math]::Round(($w - $d) / 2.0)
- $offY = [int][Math]::Round(($h - $d) / 2.0)
- $g.DrawImage($textBmp, $offX, $offY, $d, $d)
- $g.Dispose()
- Save-Png $bmp $f
- $bmp.Dispose()
- Write-Output "splash: $($dir.Name) ${w}x${h}"
- }
- $iconBmp.Dispose(); $textBmp.Dispose()
- Write-Output 'DONE'
|