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.

Music videosArt & cultureEmotional ads
Style prompt · STYLE.md

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 appear sweep 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 with grey: 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 #dca064 and 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.25 so 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
  1. timeline.js first: BPM grids, cut times, melodies, pops. tools/dump_timeline.mjs writes out/timeline.json for the score.
  2. Sets in scenes/*.js → paintRef().
  3. film.js: shots, cameras, transitions, post.
  4. music/score.py, then mix.py.
  5. sh styles/impasto/demo/build.sh renders 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