Coverage Report

Created: 2026-11-12 07:35

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