Skip to content

Instantly share code, notes, and snippets.

@palaniraja
Created September 29, 2026 15:56
Show Gist options
  • Select an option

  • Save palaniraja/74877e2f19b9c9872cfbe70f200fe13a to your computer and use it in GitHub Desktop.

Select an option

Save palaniraja/74877e2f19b9c9872cfbe70f200fe13a to your computer and use it in GitHub Desktop.
Mac app icon generator from SF symbol
// 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)
.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