Painting the Oga way, in pixels by The Oga student

Broadleaf crowns: the clump

Front page: the tutorial. Code: copies/p1_foliage.py (v2; v1 is kept as copies/p1_foliage_v1.py), src/vocab.py clump, clump_mass.

P1 is a 72×60 crop of C-0007's right side (p.60): broadleaf crowns on a slope seen from slightly above, lit from the upper right, with deep holes between them and a small stippled bush as the point.

his crowns

nb207. His crowns, area-reduced to 72 px, at 8×. Rounded clumps lit warm on top, near-black olive in the holes, and in the dark mass on the right, dim clumps one value above the hole.

Stage 1: the notan, organised

Three values from his brightness, smoothed a pixel so they stay shapes, with the orphans cleaned up. About half the area is hole:

Ls = gaussian_filter(L, 1.0)
q = np.quantile(Ls, [0.45, 0.72])
cv.a[:] = np.where(Ls < q[0], I['hole'], np.where(Ls < q[1], I['dim2'], I['mid2']))
meadow = gaussian_filter(((hue < 122) & (L > 0.55)).astype(float), 1.5) > 0.55
cv.a[meadow] = I['mead']
cv.a = majority(cv.a, 2)

This alone is very Oga: masses readable "by silhouette alone" (p.47). It's the ground stage, and it's his layout by carbon transfer (how-to 2). The rest is mine.

Four wrong marks

Each read as a texture and not as leaves:

plates

nb208. Flat lit "plates" with serrated tops stamped at his bright points: mushrooms in rows.

sheets and edges

nb210. Lit sheets with "leafy edges": teeth hanging below, a lip on every top edge. Everything got a halo.

watershed

nb214. Watershed "members", each crown lit on its sun side, rounded and swelled. At 1× it read as trees for the first time; at 8× it's Voronoi polygons with sawtooth edges.

The cold critic's read: "sawtooth scales, like feathers or fern fronds … one cool green family, no warm end and no deep end … the dark block is flat, dead fill; his dark still holds dim clumps."

The mark: a rounded clump at four light levels

One mark, drawn the way a brush tip would lay a leaf cluster. It's a slightly wide lumpy ellipse (3 and 5 lobes). Pixels facing the sun (upper right) near the rim take the cap, pixels at the lower left near the rim take the crescent (where the clump tucks under the next one), and the rest is body:

def clump(cv, x, y, r, level, seed=0):
    """a rounded leaf-clump. r in px (1.5-3). Light from the upper right."""
    cap, body, cres = (cv_index(cv, k) for k in LEVELS[level])
    rr = np.random.default_rng(abs(int(seed)))
    R = int(np.ceil(r)) + 2
    tilt = rr.uniform(-0.25, 0.25)
    for dy in range(-R, R + 1):
        for dx in range(-R, R + 1):
            u, v = dx * np.cos(tilt) + dy * np.sin(tilt), -dx * np.sin(tilt) + dy * np.cos(tilt)
            ang = np.arctan2(v, u)
            rad = r * (1 + 0.18 * np.cos(3 * ang + seed) + 0.1 * np.cos(5 * ang + 2 * seed))
            d = np.hypot(u / 1.2, v) / rad
            if d > 1:
                continue
            s = (dx * 0.6 - dy * 0.8) / (r + 1e-6)          # toward the sun
            idx = body
            if s > 0.5 and d > 0.45:
                idx = cap
            elif s < -0.45 and d > 0.6:
                idx = cres
            cv.put(x + dx, y + dy, idx)

The four levels are four (cap, body, crescent) triplets, each one family down from the last. Dim clumps sit in the holes so the dark mass still holds life:

LEVELS = {  # (cap, body, crescent)
    'lit+': ('lit3', 'lit2', 'mid1'), 'lit': ('lit2', 'lit1', 'mid1'),
    'mid':  ('lit1', 'mid2', 'dim2'), 'dim': ('mid1', 'dim2', 'hole'),
}

Organising the crowd

The clumps sit on a jittered 4-px grid with alternate rows offset, painted far (top) to near (bottom) so nearer clumps overlap farther ones. Size grows toward the viewer. His organised brightness at each clump picks the level. In the darkest 30% only a third of the clumps are drawn, so holes stay holes:

qd, qm, ql, qp = np.quantile(Ls[~meadow], [0.30, 0.52, 0.72, 0.9])
for (x, y) in pts:                       # sorted by y
    v = Ls[yi, xi]
    if v < qd:
        if r.uniform() < 0.35:
            clump(cv, x, y, 2.0, 'dim', ...)
        continue
    level = 'dim' if v < qm else ('mid' if v < ql else ('lit' if v < qp else 'lit+'))
    rad = 2.2 + 1.4 * (y / H) + (0.4 if level.startswith('lit') else 0)
    clump(cv, x, y, rad, level, ...)

Finally the point: sunflecks only where his plate is yellow and my clump is lit.

detail

fig. His crowns | mine, 10×.

at size

fig. At 2× and 1×.

Colour trap

My first v2 palette went ochre-yellow in the lights, overdoing "no warm end" (nb224). His lights are fresh mid-green, and yellow belongs only to the hot spots. The final lights are h 116–130, chroma rising with value (how-to 3).

Reusing it

src/vocab.py has the same mark with named palette slots, and clump_mass(cv, mask, ix, light, levels, r_range, step, hole) does the grid, the ordering and the dim-in-holes rule for any mask and any light field. I used it for: