-
-
Notifications
You must be signed in to change notification settings - Fork 705
Add jxl chunked save #4174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add jxl chunked save #4174
Conversation
wrote some NOTES on what we need to do next
instead, try to drive libvips from data_at it should be A LOT simpler
Add preeval, eval, posteval, minimise signals. We have to move vips_image_preeval(), vips_image_eval(), vips_image_posteval() to the public API.
It seems (from #libjxl on discord) that we'll need to make a threadpool and generate libjxl tiles in parallel. We can't rely on |
…to add-jxl-chunked-save2
experiment with various caching strategies ... but it looks like none will work, since we will always throw away the cache on the original jpegload on minimise we're going to have to use the super-complicated sink_disc plan
We are doing parallel pixel generation by running a libvips pipeline for each 2k x 2k JXL tile. But this means that we emit a This PR works, but only by making We should probably bite the bullet and implement the incredibly complicated |
Ah, a better fix! Don't emit |
Sample JXL HDR image for testing: https://github.com/immich-app/immich/files/14755028/JPEG.XL.HDR.Test.zip libvips loads this as a ushort, it looks fine, I guess the HDR element is in the transfer function. There's a synthetic profile attached, but it only supports
You can import to make a LAB image:
It looks nice: But I can't see any out of range brightnesses:
(the second column in the second row is peak L*, 94.8 here, I'd expect to see values over 100 for specular highlights) You can get the LAB->sRGB conversion automatically like this:
Making this image: That will use the libvips LAB->sRGB conversion. You could also write:
That will convert to sRGB using LCMS and the srgb profile bundled with libvips. |
We'll should probably make libvips ICC import fall back to perceptual if relative is not supported. |
I've been looking into the macos crashes and it seems to be For example, running ...
VIPS_FREEF(magick_destroy_exception, magick7->exception);
g_mutex_clear(&magick7->lock); And
ie. just zero (as you'd expect). If I move the
And The docs say:
Which doesn't seem to be working. I'll try to make a small reproducer and report upstream. |
This seems to reproduce it: // compile with
// cc mutex.c `pkg-config glib-2.0 --cflags --libs`
#include <stdio.h>
#include <glib.h>
GMutex lock_static;
int
main(void)
{
GMutex lock_stack = { 0 };
printf("clearing lock in static storage ...\n");
g_mutex_clear(&lock_static);
printf("clearing lock on stack ...\n");
g_mutex_clear(&lock_stack);
return 0;
} I see:
System:
|
I made an issue here: https://gitlab.gnome.org/GNOME/glib/-/issues/3682 |
I found a bit more out: it happens when glib is NOT configured to use the native glib mutex and instead uses the platform POSIX mutex. It doesn't check for a null pointer before calling It looks like |
The glib devs clarified the docs: if you have a mutex in static storage, it's ok to not init or clear it. It'll leak with some mutex backends, but since it's in static storage, the leak is bounded and doesn't matter. For dynamically allocated memory (ie. stack or heap), you must init exactly once and you must clear exactly once. Init to zero is not enough. I think we're going to have to double-check all our GMutex usage :( |
It looks like we must only call |
Thanks for clarifying this upstream and fixing this!
Ah, bummer. This reminds me of this suboptimal approach: libvips/libvips/foreign/fits.c Lines 142 to 143 in d0c4d13
|
Yes, I saw that haha. Safe, but a little ugly. |
I've not revised jxlload or added more than four channel support, but let's try to merge, this PR has taken far too long. We can do another round of work in a follow-up. |
libvips/foreign/jxlload.c
Outdated
/* Tinkering with output transforms. Experiment with this plus | ||
* | ||
* https://sneyers.info/hdrtest/ | ||
* | ||
* and see if we can get values out of SDR range | ||
*/ | ||
|
||
JxlColorEncoding enc = { | ||
.color_space = JXL_COLOR_SPACE_RGB, | ||
.white_point = JXL_WHITE_POINT_D65, | ||
.primaries = JXL_PRIMARIES_SRGB, | ||
.transfer_function = JXL_TRANSFER_FUNCTION_SRGB, | ||
.rendering_intent = JXL_RENDERING_INTENT_RELATIVE, | ||
}; | ||
if (JxlDecoderSetPreferredColorProfile(jxl->decoder, &enc)) { | ||
vips_foreign_load_jxl_error(jxl, | ||
"JxlDecoderSetOutputColorProfile"); | ||
return -1; | ||
} | ||
if (JxlDecoderSetDesiredIntensityTarget(jxl->decoder, 10000)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we revert this to its original?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, let's do the HDR stuff in a follow up PR.
Co-authored-by: Kleis Auke Wolthuizen <github@kleisauke.nl>
libjxl 0.11 no longer has these
FWIW, I see similar results on my AMD Ryzen 9 7900 workstation.
$ /usr/bin/time -f %M:%e vips copy ~/st-francis.jpg x.jxl
5533800:50.35 This PR: $ /usr/bin/time -f %M:%e vips copy st-francis.jpg x.jxl
1877692:53.78 So, ~3x less memory, similar performance and byte-identical images. Nice! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! I noticed a couple of unused things, but that can also be cleaned up in a follow-up PR.
Phew! Thanks for the review! |
This PR adds support for the new libjxl chunked save API.
We were preparing a whole frame of pixels then writing in one shot, but that's obviously not great for large images. This adds a new save path using
JxlEncoderAddChunkedFrame()
. With a 30k x 30k sample image I see:It's very slightly faster, since it's able to overlap JPEG decode with JXL encode, and needs 7x less memory.
This is version 3 of this PR I think :( and is quite simple. The
NOTES
file outlines earlier (and crazily complicated) schemes I tried.TODO:
JxlEncoderAddChunkedFrame()
to drive libvips evaluation (and notvips_sink_*()
), so it's missing things likepreeval
,eval
etc. -- we should add these