xref: /third_party/ffmpeg/libavcodec/libjxlenc.c (revision cabdff1a)
1cabdff1aSopenharmony_ci/*
2cabdff1aSopenharmony_ci * JPEG XL encoding support via libjxl
3cabdff1aSopenharmony_ci * Copyright (c) 2021 Leo Izen <leo.izen@gmail.com>
4cabdff1aSopenharmony_ci *
5cabdff1aSopenharmony_ci * This file is part of FFmpeg.
6cabdff1aSopenharmony_ci *
7cabdff1aSopenharmony_ci * FFmpeg is free software; you can redistribute it and/or
8cabdff1aSopenharmony_ci * modify it under the terms of the GNU Lesser General Public
9cabdff1aSopenharmony_ci * License as published by the Free Software Foundation; either
10cabdff1aSopenharmony_ci * version 2.1 of the License, or (at your option) any later version.
11cabdff1aSopenharmony_ci *
12cabdff1aSopenharmony_ci * FFmpeg is distributed in the hope that it will be useful,
13cabdff1aSopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of
14cabdff1aSopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15cabdff1aSopenharmony_ci * Lesser General Public License for more details.
16cabdff1aSopenharmony_ci *
17cabdff1aSopenharmony_ci * You should have received a copy of the GNU Lesser General Public
18cabdff1aSopenharmony_ci * License along with FFmpeg; if not, write to the Free Software
19cabdff1aSopenharmony_ci * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20cabdff1aSopenharmony_ci */
21cabdff1aSopenharmony_ci
22cabdff1aSopenharmony_ci/**
23cabdff1aSopenharmony_ci * @file
24cabdff1aSopenharmony_ci * JPEG XL encoder using libjxl
25cabdff1aSopenharmony_ci */
26cabdff1aSopenharmony_ci
27cabdff1aSopenharmony_ci#include <string.h>
28cabdff1aSopenharmony_ci
29cabdff1aSopenharmony_ci#include "libavutil/avutil.h"
30cabdff1aSopenharmony_ci#include "libavutil/csp.h"
31cabdff1aSopenharmony_ci#include "libavutil/error.h"
32cabdff1aSopenharmony_ci#include "libavutil/frame.h"
33cabdff1aSopenharmony_ci#include "libavutil/libm.h"
34cabdff1aSopenharmony_ci#include "libavutil/opt.h"
35cabdff1aSopenharmony_ci#include "libavutil/pixdesc.h"
36cabdff1aSopenharmony_ci#include "libavutil/pixfmt.h"
37cabdff1aSopenharmony_ci#include "libavutil/version.h"
38cabdff1aSopenharmony_ci
39cabdff1aSopenharmony_ci#include "avcodec.h"
40cabdff1aSopenharmony_ci#include "encode.h"
41cabdff1aSopenharmony_ci#include "codec_internal.h"
42cabdff1aSopenharmony_ci
43cabdff1aSopenharmony_ci#include <jxl/encode.h>
44cabdff1aSopenharmony_ci#include <jxl/thread_parallel_runner.h>
45cabdff1aSopenharmony_ci#include "libjxl.h"
46cabdff1aSopenharmony_ci
47cabdff1aSopenharmony_citypedef struct LibJxlEncodeContext {
48cabdff1aSopenharmony_ci    AVClass *class;
49cabdff1aSopenharmony_ci    void *runner;
50cabdff1aSopenharmony_ci    JxlEncoder *encoder;
51cabdff1aSopenharmony_ci    JxlEncoderFrameSettings *options;
52cabdff1aSopenharmony_ci    int effort;
53cabdff1aSopenharmony_ci    float distance;
54cabdff1aSopenharmony_ci    int modular;
55cabdff1aSopenharmony_ci    uint8_t *buffer;
56cabdff1aSopenharmony_ci    size_t buffer_size;
57cabdff1aSopenharmony_ci} LibJxlEncodeContext;
58cabdff1aSopenharmony_ci
59cabdff1aSopenharmony_ci/**
60cabdff1aSopenharmony_ci * Map a quality setting for -qscale roughly from libjpeg
61cabdff1aSopenharmony_ci * quality numbers to libjxl's butteraugli distance for
62cabdff1aSopenharmony_ci * photographic content.
63cabdff1aSopenharmony_ci *
64cabdff1aSopenharmony_ci * Setting distance explicitly is preferred, but this will
65cabdff1aSopenharmony_ci * allow qscale to be used as a fallback.
66cabdff1aSopenharmony_ci *
67cabdff1aSopenharmony_ci * This function is continuous and injective on [0, 100] which
68cabdff1aSopenharmony_ci * makes it monotonic.
69cabdff1aSopenharmony_ci *
70cabdff1aSopenharmony_ci * @param  quality 0.0 to 100.0 quality setting, libjpeg quality
71cabdff1aSopenharmony_ci * @return         Butteraugli distance between 0.0 and 15.0
72cabdff1aSopenharmony_ci */
73cabdff1aSopenharmony_cistatic float quality_to_distance(float quality)
74cabdff1aSopenharmony_ci{
75cabdff1aSopenharmony_ci    if (quality >= 100.0)
76cabdff1aSopenharmony_ci        return 0.0;
77cabdff1aSopenharmony_ci    else if (quality >= 90.0)
78cabdff1aSopenharmony_ci        return (100.0 - quality) * 0.10;
79cabdff1aSopenharmony_ci    else if (quality >= 30.0)
80cabdff1aSopenharmony_ci        return 0.1 + (100.0 - quality) * 0.09;
81cabdff1aSopenharmony_ci    else if (quality > 0.0)
82cabdff1aSopenharmony_ci        return 15.0 + (59.0 * quality - 4350.0) * quality / 9000.0;
83cabdff1aSopenharmony_ci    else
84cabdff1aSopenharmony_ci        return 15.0;
85cabdff1aSopenharmony_ci}
86cabdff1aSopenharmony_ci
87cabdff1aSopenharmony_ci/**
88cabdff1aSopenharmony_ci * Initalize the decoder on a per-frame basis. All of these need to be set
89cabdff1aSopenharmony_ci * once each time the decoder is reset, which it must be each frame to make
90cabdff1aSopenharmony_ci * the image2 muxer work.
91cabdff1aSopenharmony_ci *
92cabdff1aSopenharmony_ci * @return       0 upon success, negative on failure.
93cabdff1aSopenharmony_ci */
94cabdff1aSopenharmony_cistatic int libjxl_init_jxl_encoder(AVCodecContext *avctx)
95cabdff1aSopenharmony_ci{
96cabdff1aSopenharmony_ci    LibJxlEncodeContext *ctx = avctx->priv_data;
97cabdff1aSopenharmony_ci
98cabdff1aSopenharmony_ci    /* reset the encoder every frame for image2 muxer */
99cabdff1aSopenharmony_ci    JxlEncoderReset(ctx->encoder);
100cabdff1aSopenharmony_ci
101cabdff1aSopenharmony_ci    ctx->options = JxlEncoderFrameSettingsCreate(ctx->encoder, NULL);
102cabdff1aSopenharmony_ci    if (!ctx->options) {
103cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_ERROR, "Failed to create JxlEncoderOptions\n");
104cabdff1aSopenharmony_ci        return AVERROR_EXTERNAL;
105cabdff1aSopenharmony_ci    }
106cabdff1aSopenharmony_ci
107cabdff1aSopenharmony_ci    /* This needs to be set each time the decoder is reset */
108cabdff1aSopenharmony_ci    if (JxlEncoderSetParallelRunner(ctx->encoder, JxlThreadParallelRunner, ctx->runner)
109cabdff1aSopenharmony_ci            != JXL_ENC_SUCCESS) {
110cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_ERROR, "Failed to set JxlThreadParallelRunner\n");
111cabdff1aSopenharmony_ci        return AVERROR_EXTERNAL;
112cabdff1aSopenharmony_ci    }
113cabdff1aSopenharmony_ci
114cabdff1aSopenharmony_ci    /* these shouldn't fail, libjxl bug notwithstanding */
115cabdff1aSopenharmony_ci    if (JxlEncoderFrameSettingsSetOption(ctx->options, JXL_ENC_FRAME_SETTING_EFFORT, ctx->effort)
116cabdff1aSopenharmony_ci            != JXL_ENC_SUCCESS) {
117cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_ERROR, "Failed to set effort to: %d\n", ctx->effort);
118cabdff1aSopenharmony_ci        return AVERROR_EXTERNAL;
119cabdff1aSopenharmony_ci    }
120cabdff1aSopenharmony_ci
121cabdff1aSopenharmony_ci    /* check for negative, our default */
122cabdff1aSopenharmony_ci    if (ctx->distance < 0.0) {
123cabdff1aSopenharmony_ci        /* use ffmpeg.c -q option if passed */
124cabdff1aSopenharmony_ci        if (avctx->flags & AV_CODEC_FLAG_QSCALE)
125cabdff1aSopenharmony_ci            ctx->distance = quality_to_distance((float)avctx->global_quality / FF_QP2LAMBDA);
126cabdff1aSopenharmony_ci        else
127cabdff1aSopenharmony_ci            /* default 1.0 matches cjxl */
128cabdff1aSopenharmony_ci            ctx->distance = 1.0;
129cabdff1aSopenharmony_ci    }
130cabdff1aSopenharmony_ci
131cabdff1aSopenharmony_ci    /*
132cabdff1aSopenharmony_ci     * 0.01 is the minimum distance accepted for lossy
133cabdff1aSopenharmony_ci     * interpreting any positive value less than this as minimum
134cabdff1aSopenharmony_ci     */
135cabdff1aSopenharmony_ci    if (ctx->distance > 0.0 && ctx->distance < 0.01)
136cabdff1aSopenharmony_ci        ctx->distance = 0.01;
137cabdff1aSopenharmony_ci    if (JxlEncoderSetFrameDistance(ctx->options, ctx->distance) != JXL_ENC_SUCCESS) {
138cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_ERROR, "Failed to set distance: %f\n", ctx->distance);
139cabdff1aSopenharmony_ci        return AVERROR_EXTERNAL;
140cabdff1aSopenharmony_ci    }
141cabdff1aSopenharmony_ci
142cabdff1aSopenharmony_ci    /*
143cabdff1aSopenharmony_ci     * In theory the library should automatically enable modular if necessary,
144cabdff1aSopenharmony_ci     * but it appears it won't at the moment due to a bug. This will still
145cabdff1aSopenharmony_ci     * work even if that is patched.
146cabdff1aSopenharmony_ci     */
147cabdff1aSopenharmony_ci    if (JxlEncoderFrameSettingsSetOption(ctx->options, JXL_ENC_FRAME_SETTING_MODULAR,
148cabdff1aSopenharmony_ci            ctx->modular || ctx->distance <= 0.0 ? 1 : -1) != JXL_ENC_SUCCESS) {
149cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_ERROR, "Failed to set modular\n");
150cabdff1aSopenharmony_ci        return AVERROR_EXTERNAL;
151cabdff1aSopenharmony_ci    }
152cabdff1aSopenharmony_ci
153cabdff1aSopenharmony_ci    return 0;
154cabdff1aSopenharmony_ci}
155cabdff1aSopenharmony_ci
156cabdff1aSopenharmony_ci/**
157cabdff1aSopenharmony_ci * Global encoder initialization. This only needs to be run once,
158cabdff1aSopenharmony_ci * not every frame.
159cabdff1aSopenharmony_ci */
160cabdff1aSopenharmony_cistatic av_cold int libjxl_encode_init(AVCodecContext *avctx)
161cabdff1aSopenharmony_ci{
162cabdff1aSopenharmony_ci    LibJxlEncodeContext *ctx = avctx->priv_data;
163cabdff1aSopenharmony_ci    JxlMemoryManager manager;
164cabdff1aSopenharmony_ci
165cabdff1aSopenharmony_ci    ff_libjxl_init_memory_manager(&manager);
166cabdff1aSopenharmony_ci    ctx->encoder = JxlEncoderCreate(&manager);
167cabdff1aSopenharmony_ci    if (!ctx->encoder) {
168cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_ERROR, "Failed to create JxlEncoder\n");
169cabdff1aSopenharmony_ci        return AVERROR_EXTERNAL;
170cabdff1aSopenharmony_ci    }
171cabdff1aSopenharmony_ci
172cabdff1aSopenharmony_ci    ctx->runner = JxlThreadParallelRunnerCreate(&manager, ff_libjxl_get_threadcount(avctx->thread_count));
173cabdff1aSopenharmony_ci    if (!ctx->runner) {
174cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_ERROR, "Failed to create JxlThreadParallelRunner\n");
175cabdff1aSopenharmony_ci        return AVERROR_EXTERNAL;
176cabdff1aSopenharmony_ci    }
177cabdff1aSopenharmony_ci
178cabdff1aSopenharmony_ci    ctx->buffer_size = 4096;
179cabdff1aSopenharmony_ci    ctx->buffer = av_realloc(NULL, ctx->buffer_size);
180cabdff1aSopenharmony_ci
181cabdff1aSopenharmony_ci    if (!ctx->buffer) {
182cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_ERROR, "Could not allocate encoding buffer\n");
183cabdff1aSopenharmony_ci        return AVERROR(ENOMEM);
184cabdff1aSopenharmony_ci    }
185cabdff1aSopenharmony_ci
186cabdff1aSopenharmony_ci    return 0;
187cabdff1aSopenharmony_ci}
188cabdff1aSopenharmony_ci
189cabdff1aSopenharmony_ci/**
190cabdff1aSopenharmony_ci * Populate a JxlColorEncoding with the given enum AVColorPrimaries.
191cabdff1aSopenharmony_ci * @return < 0 upon failure, >= 0 upon success
192cabdff1aSopenharmony_ci */
193cabdff1aSopenharmony_cistatic int libjxl_populate_primaries(void *avctx, JxlColorEncoding *jxl_color, enum AVColorPrimaries prm)
194cabdff1aSopenharmony_ci{
195cabdff1aSopenharmony_ci    const AVColorPrimariesDesc *desc;
196cabdff1aSopenharmony_ci
197cabdff1aSopenharmony_ci    switch (prm) {
198cabdff1aSopenharmony_ci    case AVCOL_PRI_BT709:
199cabdff1aSopenharmony_ci        jxl_color->primaries = JXL_PRIMARIES_SRGB;
200cabdff1aSopenharmony_ci        jxl_color->white_point = JXL_WHITE_POINT_D65;
201cabdff1aSopenharmony_ci        return 0;
202cabdff1aSopenharmony_ci    case AVCOL_PRI_BT2020:
203cabdff1aSopenharmony_ci        jxl_color->primaries = JXL_PRIMARIES_2100;
204cabdff1aSopenharmony_ci        jxl_color->white_point = JXL_WHITE_POINT_D65;
205cabdff1aSopenharmony_ci        return 0;
206cabdff1aSopenharmony_ci    case AVCOL_PRI_SMPTE431:
207cabdff1aSopenharmony_ci        jxl_color->primaries = JXL_PRIMARIES_P3;
208cabdff1aSopenharmony_ci        jxl_color->white_point = JXL_WHITE_POINT_DCI;
209cabdff1aSopenharmony_ci        return 0;
210cabdff1aSopenharmony_ci    case AVCOL_PRI_SMPTE432:
211cabdff1aSopenharmony_ci        jxl_color->primaries = JXL_PRIMARIES_P3;
212cabdff1aSopenharmony_ci        jxl_color->white_point = JXL_WHITE_POINT_D65;
213cabdff1aSopenharmony_ci        return 0;
214cabdff1aSopenharmony_ci    case AVCOL_PRI_UNSPECIFIED:
215cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_WARNING, "Unknown primaries, assuming BT.709/sRGB. Colors may be wrong.\n");
216cabdff1aSopenharmony_ci        jxl_color->primaries = JXL_PRIMARIES_SRGB;
217cabdff1aSopenharmony_ci        jxl_color->white_point = JXL_WHITE_POINT_D65;
218cabdff1aSopenharmony_ci        return 0;
219cabdff1aSopenharmony_ci    }
220cabdff1aSopenharmony_ci
221cabdff1aSopenharmony_ci    desc = av_csp_primaries_desc_from_id(prm);
222cabdff1aSopenharmony_ci    if (!desc)
223cabdff1aSopenharmony_ci        return AVERROR(EINVAL);
224cabdff1aSopenharmony_ci
225cabdff1aSopenharmony_ci    jxl_color->primaries = JXL_PRIMARIES_CUSTOM;
226cabdff1aSopenharmony_ci    jxl_color->white_point = JXL_WHITE_POINT_CUSTOM;
227cabdff1aSopenharmony_ci
228cabdff1aSopenharmony_ci    jxl_color->primaries_red_xy[0] = av_q2d(desc->prim.r.x);
229cabdff1aSopenharmony_ci    jxl_color->primaries_red_xy[1] = av_q2d(desc->prim.r.y);
230cabdff1aSopenharmony_ci    jxl_color->primaries_green_xy[0] = av_q2d(desc->prim.g.x);
231cabdff1aSopenharmony_ci    jxl_color->primaries_green_xy[1] = av_q2d(desc->prim.g.y);
232cabdff1aSopenharmony_ci    jxl_color->primaries_blue_xy[0] = av_q2d(desc->prim.b.x);
233cabdff1aSopenharmony_ci    jxl_color->primaries_blue_xy[1] = av_q2d(desc->prim.b.y);
234cabdff1aSopenharmony_ci    jxl_color->white_point_xy[0] = av_q2d(desc->wp.x);
235cabdff1aSopenharmony_ci    jxl_color->white_point_xy[1] = av_q2d(desc->wp.y);
236cabdff1aSopenharmony_ci
237cabdff1aSopenharmony_ci    return 0;
238cabdff1aSopenharmony_ci}
239cabdff1aSopenharmony_ci
240cabdff1aSopenharmony_ci/**
241cabdff1aSopenharmony_ci * Encode an entire frame. Currently animation, is not supported by
242cabdff1aSopenharmony_ci * this encoder, so this will always reinitialize a new still image
243cabdff1aSopenharmony_ci * and encode a one-frame image (for image2 and image2pipe).
244cabdff1aSopenharmony_ci */
245cabdff1aSopenharmony_cistatic int libjxl_encode_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame, int *got_packet)
246cabdff1aSopenharmony_ci{
247cabdff1aSopenharmony_ci    LibJxlEncodeContext *ctx = avctx->priv_data;
248cabdff1aSopenharmony_ci    AVFrameSideData *sd;
249cabdff1aSopenharmony_ci    const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(frame->format);
250cabdff1aSopenharmony_ci    JxlBasicInfo info;
251cabdff1aSopenharmony_ci    JxlColorEncoding jxl_color;
252cabdff1aSopenharmony_ci    JxlPixelFormat jxl_fmt;
253cabdff1aSopenharmony_ci    JxlEncoderStatus jret;
254cabdff1aSopenharmony_ci    int ret;
255cabdff1aSopenharmony_ci    size_t available = ctx->buffer_size;
256cabdff1aSopenharmony_ci    size_t bytes_written = 0;
257cabdff1aSopenharmony_ci    uint8_t *next_out = ctx->buffer;
258cabdff1aSopenharmony_ci
259cabdff1aSopenharmony_ci    ret = libjxl_init_jxl_encoder(avctx);
260cabdff1aSopenharmony_ci    if (ret) {
261cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_ERROR, "Error frame-initializing JxlEncoder\n");
262cabdff1aSopenharmony_ci        return ret;
263cabdff1aSopenharmony_ci    }
264cabdff1aSopenharmony_ci
265cabdff1aSopenharmony_ci    /* populate the basic info settings */
266cabdff1aSopenharmony_ci    JxlEncoderInitBasicInfo(&info);
267cabdff1aSopenharmony_ci    jxl_fmt.num_channels = pix_desc->nb_components;
268cabdff1aSopenharmony_ci    info.xsize = frame->width;
269cabdff1aSopenharmony_ci    info.ysize = frame->height;
270cabdff1aSopenharmony_ci    info.num_extra_channels = (jxl_fmt.num_channels + 1) % 2;
271cabdff1aSopenharmony_ci    info.num_color_channels = jxl_fmt.num_channels - info.num_extra_channels;
272cabdff1aSopenharmony_ci    info.bits_per_sample = av_get_bits_per_pixel(pix_desc) / jxl_fmt.num_channels;
273cabdff1aSopenharmony_ci    info.alpha_bits = (info.num_extra_channels > 0) * info.bits_per_sample;
274cabdff1aSopenharmony_ci    if (pix_desc->flags & AV_PIX_FMT_FLAG_FLOAT) {
275cabdff1aSopenharmony_ci        info.exponent_bits_per_sample = info.bits_per_sample > 16 ? 8 : 5;
276cabdff1aSopenharmony_ci        info.alpha_exponent_bits = info.alpha_bits ? info.exponent_bits_per_sample : 0;
277cabdff1aSopenharmony_ci        jxl_fmt.data_type = info.bits_per_sample > 16 ? JXL_TYPE_FLOAT : JXL_TYPE_FLOAT16;
278cabdff1aSopenharmony_ci    } else {
279cabdff1aSopenharmony_ci        info.exponent_bits_per_sample = 0;
280cabdff1aSopenharmony_ci        info.alpha_exponent_bits = 0;
281cabdff1aSopenharmony_ci        jxl_fmt.data_type = info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16;
282cabdff1aSopenharmony_ci    }
283cabdff1aSopenharmony_ci
284cabdff1aSopenharmony_ci    /* JPEG XL format itself does not support limited range */
285cabdff1aSopenharmony_ci    if (avctx->color_range == AVCOL_RANGE_MPEG ||
286cabdff1aSopenharmony_ci        avctx->color_range == AVCOL_RANGE_UNSPECIFIED && frame->color_range == AVCOL_RANGE_MPEG)
287cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_WARNING, "This encoder does not support limited (tv) range, colors will be wrong!\n");
288cabdff1aSopenharmony_ci    else if (avctx->color_range != AVCOL_RANGE_JPEG && frame->color_range != AVCOL_RANGE_JPEG)
289cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_WARNING, "Unknown color range, assuming full (pc)\n");
290cabdff1aSopenharmony_ci
291cabdff1aSopenharmony_ci    /* bitexact lossless requires there to be no XYB transform */
292cabdff1aSopenharmony_ci    info.uses_original_profile = ctx->distance == 0.0;
293cabdff1aSopenharmony_ci
294cabdff1aSopenharmony_ci    if (JxlEncoderSetBasicInfo(ctx->encoder, &info) != JXL_ENC_SUCCESS) {
295cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_ERROR, "Failed to set JxlBasicInfo\n");
296cabdff1aSopenharmony_ci        return AVERROR_EXTERNAL;
297cabdff1aSopenharmony_ci    }
298cabdff1aSopenharmony_ci
299cabdff1aSopenharmony_ci    /* rendering intent doesn't matter here
300cabdff1aSopenharmony_ci     * but libjxl will whine if we don't set it */
301cabdff1aSopenharmony_ci    jxl_color.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
302cabdff1aSopenharmony_ci
303cabdff1aSopenharmony_ci    switch (frame->color_trc && frame->color_trc != AVCOL_TRC_UNSPECIFIED
304cabdff1aSopenharmony_ci            ? frame->color_trc : avctx->color_trc) {
305cabdff1aSopenharmony_ci    case AVCOL_TRC_BT709:
306cabdff1aSopenharmony_ci        jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_709;
307cabdff1aSopenharmony_ci        break;
308cabdff1aSopenharmony_ci    case AVCOL_TRC_LINEAR:
309cabdff1aSopenharmony_ci        jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
310cabdff1aSopenharmony_ci        break;
311cabdff1aSopenharmony_ci    case AVCOL_TRC_IEC61966_2_1:
312cabdff1aSopenharmony_ci        jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
313cabdff1aSopenharmony_ci        break;
314cabdff1aSopenharmony_ci    case AVCOL_TRC_SMPTE428:
315cabdff1aSopenharmony_ci        jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_DCI;
316cabdff1aSopenharmony_ci        break;
317cabdff1aSopenharmony_ci    case AVCOL_TRC_SMPTE2084:
318cabdff1aSopenharmony_ci        jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_PQ;
319cabdff1aSopenharmony_ci        break;
320cabdff1aSopenharmony_ci    case AVCOL_TRC_ARIB_STD_B67:
321cabdff1aSopenharmony_ci        jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_HLG;
322cabdff1aSopenharmony_ci        break;
323cabdff1aSopenharmony_ci    case AVCOL_TRC_GAMMA22:
324cabdff1aSopenharmony_ci        jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
325cabdff1aSopenharmony_ci        jxl_color.gamma = 2.2;
326cabdff1aSopenharmony_ci        break;
327cabdff1aSopenharmony_ci    case AVCOL_TRC_GAMMA28:
328cabdff1aSopenharmony_ci        jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
329cabdff1aSopenharmony_ci        jxl_color.gamma = 2.8;
330cabdff1aSopenharmony_ci        break;
331cabdff1aSopenharmony_ci    default:
332cabdff1aSopenharmony_ci        if (pix_desc->flags & AV_PIX_FMT_FLAG_FLOAT) {
333cabdff1aSopenharmony_ci            av_log(avctx, AV_LOG_WARNING, "Unknown transfer function, assuming Linear Light. Colors may be wrong.\n");
334cabdff1aSopenharmony_ci            jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
335cabdff1aSopenharmony_ci        } else {
336cabdff1aSopenharmony_ci            av_log(avctx, AV_LOG_WARNING, "Unknown transfer function, assuming IEC61966-2-1/sRGB. Colors may be wrong.\n");
337cabdff1aSopenharmony_ci            jxl_color.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
338cabdff1aSopenharmony_ci        }
339cabdff1aSopenharmony_ci    }
340cabdff1aSopenharmony_ci
341cabdff1aSopenharmony_ci    /* This should be implied to be honest
342cabdff1aSopenharmony_ci     * but a libjxl bug makes it fail otherwise */
343cabdff1aSopenharmony_ci    if (info.num_color_channels == 1)
344cabdff1aSopenharmony_ci        jxl_color.color_space = JXL_COLOR_SPACE_GRAY;
345cabdff1aSopenharmony_ci    else
346cabdff1aSopenharmony_ci        jxl_color.color_space = JXL_COLOR_SPACE_RGB;
347cabdff1aSopenharmony_ci
348cabdff1aSopenharmony_ci    ret = libjxl_populate_primaries(avctx, &jxl_color,
349cabdff1aSopenharmony_ci            frame->color_primaries && frame->color_primaries != AVCOL_PRI_UNSPECIFIED
350cabdff1aSopenharmony_ci            ? frame->color_primaries : avctx->color_primaries);
351cabdff1aSopenharmony_ci    if (ret < 0)
352cabdff1aSopenharmony_ci        return ret;
353cabdff1aSopenharmony_ci
354cabdff1aSopenharmony_ci    sd = av_frame_get_side_data(frame, AV_FRAME_DATA_ICC_PROFILE);
355cabdff1aSopenharmony_ci    if (sd && sd->size && JxlEncoderSetICCProfile(ctx->encoder, sd->data, sd->size) != JXL_ENC_SUCCESS)
356cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_WARNING, "Could not set ICC Profile\n");
357cabdff1aSopenharmony_ci    if (JxlEncoderSetColorEncoding(ctx->encoder, &jxl_color) != JXL_ENC_SUCCESS)
358cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_WARNING, "Failed to set JxlColorEncoding\n");
359cabdff1aSopenharmony_ci
360cabdff1aSopenharmony_ci    /* depending on basic info, level 10 might
361cabdff1aSopenharmony_ci     * be required instead of level 5 */
362cabdff1aSopenharmony_ci    if (JxlEncoderGetRequiredCodestreamLevel(ctx->encoder) > 5) {
363cabdff1aSopenharmony_ci        if (JxlEncoderSetCodestreamLevel(ctx->encoder, 10) != JXL_ENC_SUCCESS)
364cabdff1aSopenharmony_ci            av_log(avctx, AV_LOG_WARNING, "Could not increase codestream level\n");
365cabdff1aSopenharmony_ci    }
366cabdff1aSopenharmony_ci
367cabdff1aSopenharmony_ci    jxl_fmt.endianness = JXL_NATIVE_ENDIAN;
368cabdff1aSopenharmony_ci    jxl_fmt.align = frame->linesize[0];
369cabdff1aSopenharmony_ci
370cabdff1aSopenharmony_ci    if (JxlEncoderAddImageFrame(ctx->options, &jxl_fmt, frame->data[0], jxl_fmt.align * info.ysize) != JXL_ENC_SUCCESS) {
371cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_ERROR, "Failed to add Image Frame\n");
372cabdff1aSopenharmony_ci        return AVERROR_EXTERNAL;
373cabdff1aSopenharmony_ci    }
374cabdff1aSopenharmony_ci
375cabdff1aSopenharmony_ci    /*
376cabdff1aSopenharmony_ci     * Run this after the last frame in the image has been passed.
377cabdff1aSopenharmony_ci     * TODO support animation
378cabdff1aSopenharmony_ci     */
379cabdff1aSopenharmony_ci    JxlEncoderCloseInput(ctx->encoder);
380cabdff1aSopenharmony_ci
381cabdff1aSopenharmony_ci    while (1) {
382cabdff1aSopenharmony_ci        jret = JxlEncoderProcessOutput(ctx->encoder, &next_out, &available);
383cabdff1aSopenharmony_ci        if (jret == JXL_ENC_ERROR) {
384cabdff1aSopenharmony_ci            av_log(avctx, AV_LOG_ERROR, "Unspecified libjxl error occurred\n");
385cabdff1aSopenharmony_ci            return AVERROR_EXTERNAL;
386cabdff1aSopenharmony_ci        }
387cabdff1aSopenharmony_ci        bytes_written = ctx->buffer_size - available;
388cabdff1aSopenharmony_ci        /* all data passed has been encoded */
389cabdff1aSopenharmony_ci        if (jret == JXL_ENC_SUCCESS)
390cabdff1aSopenharmony_ci            break;
391cabdff1aSopenharmony_ci        if (jret == JXL_ENC_NEED_MORE_OUTPUT) {
392cabdff1aSopenharmony_ci            /*
393cabdff1aSopenharmony_ci             * at the moment, libjxl has no way to
394cabdff1aSopenharmony_ci             * tell us how much space it actually needs
395cabdff1aSopenharmony_ci             * so we need to malloc loop
396cabdff1aSopenharmony_ci             */
397cabdff1aSopenharmony_ci            uint8_t *temp;
398cabdff1aSopenharmony_ci            size_t new_size = ctx->buffer_size * 2;
399cabdff1aSopenharmony_ci            temp = av_realloc(ctx->buffer, new_size);
400cabdff1aSopenharmony_ci            if (!temp)
401cabdff1aSopenharmony_ci                return AVERROR(ENOMEM);
402cabdff1aSopenharmony_ci            ctx->buffer = temp;
403cabdff1aSopenharmony_ci            ctx->buffer_size = new_size;
404cabdff1aSopenharmony_ci            next_out = ctx->buffer + bytes_written;
405cabdff1aSopenharmony_ci            available = new_size - bytes_written;
406cabdff1aSopenharmony_ci            continue;
407cabdff1aSopenharmony_ci        }
408cabdff1aSopenharmony_ci        av_log(avctx, AV_LOG_ERROR, "Bad libjxl event: %d\n", jret);
409cabdff1aSopenharmony_ci        return AVERROR_EXTERNAL;
410cabdff1aSopenharmony_ci    }
411cabdff1aSopenharmony_ci
412cabdff1aSopenharmony_ci    ret = ff_get_encode_buffer(avctx, pkt, bytes_written, 0);
413cabdff1aSopenharmony_ci    if (ret < 0)
414cabdff1aSopenharmony_ci        return ret;
415cabdff1aSopenharmony_ci
416cabdff1aSopenharmony_ci    memcpy(pkt->data, ctx->buffer, bytes_written);
417cabdff1aSopenharmony_ci    *got_packet = 1;
418cabdff1aSopenharmony_ci
419cabdff1aSopenharmony_ci    return 0;
420cabdff1aSopenharmony_ci}
421cabdff1aSopenharmony_ci
422cabdff1aSopenharmony_cistatic av_cold int libjxl_encode_close(AVCodecContext *avctx)
423cabdff1aSopenharmony_ci{
424cabdff1aSopenharmony_ci    LibJxlEncodeContext *ctx = avctx->priv_data;
425cabdff1aSopenharmony_ci
426cabdff1aSopenharmony_ci    if (ctx->runner)
427cabdff1aSopenharmony_ci        JxlThreadParallelRunnerDestroy(ctx->runner);
428cabdff1aSopenharmony_ci    ctx->runner = NULL;
429cabdff1aSopenharmony_ci
430cabdff1aSopenharmony_ci    /*
431cabdff1aSopenharmony_ci     * destroying the decoder also frees
432cabdff1aSopenharmony_ci     * ctx->options so we don't need to
433cabdff1aSopenharmony_ci     */
434cabdff1aSopenharmony_ci    if (ctx->encoder)
435cabdff1aSopenharmony_ci        JxlEncoderDestroy(ctx->encoder);
436cabdff1aSopenharmony_ci    ctx->encoder = NULL;
437cabdff1aSopenharmony_ci
438cabdff1aSopenharmony_ci    av_freep(&ctx->buffer);
439cabdff1aSopenharmony_ci
440cabdff1aSopenharmony_ci    return 0;
441cabdff1aSopenharmony_ci}
442cabdff1aSopenharmony_ci
443cabdff1aSopenharmony_ci#define OFFSET(x) offsetof(LibJxlEncodeContext, x)
444cabdff1aSopenharmony_ci#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
445cabdff1aSopenharmony_ci
446cabdff1aSopenharmony_cistatic const AVOption libjxl_encode_options[] = {
447cabdff1aSopenharmony_ci    { "effort",        "Encoding effort",                                  OFFSET(effort),     AV_OPT_TYPE_INT,    { .i64 =    7 },    1,     9, VE },
448cabdff1aSopenharmony_ci    { "distance",      "Maximum Butteraugli distance (quality setting, "
449cabdff1aSopenharmony_ci                        "lower = better, zero = lossless, default 1.0)",   OFFSET(distance),   AV_OPT_TYPE_FLOAT,  { .dbl = -1.0 }, -1.0,  15.0, VE },
450cabdff1aSopenharmony_ci    { "modular",       "Force modular mode",                               OFFSET(modular),    AV_OPT_TYPE_INT,    { .i64 =    0 },    0,     1, VE },
451cabdff1aSopenharmony_ci    { NULL },
452cabdff1aSopenharmony_ci};
453cabdff1aSopenharmony_ci
454cabdff1aSopenharmony_cistatic const AVClass libjxl_encode_class = {
455cabdff1aSopenharmony_ci    .class_name = "libjxl",
456cabdff1aSopenharmony_ci    .item_name  = av_default_item_name,
457cabdff1aSopenharmony_ci    .option     = libjxl_encode_options,
458cabdff1aSopenharmony_ci    .version    = LIBAVUTIL_VERSION_INT,
459cabdff1aSopenharmony_ci};
460cabdff1aSopenharmony_ci
461cabdff1aSopenharmony_ciconst FFCodec ff_libjxl_encoder = {
462cabdff1aSopenharmony_ci    .p.name           = "libjxl",
463cabdff1aSopenharmony_ci    .p.long_name      = NULL_IF_CONFIG_SMALL("libjxl JPEG XL"),
464cabdff1aSopenharmony_ci    .p.type           = AVMEDIA_TYPE_VIDEO,
465cabdff1aSopenharmony_ci    .p.id             = AV_CODEC_ID_JPEGXL,
466cabdff1aSopenharmony_ci    .priv_data_size   = sizeof(LibJxlEncodeContext),
467cabdff1aSopenharmony_ci    .init             = libjxl_encode_init,
468cabdff1aSopenharmony_ci    FF_CODEC_ENCODE_CB(libjxl_encode_frame),
469cabdff1aSopenharmony_ci    .close            = libjxl_encode_close,
470cabdff1aSopenharmony_ci    .p.capabilities   = AV_CODEC_CAP_OTHER_THREADS,
471cabdff1aSopenharmony_ci    .caps_internal    = FF_CODEC_CAP_AUTO_THREADS | FF_CODEC_CAP_INIT_CLEANUP,
472cabdff1aSopenharmony_ci    .p.pix_fmts       = (const enum AVPixelFormat[]) {
473cabdff1aSopenharmony_ci        AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA,
474cabdff1aSopenharmony_ci        AV_PIX_FMT_RGB48, AV_PIX_FMT_RGBA64,
475cabdff1aSopenharmony_ci        AV_PIX_FMT_GRAY8, AV_PIX_FMT_YA8,
476cabdff1aSopenharmony_ci        AV_PIX_FMT_GRAY16, AV_PIX_FMT_YA16,
477cabdff1aSopenharmony_ci        AV_PIX_FMT_GRAYF32,
478cabdff1aSopenharmony_ci        AV_PIX_FMT_NONE
479cabdff1aSopenharmony_ci    },
480cabdff1aSopenharmony_ci    .p.priv_class     = &libjxl_encode_class,
481cabdff1aSopenharmony_ci    .p.wrapper_name   = "libjxl",
482cabdff1aSopenharmony_ci};
483