关于 krita
krita skill 通过 Krita Bridge 在运行中的 Krita 会话里执行 Python 脚本,面向需要自动构建、检查和导出图像的创作者。它利用 libkis 与 PyQt5 操作文档,并通过画布投影和脚本输出验证结果。
主要功能
- 01在实时 Krita 会话执行脚本
- 02创建并编辑文档与图层
- 03绘制像素、矢量和文字
- 04应用滤镜、选区与蒙版
- 05检查画布投影并导出文件
适用场景
- 01自动构建海报并导出
- 02批量导出图层或文件
- 03处理动画帧并导出视频
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jablonkai/ skills --skill krita安装后在 Claude Code 中输入/krita确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api-reference.mdrecipes.mdscripts/example-poster.pykrita_bridge.desktopkrita-install-bridge.shkrita-send.shscripts/krita_bridge/__init__.pykrita_bridge.py详细介绍
Krita 控制
Krita(/Applications/krita.app)内置 Python 3,并通过 libkis API(from krita import *)开放其整个文档模型,同时提供 PyQt5 用于像素处理。
通过 Krita Bridge 控制它——这是一个在 运行中的 Krita 内运行的 pykrita 插件,可执行 POST 到 127.0.0.1:8737 的任意脚本。由于脚本在运行中的会话的 GUI 线程上执行,画布会随着创作过程实时更新,用户可以看到操作过程,而且只能在主线程中调用的 API 部分也能安全使用。以下每个调用都在 Krita 5.3.4(Python 3.13,PyQt5 5.15.11)中运行过;该 skill 最初是在 5.3.3 上编写的。
- scripts/krita-install-bridge.sh — 将桥接插件安装到用户的资源文件夹,并在
kritarc中启用它(一次性操作)。 - scripts/krita_bridge/ — 插件本身;也可粘贴到 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,以及 API 无法实现的功能。
控制循环
-
确保桥接服务已启动:
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——这与 Settings ▸ Configure Krita ▸ Python Plugin Manager 复选框的作用相同。修改其他应用的配置属于用户级更改,有些设置 会拒绝让你执行此操作;请将这一行命令交给用户,而不是绕过限制。如果当前会话完全不能 安装任何内容,用户可以将 krita_bridge.py 粘贴到 Tools ▸ Scripts ▸ Scripter,然后按 Ctrl+R——桥接服务功能相同,退出时即消失。 -
将构建脚本写入临时目录并发送:
OUT=/path/to/outdir bash scripts/krita-send.sh /path/build.py。脚本的命名空间中会预先注入Krita和OUT(OUT也会作为os.environ["OUT"]注入),而KRITA_SEND_TIMEOUT=600(秒)用于应对耗时较长的导出。 -
**读取反馈。**桥接服务会返回脚本捕获的 stdout/stderr,失败时还会返回 Python 回溯信息——因此
print(...)是你获取反馈的渠道。其他信息不会报告:Krita 自己的日志 窗口仍在应用内。 -
查看结果。
doc.projection(0, 0, w, h)会返回合并画布的QImage——保存后读取 PNG。这比截图更省事,也更可靠,因为它与导出时生成的内容完全一致。 -
迭代,然后交付:修复脚本并重新发送。脚本必须可重复运行——每个版本都创建 新文档,而不是修改已打开的文档,这样重新发送时就不会在上一次尝试的结果上叠加图层。
**所有操作都通过正在运行的会话完成。**不要从 shell 启动第二个 Krita——不要使用
krita --export 或 --export-sequence,也不要在后台启动无头实例。两个 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
新建文档时已包含一个绘画图层,该图层填充为不透明白色,并且使用用户语言命名
(匈牙利语版 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 会引发 TypeError,尽管基于矩形的调用需要 QRectF。在调用 paintAbility() 或任何 paint* 方法之前,先添加视图:对于没有视图的文档,这些方法不会友好地报错,而是会导致 Krita 崩溃(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,然后读取。请在每个构建脚本末尾执行此操作——只需一行,就能捕获断言无法发现的错误。 - 数值数据:将尺寸、分辨率和图层列表(名称、类型、边界、混合模式、不透明度)导出为
metrics.json并读取,不要只凭目测判断图层堆栈。 - 交付:导出的 PNG/JPEG 可在任何地方打开;保存的
.kra会保留所有图层,供用户继续编辑。
注意事项
- 桥接程序在当前会话中运行,因此状态会在多次发送之间保留:打开的文档、当前视图和选区都会保持不变。为文档添加版本号(
createDocument(..., "poster_v2", ...)),以免重新发送时与之前的文档冲突。 - 在导出或保存前调用
setBatchmode(True),否则 Krita 可能会弹出格式选项对话框,但无人可以点击,导致发送超时。它不适用于close()——请参阅关闭文档。 - 发送超时通常意味着出现了模态对话框,而不是桥接程序失效:对话框会运行嵌套事件循环,因此桥接程序仍会响应,且
QApplication.activeModalWidget()会指出该对话框——请参阅 references/recipes.md。 - 每次读取或导出前调用
refreshProjection()+waitForDone();否则导出的画布可能比最新合成结果落后一版。 - 不透明度范围为 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()返回None,而不是文档所述的布尔值;请检查文件是否存在。- 无法通过 API 创建关键帧——
setCurrentTime+setPixelData始终不会创建关键帧,尽管每次保存看起来都正常。每帧都要触发add_blank_frame,并在之后调用doc.waitForDone()——该操作是异步的,不等待的话,每帧像素都会提前落在前一帧;请参阅 references/api-reference.md。 setPixelData需要的是节点的色彩空间,而不是任意顺序的 RGBA:对于"RGBA"/"U8"文档,Format_ARGB32缓冲区可以直接使用;对于 U16/F32 文档,请构建匹配的字节数据,或先转换文档。- 图层边界不等于文档边界——绘画图层的
bounds()是已绘制内容的范围,在像素写入前可能为空;处理完整画布时请使用doc.width()/height()。 - Krita 退出时会重写
kritarc,因此在运行期间安装或启用插件会被悄悄还原——请先退出 Krita(否则安装程序会拒绝继续)。 - 通过 GUI 启动的 Krita(
open -a krita)无法获取环境变量,因此只有从终端启动 Krita 时,KRITA_BRIDGE_PORT才会生效。 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 都会开始监听,无论是否有 Agent 在操控它。通过 Scripter 粘贴代码的方式只会持续到退出 Krita 为止——如果用户不想让端口永久保持监听,请提供这种方式。 - 要停止桥接程序,请退出 Krita;要移除它,请在 Settings ▸ Configure Krita ▸ Python Plugin Manager 中取消勾选 Krita Bridge。无法远程关闭。