Coverage Report

Created: 2026-07-25 07:03

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
5.26k
                               size_t num_passes, size_t used_acs) {
53
10.5k
  for (size_t i = 0; i < num_passes; i++) {
54
5.27k
    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.02k
      JXL_ASSIGN_OR_RETURN(num_nzeroes[i],
60
5.02k
                           Image3I::Create(memory_manager, kGroupDimInBlocks,
61
5.02k
                                           kGroupDimInBlocks));
62
5.02k
    }
63
5.27k
  }
64
5.26k
  size_t max_block_area = 0;
65
66
147k
  for (uint8_t o = 0; o < AcStrategy::kNumValidStrategies; ++o) {
67
142k
    AcStrategy acs = AcStrategy::FromRawStrategy(o);
68
142k
    if ((used_acs & (1 << o)) == 0) continue;
69
13.3k
    size_t area =
70
13.3k
        acs.covered_blocks_x() * acs.covered_blocks_y() * kDCTBlockSize;
71
13.3k
    max_block_area = std::max(area, max_block_area);
72
13.3k
  }
73
74
5.26k
  if (max_block_area > max_block_area_) {
75
5.01k
    max_block_area_ = max_block_area;
76
    // We need 3x float blocks for dequantized coefficients and 1x for scratch
77
    // space for transforms.
78
5.01k
    JXL_ASSIGN_OR_RETURN(
79
5.01k
        float_memory_,
80
5.01k
        AlignedMemory::Create(memory_manager,
81
5.01k
                              max_block_area_ * 7 * sizeof(float)));
82
    // We need 3x int32 or int16 blocks for quantized coefficients.
83
5.01k
    JXL_ASSIGN_OR_RETURN(
84
5.01k
        int32_memory_,
85
5.01k
        AlignedMemory::Create(memory_manager,
86
5.01k
                              max_block_area_ * 3 * sizeof(int32_t)));
87
5.01k
    JXL_ASSIGN_OR_RETURN(
88
5.01k
        int16_memory_,
89
5.01k
        AlignedMemory::Create(memory_manager,
90
5.01k
                              max_block_area_ * 3 * sizeof(int16_t)));
91
5.01k
  }
92
93
5.26k
  dec_group_block = float_memory_.address<float>();
94
5.26k
  scratch_space = dec_group_block + max_block_area_ * 3;
95
5.26k
  dec_group_qblock = int32_memory_.address<int32_t>();
96
5.26k
  dec_group_qblock16 = int16_memory_.address<int16_t>();
97
5.26k
  return true;
98
5.26k
}
99
100
// Initialize the decoder state after all of DC is decoded.
101
13.7k
Status PassesDecoderState::InitForAC(size_t num_passes, ThreadPool* pool) {
102
13.7k
  shared_storage.coeff_order_size = 0;
103
384k
  for (uint8_t o = 0; o < AcStrategy::kNumValidStrategies; ++o) {
104
370k
    if (((1 << o) & used_acs) == 0) continue;
105
12.0k
    uint8_t ord = kStrategyOrder[o];
106
12.0k
    shared_storage.coeff_order_size =
107
12.0k
        std::max(kCoeffOrderOffset[3 * (ord + 1)] * kDCTBlockSize,
108
12.0k
                 shared_storage.coeff_order_size);
109
12.0k
  }
110
13.7k
  size_t sz = num_passes * shared_storage.coeff_order_size;
111
13.7k
  if (sz > shared_storage.coeff_orders.size()) {
112
55
    shared_storage.coeff_orders.resize(sz);
113
55
  }
114
13.7k
  return true;
115
13.7k
}
116
117
Status PassesDecoderState::PreparePipeline(const FrameHeader& frame_header,
118
                                           const ImageMetadata* metadata,
119
                                           ImageBundle* decoded,
120
13.7k
                                           PipelineOptions options) {
121
13.7k
  JxlMemoryManager* memory_manager = this->memory_manager();
122
13.7k
  size_t num_c = 3 + frame_header.nonserialized_metadata->m.num_extra_channels;
123
13.7k
  bool render_noise =
124
13.7k
      (options.render_noise && (frame_header.flags & FrameHeader::kNoise) != 0);
125
13.7k
  size_t num_tmp_c = render_noise ? 3 : 0;
126
127
13.7k
  if (frame_header.CanBeReferenced()) {
128
    // Necessary so that SetInputSizes() can allocate output buffers as needed.
129
10.0k
    frame_storage_for_referencing = ImageBundle(memory_manager, metadata);
130
10.0k
  }
131
132
13.7k
  RenderPipeline::Builder builder(memory_manager, num_c + num_tmp_c);
133
134
13.7k
  if (options.use_slow_render_pipeline) {
135
0
    builder.UseSimpleImplementation();
136
0
  }
137
138
13.7k
  if (!frame_header.chroma_subsampling.Is444()) {
139
6.02k
    for (size_t c = 0; c < 3; c++) {
140
4.51k
      if (frame_header.chroma_subsampling.HShift(c) != 0) {
141
1.97k
        JXL_RETURN_IF_ERROR(
142
1.97k
            builder.AddStage(GetChromaUpsamplingStage(c, /*horizontal=*/true)));
143
1.97k
      }
144
4.51k
      if (frame_header.chroma_subsampling.VShift(c) != 0) {
145
1.58k
        JXL_RETURN_IF_ERROR(builder.AddStage(
146
1.58k
            GetChromaUpsamplingStage(c, /*horizontal=*/false)));
147
1.58k
      }
148
4.51k
    }
149
1.50k
  }
150
151
13.7k
  if (frame_header.loop_filter.gab) {
152
717
    JXL_RETURN_IF_ERROR(
153
717
        builder.AddStage(GetGaborishStage(frame_header.loop_filter)));
154
717
  }
155
156
13.7k
  {
157
13.7k
    const LoopFilter& lf = frame_header.loop_filter;
158
13.7k
    if (lf.epf_iters >= 3) {
159
1.18k
      JXL_RETURN_IF_ERROR(
160
1.18k
          builder.AddStage(GetEPFStage(lf, sigma, EpfStage::Zero)));
161
1.18k
    }
162
13.7k
    if (lf.epf_iters >= 1) {
163
6.43k
      JXL_RETURN_IF_ERROR(
164
6.43k
          builder.AddStage(GetEPFStage(lf, sigma, EpfStage::One)));
165
6.43k
    }
166
13.7k
    if (lf.epf_iters >= 2) {
167
6.43k
      JXL_RETURN_IF_ERROR(
168
6.43k
          builder.AddStage(GetEPFStage(lf, sigma, EpfStage::Two)));
169
6.43k
    }
170
13.7k
  }
171
172
13.7k
  bool late_ec_upsample = frame_header.upsampling != 1;
173
13.7k
  for (auto ecups : frame_header.extra_channel_upsampling) {
174
4.36k
    if (ecups != frame_header.upsampling) {
175
      // If patches are applied, either frame_header.upsampling == 1 or
176
      // late_ec_upsample is true.
177
654
      late_ec_upsample = false;
178
654
    }
179
4.36k
  }
180
181
13.7k
  if (!late_ec_upsample) {
182
8.26k
    for (size_t ec = 0; ec < frame_header.extra_channel_upsampling.size();
183
4.96k
         ec++) {
184
3.29k
      if (frame_header.extra_channel_upsampling[ec] != 1) {
185
654
        JXL_RETURN_IF_ERROR(builder.AddStage(GetUpsamplingStage(
186
654
            memory_manager, frame_header.nonserialized_metadata->transform_data,
187
654
            3 + ec,
188
654
            CeilLog2Nonzero(frame_header.extra_channel_upsampling[ec]))));
189
654
      }
190
3.29k
    }
191
4.96k
  }
192
193
13.7k
  if ((frame_header.flags & FrameHeader::kPatches) != 0) {
194
959
    JXL_RETURN_IF_ERROR(builder.AddStage(GetPatchesStage(
195
959
        &shared->image_features.patches,
196
959
        &frame_header.nonserialized_metadata->m.extra_channel_info)));
197
959
  }
198
13.7k
  if ((frame_header.flags & FrameHeader::kSplines) != 0) {
199
557
    JXL_RETURN_IF_ERROR(
200
557
        builder.AddStage(GetSplineStage(&shared->image_features.splines)));
201
557
  }
202
203
13.7k
  if (frame_header.upsampling != 1) {
204
8.75k
    size_t nb_channels =
205
8.75k
        3 +
206
8.75k
        (late_ec_upsample ? frame_header.extra_channel_upsampling.size() : 0);
207
36.0k
    for (size_t c = 0; c < nb_channels; c++) {
208
27.3k
      JXL_RETURN_IF_ERROR(builder.AddStage(GetUpsamplingStage(
209
27.3k
          memory_manager, frame_header.nonserialized_metadata->transform_data,
210
27.3k
          c, CeilLog2Nonzero(frame_header.upsampling))));
211
27.3k
    }
212
8.75k
  }
213
  // Starting from this line all the stages considered to have zero xextra.
214
  // Upsampling does not have xextra as well (even if it happens before
215
  // splines/patches for EC).
216
13.7k
  if (render_noise) {
217
4.10k
    JXL_RETURN_IF_ERROR(builder.AddStage(GetConvolveNoiseStage(num_c)));
218
4.10k
    JXL_RETURN_IF_ERROR(builder.AddStage(GetAddNoiseStage(
219
4.10k
        shared->image_features.noise_params, shared->cmap.base(), num_c)));
220
4.10k
  }
221
13.7k
  if (frame_header.dc_level != 0) {
222
2.64k
    JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToImage3FStage(
223
2.64k
        memory_manager, &shared_storage.dc_frames[frame_header.dc_level - 1])));
224
2.64k
  }
225
226
13.7k
  if (frame_header.CanBeReferenced() &&
227
10.0k
      frame_header.save_before_color_transform) {
228
746
    JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToImageBundleStage(
229
746
        &frame_storage_for_referencing, &metadata->color_encoding)));
230
746
  }
231
232
13.7k
  bool has_alpha = false;
233
13.7k
  size_t alpha_c = 0;
234
14.8k
  for (size_t i = 0; i < metadata->extra_channel_info.size(); i++) {
235
3.78k
    if (metadata->extra_channel_info[i].type == ExtraChannel::kAlpha) {
236
2.64k
      has_alpha = true;
237
2.64k
      alpha_c = 3 + i;
238
2.64k
      break;
239
2.64k
    }
240
3.78k
  }
241
242
13.7k
  if (fast_xyb_srgb8_conversion) {
243
#if !JXL_HIGH_PRECISION
244
    JXL_ENSURE(!NeedsBlending(frame_header));
245
    JXL_ENSURE(!frame_header.CanBeReferenced() ||
246
               frame_header.save_before_color_transform);
247
    JXL_ENSURE(!options.render_spotcolors ||
248
               !metadata->Find(ExtraChannel::kSpotColor));
249
    bool is_rgba = (main_output.format.num_channels == 4);
250
    uint8_t* rgb_output = reinterpret_cast<uint8_t*>(main_output.buffer);
251
    JXL_RETURN_IF_ERROR(builder.AddStage(
252
        GetFastXYBTosRGB8Stage(rgb_output, main_output.stride, width, height,
253
                               is_rgba, has_alpha, alpha_c)));
254
#endif
255
13.7k
  } else {
256
13.7k
    bool linear = false;
257
13.7k
    if (frame_header.color_transform == ColorTransform::kYCbCr) {
258
1.52k
      JXL_RETURN_IF_ERROR(builder.AddStage(GetYCbCrStage()));
259
12.1k
    } else if (frame_header.color_transform == ColorTransform::kXYB) {
260
5.16k
      JXL_RETURN_IF_ERROR(builder.AddStage(GetXYBStage(output_encoding_info)));
261
5.16k
      if (output_encoding_info.color_encoding.GetColorSpace() !=
262
5.16k
          ColorSpace::kXYB) {
263
5.16k
        linear = true;
264
5.16k
      }
265
5.16k
    }  // Nothing to do for kNone.
266
267
13.7k
    if (options.coalescing && NeedsBlending(frame_header)) {
268
2.82k
      if (linear) {
269
1.11k
        JXL_RETURN_IF_ERROR(
270
1.11k
            builder.AddStage(GetFromLinearStage(output_encoding_info)));
271
1.11k
        linear = false;
272
1.11k
      }
273
2.82k
      JXL_RETURN_IF_ERROR(builder.AddStage(GetBlendingStage(
274
2.82k
          frame_header, this, output_encoding_info.color_encoding)));
275
2.82k
    }
276
277
13.7k
    if (options.coalescing && frame_header.CanBeReferenced() &&
278
10.0k
        !frame_header.save_before_color_transform) {
279
9.31k
      if (linear) {
280
1.05k
        JXL_RETURN_IF_ERROR(
281
1.05k
            builder.AddStage(GetFromLinearStage(output_encoding_info)));
282
1.05k
        linear = false;
283
1.05k
      }
284
9.31k
      JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToImageBundleStage(
285
9.31k
          &frame_storage_for_referencing, &metadata->color_encoding)));
286
9.31k
    }
287
288
13.7k
    if (options.render_spotcolors &&
289
13.7k
        frame_header.nonserialized_metadata->m.Find(ExtraChannel::kSpotColor)) {
290
388
      for (size_t i = 0; i < metadata->extra_channel_info.size(); i++) {
291
        // Don't use Find() because there may be multiple spot color channels.
292
195
        const ExtraChannelInfo& eci = metadata->extra_channel_info[i];
293
195
        if (eci.type == ExtraChannel::kSpotColor) {
294
193
          JXL_RETURN_IF_ERROR(
295
193
              builder.AddStage(GetSpotColorStage(i, eci.spot_color)));
296
193
        }
297
195
      }
298
193
    }
299
300
13.7k
    auto tone_mapping_stage = GetToneMappingStage(output_encoding_info);
301
13.7k
    if (tone_mapping_stage) {
302
0
      if (!linear) {
303
0
        auto to_linear_stage = GetToLinearStage(output_encoding_info);
304
0
        if (!to_linear_stage) {
305
0
          if (!output_encoding_info.cms_set) {
306
0
            return JXL_FAILURE("Cannot tonemap this colorspace without a CMS");
307
0
          }
308
0
          auto cms_stage = GetCmsStage(output_encoding_info);
309
0
          if (cms_stage) {
310
0
            JXL_RETURN_IF_ERROR(builder.AddStage(std::move(cms_stage)));
311
0
          }
312
0
        } else {
313
0
          JXL_RETURN_IF_ERROR(builder.AddStage(std::move(to_linear_stage)));
314
0
        }
315
0
        linear = true;
316
0
      }
317
0
      JXL_RETURN_IF_ERROR(builder.AddStage(std::move(tone_mapping_stage)));
318
0
    }
319
320
13.7k
    if (linear) {
321
3.00k
      const size_t channels_src =
322
3.00k
          (output_encoding_info.orig_color_encoding.IsCMYK()
323
3.00k
               ? 4
324
3.00k
               : output_encoding_info.orig_color_encoding.Channels());
325
3.00k
      const size_t channels_dst =
326
3.00k
          output_encoding_info.color_encoding.Channels();
327
3.00k
      bool mixing_color_and_grey = (channels_dst != channels_src);
328
3.00k
      if ((output_encoding_info.color_encoding_is_original) ||
329
2.44k
          (!output_encoding_info.cms_set) || mixing_color_and_grey) {
330
        // in those cases we only need a linear stage in other cases we attempt
331
        // to obtain a cms stage: the cases are
332
        // - output_encoding_info.color_encoding_is_original: no cms stage
333
        // needed because it would be a no-op
334
        // - !output_encoding_info.cms_set: can't use the cms, so no point in
335
        // trying to add a cms stage
336
        // - mixing_color_and_grey: cms stage can't handle that
337
        // TODO(firsching): remove "mixing_color_and_grey" condition after
338
        // adding support for greyscale to cms stage.
339
2.44k
        JXL_RETURN_IF_ERROR(
340
2.44k
            builder.AddStage(GetFromLinearStage(output_encoding_info)));
341
2.44k
      } else {
342
562
        if (!output_encoding_info.linear_color_encoding.CreateICC()) {
343
0
          return JXL_FAILURE("Failed to create ICC");
344
0
        }
345
562
        auto cms_stage = GetCmsStage(output_encoding_info);
346
562
        if (cms_stage) {
347
562
          JXL_RETURN_IF_ERROR(builder.AddStage(std::move(cms_stage)));
348
562
        }
349
562
      }
350
3.00k
      linear = false;
351
10.7k
    } else {
352
10.7k
      auto cms_stage = GetCmsStage(output_encoding_info, false);
353
10.7k
      if (cms_stage) {
354
778
        JXL_RETURN_IF_ERROR(builder.AddStage(std::move(cms_stage)));
355
778
      }
356
10.7k
    }
357
13.7k
    (void)linear;
358
359
13.7k
    if (main_output.callback.IsPresent() || main_output.buffer) {
360
1.01k
      JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToOutputStage(
361
1.01k
          main_output, width, height, has_alpha, unpremul_alpha, alpha_c,
362
1.01k
          undo_orientation, extra_output, memory_manager)));
363
12.7k
    } else {
364
12.7k
      JXL_RETURN_IF_ERROR(builder.AddStage(GetWriteToImageBundleStage(
365
12.7k
          decoded, &output_encoding_info.color_encoding)));
366
12.7k
    }
367
13.7k
  }
368
13.7k
  JXL_ASSIGN_OR_RETURN(render_pipeline,
369
13.7k
                       std::move(builder).Finalize(shared->frame_dim));
370
13.7k
  return render_pipeline->IsInitialized();
371
13.7k
}
372
373
}  // namespace jxl