Coverage Report

Created: 2025-06-22 08:04

/src/libjxl/lib/jxl/dec_frame.cc
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2
//
3
// Use of this source code is governed by a BSD-style
4
// license that can be found in the LICENSE file.
5
6
#include "lib/jxl/dec_frame.h"
7
8
#include <jxl/decode.h>
9
#include <jxl/memory_manager.h>
10
11
#include <algorithm>
12
#include <cstddef>
13
#include <cstdint>
14
#include <cstdlib>
15
#include <memory>
16
#include <utility>
17
#include <vector>
18
19
#include "lib/jxl/ac_context.h"
20
#include "lib/jxl/ac_strategy.h"
21
#include "lib/jxl/base/bits.h"
22
#include "lib/jxl/base/common.h"
23
#include "lib/jxl/base/compiler_specific.h"
24
#include "lib/jxl/base/data_parallel.h"
25
#include "lib/jxl/base/printf_macros.h"
26
#include "lib/jxl/base/rect.h"
27
#include "lib/jxl/base/status.h"
28
#include "lib/jxl/chroma_from_luma.h"
29
#include "lib/jxl/coeff_order.h"
30
#include "lib/jxl/coeff_order_fwd.h"
31
#include "lib/jxl/common.h"  // kMaxNumPasses
32
#include "lib/jxl/compressed_dc.h"
33
#include "lib/jxl/dct_util.h"
34
#include "lib/jxl/dec_ans.h"
35
#include "lib/jxl/dec_bit_reader.h"
36
#include "lib/jxl/dec_cache.h"
37
#include "lib/jxl/dec_group.h"
38
#include "lib/jxl/dec_modular.h"
39
#include "lib/jxl/dec_noise.h"
40
#include "lib/jxl/dec_patch_dictionary.h"
41
#include "lib/jxl/entropy_coder.h"
42
#include "lib/jxl/epf.h"
43
#include "lib/jxl/fields.h"
44
#include "lib/jxl/frame_dimensions.h"
45
#include "lib/jxl/frame_header.h"
46
#include "lib/jxl/image_bundle.h"
47
#include "lib/jxl/image_metadata.h"
48
#include "lib/jxl/image_ops.h"
49
#include "lib/jxl/jpeg/jpeg_data.h"
50
#include "lib/jxl/loop_filter.h"
51
#include "lib/jxl/passes_state.h"
52
#include "lib/jxl/quant_weights.h"
53
#include "lib/jxl/quantizer.h"
54
#include "lib/jxl/render_pipeline/render_pipeline.h"
55
#include "lib/jxl/splines.h"
56
#include "lib/jxl/toc.h"
57
58
namespace jxl {
59
60
namespace {
61
Status DecodeGlobalDCInfo(BitReader* reader, bool is_jpeg,
62
12.2k
                          PassesDecoderState* state, ThreadPool* pool) {
63
12.2k
  JXL_RETURN_IF_ERROR(state->shared_storage.quantizer.Decode(reader));
64
65
12.2k
  JXL_RETURN_IF_ERROR(DecodeBlockCtxMap(state->memory_manager(), reader,
66
12.2k
                                        &state->shared_storage.block_ctx_map));
67
68
12.2k
  JXL_RETURN_IF_ERROR(state->shared_storage.cmap.DecodeDC(reader));
69
70
  // Pre-compute info for decoding a group.
71
12.2k
  if (is_jpeg) {
72
0
    state->shared_storage.quantizer.ClearDCMul();  // Don't dequant DC
73
0
  }
74
75
12.2k
  state->shared_storage.ac_strategy.FillInvalid();
76
12.2k
  return true;
77
12.2k
}
78
}  // namespace
79
80
Status DecodeFrame(PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool,
81
                   const uint8_t* next_in, size_t avail_in,
82
                   FrameHeader* frame_header, ImageBundle* decoded,
83
                   const CodecMetadata& metadata,
84
0
                   bool use_slow_rendering_pipeline) {
85
0
  FrameDecoder frame_decoder(dec_state, metadata, pool,
86
0
                             use_slow_rendering_pipeline);
87
88
0
  BitReader reader(Bytes(next_in, avail_in));
89
0
  JXL_RETURN_IF_ERROR(frame_decoder.InitFrame(&reader, decoded,
90
0
                                              /*is_preview=*/false));
91
0
  JXL_RETURN_IF_ERROR(frame_decoder.InitFrameOutput());
92
0
  if (frame_header) {
93
0
    *frame_header = frame_decoder.GetFrameHeader();
94
0
  }
95
96
0
  JXL_RETURN_IF_ERROR(reader.AllReadsWithinBounds());
97
0
  size_t header_bytes = reader.TotalBitsConsumed() / kBitsPerByte;
98
0
  JXL_RETURN_IF_ERROR(reader.Close());
99
100
0
  size_t processed_bytes = header_bytes;
101
0
  Status close_ok = true;
102
0
  std::vector<std::unique_ptr<BitReader>> section_readers;
103
0
  {
104
0
    std::vector<std::unique_ptr<BitReaderScopedCloser>> section_closers;
105
0
    std::vector<FrameDecoder::SectionInfo> section_info;
106
0
    std::vector<FrameDecoder::SectionStatus> section_status;
107
0
    size_t pos = header_bytes;
108
0
    size_t index = 0;
109
0
    for (auto toc_entry : frame_decoder.Toc()) {
110
0
      JXL_RETURN_IF_ERROR(pos + toc_entry.size <= avail_in);
111
0
      auto br = make_unique<BitReader>(Bytes(next_in + pos, toc_entry.size));
112
0
      section_info.emplace_back(
113
0
          FrameDecoder::SectionInfo{br.get(), toc_entry.id, index++});
114
0
      section_closers.emplace_back(
115
0
          make_unique<BitReaderScopedCloser>(*br, close_ok));
116
0
      section_readers.emplace_back(std::move(br));
117
0
      pos += toc_entry.size;
118
0
    }
119
0
    section_status.resize(section_info.size());
120
0
    JXL_RETURN_IF_ERROR(frame_decoder.ProcessSections(
121
0
        section_info.data(), section_info.size(), section_status.data()));
122
0
    for (size_t i = 0; i < section_status.size(); i++) {
123
0
      JXL_RETURN_IF_ERROR(section_status[i] == FrameDecoder::kDone);
124
0
      processed_bytes += frame_decoder.Toc()[i].size;
125
0
    }
126
0
  }
127
0
  JXL_RETURN_IF_ERROR(close_ok);
128
0
  JXL_RETURN_IF_ERROR(frame_decoder.FinalizeFrame());
129
0
  decoded->SetDecodedBytes(processed_bytes);
130
0
  return true;
131
0
}
132
133
Status FrameDecoder::InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
134
109k
                               bool is_preview) {
135
109k
  decoded_ = decoded;
136
109k
  JXL_ENSURE(is_finalized_);
137
109k
  JxlMemoryManager* memory_manager = decoded_->memory_manager();
138
139
  // Reset the dequantization matrices to their default values.
140
109k
  dec_state_->shared_storage.matrices = DequantMatrices();
141
142
109k
  frame_header_.nonserialized_is_preview = is_preview;
143
109k
  JXL_ENSURE(frame_header_.nonserialized_metadata != nullptr);
144
109k
  JXL_RETURN_IF_ERROR(ReadFrameHeader(br, &frame_header_));
145
95.1k
  frame_dim_ = frame_header_.ToFrameDimensions();
146
95.1k
  JXL_DEBUG_V(2, "FrameHeader: %s", frame_header_.DebugString().c_str());
147
148
95.1k
  const size_t num_passes = frame_header_.passes.num_passes;
149
95.1k
  const size_t num_groups = frame_dim_.num_groups;
150
151
  // If the previous frame was not a kRegularFrame, `decoded` may have different
152
  // dimensions; must reset to avoid errors.
153
95.1k
  decoded->RemoveColor();
154
95.1k
  decoded->ClearExtraChannels();
155
156
95.1k
  decoded->duration = frame_header_.animation_frame.duration;
157
158
95.1k
  if (!frame_header_.nonserialized_is_preview &&
159
95.1k
      (frame_header_.is_last || frame_header_.animation_frame.duration > 0) &&
160
95.1k
      (frame_header_.frame_type == kRegularFrame ||
161
15.1k
       frame_header_.frame_type == kSkipProgressive)) {
162
15.1k
    ++dec_state_->visible_frame_index;
163
15.1k
    dec_state_->nonvisible_frame_index = 0;
164
80.0k
  } else {
165
80.0k
    ++dec_state_->nonvisible_frame_index;
166
80.0k
  }
167
168
  // Read TOC.
169
95.1k
  const size_t toc_entries =
170
95.1k
      NumTocEntries(num_groups, frame_dim_.num_dc_groups, num_passes);
171
95.1k
  std::vector<uint32_t> sizes;
172
95.1k
  std::vector<coeff_order_t> permutation;
173
95.1k
  JXL_RETURN_IF_ERROR(
174
95.1k
      ReadToc(memory_manager, toc_entries, br, &sizes, &permutation));
175
88.4k
  bool have_permutation = !permutation.empty();
176
88.4k
  toc_.resize(toc_entries);
177
88.4k
  section_sizes_sum_ = 0;
178
582k
  for (size_t i = 0; i < toc_entries; ++i) {
179
494k
    toc_[i].size = sizes[i];
180
494k
    size_t index = have_permutation ? permutation[i] : i;
181
494k
    toc_[index].id = i;
182
494k
    if (section_sizes_sum_ + toc_[i].size < section_sizes_sum_) {
183
0
      return JXL_FAILURE("group offset overflow");
184
0
    }
185
494k
    section_sizes_sum_ += toc_[i].size;
186
494k
  }
187
188
88.4k
  if (JXL_DEBUG_V_LEVEL >= 3) {
189
0
    for (size_t i = 0; i < toc_entries; ++i) {
190
0
      JXL_DEBUG_V(3, "TOC entry %" PRIuS " size %" PRIuS " id %" PRIuS "", i,
191
0
                  toc_[i].size, toc_[i].id);
192
0
    }
193
0
  }
194
195
88.4k
  JXL_ENSURE((br->TotalBitsConsumed() % kBitsPerByte) == 0);
196
88.4k
  const size_t group_codes_begin = br->TotalBitsConsumed() / kBitsPerByte;
197
88.4k
  JXL_ENSURE(!toc_.empty());
198
199
  // Overflow check.
200
88.4k
  if (group_codes_begin + section_sizes_sum_ < group_codes_begin) {
201
0
    return JXL_FAILURE("Invalid group codes");
202
0
  }
203
204
88.4k
  if (!frame_header_.chroma_subsampling.Is444() &&
205
88.4k
      !(frame_header_.flags & FrameHeader::kSkipAdaptiveDCSmoothing) &&
206
88.4k
      frame_header_.encoding == FrameEncoding::kVarDCT) {
207
3
    return JXL_FAILURE(
208
3
        "Non-444 chroma subsampling is not allowed when adaptive DC "
209
3
        "smoothing is enabled");
210
3
  }
211
88.4k
  return true;
212
88.4k
}
213
214
43.9k
Status FrameDecoder::InitFrameOutput() {
215
43.9k
  JXL_RETURN_IF_ERROR(
216
43.9k
      InitializePassesSharedState(frame_header_, &dec_state_->shared_storage));
217
43.9k
  JXL_RETURN_IF_ERROR(dec_state_->Init(frame_header_));
218
43.9k
  modular_frame_decoder_.Init(frame_dim_);
219
220
43.9k
  if (decoded_->IsJPEG()) {
221
0
    if (frame_header_.encoding == FrameEncoding::kModular) {
222
0
      return JXL_FAILURE("Cannot output JPEG from Modular");
223
0
    }
224
0
    jpeg::JPEGData* jpeg_data = decoded_->jpeg_data.get();
225
0
    size_t num_components = jpeg_data->components.size();
226
0
    if (num_components != 1 && num_components != 3) {
227
0
      return JXL_FAILURE("Invalid number of components");
228
0
    }
229
0
    if (frame_header_.nonserialized_metadata->m.xyb_encoded) {
230
0
      return JXL_FAILURE("Cannot decode to JPEG an XYB image");
231
0
    }
232
0
    auto jpeg_c_map = JpegOrder(ColorTransform::kYCbCr, num_components == 1);
233
0
    decoded_->jpeg_data->width = frame_dim_.xsize;
234
0
    decoded_->jpeg_data->height = frame_dim_.ysize;
235
0
    for (size_t c = 0; c < num_components; c++) {
236
0
      auto& component = jpeg_data->components[jpeg_c_map[c]];
237
0
      component.width_in_blocks =
238
0
          frame_dim_.xsize_blocks >> frame_header_.chroma_subsampling.HShift(c);
239
0
      component.height_in_blocks =
240
0
          frame_dim_.ysize_blocks >> frame_header_.chroma_subsampling.VShift(c);
241
0
      component.h_samp_factor =
242
0
          1 << frame_header_.chroma_subsampling.RawHShift(c);
243
0
      component.v_samp_factor =
244
0
          1 << frame_header_.chroma_subsampling.RawVShift(c);
245
0
      component.coeffs.resize(component.width_in_blocks *
246
0
                              component.height_in_blocks * jxl::kDCTBlockSize);
247
0
    }
248
0
  }
249
250
  // Clear the state.
251
43.9k
  decoded_dc_global_ = false;
252
43.9k
  decoded_ac_global_ = false;
253
43.9k
  is_finalized_ = false;
254
43.9k
  finalized_dc_ = false;
255
43.9k
  num_sections_done_ = 0;
256
43.9k
  decoded_dc_groups_.clear();
257
43.9k
  decoded_dc_groups_.resize(frame_dim_.num_dc_groups);
258
43.9k
  decoded_passes_per_ac_group_.clear();
259
43.9k
  decoded_passes_per_ac_group_.resize(frame_dim_.num_groups, 0);
260
43.9k
  processed_section_.clear();
261
43.9k
  processed_section_.resize(toc_.size());
262
43.9k
  allocated_ = false;
263
43.9k
  return true;
264
43.9k
}
265
266
43.3k
Status FrameDecoder::ProcessDCGlobal(BitReader* br) {
267
43.3k
  PassesSharedState& shared = dec_state_->shared_storage;
268
43.3k
  JxlMemoryManager* memory_manager = shared.memory_manager;
269
43.3k
  if (frame_header_.flags & FrameHeader::kPatches) {
270
2.97k
    bool uses_extra_channels = false;
271
2.97k
    JXL_RETURN_IF_ERROR(shared.image_features.patches.Decode(
272
2.97k
        memory_manager, br, frame_dim_.xsize_padded, frame_dim_.ysize_padded,
273
2.97k
        shared.metadata->m.num_extra_channels, &uses_extra_channels));
274
2.83k
    if (uses_extra_channels && frame_header_.upsampling != 1) {
275
91
      for (size_t ecups : frame_header_.extra_channel_upsampling) {
276
91
        if (ecups != frame_header_.upsampling) {
277
1
          return JXL_FAILURE(
278
1
              "Cannot use extra channels in patches if color channels are "
279
1
              "subsampled differently from extra channels");
280
1
        }
281
91
      }
282
54
    }
283
40.3k
  } else {
284
40.3k
    shared.image_features.patches.Clear();
285
40.3k
  }
286
43.1k
  shared.image_features.splines.Clear();
287
43.1k
  if (frame_header_.flags & FrameHeader::kSplines) {
288
2.56k
    JXL_RETURN_IF_ERROR(shared.image_features.splines.Decode(
289
2.56k
        memory_manager, br, frame_dim_.xsize * frame_dim_.ysize));
290
2.56k
  }
291
42.8k
  if (frame_header_.flags & FrameHeader::kNoise) {
292
6.29k
    JXL_RETURN_IF_ERROR(DecodeNoise(br, &shared.image_features.noise_params));
293
6.29k
  }
294
42.8k
  JXL_RETURN_IF_ERROR(dec_state_->shared_storage.matrices.DecodeDC(br));
295
296
42.0k
  if (frame_header_.encoding == FrameEncoding::kVarDCT) {
297
12.2k
    JXL_RETURN_IF_ERROR(
298
12.2k
        jxl::DecodeGlobalDCInfo(br, decoded_->IsJPEG(), dec_state_, pool_));
299
12.2k
  }
300
  // Splines' draw cache uses the color correlation map.
301
41.9k
  if (frame_header_.flags & FrameHeader::kSplines) {
302
2.16k
    JXL_RETURN_IF_ERROR(shared.image_features.splines.InitializeDrawCache(
303
2.16k
        frame_dim_.xsize_upsampled, frame_dim_.ysize_upsampled,
304
2.16k
        dec_state_->shared->cmap.base()));
305
2.16k
  }
306
41.7k
  Status dec_status = modular_frame_decoder_.DecodeGlobalInfo(
307
41.7k
      br, frame_header_, /*allow_truncated_group=*/false);
308
41.7k
  if (dec_status.IsFatalError()) return dec_status;
309
39.6k
  if (dec_status) {
310
39.3k
    decoded_dc_global_ = true;
311
39.3k
  }
312
39.6k
  return dec_status;
313
41.7k
}
314
315
41.6k
Status FrameDecoder::ProcessDCGroup(size_t dc_group_id, BitReader* br) {
316
41.6k
  const size_t gx = dc_group_id % frame_dim_.xsize_dc_groups;
317
41.6k
  const size_t gy = dc_group_id / frame_dim_.xsize_dc_groups;
318
41.6k
  const LoopFilter& lf = frame_header_.loop_filter;
319
41.6k
  if (frame_header_.encoding == FrameEncoding::kVarDCT &&
320
41.6k
      !(frame_header_.flags & FrameHeader::kUseDcFrame)) {
321
12.1k
    JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeVarDCTDC(
322
12.1k
        frame_header_, dc_group_id, br, dec_state_));
323
12.1k
  }
324
41.1k
  const Rect mrect(gx * frame_dim_.dc_group_dim, gy * frame_dim_.dc_group_dim,
325
41.1k
                   frame_dim_.dc_group_dim, frame_dim_.dc_group_dim);
326
41.1k
  JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup(
327
41.1k
      frame_header_, mrect, br, 3, 1000,
328
41.1k
      ModularStreamId::ModularDC(dc_group_id),
329
41.1k
      /*zerofill=*/false, nullptr, nullptr,
330
41.1k
      /*allow_truncated=*/false));
331
41.1k
  if (frame_header_.encoding == FrameEncoding::kVarDCT) {
332
11.6k
    JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeAcMetadata(
333
11.6k
        frame_header_, dc_group_id, br, dec_state_));
334
29.5k
  } else if (lf.epf_iters > 0) {
335
11.8k
    FillImage(kInvSigmaNum / lf.epf_sigma_for_modular, &dec_state_->sigma);
336
11.8k
  }
337
40.2k
  decoded_dc_groups_[dc_group_id] = JXL_TRUE;
338
40.2k
  return true;
339
41.1k
}
340
341
37.8k
Status FrameDecoder::FinalizeDC() {
342
  // Do Adaptive DC smoothing if enabled. This *must* happen between all the
343
  // ProcessDCGroup and ProcessACGroup.
344
37.8k
  JxlMemoryManager* memory_manager = dec_state_->memory_manager();
345
37.8k
  if (frame_header_.encoding == FrameEncoding::kVarDCT &&
346
37.8k
      !(frame_header_.flags & FrameHeader::kSkipAdaptiveDCSmoothing) &&
347
37.8k
      !(frame_header_.flags & FrameHeader::kUseDcFrame)) {
348
9.97k
    JXL_RETURN_IF_ERROR(AdaptiveDCSmoothing(
349
9.97k
        memory_manager, dec_state_->shared->quantizer.MulDC(),
350
9.97k
        &dec_state_->shared_storage.dc_storage, pool_));
351
9.97k
  }
352
353
37.8k
  finalized_dc_ = true;
354
37.8k
  return true;
355
37.8k
}
356
357
37.8k
Status FrameDecoder::AllocateOutput() {
358
37.8k
  if (allocated_) return true;
359
37.8k
  modular_frame_decoder_.MaybeDropFullImage();
360
37.8k
  decoded_->origin = frame_header_.frame_origin;
361
37.8k
  JXL_RETURN_IF_ERROR(
362
37.8k
      dec_state_->InitForAC(frame_header_.passes.num_passes, nullptr));
363
37.8k
  allocated_ = true;
364
37.8k
  return true;
365
37.8k
}
366
367
37.8k
Status FrameDecoder::ProcessACGlobal(BitReader* br) {
368
37.8k
  JXL_ENSURE(finalized_dc_);
369
37.8k
  JxlMemoryManager* memory_manager = dec_state_->memory_manager();
370
371
  // Decode AC group.
372
37.8k
  if (frame_header_.encoding == FrameEncoding::kVarDCT) {
373
10.7k
    JXL_RETURN_IF_ERROR(dec_state_->shared_storage.matrices.Decode(
374
10.7k
        memory_manager, br, &modular_frame_decoder_));
375
10.4k
    JXL_RETURN_IF_ERROR(dec_state_->shared_storage.matrices.EnsureComputed(
376
10.4k
        memory_manager, dec_state_->used_acs));
377
378
10.3k
    size_t num_histo_bits =
379
10.3k
        CeilLog2Nonzero(dec_state_->shared->frame_dim.num_groups);
380
10.3k
    dec_state_->shared_storage.num_histograms =
381
10.3k
        1 + br->ReadBits(num_histo_bits);
382
383
10.3k
    JXL_DEBUG_V(3,
384
10.3k
                "Processing AC global with %d passes and %" PRIuS
385
10.3k
                " sets of histograms",
386
10.3k
                frame_header_.passes.num_passes,
387
10.3k
                dec_state_->shared_storage.num_histograms);
388
389
10.3k
    dec_state_->code.resize(kMaxNumPasses);
390
10.3k
    dec_state_->context_map.resize(kMaxNumPasses);
391
    // Read coefficient orders and histograms.
392
10.3k
    size_t max_num_bits_ac = 0;
393
20.6k
    for (size_t i = 0; i < frame_header_.passes.num_passes; i++) {
394
10.3k
      uint16_t used_orders = U32Coder::Read(kOrderEnc, br);
395
10.3k
      JXL_RETURN_IF_ERROR(DecodeCoeffOrders(
396
10.3k
          memory_manager, used_orders, dec_state_->used_acs,
397
10.3k
          &dec_state_->shared_storage
398
10.3k
               .coeff_orders[i * dec_state_->shared_storage.coeff_order_size],
399
10.3k
          br));
400
10.3k
      size_t num_contexts =
401
10.3k
          dec_state_->shared->num_histograms *
402
10.3k
          dec_state_->shared_storage.block_ctx_map.NumACContexts();
403
10.3k
      JXL_RETURN_IF_ERROR(DecodeHistograms(memory_manager, br, num_contexts,
404
10.3k
                                           &dec_state_->code[i],
405
10.3k
                                           &dec_state_->context_map[i]));
406
      // Add extra values to enable the cheat in hot loop of DecodeACVarBlock.
407
10.2k
      dec_state_->context_map[i].resize(
408
10.2k
          num_contexts + kZeroDensityContextLimit - kZeroDensityContextCount);
409
10.2k
      max_num_bits_ac =
410
10.2k
          std::max(max_num_bits_ac, dec_state_->code[i].max_num_bits);
411
10.2k
    }
412
10.2k
    max_num_bits_ac += CeilLog2Nonzero(frame_header_.passes.num_passes);
413
    // 16-bit buffer for decoding to JPEG are not implemented.
414
    // TODO(veluca): figure out the exact limit - 16 should still work with
415
    // 16-bit buffers, but we are excluding it for safety.
416
10.2k
    bool use_16_bit = max_num_bits_ac < 16 && !decoded_->IsJPEG();
417
10.2k
    bool store = frame_header_.passes.num_passes > 1;
418
10.2k
    size_t xs = store ? kGroupDim * kGroupDim : 0;
419
10.2k
    size_t ys = store ? frame_dim_.num_groups : 0;
420
10.2k
    if (use_16_bit) {
421
8.65k
      JXL_ASSIGN_OR_RETURN(dec_state_->coefficients,
422
8.65k
                           ACImageT<int16_t>::Make(memory_manager, xs, ys));
423
8.65k
    } else {
424
1.56k
      JXL_ASSIGN_OR_RETURN(dec_state_->coefficients,
425
1.56k
                           ACImageT<int32_t>::Make(memory_manager, xs, ys));
426
1.56k
    }
427
10.2k
    if (store) {
428
14
      dec_state_->coefficients->ZeroFill();
429
14
    }
430
10.2k
  }
431
432
  // Set JPEG decoding data.
433
37.3k
  if (decoded_->IsJPEG()) {
434
0
    decoded_->color_transform = frame_header_.color_transform;
435
0
    decoded_->chroma_subsampling = frame_header_.chroma_subsampling;
436
0
    const std::vector<QuantEncoding>& qe =
437
0
        dec_state_->shared_storage.matrices.encodings();
438
0
    if (qe.empty() || qe[0].mode != QuantEncoding::Mode::kQuantModeRAW ||
439
0
        std::abs(qe[0].qraw.qtable_den - 1.f / (8 * 255)) > 1e-8f) {
440
0
      return JXL_FAILURE(
441
0
          "Quantization table is not a JPEG quantization table.");
442
0
    }
443
0
    jpeg::JPEGData* jpeg_data = decoded_->jpeg_data.get();
444
0
    size_t num_components = jpeg_data->components.size();
445
0
    bool is_gray = (num_components == 1);
446
0
    JXL_ENSURE(frame_header_.color_transform != ColorTransform::kXYB);
447
0
    auto jpeg_c_map = JpegOrder(frame_header_.color_transform, is_gray);
448
0
    size_t qt_set = 0;
449
0
    JXL_ENSURE(num_components <= 3);
450
0
    JXL_ENSURE(qe[0].qraw.qtable->size() == 3 * 8 * 8);
451
0
    int* qtable = qe[0].qraw.qtable->data();
452
0
    for (size_t c = 0; c < num_components; c++) {
453
      // TODO(eustas): why 1-st quant table for gray?
454
0
      size_t quant_c = is_gray ? 1 : c;
455
0
      size_t qpos = jpeg_data->components[jpeg_c_map[c]].quant_idx;
456
0
      JXL_ENSURE(qpos != jpeg_data->quant.size());
457
0
      qt_set |= 1 << qpos;
458
0
      for (size_t x = 0; x < 8; x++) {
459
0
        for (size_t y = 0; y < 8; y++) {
460
0
          jpeg_data->quant[qpos].values[x * 8 + y] =
461
0
              qtable[quant_c * 64 + y * 8 + x];
462
0
        }
463
0
      }
464
0
    }
465
0
    for (size_t i = 0; i < jpeg_data->quant.size(); i++) {
466
0
      if (qt_set & (1 << i)) continue;
467
0
      if (i == 0) return JXL_FAILURE("First quant table unused.");
468
      // Unused quant table is set to copy of previous quant table
469
0
      for (size_t j = 0; j < 64; j++) {
470
0
        jpeg_data->quant[i].values[j] = jpeg_data->quant[i - 1].values[j];
471
0
      }
472
0
    }
473
0
  }
474
37.3k
  decoded_ac_global_ = true;
475
37.3k
  return true;
476
37.3k
}
477
478
Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
479
                                    BitReader* JXL_RESTRICT* br,
480
                                    size_t num_passes, size_t thread,
481
44.3k
                                    bool force_draw, bool dc_only) {
482
44.3k
  size_t group_dim = frame_dim_.group_dim;
483
44.3k
  const size_t gx = ac_group_id % frame_dim_.xsize_groups;
484
44.3k
  const size_t gy = ac_group_id / frame_dim_.xsize_groups;
485
44.3k
  const size_t x = gx * group_dim;
486
44.3k
  const size_t y = gy * group_dim;
487
44.3k
  JxlMemoryManager* memory_manager = dec_state_->memory_manager();
488
44.3k
  JXL_DEBUG_V(3,
489
44.3k
              "Processing AC group %" PRIuS "(%" PRIuS ",%" PRIuS
490
44.3k
              ") group_dim: %" PRIuS " decoded passes: %u new passes: %" PRIuS,
491
44.3k
              ac_group_id, gx, gy, group_dim,
492
44.3k
              decoded_passes_per_ac_group_[ac_group_id], num_passes);
493
494
44.3k
  RenderPipelineInput render_pipeline_input =
495
44.3k
      dec_state_->render_pipeline->GetInputBuffers(ac_group_id, thread);
496
497
44.3k
  bool should_run_pipeline = true;
498
499
44.3k
  if (frame_header_.encoding == FrameEncoding::kVarDCT) {
500
10.2k
    JXL_RETURN_IF_ERROR(group_dec_caches_[thread].InitOnce(
501
10.2k
        memory_manager, frame_header_.passes.num_passes, dec_state_->used_acs));
502
10.2k
    JXL_RETURN_IF_ERROR(DecodeGroup(
503
10.2k
        frame_header_, br, num_passes, ac_group_id, dec_state_,
504
10.2k
        &group_dec_caches_[thread], thread, render_pipeline_input,
505
10.2k
        decoded_->jpeg_data.get(), decoded_passes_per_ac_group_[ac_group_id],
506
10.2k
        force_draw, dc_only, &should_run_pipeline));
507
10.2k
  }
508
509
  // don't limit to image dimensions here (is done in DecodeGroup)
510
43.9k
  const Rect mrect(x, y, group_dim, group_dim);
511
43.9k
  bool modular_ready = false;
512
43.9k
  size_t pass0 = decoded_passes_per_ac_group_[ac_group_id];
513
43.9k
  size_t pass1 =
514
43.9k
      force_draw ? frame_header_.passes.num_passes : pass0 + num_passes;
515
88.3k
  for (size_t i = pass0; i < pass1; ++i) {
516
44.6k
    int minShift;
517
44.6k
    int maxShift;
518
44.6k
    frame_header_.passes.GetDownsamplingBracket(i, minShift, maxShift);
519
44.6k
    bool modular_pass_ready = true;
520
44.6k
    JXL_DEBUG_V(2, "Decoding modular in group %d pass %d",
521
44.6k
                static_cast<int>(ac_group_id), static_cast<int>(i));
522
44.6k
    if (i < pass0 + num_passes) {
523
44.6k
      JXL_DEBUG_V(2, "Bit reader position: %" PRIuS " / %" PRIuS,
524
44.6k
                  br[i - pass0]->TotalBitsConsumed(),
525
44.6k
                  br[i - pass0]->TotalBytes() * kBitsPerByte);
526
44.6k
      JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup(
527
44.6k
          frame_header_, mrect, br[i - pass0], minShift, maxShift,
528
44.6k
          ModularStreamId::ModularAC(ac_group_id, i),
529
44.6k
          /*zerofill=*/false, dec_state_, &render_pipeline_input,
530
44.6k
          /*allow_truncated=*/false, &modular_pass_ready));
531
18.4E
    } else {
532
18.4E
      JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup(
533
18.4E
          frame_header_, mrect, nullptr, minShift, maxShift,
534
18.4E
          ModularStreamId::ModularAC(ac_group_id, i), /*zerofill=*/true,
535
18.4E
          dec_state_, &render_pipeline_input,
536
18.4E
          /*allow_truncated=*/false, &modular_pass_ready));
537
18.4E
    }
538
44.3k
    if (modular_pass_ready) modular_ready = true;
539
44.3k
  }
540
43.7k
  decoded_passes_per_ac_group_[ac_group_id] += num_passes;
541
542
43.7k
  if ((frame_header_.flags & FrameHeader::kNoise) != 0) {
543
8.24k
    PrepareNoiseInput(*dec_state_, frame_dim_, frame_header_, ac_group_id,
544
8.24k
                      thread);
545
8.24k
  }
546
547
43.7k
  if (!modular_frame_decoder_.UsesFullImage() && !decoded_->IsJPEG()) {
548
15.0k
    if (should_run_pipeline && modular_ready) {
549
14.5k
      JXL_RETURN_IF_ERROR(render_pipeline_input.Done());
550
14.5k
    } else if (force_draw) {
551
0
      return JXL_FAILURE("Modular group decoding failed.");
552
0
    }
553
15.0k
  }
554
43.7k
  return true;
555
43.7k
}
556
557
void FrameDecoder::MarkSections(const SectionInfo* sections, size_t num,
558
38.7k
                                const SectionStatus* section_status) {
559
38.7k
  num_sections_done_ += num;
560
102k
  for (size_t i = 0; i < num; i++) {
561
64.0k
    if (section_status[i] != SectionStatus::kDone) {
562
10.9k
      processed_section_[sections[i].id] = JXL_FALSE;
563
10.9k
      num_sections_done_--;
564
10.9k
    }
565
64.0k
  }
566
38.7k
}
567
568
Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
569
46.0k
                                     SectionStatus* section_status) {
570
46.0k
  if (num == 0) return true;  // Nothing to process
571
44.7k
  std::fill(section_status, section_status + num, SectionStatus::kSkipped);
572
44.7k
  size_t dc_global_sec = num;
573
44.7k
  size_t ac_global_sec = num;
574
44.7k
  std::vector<size_t> dc_group_sec(frame_dim_.num_dc_groups, num);
575
44.7k
  std::vector<std::vector<size_t>> ac_group_sec(
576
44.7k
      frame_dim_.num_groups,
577
44.7k
      std::vector<size_t>(frame_header_.passes.num_passes, num));
578
  // This keeps track of the number of ac passes we want to process during this
579
  // call of ProcessSections.
580
44.7k
  std::vector<size_t> desired_num_ac_passes(frame_dim_.num_groups);
581
44.7k
  bool single_section =
582
44.7k
      frame_dim_.num_groups == 1 && frame_header_.passes.num_passes == 1;
583
44.7k
  if (single_section) {
584
40.4k
    JXL_ENSURE(num == 1);
585
40.4k
    JXL_ENSURE(sections[0].id == 0);
586
40.4k
    if (processed_section_[0] == JXL_FALSE) {
587
40.4k
      processed_section_[0] = JXL_TRUE;
588
40.4k
      ac_group_sec[0].resize(1);
589
40.4k
      dc_global_sec = ac_global_sec = dc_group_sec[0] = ac_group_sec[0][0] = 0;
590
40.4k
      desired_num_ac_passes[0] = 1;
591
40.4k
    } else {
592
0
      section_status[0] = SectionStatus::kDuplicate;
593
0
    }
594
40.4k
  } else {
595
4.30k
    size_t ac_global_index = frame_dim_.num_dc_groups + 1;
596
52.7k
    for (size_t i = 0; i < num; i++) {
597
48.4k
      JXL_ENSURE(sections[i].id < processed_section_.size());
598
48.4k
      if (processed_section_[sections[i].id]) {
599
0
        section_status[i] = SectionStatus::kDuplicate;
600
0
        continue;
601
0
      }
602
48.4k
      if (sections[i].id == 0) {
603
2.89k
        dc_global_sec = i;
604
45.5k
      } else if (sections[i].id < ac_global_index) {
605
15.9k
        dc_group_sec[sections[i].id - 1] = i;
606
29.5k
      } else if (sections[i].id == ac_global_index) {
607
3.29k
        ac_global_sec = i;
608
26.2k
      } else {
609
26.2k
        size_t ac_idx = sections[i].id - ac_global_index - 1;
610
26.2k
        size_t acg = ac_idx % frame_dim_.num_groups;
611
26.2k
        size_t acp = ac_idx / frame_dim_.num_groups;
612
26.2k
        if (acp >= frame_header_.passes.num_passes) {
613
0
          return JXL_FAILURE("Invalid section ID");
614
0
        }
615
26.2k
        ac_group_sec[acg][acp] = i;
616
26.2k
      }
617
48.4k
      processed_section_[sections[i].id] = JXL_TRUE;
618
48.4k
    }
619
    // Count number of new passes per group.
620
377k
    for (size_t g = 0; g < ac_group_sec.size(); g++) {
621
373k
      size_t j = 0;
622
399k
      for (; j + decoded_passes_per_ac_group_[g] <
623
399k
             frame_header_.passes.num_passes;
624
375k
           j++) {
625
375k
        if (ac_group_sec[g][j + decoded_passes_per_ac_group_[g]] == num) {
626
349k
          break;
627
349k
        }
628
375k
      }
629
373k
      desired_num_ac_passes[g] = j;
630
373k
    }
631
4.30k
  }
632
44.7k
  if (dc_global_sec != num) {
633
43.3k
    Status dc_global_status = ProcessDCGlobal(sections[dc_global_sec].br);
634
43.3k
    if (dc_global_status.IsFatalError()) return dc_global_status;
635
39.7k
    if (dc_global_status) {
636
39.3k
      section_status[dc_global_sec] = SectionStatus::kDone;
637
39.3k
    } else {
638
378
      section_status[dc_global_sec] = SectionStatus::kPartial;
639
378
    }
640
39.7k
  }
641
642
41.1k
  if (decoded_dc_global_) {
643
39.3k
    const auto process_section = [this, &dc_group_sec, &num, &sections,
644
39.3k
                                  &section_status](size_t i,
645
44.2k
                                                   size_t thread) -> Status {
646
44.2k
      if (dc_group_sec[i] != num) {
647
41.7k
        JXL_RETURN_IF_ERROR(ProcessDCGroup(i, sections[dc_group_sec[i]].br));
648
40.3k
        section_status[dc_group_sec[i]] = SectionStatus::kDone;
649
40.3k
      }
650
42.8k
      return true;
651
44.2k
    };
652
39.3k
    JXL_RETURN_IF_ERROR(RunOnPool(pool_, 0, dc_group_sec.size(),
653
39.3k
                                  ThreadPool::NoInit, process_section,
654
39.3k
                                  "DecodeDCGroup"));
655
39.3k
  }
656
657
39.7k
  if (!HasDcGroupToDecode() && !finalized_dc_) {
658
37.8k
    PassesDecoderState::PipelineOptions pipeline_options;
659
37.8k
    pipeline_options.use_slow_render_pipeline = use_slow_rendering_pipeline_;
660
37.8k
    pipeline_options.coalescing = coalescing_;
661
37.8k
    pipeline_options.render_spotcolors = render_spotcolors_;
662
37.8k
    pipeline_options.render_noise = true;
663
37.8k
    JXL_RETURN_IF_ERROR(dec_state_->PreparePipeline(
664
37.8k
        frame_header_, &frame_header_.nonserialized_metadata->m, decoded_,
665
37.8k
        pipeline_options));
666
37.8k
    JXL_RETURN_IF_ERROR(FinalizeDC());
667
37.8k
    JXL_RETURN_IF_ERROR(AllocateOutput());
668
37.8k
    if (progressive_detail_ >= JxlProgressiveDetail::kDC) {
669
0
      MarkSections(sections, num, section_status);
670
0
      return true;
671
0
    }
672
37.8k
  }
673
674
39.7k
  if (finalized_dc_ && ac_global_sec != num && !decoded_ac_global_) {
675
37.8k
    JXL_RETURN_IF_ERROR(ProcessACGlobal(sections[ac_global_sec].br));
676
37.3k
    section_status[ac_global_sec] = SectionStatus::kDone;
677
37.3k
  }
678
679
39.2k
  if (progressive_detail_ >= JxlProgressiveDetail::kLastPasses) {
680
    // Mark that we only want the next progression pass.
681
0
    size_t target_complete_passes = NextNumPassesToPause();
682
0
    for (size_t i = 0; i < ac_group_sec.size(); i++) {
683
0
      desired_num_ac_passes[i] =
684
0
          std::min(desired_num_ac_passes[i],
685
0
                   target_complete_passes - decoded_passes_per_ac_group_[i]);
686
0
    }
687
0
  }
688
689
39.2k
  if (decoded_ac_global_) {
690
    // Mark all the AC groups that we received as not complete yet.
691
101k
    for (size_t i = 0; i < ac_group_sec.size(); i++) {
692
63.6k
      if (desired_num_ac_passes[i] != 0) {
693
44.7k
        dec_state_->render_pipeline->ClearDone(i);
694
44.7k
      }
695
63.6k
    }
696
697
37.3k
    const auto prepare_storage = [this](size_t num_threads) -> Status {
698
37.3k
      JXL_RETURN_IF_ERROR(
699
37.3k
          PrepareStorage(num_threads, decoded_passes_per_ac_group_.size()));
700
37.3k
      return true;
701
37.3k
    };
702
37.3k
    const auto process_group = [this, &ac_group_sec, &desired_num_ac_passes,
703
37.3k
                                &num, &sections, &section_status](
704
57.5k
                                   size_t g, size_t thread) -> Status {
705
57.5k
      if (desired_num_ac_passes[g] == 0) {
706
        // no new AC pass, nothing to do
707
13.0k
        return true;
708
13.0k
      }
709
44.4k
      (void)num;
710
44.4k
      size_t first_pass = decoded_passes_per_ac_group_[g];
711
44.4k
      BitReader* JXL_RESTRICT readers[kMaxNumPasses];
712
89.8k
      for (size_t i = 0; i < desired_num_ac_passes[g]; i++) {
713
45.3k
        JXL_ENSURE(ac_group_sec[g][first_pass + i] != num);
714
45.3k
        readers[i] = sections[ac_group_sec[g][first_pass + i]].br;
715
45.3k
      }
716
44.4k
      JXL_RETURN_IF_ERROR(ProcessACGroup(
717
44.4k
          g, readers, desired_num_ac_passes[g], GetStorageLocation(thread, g),
718
44.4k
          /*force_draw=*/false, /*dc_only=*/false));
719
88.5k
      for (size_t i = 0; i < desired_num_ac_passes[g]; i++) {
720
44.6k
        section_status[ac_group_sec[g][first_pass + i]] = SectionStatus::kDone;
721
44.6k
      }
722
43.8k
      return true;
723
44.4k
    };
724
37.3k
    JXL_RETURN_IF_ERROR(RunOnPool(pool_, 0, ac_group_sec.size(),
725
37.3k
                                  prepare_storage, process_group,
726
37.3k
                                  "DecodeGroup"));
727
37.3k
  }
728
729
38.7k
  MarkSections(sections, num, section_status);
730
38.7k
  return true;
731
39.2k
}
732
733
0
Status FrameDecoder::Flush() {
734
0
  bool has_blending = frame_header_.blending_info.mode != BlendMode::kReplace ||
735
0
                      frame_header_.custom_size_or_origin;
736
0
  for (const auto& blending_info_ec :
737
0
       frame_header_.extra_channel_blending_info) {
738
0
    if (blending_info_ec.mode != BlendMode::kReplace) has_blending = true;
739
0
  }
740
  // No early Flush() if blending is enabled.
741
0
  if (has_blending && !is_finalized_) {
742
0
    return false;
743
0
  }
744
  // No early Flush() - nothing to do - if the frame is a kSkipProgressive
745
  // frame.
746
0
  if (frame_header_.frame_type == FrameType::kSkipProgressive &&
747
0
      !is_finalized_) {
748
0
    return true;
749
0
  }
750
0
  if (decoded_->IsJPEG()) {
751
    // Nothing to do.
752
0
    return true;
753
0
  }
754
0
  JXL_RETURN_IF_ERROR(AllocateOutput());
755
756
0
  uint32_t completely_decoded_ac_pass = *std::min_element(
757
0
      decoded_passes_per_ac_group_.begin(), decoded_passes_per_ac_group_.end());
758
0
  if (completely_decoded_ac_pass < frame_header_.passes.num_passes) {
759
    // We don't have all AC yet: force a draw of all the missing areas.
760
    // Mark all sections as not complete.
761
0
    for (size_t i = 0; i < decoded_passes_per_ac_group_.size(); i++) {
762
0
      if (decoded_passes_per_ac_group_[i] < frame_header_.passes.num_passes) {
763
0
        dec_state_->render_pipeline->ClearDone(i);
764
0
      }
765
0
    }
766
0
    const auto prepare_storage = [this](const size_t num_threads) -> Status {
767
0
      JXL_RETURN_IF_ERROR(
768
0
          PrepareStorage(num_threads, decoded_passes_per_ac_group_.size()));
769
0
      return true;
770
0
    };
771
0
    const auto process_group = [this](const uint32_t g,
772
0
                                      size_t thread) -> Status {
773
0
      if (decoded_passes_per_ac_group_[g] == frame_header_.passes.num_passes) {
774
        // This group was drawn already, nothing to do.
775
0
        return true;
776
0
      }
777
0
      BitReader* JXL_RESTRICT readers[kMaxNumPasses] = {};
778
0
      JXL_RETURN_IF_ERROR(ProcessACGroup(
779
0
          g, readers, /*num_passes=*/0, GetStorageLocation(thread, g),
780
0
          /*force_draw=*/true, /*dc_only=*/!decoded_ac_global_));
781
0
      return true;
782
0
    };
783
0
    JXL_RETURN_IF_ERROR(RunOnPool(pool_, 0, decoded_passes_per_ac_group_.size(),
784
0
                                  prepare_storage, process_group,
785
0
                                  "ForceDrawGroup"));
786
0
  }
787
788
  // undo global modular transforms and copy int pixel buffers to float ones
789
0
  JXL_RETURN_IF_ERROR(modular_frame_decoder_.FinalizeDecoding(
790
0
      frame_header_, dec_state_, pool_, is_finalized_));
791
792
0
  return true;
793
0
}
794
795
88.3k
int FrameDecoder::SavedAs(const FrameHeader& header) {
796
88.3k
  if (header.frame_type == FrameType::kDCFrame) {
797
    // bits 16, 32, 64, 128 for DC level
798
10.3k
    return 16 << (header.dc_level - 1);
799
77.9k
  } else if (header.CanBeReferenced()) {
800
    // bits 1, 2, 4 and 8 for the references
801
68.0k
    return 1 << header.save_as_reference;
802
68.0k
  }
803
804
9.92k
  return 0;
805
88.3k
}
806
807
36.2k
bool FrameDecoder::HasEverything() const {
808
36.2k
  if (!decoded_dc_global_) return false;
809
36.2k
  if (!decoded_ac_global_) return false;
810
36.2k
  if (HasDcGroupToDecode()) return false;
811
38.5k
  for (const auto& nb_passes : decoded_passes_per_ac_group_) {
812
38.5k
    if (nb_passes < frame_header_.passes.num_passes) return false;
813
38.5k
  }
814
36.2k
  return true;
815
36.2k
}
816
817
36.2k
int FrameDecoder::References() const {
818
36.2k
  if (is_finalized_) {
819
0
    return 0;
820
0
  }
821
36.2k
  if (!HasEverything()) return 0;
822
823
36.2k
  int result = 0;
824
825
  // Blending
826
36.2k
  if (frame_header_.frame_type == FrameType::kRegularFrame ||
827
36.2k
      frame_header_.frame_type == FrameType::kSkipProgressive) {
828
20.9k
    bool cropped = frame_header_.custom_size_or_origin;
829
20.9k
    if (cropped || frame_header_.blending_info.mode != BlendMode::kReplace) {
830
5.88k
      result |= (1 << frame_header_.blending_info.source);
831
5.88k
    }
832
20.9k
    const auto& extra = frame_header_.extra_channel_blending_info;
833
20.9k
    for (const auto& ecbi : extra) {
834
5.53k
      if (cropped || ecbi.mode != BlendMode::kReplace) {
835
3.32k
        result |= (1 << ecbi.source);
836
3.32k
      }
837
5.53k
    }
838
20.9k
  }
839
840
  // Patches
841
36.2k
  if (frame_header_.flags & FrameHeader::kPatches) {
842
2.16k
    result |= dec_state_->shared->image_features.patches.GetReferences();
843
2.16k
  }
844
845
  // DC Level
846
36.2k
  if (frame_header_.flags & FrameHeader::kUseDcFrame) {
847
    // Reads from the next dc level
848
19
    int dc_level = frame_header_.dc_level + 1;
849
    // bits 16, 32, 64, 128 for DC level
850
19
    result |= (16 << (dc_level - 1));
851
19
  }
852
853
36.2k
  return result;
854
36.2k
}
855
856
36.2k
Status FrameDecoder::FinalizeFrame() {
857
36.2k
  if (is_finalized_) {
858
0
    return JXL_FAILURE("FinalizeFrame called multiple times");
859
0
  }
860
36.2k
  is_finalized_ = true;
861
36.2k
  if (decoded_->IsJPEG()) {
862
    // Nothing to do.
863
0
    return true;
864
0
  }
865
866
  // undo global modular transforms and copy int pixel buffers to float ones
867
36.2k
  JXL_RETURN_IF_ERROR(
868
36.2k
      modular_frame_decoder_.FinalizeDecoding(frame_header_, dec_state_, pool_,
869
36.2k
                                              /*inplace=*/true));
870
871
36.2k
  if (frame_header_.CanBeReferenced()) {
872
24.3k
    auto& info = dec_state_->shared_storage
873
24.3k
                     .reference_frames[frame_header_.save_as_reference];
874
24.3k
    *info.frame = std::move(dec_state_->frame_storage_for_referencing);
875
24.3k
    info.ib_is_in_xyb = frame_header_.save_before_color_transform;
876
24.3k
  }
877
36.2k
  return true;
878
36.2k
}
879
880
}  // namespace jxl