Papa Labs

不装任何工具,用 PowerShell 给 logo 抠掉白底(含防白边处理)

做本站 header 的时候碰到一个典型小问题:品牌 logo 只有不透明白底的 PNG(24bpp,没有 alpha 通道),放到暗色模式的页面上就是一块刺眼的白色方块。

机器上没有 Photoshop,也不想为这一件事装 ImageMagick —— 其实 Windows 自带的 .NET System.Drawing 就够了。

处理前后对比:白底 PNG 在暗色页面上是一块白斑,处理后背景完全透明

左:原图直接放暗色页面的惨状;右:脚本处理后的效果

思路

  1. 把图片重绘到一张 32bpp(带 alpha)的画布上;
  2. 遍历像素:接近白色的设为全透明;
  3. 关键:边缘像素做渐变透明,否则抠出来一圈白色毛边(halo)。

判断「接近白色」用 RGB 三通道的最小值 m

  • m ≥ 238 → 完全透明(背景);
  • 210 ≤ m < 238 → 按比例给部分透明度(抗锯齿边缘);
  • 其余 → 保持不动(logo 本体)。

代码

性能要点:不要用 GetPixel/SetPixel 逐像素调用(一张图几十万次方法调用,PowerShell 里能慢到分钟级),用 LockBits 把整块像素数据拷出来在字节数组上操作:

Add-Type -AssemblyName System.Drawing

$src = New-Object System.Drawing.Bitmap "logo.png"
$bmp = New-Object System.Drawing.Bitmap $src.Width, $src.Height, `
  ([System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.DrawImage($src, 0, 0, $src.Width, $src.Height)
$g.Dispose(); $src.Dispose()

$rect = New-Object System.Drawing.Rectangle 0, 0, $bmp.Width, $bmp.Height
$data = $bmp.LockBits($rect, 'ReadWrite', $bmp.PixelFormat)
$bytes = New-Object byte[] ($data.Stride * $data.Height)
[System.Runtime.InteropServices.Marshal]::Copy($data.Scan0, $bytes, 0, $bytes.Length)

for ($i = 0; $i -lt $bytes.Length; $i += 4) {
  # BGRA 顺序
  $m = [Math]::Min([Math]::Min([int]$bytes[$i], [int]$bytes[$i+1]), [int]$bytes[$i+2])
  if ($m -ge 238) { $bytes[$i+3] = 0 }
  elseif ($m -ge 210) { $bytes[$i+3] = [byte][Math]::Round(255 * (238 - $m) / 28) }
}

[System.Runtime.InteropServices.Marshal]::Copy($bytes, 0, $data.Scan0, $bytes.Length)
$bmp.UnlockBits($data)
$bmp.Save("logo-transparent.png", [System.Drawing.Imaging.ImageFormat]::Png)
$bmp.Dispose()

两个踩过的点

  • 像素是 BGRA 不是 RGBALockBits 出来的字节顺序是 Blue、Green、Red、Alpha,通道搞反了阈值判断就全错;
  • 背景不是纯白:真实图片的”白底”往往是 #fefefc#f8f9fb 这种近白渐变(特别是 AI 生成或扫描的图),阈值要留余量,别写死 255

暗色模式的后续处理

抠完透明底还有一步:logo 里深色的文字部分在暗色背景上对比度不够。我的做法是把图标和文字分开裁剪,暗色模式下只对文字部分套一层 CSS filter 转成近白色,图标保持品牌原色:

.dark .wordmark img.word {
  filter: brightness(0) invert(0.92);
}

一套下来零安装、全程命令行,本站 header 上的效果就是这个脚本的产物。

← 全部文章