Files
0a32b3593d Add Krita (.kra) import (#4388)
Open .kra and .krz with paint layers (8/16-bit RGBA and gray, any offset), nested
and pass-through groups, names, visibility, opacity, locks, blend modes and
transparency masks, plus the image's ICC profile and resolution. Vector, filter,
fill, file and clone layers and the other mask kinds are left out with a warning;
float, CMYK and Lab documents open as Krita's merged image (preview). Import only:
Save suggests .ora. Checked against 35 Krita 5.2.9-authored files.

Co-authored-by: Scott Campbell <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 22:41:38 -04:00

78 lines
4.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Krita documents
PhotoCraft opens Krita's native documents (`.kra`, and the `.krz` archive variant) with their
layers. File › Open, recent files, drops, the CLI (`convert`, `info`, `batch`) and automation
(`doc.open`, MCP) all use the reader in `photocraft-io`; the browser build accepts them too. A
file is recognised by its `mimetype` entry (`application/x-krita`). Krita files are import-only:
Save suggests OpenRaster (`.ora`), which Krita opens with its layers, and `.pcraft` keeps
everything.
## What is read
| Krita | PhotoCraft |
|---|---|
| Paint layers, 8 and 16-bit RGBA, 8 and 16-bit gray | pixel layers, at the document's depth (a gray layer in an RGB document is converted) |
| Layer position (`x`, `y`), tiles anywhere, also off-canvas | kept |
| Groups, nested; pass-through groups; collapsed state | groups; pass-through; expanded state |
| Names, visibility, opacity, lock | kept |
| Blend modes | Krita's ids, mapped as for OpenRaster (`docs/ora.md`); a few are approximations, reported |
| Transparency masks | the layer mask (the first one; more are reported and left out) |
| Selection masks | ignored (a layer's local selection, not part of its look) |
| The image's ICC profile, resolution | kept |
Left out with a warning per layer: vector, filter, fill, file and clone layers, filter, transform
and colorize masks, layers in another colour space than those above, Inherit Alpha, per-channel
blending flags (channel locks), and animation beyond the frame saved in the layer. Drawing
assistants and reference images are reported as left out. Krita's merged image still shows
the original look, so the result can differ where those were used.
Documents whose image colour space we don't read yet (CMYK, Lab, XYZ, YCbCr, 16 and 32-bit
float) open as Krita's merged image (`mergedimage.png`), flagged as a preview, with a warning.
## The format, as observed
A ZIP whose first, stored entry is `mimetype`; `maindoc.xml` holds the node tree (topmost first)
and points each paint layer and mask at a file under `<image name>/layers/`. Those files are a
text header (`VERSION 2`, `TILEWIDTH`, `TILEHEIGHT`, `PIXELSIZE`, `DATA <tiles>`) and per tile a
`x,y,LZF,<bytes>` line followed by a compression flag and the tile's pixels in byte planes, LZF
compressed. RGB pixels are B, G, R, A; 16-bit samples are little-endian. A `.defaultpixel` file
next to each holds the colour outside its tiles. A group's own `x`/`y` does not move its
children (checked against Krita's merged image).
## Limits and safety
The reader uses the bounded ZIP reader of `photocraft-format` (no ZIP64, CRC-checked), parses
`maindoc.xml` with `roxmltree` (32 MiB cap; the DTD is declared by public id and not fetched),
and decodes tiles with a bounds-checked LZF decoder that must produce exactly one tile. Tiles
are at most 1024 px a side; canvases at most 300,000 px a side and 1 GiB of decoded pixels;
8,192 nodes; nesting at most `MAX_GROUP_DEPTH`. Version 1 tile files (Krita 1.x) are refused.
Malformed archives, XML, numbers, tile headers and tile data return errors; truncated and
corrupted files are covered by tests.
## Verification
Unit tests (`crates/io/src/kra/tests.rs`) build Krita archives in code (raw and LZF tiles) and
check layers, groups, pass-through, offsets, opacity, visibility, locks, masks, default pixels,
16-bit and gray conversion, the merged-image fallback, LZF edge cases and malformed input.
`crates/automation/tests/krita.rs` opens a `.kra` through the agent surface, edits it and saves
it as OpenRaster. `crates/engine/tests/kra.rs` (opt-in) opens a Krita-authored 16-bit file in a
session, checks it is read-only, edits with undo and saves as `.ora` and `.pcraft`.
Real-file checks use the Krita-authored oracles in
[photocraft-corpus](https://github.com/storytold/photocraft-corpus) (`krita/`, made by
`tools/krita-oracles/generate.py`, which removes Krita's embedded third-party ICC profiles so
the files are sRGB):
```sh
KRITA_TEST_DIR=/path/to/photocraft-corpus/krita cargo test -p photocraft-io --test krita_corpus -- --ignored --nocapture
KRITA_TEST_DIR=/path/to/photocraft-corpus/krita cargo test -p photocraft-engine --test kra -- --ignored
```
2026-10-09, Krita 5.2.9: 31 of 35 `.kra` files render within 3/255 of Krita's merged image; the
others differ for known reasons (the three blend-mode tie-breaks as for OpenRaster, and the
file with a vector layer, which is left out). Before the profiles were stripped, a linear-light
16-bit document with its embedded profile matched Krita's merged image within 0.5/255 after
flattening and converting to sRGB.
The reader is written from files Krita writes; no Krita code was used.