Bug 500877 - Inefficient storage of layer bitmaps
Summary: Inefficient storage of layer bitmaps
Status: CONFIRMED
Alias: None
Product: krita
Classification: Applications
Component: File formats (other bugs)
Version First Reported In: 5.2.9
Platform: Microsoft Windows Microsoft Windows
: NOR normal
Target Milestone: ---
Assignee: Krita Bugs
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2025-03-01 01:22 UTC by jonathanbr30
Modified: 2025-05-22 22:54 UTC (History)
2 users (show)

See Also:
Latest Commit:
Version Fixed/Implemented In:
Sentry Crash Report:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description jonathanbr30 2025-03-01 01:22:41 UTC
While exploring the KRA file structure, I noticed each layer has the image stored using LFZ compression.
Upon further research, I discovered LFZ has been untouched in over 15 years, and performs worse than even raw pixel data when inside the ZIP format.
JPEG XL is also shown for comparison, as the only format which can handle all data types Krita supports (8bit int to 32bit float), encoded at effort 2 for decode speeds twice as fast as PNG.

8bit 1920 x 1080:
LZF     5.5 MB
PPM     4.6 MB
JXL     2.5 MB

8bit 3000 x 4000:
LZF    30.7 MB
PPM    24.2 MB
JXL     9.8 MB

16bit 2048 x 2048:
LZF    13.3 MB
PPM    14.5 MB
JXL     9.3 MB

16bit Float 2048 x 2048:
LZF    11.5 MB
PFM    18.7 MB
JXL     8.3 MB

32bit Float 2048 x 1024:
LZF     5.6 MB
PFM     6.2 MB
JXL     4.7 MB

Outright changing the format would be a long term goal and no doubt break backwards compatibility, however I think it's about time Krita caught up with the 21st century, or at least used a compression method recognised by others too. (This is the only program to use LFZ as far as I'm aware)

In a world of ever growing resolution and bitdepth, this seems like an inevitable step to keep file sizes manageable.
Comment 1 Halla Rempt 2025-03-01 11:37:21 UTC
Git commit 66c8d985cc4aa5e92afcb6c904959303d6a08f0d by Halla Rempt.
Committed on 01/03/2025 at 11:37.
Pushed by rempt into branch 'master'.

Remove unused KoLZF code

This dates back to the old KOffice days...

M  +0    -1    libs/store/CMakeLists.txt
D  +0    -354  libs/store/KoLZF.cpp
D  +0    -50   libs/store/KoLZF.h
M  +1    -8    libs/store/tests/CMakeLists.txt
D  +0    -235  libs/store/tests/TestKoLZF.cpp
D  +0    -35   libs/store/tests/TestKoLZF.h

https://invent.kde.org/graphics/krita/-/commit/66c8d985cc4aa5e92afcb6c904959303d6a08f0d
Comment 2 Halla Rempt 2025-03-01 11:41:34 UTC
Yes... This is something we have to look into. We can either add an option to the file handling settings to select the compression method, and depending on the method update the file version. There is already code to switch which tile compressor is used, with even support for a legacy tile compressor that doesn't actually compress the tile data, which could work better with the zip compressor.
Comment 3 Halla Rempt 2025-03-01 11:47:16 UTC
However, switching to uncompressed tiles make the .kra file balloon:

-rw-rw-r-- 1 halla halla  11050830 mrt  1 12:43 /home/halla/bla1.kra -- lzf
-rw-rw-r-- 1 halla halla 141850973 mrt  1 12:45 /home/halla/bla2.kra  -- legacy, no compression
-rw-rw-r-- 1 halla halla  13625370 mrt  1 12:43 /home/halla/bla.psd -- psd is bigger than .kra, probably because it uses RLE.

An interesting experiment would be to make a file exporter based on openraster (which saves to png) but which uses the jxl export code instead.
Comment 4 Halla Rempt 2025-03-01 11:49:58 UTC
Ora is already much smaller:

-rw-rw-r-- 1 halla halla   3754673 mrt  1 12:47 /home/halla/bla3.ora

and on manually converting the png in the ora to jxl and zipping back up:

-rw-rw-r-- 1 halla halla 2600118 mrt  1 12:48 bla-jxl.zi
Comment 5 jonathanbr30 2025-03-01 11:57:10 UTC
If you test a file using floats, it should be noted that libjxl has a regression since v0.10 that causes 32bit files to bloat massively when the real/effective bitdepth is actually lower. In my short tests, it was a difference of almost 10x, so the sizes you see may not be representative.
Comment 6 jonathanbr30 2025-03-01 12:45:12 UTC
(In reply to jonathanbr30 from comment #5)
> If you test a file using floats, it should be noted that libjxl has a regression since v0.10
> that causes 32bit files to bloat massively when the real/effective bitdepth is actually lower.
> In my short tests, it was a difference of almost 10x, so the sizes you see may not be representative.
Relevant issue https://github.com/libjxl/libjxl/issues/3511

It should be noted that with libjxl's effort settings, the "Compress .kra files more" option under File Handling could be expanded to also increase effort, making it a much more impactful option.
Comment 7 Halla Rempt 2025-03-03 15:01:00 UTC
Yes, I am kinda afraid of depending on an external library for that reason.
Comment 8 jonathanbr30 2025-03-03 15:53:30 UTC
If you mean regressions, the float issue hasn't been fixed since as far as I'm aware, no one else is using 32bit float in such a large scale yet, so it's low priority. It could likely be fixed within a week if it blocks updates to Krita, though the actual impact is also hard to measure. The regression usually only applies to 'false' 32bit images, that are simply rescaled from lower bitdepths or don't use the full range.

The current LZF compression has test files inside the repo, a .KRA file for each data type to test libjxl before release would likely catch any issues or regressions. (Files not being lossless, failing to decode, a larger than 20% size increase, ect). But that's assuming we get that far.
Comment 9 Dmitry Kazakov 2025-05-12 09:12:59 UTC
Just to make it clear, the same compression method is used when the tiles are dumped into the swap file! So switching the compression by default right away is **NOT** a go.

Requirements for the compression methods:

1) We have three uses of the compression:
    * saving to a zip-compressed .kra
    * saving to an uncompressed .kra (used in autosave)
    * saving to an uncompressed swap file

2) "saving to a zip-compressed .kra" should target the best efficiency

3) "saving to an uncompressed .kra (used in autosave)" should target the best speed

4) "saving to an uncompressed swap file" should be **strictly** not slower than LZF for both, compression and uncompression, which should be proved by unittests.

5) The compression method should support "sparse" tiles placement

6) The compression method should support U8, U16, F16 and F32 pixel formats in RGBA, CMYKA, GrayA, LabA and XYZA color spaces. Ideally, it should be covered by unittests before merging.
Comment 10 jonathanbr30 2025-05-12 09:22:11 UTC
In the context of JPEG XL
2, 3 and 6 should be covered by default, using the effort setting. 
5 should be possible in future with the cropped decoding API, since it already uses groups of pixels. 
4 would be more tricky... The fastest setting can do 600MP/s, but currently that's only for int16 at most. No doubt something faster could be made, especially if it were targeting this application.
Comment 11 jonathanbr30 2025-05-22 22:54:58 UTC
Another idea. If the compression method for swapping could be decoupled from file saving, then it could also be upgraded to a modern alternative optimized for speed.

Based on https://github.com/lz4/lz4?tab=readme-ov-file#benchmarks, ZSTD is 30% smaller while maintaining the same speeds, or LZ4 is twice as fast at the same compression ratio. It would obviously be more work, but would also futureproof it for upgrades in the future.