|
| 1 | +import Gen |
| 2 | +import UIKit |
| 3 | + |
| 4 | +// We want to create a random art generator that creates UIImages. |
| 5 | +// ⚠️ Run this playground with the live view open. ⚠️ |
| 6 | + |
| 7 | +let canvas = CGRect(x: 0, y: 0, width: 600, height: 600) |
| 8 | +let mainArea = canvas.insetBy(dx: 130, dy: 100) |
| 9 | +let numLines = 60 |
| 10 | +let numPointsPerLine = 60 |
| 11 | +let dx = mainArea.width / CGFloat(numPointsPerLine) |
| 12 | +let dy = mainArea.height / CGFloat(numLines) |
| 13 | + |
| 14 | +func bump( |
| 15 | + amplitude: CGFloat, |
| 16 | + center: CGFloat, |
| 17 | + plateauSize: CGFloat, |
| 18 | + curveSize: CGFloat |
| 19 | + ) -> (CGFloat) -> CGFloat { |
| 20 | + |
| 21 | + // A nice smooth curve that starts at zero and trends towards 1 asymptotically |
| 22 | + func f(_ x: CGFloat) -> CGFloat { |
| 23 | + if x <= 0 { return 0 } |
| 24 | + return exp(-1 / x) |
| 25 | + } |
| 26 | + |
| 27 | + // A nice smooth curve that starts at zero and curves up to 1 on the unit interval. |
| 28 | + func g(_ x: CGFloat) -> CGFloat { |
| 29 | + return f(x) / (f(x) + f(1 - x)) |
| 30 | + } |
| 31 | + |
| 32 | + return { x in |
| 33 | + let plateauSize = plateauSize / 2 |
| 34 | + let curveSize = curveSize / 2 |
| 35 | + let size = plateauSize + curveSize |
| 36 | + let x = x - center |
| 37 | + return amplitude * (1 - g((x * x - plateauSize * plateauSize) / (size * size - plateauSize * plateauSize))) |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +func noisyBump( |
| 42 | + amplitude: CGFloat, |
| 43 | + center: CGFloat, |
| 44 | + plateauSize: CGFloat, |
| 45 | + curveSize: CGFloat |
| 46 | + ) -> (CGFloat) -> Gen<CGFloat> { |
| 47 | + |
| 48 | + let curve = bump(amplitude: amplitude, center: center, plateauSize: plateauSize, curveSize: curveSize) |
| 49 | + |
| 50 | + return { x in |
| 51 | + let y = curve(x) |
| 52 | + return Gen<CGFloat>.float(in: 0...3).map { $0 * (y / amplitude + 0.5) + y } |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +let curve = zip( |
| 57 | + Gen<CGFloat>.float(in: -30...(-1)), |
| 58 | + Gen<CGFloat>.float(in: -60...60) |
| 59 | + .map { $0 + canvas.width / 2 }, |
| 60 | + Gen<CGFloat>.float(in: 0...60), |
| 61 | + Gen<CGFloat>.float(in: 10...60) |
| 62 | + ) |
| 63 | + .map(noisyBump(amplitude:center:plateauSize:curveSize:)) |
| 64 | + |
| 65 | +func path(from min: CGFloat, to max: CGFloat, baseline: CGFloat) -> Gen<CGPath> { |
| 66 | + return Gen<CGPath> { rng in |
| 67 | + let bumps = curve.array(of: .int(in: 1...4)) |
| 68 | + .run(using: &rng) |
| 69 | + |
| 70 | + let path = CGMutablePath() |
| 71 | + path.move(to: CGPoint(x: min, y: baseline)) |
| 72 | + stride(from: min, to: max, by: dx).forEach { x in |
| 73 | + let ys = bumps.map { $0(x).run(using: &rng) } |
| 74 | + let average = ys.reduce(0, +) / CGFloat(ys.count) |
| 75 | + path.addLine(to: CGPoint(x: x, y: baseline + average)) |
| 76 | + } |
| 77 | + path.addLine(to: CGPoint.init(x: max, y: baseline)) |
| 78 | + return path |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +let paths = stride(from: mainArea.minY, to: mainArea.maxY, by: dy) |
| 83 | + .map { path(from: mainArea.minX, to: mainArea.maxX, baseline: $0) } |
| 84 | + .sequence() |
| 85 | + |
| 86 | +let colors = [ |
| 87 | + UIColor(red: 0.47, green: 0.95, blue: 0.69, alpha: 1), |
| 88 | + UIColor(red: 1, green: 0.94, blue: 0.5, alpha: 1), |
| 89 | + UIColor(red: 0.3, green: 0.80, blue: 1, alpha: 1), |
| 90 | + UIColor(red: 0.59, green: 0.30, blue: 1, alpha: 1) |
| 91 | +] |
| 92 | + |
| 93 | +let image = paths.map { paths in |
| 94 | + UIGraphicsImageRenderer(bounds: canvas).image { ctx in |
| 95 | + let ctx = ctx.cgContext |
| 96 | + |
| 97 | + ctx.setFillColor(UIColor.black.cgColor) |
| 98 | + ctx.fill(canvas) |
| 99 | + |
| 100 | + paths.enumerated().forEach { idx, path in |
| 101 | + ctx.setStrokeColor( |
| 102 | + colors[colors.count * idx / paths.count].cgColor |
| 103 | + ) |
| 104 | + ctx.addPath(path) |
| 105 | + ctx.drawPath(using: .fillStroke) |
| 106 | + } |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +let imageView = image.map(UIImageView.init) |
| 111 | + |
| 112 | +import PlaygroundSupport |
| 113 | +PlaygroundPage.current.liveView = imageView.run() |
0 commit comments