Files
hcie-rust-v3.05/.kilo/plans/1784862272150-kra-format-fix.md
T

164 lines
6.8 KiB
Markdown
Raw Normal View History

2026-07-24 16:23:45 +03:00
# 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 324329, 409415
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 308316, 394403
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 166168).
### 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 647698
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:
- `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 308316)
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 35 for `encode_mask_vers` (lines 394403)
### 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::<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
);
```
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 `<IMAGE>`:
- 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)