import AppKit import Foundation let output = URL(fileURLWithPath: CommandLine.arguments.dropFirst().first ?? "dist/GrowthLapse.iconset") try FileManager.default.createDirectory(at: output, withIntermediateDirectories: true) struct IconFile { let name: String let size: Int } let files = [ IconFile(name: "icon_16x16.png", size: 16), IconFile(name: "icon_16x16@2x.png", size: 32), IconFile(name: "icon_32x32.png", size: 32), IconFile(name: "icon_32x32@2x.png", size: 64), IconFile(name: "icon_128x128.png", size: 128), IconFile(name: "icon_128x128@2x.png", size: 256), IconFile(name: "icon_256x256.png", size: 256), IconFile(name: "icon_256x256@2x.png", size: 512), IconFile(name: "icon_512x512.png", size: 512), IconFile(name: "icon_512x512@2x.png", size: 1024), ] func drawIcon(size: Int) throws -> Data { let rep = NSBitmapImageRep( bitmapDataPlanes: nil, pixelsWide: size, pixelsHigh: size, bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0 )! NSGraphicsContext.saveGraphicsState() NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep) let scale = CGFloat(size) / 1024 let canvas = NSRect(x: 0, y: 0, width: CGFloat(size), height: CGFloat(size)) NSColor.clear.setFill() canvas.fill() func rect(_ x: CGFloat, _ y: CGFloat, _ w: CGFloat, _ h: CGFloat) -> NSRect { NSRect(x: x * scale, y: y * scale, width: w * scale, height: h * scale) } let bg = NSBezierPath(roundedRect: rect(72, 72, 880, 880), xRadius: 210 * scale, yRadius: 210 * scale) bg.addClip() let gradient = NSGradient(colors: [ NSColor(calibratedRed: 0.06, green: 0.35, blue: 0.90, alpha: 1.0), NSColor(calibratedRed: 0.10, green: 0.72, blue: 0.58, alpha: 1.0) ])! gradient.draw(in: canvas, angle: 38) NSColor(calibratedWhite: 1.0, alpha: 0.16).setFill() NSBezierPath(ovalIn: rect(560, 74, 520, 520)).fill() NSBezierPath(ovalIn: rect(-120, 520, 440, 440)).fill() NSGraphicsContext.restoreGraphicsState() NSGraphicsContext.saveGraphicsState() NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep) let shadow = NSShadow() shadow.shadowColor = NSColor.black.withAlphaComponent(0.24) shadow.shadowBlurRadius = 34 * scale shadow.shadowOffset = NSSize(width: 0, height: -10 * scale) shadow.set() for (index, item) in [ rect(218, 250, 410, 520), rect(306, 214, 410, 520), rect(394, 178, 410, 520) ].enumerated() { let frame = NSBezierPath(roundedRect: item, xRadius: 54 * scale, yRadius: 54 * scale) NSColor(calibratedWhite: 1, alpha: index == 2 ? 0.95 : 0.46).setFill() frame.fill() NSColor(calibratedWhite: 1, alpha: 0.95).setStroke() frame.lineWidth = 10 * scale frame.stroke() } shadow.shadowColor = .clear shadow.set() let photo = NSBezierPath(roundedRect: rect(430, 214, 338, 436), xRadius: 36 * scale, yRadius: 36 * scale) NSColor(calibratedRed: 0.07, green: 0.16, blue: 0.25, alpha: 1).setFill() photo.fill() NSColor(calibratedRed: 0.72, green: 0.92, blue: 1.0, alpha: 1).setFill() NSBezierPath(ovalIn: rect(552, 438, 94, 94)).fill() let body = NSBezierPath(roundedRect: rect(504, 292, 190, 150), xRadius: 82 * scale, yRadius: 82 * scale) body.fill() NSColor(calibratedWhite: 1, alpha: 0.96).setStroke() let curve = NSBezierPath() curve.move(to: NSPoint(x: 210 * scale, y: 270 * scale)) curve.curve( to: NSPoint(x: 822 * scale, y: 760 * scale), controlPoint1: NSPoint(x: 360 * scale, y: 312 * scale), controlPoint2: NSPoint(x: 618 * scale, y: 620 * scale) ) curve.lineWidth = 38 * scale curve.lineCapStyle = .round curve.stroke() for point in [(210, 270), (420, 390), (620, 590), (822, 760)] { NSColor(calibratedWhite: 1, alpha: 1).setFill() NSBezierPath(ovalIn: rect(CGFloat(point.0 - 34), CGFloat(point.1 - 34), 68, 68)).fill() NSColor(calibratedRed: 0.08, green: 0.47, blue: 0.78, alpha: 1).setFill() NSBezierPath(ovalIn: rect(CGFloat(point.0 - 18), CGFloat(point.1 - 18), 36, 36)).fill() } NSGraphicsContext.restoreGraphicsState() guard let data = rep.representation(using: .png, properties: [:]) else { throw NSError(domain: "GrowthLapseIcon", code: 1) } return data } for file in files { let data = try drawIcon(size: file.size) try data.write(to: output.appendingPathComponent(file.name)) }