1 /*
2 * JPEG XL decoding support via libjxl
3 * Copyright (c) 2021 Leo Izen <leo.izen@gmail.com>
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 /**
23 * @file
24 * JPEG XL decoder using libjxl
25 */
26
37
43
44 #include <jxl/decode.h>
45 #include <jxl/thread_parallel_runner.h>
47
53 #if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
55 #endif
70
72 {
74
75 ctx->events = JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE
76 | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME
77 | JXL_DEC_BOX;
78 if (JxlDecoderSubscribeEvents(
ctx->decoder,
ctx->events) != JXL_DEC_SUCCESS) {
81 }
82
83 if (JxlDecoderSetDecompressBoxes(
ctx->decoder, JXL_TRUE) != JXL_DEC_SUCCESS) {
86 }
87
88 if (JxlDecoderSetParallelRunner(
ctx->decoder, JxlThreadParallelRunner,
ctx->runner) != JXL_DEC_SUCCESS) {
91 }
92
94 memset(&
ctx->basic_info, 0,
sizeof(JxlBasicInfo));
95 memset(&
ctx->jxl_pixfmt, 0,
sizeof(JxlPixelFormat));
96 ctx->prev_is_last = 1;
97
98 return 0;
99 }
100
102 {
104 JxlMemoryManager manager;
105
107 ctx->decoder = JxlDecoderCreate(&manager);
111 }
112
117 }
118
123
125 }
126
128 {
129 const JxlBasicInfo *basic_info = &
ctx->basic_info;
130 JxlPixelFormat *
format = &
ctx->jxl_pixfmt;
131 format->endianness = JXL_NATIVE_ENDIAN;
132 format->num_channels = basic_info->num_color_channels + (basic_info->alpha_bits > 0);
133 #if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
135 ctx->jxl_bit_depth.type = JXL_BIT_DEPTH_FROM_PIXEL_FORMAT;
136 ctx->jxl_bit_depth.exponent_bits_per_sample = basic_info->exponent_bits_per_sample;
137 #endif
138 /* Gray */
139 if (basic_info->num_color_channels == 1) {
140 if (basic_info->bits_per_sample <= 8) {
141 format->data_type = JXL_TYPE_UINT8;
143 }
144 if (basic_info->exponent_bits_per_sample || basic_info->bits_per_sample > 16) {
145 if (!basic_info->alpha_bits) {
146 format->data_type = JXL_TYPE_FLOAT;
148 }
150 }
151 format->data_type = JXL_TYPE_UINT16;
153 }
154 /* rgb only */
155 /* libjxl only supports packed RGB and gray output at the moment */
156 if (basic_info->num_color_channels == 3) {
157 if (basic_info->bits_per_sample <= 8) {
158 format->data_type = JXL_TYPE_UINT8;
160 }
161 if (basic_info->exponent_bits_per_sample || basic_info->bits_per_sample > 16) {
162 format->data_type = JXL_TYPE_FLOAT;
164 }
165 format->data_type = JXL_TYPE_UINT16;
167 }
168
170 }
171
173 {
176
177 /* libjxl populates these double values even if it uses an enum space */
178 desc.prim.r.x =
av_d2q(jxl_color->primaries_red_xy[0], 300000);
179 desc.prim.r.y =
av_d2q(jxl_color->primaries_red_xy[1], 300000);
180 desc.prim.g.x =
av_d2q(jxl_color->primaries_green_xy[0], 300000);
181 desc.prim.g.y =
av_d2q(jxl_color->primaries_green_xy[1], 300000);
182 desc.prim.b.x =
av_d2q(jxl_color->primaries_blue_xy[0], 300000);
183 desc.prim.b.y =
av_d2q(jxl_color->primaries_blue_xy[1], 300000);
184 desc.wp.x =
av_d2q(jxl_color->white_point_xy[0], 300000);
185 desc.wp.y =
av_d2q(jxl_color->white_point_xy[1], 300000);
186
189 /* try D65 with the same primaries */
190 /* BT.709 uses D65 white point */
194 }
195
196 return prim;
197 }
198
200 {
201 switch (jxl_color->transfer_function) {
208 case JXL_TRANSFER_FUNCTION_GAMMA:
209 if (jxl_color->gamma > 0.45355 && jxl_color->gamma < 0.45555)
211 else if (jxl_color->gamma > 0.35614 && jxl_color->gamma < 0.35814)
213 else
215 break;
216 default:
218 }
219
221 }
222
224 {
226 size_t icc_len;
227 JxlDecoderStatus jret;
228 /* an ICC profile is present, and we can meaningfully get it,
229 * because the pixel data is not XYB-encoded */
230 #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
231 jret = JxlDecoderGetICCProfileSize(
ctx->decoder, &
ctx->jxl_pixfmt, JXL_COLOR_PROFILE_TARGET_DATA, &icc_len);
232 #else
233 jret = JxlDecoderGetICCProfileSize(
ctx->decoder, JXL_COLOR_PROFILE_TARGET_DATA, &icc_len);
234 #endif
235 if (jret == JXL_DEC_SUCCESS && icc_len > 0) {
240 #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
241 jret = JxlDecoderGetColorAsICCProfile(
ctx->decoder, &
ctx->jxl_pixfmt, JXL_COLOR_PROFILE_TARGET_DATA,
242 ctx->iccp->data, icc_len);
243 #else
244 jret = JxlDecoderGetColorAsICCProfile(
ctx->decoder, JXL_COLOR_PROFILE_TARGET_DATA,
ctx->iccp->data, icc_len);
245 #endif
246 if (jret != JXL_DEC_SUCCESS) {
249 }
250 }
251
252 return 0;
253 }
254
255 /*
256 * There's generally four cases when it comes to decoding a libjxl image
257 * with regard to color encoding:
258 * (a) There is an embedded ICC profile in the image, and the image is XYB-encoded.
259 * (b) There is an embedded ICC profile in the image, and the image is not XYB-encoded.
260 * (c) There is no embedded ICC profile, and FFmpeg supports the tagged colorspace.
261 * (d) There is no embedded ICC profile, and FFmpeg does not support the tagged colorspace.
262 *
263 * In case (b), we forward the pixel data as is and forward the ICC Profile as-is.
264 * In case (c), we request the pixel data in the space it's tagged as,
265 * and tag the space accordingly.
266 * In case (a), libjxl does not support getting the pixel data in the space described by the ICC
267 * profile, so instead we request the pixel data in BT.2020/PQ as it is the widest
268 * space that FFmpeg supports.
269 * In case (d), we also request wide-gamut pixel data as a fallback since FFmpeg doesn't support
270 * the custom primaries tagged in the space.
271 */
273 {
275 JxlDecoderStatus jret;
277 JxlColorEncoding jxl_color;
278 /* set this flag if we need to fall back on wide gamut */
279 int fallback = 0;
280
281 #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
282 jret = JxlDecoderGetColorAsEncodedProfile(
ctx->decoder,
NULL, JXL_COLOR_PROFILE_TARGET_ORIGINAL, &jxl_color);
283 #else
284 jret = JxlDecoderGetColorAsEncodedProfile(
ctx->decoder, JXL_COLOR_PROFILE_TARGET_ORIGINAL, &jxl_color);
285 #endif
286 if (jret == JXL_DEC_SUCCESS) {
287 /* enum values describe the colors of this image */
288 jret = JxlDecoderSetPreferredColorProfile(
ctx->decoder, &jxl_color);
289 if (jret == JXL_DEC_SUCCESS)
290 #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
291 jret = JxlDecoderGetColorAsEncodedProfile(
ctx->decoder, &
ctx->jxl_pixfmt,
292 JXL_COLOR_PROFILE_TARGET_DATA, &jxl_color);
293 #else
294 jret = JxlDecoderGetColorAsEncodedProfile(
ctx->decoder, JXL_COLOR_PROFILE_TARGET_DATA, &jxl_color);
295 #endif
296 /* if we couldn't successfully request the pixel data space, we fall back on wide gamut */
297 /* this code path is very unlikely to happen in practice */
298 if (jret != JXL_DEC_SUCCESS)
299 fallback = 1;
300 } else {
301 /* an ICC Profile is present in the stream */
302 if (
ctx->basic_info.uses_original_profile) {
303 /* uses_original_profile is the same as !xyb_encoded */
307 } else {
308 /*
309 * an XYB-encoded image with an embedded ICC profile can't always have the
310 * pixel data requested in the original space, so libjxl has no feature
311 * to allow this to happen, so we fall back on wide gamut
312 */
313 fallback = 1;
314 }
315 }
316
318 if (
ctx->basic_info.num_color_channels > 1)
322
324 /* checking enum values */
325 if (!fallback) {
329 }
330 /* fall back on wide gamut if enum values fail */
334 jxl_color.primaries = JXL_PRIMARIES_2100;
336 }
337 /* libjxl requires this set even for grayscale */
338 jxl_color.white_point = JXL_WHITE_POINT_D65;
339 }
341 if (
ctx->jxl_pixfmt.data_type == JXL_TYPE_FLOAT
342 ||
ctx->jxl_pixfmt.data_type == JXL_TYPE_FLOAT16) {
344 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
346 } else {
348 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
350 }
351 }
352 /* all colors will be in-gamut so we want accurate colors */
353 jxl_color.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
354 jxl_color.color_space =
ctx->basic_info.num_color_channels > 1 ? JXL_COLOR_SPACE_RGB : JXL_COLOR_SPACE_GRAY;
355 jret = JxlDecoderSetPreferredColorProfile(
ctx->decoder, &jxl_color);
356 if (jret != JXL_DEC_SUCCESS) {
358 /*
359 * This should only happen if there's a non-XYB encoded image with custom primaries
360 * embedded as enums and no embedded ICC Profile.
361 * In this case, libjxl will synthesize an ICC Profile for us.
362 */
367 }
368 }
369
373
374 return 0;
375 }
376
378 {
381
386 }
387
390 /* size may be larger than exif_pos due to the realloc loop */
396 }
397 /*
398 * JPEG XL Codestream orientation overrides EXIF orientation in all cases.
399 * As a result, we remove the EXIF Orientation tag rather than just zeroing it
400 * in order to prevent any ambiguity. libjxl autorotates the image for us so we
401 * do not need to worry about that.
402 */
410 }
411
413 }
414
416 {
419 /* last box was Exif */
420 size_t remainder = JxlDecoderReleaseBoxBuffer(
ctx->decoder);
421 ctx->exif_pos =
ctx->exif->size - remainder;
423 }
426 ctx->frame_complete = 0;
428 }
429
431 {
435
436 while (1) {
437 size_t remaining;
439
445 ctx->accumulated_pts = 0;
446 ctx->frame_duration = 0;
448 /* jret set by the last iteration of the loop */
449 if (
ctx->jret == JXL_DEC_NEED_MORE_INPUT && !
ctx->frame_complete) {
452 }
else if (
ctx->frame_complete) {
454 ctx->jret = JXL_DEC_SUCCESS;
455 return 0;
456 }
458 }
459 }
460
462 if (
ctx->jret == JXL_DEC_ERROR) {
463 /* this should never happen here unless there's a bug in libjxl */
466 }
467
468 ctx->jret = JxlDecoderProcessInput(
ctx->decoder);
469 /*
470 * JxlDecoderReleaseInput returns the number
471 * of bytes remaining to be read, rather than
472 * the number of bytes that it did read
473 */
474 remaining = JxlDecoderReleaseInput(
ctx->decoder);
475 size_t consumed =
pkt->
size - remaining;
478
480 case JXL_DEC_ERROR:
482 /*
483 * we consume all remaining input on error, if nothing was consumed
484 * this prevents libjxl from consuming nothing forever
485 * and just dumping the last error over and over
486 */
487 if (!consumed)
490 case JXL_DEC_NEED_MORE_INPUT:
492 continue;
493 case JXL_DEC_BASIC_INFO:
495 if (JxlDecoderGetBasicInfo(
ctx->decoder, &
ctx->basic_info) != JXL_DEC_SUCCESS) {
496 /*
497 * this should never happen
498 * if it does it is likely a libjxl decoder bug
499 */
502 }
507 }
510 if (
ctx->basic_info.have_animation)
511 ctx->anim_timebase =
av_make_q(
ctx->basic_info.animation.tps_denominator,
512 ctx->basic_info.animation.tps_numerator);
513 if (
ctx->basic_info.alpha_bits) {
514 if (
ctx->basic_info.alpha_premultiplied)
516 else
518 }
519 continue;
520 case JXL_DEC_COLOR_ENCODING:
525 continue;
526 case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
531 ctx->jxl_pixfmt.align =
ctx->frame->linesize[0];
532 if (JxlDecoderSetImageOutBuffer(
ctx->decoder, &
ctx->jxl_pixfmt,
533 ctx->frame->data[0],
ctx->frame->buf[0]->size)
534 != JXL_DEC_SUCCESS) {
537 }
538 #if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
539 if (JxlDecoderSetImageOutBitDepth(
ctx->decoder, &
ctx->jxl_bit_depth) != JXL_DEC_SUCCESS) {
542 }
543 #endif
544 continue;
545 case JXL_DEC_FRAME:
546 /* Frame here refers to the Frame bundle, not a decoded picture */
548 if (
ctx->prev_is_last) {
549 /*
550 * The last frame sent was tagged as "is_last" which
551 * means this is a new image file altogether.
552 */
555 }
556 if (JxlDecoderGetFrameHeader(
ctx->decoder, &
header) != JXL_DEC_SUCCESS) {
559 }
561 /* zero duration for animation means the frame is not presented */
562 if (
ctx->basic_info.have_animation &&
header.duration)
564 continue;
565 case JXL_DEC_FULL_IMAGE:
566 /* full image is one frame, even if animated */
568 if (
ctx->basic_info.have_animation) {
571 } else {
574 }
577 ctx->accumulated_pts +=
ctx->frame_duration;
579 if (
ctx->basic_info.have_animation && !
ctx->prev_is_last) {
581 return 0;
582 } else {
583 ctx->frame_complete = 1;
584 continue;
585 }
586 case JXL_DEC_SUCCESS:
588 /*
589 * this event will be fired when the zero-length EOF
590 * packet is sent to the decoder by the client,
591 * but it will also be fired when the next image of
592 * an image2pipe sequence is loaded up
593 */
595 JxlDecoderReset(
ctx->decoder);
597 return 0;
598 case JXL_DEC_BOX: {
602 /* last box was Exif */
603 size_t remainder = JxlDecoderReleaseBoxBuffer(
ctx->decoder);
604 ctx->exif_pos =
ctx->exif->size - remainder;
605 }
606 if (JxlDecoderGetBoxType(
ctx->decoder,
type, JXL_TRUE) != JXL_DEC_SUCCESS) {
609 }
612 continue;
613 }
617 // 4k buffer should usually be enough
621 if (JxlDecoderSetBoxBuffer(
ctx->decoder,
ctx->exif->data,
ctx->exif->size) != JXL_DEC_SUCCESS) {
624 }
625 continue;
626 }
627 case JXL_DEC_BOX_NEED_MORE_OUTPUT: {
629 size_t remainder = JxlDecoderReleaseBoxBuffer(
ctx->decoder);
630 ctx->exif_pos =
ctx->exif->size - remainder;
631 size_t new_size =
ctx->exif->size << 1;
635 if (JxlDecoderSetBoxBuffer(
ctx->decoder,
ctx->exif->data +
ctx->exif_pos,
636 ctx->exif->size -
ctx->exif_pos) != JXL_DEC_SUCCESS) {
639 }
640 continue;
641 }
642 default:
645 }
646 }
647 }
648
650 {
652
654 JxlThreadParallelRunnerDestroy(
ctx->runner);
657 JxlDecoderDestroy(
ctx->decoder);
662
663 return 0;
664 }
665
679 .p.wrapper_name = "libjxl",
680 };
681
683 .
p.
name =
"libjxl_anim",
695 .p.wrapper_name = "libjxl",
696 };