Coverage Report

Created: 2025-06-22 08:04

/src/libjxl/lib/jxl/dec_xyb.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_xyb.h"
7
8
#include <cstring>
9
10
#undef HWY_TARGET_INCLUDE
11
#define HWY_TARGET_INCLUDE "lib/jxl/dec_xyb.cc"
12
#include <hwy/foreach_target.h>
13
#include <hwy/highway.h>
14
15
#include "lib/jxl/base/compiler_specific.h"
16
#include "lib/jxl/base/matrix_ops.h"
17
#include "lib/jxl/base/rect.h"
18
#include "lib/jxl/base/sanitizers.h"
19
#include "lib/jxl/base/status.h"
20
#include "lib/jxl/cms/jxl_cms_internal.h"
21
#include "lib/jxl/cms/opsin_params.h"
22
#include "lib/jxl/color_encoding_internal.h"
23
#include "lib/jxl/dec_xyb-inl.h"
24
#include "lib/jxl/image.h"
25
#include "lib/jxl/opsin_params.h"
26
#include "lib/jxl/quantizer.h"
27
HWY_BEFORE_NAMESPACE();
28
namespace jxl {
29
namespace HWY_NAMESPACE {
30
31
// These templates are not found via ADL.
32
using hwy::HWY_NAMESPACE::MulAdd;
33
34
Status OpsinToLinearInplace(Image3F* JXL_RESTRICT inout, ThreadPool* pool,
35
0
                            const OpsinParams& opsin_params) {
36
0
  JXL_CHECK_IMAGE_INITIALIZED(*inout, Rect(*inout));
37
38
0
  const size_t xsize = inout->xsize();  // not padded
39
0
  const auto process_row = [&](const uint32_t task,
40
0
                               size_t /* thread */) -> Status {
41
0
    const size_t y = task;
42
43
    // Faster than adding via ByteOffset at end of loop.
44
0
    float* JXL_RESTRICT row0 = inout->PlaneRow(0, y);
45
0
    float* JXL_RESTRICT row1 = inout->PlaneRow(1, y);
46
0
    float* JXL_RESTRICT row2 = inout->PlaneRow(2, y);
47
48
0
    const HWY_FULL(float) d;
49
50
0
    for (size_t x = 0; x < xsize; x += Lanes(d)) {
51
0
      const auto in_opsin_x = Load(d, row0 + x);
52
0
      const auto in_opsin_y = Load(d, row1 + x);
53
0
      const auto in_opsin_b = Load(d, row2 + x);
54
0
      auto linear_r = Undefined(d);
55
0
      auto linear_g = Undefined(d);
56
0
      auto linear_b = Undefined(d);
57
0
      XybToRgb(d, in_opsin_x, in_opsin_y, in_opsin_b, opsin_params, &linear_r,
58
0
               &linear_g, &linear_b);
59
60
0
      Store(linear_r, d, row0 + x);
61
0
      Store(linear_g, d, row1 + x);
62
0
      Store(linear_b, d, row2 + x);
63
0
    }
64
0
    return true;
65
0
  };
66
0
  JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, inout->ysize(), ThreadPool::NoInit,
67
0
                                process_row, "OpsinToLinear"));
68
0
  return true;
69
0
}
70
71
// Same, but not in-place.
72
Status OpsinToLinear(const Image3F& opsin, const Rect& rect, ThreadPool* pool,
73
                     Image3F* JXL_RESTRICT linear,
74
0
                     const OpsinParams& opsin_params) {
75
0
  JXL_ENSURE(SameSize(rect, *linear));
76
0
  JXL_CHECK_IMAGE_INITIALIZED(opsin, rect);
77
78
0
  const auto process_row = [&](const uint32_t task,
79
0
                               size_t /*thread*/) -> Status {
80
0
    const size_t y = static_cast<size_t>(task);
81
82
    // Faster than adding via ByteOffset at end of loop.
83
0
    const float* JXL_RESTRICT row_opsin_0 = rect.ConstPlaneRow(opsin, 0, y);
84
0
    const float* JXL_RESTRICT row_opsin_1 = rect.ConstPlaneRow(opsin, 1, y);
85
0
    const float* JXL_RESTRICT row_opsin_2 = rect.ConstPlaneRow(opsin, 2, y);
86
0
    float* JXL_RESTRICT row_linear_0 = linear->PlaneRow(0, y);
87
0
    float* JXL_RESTRICT row_linear_1 = linear->PlaneRow(1, y);
88
0
    float* JXL_RESTRICT row_linear_2 = linear->PlaneRow(2, y);
89
90
0
    const HWY_FULL(float) d;
91
92
0
    for (size_t x = 0; x < rect.xsize(); x += Lanes(d)) {
93
0
      const auto in_opsin_x = Load(d, row_opsin_0 + x);
94
0
      const auto in_opsin_y = Load(d, row_opsin_1 + x);
95
0
      const auto in_opsin_b = Load(d, row_opsin_2 + x);
96
0
      auto linear_r = Undefined(d);
97
0
      auto linear_g = Undefined(d);
98
0
      auto linear_b = Undefined(d);
99
0
      XybToRgb(d, in_opsin_x, in_opsin_y, in_opsin_b, opsin_params, &linear_r,
100
0
               &linear_g, &linear_b);
101
102
0
      Store(linear_r, d, row_linear_0 + x);
103
0
      Store(linear_g, d, row_linear_1 + x);
104
0
      Store(linear_b, d, row_linear_2 + x);
105
0
    }
106
0
    return true;
107
0
  };
108
0
  JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<int>(rect.ysize()),
109
0
                                ThreadPool::NoInit, process_row,
110
0
                                "OpsinToLinear(Rect)"));
111
0
  JXL_CHECK_IMAGE_INITIALIZED(*linear, rect);
112
0
  return true;
113
0
}
114
115
// Transform YCbCr to RGB.
116
// Could be performed in-place (i.e. Y, Cb and Cr could alias R, B and B).
117
0
void YcbcrToRgb(const Image3F& ycbcr, Image3F* rgb, const Rect& rect) {
118
0
  JXL_CHECK_IMAGE_INITIALIZED(ycbcr, rect);
119
0
  const HWY_CAPPED(float, kBlockDim) df;
120
0
  const size_t S = Lanes(df);  // Step.
121
122
0
  const size_t xsize = rect.xsize();
123
0
  const size_t ysize = rect.ysize();
124
0
  if ((xsize == 0) || (ysize == 0)) return;
125
126
  // Full-range BT.601 as defined by JFIF Clause 7:
127
  // https://www.itu.int/rec/T-REC-T.871-201105-I/en
128
0
  const auto c128 = Set(df, 128.0f / 255);
129
0
  const auto crcr = Set(df, 1.402f);
130
0
  const auto cgcb = Set(df, -0.114f * 1.772f / 0.587f);
131
0
  const auto cgcr = Set(df, -0.299f * 1.402f / 0.587f);
132
0
  const auto cbcb = Set(df, 1.772f);
133
134
0
  for (size_t y = 0; y < ysize; y++) {
135
0
    const float* y_row = rect.ConstPlaneRow(ycbcr, 1, y);
136
0
    const float* cb_row = rect.ConstPlaneRow(ycbcr, 0, y);
137
0
    const float* cr_row = rect.ConstPlaneRow(ycbcr, 2, y);
138
0
    float* r_row = rect.PlaneRow(rgb, 0, y);
139
0
    float* g_row = rect.PlaneRow(rgb, 1, y);
140
0
    float* b_row = rect.PlaneRow(rgb, 2, y);
141
0
    for (size_t x = 0; x < xsize; x += S) {
142
0
      const auto y_vec = Add(Load(df, y_row + x), c128);
143
0
      const auto cb_vec = Load(df, cb_row + x);
144
0
      const auto cr_vec = Load(df, cr_row + x);
145
0
      const auto r_vec = MulAdd(crcr, cr_vec, y_vec);
146
0
      const auto g_vec = MulAdd(cgcr, cr_vec, MulAdd(cgcb, cb_vec, y_vec));
147
0
      const auto b_vec = MulAdd(cbcb, cb_vec, y_vec);
148
0
      Store(r_vec, df, r_row + x);
149
0
      Store(g_vec, df, g_row + x);
150
0
      Store(b_vec, df, b_row + x);
151
0
    }
152
0
  }
153
0
  JXL_CHECK_IMAGE_INITIALIZED(*rgb, rect);
154
0
}
155
156
// NOLINTNEXTLINE(google-readability-namespace-comments)
157
}  // namespace HWY_NAMESPACE
158
}  // namespace jxl
159
HWY_AFTER_NAMESPACE();
160
161
#if HWY_ONCE
162
namespace jxl {
163
164
HWY_EXPORT(OpsinToLinearInplace);
165
Status OpsinToLinearInplace(Image3F* JXL_RESTRICT inout, ThreadPool* pool,
166
0
                            const OpsinParams& opsin_params) {
167
0
  return HWY_DYNAMIC_DISPATCH(OpsinToLinearInplace)(inout, pool, opsin_params);
168
0
}
169
170
HWY_EXPORT(OpsinToLinear);
171
Status OpsinToLinear(const Image3F& opsin, const Rect& rect, ThreadPool* pool,
172
                     Image3F* JXL_RESTRICT linear,
173
0
                     const OpsinParams& opsin_params) {
174
0
  return HWY_DYNAMIC_DISPATCH(OpsinToLinear)(opsin, rect, pool, linear,
175
0
                                             opsin_params);
176
0
}
177
178
HWY_EXPORT(YcbcrToRgb);
179
0
void YcbcrToRgb(const Image3F& ycbcr, Image3F* rgb, const Rect& rect) {
180
0
  HWY_DYNAMIC_DISPATCH(YcbcrToRgb)(ycbcr, rgb, rect);
181
0
}
182
183
HWY_EXPORT(HasFastXYBTosRGB8);
184
0
bool HasFastXYBTosRGB8() { return HWY_DYNAMIC_DISPATCH(HasFastXYBTosRGB8)(); }
185
186
HWY_EXPORT(FastXYBTosRGB8);
187
Status FastXYBTosRGB8(const float* input[4], uint8_t* output, bool is_rgba,
188
0
                      size_t xsize) {
189
0
  return HWY_DYNAMIC_DISPATCH(FastXYBTosRGB8)(input, output, is_rgba, xsize);
190
0
}
191
192
0
void OpsinParams::Init(float intensity_target) {
193
0
  InitSIMDInverseMatrix(GetOpsinAbsorbanceInverseMatrix(), inverse_opsin_matrix,
194
0
                        intensity_target);
195
0
  memcpy(opsin_biases, jxl::cms::kNegOpsinAbsorbanceBiasRGB.data(),
196
0
         sizeof(jxl::cms::kNegOpsinAbsorbanceBiasRGB));
197
0
  memcpy(quant_biases, kDefaultQuantBias, sizeof(kDefaultQuantBias));
198
0
  for (size_t c = 0; c < 4; c++) {
199
0
    opsin_biases_cbrt[c] = cbrtf(opsin_biases[c]);
200
0
  }
201
0
}
202
203
33.6k
bool CanOutputToColorEncoding(const ColorEncoding& c_desired) {
204
33.6k
  if (!c_desired.HaveFields()) {
205
5.76k
    return false;
206
5.76k
  }
207
  // TODO(veluca): keep in sync with dec_reconstruct.cc
208
27.8k
  const auto& tf = c_desired.Tf();
209
27.8k
  if (!tf.IsPQ() && !tf.IsSRGB() && !tf.have_gamma && !tf.IsLinear() &&
210
27.8k
      !tf.IsHLG() && !tf.IsDCI() && !tf.Is709()) {
211
0
    return false;
212
0
  }
213
27.8k
  if (c_desired.IsGray() && c_desired.GetWhitePointType() != WhitePoint::kD65) {
214
    // TODO(veluca): figure out what should happen here.
215
406
    return false;
216
406
  }
217
27.4k
  return true;
218
27.8k
}
219
220
33.6k
Status OutputEncodingInfo::SetFromMetadata(const CodecMetadata& metadata) {
221
33.6k
  orig_color_encoding = metadata.m.color_encoding;
222
33.6k
  orig_intensity_target = metadata.m.IntensityTarget();
223
33.6k
  desired_intensity_target = orig_intensity_target;
224
33.6k
  const auto& im = metadata.transform_data.opsin_inverse_matrix;
225
33.6k
  orig_inverse_matrix = im.inverse_matrix;
226
33.6k
  default_transform = im.all_default;
227
33.6k
  xyb_encoded = metadata.m.xyb_encoded;
228
33.6k
  std::copy(std::begin(im.opsin_biases), std::end(im.opsin_biases),
229
33.6k
            opsin_params.opsin_biases);
230
134k
  for (int i = 0; i < 3; ++i) {
231
100k
    opsin_params.opsin_biases_cbrt[i] = cbrtf(opsin_params.opsin_biases[i]);
232
100k
  }
233
33.6k
  opsin_params.opsin_biases_cbrt[3] = opsin_params.opsin_biases[3] = 1;
234
33.6k
  std::copy(std::begin(im.quant_biases), std::end(im.quant_biases),
235
33.6k
            opsin_params.quant_biases);
236
33.6k
  bool orig_ok = CanOutputToColorEncoding(orig_color_encoding);
237
33.6k
  bool orig_grey = orig_color_encoding.IsGray();
238
33.6k
  return SetColorEncoding(!xyb_encoded || orig_ok
239
33.6k
                              ? orig_color_encoding
240
33.6k
                              : ColorEncoding::LinearSRGB(orig_grey));
241
33.6k
}
242
243
Status OutputEncodingInfo::MaybeSetColorEncoding(
244
560
    const ColorEncoding& c_desired) {
245
560
  if (c_desired.GetColorSpace() == ColorSpace::kXYB &&
246
560
      ((color_encoding.GetColorSpace() == ColorSpace::kRGB &&
247
0
        color_encoding.GetPrimariesType() != Primaries::kSRGB) ||
248
0
       color_encoding.Tf().IsPQ())) {
249
0
    return false;
250
0
  }
251
560
  if (!xyb_encoded && !CanOutputToColorEncoding(c_desired)) {
252
0
    return false;
253
0
  }
254
560
  return SetColorEncoding(c_desired);
255
560
}
256
257
34.1k
Status OutputEncodingInfo::SetColorEncoding(const ColorEncoding& c_desired) {
258
34.1k
  color_encoding = c_desired;
259
34.1k
  linear_color_encoding = color_encoding;
260
34.1k
  linear_color_encoding.Tf().SetTransferFunction(TransferFunction::kLinear);
261
34.1k
  color_encoding_is_original = orig_color_encoding.SameColorEncoding(c_desired);
262
263
  // Compute the opsin inverse matrix and luminances based on primaries and
264
  // white point.
265
34.1k
  Matrix3x3 inverse_matrix;
266
34.1k
  bool inverse_matrix_is_default = default_transform;
267
34.1k
  inverse_matrix = orig_inverse_matrix;
268
34.1k
  constexpr Vector3 kSRGBLuminances{0.2126, 0.7152, 0.0722};
269
34.1k
  luminances = kSRGBLuminances;
270
34.1k
  if ((c_desired.GetPrimariesType() != Primaries::kSRGB ||
271
34.1k
       c_desired.GetWhitePointType() != WhitePoint::kD65) &&
272
34.1k
      !c_desired.IsGray()) {
273
446
    Matrix3x3 srgb_to_xyzd50;
274
446
    const auto& srgb = ColorEncoding::SRGB(/*is_gray=*/false);
275
446
    PrimariesCIExy p;
276
446
    JXL_RETURN_IF_ERROR(srgb.GetPrimaries(p));
277
446
    CIExy w = srgb.GetWhitePoint();
278
446
    JXL_RETURN_IF_ERROR(PrimariesToXYZD50(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x,
279
446
                                          p.b.y, w.x, w.y, srgb_to_xyzd50));
280
446
    Matrix3x3 original_to_xyz;
281
446
    JXL_RETURN_IF_ERROR(c_desired.GetPrimaries(p));
282
446
    w = c_desired.GetWhitePoint();
283
446
    if (!PrimariesToXYZ(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x, p.b.y, w.x, w.y,
284
446
                        original_to_xyz)) {
285
0
      return JXL_FAILURE("PrimariesToXYZ failed");
286
0
    }
287
446
    luminances = original_to_xyz[1];
288
446
    if (xyb_encoded) {
289
171
      Matrix3x3 adapt_to_d50;
290
171
      if (!AdaptToXYZD50(c_desired.GetWhitePoint().x,
291
171
                         c_desired.GetWhitePoint().y, adapt_to_d50)) {
292
0
        return JXL_FAILURE("AdaptToXYZD50 failed");
293
0
      }
294
171
      Matrix3x3 xyzd50_to_original;
295
171
      Mul3x3Matrix(adapt_to_d50, original_to_xyz, xyzd50_to_original);
296
171
      JXL_RETURN_IF_ERROR(Inv3x3Matrix(xyzd50_to_original));
297
170
      Matrix3x3 srgb_to_original;
298
170
      Mul3x3Matrix(xyzd50_to_original, srgb_to_xyzd50, srgb_to_original);
299
170
      Mul3x3Matrix(srgb_to_original, orig_inverse_matrix, inverse_matrix);
300
170
      inverse_matrix_is_default = false;
301
170
    }
302
446
  }
303
304
34.1k
  if (c_desired.IsGray()) {
305
1.91k
    Matrix3x3 tmp_inv_matrix = inverse_matrix;
306
1.91k
    Matrix3x3 srgb_to_luma{luminances, luminances, luminances};
307
1.91k
    Mul3x3Matrix(srgb_to_luma, tmp_inv_matrix, inverse_matrix);
308
1.91k
  }
309
310
  // The internal XYB color space uses absolute luminance, so we scale back the
311
  // opsin inverse matrix to relative luminance where 1.0 corresponds to the
312
  // original intensity target.
313
34.1k
  if (xyb_encoded) {
314
19.4k
    InitSIMDInverseMatrix(inverse_matrix, opsin_params.inverse_opsin_matrix,
315
19.4k
                          orig_intensity_target);
316
19.4k
    all_default_opsin = (std::abs(orig_intensity_target - 255.0) <= 0.1f &&
317
19.4k
                         inverse_matrix_is_default);
318
19.4k
  }
319
320
  // Set the inverse gamma based on color space transfer function.
321
34.1k
  const auto& tf = c_desired.Tf();
322
34.1k
  inverse_gamma = (tf.have_gamma ? tf.GetGamma()
323
34.1k
                   : tf.IsDCI()  ? 1.0f / 2.6f
324
33.6k
                                 : 1.0);
325
34.1k
  return true;
326
34.1k
}
327
328
}  // namespace jxl
329
#endif  // HWY_ONCE