The line
Up to here a face is a list of point sequences. What turns that into a drawing is three decisions in the pen – and all three depend on every frame being drawn anew.
macheStift gets a drawing surface, a seed, an ink colour,
a scale factor and a beat. Back come three tools:
zug, flaeche, punkt. The whole
style needs no more. All measures are unit measures – the caller has
long since scaled the surface to head size, so a width of
0.024 is a good two percent of the head height.
Every line is a surface
No ctx.stroke(). A stroke is a ribbon:
the centre line plus half a width to each side, as a single filled
polygon. The reason is the width itself – it is supposed to change
within a stroke: thin on touching down, full in the middle,
thin on lifting off.
A line in canvas has a width, and the same one everywhere. Whoever
wants the taper anyway needs one stroke() per segment. At
nine heads times forty strokes times sixty frames a second, that is
the difference between smooth and sluggish.
The pressure curve is spitz: 0 means blunt ends, 1 means
running out to a point, the default is 0.75. The sine term is
deliberately kept flat – a line twice as thick in the middle as at the
ends looks like a brush, not like a pen.
The ribbon is also the reason flaeche() exists as a
tool of its own and not just as a closed stroke: a surface has no
width, it has an edge. What happens to it in printing is in the
chapter The press.
The warp has a name
A hand-drawn line does not hit its intended curve. The obvious implementation: a random stream from which every stroke draws its deviation. It is wrong, and not by a little.
Symptom: During a blink it is not the eye that changes shape but half the face – nose, mouth and cheeks jump to a different drawing for three frames.
Cause: The warp hung off the call order. A closed lid is one stroke instead of four; everything drawn afterwards draws its random numbers offset by four places and is therefore a different line.
Fix: The warp hangs off a TRACE –
a name: "kontur", "auge-l". The same name
plus the same seed gives the same tremor in every frame, no matter
what was drawn before it or not drawn at all.
From the name an FNV hash makes a start value, and from the start
value a field of 52 support values. It lives in a Map on
the pen: the same trace is needed several times per frame, and a head
does not have a hundred of them.
The line boils
A hand drawing that moves is animation: every frame is a drawing of
its own, and none of them hits the previous one exactly. So every
trace gets a BEAT as well –
TAKT_JE_SEKUNDE = 8. Within one beat everything stands
still, between two beats every line slips a little.
The beat does not come from the pen but from above – the style
computes it when drawing each head:
takt = Math.floor(zeit * TAKT_JE_SEKUNDE + d.taktVersatz).
The taktVersatz is rolled per head. Without it nine heads
restart at the same moment, and a sheet twitching in lockstep looks
like a display fault.
Inside the pen two streams are mixed for this: one hangs only off the seed – the hand – and one off the beat as well – the frame. The mix is 0.62 to 0.5. With only the frame stream the line jumps somewhere else at every beat; with only the hand it stands still.
Rolled anew sixty times a second would be noise, eight times is a pen. The slider shows both.
koch hangs off the length
Not every stroke may boil equally. A mouth that changes length and arc
eight times a second is no longer a pen but restlessness. So
zug() first measures the length of the path:
| Length | koch | who |
|---|---|---|
| below 0.4 | 0 – the hand only | mouth, brow, nose, spike |
| 0.4 to 1.2 | mixed | lid arcs, ears |
| from 1.2 | 1 – fully on the beat | contour, hair mass |
The same length also controls the strength of the warp
(kurz, capped at half the amplitude): the warp is a
measure in head space, and the same amount is a meander on a long
contour but a jump of the whole line on a short brow.
Beside that there is ruhig as a hard switch. Eye rings,
hatching and forehead lines are pale and fine – their boiling would
not read as a tremor of the hand but as a flicker of the picture.
The second layer
Under every stroke lies the same stroke once more: half as wide, at
32 % opacity – and with a warp of its own (trace
name~, amplitude 1.7 times plus 0.004). That is precisely
where the pencil lives: the second layer does not lie on the
first, but beside it.
einlagig: true switches it off. That applies to the edges
of colour surfaces and to trimmings, where the doubling would only be
mush – the pale lower lip in the grin, for instance, is a hair's
breadth wide, and a second layer beside it turns it into a stain.
From point sequence to path
Before a ribbon comes about at all, the point sequence becomes an
evenly sampled path: entdoppeln, then
catmull, then sampling by arc length. A mouth made of
three points has to become an arc, not a kink.
Symptom: Short lines – brows, mouth corners – tremble at their ends, although the warp hangs off a fixed trace and ought not to boil at all.
Cause: The sum of steps hits the end point only up to rounding. Whether the last step just fits or just does not tips with every tiny change in length – and the stroke is up to one step longer or shorter from frame to frame.
Fix: The end point belongs always. If the last point set lies further than half a step away, the end point is appended; otherwise it replaces it.
Closed strokes run 3.5 % past their start. The place where the hand set down stays visible – a circle that runs back into itself exactly is an ellipse from a drawing program.
Meander and scratch
The warp itself comes from two waves: a coarse one with 12 support values (the meander) and a fine one with 40, weighted at 0.35 (the scratch). Both are interpolated cubically.
The meander alone would look like a rubber hose, the scratch alone like a short circuit.
A dot is not a circle
A filled circle is the place where a drawing looks like vector
graphics fastest: ctx.arc hits the curve exactly, and
that is exactly what the eye recognises – with a pupil, a freckle and
a stubble dot alike.
So punkt() goes the same way as any other stroke: a
slightly oval, tilted ring, warped with this pen's noise, and beneath
it a pale seam from the bleed. Four details decide:
| Detail | without it |
|---|---|
a separate spur per dot |
two pupils with the same bulge in the same place – twins |
| edge not computed as closed | the noise runs from 0 to 1 and at the end does not meet the start; that is where the set-down mark sits |
| warp capped at the radius (0.15 r, at most 0.007) | the freckle becomes an amoeba |
ohneSaum for dots lying ON something |
the highlight in the eye gets a pale rim and looks like a stain |
A dot does not boil. It always draws its trace calmly, without a beat. A pupil that sits somewhere else at every beat is a tremor, not a line.
The feld is the last ingredient: the four values a field
carries anyway (ex, ey, fx,
fy, see Space). Without them
the dot draws in screen space – the lid arcs of a turned head squash,
the pupil inside them stays a full circle and juts out past the
contour at the side. The field is left out for everything that is
not on the head: an antenna ball stands in space, no cheek
carries it.
A hand draws large with the same pen
A head in the close-up is three times as large as in the grid. If the line simply grew with it, it would be three times as thick there – a felt-tip. But it must not stay exactly the same either: the same pixel line that is right when large is a hair in the grid. The comment below says how the factor mediates between the two.
Exponent 0.4, clamped to 0.62 up to 1.3, reference size 95 – the size a head has in the grid. In the grid the factor is therefore 1; in the close-up it sinks towards the clamp.
The factor is set once when the pen is created
(strichFaktor(at.mass)) and multiplies every width after
that. No drawing step has to know about it.