Illustracy

Illustracy — 4. Algorithms and formulas (4/4): Rough · PSD · preview · manual correction

← README · Section 4 1 · 2 · 3 · 4 · 한국어


4.14 Rough generation

Rough 1 · rough 2 go through the same steps and differ in the values of the table below and, from 0.57, in rough 2 using step 11 (tracing with a G-pen) instead of steps 4 · 6. They use their own random numbers (mulberry32, seed 20261011 for rough 1 and 20261012 for rough 2), so the same picture always gives the same roughs regardless of the other layers. Below, $s' = \max(1, s)$ is the size factor for stroke width · offsets, $s_g = \max(0.5, s)$ the size factor for blurs · tolerances · length thresholds (small pictures hold the same shape in fewer pixels), 𝑢 is a new random number in $[0, 1)$ each time it is used, and 𝑣 one in $[-1, 1)$.

Note — Why: A rough should wobble like a human stroke, yet the same picture must always give the same rough so results can be compared and reproduced. Separate size ratios keep line widths and shape thresholds from becoming too thin or too large on small pictures. How it works: mulberry32 is a pseudorandom generator that adds a fixed constant to one 32-bit state and mixes it, so the same seed gives the same sequence. A seed reserved for the rough makes it independent of random use in other layers. $s' = \max(1, s)$ keeps line widths from shrinking below 1× on small pictures, and $s_g = \max(0.5, s)$ shrinks length thresholds at most to half. 𝑢 is uniform on $[0, 1)$ and $v = 2u - 1$ on $[-1, 1)$. References: R48 Ettinger 2017 · a rule set by this program

Value Rough 1 (concept) Rough 2 (refined) Step
Part blur $\sigma_p$ · large-part threshold $a_{min}$ $6s_g$ · 0.8% of the character area $2.5s_g$ · 0.2% 1
Inner color boundaries · silhouette where line art exists drawn if line art lies under them · drawn not drawn · drawn by the line art 2
Composition offset rotation $0.05v$ rad, scale $1 + 0.04v$, warp 3.5% of the character size none 3
Inner lines (line alpha threshold · length · density) 0.3 · at least $40s_g$ (where sparse on a wider view, $12s_g$) · at most 0.11 0.25 · at least $12s_g$ (outside the character, $25s_g$) · at most 0.18 (where sparse on a wider view, even if dense) 4
Inner lines near mass lines dropped drawn 4
Background composition not drawn brightness edges of at least $40s_g$, strength 0.45 5
Skin color · guessed face areas a* at least 4 · one mass in pictures with no face found at all a* at least 2 and a* + b* at least 6 · every mass without a found face 6
Stroke fitting break at bends over 0.8 rad, cubic (tolerance $3s_g$, faces $1.2s_g$) break at bends over 0.9 rad, fit cubics (tolerance $3.5s_g$, faces $0.7s_g$) between corners (0.65 rad), then join pieces bending 1.2 rad or less into one stroke 7
Stroke length · overlap $(110 + 60u)s_g$ · 18% $(90 + 50u)s_g$ · 10% 7
Mass-line radius · offset (first · later passes) · silhouette / inner strength $1.2s'$ · $0.8s'$ · $2.2s'$ · 0.9 / 0.7 $1.7s'$ · $0.3s'$ · $0.5s'$ · 1 / 0.85 8
Inner line radius · passes · strength factor $1.15s'$ · 2 · 0.8 $1.6s'$ · 1 (2 with 50%) · 1 8
Face stroke radius · darkness · offset $1.1s'$ · $0.35 + 0.65\bar\ell$ · $0.35s'$, $0.9s'$ $1.8s'$ · $0.8 + 0.2\bar\ell$ · $0.12s'$, $0.35s'$ 8
Pressure floor at stroke ends $p_0$ · construction line darkness 0.18 · ×1.3 0.35 · ×0.5 8, 9

Note — Why: Rough 1 (concept) blocks in large masses and composition with large wobble, while rough 2 (refined) is a committed line close to the real outline, so the two layers use different values. How it works: All lengths and radii are multiples of the size ratios ($s_g$, $s'$), so the same shapes appear at any picture size. Rough 1 has larger blur, tolerance and offset, longer strokes and more overlap; rough 2 the opposite. References: a rule set by this program

Face area 𝐹: for each naming · folder face (step 9 of face finding in 4.5) with eye center $(c_x, c_y)$, eye distance 𝑑, unit eye direction 𝐞 and the downward direction 𝐟 perpendicular to it, the pixels with $\lvert u\rvert \le 1.1$ and $-0.6 \le v \le 1.6$ where $u = (\mathbf p - \mathbf c)\cdot\mathbf e / d$, $v = (\mathbf p - \mathbf c)\cdot\mathbf f / d$ (from eyebrows to chin).

Note — Why: Faces must be drawn differently from elsewhere (no spur pruning, finer), so the face area is fixed first. How it works: It is the rectangle from brows to chin in face-frame coordinates $(u, v)$ (4.5), obtained by dot products with the eye distance 𝑑 and the orthonormal eye basis $(\mathbf e, \mathbf f)$. The same area is found whatever the face size or tilt. References: R35 Strang 2016

  1. Mass map: character pixels (alpha > 0.02 and not the background tone) are split into base-color groups, and groups of at least $a_{min}$ of the character area $N_c$ and at least $150s_g^2$ pixels are the large parts. The background · character indicator functions are blurred by $\sigma_p$ and the places where the character side is larger form the inside of the silhouette (the silhouette is set by the whole character only); inside it, each pixel takes whichever of the large parts and “the rest” (small pieces · frills) has the largest blurred indicator, giving the map $L_2$. Painting with the large parts only would leave places full of small pieces empty like background and break the masses apart.
    • Note — Why: An artist’s rough draws large masses (head, body, large areas of clothing) first, not details. A map that drops small pieces and keeps only large parts, smoothed, is needed. How it works: Gaussian-blurring each part’s 0/1 indicator with $\sigma_p$ gives the share of that part around each pixel. Painting each pixel with the largest value (argmax) is a majority filter that removes small bumps and smooths boundaries. Counting small pieces together as one “rest” group keeps areas full of small pieces from emptying like background. The large-part threshold $150s_g^2$ is an area, so it scales with the square of the size ratio. References: R14 Gonzalez · Woods 2018 · a rule set by this program
  2. Mass lines: where the $L_2$ of a pixel and its right · lower neighbor differ, it is a silhouette if one side is outside the character and an inner boundary if the two large parts’ base colors have $\Delta E \ge 15$ (except where they touch the rest, and inside 𝐹). Lines are traced as in step 7 (pieces of at least $20s_g$ pixels, spurs trimmed by $5s_g$ steps, loops under $30s_g$ pixels dropped), and an inner boundary line is used only if at least 60% of its points lie within $r_k = \max(2, 2.5s_g)$px of line art (line alpha ≥ 0.25). Color boundaries without a line, such as the same hair split into light and shadow, are not in an artist’s rough. Rough 2 draws no inner boundaries (in complex pictures they become a cell-like polygon mesh). Rough 2, as if redrawing the real outline over rough 1’s masses, cuts each silhouette line at points with line art within $r_k$ and keeps as mass lines only the stretches without line art (at least $10s_g$). Outlines where line art exists are drawn by the line-art lines of step 4.
    • Note — Why: The boundaries of the mass map become the rough’s mass lines, except color boundaries with no line (the same hair split by light and shadow), which are not in an artist’s rough. How it works: Pixels whose map value differs from the right or lower neighbor are boundaries. $\Delta E \ge 15$ between two parts’ base colors is the threshold for clearly different colors, and inner boundaries are used only if 60% of their points lie within $r_k$ of actual line art, keeping only boundaries that have lines. Rough 2 cuts silhouette runs where line art exists and leaves them to the line-art strokes (4), keeping only runs without line art as mass lines. References: R7 CIE 2004 · a rule set by this program
  3. Composition offset (rough 1): with the character’s center of mass 𝐦 and the diagonal 𝑆 of its bounding box, every stroke point is moved to $\mathbf p' = \mathbf m + z\,R_\theta(\mathbf p - \mathbf m) + 0.035S\,\big(n_x(\mathbf p),\ n_y(\mathbf p)\big)$ before drawing, where $n_x, n_y$ are value noise (4.2) with cell size $\max(8, 0.45S)$.
    • Note — Why: Hand-drawn concept roughs are slightly off in proportion and tilt. Moving every stroke by the same transform keeps strokes consistent with each other while the overall composition drifts slightly. How it works: $\mathbf m + zR_\theta(\mathbf p - \mathbf m)$ is a rotation ($R_\theta$, at most about 2.9°) and scaling (±4%) about the center of mass, and the added $0.035S(n_x, n_y)$ is a smooth displacement field from value noise (4.2), bending each place slightly differently. The cell size is 45% of the character diagonal, so the warp acts on large parts. References: R35 Strang 2016 · R47 Ebert et al. 2003
  4. Inner lines: the center lines of pixels outside 𝐹 with line alpha at least the threshold are traced. Rough 1 drops lines with more than half their points within $4s_g$px of a drawn mass line. Density: the share ρ of center-line · mass-line pixels in a window of radius $r_d = \max(2, \mathrm{round}(10s_g))$, averaged along the line, is compared as $\rho\,(2r_d + 1)/21$, which in terms of the spacing of parallel lines does not depend on size. Lines in dense places like patterns · hatching · hair grain are not in a rough and are dropped, but lines whose same value in a 4× wider window ($4r_d$) is at most 0.12 (lines gathered in small clusters that are sparse on a wider view: eyes · mouths · fingers) are kept even if short or dense. Lines shorter than the outside-the-character threshold (table) are used only if at least half their points lie inside the silhouette (dropping signatures · background lettering). Pattern loops: for each closed line with a perimeter up to $60s_g$ pixels, its center and mean radius 𝑟 are measured; if at least two other closed lines have a radius ratio within 2× and a center distance of at most $6\max(r_i, r_j)$, it is taken as a repeated pattern (sweater pattern · snowflakes · petals · railing cells · window rows) and not drawn.
    • Note — Why: A rough has only major lines, not dense ones such as patterns, hatching or hair strands. Short dense lines are filtered out, but important small clusters such as eyes, mouths and fingers must stay. How it works: The share ρ of line pixels in the window is about $1/g$ for parallel 1px lines spaced 𝑔 apart. Multiplying by $(2r_d + 1)/21$ normalizes to the reference window (21px across at 𝑠 = 1), so the value stays the same when the picture grows and both spacing and window grow with it. Lines that are sparse in a window 4 times wider form a small cluster in an otherwise empty area and are kept. Pattern rings are detected as three or more nearby closed lines of similar radius. References: a rule set by this program
  5. Background composition (rough 2): the lines of the background picture have moved to the background layer, so the brightness of background-tone pixels (0.299R + 0.587G + 0.114B) is blurred by $1.5s'$, the magnitude · direction of the Sobel gradient are measured and only maxima along the gradient direction are kept, and edges are obtained by starting at magnitude 0.045 and continuing into neighbors of at least 0.02 (hysteresis) (excluding within $3s_g$px of drawn mass lines). These are traced as in step 7, and only lines of at least $40s_g$ (closed lines at least $80s_g$) that pass the density · pattern-loop filters of step 4 are drawn once with strength 0.45 (halftone · grass · cloud texture drops out).
    • Note — Why: Rough 2 also needs composition lines for the background. Only large lightness boundaries (horizon, wall corners) should remain, without texture or halftone. How it works: Measuring gradient magnitude and direction with the 3×3 Sobel operator, keeping only points larger than their neighbors along the gradient (non-maximum suppression), and linking from 0.045 through neighbors above 0.02 (hysteresis) is the same sequence as Canny edge detection. A blur of $1.5s'$ reduces noise, and length thresholds and the density filter remove short texture. References: R19 Sobel · Feldman 1968 · R18 Canny 1986
  6. Face details: inside 𝐹 and the guessed face areas, the center lines of pixels with line alpha ≥ 0.25 are traced without spur trimming down to 4-pixel pieces, dropping only loops under $8s_g$ pixels.
    • Guessing face areas: skin-colored groups (lightness ≥ 55, a* from the threshold to 30, b* 0–35) are closed with radius $r = \max(3, 0.035\sqrt{N_c})$ (widened and narrowed by distance transforms), and among the resulting masses, those covering at least 0.4% (rough 2: 0.2%) of the character and with dark lines (line alpha ≥ 0.5) in their non-skin holes covering at least 1% of the mass are used, by their inside (more than $2s_g$ from the edge). In pictures with no face found at all, the one with the most lines in its holes is taken as the face; rough 2 also uses every mass without a found face (unfound faces · hands).
      • Note — Why: Characters whose faces were not found (profiles, small faces) still need facial detail in the rough, so places with dark lines (eyes, mouth) inside a skin-colored blob are guessed to be faces. How it works: Closing the skin blobs with radius 𝑟 (dilation then erosion) fills holes such as eyes and mouth so the face becomes one blob; growing and shrinking by distance transform closes with a round structuring element quickly. 𝑟 is proportional to $\sqrt{N_c}$ (the square root of the character area, a length), so it matches character size. Requiring dark lines in the holes to be at least 1% of the blob excludes bare arms and legs. References: R15 Soille 2003 · R21 Borgefors 1986
    • Contours of painted shapes: in the face-area lines, thick cores whose inner radius (distance transform to outside the line) is at least $\tau = \max(1.5,\ 2.5s_g)$ are grouped into blobs, and only those whose largest radius is $\rho_{max} \ge 2\tau$ and whose long side is at most $4\rho_{max} + 4$px (gathered roundly) are chosen (thick lines · line ends drop out). The shape formed by the disks of each core pixel’s inner radius (holes filled) is removed from the lines, and its outer contour is traced as a closed line and drawn as face strokes. Thinning a painted iris to a line would leave a single dot. Thin attached eyelash · eyelid lines are still drawn as center lines.
      • Note — Why: Reducing a thickly painted shape such as a filled pupil to a thin centerline leaves a single dot. Its outline must be drawn instead, as an artist would. How it works: The distance transform gives each line pixel its distance to outside the line (inner radius). Cores with radius at least 𝜏 are thick paint, and if short relative to the largest radius (at most $4\rho_{max} + 4$) the shape is compact and round. Collecting a disk of each core pixel’s radius restores the shape, the property of the medial axis transform (Blum) that the union of maximal disks rebuilds a shape. Only that shape’s outer boundary is drawn, as a closed line. References: R37 Blum 1967 · R21 Borgefors 1986
  7. Lines and strokes: the material is widened by 1px with a 3×3 window to close gaps, a 1px center line is obtained by **Zhang–Suen thinning**, and short spurs are trimmed by deleting end points a set number of steps and growing them back (not inside 𝐹). End points (at most one 8-neighbor) and junctions (three or more 0 → 1 changes around the 8-neighborhood) are nodes, chains are taken between nodes, and pairs of ends within $3s' + 1.5$px whose outward directions have a dot product under $-0.766$ (continuing straight within 40°) are joined into one line.
    • Note — Why: Line-art pixel blobs must become lines (ordered points) that a brush can follow before strokes can be made. How it works: After a 3×3 dilation bridges 1px gaps, Zhang–Suen thinning produces a 1px centerline that preserves connectivity. Erasing endpoints for a fixed number of steps and regrowing them removes only short spurs. Going once around the 8 neighbors, a count of 0 → 1 changes (crossing number) of 3 or more marks a junction. If the outward directions of two ends have a dot product below $-0.766 = \cos 140^\circ$, they nearly face each other (within 40° of a straight continuation) and are joined into one line. References: R36 Zhang · Suen 1984 · R14 Gonzalez · Woods 2018
    • With 𝑛 points in a line, a moving average with half-window $\mathrm{clamp}(\mathrm{round}(n/25), 2, 6)$ (1 for rough 2’s mass · inner lines, 2 for face lines) is applied twice and the line is resampled at 1.5px.
      • Note — Why: Thinned lines are jagged with pixel steps, so they are smoothed and resampled evenly. How it works: Applying a moving average of half-window $\mathrm{clamp}(\mathrm{round}(n/25), 2, 6)$ twice amounts to a triangular weighted average that removes the steps; longer lines get wider windows. Resampling at 1.5px keeps curvature and length calculations independent of point density. References: R14 Gonzalez · Woods 2018
    • Bends: a line is cut at points where the angle between the directions $i - 4 \to i$ and $i \to i + 4$ exceeds the threshold, is not smaller than at the neighbors, and lies at least 4 points from the previous cut. People lift the pencil and start a new stroke at a bend.
      • Note — Why: People lift the pencil at corners and start a new stroke, so strokes are cut at corners to look drawn. How it works: A point counts as a corner if the angle between direction vectors 4 points before and after (the turning angle) exceeds the threshold, is a local maximum, and lies at least 4 points from the previous corner. References: a rule set by this program
    • Gentle curves: between bends, cubics $x(t), y(t)$ in arc length $t \in [0, 1]$ are fitted by least squares; if the error at the farthest point exceeds the tolerance, the line is split there and fitted again (under 9 points it is kept as is).
      • Note — Why: Lines between corners are turned into smooth curves like brush strokes. How it works: With a parameter 𝑡 set to arc length scaled to 0–1, $x(t)$ and $y(t)$ are each fitted by a cubic polynomial with least squares (normal equations). If the farthest point’s error exceeds the tolerance, the line is split there and refitted recursively, so it is fitted with the fewest pieces within the tolerance. References: R38 Schneider 1990 · R43 Björck 1996
    • Corners of rough 2: between bends the line is first reduced to straight segments within the tolerance by splitting at the farthest point (Ramer–Douglas–Peucker), and vertices next to a segment shorter than $10s_g$ are removed, least deviating first, up to $2\times$ the tolerance (removing small teeth). The line is split at remaining vertices bending more than 0.65 rad and the cubic above is fitted in between. Closed lines with a perimeter up to $60s_g$ (eyes · buttons) are not split at corners but fitted smoothly with the face tolerance.
      • Note — Why: Rough 2 is a committed line: corners must turn crisply, the stretches between them must be smooth, and small teeth from pixel noise must go. How it works: Ramer–Douglas–Peucker repeatedly splits at the point farthest from the chord between the ends until within tolerance, reducing a line to a polyline with few vertices. Vertices next to short segments are removed in order of smallest deviation to remove teeth, and the line is split only at vertices turning more than 0.65 rad, with cubics fitted between them. References: R41 Ramer 1972 · R42 Douglas · Peucker 1973 · R38 Schneider 1990
    • Continuous strokes (rough 2): among the fitted pieces, those whose end points meet with a bend of 1.2 rad or less between them are joined into one stroke without lifting the pencil. Corners are turned within one stroke, and a new stroke starts only at sharp bends.
    • A curve longer than the stroke length is split into $k = \min(3, \lceil \text{length} / \text{stroke length}\rceil)$ strokes overlapping their neighbors by the overlap share (one curve drawn with 2–3 strokes).
      • Note — Why: People draw a long curve not in one go but in 2–3 strokes. How it works: $k = \min(3, \lceil \text{length}/\text{stroke length}\rceil)$ sets the number of strokes (at most 3), and neighboring strokes overlap by the overlap ratio so the joins do not break. References: a rule set by this program
  8. Drawing: each stroke is drawn the set number of times (mass lines 2 (3 with 30%), face strokes 2 (3 with 45%), construction lines 1–2). The 𝑘-th pass ($k = 0, 1, 2$) is:
    • Start · end: with $\ell_e = \min(9s_g, 0.2\,\text{stroke length})$, the first pass has $e_0, e_1 = -0.6u\,\ell_e$ and later passes $e_0, e_1 = (1.6u - 0.6)\,\ell_e$. Positive values start that much later or stop that much earlier (falling short), negative values extend that much along the tangent (overshooting). Face strokes never fall short.
      • Note — Why: Hand-drawn strokes do not end exactly; they fall a little short or overshoot. How it works: The first pass uses $-0.6u\ell_e$, so it always overshoots a little (0–0.6×), and repeat passes use $(1.6u - 0.6)\ell_e$, between −0.6× and 1×, either overshooting or falling short. Capping $\ell_e$ at 20% of the stroke length keeps short strokes from vanishing. References: a rule set by this program
    • Sideways offset: along the stroke with $t \in [0, 1]$, the stroke is moved along the normal by $o(t) = g_0 + g_l(t - 0.5) + g_1\sin(\pi t + \varphi)$, with $g_0 = 0.9\,\sigma_k v$, $g_l = 1.6\,\sigma_k v$, $g_1 = 1.4\,\sigma_k v$, $\varphi = \pi u$ and $\sigma_k$ the offset of the table (first · later passes).
      • Note — Why: A repeat pass runs slightly off the original stroke and, because of wrist motion, tilts to one side or bows in the middle. How it works: The normal offset $o(t)$ is a constant (shift) + a linear term (tilt) + a half-period sine (bow). All three amplitudes are proportional to the table offset $\sigma_k$ and the same random 𝑣, and the phase 𝜑 varies the bow per stroke. References: a rule set by this program
    • Pressure: with $\tau(t) = \mathrm{smoothstep}(0, t_a, t)\,\big(1 - \mathrm{smoothstep}(1 - t_b, 1, t)\big)$, $t_a = 0.12 + 0.18u$, $t_b = 0.15 + 0.2u$,

      $\displaystyle \mathrm{pr}(t) = P_k\,\big(p_0 + (1 - p_0)\,\tau(t)\big)\,\big(0.88 + 0.12\sin(2\pi f t + \psi)\big),\qquad P_0 = 0.75 + 0.3u,\quad P_{k \ge 1} = 0.5 + 0.3u,\quad f = 1 + 2u,\quad \psi = 2\pi u$

      $p_0$ is the pressure floor of the table (rough 2 is the settled line, so its ends taper less).

      • Note — Why: Pencil strokes thin and fade at the start and end as pressure eases, and pressure also wavers along the way. How it works: $\tau(t)$ multiplies a rising and a falling smoothstep into a trapezoid-like shape, 0 at both ends and 1 in the middle. The pressure floor $p_0$ keeps the ends from going fully to 0, and $0.88 + 0.12\sin(2\pi ft + \psi)$ is a ±12% waver rising and falling 1–3 times per stroke. The first pass is stronger than repeats ($P_0 > P_{k\ge1}$). References: R47 Ebert et al. 2003 · a rule set by this program
    • Width: with the stroke radius 𝑅 (table) and the end width ratio $\lambda = 0.45 + 0.4\,\mathrm{clamp}\big((R/s' - 1.5)/2,\ 0,\ 1\big)$, $r = R\,\big(\lambda + (1.3 - \lambda)\min(1, \mathrm{pr})\big)$.
      • Note — Why: Strokes get thicker the harder the pressure. How it works: $r = R(\lambda + (1.3 - \lambda)\min(1, \mathrm{pr}))$ interpolates linearly from $\lambda R$ at zero pressure to $1.3R$ at full pressure. The end-width ratio 𝜆 rises from 0.45 to 0.85 as the brush gets thicker (larger 𝑅/𝑠′), so thick brushes taper less. References: a rule set by this program
    • Darkness: $a = o_k\,(0.35 + 0.65\,\mathrm{pr})$, $o_k = (1 \text{ or } 0.75 \text{ for later passes}) \cdot w$. The strength 𝑤 is $0.2 + 0.8\,c^2$ for mass lines (𝑐 the strongest boundary strength within $\mathrm{tol} + 2$px of the stroke: silhouette · inner), $0.2 + 0.8\min(1, \bar\ell)\cdot\text{the table's strength factor}$ for inner lines, the table’s value for face strokes (ℓ̄ the mean line alpha along the stroke), and the table’s strength for the background composition.
      • Note — Why: Strokes on clear boundaries and dark lines are drawn darker and weak ones lighter, so important lines read first even in the rough. How it works: Opacity is linear in pressure ($0.35 + 0.65\,\mathrm{pr}$) times the strength 𝑤. For mass lines $0.2 + 0.8c^2$ squares the boundary strength 𝑐 so weak boundaries fade more; inner lines follow the mean line alpha ℓ̄ along them. Repeat passes are 0.75×, so overlaps darken gradually. References: a rule set by this program
    • Painting: each pair of neighboring points is drawn as a round-capped segment along which 𝑟 · 𝑎 change linearly. With 𝑑 the distance from a pixel center to the nearest point of the segment (parameter $t^*$), the coverage is $c = \min\big(1,\ r(t^*) + 0.5 - d\big)$ (not covered if 0 or less) and the value $c \cdot a(t^*)$ (anti-aliasing). Within one pass each pixel takes its largest value 𝑥, and passes stack by screen: $\mathrm{acc} \gets 1 - (1 - \mathrm{acc})(1 - x)$.
      • Note — Why: Strokes are drawn smoothly without staircase, and places stroked several times darken gradually like pencil. How it works: From the distance 𝑑 of a pixel center to the segment and the radius $r(t^*)$ there, $c = \min(1, r + 0.5 - d)$ approximates coverage falling from 1 to 0 across a 1px band, giving anti-aliasing (a round-capped segment is a capsule). Within one pass the maximum is used, so overlapping neighboring segments do not darken, and passes accumulate by screen $1 - (1 - \mathrm{acc})(1 - x)$, which never exceeds 1 yet darkens with each overlap. References: R14 Gonzalez · Woods 2018 · R3 ISO 32000-1 2008
  9. Face construction: among the character faces (chosen as in the character split of 4.8), faces where at least three of the four places cheeks $(\pm0.6, 0.7)$ · under the nose $(0, 0.5)$ · chin $(0, 1.1)$ are skin colored (lightness ≥ 55, a* · b* −2 to 35) or with a score ≥ 12.5 (if none, the highest-scoring such face), and only if the face center lies on the character, get a skull circle (center $(0, -0.5)$, radius $1.3d$, three arcs of $2\pi/3 + 0.3$ rad), a vertical center line ($v = -1.9$ to $1.9$) and an eye line ($u = -1.5$ to $1.5$), drawn faintly (circle 0.3, lines 0.25, times the table’s factor).
    • Note — Why: Before drawing a face, artists set its construction with a skull circle, a center line and an eye line. How it works: In face-frame coordinates (units of 𝑑), a circle of radius $1.3d$ is drawn as three broken arcs ($2\pi/3 + 0.3$ rad each), plus a vertical center line and a horizontal eye line, lightly, in the Loomis manner. It is drawn only for faces with skin at three of four points or a high score, so fake faces get none. References: R39 Loomis 1956 · a rule set by this program
  10. Alpha: $a = \min\big(1,\ \mathrm{acc}\,(0.92 + 0.08\,n)\big)$, where 𝑛 is value noise (4.2) with cell size $2.5s'$, a soft pencil grain 2–3px in size. From 0.57 rough 1 (pencil) adds noise $n_f$ with cell $1.1s'$: $a = \min\big(1,\ \mathrm{acc}\cdot\max(0,\ 0.74 + 0.18n + 0.22n_f)\big)$ (paper grain), and rough 2 (G-pen) has no grain: $a = \min(1, \mathrm{acc})$. Pixels with $\mathrm{acc} < 0.01$ are left empty and the 8-bit alpha is $\mathrm{round}(255a)$. Rough color (default #2F6FE0), opacity $115/255 \approx 45\%$, hidden. The two layers go into the [Rough] folder with rough 1 below and rough 2 above.
    • Note — Why: Pencil strokes have a mottled 2–3px grain from the paper texture. How it works: The accumulated value acc is multiplied by 0.92–1× using value noise 𝑛 with cell size $2.5s'$. Values below 0.01 are left empty and the rest rounded to 8 bits. The layer opacity $115/255 \approx 45\%$ keeps it faint like an underdrawing. References: R47 Ebert et al. 2003 · a rule set by this program
  11. Tracing with a G-pen (rough 2, from 0.57): rough 2 does not draw steps 4 (inner lines) and 6 (face details); instead it traces the line art once with a G-pen as below. Steps 1 · 2 · 5 · 9 (masses · silhouette · background composition · face construction) are still drawn, but step 8 draws them once (no re-strokes, offset $\le 0.25$) with darkness independent of pressure (strength 𝑤 unchanged).
    • Pen width: pixels with line alpha ℓ ≥ 0.25 inside the character (alpha ≥ 0.5) and $\ell(\mathbf p) - \bar\ell_4(\mathbf p) \ge 0.15$ are collected ($\bar\ell_r$ is the mean of ℓ in a box of radius 𝑟), and $w_p = \sum \ell \,/\, \lvert \mathrm{thin} \rvert$ (ink sum ÷ length of the Zhang–Suen-thinned center line) is the pen width (1.25 if the center line has 50 pixels or fewer). The pen scale is $s_P = \mathrm{clamp}(w_p / 1.25,\ 0.5,\ 3)$.
    • Note — Why: Artists draw with the same pen for the canvas format regardless of resolution, so the same pen covers a different number of pixels in each picture. Instead of the picture-size factor ($s_g$), the pen width measured from the picture sets the length thresholds · radii. How it works: line area divided by center-line length is the mean width. Only pixels darker than their surroundings are collected so masses of fill · shading do not inflate it. 1.25px is near the middle of pen widths measured on AI-generated pixiv pictures. References: R36 Zhang · Suen 1984 · rule set by this program - Line selection (local contrast): with $r_C = \max(2,\ \mathrm{round}(3s_P))$, a pixel is a line if $\ell \ge 0.25$ and $\ell - \bar\ell_{r_C} \ge 0.15$ or $\ell - \bar\ell_{2r_C} \ge 0.15$ (two window sizes, 0.58). Connected components of non-line pixels that do not touch the picture border and have area at most $25s_P^2$ are filled as line (sparkles · dots). In solid cores where the distance to the outside of the line $d_{in}$ exceeds $2.5s_P$, pixels within $2.5s_P - 1$ of the core with $d_{in} > 1.5$ are removed from the line, so filled masses keep only their outline.
    • Note — Why: selecting by line alpha alone lets broadly dark areas such as fill · shading · hair texture in, and the thinned center line of such a mass becomes a meaningless net (about 17% of unseen pictures up to 0.56). A line is a thin band darker than its surroundings, so it is selected by its difference from the surrounding mean. The middle of a thick line (eye rims · lashes, 5–6px) is about as dark as a small window’s mean, so only its two edges would remain (small eyes become square boxes); a window twice as wide is checked too. How it works: box means come from an integral image in constant time per pixel. Small holes are measured by 4-neighbour flood fill, and solid masses are found with a (chamfer) distance transform, keeping only the rim band. References: rule set by this program - Lines and pieces: lines are traced as in step 7 (pieces of at least $\max(4, 6s_P)$ pixels, spurs of $\max(2, 2s_P)$ steps pruned); the point list is smoothed twice with a ±2-point moving average and resampled every 1.5px. It is split only where the angle between the directions $c_w = \max(3,\ \mathrm{round}(3s_P))$ points before and after exceeds 1.05 rad, is a local maximum, and lies at least $2c_w$ points past the previous split (not by length or fit error). An end within 2px of another line is a junction end, otherwise a free end.
    • Note — Why: a professional draws a gentle curve in one stroke and lifts the pen only where the line really turns (more than about 60°). How it works: cutting only at local maxima of the turning angle is as in step 7; the higher threshold and no splitting by length or cubic-fit error leave fewer strokes. References: rule set by this program - Stabilizer: each piece is smoothed with a Gaussian along arc length, $\sigma = \min(3.5s_P,\ L/6)$px (up to 0.58; from 0.59 see step 12) (beyond the ends the points are mirrored through the end point, so the ends stay in place). Pieces shorter than $4s_P$px are not drawn.
    • Note — Why: people draw with the stabilizer of their drawing program on, so strokes have no fine teeth. How it works: a Gaussian-weighted average is a low-pass filter that removes only short-wavelength wobble; point mirroring keeps the curve from shrinking inward at its ends. References: rule set by this program - Pressure · width: for each stroke point, ℎ is the distance to the outside of the line (max in 3×3); with its median $\tilde h = \max(0.5,\ \mathrm{med}\,h - 0.5)$ the base radius is $r_0 = \mathrm{clamp}(1.25\tilde h + 0.3,\ 1.15s_P,\ 2.2s_P)$. Emphasis uses the ±20-point mean $\bar h_j$: $e_j = \mathrm{clamp}(0.7 + 0.3(\bar h_j - 0.5)/\tilde h,\ 0.85,\ 1.2)$. With entry $t_i = \min(0.18L,\ 5s_P)$ and exit $t_o = \min(0.35L,\ 14s_P)$, the pressure at distance 𝑠 from the stroke start is

    $\displaystyle \mathrm{pr}(s) = \big(0.15 + 0.85\,E(s/t_i)\big)\,\big(0.05 + 0.95\,E((L - s)/t_o)\big),\qquad E(t) = \mathrm{smoothstep}(0, 1, t)$

    (junction ends taper only a little: $0.75 + 0.25E$ · $0.7 + 0.3E$), the radius is $r = r_0\,e_j\,\mathrm{pr}$ and the darkness a constant 0.92 (up to 0.58; from 0.59 see step 12). Free ends are extended along their direction by 0.5× the entry · exit length.

    • Note — Why: a G-pen keeps the same ink darkness and changes only width with pressure. Inked lines taper short going in and long coming out, and where the artist drew wider they pressed harder. How it works: the distance to the outside of the line is about half the line width + 0.5, so its median gives the original width. The product of two smoothsteps drops to 0 only at the ends; the extension lets the stroke reach the original line’s tapered tip. References: rule set by this program - Wobble · direction: points move along the normal by $o(t) = 0.45s_P\,\big(0.65\sin(2\pi c t + \varphi_1) + 0.35\sin(2\pi \cdot 2.3\,c t + \varphi_2)\big)$, $c = \max(0.3,\ L/(45s_P))$. Strokes run like a right-hander’s, mostly top → bottom and left → right (the tapering end at the bottom · right).
    • Note — Why: a hand-drawn line sways very slightly, but an inked line with a stabilizer does not shake much. How it works: a smooth wobble mixing two sines of wavelength about $45s_P$px, with amplitude about half the pen width. References: rule set by this program
  12. Pressure curve · line importance · overstrokes (rough 2, from 0.59): the following is added to step 11’s G-pen strokes. Step 11’s stabilizer becomes stronger, $\sigma = \min(6s_P,\ L/4)$, the wobble amplitude drops to $0.15s_P$, the base radius is $r_0 = \mathrm{clamp}(1.4\tilde h + 0.3,\ 0.9s_P,\ 3.2s_P)$, and the width emphasis uses the mean $\bar h_j$ over 20 points on each side, $e_j = \mathrm{clamp}(0.7 + 0.3\,\bar h_j/\tilde h,\ 0.75,\ 1.35)$. Pieces split at bends are drawn only if at least $6s_P$ long.
    • Pressure curve: at relative position $t \in [0, 1]$ along the stroke

    $\displaystyle q(t) = \mathrm{clamp}\Big(0.5 + 0.5\,e^{-((t - c)/w)^2} + 0.12\sin(2\pi f t + \varphi) + 0.15\min(1,\ \theta/0.8),\ 0.3,\ 1\Big)$

    with 𝑐 ∈ [0.3, 0.6] · 𝑤 ∈ [0.3, 0.55] · φ random per stroke, $f = \max(0.5,\ L/(80s_P))$ times a factor in [0.7, 1.3], and θ the bend angle between the directions 3 points before and after. The radius is $r = r_0\,e_j\,\mathrm{pr}\,(0.5 + 0.5q)$ (pr is step 11’s entry · exit taper).

    • Note — Why: a person’s pen line gains force after it starts, is strongest around the middle and then eases off, pressing a little more at bends. 0.58 had constant darkness with only the width following the original line, and looked “mechanical”. How it works: one Gaussian bump peaking in the middle plus a slow sine wobble and a bend bonus; only half of it goes into width, so the stroke stays close to the original line width. - Line importance and darkness: from the median local contrast along the stroke $c_N = \mathrm{clamp}\big(\mathrm{med}(\ell - \bar\ell_{r_C})/0.3,\ 0,\ 1\big)$ and the width $w_N = \mathrm{clamp}(2\tilde h/w_p - 0.5,\ 0,\ 1)$, the importance is $m = \min\big(1,\ 0.5c_N + 0.5w_N + 0.25\,[L > 40s_P] + 0.15\,[\tilde\ell > 0.85]\big)$ (𝓁̃: median line alpha on the stroke), and the base darkness $a_0 = 0.95\,(0.7 + 0.3m)$. If more than half of the points are inside a face area, $a_0 \ge 0.95 \times 0.95$. The darkness at a point is $a = a_0\,(0.75 + 0.25q)\,(0.65 + 0.35\,\mathrm{pr})$.
    • Note — Why: in a rough the artist draws important lines such as outlines · faces darker and side lines such as clothing folds · hair texture lighter. How it works: lines clearly darker than their surroundings · wide lines · long lines · dark lines count as important, and even the lightest keeps 70% so it does not look broken. - Overstrokes: about a quarter (random) of strokes with $L > 20s_P$ get a section of length [0.3, 0.55] starting at [0.12, 0.42] of the stroke drawn once more, offset [0.15, 0.3]$s_P$ along the normal, with radius $0.7\,r_0\,(0.3 + 0.7\sin\pi u)$ and darkness $0.5\,a_0\,(0.3 + 0.7\sin\pi u)$ (𝑢: relative position within the section).
    • Note — Why: a hand-drawn rough goes over the same place again slightly offset, so overlaps get darker. How it works: overlapping only part of the stroke, thinner and lighter, adds variation without making the line look split in two. - Short-piece cleanup: outside face areas, open lines shorter than $8s_P$ (shorter than $12s_P$ if neither end touches another line) are not drawn. From 0.59e they are removed only when more than half of their points have another line within $2.5s_P$ (ladder rungs · spurs running alongside), and lone short strokes (glasses frames · fingers) are kept.
  13. Eye · mouth boxes (rough 2, from 0.59e): for each found face, with eye distance 𝑑, inside the box $c_x \pm 1.0d$ across and $c_y - 0.35d$ to $c_y + 1.25d$ down (for a guessed face, the whole guessed face area) $\ell \ge 0.15$ counts as line regardless of step 11’s local contrast, and solid blobs are not hollowed (one stroke through the middle).
    • Note — Why: eyes · mouths are drawn over dark irises · mouth interiors, so they are not darker than their surroundings, and even when caught, hollowing the blob leaves only an outline ring (graders wrote “missing form” · “eyes as rings”). How it works: only at eye · mouth spots is the line chosen by a line-alpha threshold; elsewhere step 11’s local contrast is kept.

4.15 PSD format

Part Content
Header 8BPS, version 1, 6 reserved bytes, 3 channels (4 for transparent pictures), height, width, 8 bits, color mode 3 (RGB), color mode data length 0
Image resources resolution info (8BIM, 0x03ED, length 16): horizontal · vertical 72 dpi as 16.16 fixed point $72 \cdot 2^{16}$, unit 1 (inch) · 1
Layer info layer count (negative for transparent pictures: the first alpha of the merged image is the transparency), per layer the bounds (top, left, $\text{bottom} = \text{top} + \text{height}$, $\text{right} = \text{left} + \text{width}$) · 4 channels (ID −1 for alpha, 0 · 1 · 2 for R · G · B, each with its data length) · 8BIM · blend key · opacity (0–255) · clipping (0 / 1) · flags (hidden 2, folder records add 0x18) · 0
Layer extra info mask length 0, blending ranges (length 40: source · destination ranges 0x0000FFFF for gray + 4 channels, 10 values), Pascal name, luni (Unicode name), lsct for folders
Channel data per layer, alpha, R, G, B in order, PackBits RLE per row (compression 1 + row length table + data)
Merged image the same composite as the preview (on a white backing for opaque pictures), channels R, G, B(, A) all RLE

Note — Why: The byte layout of the PSD specification is followed exactly so Photoshop and other paint programs can read the file. How it works: Resolution is 16.16 fixed point (the upper 16 bits are the integer part), so 72dpi is $72 \cdot 2^{16}$. Layer bounds are top, left, bottom, right coordinates, so bottom = top + height. For transparent pictures the layer count is written negative to signal that the merged image’s first alpha channel is transparency. References: R4 Adobe PSD

Fixing layer names in old PSDs (PSD.rename)

When a PSD · PSB comes in as input (extension .psd · .psb, or type image/vnd.adobe.photoshop), this is done instead of splitting a picture. For each entry of the old name → current name list (색트레스 → 색 트레이스), the file is read byte by byte as below and only the layer records containing the name are rewritten; if any layer changed, it is saved under the same file name (0.4 s apart for several files). Below, from is the old name and to the current name.

  1. After the header (26 bytes), the color mode data · image resources are skipped by their lengths and the layer and mask info is entered. In PSB, the layer and mask · layer info · channel lengths are 8 bytes.
  2. The 8-bit layer info, and Lr16 · Lr32 · Layr (layer info of 16 · 32-bit pictures) in the global extra info after it, are read the same way. For each layer, the channel lengths are summed to get the total channel data length, and every from inside the luni names of the extra info is replaced with to. The Pascal name is also read as EUC-KR, and if it contains from, the changed name is written in EUC-KR (when 255 bytes or less; files re-saved by the Korean edition of Photoshop). The EUC-KR table is built in reverse by decoding all two-byte codes 0xA1A1–0xFEFE with TextDecoder('euc-kr').
  3. The length width of extra info blocks (4 or 8 bytes in PSB) and the padding after blocks (none · multiple of 2 · 4) differ between programs, so the combination whose block end is the section end or matches the next 8BIM · 8B64 signature is chosen (in PSB, the document’s 8-byte keys and cinf try 8 first, the rest try 4 first).
  4. A changed luni is rewritten with length $4 + 2n$ rounded up to a multiple of 4, and only that layer’s extra info length, the layer info length (padded to 4 if it was originally a multiple of 4, otherwise to a multiple of 2; Lr16 etc. to a multiple of 4), and the layer and mask info length are written anew. Channel pixels · merged image · image resources · global mask are copied byte for byte. If no layer needs changing, nothing is saved and the program says so.
    • Note — Why: When a layer name changes length, the lengths of all blocks containing it must be rewritten or the file breaks. How it works: The new luni length $4 + 2n$ is rounded up to a multiple of 4, and the outer block lengths (layer extra data → layer info → layer and mask info) are corrected by the difference in turn. Pixel data is copied unchanged, so the image does not change. References: R4 Adobe PSD

4.16 Preview compositing

Layers are stacked with the canvas 2D globalCompositeOperation (multiply, screen) and $\texttt{globalAlpha} = \text{opacity}/255$. Clipping groups work like Photoshop: the clipping layers are blended over the base layer made opaque (all alpha 255), then cut once by the base layer’s alpha (destination-in) and drawn with the base layer’s blend mode · opacity. If the base layer is hidden, its clipping layers are hidden too. A folder is drawn separately and stacked as one sheet if its opacity is below 255 or its blend mode is not pass-through · normal; otherwise its layers are stacked as they are. PNG · PSD merged images of opaque pictures are composited on a white backing.

Note — Why: The on-screen preview must match what Photoshop shows so the PSD can be checked before opening it. How it works: The canvas multiply and screen modes use the same blend formulas as PDF/Photoshop, and layer opacity 0–255 is divided into $\texttt{globalAlpha}$ 0–1. Clipping cuts once with the base layer’s alpha (destination-in), so the whole clipping group is confined to the base layer’s shape as in Photoshop. References: R3 ISO 32000-1 2008 · R4 Adobe PSD

Screen

4.17 Manual correction algorithms

Send to background

The line alpha is the largest 8-bit alpha $\alpha_8$ (0–255) among all line art layers. “On a line” is $\alpha_8 \ge 110$ (about 0.43), and “surrounding line” is $\alpha_8 \ge 20$ (about 0.08). The brush width is $R_b = \max(24, \mathrm{round}(0.05 \cdot \max(W, H)))$, and the distance from the stroke is the number of 4-neighbor breadth-first steps starting from the stroke’s points (up to $R_b$). If all points of the stroke are within $\lvert \Delta x\rvert + \lvert \Delta y\rvert \le 4$ of the first point, it is a single click; otherwise it is rubbing. The length of rubbing is the sum of distances between neighboring points, and if it is 48px or more, points within 12px of both ends (distance measured along the stroke) are treated as stroke ends.

Note — Why: Even when the user’s brush crosses a line or grazes a boundary, only the intended object must move, so lines, brush width and tap versus drag are settled first. How it works: The largest alpha among line-art layers is the line alpha; 110/255 (about 0.43) or more is an uncrossable line and 20/255 or more a rim line. The brush width is 5% of the long side (at least 24px). If every point is within Manhattan distance 4 of the first, it is one tap with a shaky hand. The 12px at each end of long strokes start and stop imprecisely, so they are excluded as fill seeds. References: a rule set by this program

Sending to background

Return to character: chosen inside the background the same way (tolerance 40, no part distinction), 2 steps toward the background (anti-aliased edge) within the brush width are added, then recorded in the “character” mask and split again.

Loop: with 30 or more points, a stroke length of 120px or more, and the end point within $\max(24, 0.2 \times \text{diagonal of the bounding rectangle})$ of the start point, it is treated as a closed loop. For each row, the 𝑥 where the loop’s edges cross height $y + 0.5$ ($x = x_a + (y + 0.5 - y_a)(x_b - x_a)/(y_b - y_a)$, edges whose two end points are on different sides of that height) are sorted and the paired spans $[\lceil x_1 - 0.5\rceil, \lfloor x_2 - 0.5\rfloor]$ are filled (even-odd rule). If the inside is under 400px, it is not treated as a loop. The inside is matched per piece surrounded by lines (4-connected pieces of the same part for sending to background, 4-connected pieces of the background for returning to character): if the share 𝑓 of the piece’s pixels inside the loop is $f \ge 0.6$, the whole piece; if $f \le 0.4$, excluded; in between, only the inside of the loop is chosen. Sending to background uses up to 12 steps from the loop; returning to character widens the chosen place by 2 steps but uses only up to 2 steps from the loop.

Note — Why: When the user draws around an object (lasso), its inside must be selectable at once. How it works: At each row’s pixel-center height $y + 0.5$, the 𝑥 where the loop’s edges cross is found by linear interpolation $x = x_a + (y + 0.5 - y_a)(x_b - x_a)/(y_b - y_a)$, and the sorted crossings are filled in pairs: scanline filling with the even–odd rule. Only edges whose ends lie on different sides of that height are counted, so vertices are not counted twice. Line-enclosed pieces are taken whole at 0.6 or more inside the loop, dropped at 0.4 or less, and only in between follow the loop (the gap between thresholds lets slightly sloppy loops still snap to pieces). References: a rule set by this program

Re-split cache: the automatic background result is reused if $(W, H, \text{picture hash}, \text{line width}, \text{line sensitivity}, \text{line color mode}, \text{background separation})$ are the same. The picture hash is a two-way FNV-1a (32-bit multiplication) over the RGBA bytes $v_i$, plus the length.

h_1\leftarrow (h_1\oplus v_i)\cdot 16777619,\quad h_1^{(0)}=\mathtt{0x811c9dc5};\qquad
h_2\leftarrow (h_2\oplus v_i)\cdot 2246822519+i,\quad h_2^{(0)}=\mathtt{0x9747b28c}\qquad(\bmod 2^{32})

Note — Why: Background detection is slow, so re-splitting the same picture with the same settings reuses the previous result. A fingerprint is needed to check quickly that the picture is the same. How it works: FNV-1a is a fast hash that XORs each byte and multiplies by the prime 16777619 ($\bmod 2^{32}$). Using a second hash with a different multiplier and seed that also mixes in the position 𝑖, plus the length, makes an accidental match between different pictures roughly $2^{-64}$ likely. References: R40 Fowler et al.


← README