Skip to content

Instantly share code, notes, and snippets.

@nberlette
Last active June 27, 2025 05:01
Show Gist options
  • Save nberlette/66f6e8104ab50386da41a57204d79c00 to your computer and use it in GitHub Desktop.
Save nberlette/66f6e8104ab50386da41a57204d79c00 to your computer and use it in GitHub Desktop.
Apple .icns conversion helper
#!/usr/bin/env bash
# helper function for constructing escape sequences
# this does NOT include the CSI character by default.
# for that, see the [`csi`] function below this.
function esc() {
local -r escape="\x1b"
local -a args=()
local delim=";" prefix="" suffix="" code=""
# parse the arguments:
# - allow overrides for prefix, delim, and suffix.
# - e.g. `--prefix="["`, `-d,` or `--delim=,`, `--suffix=m]`
# - !!! equals sign is optional, e.g. `--prefix [`
# - short options are allowed, e.g. `-p[`, `-d,`, `-s]`
# - all remaining arguments are treated as codes.
# - respect the `--` argument terminator to indicate the end of options.
# - respect the order of all positional arguments.
while [[ $# -gt 0 ]]; do
local arg="$1"
# end of options, break the loop
[[ "$arg" == "--" ]] && break;
# handle options
case "$arg" in
# handle short opts, and long options with equals sign
(--prefix=*|-p*) prefix="${arg#*=}" ;;
(--prefix|-p) shift; prefix="$1"; shift ;;
(--delim=*|-d*) delim="${arg#*=}" ;;
(--delim|-d) shift; delim="$1"; shift ;;
(--suffix=*|-s*) suffix="${arg#*=}" ;;
(--suffix|-s) shift; suffix="$1"; shift ;;
(--) shift; continue;; # end of options
(--*|-*) echo "Unknown option: $arg" >&2; return 1 ;;
(*) {
# if the argument is not an option, treat it as a code
[[ "$arg" =~ ^[0-9]+$ ]] && code+="${arg}" || {
# if the argument is not a number, treat it as a string
# and escape it to remove non-pri ntable characters
if [[ -n "$code" ]]; then
code+="$delim"
fi
}
code+="$(printf '%s' "$arg" | sed 's/[^[:print:]]//g')"
continue
} ;;
(*) args+=("$arg")
;;
esac
shift
done
}
function main () {
# show the usage page if no args are given, or if passed any derivation of "-h" "--help" "-?" etc
if [ $# -eq 0 ] || [[ "$1" =~ ^[-]{0,2}(h(elp)?|[?])$ ]]; then
local bold undl ital dark reset
bold=$(printf '\033[1m')
dark=$(printf '\033[2m')
ital=$(printf '\033[3m')
undl=$(printf '\033[4m')
reset=$(printf '\033[0m')
cat<<EOL
${bold}icns.sh${reset}${dark} - automated macOS .icns conversion helper${reset}
${bold}${undl}USAGE${reset}
./icns.sh <${bold}${undl}${ital}source.png${reset}> <${bold}${undl}${ital}destination.icns${reset}>
${bold}${undl}SUMMARY${reset}
Resizes ${bold}${undl}${ital}source.png${reset} to the required sizes, formatted into one file,
output to ${bold}${undl}${ital}destination.icns${reset} in Apple .icns, in these resolutions:
16x16 32x32 128x128 256x256 512x512
16x16${dark}@2x${reset} 32x32${dark}@2x${reset} 128x128${dark}@2x${reset} 256x256${dark}@2x${reset} 512x512${dark}@2x${reset}
${bold}${undl}RECOMMENDATIONS${reset}
o Use a ${bold}${undl}${ital}source.png${reset} >= 1024x1024 resolution.
o Use absolute paths for ${bold}${undl}${ital}source.png${reset} and ${bold}${undl}${ital}destination.icns${reset}
${dark}(reduces the chance of a file mishap during conversion)${reset}
${bold}${undl}LICENSE${reset}: MIT © ${ital}Nicholas Berlette${reset} <${dark}${ital}${undl}https://github.com/nberlette${reset}>
EOL
# non-zero exit code if no arguments are provided
[ $# = 0 ] && return 1 || return 0;
else
# main program
local input output iconset
input=${1:-"./AppIcon.png"}
output=${2:-"./icon.icns"}
iconset="$(dirname "$output")/$(basename "$output" | cut -d. -f1).iconset"
# outputpng="$(dirname "$output")/icon.png"
mkdir -p "$iconset" >&/dev/null
local xy
for xy in 16 32 128 256 512; do
sips -z $xy $xy "$input" --out "${iconset}/icon_${xy}x${xy}.png" > /dev/null
sips -z $((xy * 2)) $((xy * 2)) "$input" --out "${iconset}/icon_${xy}x${xy}@2x.png" > /dev/null
done
iconutil -c icns "$iconset" && rm -rf "$iconset"
fi
}
{ main "$@" && unset -f main; } || exit $?;
@nberlette
Copy link
Author

nberlette commented Mar 24, 2022

USAGE

    ./icns.sh <source.png> <destination.icns>

SUMMARY

    Resizes source.png to the required sizes, formatted into one file,
    output to destination.icns in Apple .icns, in these resolutions:

    16x16       32x32       128x128       256x256       512x512
    16x16@2x    32x32@2x    128x128@2x    256x256@2x    512x512@2x

RECOMMENDATIONS

    o Use a source.png >= 1024x1024 resolution.
    o Use absolute paths for source.png and destination.icns

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment