Created
September 29, 2026 15:56
-
-
Save palaniraja/74877e2f19b9c9872cfbe70f200fe13a to your computer and use it in GitHub Desktop.
Mac app icon generator from SF symbol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Usage: swift scripts/appicon.swift <sf-symbol-name> <out.icns> [#RRGGBB background] | |
| import AppKit | |
| let args = CommandLine.arguments | |
| guard args.count >= 3, let symbol = NSImage(systemSymbolName: args[1], accessibilityDescription: nil) else { | |
| FileHandle.standardError.write("unknown SF Symbol: \(args.count > 1 ? args[1] : "(none)")\n".data(using: .utf8)!) | |
| exit(1) | |
| } | |
| let out = URL(fileURLWithPath: args[2]) | |
| func color(_ hex: String) -> NSColor { | |
| let v = UInt32(hex.trimmingCharacters(in: CharacterSet(charactersIn: "#")), radix: 16) ?? 0x2F6FEB | |
| return NSColor(srgbRed: CGFloat(v >> 16 & 0xff) / 255, green: CGFloat(v >> 8 & 0xff) / 255, blue: CGFloat(v & 0xff) / 255, alpha: 1) | |
| } | |
| let base = color(args.count > 3 ? args[3] : "#2F6FEB") | |
| func render(_ px: Int) -> Data { | |
| let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: px, pixelsHigh: px, bitsPerSample: 8, samplesPerPixel: 4, | |
| hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0)! | |
| NSGraphicsContext.saveGraphicsState() | |
| NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep) | |
| let s = CGFloat(px) | |
| // macOS icon grid: 824/1024 body with ~22.5% corner radius, centred. | |
| let body = NSRect(x: s * 100 / 1024, y: s * 100 / 1024, width: s * 824 / 1024, height: s * 824 / 1024) | |
| let path = NSBezierPath(roundedRect: body, xRadius: body.width * 0.225, yRadius: body.width * 0.225) | |
| NSGradient(starting: base.blended(withFraction: 0.25, of: .white)!, ending: base.blended(withFraction: 0.2, of: .black)!)!.draw(in: path, angle: -90) | |
| let cfg = NSImage.SymbolConfiguration(pointSize: body.width * 0.5, weight: .semibold).applying(.init(paletteColors: [.white])) | |
| let img = symbol.withSymbolConfiguration(cfg)! | |
| let fit = min(body.width * 0.58 / img.size.width, body.height * 0.58 / img.size.height) | |
| let sz = NSSize(width: img.size.width * fit, height: img.size.height * fit) | |
| img.draw(in: NSRect(x: body.midX - sz.width / 2, y: body.midY - sz.height / 2, width: sz.width, height: sz.height)) | |
| NSGraphicsContext.restoreGraphicsState() | |
| return rep.representation(using: .png, properties: [:])! | |
| } | |
| let set = FileManager.default.temporaryDirectory.appendingPathComponent("AppIcon-\(getpid()).iconset") | |
| try FileManager.default.createDirectory(at: set, withIntermediateDirectories: true) | |
| for pt in [16, 32, 128, 256, 512] { | |
| try render(pt).write(to: set.appendingPathComponent("icon_\(pt)x\(pt).png")) | |
| try render(pt * 2).write(to: set.appendingPathComponent("icon_\(pt)x\(pt)@2x.png")) | |
| } | |
| let p = Process() | |
| p.executableURL = URL(fileURLWithPath: "/usr/bin/iconutil") | |
| p.arguments = ["-c", "icns", set.path, "-o", out.path] | |
| try p.run(); p.waitUntilExit() | |
| try? FileManager.default.removeItem(at: set) | |
| exit(p.terminationStatus) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| .PHONY: icon | |
| icon: | |
| @[ -n "$(SYMBOL)" ] || { echo "usage: make icon SYMBOL=<sf-symbol-name> [COLOR=#RRGGBB]"; exit 2; } | |
| swift scripts/appicon.swift "$(SYMBOL)" app/AppIcon.icns $(COLOR) | |
| @echo "Wrote app/AppIcon.icns from $(SYMBOL)." | |
| [ ! -f app/AppIcon.icns ] || cp app/AppIcon.icns "$(DISTDIR)/<YOURAPP>.app/Contents/Resources/" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment