From 83c68713d0fc0dd6cc481a04d07cf8e9eb8c509b Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Mon, 3 Oct 2022 20:27:48 +0200 Subject: [PATCH] Iconset generator for macos --- sbapp/assets/icons_macos.py | 315 ++++++++++++++++++++++++++++++++++++ sbapp/assets/icons_macos.sh | 6 + 2 files changed, 321 insertions(+) create mode 100644 sbapp/assets/icons_macos.py create mode 100644 sbapp/assets/icons_macos.sh diff --git a/sbapp/assets/icons_macos.py b/sbapp/assets/icons_macos.py new file mode 100644 index 0000000..cbab2cb --- /dev/null +++ b/sbapp/assets/icons_macos.py @@ -0,0 +1,315 @@ +import sys +import pathlib +import argparse +import subprocess +import platform + +__version_info__ = (1, 2, 0) +__version__ = ".".join(map(str, __version_info__)) + +__copyright__: str = f"Copyright (C) 2018-2022 Declaration of VAR" + +# default iconset files extension, gets the value from original image +ext = ".png" +# programs names +iconutilProgram = "iconutil" +magickProgram = "magick" +sipsProgram = "sips" + + +class IconParameters(): + width = 0 + scale = 1 + + def __init__(self, width, scale): + self.width = width + self.scale = scale + + def getIconName(self): + global ext + scaleString = "" if self.scale == 1 else f"@{self.scale}x" + return f"icon_{self.width}x{self.width}{scaleString}{ext}" + + +def checkProgramInPath(programName, errorMsg=None): + checkResult = subprocess.run( + ["which", programName], + capture_output=True, + text=True + ) + errorMsgString = "" if errorMsg is None else f". {errorMsg}" + if checkResult.returncode != 0: + raise SystemExit( + f"[ERROR] Couldn't find {programName} in your PATH{errorMsgString}" + ) + else: + print(f"Found {programName}: {checkResult.stdout.strip()}") + + +def generateImageConvertingCommand(forSips, originalPicture, ip, iconsetDir): + if not forSips: + return [ + magickProgram, + "convert", + originalPicture, + "-resize", + str(ip.width * ip.scale), + iconsetDir / ip.getIconName() + ] + else: + return [ + sipsProgram, + "-z", + str(ip.width * ip.scale), + str(ip.width * ip.scale), + originalPicture, + "--out", + iconsetDir / ip.getIconName() + ] + + +def main(): + global ext + + argParser = argparse.ArgumentParser( + prog="generate-iconset", + description=" ".join(( + f"%(prog)s\n{__copyright__}\nGenerate", + "an iconset for a Mac OS application", + f"using {iconutilProgram} tool" + )), + formatter_class=argparse.RawDescriptionHelpFormatter, + allow_abbrev=False + ) + argParser.add_argument( + "--version", + action="version", + version=f"%(prog)s {__version__}" + ) + argParser.add_argument( + "image", + metavar="/path/image.png", + help="path to the original image" + ) + argParser.add_argument( + "--out", + metavar="/path/out/", + help=" ".join(( + "path to the output folder, where to put resulting", + ".icns file (default: same folder as original image)" + )) + ) + argParser.add_argument( + "--use-sips", + action='store_true', + help=f"use {sipsProgram} instead of ImageMagick (default: %(default)s)" + ) + argParser.add_argument( + "--force-png", + action='store_true', + help=" ".join(( + "force non-.png original image to be converted", + "to .png (default: %(default)s)" + )) + ) + argParser.add_argument( + "--ignore-non-mac", + action='store_true', + help=" ".join(( + "allows to run the script on any OS, not only on Mac OS", + "(default: %(default)s)" + )) + ) + # argParser.add_argument( + # "--delete-tmp-iconset", + # action='store_true', + # help=" ".join(( + # "delete temporary iconset directory,", + # "if it already exists (default: %(default)s)" + # )) + # ) + cliArgs = argParser.parse_args() + # print(cliArgs) + + if platform.system() != "Darwin": + if not cliArgs.ignore_non_mac: + raise SystemExit( + " ".join(( + "[ERROR] The script is meant to be executed", + f"on Mac OS only, as {iconutilProgram} tool is only available", + "there. You can ignore this condition", + "with --ignore-non-mac" + )) + ) + else: + print( + " ".join(( + "[WARNING] You are running the script not on Mac OS,", + "so it is likely to fail,", + f"unless you have {iconutilProgram} tool installed" + )) + ) + + checkProgramInPath(iconutilProgram) + + if not cliArgs.use_sips: + print("Will use ImageMagick for converting the original image") + checkProgramInPath( + magickProgram, + " ".join(( + "Perhaps, you don't have it installed?", + f"You can also use {sipsProgram} tool instead", + "with --use-sips" + )) + ) + else: + print(f"Will use {sipsProgram} for converting the original image") + print( + " ".join(( + "[WARNING] ImageMagick provides better quality results,", + f"so do consider using it instead of {sipsProgram}. More details:", + "https://decovar.dev/blog/2019/12/12/imagemagick-vs-sips-resize/" + )) + ) + checkProgramInPath(sipsProgram) + + print() + + originalPicture = pathlib.Path(cliArgs.image) + if not (originalPicture.is_file()): + raise SystemExit( + f"[ERROR] There is no such image file: {cliArgs.image}" + ) + print(f"Original image: {originalPicture}") + + fname = pathlib.Path(originalPicture).stem + ext = pathlib.Path(originalPicture).suffix + if ext != ".png": + if not cliArgs.force_png: + print( + " ".join(( + "[WARNING] Original image extension is not .png,", + f"{iconutilProgram} is likely to fail,", + "pass --force-png to avoid that" + )) + ) + else: + ext = ".png" + + # destination path for output + destDir = ( + pathlib.Path(originalPicture).parent + if cliArgs.out is None + else pathlib.Path(cliArgs.out) + ) + if not (destDir.is_dir()): + try: + destDir.mkdir(parents=True) + except Exception as ex: + raise SystemExit( + " ".join(( + "[ERROR] The specified output folder doesn't exist", + f"and could not be created: {cliArgs.out}" + )) + ) + # path to resulting .icns file + resultingIconset = destDir / f"{fname}.icns" + + # path to temporary iconset folder + iconsetDir = pathlib.Path(destDir / f"{fname}.iconset") + if not (iconsetDir.is_dir()): + try: + iconsetDir.mkdir() + except Exception as ex: + raise SystemExit( + " ".join(( + "[ERROR] Could not create temporary", + f"iconset folder: {iconsetDir}" + )) + ) + else: + if False: # cliArgs.delete_tmp_iconset: + # not the best idea to let script delete files on disk + print("[DEBUG] Deleting temporary iconset folder") + else: + raise SystemExit( + " ".join(( + f"[ERROR] Temporary iconset directory ({iconsetDir})", + "already exists, you need to", + "delete it first" + # "either delete it manually", + # "or use --delete-tmp-iconset" + )) + ) + + # https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/app-icon#app-icon-sizes + ListOfIconParameters = [ + IconParameters(16, 1), + IconParameters(16, 2), + IconParameters(32, 1), + IconParameters(32, 2), + IconParameters(128, 1), + IconParameters(128, 2), + IconParameters(256, 1), + IconParameters(256, 2), + IconParameters(512, 1), + IconParameters(512, 2) + ] + + print("Converting images for iconset") + + # generate iconset + currentImage = 0 + for ip in ListOfIconParameters: + currentImage += 1 + convertingResult = subprocess.run( + generateImageConvertingCommand( + cliArgs.use_sips, + originalPicture, + ip, + iconsetDir + ), + capture_output=True, + text=True + ) + if convertingResult.returncode != 0: + raise SystemExit( + f"[ERROR] Conversion failed. {convertingResult.stderr.strip()}" + ) + else: + print(f"{currentImage}/{len(ListOfIconParameters)}...") + # print(f"Generated {ip.getIconName()}") + + print("\nGenerating .icns file...") + # convert iconset folder to .icns file + iconutilResult = subprocess.run( + [ + iconutilProgram, + "-c", + "icns", + iconsetDir, + "-o", + resultingIconset + ], + capture_output=True, + text=True + ) + if iconutilResult.returncode != 0: + raise SystemExit( + " ".join(( + f"[ERROR] {iconutilProgram} could not generate", + f"an iconset. {iconutilResult.stderr.strip()}" + )) + ) + else: + print( + " ".join(( + "[SUCCESS] An iconset was successfully", + f"generated to {resultingIconset}" + )) + ) + raise SystemExit(0) + + +if __name__ == '__main__': + main() diff --git a/sbapp/assets/icons_macos.sh b/sbapp/assets/icons_macos.sh new file mode 100644 index 0000000..c998e5e --- /dev/null +++ b/sbapp/assets/icons_macos.sh @@ -0,0 +1,6 @@ +#!/bin/bash +rm icon.icns +rm -r icon.iconset +python3 icons_macos.py icon_macos.png --out ./ --use-sips +mv icon_macos.icns icon.icns +mv icon_macos.iconset icon.iconset