164 lines
6.8 KiB
Markdown
164 lines
6.8 KiB
Markdown
|
|
# 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:
|
|||
|
|
- `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::<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)
|