// Renders Snapshot's app icon at every size an .iconset needs. // // Drawn rather than designed so the icon is reproducible from source and has no // binary asset to keep in the repository. SF Symbols are deliberately not used: // Apple's licence forbids them in app icons. // // The mark is three cards stacked along a diagonal — successive snapshots of the // same thing, receding into the past. Cards rather than horizontal bars: three // equal bars are the universal hamburger-menu glyph and read as a menu, not a // backup. The front card is fully opaque so it occludes the ones behind cleanly // instead of blending into mush at small sizes. import AppKit import CoreGraphics import Foundation import ImageIO import UniformTypeIdentifiers let sizes: [(name: String, pixels: Int)] = [ ("icon_16x16", 16), ("icon_16x16@2x", 32), ("icon_32x32", 32), ("icon_32x32@2x", 64), ("icon_128x128", 128), ("icon_128x128@2x", 256), ("icon_256x256", 256), ("icon_256x256@2x", 512), ("icon_512x512", 512), ("icon_512x512@2x", 1024), ] func draw(pixels: Int) -> CGImage? { let side = CGFloat(pixels) let scale = side / 1024 guard let context = CGContext( data: nil, width: pixels, height: pixels, bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpace(name: CGColorSpace.sRGB)!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil } context.setShouldAntialias(true) context.interpolationQuality = .high // macOS icons sit inset within the canvas rather than filling it. let inset: CGFloat = 100 * scale let plate = CGRect(x: inset, y: inset, width: side - inset * 2, height: side - inset * 2) let radius = plate.width * 0.2246 // the Big Sur corner ratio context.saveGState() context.addPath(CGPath(roundedRect: plate, cornerWidth: radius, cornerHeight: radius, transform: nil)) context.clip() let gradient = CGGradient( colorsSpace: CGColorSpace(name: CGColorSpace.sRGB)!, colors: [ CGColor(red: 0.30, green: 0.72, blue: 0.76, alpha: 1), CGColor(red: 0.16, green: 0.36, blue: 0.68, alpha: 1), ] as CFArray, locations: [0, 1])! context.drawLinearGradient( gradient, start: CGPoint(x: plate.midX, y: plate.maxY), end: CGPoint(x: plate.midX, y: plate.minY), options: []) context.restoreGState() let card: CGFloat = 400 * scale let cornerRadius = card * 0.22 let step: CGFloat = 78 * scale // Three positions along a diagonal, centred as a group on the plate. let spread = step * 2 let originX = plate.midX - card / 2 - spread / 2 let originY = plate.midY - card / 2 + spread / 2 // Back to front, so the opaque front card covers the ones behind it. for (index, opacity) in [0.45, 0.70, 1.0].enumerated() { let offset = CGFloat(2 - index) let rect = CGRect( x: originX + step * offset, y: originY - step * offset, width: card, height: card) context.setFillColor(CGColor(red: 1, green: 1, blue: 1, alpha: opacity)) context.addPath( CGPath( roundedRect: rect, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)) context.fillPath() } return context.makeImage() } let output = URL(fileURLWithPath: CommandLine.arguments[1], isDirectory: true) try? FileManager.default.createDirectory(at: output, withIntermediateDirectories: true) for entry in sizes { guard let image = draw(pixels: entry.pixels) else { FileHandle.standardError.write(Data("could not render \(entry.name)\n".utf8)) exit(1) } let url = output.appendingPathComponent("\(entry.name).png") guard let destination = CGImageDestinationCreateWithURL( url as CFURL, UTType.png.identifier as CFString, 1, nil) else { exit(1) } CGImageDestinationAddImage(destination, image, nil) CGImageDestinationFinalize(destination) } print("rendered \(sizes.count) icon sizes")