| name | app-icon-rasterize |
| description | Rasterize a designer-authored 1024×1024 SVG into the PNG that Apple's asset catalog wants — no Homebrew dependency, no Cloud service, just `qlmanage` already on the Mac. |
App icon rasterize (SVG → 1024 PNG)
Use when a designer hands off the icon as SVG and you need the PNG that AppIcon.appiconset/Contents.json references.
This is the gap discovered during first-pass icon production on a real project — the SVG → 1024 PNG step was undocumented (only the downstream Pillow Lanczos downscale to the macOS ladder was written up). Each appearance — Light, Dark, Tinted — is a single 1024 universal PNG produced by exactly one rasterize step, documented here. (Tinted is now a standard third appearance alongside Light and Dark; all apps in the reference project ship AppIcon-Tinted.png.)
When to use
- Designer subagent (or human designer) produced a clean
light.svg + dark.svg + tinted.svg matching the icon spec
- You need the matching
AppIcon-Light.png + AppIcon-Dark.png in <app>/Assets.xcassets/AppIcon.appiconset/
- No
rsvg-convert / inkscape / imagemagick and no Homebrew — qlmanage (QuickLook) ships with macOS and is the rasteriser of record here (this skill exists precisely for the no-Homebrew case)
When NOT to use
- The SVG uses
<filter> blurs, <text>, embedded fonts, or other features QuickLook's SVG generator may render unreliably. Re-author the SVG with flat shapes first.
- The SVG already ships as PNG from the designer (e.g. Affinity / Figma export). Just commit the PNG.
macOS ladder reality check
As of Xcode 26 / macOS Sequoia, macOS app icons still require the explicit 16/32/128/256/512 ladder — "Single Size" is iOS-only. (Re-verify against your current Xcode; Apple could add single-size macOS support in a later release.) The Appearances Inspector for macOS targets does not expose a single-asset option. Without the ladder, Xcode emits "AppIcon has N unassigned children" on the macOS target because the universal idiom does not satisfy AppKit's expected slots.
After producing the 1024 master:
SRC=<App>/Assets.xcassets/AppIcon.appiconset/AppIcon-Light.png
DST=<App>/Assets.xcassets/AppIcon-macOS.appiconset
mkdir -p "$DST"
cp "$SRC" "$DST/icon_512x512@2x.png"
sips -Z 512 "$SRC" --out "$DST/icon_256x256@2x.png"
sips -Z 512 "$SRC" --out "$DST/icon_512x512.png"
sips -Z 256 "$SRC" --out "$DST/icon_128x128@2x.png"
sips -Z 256 "$SRC" --out "$DST/icon_256x256.png"
sips -Z 128 "$SRC" --out "$DST/icon_128x128.png"
sips -Z 64 "$SRC" --out "$DST/icon_32x32@2x.png"
sips -Z 32 "$SRC" --out "$DST/icon_32x32.png"
sips -Z 32 "$SRC" --out "$DST/icon_16x16@2x.png"
sips -Z 16 "$SRC" --out "$DST/icon_16x16.png"
sips -Z <max> resizes the longest dimension to <max> (Lanczos-equivalent quality). Each downscale is from the 1024 master, not the previous step.
Project.swift needs the SDK-scoped override so iOS targets keep the universal AppIcon and macOS targets pick up the ladder:
let appTargetSettings: SettingsDictionary = swiftSettings.merging([
"ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon",
"ASSETCATALOG_COMPILER_APPICON_NAME[sdk=macosx*]": "AppIcon-macOS",
]) { _, new in new }
SVG authoring contract — read BEFORE writing the SVG
The designer's SVG must NOT bake rounded corners into the artwork. Apple's compositor applies the squircle mask at render time (iOS Springboard, macOS Dock, every preview surface). Baking corners produces a double-mask look — the icon shows up smaller than its peers with visible inner padding.
Common mistakes that cause this:
<defs>
<clipPath id="iconMask">
<rect width="1024" height="1024" rx="246" ry="246"/>
</clipPath>
</defs>
<g clip-path="url(#iconMask)">
<rect width="1024" height="1024" fill="#FAF8F3"/>
...
</g>
<rect width="1024" height="1024" rx="246" ry="246" fill="#FAF8F3"/>
<rect x="0" y="0" width="1024" height="1024" fill="#FAF8F3"/>
After rasterize, sips -g hasAlpha …png will still report yes because QuickLook writes 8-bit RGBA — but every pixel including the corners must be opaque. Quick check: open the PNG in Preview at 100%, hover the corners — Digital Color Meter should report the background color (e.g. #FAF8F3), not transparency.
This rule applies to Light, Dark, and Tinted variants. There is no platform on which the artwork should pre-apply its own squircle.
Procedure
ls -la ../tmp/<app>-icon-light.svg ../tmp/<app>-icon-dark.svg
qlmanage -t -s 1024 -o ../tmp \
../tmp/<app>-icon-light.svg \
../tmp/<app>-icon-dark.svg \
../tmp/<app>-icon-tinted.svg
mv ../tmp/<app>-icon-light.svg.png \
<App>/Assets.xcassets/AppIcon.appiconset/AppIcon-Light.png
mv ../tmp/<app>-icon-dark.svg.png \
<App>/Assets.xcassets/AppIcon.appiconset/AppIcon-Dark.png
mv ../tmp/<app>-icon-tinted.svg.png \
<App>/Assets.xcassets/AppIcon.appiconset/AppIcon-Tinted.png
file <App>/Assets.xcassets/AppIcon.appiconset/AppIcon-Light.png
sips -g pixelWidth -g pixelHeight <App>/Assets.xcassets/AppIcon.appiconset/AppIcon-Light.png
mkdir -p docs/app-store/icons/<app>/
cp ../tmp/<app>-icon-light.svg docs/app-store/icons/<app>/light.svg
cp ../tmp/<app>-icon-dark.svg docs/app-store/icons/<app>/dark.svg
cp ../tmp/<app>-icon-tinted.svg docs/app-store/icons/<app>/tinted.svg
Verify visually
Open the produced PNGs in Preview.app and confirm:
- Background fill reaches all 4 edges (no transparency at canvas border — Apple's compositor adds the squircle mask)
- Light variant uses paper
#FAF8F3 background
- Dark variant uses a near-black ink background (e.g.
#15171A — replace with your project's dark background hex); pixel-meter the corners against it
- Colors match the spec hex values byte-for-byte
- No anti-alias bleed from
<filter> effects (rare with QuickLook, but inspect the highlight + spark ring areas)
- Both PNGs are 1024×1024 exactly (no off-by-one from QuickLook scaling)
If any check fails, the fix lives in the SVG, not in a post-process pass — re-edit, re-rasterize.
Asset-catalog metadata
AppIcon.appiconset/Contents.json must reference both PNGs as universal idiom (no "platform": "ios"), so Apple auto-adapts the 1024 master across iOS + macOS:
{
"images" : [
{
"filename" : "AppIcon-Light.png",
"idiom" : "universal",
"size" : "1024x1024"
},
{
"appearances" : [
{ "appearance" : "luminosity", "value" : "dark" }
],
"filename" : "AppIcon-Dark.png",
"idiom" : "universal",
"size" : "1024x1024"
}
],
"info" : { "author" : "xcode", "version" : 1 }
}
Tinted entry present — AppIcon-Tinted.png, appearance: luminosity / value: tinted, universal idiom. For iOS only — the universal single PNG does NOT satisfy AppKit's macOS slots. macOS targets must use a separate AppIcon-macOS.appiconset with the explicit size ladder (see "macOS ladder reality check" above).
Why qlmanage and not X
| Tool | Reason rejected |
|---|
rsvg-convert (librsvg) | Requires Homebrew, not installed on this Mac |
inkscape --export-png | Requires Homebrew or .app install |
imagemagick convert | Requires Homebrew |
sips | Does not read SVG input on macOS as of Sequoia |
cairosvg (Python) | Requires Homebrew (Cairo system lib) |
| Pillow | No SVG support natively |
| Swift + WebKit CLI | Works but ~80 LOC of bespoke code for a one-step thumbnail render that qlmanage already does |
| Browser screenshot | Manual, unreproducible across machines |
qlmanage ships with macOS, takes one command, outputs the exact PNG shape Apple wants. The trade-off is that QuickLook's SVG generator may diverge slightly from full-spec SVG 1.1 — keep the SVG simple (flat shapes, no filters, no text) and the output is faithful.
Companion skill
The macOS size-ladder downscale workflow is documented in the "macOS ladder reality check" section above; it is superseded by the universal single-PNG approach for new projects, but remains useful as a reference for older Xcode targets that still require the explicit size ladder.