Coverage Report

Created: 2025-06-16 07:00

/src/libjxl/lib/jxl/enc_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/enc_cache.h"
7
8
#include <jxl/cms_interface.h>
9
#include <jxl/memory_manager.h>
10
11
#include <algorithm>
12
#include <cmath>
13
#include <cstddef>
14
#include <cstdint>
15
#include <memory>
16
#include <utility>
17
#include <vector>
18
19
#include "lib/jxl/base/common.h"
20
#include "lib/jxl/base/compiler_specific.h"
21
#include "lib/jxl/base/data_parallel.h"
22
#include "lib/jxl/base/override.h"
23
#include "lib/jxl/base/rect.h"
24
#include "lib/jxl/base/span.h"
25
#include "lib/jxl/base/status.h"
26
#include "lib/jxl/color_encoding_internal.h"
27
#include "lib/jxl/common.h"
28
#include "lib/jxl/compressed_dc.h"
29
#include "lib/jxl/dct_util.h"
30
#include "lib/jxl/dec_cache.h"
31
#include "lib/jxl/dec_frame.h"
32
#include "lib/jxl/enc_aux_out.h"
33
#include "lib/jxl/enc_frame.h"
34
#include "lib/jxl/enc_group.h"
35
#include "lib/jxl/enc_modular.h"
36
#include "lib/jxl/enc_params.h"
37
#include "lib/jxl/enc_quant_weights.h"
38
#include "lib/jxl/frame_dimensions.h"
39
#include "lib/jxl/frame_header.h"
40
#include "lib/jxl/image.h"
41
#include "lib/jxl/image_bundle.h"
42
#include "lib/jxl/image_ops.h"
43
#include "lib/jxl/passes_state.h"
44
#include "lib/jxl/quantizer.h"
45
46
namespace jxl {
47
48
Status ComputeACMetadata(ThreadPool* pool, PassesEncoderState* enc_state,
49
186
                         ModularFrameEncoder* modular_frame_encoder) {
50
186
  PassesSharedState& shared = enc_state->shared;
51
186
  auto compute_ac_meta = [&](int group_index, int /* thread */) -> Status {
52
186
    const Rect r = shared.frame_dim.DCGroupRect(group_index);
53
186
    int modular_group_index = group_index;
54
186
    if (enc_state->streaming_mode) {
55
0
      JXL_ENSURE(group_index == 0);
56
0
      modular_group_index = enc_state->dc_group_index;
57
0
    }
58
186
    JXL_RETURN_IF_ERROR(modular_frame_encoder->AddACMetadata(
59
186
        r, modular_group_index,
60
186
        /*jpeg_transcode=*/false, enc_state));
61
186
    return true;
62
186
  };
63
186
  JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, shared.frame_dim.num_dc_groups,
64
186
                                ThreadPool::NoInit, compute_ac_meta,
65
186
                                "Compute AC Metadata"));
66
186
  return true;
67
186
}
68
69
Status InitializePassesEncoder(const FrameHeader& frame_header,
70
                               const Image3F& opsin, const Rect& rect,
71
                               const JxlCmsInterface& cms, ThreadPool* pool,
72
                               PassesEncoderState* enc_state,
73
                               ModularFrameEncoder* modular_frame_encoder,
74
186
                               AuxOut* aux_out) {
75
186
  PassesSharedState& JXL_RESTRICT shared = enc_state->shared;
76
186
  JxlMemoryManager* memory_manager = enc_state->memory_manager();
77
78
186
  enc_state->x_qm_multiplier = std::pow(1.25f, frame_header.x_qm_scale - 2.0f);
79
186
  enc_state->b_qm_multiplier = std::pow(1.25f, frame_header.b_qm_scale - 2.0f);
80
81
186
  if (enc_state->coeffs.size() < frame_header.passes.num_passes) {
82
186
    enc_state->coeffs.reserve(frame_header.passes.num_passes);
83
186
    for (size_t i = enc_state->coeffs.size();
84
372
         i < frame_header.passes.num_passes; i++) {
85
      // Allocate enough coefficients for each group on every row.
86
186
      JXL_ASSIGN_OR_RETURN(
87
186
          std::unique_ptr<ACImageT<int32_t>> coeffs,
88
186
          ACImageT<int32_t>::Make(memory_manager, kGroupDim * kGroupDim,
89
186
                                  shared.frame_dim.num_groups));
90
186
      enc_state->coeffs.emplace_back(std::move(coeffs));
91
186
    }
92
186
  }
93
186
  while (enc_state->coeffs.size() > frame_header.passes.num_passes) {
94
0
    enc_state->coeffs.pop_back();
95
0
  }
96
97
186
  if (enc_state->initialize_global_state) {
98
186
    float scale =
99
186
        shared.quantizer.ScaleGlobalScale(enc_state->cparams.quant_ac_rescale);
100
186
    JXL_RETURN_IF_ERROR(
101
186
        DequantMatricesScaleDC(memory_manager, &shared.matrices, scale));
102
186
    shared.quantizer.RecomputeFromGlobalScale();
103
186
  }
104
105
372
  JXL_ASSIGN_OR_RETURN(
106
372
      Image3F dc, Image3F::Create(memory_manager, shared.frame_dim.xsize_blocks,
107
372
                                  shared.frame_dim.ysize_blocks));
108
595
  const auto process_group = [&](size_t group_idx, size_t _) -> Status {
109
595
    JXL_RETURN_IF_ERROR(
110
595
        ComputeCoefficients(group_idx, enc_state, opsin, rect, &dc));
111
595
    return true;
112
595
  };
113
372
  JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, shared.frame_dim.num_groups,
114
372
                                ThreadPool::NoInit, process_group,
115
372
                                "Compute coeffs"));
116
117
186
  if (frame_header.flags & FrameHeader::kUseDcFrame) {
118
0
    CompressParams cparams = enc_state->cparams;
119
0
    cparams.dots = Override::kOff;
120
0
    cparams.noise = Override::kOff;
121
0
    cparams.patches = Override::kOff;
122
0
    cparams.gaborish = Override::kOff;
123
0
    cparams.epf = 0;
124
0
    cparams.resampling = 1;
125
0
    cparams.ec_resampling = 1;
126
    // The DC frame will have alpha=0. Don't erase its contents.
127
0
    cparams.keep_invisible = Override::kOn;
128
0
    JXL_ENSURE(cparams.progressive_dc > 0);
129
0
    cparams.progressive_dc--;
130
    // Use kVarDCT in max_error_mode for intermediate progressive DC,
131
    // and kModular for the smallest DC (first in the bitstream)
132
0
    if (cparams.progressive_dc == 0) {
133
0
      cparams.modular_mode = true;
134
0
      cparams.speed_tier = static_cast<SpeedTier>(
135
0
          std::max(static_cast<int>(SpeedTier::kTortoise),
136
0
                   static_cast<int>(cparams.speed_tier) - 1));
137
0
      cparams.butteraugli_distance =
138
0
          std::max(kMinButteraugliDistance * 0.02f,
139
0
                   enc_state->cparams.butteraugli_distance * 0.02f);
140
0
    } else {
141
0
      cparams.max_error_mode = true;
142
0
      for (size_t c = 0; c < 3; c++) {
143
0
        cparams.max_error[c] = shared.quantizer.MulDC()[c];
144
0
      }
145
      // Guess a distance that produces good initial results.
146
0
      cparams.butteraugli_distance =
147
0
          std::max(kMinButteraugliDistance,
148
0
                   enc_state->cparams.butteraugli_distance * 0.1f);
149
0
    }
150
0
    auto ib =
151
0
        jxl::make_unique<ImageBundle>(memory_manager, &shared.metadata->m);
152
    // This is a lie - dc is in XYB
153
    // (but EncodeFrame will skip RGB->XYB conversion anyway)
154
0
    JXL_RETURN_IF_ERROR(ib->SetFromImage(
155
0
        std::move(dc),
156
0
        ColorEncoding::LinearSRGB(shared.metadata->m.color_encoding.IsGray())));
157
0
    if (!ib->metadata()->extra_channel_info.empty()) {
158
      // Add placeholder extra channels to the patch image: dc_level frames do
159
      // not yet support extra channels, but the codec expects that the amount
160
      // of extra channels in frames matches that in the metadata of the
161
      // codestream.
162
0
      std::vector<ImageF> extra_channels;
163
0
      extra_channels.reserve(ib->metadata()->extra_channel_info.size());
164
0
      for (size_t i = 0; i < ib->metadata()->extra_channel_info.size(); i++) {
165
0
        JXL_ASSIGN_OR_RETURN(
166
0
            ImageF ch,
167
0
            ImageF::Create(memory_manager, ib->xsize(), ib->ysize()));
168
0
        extra_channels.emplace_back(std::move(ch));
169
        // Must initialize the image with data to not affect blending with
170
        // uninitialized memory.
171
        // TODO(lode): dc_level must copy and use the real extra channels
172
        // instead.
173
0
        ZeroFillImage(&extra_channels.back());
174
0
      }
175
0
      JXL_RETURN_IF_ERROR(ib->SetExtraChannels(std::move(extra_channels)));
176
0
    }
177
0
    auto special_frame = jxl::make_unique<BitWriter>(memory_manager);
178
0
    FrameInfo dc_frame_info;
179
0
    dc_frame_info.frame_type = FrameType::kDCFrame;
180
0
    dc_frame_info.dc_level = frame_header.dc_level + 1;
181
0
    dc_frame_info.ib_needs_color_transform = false;
182
0
    dc_frame_info.save_before_color_transform = true;  // Implicitly true
183
0
    AuxOut dc_aux_out;
184
0
    JXL_RETURN_IF_ERROR(EncodeFrame(
185
0
        memory_manager, cparams, dc_frame_info, shared.metadata, *ib, cms, pool,
186
0
        special_frame.get(), aux_out ? &dc_aux_out : nullptr));
187
0
    if (aux_out) {
188
0
      for (const auto& l : dc_aux_out.layers) {
189
0
        aux_out->layer(LayerType::Dc).Assimilate(l);
190
0
      }
191
0
    }
192
0
    const Span<const uint8_t> encoded = special_frame->GetSpan();
193
0
    enc_state->special_frames.emplace_back(std::move(special_frame));
194
195
0
    auto decoded =
196
0
        jxl::make_unique<ImageBundle>(memory_manager, &shared.metadata->m);
197
0
    std::unique_ptr<PassesDecoderState> dec_state =
198
0
        jxl::make_unique<PassesDecoderState>(memory_manager);
199
0
    JXL_RETURN_IF_ERROR(
200
0
        dec_state->output_encoding_info.SetFromMetadata(*shared.metadata));
201
0
    const uint8_t* frame_start = encoded.data();
202
0
    size_t encoded_size = encoded.size();
203
0
    for (int i = 0; i <= cparams.progressive_dc; ++i) {
204
0
      JXL_RETURN_IF_ERROR(DecodeFrame(
205
0
          dec_state.get(), pool, frame_start, encoded_size,
206
0
          /*frame_header=*/nullptr, decoded.get(), *shared.metadata));
207
0
      frame_start += decoded->decoded_bytes();
208
0
      encoded_size -= decoded->decoded_bytes();
209
0
    }
210
    // TODO(lode): frame_header.dc_level should be equal to
211
    // dec_state.frame_header.dc_level - 1 here, since above we set
212
    // dc_frame_info.dc_level = frame_header.dc_level + 1, and
213
    // dc_frame_info.dc_level is used by EncodeFrame. However, if EncodeFrame
214
    // outputs multiple frames, this assumption could be wrong.
215
0
    const Image3F& dc_frame =
216
0
        dec_state->shared->dc_frames[frame_header.dc_level];
217
0
    JXL_ASSIGN_OR_RETURN(
218
0
        shared.dc_storage,
219
0
        Image3F::Create(memory_manager, dc_frame.xsize(), dc_frame.ysize()));
220
0
    JXL_RETURN_IF_ERROR(CopyImageTo(dc_frame, &shared.dc_storage));
221
0
    ZeroFillImage(&shared.quant_dc);
222
0
    shared.dc = &shared.dc_storage;
223
0
    JXL_ENSURE(encoded_size == 0);
224
186
  } else {
225
186
    auto compute_dc_coeffs = [&](int group_index, int /* thread */) -> Status {
226
186
      const Rect r = enc_state->shared.frame_dim.DCGroupRect(group_index);
227
186
      int modular_group_index = group_index;
228
186
      if (enc_state->streaming_mode) {
229
0
        JXL_ENSURE(group_index == 0);
230
0
        modular_group_index = enc_state->dc_group_index;
231
0
      }
232
186
      JXL_RETURN_IF_ERROR(modular_frame_encoder->AddVarDCTDC(
233
186
          frame_header, dc, r, modular_group_index,
234
186
          enc_state->cparams.speed_tier < SpeedTier::kFalcon, enc_state,
235
186
          /*jpeg_transcode=*/false));
236
186
      return true;
237
186
    };
238
186
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, shared.frame_dim.num_dc_groups,
239
186
                                  ThreadPool::NoInit, compute_dc_coeffs,
240
186
                                  "Compute DC coeffs"));
241
    // TODO(veluca): this is only useful in tests and if inspection is enabled.
242
186
    if (!(frame_header.flags & FrameHeader::kSkipAdaptiveDCSmoothing)) {
243
186
      JXL_RETURN_IF_ERROR(AdaptiveDCSmoothing(
244
186
          memory_manager, shared.quantizer.MulDC(), &shared.dc_storage, pool));
245
186
    }
246
186
  }
247
186
  return true;
248
186
}
249
250
}  // namespace jxl