mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-02 22:30:20 -06:00
- gpu: the test modules share one GPU_LOCK, and timing and vector_mask take it too, so one process drives one wgpu device at a time. - ui-egui: the wgpu tests in pattern_preview_canvas, adjust_preview_gpu, marquee_autoscroll and layout_perf take gpu_lock(). - affinity compound_limits: the child process selects its test by the module path (compound_limits::<name>); with --exact, the bare name matches no test and the child passes without running the check. - xtask scorecard reads crates/io/tests/it/corpus.rs. - Docs and comments name the new test paths and the --test it filters.
78 lines
4.6 KiB
Markdown
78 lines
4.6 KiB
Markdown
# 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/it/krita.rs` opens a `.kra` through the agent surface, edits it and saves
|
||
it as OpenRaster. `crates/engine/tests/it/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 it -- krita_corpus:: --ignored --nocapture
|
||
KRITA_TEST_DIR=/path/to/photocraft-corpus/krita cargo test -p photocraft-engine --test it -- 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.
|