aboutsummaryrefslogtreecommitdiff
path: root/xM/gen-icon.swift
blob: 5e29016d31b373debc555c65029479f42b41b39c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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)
}