WipesAndCopies
wipes, copies, and a glowing edge, one picker across them: two photographs wiped into each other by a new order every few seconds (a straight edge, doors, blinds, shuffled bars, rings, a clock hand, shuffled blocks, a checker of diamonds, a noise dissolve), its code written under it; one petal copied around a ring, into a grid, and along a line, each copy smaller, more turned, and further around the hue wheel; and a word and a star with light running around their edges (Combine.wipe / WipeOrder / .repeated / .edgeGlow)
- Run
swift run --package-path Examples Example-Effects-WipesAndCopies- Imports
OllinSamplePhotos- Source
- Examples/Effects/WipesAndCopies/Sketch.swift
import Ollin
import OllinSamplePhotos
/// Wipes, copies, and a glowing edge, one picker across them.
///
/// - **wipes**: two photographs taking turns, each wiped into the other by a new
/// order every few seconds: a straight edge, doors opening from the middle,
/// blinds, shuffled bars, rings popping outward, a clock hand, blocks in a
/// shuffled order, a checker of diamonds, and a soft noise dissolve. Every one
/// is the same call, `Combine.wipe`, given a different `WipeOrder`, and the name
/// of the one running is written under the picture.
/// - **copies**: one petal drawn in the middle of a layer, copied around a ring
/// (each copy turned to face out, so the ring is a flower), into a grid, and
/// along a line, each copy a step smaller, more turned, and further around the
/// hue wheel than the last, the steps swaying as it plays.
/// - **edge glow**: a word and a star with a band of light along their edges and
/// spots of three colors racing around them, beating together.
///
/// Try it: in `wipes`, set `feather` to 0 for hard edges or past 0.3 for a slow
/// melt; in `copies`, push `count` up and `shrink` down until the ring becomes a
/// spiral.
@main
final class WipesAndCopies: Sketch {
enum Look: String, CaseIterable, ParamOption { case wipes, copies, edgeGlow }
@Param(style: .segmented, icon: "rectangle.righthalf.inset.filled") var look = Look.wipes
@Param(0 ... 0.5, icon: "aqi.medium") var feather = 0.08
@Param(1 ... 24, icon: "square.on.square") var count = 9
@Param(0.6 ... 1, icon: "arrow.down.right.and.arrow.up.left") var shrink = 0.94
@Param(4 ... 40, icon: "sun.max") var glowWidth = 22.0
override var loopDuration: Double? { 27 }
private var photos: [Image] = []
let font = OutlineFont(name: "AvenirNext-Heavy") ?? .systemBold
/// The nine orders the wipes take in turn, with the code that makes each.
let orders: [(WipeOrder, String)] = [
(.directional(), ".directional()"),
(.directional().folded(), ".directional().folded()"),
(.directional(angle: .pi / 2).tiled(10), ".directional(angle: .pi / 2).tiled(10)"),
(.directional(angle: 0.3).bands(18).shuffled(seed: 4), ".directional(angle: 0.3).bands(18).shuffled(seed: 4)"),
(.radial().bands(7), ".radial().bands(7)"),
(.angular(), ".angular()"),
(.cells(columns: 9, rows: 9).shuffled(seed: 2), ".cells(columns: 9, rows: 9).shuffled(seed: 2)"),
(.cells(columns: 7, rows: 7).checker().diamond(), ".cells(columns: 7, rows: 7).checker().diamond()"),
(.noise(size: 0.18, seed: 5), ".noise(size: 0.18, seed: 5)"),
]
override func setup() {
photos = [SamplePhoto.talavera.load(), SamplePhoto.marigolds.load(), SamplePhoto.textiles.load()]
}
override func draw() {
switch look {
case .wipes: drawWipes()
case .copies: drawCopies()
case .edgeGlow: drawEdgeGlow()
}
}
// MARK: Wipes
/// Three seconds an order: two seconds of wipe and a second at rest.
private func drawWipes() {
let beat = time / 3
let index = Int(beat) % orders.count
let progress = min(1, beat.truncatingRemainder(dividingBy: 1) * 1.5)
let from = photos[index % photos.count], to = photos[(index + 1) % photos.count]
let a = makeRenderTarget(), b = makeRenderTarget()
withTarget(a) { drawImage(from, in: Rectangle(x: 0, y: 0, width: width, height: height), fit: .cover) }
withTarget(b) { drawImage(to, in: Rectangle(x: 0, y: 0, width: width, height: height), fit: .cover) }
let eased = progress * progress * (3 - 2 * progress)
drawImage(a.combined(with: b, .wipe(orders[index].0, progress: eased, feather: feather)).image, 0, 0)
caption(orders[index].1)
}
/// The order's code on a dark strip at the bottom.
private func caption(_ text: String) {
noStroke()
fill(Color(white: 0, alpha: 0.55))
drawRect(0, height - 64, width, 64)
fill(.white)
textFont(.systemMono)
textSize(26)
textAlign(.center, .center)
drawText(text, width / 2, height - 32)
}
// MARK: Copies
/// A petal in the middle of its layer, laid out around a ring, in a grid,
/// and along a line in turn, nine seconds each.
private func drawCopies() {
background(Color(hex: 0x14161C))
let sway = sin(time * 0.7)
let stage = Int(time / 9) % 3
// A grid shrinks the whole layer into a cell, so its petal is drawn to fill
// the layer; around the ring and along the line the copies keep its size.
let size = stage == 1 ? 3.2 : 1.0
let petal = makeRenderTarget()
withTarget(petal) {
noStroke()
fill(Color(hex: 0xE8A24A))
drawEllipse(width / 2, height / 2 - 40 * size, 46 * size, 120 * size)
fill(Color(hex: 0xF6D9A0))
drawCircle(width / 2, height / 2 - 120 * size, 16 * size)
}
let layout: Filter.CopyLayout
switch stage {
case 0: layout = .ring(radius: 0.18, facesOut: true)
case 1: layout = .grid(columns: Int(Double(count).squareRoot().rounded(.up)))
default: layout = .line(from: Vector2(0.12, 0.62), to: Vector2(0.88, 0.38))
}
let copies = petal.filtered(.repeated(layout, count: count, scale: shrink,
rotation: stage == 0 ? 0.05 * sway : 0.12 * sway,
opacity: 0.96, hueShift: 0.22 + 0.1 * sway))
drawImage(copies.image, 0, 0)
caption([".ring(radius: 0.18, facesOut: true)", ".grid(columns: \(Int(Double(count).squareRoot().rounded(.up))))",
".line(from:to:)"][stage])
}
// MARK: Edge glow
/// A word and a star, their edges lit and circled by three colors of spot.
private func drawEdgeGlow() {
background(Color(hex: 0x0B0C10))
let layer = makeRenderTarget()
withTarget(layer) {
noStroke()
fill(Color(hex: 0x1D2230))
withState {
translate(width * 0.5, height * 0.34)
rotate(time * 0.1)
drawStar(0, 0, 230, 100, points: 5)
}
fill(Color(hex: 0x262C3D))
textFont(font)
textSize(250)
textAlign(.center, .center)
drawText("Ollin", width * 0.5, height * 0.74)
}
let glow = Filter.edgeGlow(width: glowWidth,
colors: [Color(hex: 0x7B2FF7), Color(hex: 0x00E0FF), Color(hex: 0xFF5A8A)],
spots: 3, spotSize: 0.05, pulse: 0.6, bloom: 0.6, phase: time)
drawImage(layer.filtered(glow).image, 0, 0)
}
}