Vidéo peinture à l'huile
Vous peignez avec du code comme un peintre avec un pinceau : décidez d'abord du tableau (composition, valeurs, palette), puis posez les vraies touches de l'arrière vers l'avant, regardez le résultat et ajustez. Le moteur transforme ces touches en images ; rien n'est un filtre appliqué à un rendu — chaque marque est une touche de pinceau que vous avez placée.
La méthode a été apprise à partir de quatre démonstrations de peinture (processus, pin, arbres picturaux, nuages au coucher du soleil) et de l'analyse de 13 paysages de maîtres du domaine public. Les leçons distillées sont dans references/ ; le code fonctionnel est dans engine/ et examples/.
Configuration (une fois par projet)
cp -R /chemin/vers/peinture-video/engine ./painter-engine
cd painter-engine
npm ci
node bin/paint.mjs still ../chemin/vers/scene.js --t 8 # → PNG à côté de la scène (stills/<scene>/)
Remplacez /chemin/vers/peinture-video par le dossier contenant ce SKILL.md (par exemple ~/.claude/skills/peinture-video). Nécessite Node ≥ 22.12, Google Chrome avec WebGPU (ou CHROME_PATH), ffmpeg et ffprobe. Les études de référence nécessitent en plus Python avec tools/requirements.txt. Pas de phase de build ni de clés API de rendu ; le rendu s'effectue sur le matériel local. Commencez une nouvelle scène en copiant le fichier le plus proche dans examples/ (sunset-river.js ciel/nuages/eau · lone-pine.js vertical, sujet unique · lake-path.js arbres/profondeur/chemin · sunset-study.js ciel dramatique de masses nuageuses sombres avec lumière de contour (une étude de la structure d'une démonstration) · mountain-hamlet.js montagnes, cabane, rochers, personnage marchant, fumée · brush-chart.js pinceaux et mélanges).
Flux de travail — ne sautez pas les étapes d'observation
- Brief. Sujet, ambiance, heure de la journée, format (16:9, 9:16, 1:1 — n'importe quel
--size), durée, et comment le temps est réparti : dévoilement progressif, tableau vivant, ou les deux (par défaut : ~60 % dévoilement, ~40 % tableau vivant). - Étudiez une référence quand elle existe (
tools/study.py, voirreferences/composition.md). Elle rapporte les masses de valeurs, la tonalité, la chrominance, la palette et le point focal — copiez la structure, jamais l'image. - Composez avant la couleur. Écrivez la composition en commentaire en haut de la scène : hauteur d'horizon (jamais au milieu), point focal (sur un tiers, là où vont le contraste de valeurs le plus fort et la chrominance la plus élevée), l'entrée, ce qui encadre, et un plan de valeurs — une valeur (0–1) pour chaque grande forme, en trois masses inégales.
references/composition.md. - Palette à partir de pigments, placée sur le plan de valeurs.
tone(mix({ ultramarine: 1, transYellowOxide: 1 }), 0.42). Gardez la chrominance basse partout sauf l'accent focal (maîtres : chrominance moyenne OKLab 0.02–0.06, zone haute chrominance sous ~8 %). - Peignez de l'arrière vers l'avant avec les sujets peintres (
paintSky,paintCumulus,paintHills,paintMountains,paintWater,paintTreeline,paintPine,paintFir,paintTree,paintGrass,paintRocks,paintHouse,paintGlow,paintMoon,walker,boat,smoke…) et leslayer.scatter/stroke/pathbruts pour tout le reste. Unep.phase('nom')par étape, les couches créées loin → proche.references/api.md. - Regardez, critiquez, ajustez — au moins deux tours. Rendez une image fixe, ouvrez le PNG, et jugez-la comme un tableau : plissez les yeux (les trois masses de valeurs se lisent-elles ?), le point focal est-il là où prévu, y a-t-il quelque chose d'également espacé / de taille égale / de saturation égale, une marque a-t-elle l'air mécanique ? Corrigez la cause dans le code. Lancez
study.pysur votre propre image fixe et comparez avec le plan. Cette boucle est la compétence ; les premiers rendus ne sont jamais bons. - Le temps.
p.timelapse()pour le dévoilement, puis le mouvement :sway,drift,shimmer,p.wind,p.camera(parallaxe par profondeur de couche),p.dynamicpour les choses qui voyagent.references/animation.md. - Planche contact, puis rendu.
still --n 12 --sheet→ vérifiez chaque étape de la chronologie ; puisrender→qa.references/pipeline.md. - Rapportez honnêtement : ce qui a été vérifié automatiquement, ce que vous avez regardé, ce que vous n'avez pas fait.
Squelette de scène
import { Painting, mix, tone, mixOk, skyColor, paintSky, ridge, paintHills, paintGrass, key, ease } from 'painter';
export const meta = { duration: 16, fps: 30 };
export default function ({ aspect, seed }) { // aspect = largeur / hauteur du rendu
const p = new Painting({ aspect, seed, ground: '#c9b89a' }); // toile teintée, jamais blanche
p.duration = 16;
const A = aspect; // unités de scène : x 0…A, y 0 (haut) … 1 (bas)
const skyTop = tone(mix({ ultramarine: 2, titaniumWhite: 2 }), 0.72), skyLow = tone(mix({ cadmiumOrange: 1, titaniumWhite: 3 }), 0.88);
const hillCol = tone(mix({ ultramarine: 1, burntUmber: 1 }), 0.3);
const hill = ridge({ y: 0.68, amp: 0.05, freq: 2, seed });
p.phase('ciel');
paintSky(p.layer('ciel', { depth: 0 }), { color: skyColor({ top: 0, bottom: 0.7, stops: [[0, skyTop], [1, skyLow]] }), bottom: 0.72 });
p.phase('terre');
paintHills(p.layer('colline', { depth: 0.6 }), { top: hill, color: (x, y, k) => hillCol, size: 0.03 });
paintGrass(p.layer('herbe', { depth: 1 }), { bounds: [0, 0.8, A, 1.05], dark: hillCol, light: tone(hillCol, 0.5), blade: 0.05 });
p.timelapse({ start: 0.3, end: 10 }); // tableau fini à 10 s
p.camera = (t) => ({ zoom: key(t, [[10, 1], [16, 1.07]], ease.inOut) });
p.wind = (t) => ({ strength: key(t, [[0, 0], [10, 0.15], [12, 1]]) });
return p;
}
Règles qui le font ressembler à un tableau (la liste courte)
- La valeur fait le travail, la couleur récolte le crédit. Planifiez trois masses de valeurs inégales ; testez au plissement des yeux chaque image fixe.
- Toile teintée, jamais blanche. Choisissez l'imprimatura pour harmoniser le tableau (sienne chaude sous les verts, crème pâle sous un coucher de soleil).
- D'abord l'éloignement, puis l'avant. Loin = plus clair, plus froid, plus gris, plus doux, peinture plus fine, marques plus petites. Près = les plus sombres des ombres, contraste et chrominance les plus élevés, peinture la plus épaisse, marques les plus grandes. Utilisez
atmosphere(couleur, d, air). - Les ombres d'abord et fines, les lumières ensuite et épaisses.
thick0.1–0.3 pour les ombres et l'éloignement, 0.8–1 pour les rehauts ; les rehauts sont petits et peu nombreux. - Clair contre sombre, sombre contre clair. Brisez chaque masse avec des marques d'un petit pas de valeur (±0.05) par rapport à ce qui est dessous ;
p.valueAt(x, y)/p.sample(x, y)vous disent ce qui s'y trouve. - Les touches suivent la forme : autour d'un nuage, le long d'une pente, à plat sur l'eau, vers le haut d'un brin d'herbe, vers le bas d'un tronc.
- Mélangez les verts et terrez-les (
earth(mix({ ultramarine: 1, cadmiumYellow: 1 }), 0.3)) ; réservez la chrominance pure pour un seul accent. - Variez tout : tailles, espacements, hauteurs, intervalles. L'égal = mécanique. Groupes impairs, formes qui se chevauchent, une dominante.
- Bords perdus et retrouvés : doux là où les formes s'éloignent ou reculent (
soft0.5+), nets au point focal. - Fonction pure du temps. Une scène est déterministe à partir de
seed; une image ne dépend que det. N'utilisez jamaisMath.random()ou l'heure de l'horloge — utilisezlayer.rand/rng(seed).
Là où les choses tournent mal (appris en construisant ceci)
- Le blanc engloutit les mélanges. Le mélangeur spectral pondère les pigments par luminance ; un mélange 1:9 couleur:blanc est presque blanc. Mélangez la teinte avec peu ou pas de blanc, puis placez-la avec
tone(c, valeur). Réduisez les résultats criards avecchroma(c, 0.5–0.8). - Nuages en boules avec des bâtons → beaucoup de petits lobes sur une base plate (
cumulusShapelobes 40–60), lumière uniquement sur le côté faisant face au soleil, petites touches rondes et douces,thickmodeste. - Pin en palmier → les aiguilles sont des touffes arrondies (rafales de petites touches en éventail) posées sur les branches avec des trous de ciel entre elles, sombres en dessous, claires au-dessus ; les branches pendent bas et montent haut.
- Arbres avec des branches en rayons de parapluie / troncs noirs → les branches doivent se terminer à l'intérieur de la couronne ; les ombres des troncs sont des valeurs ~0.22, pas du noir, et reçoivent de l'air (
atmosphere) avec l'éloignement. - Toute une zone en couleur saturée (sol orange, ciel turquoise) → terrez-la ; dispersez la couleur pure en quelques petites marques.
- Un chemin lu comme une rivière → gris clair chaud, touches dans la direction du déplacement, violet froid seulement dans ses ombres.
- Eau faite de petits traits qui semble numérique → l'eau est de longues traînées horizontales fondues les unes dans les autres, avec seulement quelques marques de rides près du spectateur (ce que fait
paintWater) ; n'ajoutez pas de rangées de traits à la main. - Mer comme un miroir →
paintWater({ chop: 1 })pour les vagues et les crêtes blanches ; gardezchop: 0pour les lacs et rivières. - Lumière de lampe / fenêtre comme une tache plate →
paintGlowsur un fond sombre, puis le petit noyau chaud ; un long trait doux n'est pas une lueur. - Rayons comme des pointes blanches → peu nombreux, larges, très doux, rayons de faible opacité (
paintRayspar défaut). - La trame de la toile devient une grille sur de la peinture fine et pâle → gardez
look.weave≤ 0.2. - Montagnes comme un mur écailleux ou des rayures de zèbre → construisez l'horizon avec
peaks(), gardez la chaîne lointaine (peinture fine,size≈ 0.02, gamme de valeurs étroite) et laissezpaintMountainsla diviser en quelques grands plans éclairés et d'ombre. - Une bande plate n'est pas un banc de nuages / colline / ligne d'arbres → variez le bord supérieur, les valeurs intérieures et laissez des espaces.
- La scène échoue avec une erreur de navigateur vague → la CLI fait d'abord une vérification syntaxique ; une erreur d'exécution imprime la ligne de la scène. Une variable locale qui masque une couleur (
leaf,sky,hill) est la cause habituelle dec.map is not a function. - Lire une image fixe périmée : vérifiez toujours la sortie de la commande pour une erreur avant d'ouvrir le PNG.
- WebGPU et WebCodecs n'existent que sur une origine sécurisée — la CLI sert
http://127.0.0.1; n'ouvrez jamais les scènes viafile://.
Fichiers de référence
references/painting-lessons.md— ce que chaque démonstration enseigne et comment cela correspond aux appels du moteur.references/composition.md— composer une scène, le plan de valeurs, les chiffres des maîtres,study.py.references/api.md— référence Painting / couche / pinceau / couleur / sujet-peintre, écrire un nouveau sujet peintre.references/animation.md— rythme du dévoilement, mouvement vivant, caméra et parallaxe, couches dynamiques, bouillonnement, audio.references/pipeline.md— CLI, paramètres de rendu, performance, QA, dépannage, livrables.