Files
hcie-rust-v3.05/.kilo/plans/1784862272150-kra-format-fix.md
Your Name a056419c24
mandatory-regression-gate / deterministic-tests (push) Has been cancelled
mandatory-regression-gate / protected-performance-path (push) Has been cancelled
kra_saver_fixed
2026-07-24 16:23:45 +03:00

6.8 KiB
Raw Permalink Blame History

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)

  1. 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
    );
    
  2. Apply same fix to mask UUID generation on line 257

Task C: Add color space validation (lib.rs)

  1. 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)

  1. 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)

  1. 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)

  1. Change line 329: writeln!(output, "DATA {}", tiles.len())writeln!(output, "DATA")
  2. 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)