diff options
author | Přemysl Eric Janouch <p@janouch.name> | 2023-09-03 02:11:21 +0200 |
---|---|---|
committer | Přemysl Eric Janouch <p@janouch.name> | 2023-09-03 02:13:14 +0200 |
commit | 9e4692bb09742bb68a62ef79890279a9e967169a (patch) | |
tree | 4b4f19f2fb5851d301d85185ee15badad679c549 /xM/gen-icon.swift | |
parent | 1c4343058da2a1e0c6d2fd87a9bde4ef4b378eae (diff) | |
download | xK-9e4692bb09742bb68a62ef79890279a9e967169a.tar.gz xK-9e4692bb09742bb68a62ef79890279a9e967169a.tar.xz xK-9e4692bb09742bb68a62ef79890279a9e967169a.zip |
xM: generate and use a bundle icon
Diffstat (limited to 'xM/gen-icon.swift')
-rw-r--r-- | xM/gen-icon.swift | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/xM/gen-icon.swift b/xM/gen-icon.swift new file mode 100644 index 0000000..5e29016 --- /dev/null +++ b/xM/gen-icon.swift @@ -0,0 +1,104 @@ +// gen-icon.awk: generate a program icon for xM in the Apple icon format +// +// Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name> +// 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<CGFloat> = [16, 32, 128, 256, 512] +let icns = CGImageDestinationCreateWithURL( + URL(fileURLWithPath: filename) as CFURL, + UTType.icns.identifier as CFString, macOSSizes.count * 2, nil)! +for size in macOSSizes { + CGImageDestinationAddImage(icns, drawIcon(scale: size / 1024.0)!, nil) + CGImageDestinationAddImage(icns, drawIcon(scale: size / 1024.0 * 2)!, [ + kCGImagePropertyDPIWidth: 144, + kCGImagePropertyDPIHeight: 144, + ] as CFDictionary) +} +if !CGImageDestinationFinalize(icns) { + print("ICNS finalization failed.") + exit(EXIT_FAILURE) +} |