# 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.*

## Facts

- Version tried: 5.2.9
- Mode: `tcp`

## Start Krita with its bridge

The [guide](https://github.com/RonanDavalan/LibreModular-MCP/blob/main/docs/modules/krita.md) 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:

```sh
xvfb-run -a krita --nosplash
```

```python
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](https://libremodular.davalan.fr/compatibility/index.md#krita) 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**

```json
{
  "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
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.

**Script written**

```python
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**

```json
{
  "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**

```json
{
  "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.](https://libremodular.davalan.fr/images/applications/krita-postcard.png)

*The capture of the Postcard document after the script, as exported by the Hub.*


> **Limit**
>
> Not tried: starting the bridge from Scripter (the menu path, the environment variables that reach it, how long it lives once Scripter closes), several documents open at once, and a document with a layer group.

[Reference guide in the repository](https://github.com/RonanDavalan/LibreModular-MCP/blob/main/docs/modules/krita.md)

