Coverage Report

Created: 2025-09-08 07:52

/src/libjxl/lib/jxl/compressed_dc.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/compressed_dc.h"
7
8
#include <jxl/memory_manager.h>
9
10
#include <algorithm>
11
#include <cstdint>
12
#include <cstdlib>
13
#include <cstring>
14
#include <vector>
15
16
#undef HWY_TARGET_INCLUDE
17
#define HWY_TARGET_INCLUDE "lib/jxl/compressed_dc.cc"
18
#include <hwy/foreach_target.h>
19
#include <hwy/highway.h>
20
21
#include "lib/jxl/base/compiler_specific.h"
22
#include "lib/jxl/base/data_parallel.h"
23
#include "lib/jxl/base/rect.h"
24
#include "lib/jxl/base/status.h"
25
#include "lib/jxl/image.h"
26
HWY_BEFORE_NAMESPACE();
27
namespace jxl {
28
namespace HWY_NAMESPACE {
29
30
using D = HWY_FULL(float);
31
using DScalar = HWY_CAPPED(float, 1);
32
33
// These templates are not found via ADL.
34
using hwy::HWY_NAMESPACE::Abs;
35
using hwy::HWY_NAMESPACE::Add;
36
using hwy::HWY_NAMESPACE::Div;
37
using hwy::HWY_NAMESPACE::Max;
38
using hwy::HWY_NAMESPACE::Mul;
39
using hwy::HWY_NAMESPACE::MulAdd;
40
using hwy::HWY_NAMESPACE::Rebind;
41
using hwy::HWY_NAMESPACE::Sub;
42
using hwy::HWY_NAMESPACE::Vec;
43
using hwy::HWY_NAMESPACE::ZeroIfNegative;
44
45
// TODO(veluca): optimize constants.
46
const float w1 = 0.20345139757231578f;
47
const float w2 = 0.0334829185968739f;
48
const float w0 = 1.0f - 4.0f * (w1 + w2);
49
50
template <class V>
51
7.57k
V MaxWorkaround(V a, V b) {
52
#if (HWY_TARGET == HWY_AVX3) && HWY_COMPILER_CLANG <= 800
53
  // Prevents "Do not know how to split the result of this operator" error
54
  return IfThenElse(a > b, a, b);
55
#else
56
7.57k
  return Max(a, b);
57
7.57k
#endif
58
7.57k
}
59
60
template <typename D>
61
JXL_INLINE void ComputePixelChannel(const D d, const float dc_factor,
62
                                    const float* JXL_RESTRICT row_top,
63
                                    const float* JXL_RESTRICT row,
64
                                    const float* JXL_RESTRICT row_bottom,
65
                                    Vec<D>* JXL_RESTRICT mc,
66
                                    Vec<D>* JXL_RESTRICT sm,
67
8.59k
                                    Vec<D>* JXL_RESTRICT gap, size_t x) {
68
8.59k
  const auto tl = LoadU(d, row_top + x - 1);
69
8.59k
  const auto tc = Load(d, row_top + x);
70
8.59k
  const auto tr = LoadU(d, row_top + x + 1);
71
72
8.59k
  const auto ml = LoadU(d, row + x - 1);
73
8.59k
  *mc = Load(d, row + x);
74
8.59k
  const auto mr = LoadU(d, row + x + 1);
75
76
8.59k
  const auto bl = LoadU(d, row_bottom + x - 1);
77
8.59k
  const auto bc = Load(d, row_bottom + x);
78
8.59k
  const auto br = LoadU(d, row_bottom + x + 1);
79
80
8.59k
  const auto w_center = Set(d, w0);
81
8.59k
  const auto w_side = Set(d, w1);
82
8.59k
  const auto w_corner = Set(d, w2);
83
84
8.59k
  const auto corner = Add(Add(tl, tr), Add(bl, br));
85
8.59k
  const auto side = Add(Add(ml, mr), Add(tc, bc));
86
8.59k
  *sm = MulAdd(corner, w_corner, MulAdd(side, w_side, Mul(*mc, w_center)));
87
88
8.59k
  const auto dc_quant = Set(d, dc_factor);
89
8.59k
  *gap = MaxWorkaround(*gap, Abs(Div(Sub(*mc, *sm), dc_quant)));
90
8.59k
}
91
92
template <typename D>
93
JXL_INLINE void ComputePixel(
94
    const float* JXL_RESTRICT dc_factors,
95
    const float* JXL_RESTRICT* JXL_RESTRICT rows_top,
96
    const float* JXL_RESTRICT* JXL_RESTRICT rows,
97
    const float* JXL_RESTRICT* JXL_RESTRICT rows_bottom,
98
4.49k
    float* JXL_RESTRICT* JXL_RESTRICT out_rows, size_t x) {
99
4.49k
  const D d;
100
4.49k
  auto mc_x = Undefined(d);
101
4.49k
  auto mc_y = Undefined(d);
102
4.49k
  auto mc_b = Undefined(d);
103
4.49k
  auto sm_x = Undefined(d);
104
4.49k
  auto sm_y = Undefined(d);
105
4.49k
  auto sm_b = Undefined(d);
106
4.49k
  auto gap = Set(d, 0.5f);
107
4.49k
  ComputePixelChannel(d, dc_factors[0], rows_top[0], rows[0], rows_bottom[0],
108
4.49k
                      &mc_x, &sm_x, &gap, x);
109
4.49k
  ComputePixelChannel(d, dc_factors[1], rows_top[1], rows[1], rows_bottom[1],
110
4.49k
                      &mc_y, &sm_y, &gap, x);
111
4.49k
  ComputePixelChannel(d, dc_factors[2], rows_top[2], rows[2], rows_bottom[2],
112
4.49k
                      &mc_b, &sm_b, &gap, x);
113
4.49k
  auto factor = MulAdd(Set(d, -4.0f), gap, Set(d, 3.0f));
114
4.49k
  factor = ZeroIfNegative(factor);
115
116
4.49k
  auto out = MulAdd(Sub(sm_x, mc_x), factor, mc_x);
117
4.49k
  Store(out, d, out_rows[0] + x);
118
4.49k
  out = MulAdd(Sub(sm_y, mc_y), factor, mc_y);
119
4.49k
  Store(out, d, out_rows[1] + x);
120
4.49k
  out = MulAdd(Sub(sm_b, mc_b), factor, mc_b);
121
4.49k
  Store(out, d, out_rows[2] + x);
122
4.49k
}
123
124
Status AdaptiveDCSmoothing(JxlMemoryManager* memory_manager,
125
                           const float* dc_factors, Image3F* dc,
126
5.86k
                           ThreadPool* pool) {
127
5.86k
  const size_t xsize = dc->xsize();
128
5.86k
  const size_t ysize = dc->ysize();
129
5.86k
  if (ysize <= 2 || xsize <= 2) return true;
130
131
  // TODO(veluca): use tile-based processing?
132
  // TODO(veluca): decide if changes to the y channel should be propagated to
133
  // the x and b channels through color correlation.
134
6
  JXL_ENSURE(w1 + w2 < 0.25f);
135
136
12
  JXL_ASSIGN_OR_RETURN(Image3F smoothed,
137
12
                       Image3F::Create(memory_manager, xsize, ysize));
138
  // Fill in borders that the loop below will not. First and last are unused.
139
24
  for (size_t c = 0; c < 3; c++) {
140
36
    for (size_t y : {static_cast<size_t>(0), ysize - 1}) {
141
36
      memcpy(smoothed.PlaneRow(c, y), dc->PlaneRow(c, y),
142
36
             xsize * sizeof(float));
143
36
    }
144
18
  }
145
180
  auto process_row = [&](const uint32_t y, size_t /*thread*/) -> Status {
146
180
    const float* JXL_RESTRICT rows_top[3]{
147
180
        dc->ConstPlaneRow(0, y - 1),
148
180
        dc->ConstPlaneRow(1, y - 1),
149
180
        dc->ConstPlaneRow(2, y - 1),
150
180
    };
151
180
    const float* JXL_RESTRICT rows[3] = {
152
180
        dc->ConstPlaneRow(0, y),
153
180
        dc->ConstPlaneRow(1, y),
154
180
        dc->ConstPlaneRow(2, y),
155
180
    };
156
180
    const float* JXL_RESTRICT rows_bottom[3] = {
157
180
        dc->ConstPlaneRow(0, y + 1),
158
180
        dc->ConstPlaneRow(1, y + 1),
159
180
        dc->ConstPlaneRow(2, y + 1),
160
180
    };
161
180
    float* JXL_RESTRICT rows_out[3] = {
162
180
        smoothed.PlaneRow(0, y),
163
180
        smoothed.PlaneRow(1, y),
164
180
        smoothed.PlaneRow(2, y),
165
180
    };
166
358
    for (size_t x : {static_cast<size_t>(0), xsize - 1}) {
167
1.43k
      for (size_t c = 0; c < 3; c++) {
168
1.07k
        rows_out[c][x] = rows[c][x];
169
1.07k
      }
170
358
    }
171
172
180
    size_t x = 1;
173
    // First pixels
174
180
    const size_t N = Lanes(D());
175
180
    for (; x < std::min(N, xsize - 1); x++) {
176
0
      ComputePixel<DScalar>(dc_factors, rows_top, rows, rows_bottom, rows_out,
177
0
                            x);
178
0
    }
179
    // Full vectors.
180
4.66k
    for (; x + N <= xsize - 1; x += N) {
181
4.48k
      ComputePixel<D>(dc_factors, rows_top, rows, rows_bottom, rows_out, x);
182
4.48k
    }
183
    // Last pixels.
184
180
    for (; x < xsize - 1; x++) {
185
0
      ComputePixel<DScalar>(dc_factors, rows_top, rows, rows_bottom, rows_out,
186
0
                            x);
187
0
    }
188
180
    return true;
189
180
  };
190
12
  JXL_RETURN_IF_ERROR(RunOnPool(pool, 1, ysize - 1, ThreadPool::NoInit,
191
12
                                process_row, "DCSmoothingRow"));
192
6
  dc->Swap(smoothed);
193
6
  return true;
194
12
}
195
196
// DC dequantization.
197
void DequantDC(const Rect& r, Image3F* dc, ImageB* quant_dc, const Image& in,
198
               const float* dc_factors, float mul, const float* cfl_factors,
199
               const YCbCrChromaSubsampling& chroma_subsampling,
200
5.91k
               const BlockCtxMap& bctx) {
201
5.91k
  const HWY_FULL(float) df;
202
5.91k
  const Rebind<pixel_type, HWY_FULL(float)> di;  // assumes pixel_type <= float
203
5.91k
  if (chroma_subsampling.Is444()) {
204
5.91k
    const auto fac_x = Set(df, dc_factors[0] * mul);
205
5.91k
    const auto fac_y = Set(df, dc_factors[1] * mul);
206
5.91k
    const auto fac_b = Set(df, dc_factors[2] * mul);
207
5.91k
    const auto cfl_fac_x = Set(df, cfl_factors[0]);
208
5.91k
    const auto cfl_fac_b = Set(df, cfl_factors[2]);
209
18.1k
    for (size_t y = 0; y < r.ysize(); y++) {
210
12.2k
      float* dec_row_x = r.PlaneRow(dc, 0, y);
211
12.2k
      float* dec_row_y = r.PlaneRow(dc, 1, y);
212
12.2k
      float* dec_row_b = r.PlaneRow(dc, 2, y);
213
12.2k
      const int32_t* quant_row_x = in.channel[1].plane.Row(y);
214
12.2k
      const int32_t* quant_row_y = in.channel[0].plane.Row(y);
215
12.2k
      const int32_t* quant_row_b = in.channel[2].plane.Row(y);
216
144k
      for (size_t x = 0; x < r.xsize(); x += Lanes(di)) {
217
131k
        const auto in_q_x = Load(di, quant_row_x + x);
218
131k
        const auto in_q_y = Load(di, quant_row_y + x);
219
131k
        const auto in_q_b = Load(di, quant_row_b + x);
220
131k
        const auto in_x = Mul(ConvertTo(df, in_q_x), fac_x);
221
131k
        const auto in_y = Mul(ConvertTo(df, in_q_y), fac_y);
222
131k
        const auto in_b = Mul(ConvertTo(df, in_q_b), fac_b);
223
131k
        Store(in_y, df, dec_row_y + x);
224
131k
        Store(MulAdd(in_y, cfl_fac_x, in_x), df, dec_row_x + x);
225
131k
        Store(MulAdd(in_y, cfl_fac_b, in_b), df, dec_row_b + x);
226
131k
      }
227
12.2k
    }
228
5.91k
  } else {
229
1
    for (size_t c : {1, 0, 2}) {
230
0
      Rect rect(r.x0() >> chroma_subsampling.HShift(c),
231
0
                r.y0() >> chroma_subsampling.VShift(c),
232
0
                r.xsize() >> chroma_subsampling.HShift(c),
233
0
                r.ysize() >> chroma_subsampling.VShift(c));
234
0
      const auto fac = Set(df, dc_factors[c] * mul);
235
0
      const Channel& ch = in.channel[c < 2 ? c ^ 1 : c];
236
0
      for (size_t y = 0; y < rect.ysize(); y++) {
237
0
        const int32_t* quant_row = ch.plane.Row(y);
238
0
        float* row = rect.PlaneRow(dc, c, y);
239
0
        for (size_t x = 0; x < rect.xsize(); x += Lanes(di)) {
240
0
          const auto in_q = Load(di, quant_row + x);
241
0
          const auto in = Mul(ConvertTo(df, in_q), fac);
242
0
          Store(in, df, row + x);
243
0
        }
244
0
      }
245
0
    }
246
1
  }
247
5.91k
  if (bctx.num_dc_ctxs <= 1) {
248
17.9k
    for (size_t y = 0; y < r.ysize(); y++) {
249
12.0k
      uint8_t* qdc_row = r.Row(quant_dc, y);
250
12.0k
      memset(qdc_row, 0, sizeof(*qdc_row) * r.xsize());
251
12.0k
    }
252
5.90k
  } else {
253
259
    for (size_t y = 0; y < r.ysize(); y++) {
254
257
      uint8_t* qdc_row_val = r.Row(quant_dc, y);
255
257
      const int32_t* quant_row_x =
256
257
          in.channel[1].plane.Row(y >> chroma_subsampling.VShift(0));
257
257
      const int32_t* quant_row_y =
258
257
          in.channel[0].plane.Row(y >> chroma_subsampling.VShift(1));
259
257
      const int32_t* quant_row_b =
260
257
          in.channel[2].plane.Row(y >> chroma_subsampling.VShift(2));
261
65.9k
      for (size_t x = 0; x < r.xsize(); x++) {
262
65.6k
        int bucket_x = 0;
263
65.6k
        int bucket_y = 0;
264
65.6k
        int bucket_b = 0;
265
65.6k
        for (int t : bctx.dc_thresholds[0]) {
266
258
          if (quant_row_x[x >> chroma_subsampling.HShift(0)] > t) bucket_x++;
267
258
        }
268
131k
        for (int t : bctx.dc_thresholds[1]) {
269
131k
          if (quant_row_y[x >> chroma_subsampling.HShift(1)] > t) bucket_y++;
270
131k
        }
271
131k
        for (int t : bctx.dc_thresholds[2]) {
272
131k
          if (quant_row_b[x >> chroma_subsampling.HShift(2)] > t) bucket_b++;
273
131k
        }
274
65.6k
        int bucket = bucket_x;
275
65.6k
        bucket *= bctx.dc_thresholds[2].size() + 1;
276
65.6k
        bucket += bucket_b;
277
65.6k
        bucket *= bctx.dc_thresholds[1].size() + 1;
278
65.6k
        bucket += bucket_y;
279
65.6k
        qdc_row_val[x] = bucket;
280
65.6k
      }
281
257
    }
282
2
  }
283
5.91k
}
284
285
// NOLINTNEXTLINE(google-readability-namespace-comments)
286
}  // namespace HWY_NAMESPACE
287
}  // namespace jxl
288
HWY_AFTER_NAMESPACE();
289
290
#if HWY_ONCE
291
namespace jxl {
292
293
HWY_EXPORT(DequantDC);
294
HWY_EXPORT(AdaptiveDCSmoothing);
295
Status AdaptiveDCSmoothing(JxlMemoryManager* memory_manager,
296
                           const float* dc_factors, Image3F* dc,
297
5.86k
                           ThreadPool* pool) {
298
5.86k
  return HWY_DYNAMIC_DISPATCH(AdaptiveDCSmoothing)(memory_manager, dc_factors,
299
5.86k
                                                   dc, pool);
300
5.86k
}
301
302
void DequantDC(const Rect& r, Image3F* dc, ImageB* quant_dc, const Image& in,
303
               const float* dc_factors, float mul, const float* cfl_factors,
304
               const YCbCrChromaSubsampling& chroma_subsampling,
305
5.91k
               const BlockCtxMap& bctx) {
306
5.91k
  HWY_DYNAMIC_DISPATCH(DequantDC)
307
5.91k
  (r, dc, quant_dc, in, dc_factors, mul, cfl_factors, chroma_subsampling, bctx);
308
5.91k
}
309
310
}  // namespace jxl
311
#endif  // HWY_ONCE