Krita

A model drives Krita 5.2.9 through the Hub to paint a postcard on two layers and save it as a .kra file. The first script worked. This page gives the text, the script, the Hub’s answers and the capture, and says how the bridge was started: not from Scripter, which was not tried.

Words in several languages flow over a landscape painted in Krita; the motion is rendered by the Hub in Blender.

Start Krita with its bridge

The guide has the operator run the bridge file from Scripter. This run did not use that gesture. It started Krita on a virtual X server, in a new profile, with a startup extension whose whole __init__.py runs the bridge file:

xvfb-run -a krita --nosplash
exec(open('/usr/share/libremodular-mcp/bridges/krita_bridge.py', encoding='utf-8').read())

The encoding is not optional. Krita’s embedded Python reads files in the encoding of the locale it was started under, and the bridge file holds accented characters: under the C locale a command without encoding='utf-8' stopped with UnicodeDecodeError, as the compatibility page records. The run used port 9312 in config.toml and a new token. The port listened 10.4 s after the start, and get_state on hub listed Krita as connected:

Hub response
{
  "app": "hub",
  "app_version": "0.1.0",
  "bridge_version": null,
  "captured_at": "2026-10-21T12:47:48.951Z",
  "connected": true,
  "content_origin": "hub",
  "screenshot": null,
  "state": {
    "modules": [
      {
        "app_version": "5.2.9",
        "bridge_version": "1",
        "connected": true,
        "mode": "tcp",
        "name": "krita"
      }
    ]
  },
  "warnings": []
}

Text given to the model

Text given to the model
In Krita, create a document named “Postcard”, 600 by 400 pixels. On a paint layer named “Sky”, paint a vertical gradient from dark blue (#1c385c) at the top to teal (#29a196) at the bottom. On a second paint layer named “Hill”, paint a copper (#c25438) hill: the upper half of an ellipse centred on the bottom edge, 520 pixels wide and 240 pixels high. Save the document as postcard.kra in your working directory.

Before writing, the model called get_state on krita: "document": null. The document does not exist yet, so the script has to create it and open it in a window before it can paint.

Script written

A model, Claude Sonnet 5.5, wrote this script from the guide and from that state. It is not a script taken from the project’s tests. The script writes pixel bytes in the order blue, green, red, alpha; the colours of the capture below show that this is the order Krita expects for this document. The script sets batch mode before saveAs; what happens without it was not tried.

python · Script written
import os

krita = Krita.instance()
document = krita.createDocument(600, 400, "Postcard", "RGBA", "U8", "", 72.0)
krita.activeWindow().addView(document)
root = document.rootNode()

def lerp(a, b, t):
    return round(a + (b - a) * t)

top, bottom = (0x1c, 0x38, 0x5c), (0x29, 0xa1, 0x96)
sky_pixels = bytearray()
for y in range(400):
    t = y / 399
    r, g, b = (lerp(top[i], bottom[i], t) for i in range(3))
    sky_pixels += bytes((b, g, r, 255)) * 600
sky = document.createNode("Sky", "paintlayer")
root.addChildNode(sky, None)
sky.setPixelData(bytes(sky_pixels), 0, 0, 600, 400)

hill_pixels = bytearray()
for y in range(400):
    for x in range(600):
        inside = ((x - 300) / 260) ** 2 + ((y - 400) / 120) ** 2 <= 1
        hill_pixels += bytes((0x38, 0x54, 0xC2, 255)) if inside else bytes((0, 0, 0, 0))
hill = document.createNode("Hill", "paintlayer")
root.addChildNode(hill, None)
hill.setPixelData(bytes(hill_pixels), 0, 0, 600, 400)

document.refreshProjection()
document.setBatchmode(True)
path = os.path.join(LMM_WORK_DIR, "postcard.kra")
saved = document.saveAs(path)
result = {"saved": saved, "kra_bytes": os.path.getsize(path), "layers": [node.name() for node in document.topLevelNodes()]}

The Hub’s answer

Hub response
{
  "app": "krita",
  "duration_ms": 1060,
  "error": null,
  "result": {
    "kra_bytes": 110725,
    "layers": [
      "Background",
      "Sky",
      "Hill"
    ],
    "saved": true
  },
  "status": "ok",
  "stderr": "",
  "stdout": "",
  "truncated": false
}

The script ran in 1.06 s. The document has three layers, because a new Krita document starts with a Background layer that the script left alone, under Sky and Hill. get_state then described the document and returned a capture of its projection:

Hub response
{
  "app": "krita",
  "app_version": "5.2.9",
  "bridge_version": "1",
  "captured_at": "2026-10-21T12:47:50.526Z",
  "connected": true,
  "content_origin": "document",
  "screenshot": "$XDG_RUNTIME_DIR/libremodular-mcp/captures/krita-20261021T124750502Z.png",
  "state": {
    "document": {
      "active_layer": "{727e9cd9-e6e1-423c-ade7-1bf29d6d5289}",
      "color_depth": "U8",
      "color_model": "RGBA",
      "color_profile": "sRGB-elle-V2-srgbtrc.icc",
      "height": 400,
      "layers": [
        {
          "id": "{69f5540d-2916-4771-8df7-1fcad46b677f}",
          "name": "Background",
          "opacity": 255,
          "type": "paintlayer",
          "visible": true
        },
        {
          "id": "{ed307be8-5a93-4b16-8739-7508916ea687}",
          "name": "Sky",
          "opacity": 255,
          "type": "paintlayer",
          "visible": true
        },
        {
          "id": "{727e9cd9-e6e1-423c-ade7-1bf29d6d5289}",
          "name": "Hill",
          "opacity": 255,
          "type": "paintlayer",
          "visible": true
        }
      ],
      "name": "Postcard",
      "width": 600
    }
  },
  "warnings": []
}

The image produced

The capture is the merged image Krita displays, 600 by 400 pixels. The pixels read in it are #1C385C at the top, #29A196 at the bottom and #C25438 inside the hill, the three colours the text asked for. The .kra file the script saved is 110 725 bytes.

A vertical gradient from dark blue to teal with a copper half-ellipse rising from the bottom edge.
The capture of the Postcard document after the script, as exported by the Hub.

Reference guide in the repository →

NextBlender →