A square lattice of circles whose radii are read from Perlin noise, and nothing else.
Position is a grid; size is the picture
The whole piece is two nested loops and one lookup:
for (var posx = -Size; posx <= Size; posx += CircleSize * 2)
for (var posy = -Size; posy <= Size; posy += CircleSize * 2)
{
var amp = perlin.Get(posx, posy);
amp = (amp + 1) / 2;
var radius = CircleSize * amp;
canvas.DrawCircle(posx - radius, posy, radius, paint);
}
Nothing about the positions is interesting. They are a square lattice on a fixed
pitch, evenly spaced from edge to edge, and they do not move. Everything that makes
the result look like a field rather than like graph paper comes from one number per
site — the noise value that becomes the radius.
The lattice is bigger than the canvas: it spans the full frame in every preset, so
the outermost row and column are cut by the canvas edge and the piece reads as a
window onto a field that continues past all four sides. Nothing is cropped to
hide; the crop is the composition. The same square window frames all five
renderings — what changes between them is the pitch and the noise, never the
framing.
The lattice with the noise turned off: every circle at the mean radius,
CircleSize / 2. This is what the piece is built on, and it is worth looking at,
because nothing below changes it.
What Perlin noise gives you
The reason this reads as landscape rather than as static is that Perlin noise is
smooth. Ken Perlin’s construction assigns a random gradient to each point of a
coarse integer lattice and interpolates between them, so any two nearby samples come
from the same handful of gradients and cannot differ much. Random numbers per site
would give a field with no features; noise gives a field with nothing but features.
PerlinSize sets how coarse that gradient lattice is over the extent Size. A
small value makes wide, slow regions; a large one makes them tight. It is the only
control over the scale of the structure, and there is no octave summing here — one
layer of noise, sampled directly.
A quarter of the variation applied. The radii disagree slightly, and the first thing
visible is that they disagree together: there are no isolated large circles.
Half. Regions the size of the noise lattice are now legible, and the picture has
started to read as tone rather than as a grid.
The offset
There is one detail in the drawing call that is easy to read past:
canvas.DrawCircle(posx - radius, posy, radius, paint);
The centre is shifted left by the radius, not placed at the lattice site. So the
circles are tangent to a vertical line through posx rather than centred on it —
they all hang off the same right edge and grow leftward.
With uniform radii that is invisible; every circle shifts by the same amount and the
lattice just moves. As the noise comes up it becomes the piece’s signature: a large
circle reaches back across its neighbour to the left while a small one leaves a gap,
so the horizontal spacing appears irregular although the sites never moved. The
field looks sheared, and nothing is sheared.
The same lean decides where the lattice sits on the canvas. The ink hangs left of
its site, so centring the sites would put the picture off-centre; the lattice is
shifted half a pitch to the right instead, which centres the ink and keeps the
edge crop even on both sides.
Three quarters. The largest circles now meet, and the columns have visibly lost
their alignment.
Full noise. At the low end the radius approaches zero and circles disappear
entirely; at the high end they reach a full CircleSize and overlap their
neighbours. Both extremes are the same single number arriving from the same lookup.
Five renderings
| Rendering | Extent | Circle size | Perlin size | Seed | Ink |
|---|
| Tide | 400 | 10 | 10 | 29 | #2ec4b6 on #020d12 |
| Electric Purple | 400 | 12 | 7 | 251 | #BF00FF on #1a1a2e |
| Solar Orange | 400 | 8 | 13 | 137 | #FF6600 on #0a0a0a |
| Chalk | 400 | 5 | 16 | 43 | #e8e0d8 on #080808 |
| Oxide | 400 | 16 | 5 | 83 | #c85028 on #0c0600 |
CircleSize and PerlinSize pull against each other. A larger CircleSize means
fewer, bigger circles; a larger PerlinSize means more, tighter noise features.
The five renderings span a wide range of that space. Oxide is a few hundred large
circles in an almost featureless field — the piece read as composition. Chalk is
thousands of tiny circles in a fine, near-random texture — the piece read as tone.
Electric Purple and Solar Orange each hold one axis high while keeping the other
moderate. Tide holds the middle, where the shear effect and the regional structure
of the noise are both legible at once.
The museum plate
The gallery ends with the piece the way it would hang on a wall. MuseumFrame
is a presentation layer, not a generative one: it takes any ISkiaArt piece,
clears to a gallery wall, and draws a frame, a mat with a bevel rule, and the
artwork contain-fitted into the opening behind them. The lead preset, Tide,
appears in the gallery twice — once as the raw plate, once framed — because the
frame is a way of showing the work rather than a rendering of it, and none of
the other renderings gets a framed copy.
The proportions are fixed: the opening is 65% of the canvas, the mat 32 pixels
and the frame 16 pixels around it, and the rest is wall with a soft drop shadow
under the frame. The artwork is clipped at the mat edge, so the piece’s own
edge crop stays inside the opening instead of reaching the wall.
Determinism
The noise is seeded from the preset, and the lattice is walked in a fixed order with
no state carried between sites — each circle depends only on its own coordinates. A
site’s radius is therefore a pure function of (posx, posy, Seed, PerlinSize, Size),
and the same preset draws the same field on every run.