Просмотр исходного кода

feat: 应用图标与启动页采用 Yeswash 品牌样式,附 SVG 源文件与一键生成脚本

skyline 2 недель назад
Родитель
Сommit
8ef7aee705
2 измененных файлов с 181 добавлено и 0 удалено
  1. 171 0
      admin-h5/resources/generate-android-icons.ps1
  2. 10 0
      admin-h5/resources/icon.svg

+ 171 - 0
admin-h5/resources/generate-android-icons.ps1

@@ -0,0 +1,171 @@
+# 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'

+ 10 - 0
admin-h5/resources/icon.svg

@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
+  <!-- Yeswash 品牌图标:红底白字(取样自品牌原图 #F31C17) -->
+  <rect width="512" height="512" fill="#F31C17"/>
+  <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="#FFFFFF" textLength="428" lengthAdjust="spacingAndGlyphs">Yeswash</text>
+  </g>
+</svg>