Chameleon learned layout generator

A 6,600-weight MLP that, given the analysed layout of a design and a target canvas size, predicts the box each content unit should occupy on the target. The Chameleon resize engine turns the prediction into extra candidate layouts (learned, learned:<rung>); the engine's text fitting, hard checks, rule score and critic then judge them like every other candidate. It runs on numpy alone, offline, in well under a millisecond per task. Trained on layouts made by human designers (Crello / VistaCreate size variants), geometry only.

Status: judged (round 6) and shipped behind an admission guard. It beats the uniform stretch clearly on held-out design families and moves the engine's picks closer to what designers made, but a strong vision judge preferred its changed picks only 66 to 44 (small text on narrow canvases, top-heavy stacks on tall ones). With the guard (a learned layout ships only if it keeps the smallest text within 1 px and the utilization score within 0.05 of the pick made without it) the kept picks were judged 54 to 17, and 71 to 27 out of sample on 90 fresh designs (round 7; the reverted picks 15 to 28). Evidence, numbers and caveats: layout_report.md ยง4 to ยง6c; every changed pick: layout_picks.md.

Licence. Apache-2.0 (confirmed by the team on 2026-10-28). The weights derive from the geometry of Crello / VistaCreate templates (dataset licence CDLA-Permissive-2.0; no pixels, text, fonts or colours were used or are included).

Model details

  • Type. Multi-layer perceptron, 33 inputs, two hidden layers of 64 tanh units, 4 outputs. The output is a correction added to the source box (the uniform stretch): box = src_box + net(standardise(features)), in shares of the target canvas (cx, cy, w, h).
  • Developed by. Dev 3, Chameleon hackathon team (2026-10-27). Framework. numpy for training (a hand-written Adam loop) and inference.
  • Size. 31.3 KB (engine/models/layout.npz, float32); 6,596 weights. Limit 200 KB.
  • File. layout.npz: kind ("mlp"), activation ("tanh"), n_layers (3), feature_names (33 strings, checked against engine.resize.LAYOUT_FEATURES at load), mu and sd (input standardisation), w0..w2 and b0..b2, trained_on (a note). Arrays and strings only; loads with allow_pickle=False.
  • Loader. engine.resize._layout_model() (lazy, cached per file state); a missing, corrupt or inconsistent file means the family is not generated and the engine's output is byte-identical to the output without it. CHAMELEON_LEARNED=0 switches it off.

Intended use

Inside the engine only: _learned_candidates (end of section 2 of engine/resize.py). For every free content unit (not corner furniture, header/footer band or rotated side label) it supplies where the unit's centre goes and how big its box is; text takes the predicted width and the largest whole font size whose wrapped text fits the predicted box (never below what the proportional family gives, never above the anchor's cap), images, groups and rotated elements are fitted into the box keeping their aspect. Floors, margins, collisions, decorations and everything else are the engine's. It never moves, adds or removes an element by itself and never touches content or fonts. The engine also picks without the family and lets a learned layout replace that pick only when its smallest text is within TUNABLES["learned"]["guard_px"] (1 px) and its utilization score within guard_util (0.05) of it.

Out of scope. Aspect changes under 3% (not generated; the model also does not beat the stretch below an aspect change of 0.2), designs far from poster / social / banner layouts, scoring or ranking layouts, any claim about what a human or the organizers' judge prefers (see Limitations).

Inputs and output

Per content unit, 33 numbers from engine.resize._layout_features(analysis, target) (the same function builds the training set): the unit's source box and ink box as shares of the source canvas (8), kind flags (text, image, shape, pinned corner furniture, band, rotated, multi-element group), prominence, largest font size over the source short side, log text length, block index, block count and relative position, flow axis, number of content units, source / target / log-ratio aspect (natural logs), source and target short sides over 1000, rank by area and by font size, alignment (left / right) and a sibling flag. Geometry and text length only: nothing reads ids, asset names, text, fonts or exact canvas sizes (a rename test covers it). Output: the correction to (cx, cy, w, h) of the source box.

Training data

cyberagent/crello v5.1 (VistaCreate designs), geometry only (tools/crello/extract_layouts.py). 18,449 usable size-variant pairs (same element count, type sequence and text lengths; pairs.py) in 1,316 families, each read in both directions: 36,898 directed pairs, 240,227 content-unit rows, 5,563 source templates, no pair dropped after the pair filters. Source templates are converted into Chameleon tasks (grey boxes for vector art, missing images, synthetic words of the real length) and analysed with the engine's analyze_source; the target of a unit is the union of the designer's boxes of its member elements at the other size. Families are split 70 / 30 (915 train, 401 test) by a hash of the family, so no template family is on both sides. The shipped weights were trained on 85% of the train families (127,554 rows, both directions), early-stopped on the other 15% (33,443 rows); the 401 test families were never trained on or selected with. Rows are weighted by 1 / sqrt(pairs in the family).

Loss: the engine's own designer distance on unit boxes, hypot(dcx, dcy) + 0.5 (|dw| + |dh|) (smoothed), 40 epochs of Adam (learning rate 2e-3, cosine decay, batch 1,024, weight decay 1e-5), 5 seeds, the best on validation shipped.

Evaluation

On the 6,412 held-out pairs (39,620 unit rows, 401 families), distance to the designer layout (lower is better):

unit distance element distance (pairs pooled / family-balanced) MAE cx / cy / w / h
uniform stretch 0.1886 0.1905 / 0.1260 0.068 / 0.066 / 0.103 / 0.059
ridge regression 0.1828 0.1822 / 0.1365 0.074 / 0.069 / 0.082 / 0.057
sklearn MLP, squared loss 0.1976 0.1993 / 0.1612 0.091 / 0.077 / 0.076 / 0.053
this model 0.1525 (5 seeds: 0.1524 ยฑ 0.0004) 0.1545 / 0.1081 0.063 / 0.061 / 0.062 / 0.043

It beats the stretch in 67% of held-out pairs (35% when the aspect changes by under 0.2, 71% from 0.2 to 0.7, 84% above 0.7). In the engine (3,000 pairs, 1,008 in test families; layout_eval.py): a learned candidate exists in 94% of test pairs, is valid in 96% of those, is the valid candidate closest to the designer in 53%, and the selector ships it in 29%; pick distance 0.2522 โ†’ 0.2352 (gain +0.0169, family-bootstrap 95% interval [+0.0132, +0.0209]; train families +0.0162). On our own sets (samples 46, synthetic 271, holdout 845 pairs) hard checks, fallbacks and all-text-at-floor counts are unchanged and 136 picks move to learned (text smaller in 43, larger in 8). A strong vision judge, shown each of those changed picks against the engine's own (both letter orders), preferred the learned pick 66 to 44 (110 decided). With the admission guard, as shipped: picked in 23.5% of test pairs, pick distance 0.2522 โ†’ 0.2429 (+0.0093 [+0.0062, +0.0127]), the changed picks' smallest text larger in 392 and smaller in 36 of 699; on our three sets 97 picks change (smaller text in 15, larger in 7), judged 54 to 17; on 90 fresh designs (1,900 pairs) 117 picks change, judged 71 to 27. Full tables, the exploration (features, sizes, losses), both judge rounds and visual notes: layout_report.md.

Limitations and risks

  • Designers are not the judge. Distance to the designer rewards what designers did: text kept near the area-preserving size, sparse canvases, content bleeding at the edges. The earlier judge rounds asked for larger text and filled canvases; the model inherits the designers' habits (top-heavy stacks on taller targets, small labels), and the rule score's fidelity term likes them. Visual review: tiny text at 160 x 600 and 728 x 90, a crowded 16:9 collage, a half-empty square stack among the changes.
  • Different distribution. Crello is VistaCreate social posts, flyers and banners with grey placeholder art and synthetic text; the organizers' briefs are brand banners with real photos, logos and copy. Nothing here has been checked on a held-out brief.
  • The guard's thresholds were chosen on round 6 and checked out of sample in round 7 (90 fresh designs, 168 comparisons): kept picks 71 to 27, reverted 15 to 28. The utilization axis replicated; the text axis alone did not (7 to 6).
  • The critic never saw these layouts. 10 of the 136 unguarded changed picks are the judge-distilled critic choosing a learned finalist (judged 6 to 4); the 8 other picks its finalist set moved revert through the guard.
  • Validity. 95% of generated candidates pass every hard check; the rest are last-resort layouts with text overlapping text (never shipped: the selector only takes valid ones). At large aspect changes (>= 0.7) 91%.
  • Local measurements only (macOS CoreText text measurement); the Docker (FreeType) run was not repeated for this model.
  • Trained on the train families only so that the held-out numbers are honest; retraining on all 1,316 families (train_layout.py) would add about 30% more data and cannot be evaluated on Crello afterwards.

Reproduce

.venv/bin/python tools/crello/layout_dataset.py                       # 5 s
OMP_NUM_THREADS=4 /opt/anaconda3/bin/python3 tools/crello/train_layout.py --skip-sklearn --export engine/models/layout.npz   # ~2.5 min
.venv/bin/python tools/crello/designer_eval.py --out output/crello/eval_learned                                      # ~1.5 min on 8 workers
.venv/bin/python tools/crello/layout_eval.py --base output/crello/eval_base/candidates.jsonl --new output/crello/eval_learned/candidates.jsonl
.venv/bin/python -m unittest tests.test_learned

Standalone forward pass (no engine): standardise the 33 features with mu and sd, apply tanh(x @ w0 + b0), tanh(. @ w1 + b1), . @ w2 + b2, add the result to the first four features (the source box as cx, cy, w, h shares).

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support