Auto Layout Engine
Photos and words, automatically arranged.
Two Algorithms
Grid
Gallery-wall style. Images and text align in precise rows with uniform gaps. A genetic algorithm searches thousands of arrangements to find the best fit.
Phyllo
Named after phyllotaxis — the golden-angle spiral in sunflower seeds. Photos and text bloom outward from center. A constraint solver guarantees zero overlap.
How text joins the layout
Text isn’t glued on top — it enters the same layout search as your photos. Watch what changes as we add a greeting, a paragraph, and a title/subtitle pair to the same three photos. The engine is Phyllo; each newly-added scrap is ringed in accent.
01 Photos only
baseline · photos only
The starting point. Three photos arranged by Phyllo on a 16:9 canvas. Every step below adds text to this same set — same photos, same seed, same canvas.
02 Add a short greeting
short · ratio ≈ 4.0 · maxArea ceiling
“Good morning!” fits in one row at the reference size, so it’s classified as short: a wide tile with a max-area cap. The engine slots it alongside the photos without letting it swallow the canvas.
03 Add a long paragraph
long · flexible ratio · minArea floor
A wrapping paragraph needs floor area so the font size stays readable. Phyllo raises the text to meet minArea and proportionally shrinks the photos — images can drop to 30% of their original area before the floor kicks in.
04 Add a title + subtitle
paired · one box, two lines
A paired scrap is one layout item with one ratio — the renderer stacks the title over the subtitle and scales both together, so they never drift apart when the box shrinks.
All four layouts use the same seed and same three photos. The only input that changes is the text — so any difference you see between frames is the engine responding to text, not to a different random draw.
Pipeline
Follow the same photos and text scraps through every stage. Photos are tinted photos and text text; together they make up the list the engines work on.
- 01
Collect items
Photos and text scraps become a single flat list of items. From here on, the engines don’t care which is which — they only see an id, a ratio target, and (for text) a min or max area budget.
Photos (intrinsic ratio)
4:3 landscape1.333:4 portrait0.751:1 square1.00Text scraps (ratio + area computed)
Good morning!Wishing you a day full of warmth, … - 02
Measure text
For every text scrap the engine decides one thing first: does it fit on one row at the reference size? That answer drives both the preferred aspect ratio and whether the box has a min-area floor (long text) or max-area ceiling (short text).
“Good morning!”
SHORTratio ∈ [3.60, 5.63]maxArea 2.6% of canvas“Wishing you a day full of warmth, laughter, and small moments of joy.”
LONGratio ∈ [1.26, 2.27]minArea 1.3% of canvasPhotos skip this step — their ratio is whatever the image already is, and they don’t carry an area budget.
- 03
Search in parallel
Two engines try different arrangements of the same list. Text participates in both — the engines can resample a text scrap’s ratio from its allowed range in search of a better score.
Grid GAbinary tree · 50 × 40- Start from 2 balanced trees + random mutations.
- Per generation (× 40): flip cut, swap leaves, restructure subtree.
- Text scraps have a 50% chance to resample their ratio from [lo, hi].
- Keep top 30% each generation; refill by mutating survivors.
Phyllo trialsspiral · 30 trials- Allocate area with sizeVar hierarchy, then raise any text below minArea.
- Deficit is scaled out of non-text items (floor 0.3× of their original area).
- Seed on a golden-angle ellipse; run a 300-iteration constraint solver.
- 30 trials with fresh ratio samples; keep the best-scoring one.
- 04
Score + retry
Every candidate is multiplied through a handful of factors that each stay in [0, 1]. The text factor (tB for Grid, ts for Phyllo) can drag the whole score down if any text renders below the fs floor or overflows its area budget. If the best score is below the user’s minScore, the seed increments and the search replays — up to maxRetries.
Grid scoreexponent- fl (fill)0.15
- am (aspect match)0.15
- rcOK (row count)0.13
- rwS (row width)0.13
- gs (gap uniformity)0.13
- aOK (area balance)0.09
- co (compactness)0.05
- tB (text block)0.17
Phyllo scoreexponent- co (compactness)0.30
- gh2 (gap harmony)0.17
- cov (coverage)0.15
- am (aspect match)0.10
- axisFill0.08
- ts (text signal)0.20
retry loopwhile score < minScore && tries < maxRetries → seed += 1 - 05
Post-process
Once the engines converge, two optional passes reshape the winning frames. Neither runs a search — they’re purely geometric transforms, applied to Grid and Phyllo frames identically.
Scrap scalescrapScalePctInflate every box outward by a percentage of the canvas’s short edge. Makes individual items larger; neighbors can overlap.
TightnesstightnessPctPull every frame toward the canvas centre, then rescale the bbox back to full size. Compresses gaps without cropping content.
- 06
Render
The engine’s job ends in normalized units. The renderer maps each frame to a percentage of the live canvas width, so the same layout looks identical on 400px mobile and 1400px desktop.
- Map frames
Every frame’s
(x, y, w, h)is converted to%of NW/NH on the fly — no pixel constants leak from the engine. - Fit the text
Each text scrap runs a 25-iteration shrink loop in a
useLayoutEffect: start at the estimator’s fs, measure the rendered height, multiply fs by 0.9 until it fits, and stop at a floor of 3 display px. - Animate
Position/size changes animate over 600ms with a cubic-bezier ease. The animation is CSS-only; React just updates the percentages.
- Map frames
Playground
Adjust any parameter — both engines re-run together. Text scraps participate in the search the same way images do.
Canvas & Content›
Layout›
Post-process›
Text Ratio›
Advanced›
Try With Your Photos
Drop photos here
or click to browse · Supports JPG, PNG, WebP
Try with sample photos