Skip to content

Instantly share code, notes, and snippets.

@Qix-
Created January 26, 2019 01:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Qix-/f4090181e55ea365633da8c3d0ab5249 to your computer and use it in GitHub Desktop.
Save Qix-/f4090181e55ea365633da8c3d0ab5249 to your computer and use it in GitHub Desktop.
Build an ICNS based off a single PNG (ImageMagick) or SVG image (Inkscape)
# LICENSE: CC0 - go nuts.
# Hi :) This is what I used to generate the ICNS for my game, Tide.
# Both input formats (SVG vs PNG) work just fine, but in my experience
# the SVG came out with noticeably better results (although PNG wasn't
# a catastrophe either). The logo for the game was simple enough that
# SVG was indeed an option.
# To use:
#
# make_icns( INPUT "path/to/img.{svg,png}"
# OUTPUT ICNS_PATH )
#
# Then add it as a custom target or use it as a
# dependency somewhere - I give you that option.
#
# For example:
#
# add_custom_target( my-icns ALL DEPENDS "${ICNS_PATH}" )
#
# For the associated utilities:
#
# - PNG: brew install imagemagick
# - SVG: brew cask install inkscape
#
# Enjoy!
function (make_icns_from_png)
cmake_parse_arguments (
ARG
"" # Boolean args
"INPUT;OUTPUT" # List of single-value args
"" # Multi-valued args
${ARGN})
find_program (
convert_exe
NAMES "convert" "convert.exe"
DOC "Path to ImageMagick convert")
if (NOT convert_exe)
message (FATAL_ERROR "Could not find ImageMagick's 'convert' - is ImageMagick installed?")
endif ()
get_filename_component (ARG_INPUT_ABS "${ARG_INPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
get_filename_component (ARG_INPUT_ABS_BIN "${ARG_INPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
get_filename_component (ARG_INPUT_FN "${ARG_INPUT_ABS_BIN}" NAME_WE)
get_filename_component (ARG_INPUT_DIR "${ARG_INPUT_ABS_BIN}" DIRECTORY)
set (sourceimg "${ARG_INPUT_ABS}")
set (basepath "${ARG_INPUT_DIR}/${ARG_INPUT_FN}")
set (output_icns "${basepath}.icns")
set (iconset "${basepath}.iconset")
set (deplist "")
foreach (size IN ITEMS 16 32 128 256 512)
math (EXPR size2x "2 * ${size}")
set (ipath "${iconset}/icon_${size}x${size}.png")
set (ipath2x "${iconset}/icon_${size}x${size}@2x.png")
list (APPEND deplist "${ipath}" "${ipath2x}")
add_custom_command (
OUTPUT "${ipath}"
COMMAND "${convert_exe}" ARGS "${sourceimg}" -resize "${size}x${size}" "${ipath}"
MAIN_DEPENDENCY "${sourceimg}"
COMMENT "ICNS resize: ${ipath}"
VERBATIM)
add_custom_command (
OUTPUT "${ipath2x}"
COMMAND "${convert_exe}" ARGS "${sourceimg}" -resize "${size2x}x${size2x}" "${ipath2x}"
MAIN_DEPENDENCY "${sourceimg}"
COMMENT "ICNS resize: ${ipath2x}"
VERBATIM)
endforeach ()
add_custom_command (
OUTPUT "${output_icns}"
COMMAND iconutil ARGS --convert icns --output "${output_icns}" "${iconset}"
MAIN_DEPENDENCY "${sourceimg}"
DEPENDS ${deplist}
COMMENT "ICNS: ${output_icns}"
VERBATIM)
if (ARG_OUTPUT)
set ("${ARG_OUTPUT}" "${output_icns}" PARENT_SCOPE)
endif ()
endfunction ()
function (make_icns_from_svg)
cmake_parse_arguments (
ARG
"" # Boolean args
"INPUT;OUTPUT" # List of single-value args
"" # Multi-valued args
${ARGN})
set (CMAKE_FIND_APPBUNDLE NEVER) # otherwise, it'll pick up the app bundle and open a shit ton of windows
find_program (
inkscape_exe
NAMES "inkscape" "inkscape.exe"
DOC "Path to Inkscape"
PATHS "/usr/local/bin" "/usr/bin")
message (STATUS "Inkscape path: ${inkscape_exe}")
if (NOT inkscape_exe)
message (FATAL_ERROR "Could not find Inkscape - is it installed?")
endif ()
get_filename_component (ARG_INPUT_ABS "${ARG_INPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
get_filename_component (ARG_INPUT_ABS_BIN "${ARG_INPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
get_filename_component (ARG_INPUT_FN "${ARG_INPUT_ABS_BIN}" NAME_WE)
get_filename_component (ARG_INPUT_DIR "${ARG_INPUT_ABS_BIN}" DIRECTORY)
set (sourceimg "${ARG_INPUT_ABS}")
set (basepath "${ARG_INPUT_DIR}/${ARG_INPUT_FN}")
set (output_icns "${basepath}.icns")
set (iconset "${basepath}.iconset")
set (deplist "")
foreach (size IN ITEMS 16 32 128 256 512)
math (EXPR size2x "2 * ${size}")
set (ipath "${iconset}/icon_${size}x${size}.png")
set (ipath2x "${iconset}/icon_${size}x${size}@2x.png")
list (APPEND deplist "${ipath}" "${ipath2x}")
add_custom_command (
OUTPUT "${ipath}"
COMMAND "${inkscape_exe}" ARGS -z -e "${ipath}" -w ${size} -h ${size} "${sourceimg}"
MAIN_DEPENDENCY "${sourceimg}"
COMMENT "ICNS resize: ${ipath}"
VERBATIM)
add_custom_command (
OUTPUT "${ipath2x}"
COMMAND "${inkscape_exe}" ARGS -z -e "${ipath2x}" -w ${size2x} -h ${size2x} "${sourceimg}"
MAIN_DEPENDENCY "${sourceimg}"
COMMENT "ICNS resize: ${ipath2x}"
VERBATIM)
endforeach ()
add_custom_command (
OUTPUT "${output_icns}"
COMMAND iconutil ARGS --convert icns --output "${output_icns}" "${iconset}"
MAIN_DEPENDENCY "${sourceimg}"
DEPENDS ${deplist}
COMMENT "ICNS: ${output_icns}"
VERBATIM)
if (ARG_OUTPUT)
set ("${ARG_OUTPUT}" "${output_icns}" PARENT_SCOPE)
endif ()
endfunction ()
function (make_icns)
cmake_parse_arguments (
ARG
"" # Boolean args
"INPUT;OUTPUT" # List of single-value args
"" # Multi-valued args
${ARGN})
if (NOT ARG_INPUT)
message (FATAL_ERROR "INPUT is required")
endif ()
if (NOT IS_ABSOLUTE "${ARG_INPUT}")
get_filename_component (ARG_INPUT "${ARG_INPUT}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
endif ()
if (NOT EXISTS "${ARG_INPUT}")
message (FATAL_ERROR "INPUT does not exist: ${ARG_INPUT}")
endif ()
file (RELATIVE_PATH ARG_INPUT "${CMAKE_CURRENT_SOURCE_DIR}" "${ARG_INPUT}")
get_filename_component (ARG_INPUT_EXT "${ARG_INPUT}" EXT)
if ("${ARG_INPUT_EXT}" STREQUAL ".png")
make_icns_from_png (INPUT "${ARG_INPUT}" OUTPUT child_output)
elseif ("${ARG_INPUT_EXT}" STREQUAL ".svg")
make_icns_from_svg (INPUT "${ARG_INPUT}" OUTPUT child_output)
else ()
message (FATAL_ERROR "INPUT must refer to a .png or .svg, but a ${ARG_INPUT_EXT} was provided")
endif ()
if (ARG_OUTPUT)
set ("${ARG_OUTPUT}" "${child_output}" PARENT_SCOPE)
endif ()
endfunction ()
@eduard-sukharev
Copy link

Hi, thanks for sharing this. How do I use it in a standalone mode? provided that I have convert and cmake installed

@Qix-
Copy link
Author

Qix- commented May 2, 2022

What do you mean by standalone mode in this context? :)

@eduard-sukharev
Copy link

by standalone I meant that I have PNG files, convert and cmake installed, how to use this file to convert PNG to ICNS?
What preparations to make to directory structure? How to invoke cmake? What arguments to pass in?
But then I've found the icnsutils package which solved my simple CLI needs with png2icns executable.

@Qix-
Copy link
Author

Qix- commented May 5, 2022

It only supports one image that gets resized, you don't need to do anything except pass in the main image. The output argument is the variable name in the caller scope that receives the final path of the icon file, which you can use in another custom command's list of dependencies.

@till213
Copy link

till213 commented May 28, 2022

Hello Qix,

I am the author of Sky Dolly, an open source flight recorder and replay application for Microsoft Flight Simulator 2020 (MSFS). Sky Dolly essentially

• Records flights done in MSFS, by sampling position, aircraft attitude, engine and other aircraft status variables («simulation variables»)
• Replays the recorded flights in MSFS, by sending back the previously sampled «simulation variables»
• Provides several import and export formats (GPX, IGC, KML, CSV, …)

Your gist to automatically generate application icon sets (*.icns) is used for the macOS build of Sky Dolly (currently Sky Dolly is only really useful on Windows, because MSFS only runs on Windows - but I mainly develop Sky Dolly on macOS (and "cross-check" the builds on Linux with another compiler), with the long-term plan to implement - maybe - a networked "SimConnect" connection between the apps running on different platforms.

Anyway... ;)

Your contribution is mentioned here:

https://github.com/till213/SkyDolly/blob/main/THIRD_PARTY.md

And within in the application itself (embedded in the “About” dialog):

https://github.com/till213/SkyDolly/blob/main/src/UserInterface/res/text/ThirdParty.md

Thank you so much for providing this very useful CMake snippet, it was very helpful and saved me a couple of hours 😊

Best, Oliver

@Qix-
Copy link
Author

Qix- commented May 28, 2022

Hey thanks for letting me know! Cool stuff, appreciate that!

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