fix(desktop): macOS app icon renders as noise at small sizes (closes #11187)

`icnsutil compose` picks a chunk's OSType from the image size, so the 16px and
32px slots were stored as `icp4`/`icp5`. macOS IconServices decodes those as raw
pixel data rather than PNG, so every consumer that lands on a small
representation drew colored noise -- Raycast's result list, Finder at 16pt, and
Apple's own `iconutil -c iconset`. Spotlight draws the icon large enough to miss
it.

icnsutil also takes the OSType from the file name, so the intermediates are now
named by OSType instead of by size. That drops `icp4`/`icp5` and fills in the
slots the size-driven loop never produced: `ic08` (256) and the retina
`ic11`/`ic12`/`ic13`. Default and nightly get separate scratch directories --
they used to share `mac/` and be told apart by a `*-dev.png` glob, which cannot
survive OSType names.

The slot table avoids `declare -A`: macOS ships bash 3.2, and this is the
section a Mac user is most likely to re-run.

Also replaces `rm -r mac/*` with `rm -rf mac`. `app-icon/mac` is gitignored, so
on a fresh clone that line aborted the whole script under `set -e`.

Verified by building two throwaway `.app` bundles differing only in their icns
and drawing both through `NSWorkspace.icon(forFile:)`: before, 32pt is noise and
16pt is a cyan wash; after, both render the logo. `iconutil -c iconset` decodes
every slot of the regenerated files.

No 16pt slot is shipped. Its modern type, `ic04`, requires raw ARGB -- a PNG
payload under `ic04` is mis-decoded exactly like `icp4` -- and icnsutil needs
Pillow to produce ARGB. macOS synthesizes 16pt from `ic11` instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Elian Doran 2026-08-25 12:26:15 +03:00
parent 9ecc77dd72
commit 9f2d87a0df
No known key found for this signature in database
3 changed files with 24 additions and 14 deletions

View File

@ -39,14 +39,28 @@ for size in "${sizes[@]}"; do
inkscape -w $size -h $size "$source_icon_dir/icon-color.svg" -o "./png/${size}x${size}.png" inkscape -w $size -h $size "$source_icon_dir/icon-color.svg" -o "./png/${size}x${size}.png"
done done
rm -r mac/* # The .icns slots, as "chunk OSType:pixel size" pairs. icnsutil takes the OSType from the file
mkdir -p fakeapp.app # name, so the intermediates must be named by OSType rather than by size: a 16px or 32px PNG named
npx iconsur set fakeapp.app -l -i "png/1024x1024.png" -o "mac/1024x1024.png" -s 0.8 # after its size is stored as icp4/icp5, which macOS IconServices decodes as raw pixel data and
declare -a sizes=("16x16" "32x32" "128x128" "512x512") # draws as colored noise. macOS synthesizes the 16pt slot by downscaling ic11.
for size in "${sizes[@]}"; do icns_slots=( ic11:32 ic12:64 ic07:128 ic13:256 ic08:256 ic09:512 ic10:1024 )
magick "mac/1024x1024.png" -resize "${size}" "mac/${size}.png"
done # Packs one .icns from a 1024x1024 master, using $work as scratch space for the resized slots.
icnsutil compose -f "icon.icns" ./mac/*.png build_icns() {
local master="$1" out="$2" work="$3" slot key px
mkdir -p "$work"
for slot in "${icns_slots[@]}"; do
key=${slot%%:*}
px=${slot##*:}
magick "$master" -resize "${px}x${px}" "$work/${key}.png"
done
icnsutil compose -f "$out" "$work"/ic*.png
}
rm -rf mac
mkdir -p mac fakeapp.app
npx iconsur set fakeapp.app -l -i "png/1024x1024.png" -o "mac/master.png" -s 0.8
build_icns "mac/master.png" "icon.icns" "mac/default"
# Build Mac dev .icns # Build Mac dev .icns
declare -a sizes=("16" "32" "512" "1024") declare -a sizes=("16" "32" "512" "1024")
@ -54,12 +68,8 @@ for size in "${sizes[@]}"; do
inkscape -w $size -h $size "$source_icon_dir/icon-purple.svg" -o "./png/${size}x${size}-dev.png" inkscape -w $size -h $size "$source_icon_dir/icon-purple.svg" -o "./png/${size}x${size}-dev.png"
done done
npx iconsur set fakeapp.app -l -i "png/1024x1024-dev.png" -o "mac/1024x1024-dev.png" -s 0.8 npx iconsur set fakeapp.app -l -i "png/1024x1024-dev.png" -o "mac/master-dev.png" -s 0.8
declare -a sizes=("16x16" "32x32" "128x128" "512x512") build_icns "mac/master-dev.png" "icon-dev.icns" "mac/dev"
for size in "${sizes[@]}"; do
magick "mac/1024x1024-dev.png" -resize "${size}" "mac/${size}-dev.png"
done
icnsutil compose -f "icon-dev.icns" ./mac/*-dev.png
# Build Windows icon # Build Windows icon
magick -background none "$source_icon_dir/icon-color.svg" -define icon:auto-resize=16,32,48,64,128,256 "./icon.ico" magick -background none "$source_icon_dir/icon-color.svg" -define icon:auto-resize=16,32,48,64,128,256 "./icon.ico"