mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -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.
100 lines
6.3 KiB
Markdown
100 lines
6.3 KiB
Markdown
# GIMP XCF documents
|
||
|
||
PhotoCraft opens GIMP's native `.xcf` files (and `.xcf.gz`) with their layers, read only. File ›
|
||
Open, recent files, drops, the CLI (`convert`, `info`, `batch`) and automation (`doc.open`, MCP)
|
||
use the same reader in `photocraft-io` (`crates/io/src/xcf.rs`). A file is recognised by its
|
||
`gimp xcf ` signature, so an XCF with another extension still opens as one. Save suggests
|
||
`.pcraft`, which keeps everything PhotoCraft holds; exporting as `.xcf` fails with a message.
|
||
|
||
## What is kept
|
||
|
||
| | Open |
|
||
|---|---|
|
||
| Pixel layers (any size and position, also off-canvas), opaque or with alpha | yes |
|
||
| Groups, nested; a group in Pass through mode | yes |
|
||
| Names, visibility, opacity, offsets | yes |
|
||
| Layer masks | yes, applied or disabled as in GIMP |
|
||
| Blend modes | the 2.10 set and the legacy set, mapped to Photoshop's (table below) |
|
||
| Locks (alpha, content, position), colour tags | yes |
|
||
| Precision | 8 and 16-bit integer as such; 32-bit integer as 16-bit; 16/32/64-bit float as 32-bit float |
|
||
| Linear-light precisions | the document gets a linear sRGB (or linear gray) profile |
|
||
| Colour profile (`icc-profile` parasite), resolution, guides, comment | yes |
|
||
| Grayscale, RGB | as such; indexed images open as RGB |
|
||
| Extra channels, the saved selection | yes (alpha channels, the selection) |
|
||
| Text, vector and link layers | as their pixels, with a note |
|
||
| Compression | none, RLE, zlib |
|
||
| XCF versions | 0 to 26 (GIMP 1.x to 3.2); newer files open with a note, unknown parts skipped |
|
||
|
||
Not kept, each with a note on open: non-destructive layer effects (GIMP 3's layer filters; the
|
||
layer opens unfiltered), paths, floating selections, item sets, sample points. Layer effects are
|
||
not representable without GEGL's operations; paths could follow later.
|
||
|
||
## Blend modes
|
||
|
||
| GIMP | PhotoCraft |
|
||
|---|---|
|
||
| Normal, Dissolve, Multiply, Screen, Overlay, Difference, Darken only, Lighten only, Divide, Hard light, Vivid light, Pin light, Linear light, Hard mix, Exclusion, Linear burn, Subtract | the same |
|
||
| Addition | Linear Dodge (Add) |
|
||
| Dodge, Burn | Color Dodge, Color Burn |
|
||
| Pass through (groups) | Pass Through |
|
||
| Soft light (both the 2.10 and the legacy one) | Soft Light (GIMP's formula differs a little; noted) |
|
||
| HSV Hue / Saturation / Value, HSL Color, LCH Hue / Chroma / Color / Lightness, Luminance | Hue, Saturation, Luminosity, Color (Photoshop's HSL-based modes; noted) |
|
||
| Luma darken only, Luma lighten only | Darker Color, Lighter Color (noted) |
|
||
| Behind, Grain extract, Grain merge, Color erase, Erase, Merge, Split | Normal, with a note |
|
||
|
||
## How the composite differs from GIMP
|
||
|
||
Three of GIMP's defaults have no Photoshop counterpart, so a file opened in PhotoCraft can render
|
||
a little differently from GIMP; the first two are noted on open when they apply:
|
||
|
||
- **Linear light.** Since 2.10 GIMP blends most modes and composites every layer (alpha blending
|
||
too) in linear light by default. PhotoCraft, like Photoshop, composes in the document's own
|
||
encoding. Semi-transparent edges, masks and most blend modes therefore render slightly
|
||
differently: in the test files, a Multiply layer at 60% differs by up to 38/255 in one channel,
|
||
anti-aliased text edges by up to 73/255 at the edge pixels. A layer whose blend and composite
|
||
spaces GIMP set to *RGB (perceptual)* matches PhotoCraft within 2.5/255 (the `-perceptual` test
|
||
files).
|
||
- **Clip to backdrop.** GIMP composites every mode but Normal with *Clip to backdrop*: the layer
|
||
shows nothing where the layers beneath it are transparent. PhotoCraft shows it there. The
|
||
difference only appears over transparent areas of the stack.
|
||
|
||
- **Clamping after alpha.** For modes whose result can leave 0..1 (Addition, Subtract, Divide,
|
||
Dodge, Burn, Linear light), GIMP applies the pixel's alpha and the layer's opacity first and
|
||
clamps at the end; Photoshop clamps the blend result first. Fully opaque pixels at 100% match
|
||
exactly; translucent ones differ where the raw result overshoots.
|
||
|
||
Modes without an equivalent open as Normal, and the HSV/LCH/luma modes use Photoshop's HSL-based
|
||
formulas, so those layers render differently; the note names each layer.
|
||
|
||
## Limits and safety
|
||
|
||
Written from the public XCF specification (https://developer.gimp.org/core/standards/xcf/) and
|
||
files GIMP 3.2 writes; no GIMP code. Every read is bounds-checked and returns an error on a
|
||
malformed file, never a panic: canvases at most 300,000 px a side and 1 GiB of decoded pixels,
|
||
at most 8,192 layers and 1,024 channels, nesting at most 64 deep, property payloads at most
|
||
64 MiB, strings 1 MiB, gzipped files inflating to at most 1 GiB. Tile data is decoded into the
|
||
exact size the drawable declares; a run that crosses a tile or a zlib stream that ends early is an
|
||
error. The property lengths the specification warns about (`PROP_COLORMAP`, `PROP_USER_UNIT`) are
|
||
computed from the payload instead of trusted. Truncated files are covered by tests at every cut.
|
||
|
||
## Verification
|
||
|
||
Unit tests (`crates/io/src/xcf/tests.rs`) build XCF files from the specification with a small
|
||
writer and check every compression, precisions, pointer widths, groups by item path, masks,
|
||
channels, the colour map, parasites, limits and malformed input.
|
||
|
||
Real files come from GIMP 3.2.6 itself: `gimp/` of [photocraft-corpus](https://github.com/storytold/photocraft-corpus)
|
||
(generator `tools/gimp-xcf/xcf_fixtures.py`, run inside GIMP), fetched into `corpus/gimp/` by
|
||
`cargo xtask corpus --gimp` (28 files, 251 KB): procedural layers the tests regenerate sample for
|
||
sample, and for each file GIMP's own merged rendering as a PNG. `crates/io/tests/it/xcf.rs` (feature
|
||
`corpus`, run by `cargo xtask test-corpus -p io`) checks the layer data against the formulas and our
|
||
composite against GIMP's:
|
||
|
||
| File | Composite vs GIMP |
|
||
|---|---|
|
||
| `layers-8bit-perceptual`, `mask-perceptual`, `text-perceptual` | within 2.5/255 everywhere |
|
||
| `gray16`, `indexed`, `channel` | within 2/255 |
|
||
| `groups-perceptual` | within 2.5/255 except where Clip to backdrop applies (15% of pixels) |
|
||
| `legacy-modes-perceptual` | Addition, Subtract, Divide, Dodge, Burn, Hard light, Exclusion, Linear light within 2.5/255 on opaque pixels (translucent ones differ by the clamping order above); the approximated modes reported |
|
||
| the default (linear-light) files | the known gap, measured: up to 38/255 (Multiply), 59/255 (mask edge), 73/255 (text edge) |
|