Coverage Report

Created: 2026-02-14 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/dec_cache.cc
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
#include "lib/jxl/dec_cache.h"
7
8
#include <jxl/memory_manager.h>
9
10
#include <algorithm>
11
#include <cstddef>
12
#include <cstdint>
13
#include <utility>
14
15
#include "lib/jxl/ac_strategy.h"
16
#include "lib/jxl/base/bits.h"
17
#include "lib/jxl/base/data_parallel.h"
18
#include "lib/jxl/base/status.h"
19
#include "lib/jxl/blending.h"
20
#include "lib/jxl/coeff_order.h"
21
#include "lib/jxl/color_encoding_internal.h"
22
#include "lib/jxl/common.h"  // JXL_HIGH_PRECISION
23
#include "lib/jxl/frame_dimensions.h"
24
#include "lib/jxl/frame_header.h"
25
#include "lib/jxl/image.h"
26
#include "lib/jxl/image_bundle.h"
27
#include "lib/jxl/image_metadata.h"
28
#include "lib/jxl/loop_filter.h"
29
#include "lib/jxl/memory_manager_internal.h"
30
#include "lib/jxl/render_pipeline/render_pipeline.h"
31
#include "lib/jxl/render_pipeline/stage_blending.h"
32
#include "lib/jxl/render_pipeline/stage_chroma_upsampling.h"
33
#include "lib/jxl/render_pipeline/stage_cms.h"
34
#include "lib/jxl/render_pipeline/stage_epf.h"
35
#include "lib/jxl/render_pipeline/stage_from_linear.h"
36
#include "lib/jxl/render_pipeline/stage_gaborish.h"
37
#include "lib/jxl/render_pipeline/stage_noise.h"
38
#include "lib/jxl/render_pipeline/stage_patches.h"
39
#include "lib/jxl/render_pipeline/stage_splines.h"
40
#include "lib/jxl/render_pipeline/stage_spot.h"
41
#include "lib/jxl/render_pipeline/stage_to_linear.h"
42
#include "lib/jxl/render_pipeline/stage_tone_mapping.h"
43
#include "lib/jxl/render_pipeline/stage_upsampling.h"
44
#include "lib/jxl/render_pipeline/stage_write.h"
45
#include "lib/jxl/render_pipeline/stage_xyb.h"
46
#include "lib/jxl/render_pipeline/stage_ycbcr.h"
47
48
namespace jxl {
49
50
Status GroupDecCache::InitOnce(JxlMemoryManager* memory_manager,
51
5.10k
                               size_t num_passes, size_t used_acs) {
52
10.2k
  for (size_t i = 0; i < num_passes; i++) {
53
5.10k
    if (num_nzeroes[i].xsize() == 0) {
54
      // Allocate enough for a whole group - partial groups on the
55
      // right/bottom border just use a subset. The valid size is passed via
56
      // Rect.
57
58
4.88k
      JXL_ASSIGN_OR_RETURN(num_nzeroes[i],
59
4.88k
                           Image3I::Create(memory_manager, kGroupDimInBlocks,
60
4.88k
                                           kGroupDimInBlocks));
61
4.88k
    }
62
5.10k
  }
63
5.10k
  size_t max_block_area = 0;
64
65
142k
  for (uint8_t o = 0; o < AcStrategy::kNumValidStrategies; ++o) {
66
137k
    AcStrategy acs = AcStrategy::FromRawStrategy(o);
67
137k
    if ((used_acs & (1 << o)) == 0) continue;
68
13.1k
    size_t area =
69
13.1k
        acs.covered_blocks_x() * acs.covered_blocks_y() * kDCTBlockSize;
70
13.1k
    max_block_area = std::max(area, max_block_area);
71
13.1k
  }
72
73
5.10k
  if (max_block_area > max_block_area_) {
74
4.88k
    max_block_area_ = max_block_area;
75
    // We need 3x float blocks for dequantized coefficients and 1x for scratch
76
    // space for transforms.
77
4.88k
    JXL_ASSIGN_OR_RETURN(
78
4.88k
        float_memory_,
79
4.88k
        AlignedMemory::Create(memory_manager,
80
4.88k
                              max_block_area_ * 7 * sizeof(float)));
81
    // We need 3x int32 or int16 blocks for quantized coefficients.
82
4.88k
    JXL_ASSIGN_OR_RETURN(
83
4.88k
        int32_memory_,
84
4.88k
        AlignedMemory::Create(memory_manager,
85
4.88k
                              max_block_area_ * 3 * sizeof(int32_t)));
86
4.88k
    JXL_ASSIGN_OR_RETURN(
87
4.88k
        int16_memory_,
88
4.88k
        AlignedMemory::Create(memory_manager,
89
4.88k
                              max_block_area_ * 3 * sizeof(int16_t)));
90
4.88k
  }
91
92
5.10k
  dec_group_block = float_memory_.address<float>();
93
5.10k
  scratch_space = dec_group_block + max_block_area_ * 3;
94
5.10k
  dec_group_qblock = int32_memory_.address<int32_t>();
95
5.10k
  dec_group_qblock16 = int16_memory_.address<int16_t>();
96
5.10k
  return true;
97
5.10k
}
98
99
// Initialize the decoder state after all of DC is decoded.
100
9.04k
Status PassesDecoderState::InitForAC(size_t num_passes, ThreadPool* pool) {
101
9.04k
  shared_storage.coeff_order_size = 0;
102
253k
  for (uint8_t o = 0; o < AcStrategy::kNumValidStrategies; ++o) {
103
244k
    if (((1 << o) & used_acs) == 0) continue;
104
11.9k
    uint8_t ord = kStrategyOrder[o];
105
11.9k
    shared_storage.coeff_order_size =
106
11.9k
        std::max(kCoeffOrderOffset[3 * (ord + 1)] * kDCTBlockSize,
107
11.9k
                 shared_storage.coeff_order_size);
108
11.9k
  }
109
9.04k
  size_t sz = num_passes * shared_storage.coeff_order_size;
110
9.04k
  if (sz > shared_storage.coeff_orders.size()) {
111
39
    shared_storage.coeff_orders.resize(sz);
112
39
  }
113
9.04k
  return true;
114
9.04k
}
115
116
Status PassesDecoderState::PreparePipeline(const FrameHeader& frame_header,
117
                                           const ImageMetadata* metadata,
118
                                           ImageBundle* decoded,
119
9.04k
                                           PipelineOptions options) {
120
9.04k
  JxlMemoryManager* memory_manager = this->memory_manager();
121
9.04k
  size_t num_c = 3 + frame_header.nonserialized_metadata->m.num_extra_channels;
122
9.04k
  size_t num_tmp_c = options.render_noise ? 3 : 0;
123
124
9.04k
  if (frame_header.CanBeReferenced()) {
125
    // Necessary so that SetInputSizes() can allocate output buffers as needed.
126
7.12k
    frame_storage_for_referencing = ImageBundle(memory_manager, metadata);
127
7.12k
  }
128
129
9.04k
  RenderPipeline::Builder builder(memory_manager, num_c + num_tmp_c);
130
131
9.04k
  if (options.use_slow_render_pipeline) {
132
0
    builder.UseSimpleImplementation();
133
0
  }
134
135
9.04k
  if (!frame_header.chroma_subsampling.Is444()) {
136
6.77k
    for (size_t c = 0; c < 3; c++) {
137
5.08k
      if (frame_header.chroma_subsampling.HShift(c) != 0) {
138
2.53k
        JXL_RETURN_IF_ERROR(
139
2.53k
            builder.AddStage(GetChromaUpsamplingStage(c, /*horizontal=*/true)));
140
2.53k
      }
141
5.08k
      if (frame_header.chroma_subsampling.VShift(c) != 0) {
142
915
        JXL_RETURN_IF_ERROR(builder.AddStage(
143
915
            GetChromaUpsamplingStage(c, /*horizontal=*/false)));
144
915
      }
145
5.08k
    }
146
1.69k
  }
147
148
9.04k
  if (frame_header.loop_filter.gab) {
149
1.18k
    JXL_RETURN_IF_ERROR(
150
1.18k
        builder.AddStage(GetGaborishStage(frame_header.loop_filter)));
151
1.18k
  }
152
153
9.04k
  {
154
9.04k
    const LoopFilter& lf = frame_header.loop_filter;
155
9.04k
    if (lf.epf_iters >= 3) {
156
546
      JXL_RETURN_IF_ERROR(
157
546
          builder.AddStage(GetEPFStage(lf, sigma, EpfStage::Zero)));
158
546
    }
159
9.04k
    if (lf.epf_iters >= 1) {
160
6.45k
      JXL_RETURN_IF_ERROR(
161
6.45k
          builder.AddStage(GetEPFStage(lf, sigma, EpfStage::One)));
162
6.45k
    }
163
9.04k
    if (lf.epf_iters >= 2) {
164
6.45k
      JXL_RETURN_IF_ERROR(
165
6.45k
          builder.AddStage(GetEPFStage(lf, sigma, EpfStage::Two)));
166
6.45k
    }
167
9.04k
  }
168
169
9.04k
  bool late_ec_upsample = frame_header.upsampling != 1;
170
9.04k
  for (auto ecups : frame_header.extra_channel_upsampling) {
171
1.53k
    if (ecups != frame_header.upsampling) {
172
      // If patches are applied, either frame_header.upsampling == 1 or
173
      // late_ec_upsample is true.
174
172
      late_ec_upsample = false;
175
172
    }
176
1.53k
  }
177
178
9.04k
  if (!late_ec_upsample) {
179
3.22k
    for (size_t ec = 0; ec < frame_header.extra_channel_upsampling.size();
180
2.23k
         ec++) {
181
995
      if (frame_header.extra_channel_upsampling[ec] != 1) {
182
172
        JXL_RETURN_IF_ERROR(builder.AddStage(GetUpsamplingStage(
183
172
            memory_manager, frame_header.nonserialized_metadata->transform_data,
184
172
            3 + ec,
185
172
            CeilLog2Nonzero(frame_header.extra_channel_upsampling[ec]))));
186
172
      }
187
995
    }
188
2.23k
  }
189
190
9.04k
  if ((frame_header.flags & FrameHeader::kPatches) != 0) {
191
522
    JXL_RETURN_IF_ERROR(builder.AddStage(GetPatchesStage(
192
522
        &shared->image_features.patches,
193
522
        &frame_header.nonserialized_metadata->m.extra_channel_info)));
194
522
  }
195
9.04k
  if ((frame_header.flags & FrameHeader::kSplines) != 0) {
196
183
    JXL_RETURN_IF_ERROR(
197
183
        builder.AddStage(GetSplineStage(&shared->image_features.splines)));
198
183
  }
199
200
9.04k
  if (frame_header.upsampling != 1) {
201
6.81k
    size_t nb_channels =
202
6.81k
        3 +
203
6.81k
        (late_ec_upsample ? frame_header.extra_channel_upsampling.size() : 0);
204
27.7k
    for (size_t c = 0; c < nb_channels; c++) {
205
20.9k
      JXL_RETURN_IF_ERROR(builder.AddStage(GetUpsamplingStage(
206
20.9k
          memory_manager, frame_header.nonserialized_metadata->transform_data,
207
20.9k
          c, CeilLog2Nonzero(frame_header.upsampling))));
208
20.9k
    }
209
6.81k
  }
210
9.04k
  if (options.render_noise) {
211
1.19k
    JXL_RETURN_IF_ERROR(builder.AddStage(GetConvolveNoiseStage(num_c)));
212
1.19k
    JXL_RETURN_IF_ERROR(builder.AddStage(GetAddNoiseStage(
213
1.19k
        shared->image_features.noise_params, shared->cmap.base(), num_c)));
214
1.19k
  }
215
9.04k
  if (frame_header.dc_level != 0) {
216
982
    JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToImage3FStage(
217
982
        memory_manager, &shared_storage.dc_frames[frame_header.dc_level - 1])));
218
982
  }
219
220
9.04k
  if (frame_header.CanBeReferenced() &&
221
7.12k
      frame_header.save_before_color_transform) {
222
450
    JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToImageBundleStage(
223
450
        &frame_storage_for_referencing, output_encoding_info)));
224
450
  }
225
226
9.04k
  bool has_alpha = false;
227
9.04k
  size_t alpha_c = 0;
228
9.59k
  for (size_t i = 0; i < metadata->extra_channel_info.size(); i++) {
229
1.02k
    if (metadata->extra_channel_info[i].type == ExtraChannel::kAlpha) {
230
484
      has_alpha = true;
231
484
      alpha_c = 3 + i;
232
484
      break;
233
484
    }
234
1.02k
  }
235
236
9.04k
  if (fast_xyb_srgb8_conversion) {
237
#if !JXL_HIGH_PRECISION
238
    JXL_ENSURE(!NeedsBlending(frame_header));
239
    JXL_ENSURE(!frame_header.CanBeReferenced() ||
240
               frame_header.save_before_color_transform);
241
    JXL_ENSURE(!options.render_spotcolors ||
242
               !metadata->Find(ExtraChannel::kSpotColor));
243
    bool is_rgba = (main_output.format.num_channels == 4);
244
    uint8_t* rgb_output = reinterpret_cast<uint8_t*>(main_output.buffer);
245
    JXL_RETURN_IF_ERROR(builder.AddStage(
246
        GetFastXYBTosRGB8Stage(rgb_output, main_output.stride, width, height,
247
                               is_rgba, has_alpha, alpha_c)));
248
#endif
249
9.04k
  } else {
250
9.04k
    bool linear = false;
251
9.04k
    if (frame_header.color_transform == ColorTransform::kYCbCr) {
252
1.73k
      JXL_RETURN_IF_ERROR(builder.AddStage(GetYCbCrStage()));
253
7.31k
    } else if (frame_header.color_transform == ColorTransform::kXYB) {
254
1.43k
      JXL_RETURN_IF_ERROR(builder.AddStage(GetXYBStage(output_encoding_info)));
255
1.43k
      if (output_encoding_info.color_encoding.GetColorSpace() !=
256
1.43k
          ColorSpace::kXYB) {
257
1.43k
        linear = true;
258
1.43k
      }
259
1.43k
    }  // Nothing to do for kNone.
260
261
9.04k
    if (options.coalescing && NeedsBlending(frame_header)) {
262
1.69k
      if (linear) {
263
938
        JXL_RETURN_IF_ERROR(
264
938
            builder.AddStage(GetFromLinearStage(output_encoding_info)));
265
938
        linear = false;
266
938
      }
267
1.69k
      JXL_RETURN_IF_ERROR(builder.AddStage(GetBlendingStage(
268
1.69k
          frame_header, this, output_encoding_info.color_encoding)));
269
1.69k
    }
270
271
9.04k
    if (options.coalescing && frame_header.CanBeReferenced() &&
272
7.12k
        !frame_header.save_before_color_transform) {
273
6.67k
      if (linear) {
274
292
        JXL_RETURN_IF_ERROR(
275
292
            builder.AddStage(GetFromLinearStage(output_encoding_info)));
276
292
        linear = false;
277
292
      }
278
6.67k
      JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToImageBundleStage(
279
6.67k
          &frame_storage_for_referencing, output_encoding_info)));
280
6.67k
    }
281
282
9.04k
    if (options.render_spotcolors &&
283
9.04k
        frame_header.nonserialized_metadata->m.Find(ExtraChannel::kSpotColor)) {
284
4
      for (size_t i = 0; i < metadata->extra_channel_info.size(); i++) {
285
        // Don't use Find() because there may be multiple spot color channels.
286
3
        const ExtraChannelInfo& eci = metadata->extra_channel_info[i];
287
3
        if (eci.type == ExtraChannel::kSpotColor) {
288
1
          JXL_RETURN_IF_ERROR(
289
1
              builder.AddStage(GetSpotColorStage(i, eci.spot_color)));
290
1
        }
291
3
      }
292
1
    }
293
294
9.04k
    auto tone_mapping_stage = GetToneMappingStage(output_encoding_info);
295
9.04k
    if (tone_mapping_stage) {
296
0
      if (!linear) {
297
0
        auto to_linear_stage = GetToLinearStage(output_encoding_info);
298
0
        if (!to_linear_stage) {
299
0
          if (!output_encoding_info.cms_set) {
300
0
            return JXL_FAILURE("Cannot tonemap this colorspace without a CMS");
301
0
          }
302
0
          auto cms_stage = GetCmsStage(output_encoding_info);
303
0
          if (cms_stage) {
304
0
            JXL_RETURN_IF_ERROR(builder.AddStage(std::move(cms_stage)));
305
0
          }
306
0
        } else {
307
0
          JXL_RETURN_IF_ERROR(builder.AddStage(std::move(to_linear_stage)));
308
0
        }
309
0
        linear = true;
310
0
      }
311
0
      JXL_RETURN_IF_ERROR(builder.AddStage(std::move(tone_mapping_stage)));
312
0
    }
313
314
9.04k
    if (linear) {
315
206
      const size_t channels_src =
316
206
          (output_encoding_info.orig_color_encoding.IsCMYK()
317
206
               ? 4
318
206
               : output_encoding_info.orig_color_encoding.Channels());
319
206
      const size_t channels_dst =
320
206
          output_encoding_info.color_encoding.Channels();
321
206
      bool mixing_color_and_grey = (channels_dst != channels_src);
322
206
      if ((output_encoding_info.color_encoding_is_original) ||
323
134
          (!output_encoding_info.cms_set) || mixing_color_and_grey) {
324
        // in those cases we only need a linear stage in other cases we attempt
325
        // to obtain a cms stage: the cases are
326
        // - output_encoding_info.color_encoding_is_original: no cms stage
327
        // needed because it would be a no-op
328
        // - !output_encoding_info.cms_set: can't use the cms, so no point in
329
        // trying to add a cms stage
330
        // - mixing_color_and_grey: cms stage can't handle that
331
        // TODO(firsching): remove "mixing_color_and_grey" condition after
332
        // adding support for greyscale to cms stage.
333
72
        JXL_RETURN_IF_ERROR(
334
72
            builder.AddStage(GetFromLinearStage(output_encoding_info)));
335
134
      } else {
336
134
        if (!output_encoding_info.linear_color_encoding.CreateICC()) {
337
0
          return JXL_FAILURE("Failed to create ICC");
338
0
        }
339
134
        auto cms_stage = GetCmsStage(output_encoding_info);
340
134
        if (cms_stage) {
341
134
          JXL_RETURN_IF_ERROR(builder.AddStage(std::move(cms_stage)));
342
134
        }
343
134
      }
344
206
      linear = false;
345
8.83k
    } else {
346
8.83k
      auto cms_stage = GetCmsStage(output_encoding_info, false);
347
8.83k
      if (cms_stage) {
348
129
        JXL_RETURN_IF_ERROR(builder.AddStage(std::move(cms_stage)));
349
129
      }
350
8.83k
    }
351
9.04k
    (void)linear;
352
353
9.04k
    if (main_output.callback.IsPresent() || main_output.buffer) {
354
939
      JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToOutputStage(
355
939
          main_output, width, height, has_alpha, unpremul_alpha, alpha_c,
356
939
          undo_orientation, extra_output, memory_manager)));
357
8.10k
    } else {
358
8.10k
      JXL_RETURN_IF_ERROR(builder.AddStage(
359
8.10k
          GetWriteToImageBundleStage(decoded, output_encoding_info)));
360
8.10k
    }
361
9.04k
  }
362
9.04k
  JXL_ASSIGN_OR_RETURN(render_pipeline,
363
9.04k
                       std::move(builder).Finalize(shared->frame_dim));
364
9.04k
  return render_pipeline->IsInitialized();
365
9.04k
}
366
367
}  // namespace jxl