130 lines
5.6 KiB
Swift
130 lines
5.6 KiB
Swift
import CoreGraphics
|
|
import Foundation
|
|
import ImageIO
|
|
import UniformTypeIdentifiers
|
|
|
|
struct FakeArtwork {
|
|
private let context: CGContext
|
|
private let space = CGColorSpaceCreateDeviceRGB()
|
|
private let size: CGFloat
|
|
private let hue: Double
|
|
private let spread: Double
|
|
private var random: SeededGenerator
|
|
|
|
static func png(seed: String, side: Int) -> Data? {
|
|
guard var artwork = FakeArtwork(seed: seed, side: side) else { return nil }
|
|
artwork.draw()
|
|
guard let image = artwork.context.makeImage() else { return nil }
|
|
let data = NSMutableData()
|
|
guard let destination = CGImageDestinationCreateWithData(data, UTType.png.identifier as CFString, 1, nil) else { return nil }
|
|
CGImageDestinationAddImage(destination, image, nil)
|
|
return CGImageDestinationFinalize(destination) ? data as Data : nil
|
|
}
|
|
|
|
private init?(seed: String, side: Int) {
|
|
guard let context = CGContext(
|
|
data: nil,
|
|
width: side,
|
|
height: side,
|
|
bitsPerComponent: 8,
|
|
bytesPerRow: 0,
|
|
space: space,
|
|
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
|
|
) else { return nil }
|
|
self.context = context
|
|
size = CGFloat(side)
|
|
random = SeededGenerator(seed: seed.utf8.reduce(UInt64(14_695_981_039_346_656_037)) { ($0 ^ UInt64($1)) &* 1_099_511_628_211 })
|
|
hue = .random(in: 0 ..< 1, using: &random)
|
|
spread = .random(in: 0.08 ... 0.45, using: &random)
|
|
}
|
|
|
|
private mutating func draw() {
|
|
let isDark = Bool.random(using: &random)
|
|
let ground = [color(hue, 0.75, isDark ? 0.35 : 0.95), color(hue + spread, 0.8, isDark ? 0.12 : 0.7)]
|
|
linear(ground, from: CGPoint(x: 0, y: size), to: CGPoint(x: size, y: 0))
|
|
switch Int.random(in: 0 ..< 4, using: &random) {
|
|
case 0: glows()
|
|
case 1: rings()
|
|
case 2: sun(horizon: ground[1])
|
|
default: blocks()
|
|
}
|
|
}
|
|
|
|
private mutating func glows() {
|
|
for _ in 0 ..< 3 {
|
|
let center = CGPoint(x: .random(in: 0 ... size, using: &random), y: .random(in: 0 ... size, using: &random))
|
|
let tint = color(hue + .random(in: -0.2 ... 0.5, using: &random), 0.7, 1)
|
|
let radius = size * .random(in: 0.4 ... 0.8, using: &random)
|
|
guard let clear = tint.copy(alpha: 0),
|
|
let gradient = CGGradient(colorsSpace: space, colors: [tint, clear] as CFArray, locations: nil)
|
|
else { continue }
|
|
context.drawRadialGradient(gradient, startCenter: center, startRadius: 0, endCenter: center, endRadius: radius, options: [])
|
|
}
|
|
}
|
|
|
|
private mutating func rings() {
|
|
let center = CGPoint(x: size * .random(in: 0.3 ... 0.7, using: &random), y: size * .random(in: 0.3 ... 0.7, using: &random))
|
|
for ring in (1 ... 7).reversed() {
|
|
context.setFillColor(color(hue + spread * Double(ring) / 5, 0.65, ring.isMultiple(of: 2) ? 0.95 : 0.25))
|
|
let radius = size * 0.09 * CGFloat(ring)
|
|
context.fillEllipse(in: CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2))
|
|
}
|
|
}
|
|
|
|
private func sun(horizon: CGColor) {
|
|
let radius = size * 0.32
|
|
let disc = CGRect(x: size / 2 - radius, y: size * 0.34, width: radius * 2, height: radius * 2)
|
|
context.saveGState()
|
|
context.addEllipse(in: disc)
|
|
context.clip()
|
|
linear(
|
|
[color(hue + 0.5, 0.5, 1), color(hue + 0.62, 0.85, 1)],
|
|
from: CGPoint(x: 0, y: disc.maxY),
|
|
to: CGPoint(x: 0, y: disc.minY)
|
|
)
|
|
context.restoreGState()
|
|
context.setFillColor(horizon)
|
|
for stripe in 0 ..< 5 {
|
|
let height = size * 0.012 * CGFloat(5 - stripe)
|
|
context.fill(CGRect(x: 0, y: disc.minY + CGFloat(stripe) * size * 0.06, width: size, height: height))
|
|
}
|
|
}
|
|
|
|
private mutating func blocks() {
|
|
let cells = Int.random(in: 2 ... 3, using: &random)
|
|
let cell = size / CGFloat(cells)
|
|
for row in 0 ..< cells {
|
|
for column in 0 ..< cells where Bool.random(using: &random) {
|
|
let rect = CGRect(x: CGFloat(column) * cell, y: CGFloat(row) * cell, width: cell, height: cell)
|
|
context.setFillColor(color(hue + .random(in: 0 ... 0.6, using: &random), 0.7, .random(in: 0.3 ... 1, using: &random)))
|
|
if Bool.random(using: &random) {
|
|
context.fill(rect)
|
|
} else {
|
|
context.fillEllipse(in: rect)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func color(_ hue: Double, _ saturation: Double, _ brightness: Double) -> CGColor {
|
|
let sector = (hue.truncatingRemainder(dividingBy: 1) + 1).truncatingRemainder(dividingBy: 1) * 6
|
|
let chroma = brightness * saturation
|
|
let second = chroma * (1 - abs(sector.truncatingRemainder(dividingBy: 2) - 1))
|
|
let table = [
|
|
[chroma, second, 0],
|
|
[second, chroma, 0],
|
|
[0, chroma, second],
|
|
[0, second, chroma],
|
|
[second, 0, chroma],
|
|
[chroma, 0, second],
|
|
]
|
|
let rgb = table[min(Int(sector), 5)].map { $0 + brightness - chroma }
|
|
return CGColor(red: rgb[0], green: rgb[1], blue: rgb[2], alpha: 1)
|
|
}
|
|
|
|
private func linear(_ colors: [CGColor], from: CGPoint, to: CGPoint) {
|
|
guard let gradient = CGGradient(colorsSpace: space, colors: colors as CFArray, locations: nil) else { return }
|
|
context.drawLinearGradient(gradient, start: from, end: to, options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
|
|
}
|
|
}
|