|
|
import Foundation
|
|
|
import AppKit
|
|
|
|
|
|
// Zeichnet das App-Symbol von Mawari (回り, „reihum“): eine Diensttafel aus Hinoki-Holz
|
|
|
// mit drei hängenden Namensschildern. Das mittlere ist auf die rote Seite
|
|
|
// gedreht – „ist dran“. Grund in Tokiwa-Grün.
|
|
|
//
|
|
|
// Aufruf: swift Tools/make-icon.swift (schreibt App/Resources/AppIcon.icns
|
|
|
// und design/icon-1024.png)
|
|
|
|
|
|
func rgb(_ hex: UInt32, _ a: CGFloat = 1) -> CGColor {
|
|
|
CGColor(red: CGFloat((hex >> 16) & 0xFF) / 255, green: CGFloat((hex >> 8) & 0xFF) / 255, blue: CGFloat(hex & 0xFF) / 255, alpha: a)
|
|
|
}
|
|
|
|
|
|
func drawIcon(size: CGFloat) -> CGImage {
|
|
|
let cs = CGColorSpaceCreateDeviceRGB()
|
|
|
let ctx = CGContext(data: nil, width: Int(size), height: Int(size), bitsPerComponent: 8, bytesPerRow: 0,
|
|
|
space: cs, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
|
|
|
let s = size / 1024
|
|
|
let inset = 100 * s
|
|
|
let rect = CGRect(x: inset, y: inset, width: size - inset * 2, height: size - inset * 2)
|
|
|
let radius = rect.width * 0.2237
|
|
|
|
|
|
// Schatten unter der Kachel
|
|
|
ctx.saveGState()
|
|
|
ctx.setShadow(offset: CGSize(width: 0, height: -10 * s), blur: 24 * s, color: rgb(0x000000, 0.28))
|
|
|
ctx.addPath(CGPath(roundedRect: rect, cornerWidth: radius, cornerHeight: radius, transform: nil))
|
|
|
ctx.setFillColor(rgb(0x2F6B4F)); ctx.fillPath()
|
|
|
ctx.restoreGState()
|
|
|
|
|
|
ctx.saveGState()
|
|
|
ctx.addPath(CGPath(roundedRect: rect, cornerWidth: radius, cornerHeight: radius, transform: nil))
|
|
|
ctx.clip()
|
|
|
let bg = CGGradient(colorsSpace: cs, colors: [rgb(0x1E4A36), rgb(0x2F6B4F), rgb(0x3E8462)] as CFArray, locations: [0, 0.55, 1])!
|
|
|
ctx.drawLinearGradient(bg, start: CGPoint(x: 0, y: rect.minY), end: CGPoint(x: 0, y: rect.maxY), options: [])
|
|
|
|
|
|
// Holztafel
|
|
|
let board = CGRect(x: rect.minX + 118 * s, y: rect.minY + 170 * s, width: rect.width - 236 * s, height: rect.height - 330 * s)
|
|
|
ctx.saveGState()
|
|
|
ctx.setShadow(offset: CGSize(width: 0, height: -12 * s), blur: 20 * s, color: rgb(0x0B1F16, 0.45))
|
|
|
ctx.addPath(CGPath(roundedRect: board, cornerWidth: 34 * s, cornerHeight: 34 * s, transform: nil))
|
|
|
ctx.setFillColor(rgb(0xE6CC9C)); ctx.fillPath()
|
|
|
ctx.restoreGState()
|
|
|
// Maserung, ganz zart
|
|
|
ctx.saveGState()
|
|
|
ctx.addPath(CGPath(roundedRect: board, cornerWidth: 34 * s, cornerHeight: 34 * s, transform: nil))
|
|
|
ctx.clip()
|
|
|
ctx.setStrokeColor(rgb(0xC9A86A, 0.35)); ctx.setLineWidth(5 * s)
|
|
|
for i in 0..<7 {
|
|
|
let y = board.minY + CGFloat(i) * board.height / 6.5 + 18 * s
|
|
|
ctx.move(to: CGPoint(x: board.minX, y: y))
|
|
|
ctx.addCurve(to: CGPoint(x: board.maxX, y: y + 10 * s),
|
|
|
control1: CGPoint(x: board.minX + board.width * 0.35, y: y + 22 * s),
|
|
|
control2: CGPoint(x: board.minX + board.width * 0.65, y: y - 18 * s))
|
|
|
}
|
|
|
ctx.strokePath()
|
|
|
ctx.restoreGState()
|
|
|
|
|
|
// Leiste oben mit drei Haken
|
|
|
let rail = CGRect(x: board.minX + 40 * s, y: board.maxY - 96 * s, width: board.width - 80 * s, height: 22 * s)
|
|
|
ctx.addPath(CGPath(roundedRect: rail, cornerWidth: 11 * s, cornerHeight: 11 * s, transform: nil))
|
|
|
ctx.setFillColor(rgb(0xB89260)); ctx.fillPath()
|
|
|
|
|
|
// Namensschilder: hoch und schmal, wie in Japan üblich
|
|
|
let plateW = 128 * s, plateH = 330 * s, gap = 40 * s
|
|
|
let total = plateW * 3 + gap * 2
|
|
|
let x0 = board.midX - total / 2
|
|
|
let top = rail.minY - 20 * s
|
|
|
for i in 0..<3 {
|
|
|
let x = x0 + CGFloat(i) * (plateW + gap)
|
|
|
let plate = CGRect(x: x, y: top - plateH, width: plateW, height: plateH)
|
|
|
let onDuty = i == 1
|
|
|
// Haken
|
|
|
ctx.setFillColor(rgb(0x6B5135))
|
|
|
ctx.fillEllipse(in: CGRect(x: plate.midX - 13 * s, y: rail.midY - 13 * s, width: 26 * s, height: 26 * s))
|
|
|
ctx.setStrokeColor(rgb(0x6B5135)); ctx.setLineWidth(6 * s)
|
|
|
ctx.move(to: CGPoint(x: plate.midX, y: rail.midY)); ctx.addLine(to: CGPoint(x: plate.midX, y: plate.maxY - 10 * s)); ctx.strokePath()
|
|
|
// Schild
|
|
|
ctx.saveGState()
|
|
|
ctx.setShadow(offset: CGSize(width: 0, height: -6 * s), blur: 10 * s, color: rgb(0x3D2A14, 0.35))
|
|
|
ctx.addPath(CGPath(roundedRect: plate, cornerWidth: 18 * s, cornerHeight: 18 * s, transform: nil))
|
|
|
ctx.setFillColor(onDuty ? rgb(0xB5452B) : rgb(0xFBF6EC)); ctx.fillPath()
|
|
|
ctx.restoreGState()
|
|
|
// "Name": drei kurze Zeilen wie Schrift (bewusst ohne Zeichen, die als Symbol gelesen werden könnten)
|
|
|
ctx.setStrokeColor(onDuty ? rgb(0xFBF6EC) : rgb(0x2B2A27, 0.85)); ctx.setLineCap(.round); ctx.setLineWidth(16 * s)
|
|
|
let lines: [(CGFloat, CGFloat)] = [(0.30, 0.62), (0.45, 0.44), (0.60, 0.54)]
|
|
|
for (y, w) in lines {
|
|
|
let yy = plate.maxY - plate.height * y
|
|
|
ctx.move(to: CGPoint(x: plate.midX - plate.width * w / 2, y: yy))
|
|
|
ctx.addLine(to: CGPoint(x: plate.midX + plate.width * w / 2, y: yy))
|
|
|
}
|
|
|
ctx.strokePath()
|
|
|
}
|
|
|
ctx.restoreGState()
|
|
|
return ctx.makeImage()!
|
|
|
}
|
|
|
|
|
|
func png(_ img: CGImage, to url: URL) {
|
|
|
let rep = NSBitmapImageRep(cgImage: img)
|
|
|
try! rep.representation(using: .png, properties: [:])!.write(to: url)
|
|
|
}
|
|
|
|
|
|
let root = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
|
|
|
let iconset = root.appendingPathComponent("build/AppIcon.iconset")
|
|
|
try? FileManager.default.removeItem(at: iconset)
|
|
|
try! FileManager.default.createDirectory(at: iconset, withIntermediateDirectories: true)
|
|
|
for base in [16, 32, 128, 256, 512] {
|
|
|
png(drawIcon(size: CGFloat(base)), to: iconset.appendingPathComponent("icon_\(base)x\(base).png"))
|
|
|
png(drawIcon(size: CGFloat(base * 2)), to: iconset.appendingPathComponent("icon_\(base)x\(base)@2x.png"))
|
|
|
}
|
|
|
try? FileManager.default.createDirectory(at: root.appendingPathComponent("design"), withIntermediateDirectories: true)
|
|
|
png(drawIcon(size: 1024), to: root.appendingPathComponent("design/icon-1024.png"))
|
|
|
let p = Process()
|
|
|
p.executableURL = URL(fileURLWithPath: "/usr/bin/iconutil")
|
|
|
p.arguments = ["-c", "icns", iconset.path, "-o", root.appendingPathComponent("App/Resources/AppIcon.icns").path]
|
|
|
try! p.run(); p.waitUntilExit()
|
|
|
print(p.terminationStatus == 0 ? "AppIcon.icns geschrieben" : "iconutil fehlgeschlagen")
|