One SVG outline, packed at successively smaller scales, largest first.
Largest first
float radius = MaxRadius;
while (radius >= MinRadius)
if (!AddShape(canvas, paint, polygons, shape0, shapes, radius, rng))
radius *= RadiusDecr;
The whole control flow is those four lines. Try to add a shape at the current radius; if
it succeeds, try again at the same radius; if it fails, shrink the radius and carry on.
Stop when the radius falls below the floor.
Packing largest-first is not an optimisation — it is what makes the result look
deliberate. A random order would scatter small shapes through the space the large ones
needed, and no large shape would ever fit.
The first shapes, at MaxRadius.
A quarter of them.
Gaps do not shrink as fast as shapes
Half of them, and the size has fallen much faster than the count has grown. That is geometry: shape area falls as the square of the radius, but the
gaps left between packed shapes are a roughly constant fraction of the region, so
each size step has proportionally more room to work in than the last.
The consequence is that almost all the shapes in the finished piece are at the smallest
few sizes, and the progression above is stated on a geometric ladder for that reason —
a linear one would have spent four stages on the first dozen shapes.
Four fifths.
Down to MinRadius. The stopping condition is a size, not a count: the piece packs
until the shapes would be too small to read, and how many that turns out to be is
whatever the geometry allows.
One outline, any size
The shape is an SVG path string parsed at startup:
var shape0 = new Shape(SvgPathData);
so the piece is a general packer with one shape supplied as data. The packing itself
knows nothing about what it is placing beyond its outline — collision testing is
polygon against polygon, and the shape could be replaced without touching the algorithm.
PackedInCircle decides whether the region being filled is the circle inscribed in the
canvas or the canvas itself, and ScalePower biases how the radius maps to the shape’s
drawn size.
Five compositions
| Rendering | Structure | Palette |
|---|
| Tidal Field | square field, tangent-aligned narrow leaves | amber and ocean blues on deep teal |
| Midnight Garden | circular, freely rotated multiscale leaves | cream, coral, and green on midnight green |
| Solar Wreath | radial leaves packed around an open center | coral, gold, and chalk on aubergine |
| Paper Kites | four-way geometric eye forms on a square field | brick, slate, and teal on warm paper |
| Copper Current | sparse leaves locked to six directions | cream and copper on soft black |
The ratio between maximum and minimum is the single most consequential number: it sets
how many size steps there are, and through that how many shapes the piece contains.
The boundary, center exclusion, aspect ratio, SVG source, and orientation rule then turn
the same largest-first packing process into markedly different compositions.
On the gallery wall
The gallery ends with one museum plate: Tidal Field set into a warm mat, a dark
frame, and a neutral wall with a soft hanging shadow. Its square field already
uses the whole plate, with edge forms intentionally cropped as though the
packing continues beyond view; the mat gives that energetic crop a deliberate
architectural boundary.
MuseumFrame is separate from the packing algorithm. It accepts any finished
ISkiaArt, contain-fits it into the opening, clips at the mat edge, and adds only
presentation. Tidal Field therefore appears once more as the selected gallery
installation, while the other renderings do not acquire redundant framed
copies.
Determinism
One Random is created per draw, seeded from the preset, and consumed by the placement
search — candidate positions and rotations — in a fixed order. The generator is
System.Random, which the project’s determinism policy flags as not guaranteed stable
across .NET versions.