mcpField
K
krita

krita

github.com/jablonkaiクリエイティブ・メディア447 回インストール今日更新
リポジトリ

krita について

krita は、起動中の Krita セッション内で Python スクリプトを実行するスキルです。画像の構築、確認、書き出しを自動化したい制作者向けで、libkis と PyQt5 を使い、キャンバスの投影画像とスクリプト出力で結果を確認できます。

主な機能

  1. 01起動中の Krita でスクリプト実行
  2. 02ドキュメントとレイヤーの作成・編集
  3. 03ピクセル、ベクター、テキストの描画
  4. 04フィルター、選択範囲、マスクの適用
  5. 05キャンバス投影の確認とファイル書き出し

活用シーン

  1. 01ポスターの自動構築と書き出し
  2. 02レイヤーやファイルの一括書き出し
  3. 03アニメーションフレームの動画化

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jablonkai/skills --skill krita

インストール後、Claude Code で/kritaを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
api-reference.md
recipes.md
scripts/
example-poster.py
krita_bridge.desktop
krita-install-bridge.sh
krita-send.sh
scripts/krita_bridge/
__init__.py
krita_bridge.py

詳細

Krita Control

Krita(/Applications/krita.app)には Python 3 が組み込まれており、libkis API(from krita import *)を通じてドキュメントモデル全体を操作できます。ピクセル処理には PyQt5 を利用できます。実行中の Krita 内で動作する pykrita プラグイン Krita Bridge を介して操作します。このプラグインは、127.0.0.1:8737 に POST されたスクリプトを実行します。スクリプトは実行中のセッションの GUI スレッドで動作するため、作業の進行に合わせてキャンバスが更新され、ユーザーはその様子を確認でき、メインスレッドでのみ利用できる API も安全に使えます。以下の各呼び出しは Krita 5.3.4(Python 3.13、PyQt5 5.15.11)で実行しました。このスキルは当初 5.3.3 で作成されました。

  • scripts/krita-install-bridge.sh — ブリッジプラグインをユーザーのリソースフォルダーにインストールし、kritarc で有効にします(一度だけ実行)。
  • scripts/krita_bridge/ — プラグイン本体です。1 回限りのセッションでは Tools ▸ Scripts ▸ Scripter に貼り付けて実行することもできます。
  • scripts/krita-send.sh — .py ファイル(または -c 'inline code')を送信し、取得した出力を表示します。--ping でブリッジの稼働状況を確認できます。
  • scripts/example-poster.py — 作成 → 検証 → エクスポートを行う完全なスクリプトです。コピーして利用できます。
  • references/api-reference.md — バージョン確認済みの libkis API(Krita、Document、Node、Selection、フィルター、アニメーション、エクスポート)。以下のチートシートより先にお読みください。
  • references/recipes.md — 一括エクスポート、レイヤーの抽出、アニメーションフレームから動画への変換、Krita をもう 1 つ起動すべきでない理由、API でできないこと。

制御ループ

  1. ブリッジが起動していることを確認します:bash scripts/krita-send.sh --ping → {"ok": true, "bridge": "krita", "version": "5.3.4 (git e7e52a7)", "documents": [...]}。 応答がない場合は、Krita が起動していない(ユーザーに起動を依頼してください。インストール済みであれば、ブリッジは Krita とともに自動起動します)か、プラグインがまだインストールされていません。

    bash scripts/krita-install-bridge.sh     # Krita must be quit: it rewrites kritarc on exit
    

    スクリプトはプラグインを pykrita フォルダーにコピーし、kritarc の [python] グループに enable_krita_bridge=true を書き込みます。これは 設定 ▸ Krita の設定 ▸ Python プラグインマネージャー のチェックボックスと同じ操作です。別のアプリの設定を編集することはユーザーレベルの変更にあたり、環境によっては実行が拒否されます。拒否を回避しようとせず、代わりにワンライナーをユーザーに渡してください。何もインストールできないセッションでは、ユーザーは krita_bridge.py を ツール ▸ スクリプト ▸ Scripter に貼り付け、Ctrl+R を押せます。同じブリッジが起動し、終了時に停止します。

  2. ビルドスクリプトを作成してスクラッチパッドに保存し、送信します: OUT=/path/to/outdir bash scripts/krita-send.sh /path/build.py。Krita と OUT はスクリプトの名前空間にあらかじめ注入されます(OUT は os.environ["OUT"] としても設定されます)。また、KRITA_SEND_TIMEOUT=600(秒)により、時間のかかるエクスポートにも対応できます。

  3. フィードバックを読みます。 ブリッジはスクリプトの標準出力/標準エラー出力を返し、失敗した場合は Python のトレースバックも返します。そのため、結果を返す手段は print(...) です。それ以外は報告されません。Krita 独自のログウィンドウはアプリ内に表示されたままです。

  4. 結果を確認します。 doc.projection(0, 0, w, h) は、統合されたキャンバスの QImage を返します。これを保存して PNG を読み取ります。エクスポートで生成されるものとまったく同じなので、スクリーンショットよりも手軽で確実です。

  5. 反復してから納品します:スクリプトを修正して再送信します。スクリプトは再実行可能である必要があります。すでに開いているドキュメントを変更するのではなく、バージョンごとに新しいドキュメントを作成してください。そうすれば、再送信時に前回の試行にレイヤーが積み重なることはありません。

すべての操作は実行中のセッションを通じて行います。 シェルから Krita をもう1つ起動しないでください。krita --export も --export-sequence も、バックグラウンドでのヘッドレスインスタンスも使用しないでください。2つの Krita プロセスが同じリソースデータベースと kritarc を取り合い、ユーザーが作業中のほうに支障が出ます。また、バッチジョブが独自のアプリを起動するとフォーカスが奪われ、双方から失敗が見えなくなります。単純な形式変換もブリッジスクリプトを使い、すでに実行中のセッションで開いて、エクスポートし、閉じます(references/recipes.md を参照)。

スクリプト早見表

Krita.instance() はアプリケーションであり、すべての機能はここから利用できます。サイズの単位はピクセルです。色はドキュメントのカラースペースによって異なります。また、エクスポートや保存の呼び出し時にユーザーへダイアログを表示させないための設定が doc.setBatchmode(True) です。

ドキュメントとレイヤースタック
from krita import Krita, InfoObject
app = Krita.instance()
doc = app.createDocument(1200, 1600, "poster_v1", "RGBA", "U8", "", 300.0)
app.activeWindow().addView(doc)          # show it — the user sees the build happen
doc.setBatchmode(True)                   # no dialogs on export/save

layer = doc.createNode("background", "paintlayer")   # grouplayer, filterlayer,
doc.rootNode().addChildNode(layer, None)             # filllayer, clonelayer, …
layer.setBlendingMode("multiply")        # Krita's blend-mode ids, not CSS names
layer.setOpacity(180)                    # 0–255, not a percentage

新しいドキュメントには、不透明な白で塗りつぶされたペイントレイヤーがすでに1つ含まれており、ユーザーの言語で名前が付けられています(ハンガリー語版の Krita では Háttér)。名前ではなく doc.rootNode().childNodes()[0] で取得し、結果に透明部分が必要な場合は非表示にするか、塗りつぶしてください。代わりに開いているドキュメントで作業するには doc = app.activeDocument() を使います。ファイルを開くには doc = app.openDocument(path) を使い、表示するにはビューを追加します。バッチ処理用のドキュメントは、作業が終わったら閉じてください。開いている各ドキュメントは画像全体をメモリに保持します。ただし、先にドキュメントを閉じるを読んでください。注意せずに close() を呼び出すと、ハングしたりクラッシュしたりします。

実際のペイントエンジンで筆致を描く

Node.paint* は、タブレットでのストロークと同じペイント処理に現在のブラシプリセットを適用するため、テクスチャ、水彩、パターンのプリセットも手描きと同じように動作します。

from krita import ManagedColor
from PyQt5.QtCore import QPoint, QRectF
from PyQt5.QtGui import QColor

view = app.activeWindow().addView(doc)           # paint* needs a view
presets = app.resources("preset")
view.setCurrentBrushPreset(presets["b) Basic-5 Size"])   # names include the "b) " prefix
view.setBrushSize(40); view.setPaintingOpacity(0.9)
view.setForeGroundColor(ManagedColor.fromQColor(QColor("#ff6b35")))

print(layer.paintAbility())              # expect "PAINT" before a long stroke loop
layer.paintLine(QPoint(120, 200), QPoint(900, 640), 1.0, 0.3, "ForegroundColor")
layer.paintEllipse(QRectF(300, 300, 400, 400), "ForegroundColor", "None")

paintLine が受け取るのは QPoint です。QPointF を渡すと、矩形ベースの呼び出しでは QRectF が必要であるにもかかわらず、TypeError が発生します。paintAbility() や paint* の呼び出しに触れる前に、ビューを追加してください。ビューのないドキュメントでは、これらは正常にエラーを返さず、Krita がクラッシュします(5.3.4 では存在しないビューを参照してしまいます)。すべて、ユーザーが現在選択しているプリセット、色、ブレンドモードを引き継ぐため、必要な設定は明示的に指定してください。毎回同じ見た目にする必要がある図形は、代わりに QPainter で描画してください。

ピクセル:QPainter で描画し、一度に反映する

再現性の高い画像は QImage に描画し、一度にペイントレイヤーへ反映します。Krita の RGBA8 色空間では BGRA バイトが必要です。これはリトルエンディアンのマシンにおける QImage.Format_ARGB32 のメモリ内レイアウトと同じなので、チャンネルの入れ替えは不要です。

from PyQt5.QtCore import QByteArray, QPointF, Qt
from PyQt5.QtGui import QColor, QImage, QPainter

img = QImage(doc.width(), doc.height(), QImage.Format_ARGB32)
img.fill(Qt.transparent)
p = QPainter(img)
p.setRenderHint(QPainter.Antialiasing)
p.setBrush(QColor("#ff6b35")); p.setPen(Qt.NoPen)
p.drawEllipse(QPointF(600, 500), 260, 260)
p.end()

ptr = img.constBits(); ptr.setsize(img.byteCount())
layer.setPixelData(QByteArray(ptr.asstring()), 0, 0, img.width(), img.height())

node.pixelData(x, y, w, h) で同じバッファーを読み戻せるため、既存のレイヤーを QImage 経由で往復させて、分析やレタッチができます。

ベクターとテキスト

Krita ではテキストはベクター形式で扱われます。確実な方法は SVG を使うことです。

vec = doc.createVectorLayer("headline")
doc.rootNode().addChildNode(vec, None)
vec.addShapesFromSvg('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1600">'
                     '<text x="90" y="1320" fill="#f5f5f5" font-family="Helvetica" '
                     'font-size="150">KRITA</text></svg>')
フィルター、選択範囲、マスク
f = app.filter("blur")                   # bind it: a chained temporary takes its
cfg = f.configuration()                  # InfoObject down with it (see below)
cfg.setProperty("halfWidth", 40); cfg.setProperty("halfHeight", 40)
f.setConfiguration(cfg)
f.apply(layer, 0, 0, doc.width(), doc.height())    # destructive, on that node

from krita import Selection
sel = Selection(); sel.select(100, 100, 400, 400, 255)   # x, y, w, h, selectedness
doc.setSelection(sel)                    # filters and edits then respect it

フィルター ID は app.filters() にある文字列そのものです。"gaussian blur" のように、スペースを含むものもあります。ID が間違っていると例外は発生せず、None が返されます。プロパティ名はフィルターごとに異なります。f.configuration().properties() を実行するとデフォルト値が表示されるため、プロパティを調べるのに最も手早い方法です。非破壊で適用するには、フィルターマスクまたはフィルターレイヤーを使います(doc.createFilterMask/doc.createFilterLayer)。API リファレンスを参照してください。

確認する前に更新する

Krita の合成処理は非同期です。結合画像を読み書きする処理(projection()、thumbnail()、exportImage()、save())の前には、まず投影画像を最新の状態にする必要があります。

doc.refreshProjection()
doc.waitForDone()
エクスポートと保存
doc.projection(0, 0, doc.width(), doc.height()).save(OUT + "/check.png")   # for you to Read
doc.exportImage(OUT + "/poster.png", InfoObject())    # PNG/JPEG/TIFF/WEBP by extension
doc.saveAs(OUT + "/poster.kra")                       # re-editable, keeps the layer stack
doc.waitForDone()

アニメーション、レイヤーごとのエクスポート、フォルダーの一括処理については、references/recipes.md を参照してください。

ドキュメントを閉じる
doc.setModified(False)   # or save first — then close() can't ask anything
doc.close()

setBatchmode(True) を設定しても、ドキュメントを閉じる処理には適用されません。ビューのある変更済みドキュメントでは、モーダルの 「変更を保存しますか?」 ダイアログが表示され、送信側は KRITA_SEND_TIMEOUT までブロックされ、Krita はクリックを待ち続けます。また、キーフレームを作成した送信処理の中で、アニメーションドキュメントを閉じてはいけません。setModified(False) の設定後や、イベントループを処理した後、file_close の実行後であっても、少し後にレイヤードッカーで Krita がクラッシュします。ユーザーが閉じられるように開いたままにするか、後続の送信処理で閉じてください。

検証

  • 返された標準出力は即座に確認できるシグナルです。print(...) の出力は送信側にそのまま返されます。失敗するとトレースバックが返され、ゼロ以外の終了コードで終了します。
  • 投影画像の PNGは見た目を確認するためのものです。doc.projection(...) を保存し(縮小してもかまいません)、$OUT に出力して Read してください。すべてのビルドスクリプトの最後に実行してください。1 行で済み、アサーションでは検出できないエラーも見つけられます。
  • 数値:サイズ、解像度、レイヤー一覧(名前、種類、境界、ブレンドモード、不透明度)を metrics.json に書き出して Read してください。レイヤー構成を目で確認するだけにしないでください。
  • 受け渡し:エクスポートした PNG/JPEG はどこでも開けます。保存した .kra では、すべてのレイヤーが保持され、ユーザーが編集できます。

注意点

  • ブリッジはライブセッションで実行されるため、送信の間も状態が保持されます。開いているドキュメント、アクティブなビュー、選択範囲はすべてそのまま残ります。ドキュメントにバージョンを付けて(createDocument(..., "poster_v2", ...))、再送時に前のドキュメントと競合しないようにしてください。
  • エクスポートや保存の前にsetBatchmode(True)を呼び出してください。そうしないと、Krita がフォーマットオプションのダイアログで止まることがあり、操作する人がいないため送信がタイムアウトします。close()には適用されません。ドキュメントを閉じるを参照してください。
  • 送信がタイムアウトする場合、ブリッジが停止しているのではなく、モーダルダイアログが原因であることがほとんどです。ダイアログはネストされたイベントループを実行するため、ブリッジは引き続き応答します。また、QApplication.activeModalWidget()でダイアログを特定できます。references/recipes.mdを参照してください。
  • 読み取り/エクスポートの前には毎回、refreshProjection()とwaitForDone()を実行してください。実行しないと、キャンバスのコンポジットが1つ前の状態でエクスポートされることがあります。
  • 不透明度は0~255、ブレンドモードにはKrita独自のID("normal"、"multiply"、"add"、"erase"など)を使います。5.3.xにはIDを一覧表示するAPIがありません。app.blendingModes()があるのはmasterのみなので、レイヤードッカーからIDを取得してください。
  • app.filter("x").configuration()のように連結しないでください。Filterの一時オブジェクトがガベージコレクションされ、InfoObjectも一緒に破棄されるため、一見問題のない行でRuntimeError: wrapped C/C++ object of type InfoObject has been deletedが発生します。フィルターを変数に代入してください。
  • 浮動小数点数を期待する箇所で整数が必要です。xRes()/yRes()は浮動小数点数を返しますが、scaleImageとsetResolutionは整数を受け取り、PyQtは丸めずにTypeErrorを発生させます。int(...)を使ってください。
  • node.save()はドキュメントに記載されたbool値ではなく、Noneを返します。ファイルが存在することを確認してください。
  • APIからキーフレームは作成できません。setCurrentTimeとsetPixelDataでは、保存結果が毎回正しく見えてもキーフレームは作成されません。フレームごとにadd_blank_frameを実行し、その後にdoc.waitForDone()も呼び出してください。アクションは非同期で、待機しないと各フレームのピクセルが1フレーム前に反映されます。references/api-reference.mdを参照してください。
  • setPixelDataには、任意の順序のRGBAではなく、ノードの色空間を指定する必要があります。"RGBA"/"U8"のドキュメントでは、Format_ARGB32バッファーをそのまま使用できます。U16/F32のドキュメントでは、対応するバイト列を作成するか、先にドキュメントを変換してください。
  • レイヤーの境界はドキュメントの境界とは異なります。ペイントレイヤーのbounds()は描画された範囲であり、ピクセルが反映されるまでは空の場合があります。キャンバス全体を扱うにはdoc.width()/height()を使ってください。
  • Kritaは終了時にkritarcを書き換えるため、Kritaの起動中にプラグインをインストールまたは有効化しても、変更は黙って元に戻されます。先にKritaを終了してください(インストーラーは、Kritaの起動中は処理を拒否します)。
  • GUIから起動したKrita(open -a krita)には環境変数が引き継がれません。そのため、KRITA_BRIDGE_PORTが有効になるのは、Kritaをターミナルから起動した場合のみです。
  • paint*にはビューが必要で、ユーザーのブラシ状態を引き継ぎます。ビューがない状態ではpaintAbility()でさえKritaをクラッシュさせます。ビューがある場合、プリセット、サイズ、不透明度、ブレンドモード、前景色はすべてアクティブなビューの設定から取得されるため、必要な値を設定し、最初にpaintAbility() == "PAINT"を確認してください。paintLineはQPointを要求しますが、矩形関連の呼び出しはQRectFを受け取ります。ツール、変形ウィジェット、キャンバスへの入力はスクリプトから操作できません。メニューコマンドを実行する方法はapp.action(id).trigger()のみで、アクティブなドキュメントに対して実行され、結果は返されません。
  • スクリプトによる編集は、確実に取り消せるとは限りません。ユーザー自身のドキュメントに対して破壊的な操作を行う前に、レイヤーを複製するかコピーを保存してください。references/recipes.mdを参照してください。

セキュリティ

ブリッジをインストールすると、ユーザーのマシン上でコード実行サーバーを実行することになります。インストールを依頼する前に、そのことを伝えてください。

  • ブリッジは 127.0.0.1:8737(KRITA_BRIDGE_PORT)にバインドし、ライブセッション内で POST された Python コードを実行します。実行権限はユーザーの権限で、対象はユーザーが開いているドキュメントです。リクエストには認証がありません。ローカルのすべてのプロセスと、共有マシン上のほかのすべてのユーザーが、このブリッジ経由で Krita を操作できます。
  • ウェブページからは操作できません。Origin ヘッダーを含むリクエストや、クロスサイトの Sec-Fetch-Site を含むリクエストは 403 で拒否されるため、ユーザーのブラウザー上のページからブリッジにアクセスすることはできません。このチェックが唯一の保護策で、トークンはありません。
  • インストール方法によって、ポートが開いたままになる期間が決まります。 krita-install-bridge.sh は pykrita プラグインを有効にするため、以降はエージェントが操作しているかどうかにかかわらず、Krita を起動するたびにリッスンします。Scripter への貼り付けによる方法は Krita を終了するまで有効です。永続的なリスナーを望まないユーザーには、こちらを案内してください。
  • ブリッジを停止するには Krita を終了してください。 削除するには、Settings ▸ Configure Krita ▸ Python Plugin Manager で Krita Bridge のチェックを外してください。リモートからのシャットダウン機能はありません。

コメント