/** * JPEG XL parser * Copyright (c) 2023 Leo Izen * * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * FFmpeg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include "libavutil/attributes.h" #include "libavutil/error.h" #include "libavutil/intmath.h" #include "libavutil/macros.h" #include "libavutil/mem.h" #include "libavutil/pixfmt.h" #include "bytestream.h" #include "codec_id.h" #define UNCHECKED_BITSTREAM_READER 0 #define BITSTREAM_READER_LE #include "get_bits.h" #include "jpegxl.h" #include "jpegxl_parse.h" #include "parser.h" #include "vlc.h" #define JXL_FLAG_NOISE 1 #define JXL_FLAG_PATCHES 2 #define JXL_FLAG_SPLINES 16 #define JXL_FLAG_USE_LF_FRAME 32 #define JXL_FLAG_SKIP_ADAPTIVE_LF_SMOOTH 128 #define clog1p(x) (ff_log2(x) + !!(x)) #define unpack_signed(x) (((x) & 1 ? -(x)-1 : (x))/2) #define div_ceil(x, y) (((x) - 1) / (y) + 1) #define vlm(a,b) {.sym = (a), .len = (b)} typedef struct JXLHybridUintConf { int split_exponent; uint32_t msb_in_token; uint32_t lsb_in_token; } JXLHybridUintConf; typedef struct JXLSymbolDistribution { JXLHybridUintConf config; int log_bucket_size; /* this is the actual size of the alphabet */ int alphabet_size; /* ceil(log(alphabet_size)) */ int log_alphabet_size; /* for prefix code distributions */ VLC vlc; /* in case bits == 0 */ uint32_t default_symbol; /* * each (1 << log_alphabet_size) length * with log_alphabet_size <= 8 */ /* frequencies associated with this Distribution */ uint32_t freq[258]; /* cutoffs for using the symbol table */ uint16_t cutoffs[258]; /* the symbol table for this distribution */ uint16_t symbols[258]; /* the offset for symbols */ uint16_t offsets[258]; /* if this distribution contains only one symbol this is its index */ int uniq_pos; } JXLSymbolDistribution; typedef struct JXLDistributionBundle { /* lz77 flags */ int lz77_enabled; uint32_t lz77_min_symbol; uint32_t lz77_min_length; JXLHybridUintConf lz_len_conf; /* one entry for each distribution */ uint8_t *cluster_map; /* length of cluster_map */ int num_dist; /* one for each cluster */ JXLSymbolDistribution *dists; int num_clusters; /* whether to use brotli prefixes or ans */ int use_prefix_code; /* bundle log alphabet size, dist ones may be smaller */ int log_alphabet_size; } JXLDistributionBundle; typedef struct JXLEntropyDecoder { /* state is a positive 32-bit integer, or -1 if unset */ int64_t state; /* lz77 values */ uint32_t num_to_copy; uint32_t copy_pos; uint32_t num_decoded; /* length is (1 << 20) */ /* if lz77 is enabled for this bundle */ /* if lz77 is disabled it's NULL */ uint32_t *window; /* primary bundle associated with this distribution */ JXLDistributionBundle bundle; /* for av_log */ void *logctx; } JXLEntropyDecoder; typedef struct JXLFrame { FFJXLFrameType type; FFJXLFrameEncoding encoding; int is_last; int full_frame; uint32_t total_length; uint32_t body_length; } JXLFrame; typedef struct JXLCodestream { FFJXLMetadata meta; JXLFrame frame; } JXLCodestream; typedef struct JXLParseContext { ParseContext pc; JXLCodestream codestream; /* using ISOBMFF-based container */ int container; int skip; int copied; int collected_size; int codestream_length; int skipped_icc; int next; uint8_t cs_buffer[4096]; } JXLParseContext; /* used for reading brotli prefixes */ static const VLCElem level0_table[16] = { vlm(0, 2), vlm(4, 2), vlm(3, 2), vlm(2, 3), vlm(0, 2), vlm(4, 2), vlm(3, 2), vlm(1, 4), vlm(0, 2), vlm(4, 2), vlm(3, 2), vlm(2, 3), vlm(0, 2), vlm(4, 2), vlm(3, 2), vlm(5, 4), }; /* prefix table for populating ANS distribution */ static const VLCElem dist_prefix_table[128] = { vlm(10, 3), vlm(12, 7), vlm(7, 3), vlm(3, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(5, 4), vlm(10, 3), vlm(4, 4), vlm(7, 3), vlm(1, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(2, 4), vlm(10, 3), vlm(0, 5), vlm(7, 3), vlm(3, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(5, 4), vlm(10, 3), vlm(4, 4), vlm(7, 3), vlm(1, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(2, 4), vlm(10, 3), vlm(11, 6), vlm(7, 3), vlm(3, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(5, 4), vlm(10, 3), vlm(4, 4), vlm(7, 3), vlm(1, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(2, 4), vlm(10, 3), vlm(0, 5), vlm(7, 3), vlm(3, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(5, 4), vlm(10, 3), vlm(4, 4), vlm(7, 3), vlm(1, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(2, 4), vlm(10, 3), vlm(13, 7), vlm(7, 3), vlm(3, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(5, 4), vlm(10, 3), vlm(4, 4), vlm(7, 3), vlm(1, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(2, 4), vlm(10, 3), vlm(0, 5), vlm(7, 3), vlm(3, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(5, 4), vlm(10, 3), vlm(4, 4), vlm(7, 3), vlm(1, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(2, 4), vlm(10, 3), vlm(11, 6), vlm(7, 3), vlm(3, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(5, 4), vlm(10, 3), vlm(4, 4), vlm(7, 3), vlm(1, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(2, 4), vlm(10, 3), vlm(0, 5), vlm(7, 3), vlm(3, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(5, 4), vlm(10, 3), vlm(4, 4), vlm(7, 3), vlm(1, 4), vlm(6, 3), vlm(8, 3), vlm(9, 3), vlm(2, 4), }; static const uint8_t prefix_codelen_map[18] = { 1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15, }; /** * Read a variable-length 8-bit integer. * Used when populating the ANS frequency tables. */ static av_always_inline uint8_t jxl_u8(GetBitContext *gb) { int n; if (!get_bits1(gb)) return 0; n = get_bits(gb, 3); return get_bitsz(gb, n) | (1 << n); } /* read a U32(c_i + u(u_i)) */ static av_always_inline uint32_t jxl_u32(GetBitContext *gb, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3, uint32_t u0, uint32_t u1, uint32_t u2, uint32_t u3) { const uint32_t constants[4] = {c0, c1, c2, c3}; const uint32_t ubits [4] = {u0, u1, u2, u3}; uint32_t ret, choice = get_bits(gb, 2); ret = constants[choice]; if (ubits[choice]) ret += get_bits_long(gb, ubits[choice]); return ret; } static av_always_inline uint32_t jxl_enum(GetBitContext *gb) { return jxl_u32(gb, 0, 1, 2, 18, 0, 0, 4, 6); } /* read a U64() */ static uint64_t jxl_u64(GetBitContext *gb) { uint64_t shift = 12, ret; switch (get_bits(gb, 2)) { case 1: ret = 1 + get_bits(gb, 4); break; case 2: ret = 17 + get_bits(gb, 8); break; case 3: ret = get_bits(gb, 12); while (get_bits1(gb)) { if (shift < 60) { ret |= (uint64_t)get_bits(gb, 8) << shift; shift += 8; } else { ret |= (uint64_t)get_bits(gb, 4) << shift; break; } } break; default: ret = 0; } return ret; } static int read_hybrid_uint_conf(GetBitContext *gb, JXLHybridUintConf *conf, int log_alphabet_size) { conf->split_exponent = get_bitsz(gb, clog1p(log_alphabet_size)); if (conf->split_exponent == log_alphabet_size) { conf->msb_in_token = conf->lsb_in_token = 0; return 0; } conf->msb_in_token = get_bitsz(gb, clog1p(conf->split_exponent)); if (conf->msb_in_token > conf->split_exponent) return AVERROR_INVALIDDATA; conf->lsb_in_token = get_bitsz(gb, clog1p(conf->split_exponent - conf->msb_in_token)); if (conf->msb_in_token + conf->lsb_in_token > conf->split_exponent) return AVERROR_INVALIDDATA; return 0; } static int read_hybrid_uint(GetBitContext *gb, const JXLHybridUintConf *conf, uint32_t token, uint32_t *hybrid_uint) { uint32_t n, low, split = 1 << conf->split_exponent; if (token < split) { *hybrid_uint = token; return 0; } n = conf->split_exponent - conf->lsb_in_token - conf->msb_in_token + ((token - split) >> (conf->msb_in_token + conf->lsb_in_token)); if (n >= 32) return AVERROR_INVALIDDATA; low = token & ((1 << conf->lsb_in_token) - 1); token >>= conf->lsb_in_token; token &= (1 << conf->msb_in_token) - 1; token |= 1 << conf->msb_in_token; *hybrid_uint = (((token << n) | get_bits_long(gb, n)) << conf->lsb_in_token ) | low; return 0; } static inline uint32_t read_prefix_symbol(GetBitContext *gb, const JXLSymbolDistribution *dist) { if (!dist->vlc.bits) return dist->default_symbol; return get_vlc2(gb, dist->vlc.table, dist->vlc.bits, 1); } static uint32_t read_ans_symbol(GetBitContext *gb, JXLEntropyDecoder *dec, const JXLSymbolDistribution *dist) { uint32_t index, i, pos, symbol, offset; if (dec->state < 0) dec->state = get_bits_long(gb, 32); index = dec->state & 0xFFF; i = index >> dist->log_bucket_size; pos = index & ((1 << dist->log_bucket_size) - 1); symbol = pos >= dist->cutoffs[i] ? dist->symbols[i] : i; offset = pos >= dist->cutoffs[i] ? dist->offsets[i] + pos : pos; dec->state = dist->freq[symbol] * (dec->state >> 12) + offset; if (dec->state < (1 << 16)) dec->state = (dec->state << 16) | get_bits(gb, 16); dec->state &= 0xFFFFFFFF; return symbol; } static int decode_hybrid_varlen_uint(GetBitContext *gb, JXLEntropyDecoder *dec, const JXLDistributionBundle *bundle, uint32_t context, uint32_t *hybrid_uint) { int ret; uint32_t token, distance; const JXLSymbolDistribution *dist; if (dec->num_to_copy > 0) { *hybrid_uint = dec->window[dec->copy_pos++ & 0xFFFFF]; dec->num_to_copy--; dec->window[dec->num_decoded++ & 0xFFFFF] = *hybrid_uint; return 0; } if (context >= bundle->num_dist) return AVERROR(EINVAL); if (bundle->cluster_map[context] >= bundle->num_clusters) return AVERROR_INVALIDDATA; dist = &bundle->dists[bundle->cluster_map[context]]; if (bundle->use_prefix_code) token = read_prefix_symbol(gb, dist); else token = read_ans_symbol(gb, dec, dist); if (bundle->lz77_enabled && token >= bundle->lz77_min_symbol) { const JXLSymbolDistribution *lz77dist = &bundle->dists[bundle->cluster_map[bundle->num_dist - 1]]; ret = read_hybrid_uint(gb, &bundle->lz_len_conf, token - bundle->lz77_min_symbol, &dec->num_to_copy); if (ret < 0) return ret; dec->num_to_copy += bundle->lz77_min_length; if (bundle->use_prefix_code) token = read_prefix_symbol(gb, lz77dist); else token = read_ans_symbol(gb, dec, lz77dist); ret = read_hybrid_uint(gb, &lz77dist->config, token, &distance); if (ret < 0) return ret; distance++; distance = FFMIN3(distance, dec->num_decoded, 1 << 20); dec->copy_pos = dec->num_decoded - distance; return decode_hybrid_varlen_uint(gb, dec, bundle, context, hybrid_uint); } ret = read_hybrid_uint(gb, &dist->config, token, hybrid_uint); if (ret < 0) return ret; if (bundle->lz77_enabled) dec->window[dec->num_decoded++ & 0xFFFFF] = *hybrid_uint; return 0; } static int populate_distribution(GetBitContext *gb, JXLSymbolDistribution *dist, int log_alphabet_size) { int len = 0, shift, omit_log = -1, omit_pos = -1; int prev = 0, num_same = 0; uint32_t total_count = 0; uint8_t logcounts[258] = { 0 }; uint8_t same[258] = { 0 }; dist->uniq_pos = -1; if (get_bits1(gb)) { /* simple code */ dist->alphabet_size = 256; if (get_bits1(gb)) { uint8_t v1 = jxl_u8(gb); uint8_t v2 = jxl_u8(gb); if (v1 == v2) return AVERROR_INVALIDDATA; dist->freq[v1] = get_bits(gb, 12); dist->freq[v2] = (1 << 12) - dist->freq[v1]; if (!dist->freq[v1]) dist->uniq_pos = v2; } else { uint8_t x = jxl_u8(gb); dist->freq[x] = 1 << 12; dist->uniq_pos = x; } return 0; } if (get_bits1(gb)) { /* flat code */ dist->alphabet_size = jxl_u8(gb) + 1; for (int i = 0; i < dist->alphabet_size; i++) dist->freq[i] = (1 << 12) / dist->alphabet_size; for (int i = 0; i < (1 << 12) % dist->alphabet_size; i++) dist->freq[i]++; return 0; } do { if (!get_bits1(gb)) break; } while (++len < 3); shift = (get_bitsz(gb, len) | (1 << len)) - 1; if (shift > 13) return AVERROR_INVALIDDATA; dist->alphabet_size = jxl_u8(gb) + 3; for (int i = 0; i < dist->alphabet_size; i++) { logcounts[i] = get_vlc2(gb, dist_prefix_table, 7, 1); if (logcounts[i] == 13) { int rle = jxl_u8(gb); same[i] = rle + 5; i += rle + 3; continue; } if (logcounts[i] > omit_log) { omit_log = logcounts[i]; omit_pos = i; } } if (omit_pos < 0 || omit_pos + 1 < dist->alphabet_size && logcounts[omit_pos + 1] == 13) return AVERROR_INVALIDDATA; for (int i = 0; i < dist->alphabet_size; i++) { if (same[i]) { num_same = same[i] - 1; prev = i > 0 ? dist->freq[i - 1] : 0; } if (num_same) { dist->freq[i] = prev; num_same--; } else { if (i == omit_pos || !logcounts[i]) continue; if (logcounts[i] == 1) { dist->freq[i] = 1; } else { int bitcount = FFMIN(FFMAX(0, shift - ((12 - logcounts[i] + 1) >> 1)), logcounts[i] - 1); dist->freq[i] = (1 << (logcounts[i] - 1)) + (get_bitsz(gb, bitcount) << (logcounts[i] - 1 - bitcount)); } } total_count += dist->freq[i]; } dist->freq[omit_pos] = (1 << 12) - total_count; return 0; } static void dist_bundle_close(JXLDistributionBundle *bundle) { if (bundle->use_prefix_code && bundle->dists) for (int i = 0; i < bundle->num_clusters; i++) ff_vlc_free(&bundle->dists[i].vlc); av_freep(&bundle->dists); av_freep(&bundle->cluster_map); } static int read_distribution_bundle(GetBitContext *gb, JXLEntropyDecoder *dec, JXLDistributionBundle *bundle, int num_dist, int disallow_lz77); static int read_dist_clustering(GetBitContext *gb, JXLEntropyDecoder *dec, JXLDistributionBundle *bundle) { int ret; bundle->cluster_map = av_malloc(bundle->num_dist); if (!bundle->cluster_map) return AVERROR(ENOMEM); if (bundle->num_dist == 1) { bundle->cluster_map[0] = 0; bundle->num_clusters = 1; return 0; } if (get_bits1(gb)) { /* simple clustering */ uint32_t nbits = get_bits(gb, 2); for (int i = 0; i < bundle->num_dist; i++) bundle->cluster_map[i] = get_bitsz(gb, nbits); } else { /* complex clustering */ int use_mtf = get_bits1(gb); JXLDistributionBundle nested = { 0 }; /* num_dist == 1 prevents this from recursing again */ ret = read_distribution_bundle(gb, dec, &nested, 1, bundle->num_dist <= 2); if (ret < 0) { dist_bundle_close(&nested); return ret; } for (int i = 0; i < bundle->num_dist; i++) { uint32_t clust; ret = decode_hybrid_varlen_uint(gb, dec, &nested, 0, &clust); if (ret < 0) { dist_bundle_close(&nested); return ret; } bundle->cluster_map[i] = clust; } dec->state = -1; /* it's not going to necessarily be zero after reading */ dec->num_to_copy = 0; dist_bundle_close(&nested); if (use_mtf) { uint8_t mtf[256]; for (int i = 0; i < 256; i++) mtf[i] = i; for (int i = 0; i < bundle->num_dist; i++) { int index = bundle->cluster_map[i]; bundle->cluster_map[i] = mtf[index]; if (index) { int value = mtf[index]; for (int j = index; j > 0; j--) mtf[j] = mtf[j - 1]; mtf[0] = value; } } } } for (int i = 0; i < bundle->num_dist; i++) { if (bundle->cluster_map[i] >= bundle->num_clusters) bundle->num_clusters = bundle->cluster_map[i] + 1; } if (bundle->num_clusters > bundle->num_dist) return AVERROR_INVALIDDATA; return 0; } static int gen_alias_map(JXLEntropyDecoder *dec, JXLSymbolDistribution *dist, int log_alphabet_size) { uint32_t bucket_size, table_size; uint8_t overfull[256], underfull[256]; int overfull_pos = 0, underfull_pos = 0; dist->log_bucket_size = 12 - log_alphabet_size; bucket_size = 1 << dist->log_bucket_size; table_size = 1 << log_alphabet_size; if (dist->uniq_pos >= 0) { for (int i = 0; i < table_size; i++) { dist->symbols[i] = dist->uniq_pos; dist->offsets[i] = bucket_size * i; dist->cutoffs[i] = 0; } return 0; } for (int i = 0; i < dist->alphabet_size; i++) { dist->cutoffs[i] = dist->freq[i]; dist->symbols[i] = i; if (dist->cutoffs[i] > bucket_size) overfull[overfull_pos++] = i; else if (dist->cutoffs[i] < bucket_size) underfull[underfull_pos++] = i; } for (int i = dist->alphabet_size; i < table_size; i++) { dist->cutoffs[i] = 0; underfull[underfull_pos++] = i; } while (overfull_pos) { int o, u, by; /* this should be impossible */ if (!underfull_pos) return AVERROR_INVALIDDATA; u = underfull[--underfull_pos]; o = overfull[--overfull_pos]; by = bucket_size - dist->cutoffs[u]; dist->cutoffs[o] -= by; dist->symbols[u] = o; dist->offsets[u] = dist->cutoffs[o]; if (dist->cutoffs[o] < bucket_size) underfull[underfull_pos++] = o; else if (dist->cutoffs[o] > bucket_size) overfull[overfull_pos++] = o; } for (int i = 0; i < table_size; i++) { if (dist->cutoffs[i] == bucket_size) { dist->symbols[i] = i; dist->offsets[i] = 0; dist->cutoffs[i] = 0; } else { dist->offsets[i] -= dist->cutoffs[i]; } } return 0; } static int read_simple_vlc_prefix(GetBitContext *gb, JXLEntropyDecoder *dec, JXLSymbolDistribution *dist) { int nsym, tree_select, bits; int8_t lens[4]; int16_t symbols[4]; nsym = 1 + get_bits(gb, 2); for (int i = 0; i < nsym; i++) symbols[i] = get_bitsz(gb, dist->log_alphabet_size); if (nsym == 4) tree_select = get_bits1(gb); switch (nsym) { case 1: dist->vlc.bits = 0; dist->default_symbol = symbols[0]; return 0; case 2: bits = 1; lens[0] = 1, lens[1] = 1, lens[2] = 0, lens[3] = 0; if (symbols[1] < symbols[0]) FFSWAP(int16_t, symbols[0], symbols[1]); break; case 3: bits = 2; lens[0] = 1, lens[1] = 2, lens[2] = 2, lens[3] = 0; if (symbols[2] < symbols[1]) FFSWAP(int16_t, symbols[1], symbols[2]); break; case 4: if (tree_select) { bits = 3; lens[0] = 1, lens[1] = 2, lens[2] = 3, lens[3] = 3; if (symbols[3] < symbols[2]) FFSWAP(int16_t, symbols[2], symbols[3]); } else { bits = 2; lens[0] = 2, lens[1] = 2, lens[2] = 2, lens[3] = 2; while (1) { if (symbols[1] < symbols[0]) FFSWAP(int16_t, symbols[0], symbols[1]); if (symbols[3] < symbols[2]) FFSWAP(int16_t, symbols[2], symbols[3]); if (symbols[1] <= symbols[2]) break; FFSWAP(int16_t, symbols[1], symbols[2]); } } break; default: // Challenge Complete! How did we get here? return AVERROR_BUG; } return ff_vlc_init_from_lengths(&dist->vlc, bits, nsym, lens, 1, symbols, 2, 2, 0, VLC_INIT_LE, dec->logctx); } static int read_vlc_prefix(GetBitContext *gb, JXLEntropyDecoder *dec, JXLSymbolDistribution *dist) { int8_t level1_lens[18] = { 0 }; int8_t level1_lens_s[18] = { 0 }; int16_t level1_syms[18] = { 0 }; uint32_t level1_codecounts[19] = { 0 }; uint8_t *buf = NULL; int8_t *level2_lens, *level2_lens_s; int16_t *level2_syms; uint32_t *level2_codecounts; int repeat_count_prev = 0, repeat_count_zero = 0, prev = 8; int total_code = 0, len, hskip, num_codes = 0, ret; VLC level1_vlc; if (dist->alphabet_size == 1) { dist->vlc.bits = 0; dist->default_symbol = 0; return 0; } hskip = get_bits(gb, 2); if (hskip == 1) return read_simple_vlc_prefix(gb, dec, dist); level1_codecounts[0] = hskip; for (int i = hskip; i < 18; i++) { len = level1_lens[prefix_codelen_map[i]] = get_vlc2(gb, level0_table, 4, 1); level1_codecounts[len]++; if (len) { total_code += (32 >> len); num_codes++; } if (total_code >= 32) { level1_codecounts[0] += 18 - i - 1; break; } } if (total_code != 32 && num_codes >= 2 || num_codes < 1) return AVERROR_INVALIDDATA; for (int i = 1; i < 19; i++) level1_codecounts[i] += level1_codecounts[i - 1]; for (int i = 17; i >= 0; i--) { int idx = --level1_codecounts[level1_lens[i]]; level1_lens_s[idx] = level1_lens[i]; level1_syms[idx] = i; } ret = ff_vlc_init_from_lengths(&level1_vlc, 5, 18, level1_lens_s, 1, level1_syms, 2, 2, 0, VLC_INIT_LE, dec->logctx); if (ret < 0) goto end; buf = av_calloc(1, 262148); // 32768 * 8 + 4 if (!buf) { ret = AVERROR(ENOMEM); goto end; } level2_lens = (int8_t *)buf; level2_lens_s = (int8_t *)(buf + 32768); level2_syms = (int16_t *)(buf + 65536); level2_codecounts = (uint32_t *)(buf + 131072); total_code = 0; for (int i = 0; i < dist->alphabet_size; i++) { len = get_vlc2(gb, level1_vlc.table, 5, 1); if (len == 16) { int extra = 3 + get_bits(gb, 2); if (repeat_count_prev) extra = 4 * (repeat_count_prev - 2) - repeat_count_prev + extra; for (int j = 0; j < extra; j++) level2_lens[i + j] = prev; total_code += (32768 >> prev) * extra; i += extra - 1; repeat_count_prev += extra; repeat_count_zero = 0; level2_codecounts[prev] += extra; } else if (len == 17) { int extra = 3 + get_bits(gb, 3); if (repeat_count_zero > 0) extra = 8 * (repeat_count_zero - 2) - repeat_count_zero + extra; i += extra - 1; repeat_count_prev = 0; repeat_count_zero += extra; level2_codecounts[0] += extra; } else { level2_lens[i] = len; repeat_count_prev = repeat_count_zero = 0; if (len) { total_code += (32768 >> len); prev = len; } level2_codecounts[len]++; } if (total_code >= 32768) { level2_codecounts[0] += dist->alphabet_size - i - 1; break; } } if (total_code != 32768 && level2_codecounts[0] < dist->alphabet_size - 1) return AVERROR_INVALIDDATA; for (int i = 1; i < dist->alphabet_size + 1; i++) level2_codecounts[i] += level2_codecounts[i - 1]; for (int i = dist->alphabet_size - 1; i >= 0; i--) { int idx = --level2_codecounts[level2_lens[i]]; level2_lens_s[idx] = level2_lens[i]; level2_syms[idx] = i; } ret = ff_vlc_init_from_lengths(&dist->vlc, 15, dist->alphabet_size, level2_lens_s, 1, level2_syms, 2, 2, 0, VLC_INIT_LE, dec->logctx); end: av_freep(&buf); ff_vlc_free(&level1_vlc); return ret; } static int read_distribution_bundle(GetBitContext *gb, JXLEntropyDecoder *dec, JXLDistributionBundle *bundle, int num_dist, int disallow_lz77) { int ret; if (num_dist <= 0) return AVERROR(EINVAL); bundle->num_dist = num_dist; bundle->lz77_enabled = get_bits1(gb); if (bundle->lz77_enabled) { if (disallow_lz77) return AVERROR_INVALIDDATA; bundle->lz77_min_symbol = jxl_u32(gb, 224, 512, 4096, 8, 0, 0, 0, 15); bundle->lz77_min_length = jxl_u32(gb, 3, 4, 5, 9, 0, 0, 2, 8); bundle->num_dist++; ret = read_hybrid_uint_conf(gb, &bundle->lz_len_conf, 8); if (ret < 0) return ret; } if (bundle->lz77_enabled && !dec->window) { dec->window = av_malloc_array(1 << 20, sizeof(uint32_t)); if (!dec->window) return AVERROR(ENOMEM); } ret = read_dist_clustering(gb, dec, bundle); if (ret < 0) return ret; if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; bundle->dists = av_calloc(bundle->num_clusters, sizeof(JXLSymbolDistribution)); if (!bundle->dists) return AVERROR(ENOMEM); bundle->use_prefix_code = get_bits1(gb); bundle->log_alphabet_size = bundle->use_prefix_code ? 15 : 5 + get_bits(gb, 2); for (int i = 0; i < bundle->num_clusters; i++) { ret = read_hybrid_uint_conf(gb, &bundle->dists[i].config, bundle->log_alphabet_size); if (ret < 0) return ret; if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; } if (bundle->use_prefix_code) { for (int i = 0; i < bundle->num_clusters; i++) { JXLSymbolDistribution *dist = &bundle->dists[i]; if (get_bits1(gb)) { int n = get_bits(gb, 4); dist->alphabet_size = 1 + (1 << n) + get_bitsz(gb, n); } else { dist->alphabet_size = 1; } dist->log_alphabet_size = clog1p(dist->alphabet_size - 1); } for (int i = 0; i < bundle->num_clusters; i++) { ret = read_vlc_prefix(gb, dec, &bundle->dists[i]); if (ret < 0) return ret; if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; } } else { for (int i = 0; i < bundle->num_clusters; i++) { ret = populate_distribution(gb, &bundle->dists[i], bundle->log_alphabet_size); if (ret < 0) return ret; if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; } for (int i = 0; i < bundle->num_clusters; i++) { ret = gen_alias_map(dec, &bundle->dists[i], bundle->log_alphabet_size); if (ret < 0) return ret; } } return 0; } static void entropy_decoder_close(JXLEntropyDecoder *dec) { if (!dec) return; av_freep(&dec->window); dist_bundle_close(&dec->bundle); } static int entropy_decoder_init(void *avctx, GetBitContext *gb, JXLEntropyDecoder *dec, int num_dist) { int ret; memset(dec, 0, sizeof(*dec)); dec->logctx = avctx; dec->state = -1; ret = read_distribution_bundle(gb, dec, &dec->bundle, num_dist, 0); if (ret < 0) { entropy_decoder_close(dec); return ret; } return 0; } static int64_t entropy_decoder_read_symbol(GetBitContext *gb, JXLEntropyDecoder *dec, uint32_t context) { int ret; uint32_t hybrid_uint; ret = decode_hybrid_varlen_uint(gb, dec, &dec->bundle, context, &hybrid_uint); if (ret < 0) return ret; return hybrid_uint; } static inline uint32_t icc_context(uint64_t i, uint32_t b1, uint32_t b2) { uint32_t p1, p2; if (i <= 128) return 0; if (b1 >= 'a' && b1 <= 'z' || b1 >= 'A' && b1 <= 'Z') p1 = 0; else if (b1 >= '0' && b1 <= '9' || b1 == '.' || b1 == ',') p1 = 1; else if (b1 <= 1) p1 = b1 + 2; else if (b1 > 1 && b1 < 16) p1 = 4; else if (b1 > 240 && b1 < 255) p1 = 5; else if (b1 == 255) p1 = 6; else p1 = 7; if (b2 >= 'a' && b2 <= 'z' || b2 >= 'A' && b2 <= 'Z') p2 = 0; else if (b2 >= '0' && b2 <= '9' || b2 == '.' || b2 == ',') p2 = 1; else if (b2 < 16) p2 = 2; else if (b2 > 240) p2 = 3; else p2 = 4; return 1 + p1 + p2 * 8; } static inline uint32_t toc_context(uint32_t x) { return FFMIN(7, clog1p(x)); } static void populate_fields(AVCodecParserContext *s, AVCodecContext *avctx, const FFJXLMetadata *meta) { s->width = meta->width; s->height = meta->height; switch (meta->csp) { case JPEGXL_CS_RGB: case JPEGXL_CS_XYB: avctx->colorspace = AVCOL_SPC_RGB; break; default: avctx->colorspace = AVCOL_SPC_UNSPECIFIED; } if (meta->wp == JPEGXL_WP_D65) { switch (meta->primaries) { case JPEGXL_PR_SRGB: avctx->color_primaries = AVCOL_PRI_BT709; break; case JPEGXL_PR_P3: avctx->color_primaries = AVCOL_PRI_SMPTE432; break; case JPEGXL_PR_2100: avctx->color_primaries = AVCOL_PRI_BT2020; break; default: avctx->color_primaries = AVCOL_PRI_UNSPECIFIED; } } else if (meta->wp == JPEGXL_WP_DCI && meta->primaries == JPEGXL_PR_P3) { avctx->color_primaries = AVCOL_PRI_SMPTE431; } else { avctx->color_primaries = AVCOL_PRI_UNSPECIFIED; } if (meta->trc > JPEGXL_TR_GAMMA) { FFJXLTransferCharacteristic trc = meta->trc - JPEGXL_TR_GAMMA; switch (trc) { case JPEGXL_TR_BT709: avctx->color_trc = AVCOL_TRC_BT709; break; case JPEGXL_TR_LINEAR: avctx->color_trc = AVCOL_TRC_LINEAR; break; case JPEGXL_TR_SRGB: avctx->color_trc = AVCOL_TRC_IEC61966_2_1; break; case JPEGXL_TR_PQ: avctx->color_trc = AVCOL_TRC_SMPTEST2084; break; case JPEGXL_TR_DCI: avctx->color_trc = AVCOL_TRC_SMPTE428; break; case JPEGXL_TR_HLG: avctx->color_trc = AVCOL_TRC_ARIB_STD_B67; break; default: avctx->color_trc = AVCOL_TRC_UNSPECIFIED; } } else if (meta->trc > 0) { if (meta->trc > 45355 && meta->trc < 45555) avctx->color_trc = AVCOL_TRC_GAMMA22; else if (meta->trc > 35614 && meta->trc < 35814) avctx->color_trc = AVCOL_TRC_GAMMA28; else avctx->color_trc = AVCOL_TRC_UNSPECIFIED; } else { avctx->color_trc = AVCOL_TRC_UNSPECIFIED; } if (meta->csp == JPEGXL_CS_GRAY) { if (meta->bit_depth <= 8) s->format = meta->have_alpha ? AV_PIX_FMT_YA8 : AV_PIX_FMT_GRAY8; else if (meta->bit_depth <= 16) s->format = meta->have_alpha ? AV_PIX_FMT_YA16 : AV_PIX_FMT_GRAY16; else s->format = meta->have_alpha ? AV_PIX_FMT_NONE : AV_PIX_FMT_GRAYF32; } else { if (meta->bit_depth <= 8) s->format = meta->have_alpha ? AV_PIX_FMT_RGBA : AV_PIX_FMT_RGB24; else if (meta->bit_depth <= 16) s->format = meta->have_alpha ? AV_PIX_FMT_RGBA64 : AV_PIX_FMT_RGB48; else s->format = meta->have_alpha ? AV_PIX_FMT_RGBAF32 : AV_PIX_FMT_RGBF32; } } static int skip_icc_profile(void *avctx, JXLParseContext *ctx, GetBitContext *gb) { int64_t ret; uint32_t last = 0, last2 = 0; JXLEntropyDecoder dec; uint64_t enc_size = jxl_u64(gb); if (!enc_size) return AVERROR_INVALIDDATA; ret = entropy_decoder_init(avctx, gb, &dec, 41); if (ret < 0) return ret; if (get_bits_left(gb) < 0) { entropy_decoder_close(&dec); return AVERROR_BUFFER_TOO_SMALL; } for (uint64_t read = 0; read < enc_size; read++) { ret = entropy_decoder_read_symbol(gb, &dec, icc_context(read, last, last2)); if (ret < 0 || get_bits_left(gb) < 0) { entropy_decoder_close(&dec); return ret < 0 ? ret : AVERROR_BUFFER_TOO_SMALL; } last2 = last; last = ret; } entropy_decoder_close(&dec); return 0; } static int skip_extensions(GetBitContext *gb) { uint64_t extensions = jxl_u64(gb), extensions_len = 0; if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; if (!extensions) return 0; for (int i = 0; i < 64; i++) { if (extensions & (UINT64_C(1) << i)) extensions_len += jxl_u64(gb); if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; } if (extensions_len > INT_MAX || get_bits_left(gb) < extensions_len) return AVERROR_BUFFER_TOO_SMALL; skip_bits_long(gb, extensions_len); return 0; } static int parse_frame_header(void *avctx, JXLParseContext *ctx, GetBitContext *gb) { int all_default, do_yCbCr = 0, num_passes = 1, ret; int group_size_shift = 1, lf_level = 0, save_as_ref = 0; int have_crop = 0, full_frame = 1, resets_canvas = 1, upsampling = 1; JXLFrame *frame = &ctx->codestream.frame; const FFJXLMetadata *meta = &ctx->codestream.meta; int32_t x0 = 0, y0 = 0; uint32_t duration = 0, width = meta->coded_width, height = meta->coded_height; uint32_t name_len, num_groups, num_lf_groups, group_dim, lf_group_dim, toc_count; uint64_t flags = 0; int start_len = get_bits_count(gb); memset(frame, 0, sizeof(*frame)); frame->is_last = 1; all_default = get_bits1(gb); if (!all_default) { frame->type = get_bits(gb, 2); frame->encoding = get_bits1(gb); flags = jxl_u64(gb); if (!meta->xyb_encoded) do_yCbCr = get_bits1(gb); if (!(flags & JXL_FLAG_USE_LF_FRAME)) { if (do_yCbCr) skip_bits(gb, 6); // jpeg upsampling upsampling = jxl_u32(gb, 1, 2, 4, 8, 0, 0, 0, 0); skip_bits_long(gb, 2 * meta->num_extra_channels); if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; } if (frame->encoding == JPEGXL_ENC_MODULAR) group_size_shift = get_bits(gb, 2); else if (meta->xyb_encoded) skip_bits(gb, 6); // xqm and bqm scales if (frame->type != JPEGXL_FRAME_REFERENCE_ONLY) { num_passes = jxl_u32(gb, 1, 2, 3, 4, 0, 0, 0, 3); if (num_passes != 1) { int num_ds = jxl_u32(gb, 0, 1, 2, 3, 0, 0, 0, 1); skip_bits(gb, 2 * (num_passes - 1)); // shift skip_bits(gb, 2 * num_ds); // downsample for (int i = 0; i < num_ds; i++) jxl_u32(gb, 0, 1, 2, 0, 0, 0, 0, 3); } } if (frame->type == JPEGXL_FRAME_LF) lf_level = 1 + get_bits(gb, 2); else have_crop = get_bits1(gb); if (have_crop) { if (frame->type != JPEGXL_FRAME_REFERENCE_ONLY) { uint32_t ux0 = jxl_u32(gb, 0, 256, 2304, 18688, 8, 11, 14, 30); uint32_t uy0 = jxl_u32(gb, 0, 256, 2304, 18688, 8, 11, 14, 30); x0 = unpack_signed(ux0); y0 = unpack_signed(uy0); } width = jxl_u32(gb, 0, 256, 2304, 18688, 8, 11, 14, 30); height = jxl_u32(gb, 0, 256, 2304, 18688, 8, 11, 14, 30); full_frame = x0 <= 0 && y0 <= 0 && width + x0 >= meta->coded_width && height + y0 >= meta->coded_height; } if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; if (frame->type == JPEGXL_FRAME_REGULAR || frame->type == JPEGXL_FRAME_SKIP_PROGRESSIVE) { for (int i = 0; i <= meta->num_extra_channels; i++) { int mode = jxl_u32(gb, 0, 1, 2, 3, 0, 0, 0, 2); if (meta->num_extra_channels && (mode == JPEGXL_BM_BLEND || mode == JPEGXL_BM_MULADD)) jxl_u32(gb, 0, 1, 2, 3, 0, 0, 0, 2); if (meta->num_extra_channels && (mode == JPEGXL_BM_BLEND || mode == JPEGXL_BM_MULADD || mode == JPEGXL_BM_MUL)) skip_bits1(gb); if (!i) resets_canvas = mode == JPEGXL_BM_REPLACE && full_frame; if (!resets_canvas) skip_bits(gb, 2); if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; } if (meta->animation_offset) duration = jxl_u32(gb, 0, 1, 0, 0, 0, 0, 8, 32); if (meta->have_timecodes) skip_bits_long(gb, 32); frame->is_last = get_bits1(gb); } else { frame->is_last = 0; } if (frame->type != JPEGXL_FRAME_LF && !frame->is_last) save_as_ref = get_bits(gb, 2); if (frame->type == JPEGXL_FRAME_REFERENCE_ONLY || (resets_canvas && !frame->is_last && (!duration || save_as_ref) && frame->type != JPEGXL_FRAME_LF)) skip_bits1(gb); // save before color transform name_len = 8 * jxl_u32(gb, 0, 0, 16, 48, 0, 4, 5, 10); if (get_bits_left(gb) < name_len) return AVERROR_BUFFER_TOO_SMALL; skip_bits_long(gb, name_len); } if (!all_default) { int restd = get_bits1(gb), gab = 1; if (!restd) gab = get_bits1(gb); if (gab && !restd && get_bits1(gb)) // gab custom skip_bits_long(gb, 16 * 6); if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; if (!restd) { int epf = get_bits(gb, 2); if (epf) { if (frame->encoding == JPEGXL_ENC_VARDCT && get_bits1(gb)) { skip_bits_long(gb, 16 * 8); // custom epf sharpness if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; } if (get_bits1(gb)) { skip_bits_long(gb, 3 * 16 + 32); // custom epf weight if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; } if (get_bits1(gb)) { // custom epf sigma if (frame->encoding == JPEGXL_ENC_VARDCT) skip_bits(gb, 16); skip_bits_long(gb, 16 * 3); if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; } if (frame->encoding == JPEGXL_ENC_MODULAR) skip_bits(gb, 16); } ret = skip_extensions(gb); if (ret < 0) return ret; } ret = skip_extensions(gb); if (ret < 0) return ret; } width = div_ceil(div_ceil(width, upsampling), 1 << (3 * lf_level)); height = div_ceil(div_ceil(height, upsampling), 1 << (3 * lf_level)); group_dim = 128 << group_size_shift; lf_group_dim = group_dim << 3; num_groups = div_ceil(width, group_dim) * div_ceil(height, group_dim); num_lf_groups = div_ceil(width, lf_group_dim) * div_ceil(height, lf_group_dim); if (num_groups == 1 && num_passes == 1) toc_count = 1; else toc_count = 2 + num_lf_groups + num_groups * num_passes; // permuted toc if (get_bits1(gb)) { JXLEntropyDecoder dec; uint32_t end, lehmer = 0; ret = entropy_decoder_init(avctx, gb, &dec, 8); if (ret < 0) return ret; if (get_bits_left(gb) < 0) { entropy_decoder_close(&dec); return AVERROR_BUFFER_TOO_SMALL; } end = entropy_decoder_read_symbol(gb, &dec, toc_context(toc_count)); if (end > toc_count) { entropy_decoder_close(&dec); return AVERROR_INVALIDDATA; } for (uint32_t i = 0; i < end; i++) { lehmer = entropy_decoder_read_symbol(gb, &dec, toc_context(lehmer)); if (get_bits_left(gb) < 0) { entropy_decoder_close(&dec); return AVERROR_BUFFER_TOO_SMALL; } } entropy_decoder_close(&dec); } align_get_bits(gb); for (uint32_t i = 0; i < toc_count; i++) { frame->body_length += 8 * jxl_u32(gb, 0, 1024, 17408, 4211712, 10, 14, 22, 30); if (get_bits_left(gb) < 0) return AVERROR_BUFFER_TOO_SMALL; } align_get_bits(gb); frame->total_length = frame->body_length + get_bits_count(gb) - start_len; return 0; } static int skip_boxes(JXLParseContext *ctx, const uint8_t *buf, int buf_size) { GetByteContext gb; if (ctx->skip > buf_size) return AVERROR_BUFFER_TOO_SMALL; buf += ctx->skip; buf_size -= ctx->skip; bytestream2_init(&gb, buf, buf_size); while (1) { uint64_t size; int head_size = 4; if (bytestream2_peek_le16(&gb) == FF_JPEGXL_CODESTREAM_SIGNATURE_LE) break; if (bytestream2_peek_le64(&gb) == FF_JPEGXL_CONTAINER_SIGNATURE_LE) break; if (bytestream2_get_bytes_left(&gb) < 8) return AVERROR_BUFFER_TOO_SMALL; size = bytestream2_get_be32(&gb); if (size == 1) { if (bytestream2_get_bytes_left(&gb) < 12) return AVERROR_BUFFER_TOO_SMALL; size = bytestream2_get_be64(&gb); head_size = 12; } if (!size) return AVERROR_INVALIDDATA; /* invalid ISOBMFF size */ if (size <= head_size + 4 || size > INT_MAX - ctx->skip) return AVERROR_INVALIDDATA; ctx->skip += size; bytestream2_skip(&gb, size - head_size); if (bytestream2_get_bytes_left(&gb) <= 0) return AVERROR_BUFFER_TOO_SMALL; } return 0; } static int try_parse(AVCodecParserContext *s, AVCodecContext *avctx, JXLParseContext *ctx, const uint8_t *buf, int buf_size) { int ret, cs_buflen, header_skip; const uint8_t *cs_buffer; GetBitContext gb; if (ctx->skip > buf_size) return AVERROR_BUFFER_TOO_SMALL; buf += ctx->skip; buf_size -= ctx->skip; if (ctx->container || AV_RL64(buf) == FF_JPEGXL_CONTAINER_SIGNATURE_LE) { ctx->container = 1; ret = ff_jpegxl_collect_codestream_header(buf, buf_size, ctx->cs_buffer, sizeof(ctx->cs_buffer), &ctx->copied); if (ret < 0) return ret; ctx->collected_size = ret; if (!ctx->copied) { ctx->skip += ret; return AVERROR_BUFFER_TOO_SMALL; } cs_buffer = ctx->cs_buffer; cs_buflen = FFMIN(sizeof(ctx->cs_buffer), ctx->copied); } else { cs_buffer = buf; cs_buflen = buf_size; } if (!ctx->codestream_length) { header_skip = ff_jpegxl_parse_codestream_header(cs_buffer, cs_buflen, &ctx->codestream.meta, 0); if (header_skip < 0) return header_skip; ctx->codestream_length = header_skip; populate_fields(s, avctx, &ctx->codestream.meta); } if (ctx->container) return ctx->collected_size; ret = init_get_bits8(&gb, cs_buffer, cs_buflen); if (ret < 0) return ret; skip_bits_long(&gb, ctx->codestream_length); if (!ctx->skipped_icc && ctx->codestream.meta.have_icc_profile) { ret = skip_icc_profile(avctx, ctx, &gb); if (ret < 0) return ret; ctx->skipped_icc = 1; align_get_bits(&gb); ctx->codestream_length = get_bits_count(&gb); } if (get_bits_left(&gb) <= 0) return AVERROR_BUFFER_TOO_SMALL; while (1) { ret = parse_frame_header(avctx, ctx, &gb); if (ret < 0) return ret; ctx->codestream_length += ctx->codestream.frame.total_length; if (ctx->codestream.frame.is_last) return ctx->codestream_length / 8; if (get_bits_left(&gb) <= ctx->codestream.frame.body_length) return AVERROR_BUFFER_TOO_SMALL; skip_bits_long(&gb, ctx->codestream.frame.body_length); } } static int jpegxl_parse(AVCodecParserContext *s, AVCodecContext *avctx, const uint8_t **poutbuf, int *poutbuf_size, const uint8_t *buf, int buf_size) { JXLParseContext *ctx = s->priv_data; int next = END_NOT_FOUND, ret; *poutbuf_size = 0; *poutbuf = NULL; if (!ctx->pc.index) goto flush; if ((!ctx->container || !ctx->codestream_length) && !ctx->next) { ret = try_parse(s, avctx, ctx, ctx->pc.buffer, ctx->pc.index); if (ret < 0) goto flush; ctx->next = ret; if (ctx->container) ctx->skip += ctx->next; } if (ctx->container && ctx->next >= 0) { ret = skip_boxes(ctx, ctx->pc.buffer, ctx->pc.index); if (ret < 0) { if (ret == AVERROR_INVALIDDATA) ctx->next = -1; goto flush; } ctx->next = ret + ctx->skip; } if (ctx->next >= 0) next = ctx->next - ctx->pc.index; flush: if (next > buf_size) next = END_NOT_FOUND; ret = ff_combine_frame(&ctx->pc, next, &buf, &buf_size); if (ret < 0) return buf_size; *poutbuf = buf; *poutbuf_size = buf_size; ctx->codestream_length = 0; ctx->collected_size = 0; ctx->container = 0; ctx->copied = 0; ctx->skip = 0; ctx->skipped_icc = 0; ctx->next = 0; memset(&ctx->codestream, 0, sizeof(ctx->codestream)); return next; } const AVCodecParser ff_jpegxl_parser = { .codec_ids = { AV_CODEC_ID_JPEGXL }, .priv_data_size = sizeof(JXLParseContext), .parser_parse = jpegxl_parse, .parser_close = ff_parse_close, };