| 1 | #!/usr/bin/swift |
| 2 | // 🤖 Regenerates a Mac dev-alt icon set from the canonical app icon: the |
| 3 | // SAME purple "11" tile, hue-rotated so a dev build is unmistakable in the |
| 4 | // Dock without inventing a new mark (CLAUDE.md: never hand-roll a |
| 5 | // substitute glyph). Two variants: "dev" (orange, the default) and "live" |
| 6 | // (green — the convention for a dev build aimed at PRODUCTION home, so two |
| 7 | // running alts with different aims never share an icon). Run from the repo |
| 8 | // root: |
| 9 | // |
| 10 | // swift tools/make-dev-icon.swift # AppIconDev (orange) |
| 11 | // swift tools/make-dev-icon.swift live # AppIconDevLive (green) |
| 12 | // |
| 13 | // Committed output; re-run only if the canonical icon changes. |
| 14 | import AppKit |
| 15 | import CoreImage |
| 16 | |
| 17 | // Purple sits ~253°; orange ~30° (-220°), green ~130° (-123°). White and |
| 18 | // near-white (the "11") are hue-less, so they ride through untouched. |
| 19 | let variants: [String: (set: String, angle: Double)] = [ |
| 20 | "dev": ("AppIconDev", -220.0), |
| 21 | "live": ("AppIconDevLive", -123.0), |
| 22 | ] |
| 23 | let name = CommandLine.arguments.count > 1 ? CommandLine.arguments[1] : "dev" |
| 24 | guard let variant = variants[name] else { |
| 25 | fatalError("unknown variant \(name) — expected: \(variants.keys.sorted().joined(separator: ", "))") |
| 26 | } |
| 27 | |
| 28 | let src = "ios/LChatMac/Assets.xcassets/AppIcon.appiconset/icon_1024.png" |
| 29 | let outDir = "ios/LChatMac/Assets.xcassets/\(variant.set).appiconset" |
| 30 | |
| 31 | guard let image = CIImage(contentsOf: URL(fileURLWithPath: src)) else { |
| 32 | fatalError("can't read \(src) — run from the repo root") |
| 33 | } |
| 34 | |
| 35 | let hue = CIFilter(name: "CIHueAdjust", parameters: [ |
| 36 | kCIInputImageKey: image, |
| 37 | kCIInputAngleKey: NSNumber(value: variant.angle * .pi / 180.0), |
| 38 | ])! |
| 39 | guard let tinted = hue.outputImage else { fatalError("hue adjust failed") } |
| 40 | |
| 41 | let ctx = CIContext() |
| 42 | guard let full = ctx.createCGImage(tinted, from: tinted.extent) else { |
| 43 | fatalError("render failed") |
| 44 | } |
| 45 | |
| 46 | try? FileManager.default.createDirectory(atPath: outDir, withIntermediateDirectories: true) |
| 47 | |
| 48 | for size in [16, 32, 64, 128, 256, 512, 1024] { |
| 49 | let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: size, pixelsHigh: size, |
| 50 | bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, |
| 51 | isPlanar: false, colorSpaceName: .deviceRGB, |
| 52 | bytesPerRow: 0, bitsPerPixel: 0)! |
| 53 | let nsCtx = NSGraphicsContext(bitmapImageRep: rep)! |
| 54 | NSGraphicsContext.saveGraphicsState() |
| 55 | NSGraphicsContext.current = nsCtx |
| 56 | nsCtx.cgContext.interpolationQuality = .high |
| 57 | nsCtx.cgContext.draw(full, in: CGRect(x: 0, y: 0, width: size, height: size)) |
| 58 | NSGraphicsContext.restoreGraphicsState() |
| 59 | let png = rep.representation(using: .png, properties: [:])! |
| 60 | try! png.write(to: URL(fileURLWithPath: "\(outDir)/icon_\(size).png")) |
| 61 | print("wrote \(outDir)/icon_\(size).png") |
| 62 | } |
| 63 | |
| 64 | let contents = """ |
| 65 | { |
| 66 | "images" : [ |
| 67 | { "idiom" : "mac", "size" : "16x16", "scale" : "1x", "filename" : "icon_16.png" }, |
| 68 | { "idiom" : "mac", "size" : "16x16", "scale" : "2x", "filename" : "icon_32.png" }, |
| 69 | { "idiom" : "mac", "size" : "32x32", "scale" : "1x", "filename" : "icon_32.png" }, |
| 70 | { "idiom" : "mac", "size" : "32x32", "scale" : "2x", "filename" : "icon_64.png" }, |
| 71 | { "idiom" : "mac", "size" : "128x128", "scale" : "1x", "filename" : "icon_128.png" }, |
| 72 | { "idiom" : "mac", "size" : "128x128", "scale" : "2x", "filename" : "icon_256.png" }, |
| 73 | { "idiom" : "mac", "size" : "256x256", "scale" : "1x", "filename" : "icon_256.png" }, |
| 74 | { "idiom" : "mac", "size" : "256x256", "scale" : "2x", "filename" : "icon_512.png" }, |
| 75 | { "idiom" : "mac", "size" : "512x512", "scale" : "1x", "filename" : "icon_512.png" }, |
| 76 | { "idiom" : "mac", "size" : "512x512", "scale" : "2x", "filename" : "icon_1024.png" } |
| 77 | ], |
| 78 | "info" : { "version" : 1, "author" : "xcode" } |
| 79 | } |
| 80 | """ |
| 81 | try! contents.write(toFile: "\(outDir)/Contents.json", atomically: true, encoding: .utf8) |
| 82 | print("wrote \(outDir)/Contents.json") |
| 83 | |