Preparing a photograph
ariadne inks by darkness. That single fact decides which photographs work,
and it is the thing to reason from when one does not.
A tour can only draw the tonal range the photograph actually spreads across.
Everything below is about getting a picture’s tones into a range the renderer
can use — before touching --gamma, which is a weight control, not a range
control.
Symptom: an even scribble with no figure
The subject and its background share a tone. There is no signal saying where the subject is, so ink lands everywhere.
Measured on one photograph: a brown sweater at luminance 0.271 against a wall
at 0.279 — a gap of 0.008. No --gamma, --floor, or point count recovers a
figure from that, because the information is not in the image.
Fix: a mask. See Masking a photograph. The mask supplies the figure/ground division the photograph lacks.
a-tsp photo.jpg --hard # segment, inspect the overlay
a-tsp photo.jpg --hard --go # render
Symptom: a flat silhouette, filled but unmodelled
The subject is separated, but reads as a solid shape with no interior form.
Its tones are bimodal rather than spread. On one photograph the subject’s median luminance was 0.165 while the 95th percentile was 0.865: 59% of the subject sat below 0.2, all mapping to nearly the same ink, while the lit 16% got almost none. The modelling in between was crushed out.
Gamma cannot fix this. Raising it empties the lit areas further (16% → 23% of the subject below ink 0.1 going from γ2.5 to γ4.0); lowering it floods the darks. You are moving a compressed range around, not expanding it.
Fix: redistribute the range first.
a-equalize photo.jpg masks/photo_mask.png prepared.png --strength 0.6
ariadne --mask masks/photo_mask.png --partition labels --groups 2 \
-g 2.0 --floor 0.06 -w 1.5 prepared.png
equalize.py equalises by rank within the masked subject, so every tonal
band ends up with a similar share of the pixels. The background is untouched,
so the mask still separates. --strength 0.6 keeps some of the photograph’s
own lighting; 1.0 opens up the most anatomy but flattens the light direction.
Then tune --gamma for weight — second, not first.
Symptom: one bright region drops out of the picture
Part of the subject is so light that it earns no ink and reads as a hole.
This is not a background problem. Darkening --background would give you a
dark field with a hole in it; the region has no lines, not insufficient
contrast. And note that equalisation makes it worse — an already-bright
region gets pushed further toward white:
| treatment | ink in that region | fraction with no ink |
|---|---|---|
| original | 0.270 | 42% |
--strength 0.6 |
0.152 | 52% |
--strength 1.0 |
0.095 | 62% |
Two fixes, with different costs.
Cap the highlights — squeeze the whole subject into [0, 0.75] so nothing
reaches paper white. Simple, and it fills the figure completely, but it
compresses the midtones where the modelling lives, so the result is flatter.
Per-region tone control — the better answer when the image is worth it. Split the subject into a shadowed region and a lit one, give each its own tonal window, and let each be its own subtour:
shadow = subject & ~lit
out[shadow] = equalise(lum[shadow], 0.6, 0.05, 0.62) # keep it dark
out[lit] = equalise(lum[lit], 0.9, 0.30, 0.80) # pull DOWN to earn ink
Paint the two regions as separate mask colours, then render with one ink each:
ariadne --mask masks/photo_3group.png --partition labels --groups 3 \
--ink '#f2f4f5,#141414,#5a6068' -n 120000 -g 2.0 --floor 0.03 photo_pertone.png
The lit region ends lighter than the shadowed one, so the light direction survives instead of being flattened. Measured on one photograph, this took the fraction of subject with no ink from 52% to 0% in shadow and 4.8% in the lit region, while keeping the interior modelling the cap approach loses.
The mask sets grouping; the image sets density. That division is the whole technique. Anything you want drawn differently within a group is done by editing a copy of the photograph, not the mask — the same move that makes a metal vessel read against the smoke it sits in.
Provenance
Every generating script writes <output>.meta.yaml beside its output,
recording the source, content hashes, tool versions, and the exact commands to
rebuild it. Read it when you want to know how a picture was made; re-run its
steps in order to remake it.
ariadne is a Rust binary and writes no sidecar of its own, so run it through
tools/draw.py when you invoke it directly — it passes every argument through
unchanged and records the call. Otherwise the parameters live only in your
shell history.