
pillow 的 `save(..., format='ico', sizes=...)` 并未忽略你指定的尺寸,而是根据输入图像的宽高比**自动等比缩放**生成图标,导致输出尺寸与预期不符(如 `(16, 16)` 变为 `(16, 15)`)。根本原因是 ico 格式要求每个图标的宽高必须严格匹配原始图像的纵横比——非方形源图将导致所有尺寸按比例压缩。
在 Windows 平台生成标准 .ico 文件时,Pillow 的 ICO 保存器会忠实遵循图标规范:所有嵌入的图标尺寸必须保持与源图像完全一致的宽高比。这意味着:如果你传入一张 256×244 的 PNG(宽高比 ≈ 1.049),那么即使你显式指定 sizes=[(16,16), (32,32), ...],Pillow 也会将每个目标尺寸按 244/256 ≈ 0.953 的比例缩放高度,最终生成 (16,15), (32,30), (256,244) 等尺寸——这正是你在 check.info['sizes'] 中看到的结果。
✅ 正确做法是:确保输入图像为严格正方形(如 256×256)。Pillow 会据此生成完全匹配你声明尺寸的图标组:
from PIL import Image
from io import BytesIO
# ✅ 关键:使用正方形源图(或主动转换为正方形)
source = Image.open("input.png").convert("RGBA")
# 方案1:裁剪为中心正方形
w, h = source.size
min_dim = min(w, h)
left = (w - min_dim) // 2
top = (h - min_dim) // 2
square_img = source.crop((left, top, left + min_dim, top + min_dim))
# 方案2:填充为正方形(推荐,保留全部内容)
background = Image.new('RGBA', (min_dim, min_dim), (0, 0, 0, 0))
x = (min_dim - w) // 2
y = (min_dim - h) // 2
background.paste(source, (x, y))
square_img = background
# 指定目标尺寸(全部为正方形)
icon_sizes = [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64),
(96, 96), (128, 128), (192, 192), (256, 256)]
# 保存 ICO —— 此时 sizes 将被精确应用
with BytesIO() as output:
square_img.save(output, format='ICO', sizes=icon_sizes, bitmap_format="bmp")
ico_data = output.getvalue()
# 验证:用 Pillow 重新打开可查看实际嵌入尺寸
with Image.open(BytesIO(ico_data)) as ico:
print("实际嵌入尺寸:", ico.info.get('sizes', set()))
# 输出: {(16, 16), (24, 24), (32, 32), ..., (256, 256)}⚠️ 注意事项:
- bitmap_format="bmp" 是可选参数(默认即 BMP),仅影响底层编码格式,不影响尺寸逻辑;
- quality=100 和 subsampling=0 对 ICO 无效(ICO 不使用 JPEG 压缩),可安全移除;
- 若需支持透明通道(Alpha),务必使用 'RGBA' 模式打开并保持——Pillow ICO 会自动处理带 Alpha 的 BMP 子图像;
- 不要依赖 img.convert('RGB') 处理 ICO 输入,它会丢弃 Alpha 通道;应优先用 'RGBA' 并在必要时手动合成背景。
? 总结:Pillow 的 ICO 尺寸行为完全符合规范,并非 Bug。解决问题的核心在于预处理输入图像为正方形——这是 Windows 图标设计的通用前提。通过裁剪或填充实现正方形化后,sizes 参数即可精准生效,无需调用 C# DLL 或第三方工具。










