Coverage Report

Created: 2026-06-30 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/dec_frame.h
Line
Count
Source
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
#ifndef LIB_JXL_DEC_FRAME_H_
7
#define LIB_JXL_DEC_FRAME_H_
8
9
#include <jxl/decode.h>
10
#include <jxl/types.h>
11
12
#include <algorithm>
13
#include <array>
14
#include <cstddef>
15
#include <cstdint>
16
#include <limits>
17
#include <utility>
18
#include <vector>
19
20
#include "lib/jxl/base/common.h"
21
#include "lib/jxl/base/compiler_specific.h"
22
#include "lib/jxl/base/data_parallel.h"
23
#include "lib/jxl/base/status.h"
24
#include "lib/jxl/common.h"  // JXL_HIGH_PRECISION
25
#include "lib/jxl/dec_bit_reader.h"
26
#include "lib/jxl/dec_cache.h"
27
#include "lib/jxl/dec_modular.h"
28
#include "lib/jxl/frame_dimensions.h"
29
#include "lib/jxl/frame_header.h"
30
#include "lib/jxl/image_bundle.h"
31
#include "lib/jxl/image_metadata.h"
32
33
namespace jxl {
34
35
// Decodes a frame. Groups may be processed in parallel by `pool`.
36
// `metadata` is the metadata that applies to all frames of the codestream
37
// `decoded->metadata` must already be set and must match metadata.m.
38
// Used in the encoder to model decoder behaviour, and in tests.
39
Status DecodeFrame(PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool,
40
                   const uint8_t* next_in, size_t avail_in,
41
                   FrameHeader* frame_header, ImageBundle* decoded,
42
                   const CodecMetadata& metadata,
43
                   bool use_slow_rendering_pipeline = false);
44
45
// TODO(veluca): implement "forced drawing".
46
class FrameDecoder {
47
 public:
48
  // All parameters must outlive the FrameDecoder.
49
  FrameDecoder(PassesDecoderState* dec_state, const CodecMetadata& metadata,
50
               ThreadPool* pool, bool use_slow_rendering_pipeline)
51
57.2k
      : dec_state_(dec_state),
52
57.2k
        pool_(pool),
53
57.2k
        frame_header_(&metadata),
54
57.2k
        modular_frame_decoder_(dec_state_->memory_manager()),
55
57.2k
        use_slow_rendering_pipeline_(use_slow_rendering_pipeline) {}
56
57
47.5k
  void SetRenderSpotcolors(bool rsc) { render_spotcolors_ = rsc; }
58
47.5k
  void SetCoalescing(bool c) { coalescing_ = c; }
59
60
  // Read FrameHeader and table of contents from the given BitReader.
61
  Status InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
62
                   bool is_preview);
63
64
  // Checks frame dimensions for their limits, and sets the output
65
  // image buffer.
66
  Status InitFrameOutput();
67
68
  struct SectionInfo {
69
    BitReader* JXL_RESTRICT br;
70
    // Logical index of the section, regardless of any permutation that may be
71
    // applied in the table of contents or of the physical position in the file.
72
    size_t id;
73
    // Index of the section in the order of the bytes inside the frame.
74
    size_t index;
75
  };
76
77
  struct TocEntry {
78
    size_t size;
79
    size_t id;
80
  };
81
82
  enum SectionStatus {
83
    // Processed correctly.
84
    kDone = 0,
85
    // Skipped because other required sections were not yet processed.
86
    kSkipped = 1,
87
    // Skipped because the section was already processed.
88
    kDuplicate = 2,
89
    // Only partially decoded: the section will need to be processed again.
90
    kPartial = 3,
91
  };
92
93
  // Processes `num` sections; each SectionInfo contains the index
94
  // of the section and a BitReader that only contains the data of the section.
95
  // `section_status` should point to `num` elements, and will be filled with
96
  // information about whether each section was processed or not.
97
  // A section is a part of the encoded file that is indexed by the TOC.
98
  Status ProcessSections(const SectionInfo* sections, size_t num,
99
                         SectionStatus* section_status);
100
101
  // Flushes all the data decoded so far to pixels.
102
  Status Flush();
103
104
  // Runs final operations once a frame data is decoded.
105
  // Must be called exactly once per frame, after all calls to ProcessSections.
106
  Status FinalizeFrame();
107
108
  // Returns dependencies of this frame on reference ids as a bit mask: bits 0-3
109
  // indicate reference frame 0-3 for patches and blending, bits 4-7 indicate DC
110
  // frames this frame depends on. Only returns a valid result after all calls
111
  // to ProcessSections are finished and before FinalizeFrame.
112
  int References() const;
113
114
  // Returns reference id of storage location where this frame is stored as a
115
  // bit flag, or 0 if not stored.
116
  // Matches the bit mask used for GetReferences: bits 0-3 indicate it is stored
117
  // for patching or blending, bits 4-7 indicate DC frame.
118
  // Unlike References, can be ran at any time as
119
  // soon as the frame header is known.
120
  static int SavedAs(const FrameHeader& header);
121
122
47.6k
  uint64_t SumSectionSizes() const { return section_sizes_sum_; }
123
90.3k
  const std::vector<TocEntry>& Toc() const { return toc_; }
124
125
47.6k
  const FrameHeader& GetFrameHeader() const { return frame_header_; }
126
127
  // Returns whether a DC image has been decoded, accessible at low resolution
128
  // at passes.shared_storage.dc_storage
129
1.36k
  bool HasDecodedDC() const { return finalized_dc_; }
130
34.5k
  bool HasDecodedAll() const { return toc_.size() == num_sections_done_; }
131
132
75.9k
  size_t NumCompletePasses() const {
133
75.9k
    return *std::min_element(decoded_passes_per_ac_group_.begin(),
134
75.9k
                             decoded_passes_per_ac_group_.end());
135
75.9k
  }
136
137
  // If enabled, ProcessSections will stop and return true when the DC
138
  // sections have been processed, instead of starting the AC sections. This
139
  // will only occur if supported (that is, flushing will produce a valid
140
  // 1/8th*1/8th resolution image). The return value of true then does not mean
141
  // all sections have been processed, use HasDecodedDC and HasDecodedAll
142
  // to check the true finished state.
143
  // Returns the progressive detail that will be effective for the frame.
144
0
  JxlProgressiveDetail SetPauseAtProgressive(JxlProgressiveDetail prog_detail) {
145
0
    bool single_section =
146
0
        frame_dim_.num_groups == 1 && frame_header_.passes.num_passes == 1;
147
0
    if (frame_header_.frame_type != kSkipProgressive &&
148
        // If there's only one group and one pass, there is no separate section
149
        // for DC and the entire full resolution image is available at once.
150
0
        !single_section &&
151
        // If extra channels are encoded with modular without squeeze, they
152
        // don't support DC. If the are encoded with squeeze, DC works in theory
153
        // but the implementation may not yet correctly support this for Flush.
154
        // Therefore, can't correctly pause for a progressive step if there is
155
        // an extra channel (including alpha channel)
156
        // TODO(firsching): Check if this is still the case.
157
0
        decoded_->metadata()->extra_channel_info.empty() &&
158
        // DC is not guaranteed to be available in modular mode and may be a
159
        // black image. If squeeze is used, it may be available depending on the
160
        // current implementation.
161
        // TODO(lode): do return DC if it's known that flushing at this point
162
        // will produce a valid 1/8th downscaled image with modular encoding.
163
0
        frame_header_.encoding == FrameEncoding::kVarDCT) {
164
0
      progressive_detail_ = prog_detail;
165
0
    } else {
166
0
      progressive_detail_ = JxlProgressiveDetail::kFrames;
167
0
    }
168
0
    if (progressive_detail_ >= JxlProgressiveDetail::kPasses) {
169
0
      for (size_t i = 1; i < frame_header_.passes.num_passes; ++i) {
170
0
        passes_to_pause_.push_back(i);
171
0
      }
172
0
    } else if (progressive_detail_ >= JxlProgressiveDetail::kLastPasses) {
173
0
      for (size_t i = 0; i < frame_header_.passes.num_downsample; ++i) {
174
0
        passes_to_pause_.push_back(frame_header_.passes.last_pass[i] + 1);
175
0
      }
176
      // The format does not guarantee that these values are sorted.
177
0
      std::sort(passes_to_pause_.begin(), passes_to_pause_.end());
178
0
    }
179
0
    return progressive_detail_;
180
0
  }
181
182
41.3k
  size_t NextNumPassesToPause() const {
183
41.3k
    auto it = std::upper_bound(passes_to_pause_.begin(), passes_to_pause_.end(),
184
41.3k
                               static_cast<int>(NumCompletePasses()));
185
41.3k
    return (it != passes_to_pause_.end() ? *it
186
41.3k
                                         : std::numeric_limits<size_t>::max());
187
41.3k
  }
188
189
  // Sets the pixel callback or image buffer where the pixels will be decoded.
190
  //
191
  // @param undo_orientation: if true, indicates the frame decoder should apply
192
  // the exif orientation to bring the image to the intended display
193
  // orientation.
194
  Status SetImageOutput(const PixelCallback& pixel_callback, void* image_buffer,
195
                        size_t image_buffer_size, size_t xsize, size_t ysize,
196
                        JxlPixelFormat format, size_t bits_per_sample,
197
7.06k
                        bool unpremul_alpha, bool undo_orientation) const {
198
7.06k
    dec_state_->width = xsize;
199
7.06k
    dec_state_->height = ysize;
200
7.06k
    dec_state_->main_output.format = format;
201
7.06k
    dec_state_->main_output.bits_per_sample = bits_per_sample;
202
7.06k
    dec_state_->main_output.callback = pixel_callback;
203
7.06k
    dec_state_->main_output.buffer = image_buffer;
204
7.06k
    dec_state_->main_output.buffer_size = image_buffer_size;
205
7.06k
    JXL_ASSIGN_OR_RETURN(dec_state_->main_output.stride,
206
7.06k
                         GetStride(xsize, format));
207
7.06k
    const jxl::ExtraChannelInfo* alpha =
208
7.06k
        decoded_->metadata()->Find(jxl::ExtraChannel::kAlpha);
209
7.06k
    if (alpha && alpha->alpha_associated && unpremul_alpha) {
210
152
      dec_state_->unpremul_alpha = true;
211
152
    }
212
7.06k
    if (undo_orientation) {
213
0
      dec_state_->undo_orientation = decoded_->metadata()->GetOrientation();
214
0
      if (static_cast<int>(dec_state_->undo_orientation) > 4) {
215
0
        std::swap(dec_state_->width, dec_state_->height);
216
0
      }
217
0
    }
218
7.06k
    dec_state_->extra_output.clear();
219
#if !JXL_HIGH_PRECISION
220
    if (dec_state_->main_output.buffer &&
221
        (format.data_type == JXL_TYPE_UINT8) && (format.num_channels >= 3) &&
222
        !dec_state_->unpremul_alpha &&
223
        (dec_state_->undo_orientation == Orientation::kIdentity) &&
224
        decoded_->metadata()->xyb_encoded &&
225
        dec_state_->output_encoding_info.color_encoding.IsSRGB() &&
226
        dec_state_->output_encoding_info.all_default_opsin &&
227
        (dec_state_->output_encoding_info.desired_intensity_target ==
228
         dec_state_->output_encoding_info.orig_intensity_target) &&
229
        HasFastXYBTosRGB8() && frame_header_.needs_color_transform()) {
230
      dec_state_->fast_xyb_srgb8_conversion = true;
231
    }
232
#endif
233
7.06k
    return true;
234
7.06k
  }
235
236
  Status AddExtraChannelOutput(void* buffer, size_t buffer_size, size_t xsize,
237
0
                               JxlPixelFormat format, size_t bits_per_sample) {
238
0
    ImageOutput out;
239
0
    out.format = format;
240
0
    out.bits_per_sample = bits_per_sample;
241
0
    out.buffer = buffer;
242
0
    out.buffer_size = buffer_size;
243
0
    JXL_ASSIGN_OR_RETURN(out.stride, GetStride(xsize, format));
244
0
    dec_state_->extra_output.push_back(out);
245
0
    return true;
246
0
  }
247
248
 private:
249
  using PassesReaders = std::array<BitReader*, kMaxNumPasses>;
250
251
  Status ProcessDCGlobal(BitReader* br);
252
  Status ProcessDCGroup(size_t dc_group_id, BitReader* br);
253
  Status FinalizeDC();
254
  Status AllocateOutput();
255
  Status ProcessACGlobal(BitReader* br);
256
  Status ProcessACGroup(size_t ac_group_id, PassesReaders& br,
257
                        size_t num_passes, size_t thread, bool force_draw,
258
                        bool dc_only);
259
  void MarkSections(const SectionInfo* sections, size_t num,
260
                    const SectionStatus* section_status);
261
262
  // Allocates storage for parallel decoding using up to `num_threads` threads
263
  // of up to `num_tasks` tasks. The value of `thread` passed to
264
  // `GetStorageLocation` must be smaller than the `num_threads` value passed
265
  // here. The value of `task` passed to `GetStorageLocation` must be smaller
266
  // than the value of `num_tasks` passed here.
267
34.8k
  Status PrepareStorage(size_t num_threads, size_t num_tasks) {
268
34.8k
    size_t storage_size = std::min(num_threads, num_tasks);
269
34.8k
    if (storage_size > group_dec_caches_.size()) {
270
34.8k
      group_dec_caches_.resize(storage_size);
271
34.8k
    }
272
34.8k
    use_task_id_ = num_threads > num_tasks;
273
34.8k
    bool use_noise = (frame_header_.flags & FrameHeader::kNoise) != 0;
274
34.8k
    bool use_group_ids =
275
34.8k
        (modular_frame_decoder_.UsesFullImage() &&
276
29.9k
         (frame_header_.encoding == FrameEncoding::kVarDCT || use_noise));
277
34.8k
    if (dec_state_->render_pipeline) {
278
34.8k
      JXL_RETURN_IF_ERROR(dec_state_->render_pipeline->PrepareForThreads(
279
34.8k
          storage_size, use_group_ids));
280
34.8k
    }
281
34.8k
    JXL_RETURN_IF_ERROR(
282
34.8k
        dec_state_->upsampler8x->PrepareForThreads(num_threads));
283
34.8k
    return true;
284
34.8k
  }
285
286
37.4k
  size_t GetStorageLocation(size_t thread, size_t task) const {
287
37.4k
    if (use_task_id_) return task;
288
37.4k
    return thread;
289
37.4k
  }
290
291
7.06k
  static size_t BytesPerChannel(JxlDataType data_type) {
292
7.06k
    return (data_type == JXL_TYPE_UINT8   ? 1u
293
7.06k
            : data_type == JXL_TYPE_FLOAT ? 4u
294
2.03k
                                          : 2u);
295
7.06k
  }
296
297
7.06k
  static StatusOr<size_t> GetStride(const size_t xsize, JxlPixelFormat format) {
298
7.06k
    size_t x_stride;
299
7.06k
    if (!SafeMul(BytesPerChannel(format.data_type), format.num_channels,
300
7.06k
                 x_stride)) {
301
0
      return JXL_FAILURE("Image too large");
302
0
    }
303
7.06k
    size_t y_stride;
304
7.06k
    if (!SafeMul(x_stride, xsize, y_stride)) {
305
0
      return JXL_FAILURE("Image too large");
306
0
    }
307
7.06k
    if (!SafeRoundUpTo(y_stride, format.align, y_stride)) {
308
0
      return JXL_FAILURE("Image too large");
309
0
    }
310
7.06k
    return y_stride;
311
7.06k
  }
312
313
69.1k
  bool HasDcGroupToDecode() const {
314
69.1k
    return std::any_of(decoded_dc_groups_.cbegin(), decoded_dc_groups_.cend(),
315
69.3k
                       [](uint8_t ready) { return ready == 0; });
316
69.1k
  }
317
318
  PassesDecoderState* dec_state_;
319
  ThreadPool* pool_;
320
  std::vector<TocEntry> toc_;
321
  uint64_t section_sizes_sum_;
322
  // TODO(veluca): figure out the duplication between these and dec_state_.
323
  FrameHeader frame_header_;
324
  FrameDimensions frame_dim_;
325
  ImageBundle* decoded_;
326
  ModularFrameDecoder modular_frame_decoder_;
327
  bool render_spotcolors_ = true;
328
  bool coalescing_ = true;
329
330
  std::vector<uint8_t> processed_section_;
331
  std::vector<uint8_t> decoded_passes_per_ac_group_;
332
  std::vector<uint8_t> decoded_dc_groups_;
333
  bool decoded_dc_global_;
334
  bool decoded_ac_global_;
335
  bool HasEverything() const;
336
  bool finalized_dc_ = true;
337
  size_t num_sections_done_ = 0;
338
  bool is_finalized_ = true;
339
  bool allocated_ = false;
340
341
  std::vector<GroupDecCache> group_dec_caches_;
342
343
  // Whether or not the task id should be used for storage indexing, instead of
344
  // the thread id.
345
  bool use_task_id_ = false;
346
347
  // Testing setting: whether or not to use the slow rendering pipeline.
348
  bool use_slow_rendering_pipeline_;
349
350
  JxlProgressiveDetail progressive_detail_ = kFrames;
351
  // Number of completed passes where section decoding should pause.
352
  // Used for progressive details at least kLastPasses.
353
  std::vector<int> passes_to_pause_;
354
};
355
356
}  // namespace jxl
357
358
#endif  // LIB_JXL_DEC_FRAME_H_