1 /*
2 * JPEG XL encoding 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 encoder using libjxl
25 */
26
27 #include <string.h>
28
38
42
43 #include <jxl/encode.h>
44 #include <jxl/thread_parallel_runner.h>
46
59
60 /**
61 * Map a quality setting for -qscale roughly from libjpeg
62 * quality numbers to libjxl's butteraugli distance for
63 * photographic content.
64 *
65 * Setting distance explicitly is preferred, but this will
66 * allow qscale to be used as a fallback.
67 *
68 * This function is continuous and injective on [0, 100] which
69 * makes it monotonic.
70 *
71 * @param quality 0.0 to 100.0 quality setting, libjpeg quality
72 * @return Butteraugli distance between 0.0 and 15.0
73 */
75 {
77 return 0.0;
79 return (100.0 -
quality) * 0.10;
81 return 0.1 + (100.0 -
quality) * 0.09;
84 else
85 return 15.0;
86 }
87
88 /**
89 * Initalize the encoder on a per-frame basis. All of these need to be set
90 * once each time the encoder is reset, which it must be each frame to make
91 * the image2 muxer work.
92 *
93 * @return 0 upon success, negative on failure.
94 */
96 {
98
99 /* reset the encoder every frame for image2 muxer */
100 JxlEncoderReset(
ctx->encoder);
101
102 ctx->options = JxlEncoderFrameSettingsCreate(
ctx->encoder,
NULL);
106 }
107
108 /* This needs to be set each time the encoder is reset */
109 if (JxlEncoderSetParallelRunner(
ctx->encoder, JxlThreadParallelRunner,
ctx->runner)
110 != JXL_ENC_SUCCESS) {
113 }
114
115 /* these shouldn't fail, libjxl bug notwithstanding */
116 if (JxlEncoderFrameSettingsSetOption(
ctx->options, JXL_ENC_FRAME_SETTING_EFFORT,
ctx->effort)
117 != JXL_ENC_SUCCESS) {
120 }
121
122 /* check for negative, our default */
123 if (
ctx->distance < 0.0) {
124 /* use ffmpeg.c -q option if passed */
127 else
128 /* default 1.0 matches cjxl */
130 }
131
132 /*
133 * 0.01 is the minimum distance accepted for lossy
134 * interpreting any positive value less than this as minimum
135 */
136 if (
ctx->distance > 0.0 &&
ctx->distance < 0.01)
137 ctx->distance = 0.01;
138 if (JxlEncoderSetFrameDistance(
ctx->options,
ctx->distance) != JXL_ENC_SUCCESS) {
141 }
142
143 /*
144 * In theory the library should automatically enable modular if necessary,
145 * but it appears it won't at the moment due to a bug. This will still
146 * work even if that is patched.
147 */
148 if (JxlEncoderFrameSettingsSetOption(
ctx->options, JXL_ENC_FRAME_SETTING_MODULAR,
149 ctx->modular ||
ctx->distance <= 0.0 ? 1 : -1) != JXL_ENC_SUCCESS) {
152 }
153
154 return 0;
155 }
156
157 /**
158 * Global encoder initialization. This only needs to be run once,
159 * not every frame.
160 */
162 {
164 JxlMemoryManager manager;
165
167 ctx->encoder = JxlEncoderCreate(&manager);
171 }
172
177 }
178
179 ctx->buffer_size = 4096;
181
185 }
186
187 return 0;
188 }
189
190 /**
191 * Populate a JxlColorEncoding with the given enum AVColorPrimaries.
192 * @return < 0 upon failure, >= 0 upon success
193 */
195 {
197
198 switch (prm) {
200 jxl_color->primaries = JXL_PRIMARIES_SRGB;
201 jxl_color->white_point = JXL_WHITE_POINT_D65;
202 return 0;
204 jxl_color->primaries = JXL_PRIMARIES_2100;
205 jxl_color->white_point = JXL_WHITE_POINT_D65;
206 return 0;
208 jxl_color->primaries = JXL_PRIMARIES_P3;
209 jxl_color->white_point = JXL_WHITE_POINT_DCI;
210 return 0;
212 jxl_color->primaries = JXL_PRIMARIES_P3;
213 jxl_color->white_point = JXL_WHITE_POINT_D65;
214 return 0;
216 av_log(avctx,
AV_LOG_WARNING,
"Unknown primaries, assuming BT.709/sRGB. Colors may be wrong.\n");
217 jxl_color->primaries = JXL_PRIMARIES_SRGB;
218 jxl_color->white_point = JXL_WHITE_POINT_D65;
219 return 0;
220 }
221
225
226 jxl_color->primaries = JXL_PRIMARIES_CUSTOM;
227 jxl_color->white_point = JXL_WHITE_POINT_CUSTOM;
228
229 jxl_color->primaries_red_xy[0] =
av_q2d(
desc->prim.r.x);
230 jxl_color->primaries_red_xy[1] =
av_q2d(
desc->prim.r.y);
231 jxl_color->primaries_green_xy[0] =
av_q2d(
desc->prim.g.x);
232 jxl_color->primaries_green_xy[1] =
av_q2d(
desc->prim.g.y);
233 jxl_color->primaries_blue_xy[0] =
av_q2d(
desc->prim.b.x);
234 jxl_color->primaries_blue_xy[1] =
av_q2d(
desc->prim.b.y);
235 jxl_color->white_point_xy[0] =
av_q2d(
desc->wp.x);
236 jxl_color->white_point_xy[1] =
av_q2d(
desc->wp.y);
237
238 return 0;
239 }
240
241 /**
242 * Encode an entire frame. Currently animation, is not supported by
243 * this encoder, so this will always reinitialize a new still image
244 * and encode a one-frame image (for image2 and image2pipe).
245 */
247 {
252 JxlColorEncoding jxl_color;
253 JxlPixelFormat jxl_fmt;
254 int bits_per_sample;
255 #if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
256 JxlBitDepth jxl_bit_depth;
257 #endif
258 JxlEncoderStatus jret;
261 size_t bytes_written = 0;
262 uint8_t *next_out =
ctx->buffer;
264
269 }
270
271 /* populate the basic info settings */
272 JxlEncoderInitBasicInfo(&
info);
276 info.num_extra_channels = (jxl_fmt.num_channels + 1) % 2;
277 info.num_color_channels = jxl_fmt.num_channels -
info.num_extra_channels;
281 info.alpha_bits = (
info.num_extra_channels > 0) *
info.bits_per_sample;
283 info.exponent_bits_per_sample =
info.bits_per_sample > 16 ? 8 : 5;
284 info.alpha_exponent_bits =
info.alpha_bits ?
info.exponent_bits_per_sample : 0;
285 jxl_fmt.data_type =
info.bits_per_sample > 16 ? JXL_TYPE_FLOAT : JXL_TYPE_FLOAT16;
286 } else {
287 info.exponent_bits_per_sample = 0;
288 info.alpha_exponent_bits = 0;
289 jxl_fmt.data_type =
info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16;
290 }
291
292 #if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
293 jxl_bit_depth.bits_per_sample = bits_per_sample;
294 jxl_bit_depth.type = JXL_BIT_DEPTH_FROM_PIXEL_FORMAT;
296 info.exponent_bits_per_sample : 0;
297 #endif
298
299 /* JPEG XL format itself does not support limited range */
302 av_log(avctx,
AV_LOG_WARNING,
"This encoder does not support limited (tv) range, colors will be wrong!\n");
305
306 /* bitexact lossless requires there to be no XYB transform */
307 info.uses_original_profile =
ctx->distance == 0.0 || !
ctx->xyb;
308 info.orientation =
frame->
linesize[0] >= 0 ? JXL_ORIENT_IDENTITY : JXL_ORIENT_FLIP_VERTICAL;
309
310 if (JxlEncoderSetBasicInfo(
ctx->encoder, &
info) != JXL_ENC_SUCCESS) {
313 }
314
315 /* rendering intent doesn't matter here
316 * but libjxl will whine if we don't set it */
317 jxl_color.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
318
322 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_709;
323 break;
325 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
326 break;
328 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
329 break;
331 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_DCI;
332 break;
334 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_PQ;
335 break;
337 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_HLG;
338 break;
340 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
341 jxl_color.gamma = 1/2.2f;
342 break;
344 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
345 jxl_color.gamma = 1/2.8f;
346 break;
347 default:
349 av_log(avctx,
AV_LOG_WARNING,
"Unknown transfer function, assuming Linear Light. Colors may be wrong.\n");
350 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
351 } else {
352 av_log(avctx,
AV_LOG_WARNING,
"Unknown transfer function, assuming IEC61966-2-1/sRGB. Colors may be wrong.\n");
353 jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
354 }
355 }
356
357 /* This should be implied to be honest
358 * but a libjxl bug makes it fail otherwise */
359 if (
info.num_color_channels == 1)
360 jxl_color.color_space = JXL_COLOR_SPACE_GRAY;
361 else
362 jxl_color.color_space = JXL_COLOR_SPACE_RGB;
363
369
371 if (sd && sd->
size && JxlEncoderSetICCProfile(
ctx->encoder, sd->
data, sd->
size) != JXL_ENC_SUCCESS)
373 if (JxlEncoderSetColorEncoding(
ctx->encoder, &jxl_color) != JXL_ENC_SUCCESS)
375
376 #if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 8, 0)
377 if (JxlEncoderSetFrameBitDepth(
ctx->options, &jxl_bit_depth) != JXL_ENC_SUCCESS)
379 #endif
380
381 /* depending on basic info, level 10 might
382 * be required instead of level 5 */
383 if (JxlEncoderGetRequiredCodestreamLevel(
ctx->encoder) > 5) {
384 if (JxlEncoderSetCodestreamLevel(
ctx->encoder, 10) != JXL_ENC_SUCCESS)
386 }
387
388 jxl_fmt.endianness = JXL_NATIVE_ENDIAN;
392 } else {
395 }
396
397 if (JxlEncoderAddImageFrame(
ctx->options, &jxl_fmt,
data, jxl_fmt.align *
info.ysize) != JXL_ENC_SUCCESS) {
400 }
401
402 /*
403 * Run this after the last frame in the image has been passed.
404 * TODO support animation
405 */
406 JxlEncoderCloseInput(
ctx->encoder);
407
408 while (1) {
409 jret = JxlEncoderProcessOutput(
ctx->encoder, &next_out, &
available);
410 if (jret == JXL_ENC_ERROR) {
413 }
415 /* all data passed has been encoded */
416 if (jret == JXL_ENC_SUCCESS)
417 break;
418 if (jret == JXL_ENC_NEED_MORE_OUTPUT) {
419 /*
420 * at the moment, libjxl has no way to
421 * tell us how much space it actually needs
422 * so we need to malloc loop
423 */
425 size_t new_size =
ctx->buffer_size * 2;
430 ctx->buffer_size = new_size;
431 next_out =
ctx->buffer + bytes_written;
433 continue;
434 }
437 }
438
442
443 memcpy(
pkt->
data,
ctx->buffer, bytes_written);
444 *got_packet = 1;
445
446 return 0;
447 }
448
450 {
452
454 JxlThreadParallelRunnerDestroy(
ctx->runner);
456
457 /*
458 * destroying the encoder also frees
459 * ctx->options so we don't need to
460 */
462 JxlEncoderDestroy(
ctx->encoder);
464
466
467 return 0;
468 }
469
470 #define OFFSET(x) offsetof(LibJxlEncodeContext, x)
471 #define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
472
475 { "distance", "Maximum Butteraugli distance (quality setting, "
478 {
"xyb",
"Use XYB-encoding for lossy images",
OFFSET(xyb),
481 };
482
488 };
489
513 },
515 .p.wrapper_name = "libjxl",
516 };