macOS Dock 图标大白底:Squircle 和留白必须烘焙进素材
给桌面应用打包完,一打开 Dock 发现图标长这样:蓝色 logo 端端正正地坐在一个白色正方形上,四个角还带着明显的白色三角。更难看的是,整个图形顶边到底边无缝铺满,完全没有 macOS 图标该有的透气感——一眼看出是从 iOS 移过来的。
找了半天,源 PNG 的四个角明明是透明的,为什么生成出来的 icon.icns 却是白的?
现象
- Dock / Finder / Cmd-Tab 里,图标四角出现白色不透明区域
- 图标形状是 iOS 风格的全出血圆角矩形,填满整个图标格,没有边距
- 用 Pillow 验证角像素:
from PIL import Image
Image.open("icon.png").convert("RGBA").getpixel((0, 0))
# 输出 (255, 255, 255, 255) ← 白色不透明,bug 就在这里根因
有两个互相叠加的根因:
第一:macOS 不会自动遮罩 Dock 图标。
iOS 在显示时会把图标裁剪成圆角矩形;macOS 完全不做这件事,.icns 里是什么形状,Dock 就渲染什么形状。Squircle(超椭圆)、透明留白、软阴影——这三样全都必须烘焙进图标素材本身。
第二:图标生成工具悄悄把透明通道合并到了白底上。
不管是 ImageMagick 的 -flatten / -background white,还是 sips,还是某些旧版图标生成脚本,都会在写出图标时把带透明度的源文件与白色背景做一次 composite,结果:源文件角是透明的,生成物角是白色不透明的。这个过程不报任何警告。
iOS 会在展示时把方形图标裁成圆角,所以你可以交一张全出血的方图。macOS 完全不做这步——.icns 里是方的,Dock 里就是方的。留白、Squircle、阴影,一个都不能省,得写进文件。
正解
为 macOS 单独制作一张 1024×1024 的主图,规格:
- 内容区 824×824 居中(四边各留 100px 透明边距)
- 圆角形状:超椭圆 / Squircle,指数 n≈5(Apple 原生规格)
- 底部叠一层 18% 不透明度的软阴影,控制在下边距内
- 四角保持完全透明(alpha=0)
用 Pillow + iconutil 生成,绝对不用 -flatten 或任何会合并透明通道的工具:
from PIL import Image, ImageFilter
BLUE = (59, 91, 219, 255)
CANVAS = 1024
SS = 4 # 4× 超采样,LANCZOS 缩小后边缘抗锯齿
C = CANVAS * SS
CONTENT = int(824 * SS)
off = (C - CONTENT) // 2
def squircle(size, n=5.0):
m = Image.new("L", (size, size), 0)
px = m.load()
h = size / 2
for y in range(size):
v = abs((y + 0.5 - h) / h) ** n
if v > 1:
continue
for x in range(size):
if abs((x + 0.5 - h) / h) ** n + v <= 1:
px[x, y] = 255
return m
mask = squircle(CONTENT)
# 超椭圆底色
shape = Image.new("RGBA", (C, C), (0, 0, 0, 0))
shape.paste(Image.new("RGBA", (CONTENT, CONTENT), BLUE), (off, off), mask)
# logo 叠上去
src = Image.open("source-1024.png").convert("RGBA").resize((CONTENT, CONTENT), Image.LANCZOS)
g = Image.new("RGBA", (C, C), (0, 0, 0, 0))
g.paste(src, (off, off), src)
shape = Image.alpha_composite(shape, g)
# 裁回超椭圆轮廓
clip = Image.new("L", (C, C), 0)
clip.paste(mask, (off, off))
shape.putalpha(Image.composite(shape.getchannel("A"), Image.new("L", (C, C), 0), clip))
# 软阴影(向下偏 8px,18% 不透明度)
sa = Image.new("L", (C, C), 0)
sa.paste(mask, (off, off + 8 * SS))
sa = sa.filter(ImageFilter.GaussianBlur(16 * SS)).point(lambda a: int(a * 0.18))
sh = Image.new("RGBA", (C, C), (0, 0, 0, 0))
sh.putalpha(sa)
# 合成并缩回 1024
result = Image.alpha_composite(sh, shape)
result.resize((CANVAS, CANVAS), Image.LANCZOS).save("source-macos-1024.png")然后用这张 macOS 专属主图降采样为各尺寸,再交给 iconutil 打包:
iconutil -c icns macos.iconset -o icon.icns验证每个输出文件的角:
Image.open("icon.png").convert("RGBA").getpixel((0, 0))
# 必须是 (0, 0, 0, 0)源文件分平台:source-1024.png(全出血,给 iOS/Android/Windows 用)和 source-macos-1024.png(Squircle + 100px 留白 + 阴影,只给 macOS 用)。macOS 的 .icns 和各尺寸 PNG 全部从后者降采样,保留 alpha 通道。
排查补充
- 改完图标文件后必须重新构建应用(Tauri
tauri build/ Electronelectron-builder),.icns是打包进.app/Contents/Resources/的,直接替换源文件对 Dock 没有任何效果。 - 重新打包后 Dock 依然显示旧图标,大概率是 macOS 图标缓存:
sudo rm -rf /Library/Caches/com.apple.iconservices.store && killall Dock一句话外卖
macOS 图标的 Squircle、留白、阴影不是运行时效果,而是你交给系统的文件本身就得长这样;任何会合并 alpha 通道的生成步骤都会把透明角变成白角。