Coverage Report

Created: 2025-06-22 08:04

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