176 lines
6.3 KiB
Swift
176 lines
6.3 KiB
Swift
import AppKit
|
|
import Foundation
|
|
|
|
struct IconRenderer {
|
|
let size: CGFloat
|
|
|
|
func render(to url: URL) throws {
|
|
guard
|
|
let bitmap = NSBitmapImageRep(
|
|
bitmapDataPlanes: nil,
|
|
pixelsWide: Int(size),
|
|
pixelsHigh: Int(size),
|
|
bitsPerSample: 8,
|
|
samplesPerPixel: 4,
|
|
hasAlpha: true,
|
|
isPlanar: false,
|
|
colorSpaceName: .deviceRGB,
|
|
bytesPerRow: 0,
|
|
bitsPerPixel: 0
|
|
),
|
|
let graphics = NSGraphicsContext(bitmapImageRep: bitmap)
|
|
else { return }
|
|
|
|
NSGraphicsContext.saveGraphicsState()
|
|
NSGraphicsContext.current = graphics
|
|
defer { NSGraphicsContext.restoreGraphicsState() }
|
|
|
|
let context = graphics.cgContext
|
|
context.setShouldAntialias(true)
|
|
context.setAllowsAntialiasing(true)
|
|
|
|
let rect = CGRect(x: 0, y: 0, width: size, height: size)
|
|
let scale = size / 1024
|
|
let radius = 226 * scale
|
|
let base = CGPath(
|
|
roundedRect: rect.insetBy(dx: 44 * scale, dy: 44 * scale),
|
|
cornerWidth: radius,
|
|
cornerHeight: radius,
|
|
transform: nil
|
|
)
|
|
|
|
context.addPath(base)
|
|
context.clip()
|
|
|
|
let background = CGGradient(
|
|
colorsSpace: CGColorSpaceCreateDeviceRGB(),
|
|
colors: [
|
|
NSColor(calibratedRed: 0.04, green: 0.12, blue: 0.24, alpha: 1).cgColor,
|
|
NSColor(calibratedRed: 0.12, green: 0.20, blue: 0.42, alpha: 1).cgColor,
|
|
NSColor(calibratedRed: 0.58, green: 0.16, blue: 0.88, alpha: 1).cgColor
|
|
] as CFArray,
|
|
locations: [0, 0.52, 1]
|
|
)!
|
|
context.drawLinearGradient(
|
|
background,
|
|
start: CGPoint(x: rect.minX, y: rect.maxY),
|
|
end: CGPoint(x: rect.maxX, y: rect.minY),
|
|
options: []
|
|
)
|
|
|
|
drawGlow(context, color: NSColor.systemBlue, center: CGPoint(x: 260 * scale, y: 760 * scale), radius: 330 * scale)
|
|
drawGlow(context, color: NSColor.systemPurple, center: CGPoint(x: 785 * scale, y: 285 * scale), radius: 430 * scale)
|
|
drawGlassHighlight(context, rect: rect, scale: scale)
|
|
drawBars(context, scale: scale)
|
|
drawLens(context, scale: scale)
|
|
|
|
context.resetClip()
|
|
context.addPath(base)
|
|
context.setStrokeColor(NSColor.white.withAlphaComponent(0.22).cgColor)
|
|
context.setLineWidth(12 * scale)
|
|
context.strokePath()
|
|
|
|
guard let png = bitmap.representation(using: .png, properties: [:]) else { return }
|
|
try png.write(to: url)
|
|
}
|
|
|
|
private func drawGlow(_ context: CGContext, color: NSColor, center: CGPoint, radius: CGFloat) {
|
|
let gradient = CGGradient(
|
|
colorsSpace: CGColorSpaceCreateDeviceRGB(),
|
|
colors: [
|
|
color.withAlphaComponent(0.55).cgColor,
|
|
color.withAlphaComponent(0.0).cgColor
|
|
] as CFArray,
|
|
locations: [0, 1]
|
|
)!
|
|
context.drawRadialGradient(
|
|
gradient,
|
|
startCenter: center,
|
|
startRadius: 0,
|
|
endCenter: center,
|
|
endRadius: radius,
|
|
options: []
|
|
)
|
|
}
|
|
|
|
private func drawGlassHighlight(_ context: CGContext, rect: CGRect, scale: CGFloat) {
|
|
let highlight = CGGradient(
|
|
colorsSpace: CGColorSpaceCreateDeviceRGB(),
|
|
colors: [
|
|
NSColor.white.withAlphaComponent(0.32).cgColor,
|
|
NSColor.white.withAlphaComponent(0.06).cgColor,
|
|
NSColor.white.withAlphaComponent(0.0).cgColor
|
|
] as CFArray,
|
|
locations: [0, 0.36, 1]
|
|
)!
|
|
let path = CGPath(
|
|
roundedRect: CGRect(x: 120 * scale, y: 610 * scale, width: 790 * scale, height: 268 * scale),
|
|
cornerWidth: 130 * scale,
|
|
cornerHeight: 130 * scale,
|
|
transform: nil
|
|
)
|
|
context.saveGState()
|
|
context.addPath(path)
|
|
context.clip()
|
|
context.drawLinearGradient(
|
|
highlight,
|
|
start: CGPoint(x: rect.minX, y: rect.maxY),
|
|
end: CGPoint(x: rect.maxX, y: rect.minY),
|
|
options: []
|
|
)
|
|
context.restoreGState()
|
|
}
|
|
|
|
private func drawBars(_ context: CGContext, scale: CGFloat) {
|
|
let bars = [
|
|
CGRect(x: 288, y: 286, width: 86, height: 300),
|
|
CGRect(x: 430, y: 212, width: 86, height: 448),
|
|
CGRect(x: 572, y: 330, width: 86, height: 252)
|
|
].map { CGRect(x: $0.minX * scale, y: $0.minY * scale, width: $0.width * scale, height: $0.height * scale) }
|
|
|
|
for (index, bar) in bars.enumerated() {
|
|
let color: NSColor = index == 1 ? .white : NSColor(calibratedWhite: 1, alpha: 0.88)
|
|
let path = CGPath(
|
|
roundedRect: bar,
|
|
cornerWidth: 44 * scale,
|
|
cornerHeight: 44 * scale,
|
|
transform: nil
|
|
)
|
|
context.addPath(path)
|
|
context.setFillColor(color.cgColor)
|
|
context.fillPath()
|
|
}
|
|
}
|
|
|
|
private func drawLens(_ context: CGContext, scale: CGFloat) {
|
|
let ring = CGRect(x: 682 * scale, y: 228 * scale, width: 170 * scale, height: 170 * scale)
|
|
context.setStrokeColor(NSColor.white.withAlphaComponent(0.92).cgColor)
|
|
context.setLineWidth(34 * scale)
|
|
context.strokeEllipse(in: ring)
|
|
context.setLineCap(.round)
|
|
context.move(to: CGPoint(x: 805 * scale, y: 275 * scale))
|
|
context.addLine(to: CGPoint(x: 892 * scale, y: 188 * scale))
|
|
context.strokePath()
|
|
}
|
|
}
|
|
|
|
let outputRoot = URL(fileURLWithPath: CommandLine.arguments.dropFirst().first ?? "NativeTokenLens/Assets/AppIcon.iconset")
|
|
try FileManager.default.createDirectory(at: outputRoot, withIntermediateDirectories: true)
|
|
|
|
let specs: [(String, CGFloat)] = [
|
|
("icon_16x16.png", 16),
|
|
("icon_16x16@2x.png", 32),
|
|
("icon_32x32.png", 32),
|
|
("icon_32x32@2x.png", 64),
|
|
("icon_128x128.png", 128),
|
|
("icon_128x128@2x.png", 256),
|
|
("icon_256x256.png", 256),
|
|
("icon_256x256@2x.png", 512),
|
|
("icon_512x512.png", 512),
|
|
("icon_512x512@2x.png", 1024)
|
|
]
|
|
|
|
for spec in specs {
|
|
try IconRenderer(size: spec.1).render(to: outputRoot.appendingPathComponent(spec.0))
|
|
}
|