6.8 KiB
KRA Format Fix Plan
Root Causes (confirmed by analysis)
1. VERSION Header Mismatch — Krita Crash + Self-Import Failures
File: hcie-kra/src/kra_saver.rs lines 324–329, 409–415
The saver writes VERSION 2\n in the VERS tile header, but the compressed payload uses
version byte 1 (meaning no delta decoding). Krita's VERS parser sees VERSION 2 and
applies delta decoding after decompression, producing garbage or a crash.
Our own importer (hcie-kra/src/lib.rs line 158) also checks if version >= 2 and
would apply delta decoding, producing wrong pixel data.
Fix: Change both encode_layer_vers and encode_mask_vers to write VERSION 1
instead of VERSION 2.
2. Fake "LZF" Compression — Bloated Files + Non-Standard
File: hcie-kra/src/kra_saver.rs lines 308–316, 394–403
The "LZF" compression is a custom RLE scheme that adds 513 bytes of overhead per tile
(1 version byte + 512 control bytes), making files larger than uncompressed. A full
1920×1080 layer (510 tiles) goes from 8.29 MB to 8.62 MB (+4%). The format happens to
look like real LZF literal runs, so our lzf_decompress function processes it correctly,
but a real LZF decoder would fail on tiles requiring back-references.
Fix: Store tiles as RAW (uncompressed). Remove the fake compression logic and write
raw planar pixel data. Label tiles as RAW instead of LZF in the tile header. The
importer already handles RAW tiles correctly (lib.rs line 166–168).
3. Invalid UUID Format — Krita Rejects Our XML
File: hcie-kra/src/kra_saver.rs line 240
UUIDs are generated from a single u64 (8 bytes) and formatted as 32 hex digits with
leading zeros: {00000000000000003aeba3f7cf423ad9}. Proper UUIDs are 128 bits (16 bytes)
with dashed formatting: {3aeba3f7-cf42-3ad9-…}. Krita may validate UUID format and
reject our non-standard forms.
Fix: Generate proper 128-bit random UUIDs using two rand::random::<u64>() calls
and format with dashes per standard UUID layout.
4. Color Space / Bit Depth Ignored — Wrong Colors on Import
File: hcie-kra/src/lib.rs lines 647–698
The importer reads colorspacename, channeldepth, and profile attributes from the
<IMAGE> and <layer> tags but ignores them. If the source file uses a non-RGBA color
space (e.g., CMYK, Grayscale) or 16-bit channel depth, the raw pixel bytes are
misinterpreted, producing wrong colors.
Fix: At minimum, validate on import that:
colorspacenameis"RGBA"channeldepthis"U8"or"U16"(for U16, convert to U8 via shifting)- Return an error if the format is unsupported
5. Silent Layer Failures — Data Loss Without Notice
File: hcie-kra/src/lib.rs lines 790, 808, 814, 837
When a layer file can't be found, VERS parsing fails, or image decoding fails, the layer is silently skipped or turned into a transparent layer. The user has no indication that data was lost.
Fix: Add log::warn! calls at each failure point, including the layer name and
reason for failure.
6. Missing layers.xml Fallback
File: hcie-kra/src/lib.rs line 626
The main importer only tries maindoc.xml. Some Krita variants write layers.xml
instead. The v2 loader (custom_loaders_kra_v2.rs) has this fallback but it's separate.
Fix: Add layers.xml as a secondary attempt in try_import_krita_maindoc.
7. Inconsistent DATA Line Format
File: hcie-kra/src/kra_saver.rs lines 329 vs 415
encode_layer_vers writes DATA {tiles.len()}\n but encode_mask_vers writes DATA\n
(no count). The parser handles both, but it's inconsistent.
Fix: Make both use DATA\n (no count) — the parser ignores the count anyway, and
removing it avoids an extra format requirement.
Implementation Tasks
Task A: Fix VERS header and compression (kra_saver.rs)
- Change
writeln!(output, "VERSION 2")→writeln!(output, "VERSION 1")inencode_layer_vers - Change
b"VERSION 2\n"→b"VERSION 1\n"inencode_mask_vers - Remove the fake LZF compression loop in
encode_layer_vers(lines 308–316) - Replace with writing raw planar pixel data directly (no version byte, no control bytes)
- Update tile header tag from
"LZF"to"RAW"(line 333) - Repeat steps 3–5 for
encode_mask_vers(lines 394–403)
Task B: Fix UUID format (kra_saver.rs)
- Replace
let uuid = format!("{{{:032x}}}", layer.id)with proper 128-bit UUID:let high = rand::random::<u64>(); let low = rand::random::<u64>(); let uuid = format!( "{{{:08x}-{:04x}-{:04x}-{:04x}-{:012x}}}", (high >> 32) as u32, (high >> 16) as u16 & 0xFFFF, high as u16, (low >> 48) as u16, low & 0x0000_FFFF_FFFF_FFFF ); - Apply same fix to mask UUID generation on line 257
Task C: Add color space validation (lib.rs)
- In
try_import_krita_maindoc, after reading width/height from<IMAGE>:- Read
colorspacenameandchanneldepthattributes - If
colorspacename != "RGBA", return error: "Unsupported color space: {name}" - If
channeldepth != "U8", return error: "Unsupported channel depth: {depth}" - (Future: add U16→U8 conversion)
- Read
Task D: Add error logging (lib.rs)
- Add
log::warn!before eachcontinue/ silent fallback:- Line 790 (layer file not found): warn with layer name
- Line 808 (SVG rasterization failed): warn with layer name
- Line 814 (VERS parse failed): warn with layer name
- Line 837 (image load failed): warn with layer name
Task E: Add layers.xml fallback (lib.rs)
- In
try_import_krita_maindoc, after themaindoc.xmlattempt fails, tryarchive.by_name("layers.xml")before returningOk(vec![]).
Task F: Normalize DATA line format (kra_saver.rs)
- Change line 329:
writeln!(output, "DATA {}", tiles.len())→writeln!(output, "DATA") - The mask encoder already uses
b"DATA\n"— leave unchanged.
Files to Modify
| File | Changes |
|---|---|
hcie-kra/src/kra_saver.rs |
Tasks A, B, F |
hcie-kra/src/lib.rs |
Tasks C, D, E |
Validation
After implementing, verify with:
-
Roundtrip test: Save a multi-layer document as KRA, then import it back. Compare pixels and layer names against the original.
-
Krita compatibility: Open the exported KRA in Krita ≥ 5.x. Verify it opens without crash and displays correct colors. Also open the PSD→KRA exported file and verify it matches the mergedimage.png preview.
-
Existing test suite: Run
cargo test -p hcie-kraandcargo test -p hcie-engine-api --test visual_regressionto ensure no regressions. -
Edge cases:
- Empty layers (all-transparent pixels)
- Layers with masks
- Group layers
- Layer names containing special characters (
&,<,>, Unicode) - Error on unsupported color space (e.g., CMYK, Grayscale)
- Error on unsupported bit depth (e.g., U16, F16)