# 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::()` 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 `` and `` 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: - `colorspacename` is `"RGBA"` - `channeldepth` is `"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`) 1. Change `writeln!(output, "VERSION 2")` → `writeln!(output, "VERSION 1")` in `encode_layer_vers` 2. Change `b"VERSION 2\n"` → `b"VERSION 1\n"` in `encode_mask_vers` 3. Remove the fake LZF compression loop in `encode_layer_vers` (lines 308–316) 4. Replace with writing raw planar pixel data directly (no version byte, no control bytes) 5. Update tile header tag from `"LZF"` to `"RAW"` (line 333) 6. Repeat steps 3–5 for `encode_mask_vers` (lines 394–403) ### Task B: Fix UUID format (`kra_saver.rs`) 7. Replace `let uuid = format!("{{{:032x}}}", layer.id)` with proper 128-bit UUID: ```rust let high = rand::random::(); let low = rand::random::(); 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 ); ``` 8. Apply same fix to mask UUID generation on line 257 ### Task C: Add color space validation (`lib.rs`) 9. In `try_import_krita_maindoc`, after reading width/height from ``: - Read `colorspacename` and `channeldepth` attributes - If `colorspacename != "RGBA"`, return error: "Unsupported color space: {name}" - If `channeldepth != "U8"`, return error: "Unsupported channel depth: {depth}" - (Future: add U16→U8 conversion) ### Task D: Add error logging (`lib.rs`) 10. Add `log::warn!` before each `continue` / 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`) 11. In `try_import_krita_maindoc`, after the `maindoc.xml` attempt fails, try `archive.by_name("layers.xml")` before returning `Ok(vec![])`. ### Task F: Normalize `DATA` line format (`kra_saver.rs`) 12. Change line 329: `writeln!(output, "DATA {}", tiles.len())` → `writeln!(output, "DATA")` 13. 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: 1. **Roundtrip test:** Save a multi-layer document as KRA, then import it back. Compare pixels and layer names against the original. 2. **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. 3. **Existing test suite:** Run `cargo test -p hcie-kra` and `cargo test -p hcie-engine-api --test visual_regression` to ensure no regressions. 4. **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)