Communitygithub.com

ninjasweb/painter-skill

コードでシーンを描き、油絵風の動画に変換します。本物のブラシストローク(毛先、インパスト、ドライブラシ、キャンバス織目)、スペクトル顔料混合、ストロークごとに描かれる「ペイントオン・リビール」、そして「生きた絵画」フェーズ(風、漂う雲、きらめく水面、パララックスカメラ)を搭載。

painter-skill とは?

このスキルは、AIエージェントがコードを使って油絵風のアニメーション動画を生成するためのものです。本物の絵画制作プロセスを模倣し、構成、バリュー、パレットを最初に計画してから、ブラシストロークを背景から前景へと配置します。エンジンはWebGPU/WGSLとヘッドレスChromeを使用してストロークをレンダリングし、WebCodecsとMediabunnyでMP4にエクスポート、ffmpegで品質検証を行います。スピードペインティングやタイムラプス風の動画、物語や音楽、アンビエント動画のための絵画的な背景、または参照絵画の構成研究に最適です。対応プラットフォームはClaude Codeなどのコマンドライン環境で、Node.js、Chrome、ffmpegが必要です。レンダリングはすべてローカルハードウェアで行われ、APIキーは不要です。

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

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

絵画風動画生成

あなたは画家が筆で描くようにコードで描きます。まず絵を決め(構成、バリュー、パレット)、実際のストロークを遠景から近景へと配置し、結果を見て調整します。 エンジンはそれらのストロークをフレームに変換します。フィルターやレンダリングの後処理ではなく、すべてのマークはあなたが置いた筆のストロークです。

この手法は、4つのペインティングデモンストレーション(プロセス、松の木、絵画的な木々、夕焼けの雲)と、13点のパブリックドメインの名作風景画の計測から学ばれました。抽出された教訓は references/ に、動作コードは engine/ と examples/ にあります。

セットアップ(プロジェクトごとに1回)

cp -R /path/to/painterly-video/engine ./painter-engine
cd painter-engine
npm ci
node bin/paint.mjs still ../path/to/scene.js --t 8          # → PNGがシーンの隣に出力されます(stills/<scene>/)

/path/to/painterly-video はこの SKILL.md が含まれているフォルダに置き換えてください(例:~/.claude/skills/painterly-video)。Node ≥ 22.12、WebGPU対応のGoogle Chrome(または CHROME_PATH)、ffmpeg、ffprobeが必要です。参考研究にはさらにPythonと tools/requirements.txt が必要です。ビルド手順やレンダリングAPIキーは不要で、レンダリングはローカルハードウェアで実行されます。新しいシーンを開始するには、examples/ 内の最も近いファイルをコピーしてください(sunset-river.js 空/雲/水面、lone-pine.js 縦長、単一被写体、lake-path.js 木々/奥行き/道、sunset-study.js ドラマチックな空、暗い雲塊にリムライト(デモの構造研究)、mountain-hamlet.js 山、小屋、岩、歩く人物、煙、brush-chart.js ブラシと混合)。

ワークフロー — 観察ステップを飛ばさないこと

  1. 概要。 被写体、雰囲気、時間帯、フォーマット(16:9、9:16、1:1 — 任意の --size)、再生時間、および時間の分割方法:ペイントオン・リビール、生きた絵画、またはその両方(デフォルト:約60%リビール、約40%生きた絵画)。
  2. 参照がある場合は研究する(tools/study.py、references/composition.md 参照)。バリューマス、キー、クロマ、パレット、焦点を報告します — 構造をコピーし、絵そのものは決してコピーしないでください。
  3. 彩色の前に構成。 シーンの先頭にコメントとして構成を記述します:地平線の高さ(中央は不可)、焦点(三分割線上、最も強いバリューコントラストと最も高いクロマが来る場所)、誘導線、それを囲むもの、そして バリュープラン — 各大きな形状に対して1つのバリュー(0–1)、不等な3つのマスに分けて。references/composition.md。
  4. 顔料からパレットを、バリュープランに配置。 tone(mix({ ultramarine: 1, transYellowOxide: 1 }), 0.42)。焦点のアクセント以外ではクロマを低く保ちます(巨匠たち:平均OKLabクロマ0.02–0.06、高クロマ領域は約8%未満)。
  5. 後ろから前に描く 被写体ペインター(paintSky、paintCumulus、paintHills、paintMountains、paintWater、paintTreeline、paintPine、paintFir、paintTree、paintGrass、paintRocks、paintHouse、paintGlow、paintMoon、walker、boat、smoke…)と、その他のものには生の layer.scatter/stroke/path を使用します。段階ごとに1つの p.phase('name')、レイヤーは遠景から近景へ作成。references/api.md。
  6. 観察し、批評し、調整 — 少なくとも2回。 静止画をレンダリングし、PNGを開いて絵画のように評価します:目を細めて(3つのバリューマスが読めるか?)、焦点は計画どおりか、等間隔・等サイズ・等彩度のものはないか、機械的に見えるマークはないか?コード内の原因を修正します。自身の静止画に対して study.py を実行し、計画と比較します。このループがスキルです。最初のレンダリングが正しいことは決してありません。
  7. 時間。 p.timelapse() でリビール、次にモーション:sway、drift、shimmer、p.wind、p.camera(レイヤーの深度によるパララックス)、移動するものには p.dynamic。references/animation.md。
  8. コンタクトシート、そしてレンダリング。 still --n 12 --sheet → タイムラインの各段階を確認。その後 render → qa。references/pipeline.md。
  9. 正直に報告: 自動でチェックしたこと、自分で確認したこと、確認しなかったこと。

シーンの骨格

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 = レンダリングの幅/高さ
  const p = new Painting({ aspect, seed, ground: '#c9b89a' });   // 色付きのキャンバス、白は避ける
  p.duration = 16;
  const A = aspect;                                     // シーンユニット:x 0…A、y 0(上部)…1(下部)

  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('sky');
  paintSky(p.layer('sky', { depth: 0 }), { color: skyColor({ top: 0, bottom: 0.7, stops: [[0, skyTop], [1, skyLow]] }), bottom: 0.72 });
  p.phase('land');
  paintHills(p.layer('hill', { depth: 0.6 }), { top: hill, color: (x, y, k) => hillCol, size: 0.03 });
  paintGrass(p.layer('grass', { 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 });                 // 10秒で絵が完成
  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;
}

絵画らしく見せるためのルール(簡易リスト)

  1. バリューが仕事をし、色が功労を得る。 不等な3つのバリューマスを計画し、すべての静止画を目を細めてテストすること。
  2. 色付きの下地、白は避ける。 インプリマトゥーラ(下塗り)を選んで絵を調和させる(緑の下には暖色のシエナ、夕焼けの下には淡いクリーム色)。
  3. 遠景から手前に。 遠く=明るく、冷たく、灰色がかり、柔らかく、薄い絵の具、小さなマーク。近く=最も暗い暗部、最も高いコントラストとクロマ、最も厚い絵の具、最も大きなマーク。atmosphere(color, d, air) を使用。
  4. 暗部を先に薄く、明部を最後に厚く。 thick は影と遠景で0.1–0.3、ハイライトで0.8–1。ハイライトは小さく、数も少なく。
  5. 明るいものは暗いものの隣に、暗いものは明るいものの隣に。 すべてのマスを、その下にあるものよりわずかに(±0.05)異なるバリューのマークで分割。p.valueAt(x, y) / p.sample(x, y) で現在の状態を確認。
  6. ストロークは形状に沿って: 雲の塊の周り、斜面に沿って、水面に水平に、草の葉に沿って上向きに、幹に沿って下向きに。
  7. 緑色は混色し、土の色で落ち着かせる(earth(mix({ ultramarine: 1, cadmiumYellow: 1 }), 0.3))。純粋なクロマは1箇所のアクセントに取っておく。
  8. すべてを変化させる: サイズ、間隔、高さ、間隔。均一=機械的。奇数のグループ、重なり合う形状、1つの支配的な要素。
  9. 失われたエッジと見つかったエッジ: 形状が奥に曲がるか後退する場所は柔らかく(soft 0.5以上)、焦点ではくっきりと。
  10. 時間の純粋関数。 シーンは seed から決定論的に決まり、フレームは t にのみ依存。Math.random() や実時間は決して使用しない — layer.rand / rng(seed) を使用。

よくある失敗(これを構築する中で学んだこと)

  • 白が混色を飲み込む。 スペクトルミキサーは顔料を輝度で重み付けするため、1:9の色:白の混色はほぼ白になります。色相を白なしまたは少量で混色し、その後 tone(c, value) で配置します。派手な結果は chroma(c, 0.5–0.8) で抑えます。
  • 雲が棒のついた球に。 → 平らな土台の上に多数の小さなローブ(cumulusShape ローブ数40–60)、太陽側のみ明るく、丸く柔らかいダブ、控えめな thick。
  • 松の木がヤシの木に。 → 針葉は丸みを帯びた房(短い扇形ストロークのバースト)で、枝の上にあり、間に空の隙間がある。下部は暗く、上部は明るい。枝は低く垂れ、高く伸びる。
  • 傘の骨のような枝 / 黒い幹の木。 → 枝は樹冠の中で終わらせる。幹の暗部はバリュー約0.22で、黒ではない。距離に応じて atmosphere を適用。
  • エリア全体が彩度の高い色に。(オレンジの地面、ターコイズの空)→ 土の色で落ち着かせ、純色を少数の小さなマークとして散らす。
  • 道が川のように見える。 → 暖かい明るいグレー、進行方向に沿ったストローク、影のみに冷たいバイオレット。
  • 短いダッシュで作られた水面がデジタル的に見える。 → 水面は長く水平な引きで互いにブレンドされ、手前のごくわずかなさざ波のマークだけがある(paintWater の動作)。手動でダッシュの列を追加しない。
  • 海が鏡のように見える。 → paintWater({ chop: 1 }) で波の裏側と白波を追加。湖や川では chop: 0 を維持。
  • ランプ / 窓の明かりが平らなパッチに。 → 暗い背景の上に paintGlow を、次に小さく熱いコアを。1本の長く柔らかいストロークはグローにならない。
  • 光線が白いとげに。 → 数本、幅広く、非常に柔らかく、低い不透明度の光線(paintRays のデフォルト)。
  • キャンバスの織目が淡い薄い絵の具でグリッド状になる。 → look.weave ≤ 0.2 を維持。
  • 山がうろこ状の壁またはシマウマの縞模様に。 → peaks() で地平線を構築し、範囲を遠くに保ち(薄い絵の具、size ≈ 0.02、狭いバリュー範囲)、paintMountains でいくつかの大きな照明面と影面に分割させる。
  • 平らな帯が雲の帯 / 丘 / 樹木線にならない。 → 上端、内部のバリューを変化させ、隙間を残す。
  • あいまいなブラウザエラーでシーンが失敗する。 → CLIが最初に構文チェックを行い、ランタイムエラーはシーンの行を表示します。ローカル変数が色名を隠蔽する(leaf、sky、hill)と、通常 c.map is not a function エラーの原因になります。
  • 古い静止画を読んでしまう。 → PNGを開く前に、常にコマンド出力でエラーを確認してください。
  • WebGPUとWebCodecsはセキュアなオリジンでのみ動作します。CLIは http://127.0.0.1 を提供します。file:// でシーンを開かないでください。

リファレンスファイル

  • references/painting-lessons.md — 各デモンストレーションが教えることと、エンジン呼び出しへのマッピング。
  • references/composition.md — シーンの構成、バリュープラン、巨匠たちの数値、study.py。
  • references/api.md — Painting / layer / brush / colour / subject-painter リファレンス、新しい被写体ペインターの作成方法。
  • references/animation.md — リビールのペーシング、生きたモーション、カメラとパララックス、ダイナミックレイヤー、ボイル、オーディオ。
  • references/pipeline.md — CLI、レンダリング設定、パフォーマンス、QA、トラブルシューティング、成果物。

関連スキル