Impasto Oil Painting 油画厚涂
The Colour of Rain·40s·Lemo-Opuscarby Lemomo
Thick oil paint laid with a palette knife: big, flat, sharp-edged planes of colour, raked by light so every ridge of paint shows. Clarity first: value structure before texture, hard edges at the focus, calm planes everywhere else.
The demo: In a grey, rainy square one red umbrella opens, and a waltz paints the whole plaza in colour.
Thick oil paint laid with a palette knife: big, flat, sharp-edged planes of colour, raked by light so every ridge of paint shows. Clarity first: value structure before texture, hard edges at the focus, calm planes everywhere else. References (grammar only): the opening titles of The Umbrellas of Cherbourg (1964) (overhead choreography read as moving colour); palette-knife street paintings (Leonid Afremov and the genre around him) (knife mosaic, vertical wet reflections, warm lights against cool shadow); Sorolla / Sargent (few, decisive strokes and strong light); Edgar Wright's music-locked editing (one action, one sound, one cut). Never copy their images, compositions, melodies or characters, and never name them in the film.
1. Essence, and what it is not
A moving oil painting made of knife strokes. Each stroke is a real object: a slanted plane of paint with a ridge where the blade pressed, a lip where it lifted off, broken streaks where the paint ran out, two colours dragged into each other. The world is repainted from its strokes every frame, but static paint never moves: only things that move in the story move. Light rakes across a height field, so the paint has body.
The look is clear and graphic (readable like a children's book at thumbnail size) but has the tactile surface of impasto at 100 % crop.
Not a Van Gogh swirl filter (strokes boiling everywhere), not a brush texture over flat vector art, not watercolour (paint is opaque), not a "painterly" photo filter (strokes are designed, not sampled from noise).
2. Materials & rendering
- Value first. Design each set as a flat, value-planned illustration (3 values minimum), then convert it to strokes. If the reference doesn't read at 480 px wide, no brushwork will save it.
- Stroke sizes by importance and depth. Coarse to fine: large planes in sky and walls, a few px only at edges and faces (per shape:
maxR,detail,dir). - Strokes never cross shape groups, so silhouettes stay crisp. Lost edges are opt-in (put two shapes in the same
group). - Thin underpainting (the reference itself, blurred ~1.5 px) under the strokes, so the canvas never peeks through as outlines.
- Knife stroke: longer than wide (≈ 1.7–3.1 × radius long, 0.95–1.45 × radius wide), slanted ends, small angle and value jitter (≈ ±13°, ±6 %). Solid objects that must read as silhouettes (figures, props) get little or no ragged run-out.
- Light rakes over a height field; wet or glossy surfaces get slightly more specular.
- Reflections (wet ground, glass, water) are explicit stacks of short horizontal knife dabs under each light source, broken and fading; never a painted rectangle.
- Render supersampled (2×). No film grain: grain plus compression eats the knife edges.
3. Colour logic
- Planes of mixed, slightly broken colour; each stroke may drag a second colour (
c2) into the first. No gradients made by blending pixels: gradients are made by stroke-to-stroke steps. - Warm light against cool shadow is the default temperature structure; the brightest, purest chroma is reserved for the story's subject (a prop, a crowd, a light source), not the background.
- Colour can be an event. Every stroke can hold a grey and a colour version; a monochrome world with one coloured exception, or a world re-laid from grey to colour, is native to the medium. The grey version is the luminance of the same strokes, slightly cool, with a small colour residue.
- Examples of palettes that fit: an after-rain sunset (cobalt → peach → gold sky, ochre facades, full-chroma props); a harvest field (straw yellow, burnt sienna earth, one viridian tractor); a night kitchen (umber shadows, sodium-lamp orange, one cold blue window). Choose from the topic.
4. Type & subtitles
- Titles are laid in with the knife (a stroke-by-stroke
appearsweep of about a second) and can be taken off by the world (washed by rain, scraped, painted over). They move with the world. - Small text (credits, data, captions) stays crisp DOM text over the painting: knife strokes can't carry small letters.
- A serif display face (e.g. Cormorant, OFL). Subtitles, if there is a voice: plain serif, ≤ 2 lines, in a calm plane; hold ≥ max(1.8 s, speech + 0.6 s).
5. Motion quality
- Static paint is static. Only the things the story moves move (weather, figures, props, ribbons, light). Smooth 24 fps, no stepping: clarity is the brief.
- Moving figures are procedural strokes rebuilt each frame from fixed seeds, so their paint doesn't boil (walk cycles, IK arms, props that open and close).
- Actions sit on the music grid: a change of direction, a pop, a swell lands on a beat.
- Anticipation → action → follow-through on every gesture (lift before the strike, dip before the snap, swing after the stop).
6. Camera grammar
A vocabulary, not a route. Opening and ending come from the topic.
| Move | What it expresses | Can serve |
|---|---|---|
| Slow truck + push on a wide | a world observed; patience | a place; a routine; a crowd |
| Push-in through a silence | inner pressure | a doubt; a decision; giving up |
| Tilt-up from the ground or a reflection | discovery from below | a small character entering; a first sight |
| Pull-back from a close-up | reaction; the world answering | consequence; a reveal of who is watching |
| Rotating crane-out, overhead | pattern emerging from many | choreography; traffic; a gathering |
| Same wide, repeated | before/after | change of colour, season or time |
| Locked frame, light moves | time and weather as the actor | a day passing; a storm arriving |
| Macro slide along the paint surface | the matter itself | a texture; a crafted object; a map |
| Whip-pan into a new painted plate | energy, a jump | a list; montage; travel |
Framing: keep the camera inside the painted plate (half = W/2/zoom), or the canvas shows. The subject gets the hard edges and the smallest strokes; everything else stays in big calm planes. Transitions: paint-over sweeps and scrapes, hard cuts on beats; no dissolves (two layers of knife ridges turn to mud).
7. Sound palette
- Foley follows the paint and the world: palette-knife scrapes on every repaint or paint-over, wet dabs, the materials of the scene (wet stone, nylon, wood, fabric, glass).
- Beds in layers (a hiss, near grains, one distinct detail), filtered when the camera goes under cover.
- Acoustic instruments with a hand-made grain: solo strings, accordion, pizzicato, harp arpeggios, glockenspiel or celesta for pops, piano, guitar, woodwinds; a solo instrument for a lonely world, an ensemble for a shared one.
- Options: one action = one sound = one cut (music-locked edit); pops pitched as a rising arpeggio so a sequence of events climbs; an unresolved phrase that the film resolves later; a hard silence that cuts music and beds to digital zero, containing at most one sound; a J-cut of the next scene's foley into a silence.
- Mix: music and ambience duck under any voice (~8 dB); −14 LUFS. Music and foley can carry a film without narration.
8. Native moves
A menu: use the ones your story needs.
- Colour re-laid by the knife. Every stroke keeps a grey and a colour version; a reveal wave re-paints them stroke by stroke (
reveal,revC). Fits content like: a town waking up; a patient recovering; a brand relaunch; a season turning. - The one coloured thing. The world drawn with
grey: 1, one object withgrey: 0. Fits content like: one person in a crowd; the fruit that ripened; a warning light. - Motion paints the picture. Moving objects drip their colour as knife dabs; seen from above, their paths leave a painted pattern. Fits content like: a dance; delivery routes across a city; a football play; migration.
- Raking light. At the peak, drop the light to a grazing angle so every ridge catches it, then let it settle. Fits content like: a sunrise; a moment of recognition; unveiling a sculpture.
- Paint-over transition. The next shot's strokes are laid over the previous one in a sweep (
appear), with a scrape on the soundtrack. Fits content like: a renovation; memory replacing the present; a timeline of versions. - Sound made visible. A note or a voice becomes a paint ribbon that carries colour to what it touches. Fits content like: a song reaching listeners; a radio broadcast; a rumour spreading.
9. Pitfalls of the medium
- Confined strokes shrink near edges and leave canvas-coloured outlines → add the underpainting texture.
- Draw order in the reference matters: painting the ground after the buildings covers their feet.
- Plain rectangles read as posts or blocks → give silhouettes taper, a lit edge, and one or two defining dabs (shoulder, hair, handle).
- Proportions carry the object's identity at this stroke size: a prop drawn too wide reads as a different thing. Check silhouettes at thumbnail size.
- Film grain or heavy compression destroys the knife edges → no grain, supersample, high bitrate.
- Strokes that are re-seeded every frame boil → fixed seeds per stroke.
- Engine-level WebGL traps (reserved words, VAO binding) are listed in DEMO.md build notes.
10. Engine
Files: demo/engine/impasto.js (renderer), demo/engine/plate.js (reference → strokes).
Main calls: new Impasto(canvas,{W,H,ss}), E.begin() / E.finish(post) (raking light + grade), E.batch() → E.draw(batch,o) for static plates (cam, grey, reveal, appear), E.drawNow() for moving strokes, E.under() for the underpainting, new Strokes().push({...}) (KNIFE, BRUSH, DAB, LINE), new Ref() + paintRef() (reference illustration → coarse-to-fine strokes confined to shape groups), strokePath(). Full parameter table: DEMO.md "Engine reference".
Minimal example: a warm-orange four-point sparkle with a short cursor tail, in knife paint.
import { Impasto, Strokes, KNIFE, DAB, hex } from './engine/impasto.js';
const E = new Impasto(canvas), s = new Strokes(), c = hex('#D97757');
for (let k = 0; k < 4; k++) { // four tapered knife points
const a = k * Math.PI / 2;
s.push({ x: 960 + Math.cos(a) * 60, y: 540 + Math.sin(a) * 60, ang: a, len: 120, wid: 46, c, c2: hex('#f0a07a'), type: KNIFE, taper: .95, seed: k });
}
s.push({ x: 960, y: 540, len: 40, wid: 40, c: hex('#f6c0a0'), type: DAB, seed: 9 }); // bright core
s.push({ x: 1060, y: 610, ang: .6, len: 90, wid: 16, c, type: KNIFE, alpha: .8, seed: 10 }); // cursor tail
E.begin(); E.drawNow(s.data(), { grey: 0, run: 0 }); E.finish();
"Only one colour" (monochrome with one exception): draw the world with grey: 1, reveal: 1e6, draw the exception with grey: 0.
11. Variation space
You decide the structure, the cast (or none), the sets, the opening, the ending, the camera path, the pacing, whether there is a voice, and where colour lives. All far from our demo:
- Structures: one object, many hands (a bowl or a boat passes through the people who make and use it, one painted plate per hand); a still life comes alive (a locked table whose objects move in turn, the light crossing it hour by hour); a portrait in layers (a face painted coarse to fine as a voice tells one life, each layer a decade).
- Openings: the bare canvas (underpainting only; the first knife stroke is the first event); macro on a ridge of paint that pulls back to become a mountain or a roof; mid-action (a figure already running across a finished plate).
- Endings: scraped back (the knife takes the painting off to the underpainting, leaving one mark); the light goes out (raking light sinks until only the ridges glow, then black); the frame in a room (the film was a painting hanging somewhere, seen by someone).
How our demo was made (story, shots, score, end card, build): DEMO.md. Read it after your treatment exists.
Director's notes for the demo film
One example among many. Don't reuse its story, arc, shots, props or timings.
Demo: The Colour of Rain (39.7 s) · impasto.mp4 · source in demo/
Story & structure
A grey square in the rain. A street cellist plays and nobody stops; halfway through he gives up. A little girl opens a red umbrella in front of him: the film's first colour. The cello starts again as a waltz, and umbrella after umbrella turns to colour on the beat. From overhead, the crowd's waltz paints a coloured mandala on the cobbles. The last chord lands, the rain stops, colour fills the whole square.
Arc: loneliness (grey) → giving up (silence) → one touch of colour (the red umbrella) → being heard (the waltz) → the crowd (overhead choreography) → echo (same camera, grey turned to colour; the phrase that broke off on E resolves to D).
Why it fits the medium: colour is the story, and the knife can re-lay any stroke in a new colour. It is a music film with no narration: the title is laid in with the knife over the grey sky and washed off by the rain (it drifts down and fades).
Native moves spent: colour re-laid by the knife (the umbrella opening, the cellist's recovery, the final chord filling the square); the one coloured thing (the red umbrella is the only colour in the first half); motion paints the picture (overhead, each umbrella drips its colour along its path, and the pull-back reveals a mandala); raking light (the sun grazes the ground at the final chord, then settles); paint-over transitions with a knife scrape.
Shots
| # | Time | Shot / camera | Why |
|---|---|---|---|
| 1 | 0–2.65 | CU cello body + bow, slight push | 3 s hook: the only colour in the grey world is the note (an amber ribbon), soon washed out by the rain |
| 2 | 2.65–7.15 | Extreme wide, slow truck + push, paint-over in | The world: grey square, people hurrying. Title knifed in, then carried away by the rain |
| 3 | 7.15–12.4 | Medium, one continuous push through the silence | Nobody stops: passers-by cross close to camera and block him; push to his face as he gives up |
| 4 | 12.18–16.66 | Low CU on a puddle → tilt-up, hard cut | A boot lands on the splash; up to her, him, the umbrella; anticipation, POP, impact zoom |
| 5 | 16.66–19.39 | Face CU → pull-back, hard cut on the downbeat | He looks up, lifts the bow; at 18.03 the bow lands and colour pours out of the cello |
| 6 | 19.39–23.48 | Closer framing of the same wide, paint-over | The ribbon carries colour to each umbrella, one per beat |
| 7 | 23.48–31.66 | Overhead, rotating + crane-out | Signature shot: choreography and the coloured trails on the ground |
| 8 | 31.66–35.75 | Extreme wide (same set-up as 2), push toward the arch | Rhyme with the opening: grey to colour, raking light, umbrellas closing, birdsong |
| 9 | 35.75–39.66 | End card, paint-over | Title, a red dot (an umbrella seen from above), credits |
Six moves (push, truck, tilt-up, pull, rotate, crane-out overhead); sizes from CU to extreme wide and overhead. The same wide camera serves the opening (grey), the middle (umbrellas in colour) and the finale (all colour): for this story, the change was the story. Keep the camera inside the painted plate.
Score structure
demo/timeline.js holds the grids; picture and score both read it.
- Part A: solo cello, D minor, 3/4 at 80 BPM from 0.4 s. The phrase stops on an unresolved E3 at 10.15 s; the bow leaves the string 0.3 s later.
- Silence 10.45–12.4: rain nearly gone, one drip at 11.15, two small steps at 11.5 / 11.95 enter before the picture (J-cut), a big splash at 12.4 is the first sound after it.
- Part B: street-musette waltz, D major, 3/4 at 132 BPM.
- 15.3 POP (pickup bar): glockenspiel + rising harp glissando.
- 16.66 accompaniment enters: pizzicato bass + accordion "oom-pah-pah".
- 18.03 the cello melody enters.
- 19.39–23.03 nine umbrellas, one per beat; glockenspiel pitches climb G→B→D / A→C#→E / D→F#→A.
- 23.48–31.21 overhead, six bars of choreography; accordion repeats the melody an octave up, one harp arpeggio per bar.
- 31.21 grand pause, digital zero; the rain freezes in the air.
- 31.66 final chord (tutti + timpani + suspended cymbal).
- Coda F#→E→D resolves the phrase that broke off at the start.
- 35.75 last pizzicato, end card.
- Other voices: violin pizz, shaker.
Sound design, section by section:
| Section | Bed | Foley | Music |
|---|---|---|---|
| CU | rain (slightly distant) | bow rosin, raindrops on varnished wood | the solo's first note |
| Grey square | full rain + gutter trickle | wet cobble footsteps (placed by the walk algorithm, panned), knife scrape on the title | solo |
| Medium | rain under the arcade (low-passed) | passers-by footsteps close + umbrella fabric brushing | solo, breaks off |
| Silence | almost nothing | one drip, small steps (J-cut), big splash | none |
| Girl | rain | nylon rustle, rib click, umbrella "fwump", paint dabs landing | POP |
| Reaction | muffled rain | bow-lift air | accompaniment, then cello |
| Street | rain (lowered) | each umbrella's "fwump" + a soft hiss as the ribbon arrives | waltz + glockenspiel arpeggios |
| Overhead | rain | the crowd's water steps on the waltz (beat 1 heavy) | tutti |
| Grand pause | digital zero | — | — |
| Ending | rain stopped | drips, umbrellas closing, birdsong | chord → coda |
Palette-knife scrapes sit on every repaint.
Palette & props
- After-rain sunset sky: cobalt
#34507e→ peach#d99a78→ gold#f7dca0. Facades ochre#dca064and rose#c98d7e, shutter green#3f7a6c, awning red#b8342a. - Umbrellas at full chroma: cadmium red
#dc2f28, yellow#f4bc2a, cobalt#2f62c0, viridian#23906c, magenta#cc3a82, orange#ee7a2a, turquoise#26aab4, violet#6e4cc0. - Grey world = luminance of the same strokes, slightly cool, 5 % colour residue; its specular is slightly higher (wet).
- Light:
light [-.55,-.6,.7],norm 3.2,amb .8,dif .28,spec .16–.3,ao 1.2. - Stroke sizes: 40–60 px planes in sky and walls, 3–6 px only at edges and faces. Knife stroke: skew ±0.55, angle jitter ±13°, value jitter ±6 %. Figures and umbrellas use
run: 0–0.25so they stay solid. - Figures: the cellist (IK bow arm, bow direction changes on each note), the girl (dips the umbrella before it snaps open), the crowd (rings swell on every downbeat; umbrellas pop on beats with a scale bounce and a burst of paint dabs, swing down to hang after closing). Faces: eyes, brows and mouth are separate procedural strokes so they can act.
- Wet reflections: stacks of short horizontal dabs under each lamp and window.
End card
Knife-painted title on deep ultramarine, a red dot (an umbrella from above), and crisp DOM text for the credits (small text must stay legible): "an impasto palette-knife study", then "Lemo-Opuscar · LemoLab × Claude Opus 5.5", then the sample and font credits. The "LemoLab × Claude Opus 5.5" sign-off belongs to this library's demo only; a user's film carries no LemoLab credit and no copy of this card (the line lives in demo/film.js: remove it if you adapt that file).
Build notes
styles/impasto/demo/
engine/impasto.js WebGL2 knife-stroke renderer engine/plate.js reference illustration → strokes
timeline.js BPM grids, cut times, melodies (MEL_A, MEL_B), pops
scenes/*.js sets as reference illustrations (Ref.fill/line/text/tint) → paintRef()
film.js shots, cameras, transitions (EDIT table), post looks, end card
music/score.py sampler score (CC0 instruments) mix.py foley + ambience + ducking + true silence
tools/dump_timeline.mjs timeline → out/timeline.json build.sh
timeline.jsfirst: BPM grids, cut times, melodies, pops.tools/dump_timeline.mjswritesout/timeline.jsonfor the score.- Sets in
scenes/*.js→paintRef(). film.js: shots, cameras, transitions, post.music/score.py, thenmix.py.sh styles/impasto/demo/build.shrenders everything (≈ 30 s of GPU render for 952 frames), muxed with grain 0.
Engine-level traps: half is a reserved word in GLSL ES 3.00; binding the composite quad while a stroke VAO is still bound silently rewires attribute 0 ("vertex buffer not big enough" on the next frame): unbind the VAO first.
Pitfalls tied to this demo's props:
- Umbrella radius 0.46 × body height looked like a parasol table; 0.34 reads as an umbrella.
- Coats as plain rectangles read as bollards: A-line taper + lit edge + shoulder dab + hair dab.
- A global index in the per-shot ribbon loop flung control points off-screen (a streak from the sky).
- Painting the ground after the buildings covered their feet.
Engine reference
demo/engine/impasto.js (renderer) and demo/engine/plate.js (reference → strokes).
| API | What it does |
|---|---|
new Impasto(canvas, {W, H, ss}) |
WebGL2 renderer, supersampled colour + height buffers |
E.begin() / E.finish(post) |
start a frame / light + grade (light, norm, amb, dif, spec, ao, expo, sat, vig…) |
E.batch(Float32Array) → E.draw(batch, o) |
static stroke set. o = {M, cam:{x,y,zoom,rot}, t, grey, reveal, revDur, appear, appDur, alpha, run, hgt} |
E.drawNow(Float32Array, o) |
transient strokes (figures, weather, ribbons) |
E.texture(canvas) + E.under(tex, w, h, o) |
thin underpainting (supports revC:[x,y,speed] reveal wave) |
new Strokes().push({x,y,ang,len,wid,c,c2,type,taper,bend,skew,alpha,hgt,rev,app,seed}) |
build strokes; type: KNIFE, BRUSH, DAB, LINE |
new Ref(w, h, scale) + .fill(path, fill, props) / .line / .text / .tint |
reference illustration; props {dir, maxR, detail, group, type, hgt, jit, len, wid, aj, fallback} |
paintRef(ref, {R:[radii], T, rev(x,y), app(x,y,level)}) |
coarse→fine knife strokes confined to shape groups |
strokePath(out, pts, {wid, c, type, step}) |
strokes along a polyline |




