Communitygithub.com

peinture-vidéo

Peins des scènes en code et transforme-les en vidéo — aspect peinture à l'huile avec de vraies touches de pinceau (poils, empâtement, pinceau sec, toile), mélange spectral des pigments, dévoilement progressif (la scène peinte touche par touche) et phase de « tableau vivant » (vent, nuages qui dérivent, eau scintillante, caméra en parallaxe). Utilise WebGPU/WGSL avec des touches instanciées dans Chrome headless, WebCodecs + export MP4 Mediabunny, ffmpeg pour l'assurance qualité.

Qu'est-ce que peinture-vidéo ?

Ce skill transforme un script JavaScript en vidéo animée avec un rendu peinture à l'huile. Chaque image est construite à partir de véritables touches de pinceau (instanciées via WebGPU/WGSL dans un navigateur Chrome headless) et aucun filtre n'est appliqué sur un rendu existant. L'utilisateur définit la composition, les valeurs, la palette et les sujets (ciel, arbres, montagnes, eau, etc.) en utilisant des fonctions JavaScript comme `paintSky`, `paintCumulus`, `paintMountains` et `paintWater`. Le rendu peut inclure deux phases temporelles : un dévoilement progressif (la peinture apparaît touche par touche) puis une phase de « tableau vivant » où des animations subtiles (vent, ondulations, scintillements, caméra en parallaxe) donnent vie à la scène. Le skill s'appuie sur des études de démonstrations de peinture et de tableaux de maîtres du domaine public (les leçons distillées sont dans le dossier `references/`). Il nécessite Node.js ≥ 22.12, Google Chrome avec WebGPU, ffmpeg et ffprobe. L'utilisateur commence par choisir un fichier exemple dans `examples/` (par exemple `sunset-river.js`, `lone-pine.js`, `lake-path.js`) puis adapte la scène via un processus itératif : écrire le code, rendre une image fixe, l'évaluer (valeur masses, point focal, équilibre des couleurs) et ajuster le code en conséquence. Ce cycle d'évaluation est essentiel : les premiers rendus ne sont jamais bons, et il faut au moins deux itérations. Le résultat final est une vidéo MP4 exportée via WebCodecs et Mediabunny, avec vérification automatique (QA) par ffmpeg. Le skill est déclenché par des expressions comme « pintar », « peinture animée », « scène peinte », « à l'huile », « painterly », « speedpaint », « tableau vivant ».

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ninjasweb/painter-skill/tree/HEAD

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

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

  1. 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).
  2. Étudiez une référence quand elle existe (tools/study.py, voir references/composition.md). Elle rapporte les masses de valeurs, la tonalité, la chrominance, la palette et le point focal — copiez la structure, jamais l'image.
  3. 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.
  4. 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 %).
  5. 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 les layer.scatter/stroke/path bruts pour tout le reste. Une p.phase('nom') par étape, les couches créées loin → proche. references/api.md.
  6. 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.py sur votre propre image fixe et comparez avec le plan. Cette boucle est la compétence ; les premiers rendus ne sont jamais bons.
  7. Le temps. p.timelapse() pour le dévoilement, puis le mouvement : sway, drift, shimmer, p.wind, p.camera (parallaxe par profondeur de couche), p.dynamic pour les choses qui voyagent. references/animation.md.
  8. Planche contact, puis rendu. still --n 12 --sheet → vérifiez chaque étape de la chronologie ; puis render → qa. references/pipeline.md.
  9. 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)

  1. 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.
  2. 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).
  3. 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).
  4. Les ombres d'abord et fines, les lumières ensuite et épaisses. thick 0.1–0.3 pour les ombres et l'éloignement, 0.8–1 pour les rehauts ; les rehauts sont petits et peu nombreux.
  5. 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.
  6. 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.
  7. Mélangez les verts et terrez-les (earth(mix({ ultramarine: 1, cadmiumYellow: 1 }), 0.3)) ; réservez la chrominance pure pour un seul accent.
  8. Variez tout : tailles, espacements, hauteurs, intervalles. L'égal = mécanique. Groupes impairs, formes qui se chevauchent, une dominante.
  9. Bords perdus et retrouvés : doux là où les formes s'éloignent ou reculent (soft 0.5+), nets au point focal.
  10. Fonction pure du temps. Une scène est déterministe à partir de seed ; une image ne dépend que de t. N'utilisez jamais Math.random() ou l'heure de l'horloge — utilisez layer.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 avec chroma(c, 0.5–0.8).
  • Nuages en boules avec des bâtons → beaucoup de petits lobes sur une base plate (cumulusShape lobes 40–60), lumière uniquement sur le côté faisant face au soleil, petites touches rondes et douces, thick modeste.
  • 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 ; gardez chop: 0 pour les lacs et rivières.
  • Lumière de lampe / fenêtre comme une tache plate → paintGlow sur 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é (paintRays par 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 laissez paintMountains la 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 de c.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 via file://.

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.

Skills associés