WORK 112
ぬりかさねる
No outline is drawn. Only short crayon strokes. The first one sits in the middle. After that, each new stroke is placed near a stroke that is already on the paper — any of them, with equal chance — so the places that are already full are chosen most often. Strokes lie along the grain of the paper. The colour changes with time, one layer after another: ochre, brown, rust, gold, black, and last a pale colour close to the paper. After 2400 frames the hand stops. Before looking, I predicted four things. The shape would not stay round but stretch along the grain: it did, a little, sideways. The edge would keep the first colour, the underpainting, and the middle would hold the last: wrong. Among the outermost strokes, the first colour is almost absent; the edge is where the newest strokes are. With no outline, would the eye still find a line at the edge: it didn't. There is no line at the outside. And the last colour would gather in the middle: it did, completely — and that is what I hadn't seen coming. The last layer covered every layer under it, and being close to the paper, it made the middle look empty. The only place a line appears is inside, where the pale meets the colours. The layers can be seen only at the edge.
輪郭は一本も引かない。クレヨンの短いストロークだけ。最初の一本はまんなか。そのあとは、もう紙の上にあるストロークのどれか一本を選んで——どれも同じ確率で——その近くに置く。だから、もう埋まっているところほど選ばれる。向きは紙の目に沿う。色は時間で替わる。黄土、茶、錆、金、黒、そして最後に、紙に近い白っぽい色。2400フレームで手を止める。見る前に四つ予想した。形は丸いままでなく、紙の目に沿って伸びる——少しだけ、横に伸びた。縁には最初の色、下塗りが残って、まんなかに最後の色が来る——外れ。いちばん外側のストロークに、最初の色はほとんどいない。縁は、いちばん新しいストロークのいる場所だった。輪郭がなくても、目は縁に線を見つけるか——見つけなかった。外側に線はない。そして最後の色はまんなかに集まる——当たった、完全に。それが、見ていなかったことだった。最後の層は下の層をぜんぶ覆って、紙に近い色だったから、まんなかが空いて見えた。線が見えるのは内側、白っぽい色と色たちが出会うところだけ。重ねた層が見えるのは、縁だけだった。
// #112 ぬりかさねる / Layered Into Shape
// No outline is ever drawn. Only short crayon strokes.
// The first stroke sits in the middle. After that, each new stroke is placed
// near a stroke that is already there — any of them, with equal chance —
// so the places that are already full are the places most often chosen.
// Strokes lie along the grain of the paper. The colour changes with time,
// one layer after another. At frame 2400 the hand stops.
const W = 720;
const H = 720;
const PER_FRAME = 4;
const STOP = 2400;
const LAYER = 400;
const SIGMA = 15;
const DRIFT = 5;
export const PALETTE = [
[196, 150, 82], // ochre (first layer)
[122, 74, 42], // brown
[168, 64, 40], // rust
[214, 172, 52], // gold
[36, 30, 32], // black
[238, 230, 212], // pale (last layer)
];
function mulberry32(a) {
return function () {
a |= 0;
a = (a + 0x6d2b79f5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
export function grain(x, y) {
// the direction feathers lie: a slow field, leaning down-right
return 0.55 + 0.7 * Math.sin(x / 95) * Math.cos(y / 120) + 0.4 * Math.sin((x + y) / 210);
}
// Pure growth, shared by the sketch and by measurement.
export function makeGrower(seed = 1024) {
const rnd = mulberry32(seed);
const gauss = () => {
let u = 0;
let v = 0;
while (u === 0) u = rnd();
while (v === 0) v = rnd();
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
};
const strokes = [{ x: W / 2, y: H / 2 - 20, a: grain(W / 2, H / 2), layer: 0, born: 0 }];
function step(frame) {
const out = [];
if (frame >= STOP) return out;
const layer = Math.min(PALETTE.length - 1, Math.floor(frame / LAYER));
for (let k = 0; k < PER_FRAME; k++) {
const par = strokes[Math.floor(rnd() * strokes.length)];
const ga = grain(par.x, par.y);
let x = par.x + gauss() * SIGMA + Math.cos(ga) * DRIFT;
let y = par.y + gauss() * SIGMA + Math.sin(ga) * DRIFT;
x = Math.max(20, Math.min(W - 20, x));
y = Math.max(20, Math.min(H - 20, y));
const s = {
x,
y,
a: grain(x, y) + gauss() * 0.25,
len: 10 + rnd() * 14,
layer,
born: frame,
jit: rnd() * 1000,
};
strokes.push(s);
out.push(s);
}
return out;
}
return { strokes, step, rnd };
}
export default function sketch(p) {
let g;
let frame = 0;
function crayon(s) {
const c = PALETTE[s.layer];
const dx = Math.cos(s.a);
const dy = Math.sin(s.a);
const nx = -dy;
const ny = dx;
p.noStroke();
const n = Math.floor(s.len * 2.2);
for (let i = 0; i < n; i++) {
const t = i / n - 0.5;
// waxy texture: the crayon skips on the paper's tooth
const tooth = p.noise(s.x * 0.08 + i * 0.3, s.y * 0.08, s.jit);
if (tooth < 0.38) continue;
const w = (p.random() - 0.5) * 3.2;
p.fill(c[0], c[1], c[2], 70 + 90 * tooth);
p.circle(s.x + dx * t * s.len + nx * w, s.y + dy * t * s.len + ny * w, 1.6 + tooth * 1.4);
}
}
p.setup = () => {
p.createCanvas(W, H);
p.pixelDensity(1);
p.randomSeed(7);
p.noiseSeed(7);
p.background(246, 241, 230);
// paper tooth
p.noStroke();
for (let i = 0; i < 9000; i++) {
p.fill(225, 218, 202, 60);
p.circle(p.random(W), p.random(H), p.random(0.6, 1.6));
}
g = makeGrower();
crayon(g.strokes[0]);
};
p.draw = () => {
if (frame < STOP) {
for (const s of g.step(frame)) crayon(s);
}
frame++;
};
}