Randomness is the raw material of generative art, and Solandra builds it in properly: every SCanvas has a seeded pseudo-random number generator. The same seed always produces exactly the same picture, which means you can reproduce, iterate on, and export the exact variation you like. Change the seed, get a sibling artwork.
All the methods below live on SCanvas and draw from that seeded generator.
s.random() // uniform number in [0, 1)
s.uniformRandomInt({ from: 3, to: 6 }) // integer: 3, 4, 5 or 6
s.uniformRandomInt({ to: 6, inclusive: false }) // 0..5
s.randomPoint() // a random point on the canvas
s.randomAngle() // uniform in [0, 2π)
s.randomPolarity() // -1 or 1
s.uniformGridPoint({ minX: 0, maxX: 4, minY: 0, maxY: 4 }) // random integer points.background(220, 25, 12)
s.times(150, () => {
s.setFillColor(s.uniformRandomInt({ from: 150, to: 220 }), 80, 60, 0.8)
s.fill(
new Star({
at: s.randomPoint(),
r: 0.01 + 0.04 * s.random(),
n: s.uniformRandomInt({ from: 4, to: 7 }),
a: s.randomAngle(),
})
)
})sample picks one element from an array, samples picks n (with replacement), and shuffle reorders an array in place:
const palette = [200, 215, 340, 45]
s.sample(palette) // e.g. 340
s.samples(3, palette) // e.g. [45, 200, 45]
s.shuffle([...palette]) // e.g. [215, 45, 200, 340]s.background(0, 0, 96)
const hues = [200, 215, 340, 45]
s.forTiling({ n: 9, type: "square", margin: 0.05 }, ([x, y], [dX]) => {
s.setFillColor(s.sample(hues), 70, s.sample([45, 55, 65]))
s.fill(new Square({ at: [x + dX * 0.05, y + dX * 0.05], s: dX * 0.9 }))
})perturb({ at, magnitude }) nudges a point by a uniform random offset up to magnitude / 2 in each direction; the quickest way to make something regular feel hand-made:
s.background(35, 40, 94)
s.lineWidth = 0.005
s.setStrokeColor(25, 60, 30)
s.forTiling({ n: 12, type: "square" }, ([x, y], [dX, dY]) => {
s.draw(
SimplePath.withPoints([
[x, y],
[x + dX, y],
[x + dX, y + dY],
[x, y + dY],
])
.close()
.transformLoopedPoints((pt) => s.perturb({ at: pt, magnitude: 0.02 }))
)
})Uniform randomness often looks too random. Solandra also offers:
gaussian({ mean, sd }) — normally distributed values, which cluster around the meanpoisson(lambda) — non-negative integers with mean (and variance) lambda, good for "how many things here?" decisionss.background(0, 0, 12)
s.times(400, () => {
const x = s.gaussian({ mean: 0.5, sd: 0.12 })
const y = s.gaussian({ mean: 0.5, sd: 0.12 })
const d = v.distance([x, y], [0.5, 0.5])
s.setFillColor(200 + d * 300, 80, 60, 0.7)
s.fill(new Circle({ at: [x, y], r: 0.012 }))
})Also see proportionately and doProportion on the Iteration page for weighted random choices, and forPoissonDiskPoints for evenly spread random points.
Random numbers are independent of each other; noise is randomness with continuity: nearby inputs give nearby outputs. Solandra exports a 2D Perlin noise function, perlin2(x, y), returning values in roughly [-1, 1]. Scale your inputs to control the frequency (zoomed-in inputs change slowly, zoomed-out ones quickly).
import { perlin2 } from "solandra"
s.background(0, 0, 96)
s.forTiling({ n: 30, type: "square" }, ([x, y], [dX], [cX, cY]) => {
const n = perlin2(x * 3, y * 3)
s.setFillColor(190 + n * 60, 70, 50)
s.withTranslation([cX, cY], () => {
s.withRotation(n * Math.PI, () => {
s.fill(
new Rect({ at: [0, 0], w: dX * 0.9, h: dX * 0.18, align: "center" })
)
})
})
})A classic use is a flow field: use noise as an angle everywhere and let particles follow it:
s.background(220, 40, 14)
s.lineWidth = 0.003
s.times(120, (i) => {
let pt = s.randomPoint()
const path = SimplePath.startAt(pt)
s.times(30, () => {
const a = perlin2(pt[0] * 2.5, pt[1] * 2.5) * Math.PI * 2
pt = v.add(pt, [0.008 * Math.cos(a), 0.008 * Math.sin(a)])
path.addPoint(pt)
})
s.setStrokeColor(170 + i, 70, 60, 0.7)
s.draw(path.chaiken({ n: 2 }))
})The seed is supplied when the SCanvas is created (in this site's canvases there's a refresh control for it; with the raw API it's a constructor argument). Within a sketch you can also rewind determinism explicitly:
s.resetRandomNumberGenerator(42) // restart the sequence from a known seedIf you need seeded randomness outside a sketch, the underlying generator is exported directly:
import { RNG } from "solandra"
const rng = new RNG(42)
rng.number() // deterministic sequence of numbers in [0, 1)Solandra was made by James Porter.
Check out the GitHub page or install with npm i solandra