// gen-icon.awk: generate a program icon for xM in the Apple icon format // // Copyright (c) 2023, Přemysl Eric Janouch
// SPDX-License-Identifier: 0BSD
//
// NSGraphicsContext mostly just weirdly wraps over Quartz,
// so we do it all in Quartz directly.
import CoreGraphics
import Foundation
import ImageIO
import UniformTypeIdentifiers
// XXX: Not even AppKit provides real superelliptic squircles; screw it.
func drawSquircle(context: CGContext, bounds: CGRect, radius: CGFloat) {
context.move(to: CGPointMake(bounds.minX, bounds.maxY - radius))
context.addArc(
center: CGPointMake(bounds.minX + radius, bounds.maxY - radius),
radius: radius, startAngle: .pi, endAngle: .pi / 2, clockwise: true)
context.addLine(to: CGPointMake(bounds.maxX - radius, bounds.maxY))
context.addArc(
center: CGPointMake(bounds.maxX - radius, bounds.maxY - radius),
radius: radius, startAngle: .pi / 2, endAngle: 0, clockwise: true)
context.addLine(to: CGPointMake(bounds.maxX, bounds.maxY - radius))
context.addArc(
center: CGPointMake(bounds.maxX - radius, bounds.minY + radius),
radius: radius, startAngle: 0, endAngle: .pi / -2, clockwise: true)
context.addLine(to: CGPointMake(bounds.minX + radius, bounds.minY))
context.addArc(
center: CGPointMake(bounds.minX + radius, bounds.minY + radius),
radius: radius, startAngle: .pi / -2, endAngle: .pi, clockwise: true)
context.closePath()
}
func drawIcon(scale: CGFloat) -> CGImage? {
let size = CGSizeMake(1024, 1024)
let colorspace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: nil,
width: Int(size.width * scale), height: Int(size.height * scale),
bitsPerComponent: 8, bytesPerRow: 0, space: colorspace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
context.scaleBy(x: scale, y: scale)
let bounds = CGRectMake(100, 100, size.width - 200, size.height - 200)
// The radius was something like size.{width,height}/6.4, at least for iOS.
drawSquircle(context: context, bounds: bounds, radius: 180)
let squircle = context.path!
// Gradients don't draw shadows, so draw it separately.
context.saveGState()
context.setShadow(offset: CGSizeMake(0, -12).applying(context.ctm),
blur: 28 * scale, color: CGColor(gray: 0, alpha: 0.5))
context.setFillColor(CGColor(red: 1, green: 0x55p-8, blue: 0, alpha: 1))
context.fillPath()
context.restoreGState()
context.saveGState()
context.addPath(squircle)
context.clip()
context.drawLinearGradient(
CGGradient(colorsSpace: colorspace, colors: [
CGColor(red: 1, green: 0x00p-8, blue: 0, alpha: 1),
CGColor(red: 1, green: 0xaap-8, blue: 0, alpha: 1)
] as CFArray, locations: [0, 1])!,
start: CGPointMake(0, 100), end: CGPointMake(0, size.height - 100),
options: CGGradientDrawingOptions(rawValue: 0))
context.restoreGState()
context.move(to: CGPoint(x: size.width * 0.30, y: size.height * 0.30))
context.addLine(to: CGPoint(x: size.width * 0.30, y: size.height * 0.70))
context.addLine(to: CGPoint(x: size.width * 0.575, y: size.height * 0.425))
context.move(to: CGPoint(x: size.width * 0.70, y: size.height * 0.30))
context.addLine(to: CGPoint(x: size.width * 0.70, y: size.height * 0.70))
context.addLine(to: CGPoint(x: size.width * 0.425, y: size.height * 0.425))
context.setLineWidth(80)
context.setLineCap(.round)
context.setLineJoin(.round)
context.setStrokeColor(CGColor.white)
context.strokePath()
return context.makeImage()
}
if CommandLine.arguments.count != 2 {
print("Usage: \(CommandLine.arguments.first!) OUTPUT.icns")
exit(EXIT_FAILURE)
}
let filename = CommandLine.arguments[1]
let macOSSizes: Array