The SwiftUI app bundles the Go swarm engine through gomobile, so an iPhone is a real peer using the existing PAKE handshake, chunk verification, and relay fallback. XcodeGen builds the project and devicectl installs it on the paired phone.
174 lines
4.9 KiB
Swift
174 lines
4.9 KiB
Swift
#!/usr/bin/env swift
|
|
// Draws the 1024x1024 Share app icon. Run from apps/ios:
|
|
// swift Branding/make-icon.swift Share/Assets.xcassets/AppIcon.appiconset/AppIcon.png
|
|
import AppKit
|
|
import CoreGraphics
|
|
import ImageIO
|
|
|
|
let size = 1024
|
|
let output = CommandLine.arguments.count > 1
|
|
? CommandLine.arguments[1]
|
|
: "Share/Assets.xcassets/AppIcon.appiconset/AppIcon.png"
|
|
|
|
func color(_ hex: UInt32, _ alpha: CGFloat = 1) -> NSColor {
|
|
NSColor(
|
|
calibratedRed: CGFloat((hex >> 16) & 0xFF) / 255,
|
|
green: CGFloat((hex >> 8) & 0xFF) / 255,
|
|
blue: CGFloat(hex & 0xFF) / 255,
|
|
alpha: alpha
|
|
)
|
|
}
|
|
|
|
guard let rep = NSBitmapImageRep(
|
|
bitmapDataPlanes: nil,
|
|
pixelsWide: size,
|
|
pixelsHigh: size,
|
|
bitsPerSample: 8,
|
|
samplesPerPixel: 4,
|
|
hasAlpha: true,
|
|
isPlanar: false,
|
|
colorSpaceName: .deviceRGB,
|
|
bytesPerRow: 0,
|
|
bitsPerPixel: 0
|
|
) else {
|
|
fatalError("Could not create bitmap")
|
|
}
|
|
|
|
NSGraphicsContext.saveGraphicsState()
|
|
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep)
|
|
NSGraphicsContext.current?.imageInterpolation = .high
|
|
NSGraphicsContext.current?.shouldAntialias = true
|
|
|
|
let canvas = NSRect(x: 0, y: 0, width: size, height: size)
|
|
let background = NSGradient(colors: [color(0x07111F), color(0x0B3B36)])!
|
|
background.draw(in: canvas, angle: -55)
|
|
|
|
// Soft teal glow in the upper-left and blue glow in the lower-right.
|
|
let tealGlow = NSGradient(colors: [color(0x5EEAD4, 0.35), color(0x5EEAD4, 0)])!
|
|
tealGlow.draw(
|
|
in: NSBezierPath(ovalIn: NSRect(x: -260, y: 430, width: 1020, height: 1020)),
|
|
relativeCenterPosition: NSPoint(x: 0, y: 0)
|
|
)
|
|
let blueGlow = NSGradient(colors: [color(0x60A5FA, 0.28), color(0x60A5FA, 0)])!
|
|
blueGlow.draw(
|
|
in: NSBezierPath(ovalIn: NSRect(x: 300, y: -300, width: 980, height: 980)),
|
|
relativeCenterPosition: NSPoint(x: 0, y: 0)
|
|
)
|
|
|
|
// A barely-there inset panel gives the flat icon some depth.
|
|
let panel = NSBezierPath(
|
|
roundedRect: NSRect(x: 116, y: 116, width: 792, height: 792),
|
|
xRadius: 180,
|
|
yRadius: 180
|
|
)
|
|
color(0xFFFFFF, 0.035).setFill()
|
|
panel.fill()
|
|
color(0xFFFFFF, 0.07).setStroke()
|
|
panel.lineWidth = 2
|
|
panel.stroke()
|
|
|
|
func strokeArrow(shaftFrom: NSPoint, shaftTo: NSPoint,
|
|
headLeft: NSPoint, headTip: NSPoint, headRight: NSPoint,
|
|
tint: NSColor) {
|
|
let shadow = NSShadow()
|
|
shadow.shadowColor = color(0x000000, 0.3)
|
|
shadow.shadowBlurRadius = 34
|
|
shadow.shadowOffset = NSSize(width: 0, height: -16)
|
|
shadow.set()
|
|
|
|
tint.setStroke()
|
|
let shaft = NSBezierPath()
|
|
shaft.move(to: shaftFrom)
|
|
shaft.line(to: shaftTo)
|
|
shaft.lineWidth = 74
|
|
shaft.lineCapStyle = .round
|
|
shaft.stroke()
|
|
|
|
let head = NSBezierPath()
|
|
head.move(to: headLeft)
|
|
head.line(to: headTip)
|
|
head.line(to: headRight)
|
|
head.lineWidth = 78
|
|
head.lineCapStyle = .round
|
|
head.lineJoinStyle = .round
|
|
head.stroke()
|
|
}
|
|
|
|
// Up arrow on the left.
|
|
strokeArrow(
|
|
shaftFrom: NSPoint(x: 350, y: 288),
|
|
shaftTo: NSPoint(x: 350, y: 610),
|
|
headLeft: NSPoint(x: 238, y: 490),
|
|
headTip: NSPoint(x: 350, y: 640),
|
|
headRight: NSPoint(x: 462, y: 490),
|
|
tint: color(0xF4FFFC)
|
|
)
|
|
|
|
// Down arrow on the right.
|
|
strokeArrow(
|
|
shaftFrom: NSPoint(x: 674, y: 736),
|
|
shaftTo: NSPoint(x: 674, y: 414),
|
|
headLeft: NSPoint(x: 562, y: 534),
|
|
headTip: NSPoint(x: 674, y: 384),
|
|
headRight: NSPoint(x: 786, y: 534),
|
|
tint: color(0x5EEAD4)
|
|
)
|
|
|
|
// Three small dots suggest a swarm: peers nearby, not a central server.
|
|
for (point, alpha) in [
|
|
(NSPoint(x: 250, y: 790), CGFloat(0.55)),
|
|
(NSPoint(x: 512, y: 840), CGFloat(0.35)),
|
|
(NSPoint(x: 790, y: 260), CGFloat(0.45)),
|
|
] {
|
|
let dot = NSBezierPath(ovalIn: NSRect(x: point.x - 7, y: point.y - 7, width: 14, height: 14))
|
|
color(0xFFFFFF, alpha).setFill()
|
|
dot.fill()
|
|
}
|
|
|
|
NSGraphicsContext.restoreGraphicsState()
|
|
|
|
guard let cgImage = rep.cgImage else {
|
|
fatalError("Could not make a CGImage")
|
|
}
|
|
|
|
// App Store icons must not contain an alpha channel. Drawing the rendered
|
|
// image into an opaque context and then encoding that image gives us an
|
|
// RGB PNG without changing a single visible pixel.
|
|
let width = cgImage.width
|
|
let height = cgImage.height
|
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
guard let context = CGContext(
|
|
data: nil,
|
|
width: width,
|
|
height: height,
|
|
bitsPerComponent: 8,
|
|
bytesPerRow: 0,
|
|
space: colorSpace,
|
|
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue
|
|
) else {
|
|
fatalError("Could not create an opaque context")
|
|
}
|
|
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
|
|
guard let opaque = context.makeImage() else {
|
|
fatalError("Could not render the opaque icon")
|
|
}
|
|
|
|
let url = URL(fileURLWithPath: output)
|
|
try FileManager.default.createDirectory(
|
|
at: url.deletingLastPathComponent(),
|
|
withIntermediateDirectories: true
|
|
)
|
|
guard let destination = CGImageDestinationCreateWithURL(
|
|
url as CFURL,
|
|
"public.png" as CFString,
|
|
1,
|
|
nil
|
|
) else {
|
|
fatalError("Could not create a PNG destination")
|
|
}
|
|
CGImageDestinationAddImage(destination, opaque, nil)
|
|
guard CGImageDestinationFinalize(destination) else {
|
|
fatalError("Could not write the PNG")
|
|
}
|
|
print("Wrote \(url.path)")
|