Give the macOS app a proper icon matching iOS (#1414)
The macOS Debug configuration uses AppIconDebug, which had no mac-idiom entries at all, so Debug builds shipped with the generic dock icon. And the mac slots in AppIcon reused the full-bleed square iOS artwork; macOS does not mask icons at render time, so even Release showed a sharp- cornered square. - Regenerate all AppIcon mac PNGs with the Big Sur icon grid baked in (824x824 rounded-rect body on a transparent 1024 canvas + standard drop shadow), derived from the same 1024 iOS art. - Add a full mac icon set to AppIconDebug from the green debug artwork. - Add scripts/generate-mac-appicon.swift (pure CoreGraphics) to re-derive the mac sizes whenever the 1024 source changes. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
Before Width: | Height: | Size: 848 B After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 356 B After Width: | Height: | Size: 460 B |
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 833 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 429 B After Width: | Height: | Size: 833 B |
|
Before Width: | Height: | Size: 597 B After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 50 KiB |
@@ -27,6 +27,66 @@
|
|||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"platform" : "ios",
|
"platform" : "ios",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "mac_16x16.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "16x16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "mac_16x16@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "16x16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "mac_32x32.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "32x32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "mac_32x32@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "32x32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "mac_128x128.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "mac_128x128@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "128x128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "mac_256x256.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "256x256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "mac_256x256@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "256x256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "mac_512x512.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "512x512"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "mac_512x512@2x.png",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "512x512"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 505 B |
|
After Width: | Height: | Size: 930 B |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 930 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 73 KiB |
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env swift
|
||||||
|
// Generates macOS app-icon PNGs from a square 1024x1024 source image.
|
||||||
|
//
|
||||||
|
// macOS does not mask icons at render time the way iOS does, so the rounded
|
||||||
|
// rectangle must be baked into the artwork. This applies Apple's Big Sur icon
|
||||||
|
// grid: an 824x824 rounded-rect body (corner radius 185.4 @1024) centered on a
|
||||||
|
// transparent 1024 canvas, with the standard subtle drop shadow.
|
||||||
|
//
|
||||||
|
// Usage: swift scripts/generate-mac-appicon.swift <source-1024.png> <output-dir> <file-prefix>
|
||||||
|
// e.g. swift scripts/generate-mac-appicon.swift icon_1024x1024.png bitchat/Assets.xcassets/AppIcon.appiconset icon
|
||||||
|
|
||||||
|
import AppKit
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
|
guard CommandLine.arguments.count == 4 else {
|
||||||
|
FileHandle.standardError.write(Data("usage: generate-mac-appicon.swift <source.png> <outdir> <prefix>\n".utf8))
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let sourcePath = CommandLine.arguments[1]
|
||||||
|
let outDir = URL(fileURLWithPath: CommandLine.arguments[2], isDirectory: true)
|
||||||
|
let prefix = CommandLine.arguments[3]
|
||||||
|
|
||||||
|
guard let dataProvider = CGDataProvider(url: URL(fileURLWithPath: sourcePath) as CFURL),
|
||||||
|
let source = CGImage(pngDataProviderSource: dataProvider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) else {
|
||||||
|
FileHandle.standardError.write(Data("error: could not read PNG at \(sourcePath)\n".utf8))
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let sRGB = CGColorSpace(name: CGColorSpace.sRGB)!
|
||||||
|
|
||||||
|
func render(pixels: Int) -> CGImage {
|
||||||
|
let ctx = CGContext(
|
||||||
|
data: nil, width: pixels, height: pixels,
|
||||||
|
bitsPerComponent: 8, bytesPerRow: 0, space: sRGB,
|
||||||
|
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
|
||||||
|
)!
|
||||||
|
let side = CGFloat(pixels)
|
||||||
|
let inset = side * 100.0 / 1024.0
|
||||||
|
let body = CGRect(x: inset, y: inset, width: side - 2 * inset, height: side - 2 * inset)
|
||||||
|
let radius = side * 185.4 / 1024.0
|
||||||
|
let path = CGPath(roundedRect: body, cornerWidth: radius, cornerHeight: radius, transform: nil)
|
||||||
|
|
||||||
|
ctx.saveGState()
|
||||||
|
ctx.setShadow(
|
||||||
|
offset: CGSize(width: 0, height: -side * 10.0 / 1024.0),
|
||||||
|
blur: side * 20.0 / 1024.0,
|
||||||
|
color: CGColor(colorSpace: sRGB, components: [0, 0, 0, 0.3])
|
||||||
|
)
|
||||||
|
ctx.addPath(path)
|
||||||
|
ctx.setFillColor(CGColor(colorSpace: sRGB, components: [0, 0, 0, 1])!)
|
||||||
|
ctx.fillPath()
|
||||||
|
ctx.restoreGState()
|
||||||
|
|
||||||
|
ctx.saveGState()
|
||||||
|
ctx.addPath(path)
|
||||||
|
ctx.clip()
|
||||||
|
ctx.interpolationQuality = .high
|
||||||
|
ctx.draw(source, in: body)
|
||||||
|
ctx.restoreGState()
|
||||||
|
|
||||||
|
return ctx.makeImage()!
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePNG(_ image: CGImage, to url: URL) {
|
||||||
|
let dest = CGImageDestinationCreateWithURL(url as CFURL, UTType.png.identifier as CFString, 1, nil)!
|
||||||
|
CGImageDestinationAddImage(dest, image, nil)
|
||||||
|
guard CGImageDestinationFinalize(dest) else {
|
||||||
|
FileHandle.standardError.write(Data("error: failed to write \(url.path)\n".utf8))
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (point size, scale) for every mac slot in an appiconset
|
||||||
|
let slots: [(Int, Int)] = [(16, 1), (16, 2), (32, 1), (32, 2), (128, 1), (128, 2), (256, 1), (256, 2), (512, 1), (512, 2)]
|
||||||
|
for (points, scale) in slots {
|
||||||
|
let suffix = scale == 1 ? "" : "@\(scale)x"
|
||||||
|
let url = outDir.appendingPathComponent("\(prefix)_\(points)x\(points)\(suffix).png")
|
||||||
|
writePNG(render(pixels: points * scale), to: url)
|
||||||
|
print("wrote \(url.lastPathComponent)")
|
||||||
|
}
|
||||||