Coverage Report

Created: 2025-08-11 08:01

/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 <algorithm>
9
#include <cmath>
10
#include <cstdint>
11
#include <cstdlib>
12
#include <cstring>
13
#include <iterator>
14
15
#include "lib/jxl/base/data_parallel.h"
16
#include "lib/jxl/image_metadata.h"
17
#include "lib/jxl/image_ops.h"
18
19
#undef HWY_TARGET_INCLUDE
20
#define HWY_TARGET_INCLUDE "lib/jxl/dec_xyb.cc"
21
#include <hwy/foreach_target.h>
22
#include <hwy/highway.h>
23
24
#include "lib/jxl/base/compiler_specific.h"
25
#include "lib/jxl/base/matrix_ops.h"
26
#include "lib/jxl/base/rect.h"
27
#include "lib/jxl/base/sanitizers.h"
28
#include "lib/jxl/base/status.h"
29
#include "lib/jxl/cms/jxl_cms_internal.h"
30
#include "lib/jxl/cms/opsin_params.h"
31
#include "lib/jxl/color_encoding_internal.h"
32
#include "lib/jxl/common.h"
33
#include "lib/jxl/dec_xyb-inl.h"
34
#include "lib/jxl/image.h"
35
#include "lib/jxl/opsin_params.h"
36
#include "lib/jxl/quantizer.h"
37
HWY_BEFORE_NAMESPACE();
38
namespace jxl {
39
namespace HWY_NAMESPACE {
40
41
// These templates are not found via ADL.
42
using hwy::HWY_NAMESPACE::MulAdd;
43
44
// Same, but not in-place.
45
Status OpsinToLinear(const Image3F& opsin, const Rect& rect, ThreadPool* pool,
46
                     Image3F* JXL_RESTRICT linear,
47
0
                     const OpsinParams& opsin_params) {
48
0
  JXL_ENSURE(SameSize(rect, *linear));
49
0
  JXL_CHECK_IMAGE_INITIALIZED(opsin, rect);
50
51
0
  const auto process_row = [&](const uint32_t task,
52
0
                               size_t /*thread*/) -> Status {
53
0
    const size_t y = static_cast<size_t>(task);
54
55
    // Faster than adding via ByteOffset at end of loop.
56
0
    const float* JXL_RESTRICT row_opsin_0 = rect.ConstPlaneRow(opsin, 0, y);
57
0
    const float* JXL_RESTRICT row_opsin_1 = rect.ConstPlaneRow(opsin, 1, y);
58
0
    const float* JXL_RESTRICT row_opsin_2 = rect.ConstPlaneRow(opsin, 2, y);
59
0
    float* JXL_RESTRICT row_linear_0 = linear->PlaneRow(0, y);
60
0
    float* JXL_RESTRICT row_linear_1 = linear->PlaneRow(1, y);
61
0
    float* JXL_RESTRICT row_linear_2 = linear->PlaneRow(2, y);
62
63
0
    const HWY_FULL(float) d;
64
65
0
    for (size_t x = 0; x < rect.xsize(); x += Lanes(d)) {
66
0
      const auto in_opsin_x = Load(d, row_opsin_0 + x);
67
0
      const auto in_opsin_y = Load(d, row_opsin_1 + x);
68
0
      const auto in_opsin_b = Load(d, row_opsin_2 + x);
69
0
      auto linear_r = Undefined(d);
70
0
      auto linear_g = Undefined(d);
71
0
      auto linear_b = Undefined(d);
72
0
      XybToRgb(d, in_opsin_x, in_opsin_y, in_opsin_b, opsin_params, &linear_r,
73
0
               &linear_g, &linear_b);
74
75
0
      Store(linear_r, d, row_linear_0 + x);
76
0
      Store(linear_g, d, row_linear_1 + x);
77
0
      Store(linear_b, d, row_linear_2 + x);
78
0
    }
79
0
    return true;
80
0
  };
Unexecuted instantiation: dec_xyb.cc:jxl::N_SSE4::OpsinToLinear(jxl::Image3<float> const&, jxl::RectT<unsigned long> const&, jxl::ThreadPool*, jxl::Image3<float>*, jxl::OpsinParams const&)::$_0::operator()(unsigned int, unsigned long) const
Unexecuted instantiation: dec_xyb.cc:jxl::N_AVX2::OpsinToLinear(jxl::Image3<float> const&, jxl::RectT<unsigned long> const&, jxl::ThreadPool*, jxl::Image3<float>*, jxl::OpsinParams const&)::$_0::operator()(unsigned int, unsigned long) const
Unexecuted instantiation: dec_xyb.cc:jxl::N_AVX3::OpsinToLinear(jxl::Image3<float> const&, jxl::RectT<unsigned long> const&, jxl::ThreadPool*, jxl::Image3<float>*, jxl::OpsinParams const&)::$_0::operator()(unsigned int, unsigned long) const
Unexecuted instantiation: dec_xyb.cc:jxl::N_AVX3_ZEN4::OpsinToLinear(jxl::Image3<float> const&, jxl::RectT<unsigned long> const&, jxl::ThreadPool*, jxl::Image3<float>*, jxl::OpsinParams const&)::$_0::operator()(unsigned int, unsigned long) const
Unexecuted instantiation: dec_xyb.cc:jxl::N_AVX3_SPR::OpsinToLinear(jxl::Image3<float> const&, jxl::RectT<unsigned long> const&, jxl::ThreadPool*, jxl::Image3<float>*, jxl::OpsinParams const&)::$_0::operator()(unsigned int, unsigned long) const
Unexecuted instantiation: dec_xyb.cc:jxl::N_SSE2::OpsinToLinear(jxl::Image3<float> const&, jxl::RectT<unsigned long> const&, jxl::ThreadPool*, jxl::Image3<float>*, jxl::OpsinParams const&)::$_0::operator()(unsigned int, unsigned long) const
81
0
  JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<int>(rect.ysize()),
82
0
                                ThreadPool::NoInit, process_row,
83
0
                                "OpsinToLinear(Rect)"));
84
0
  JXL_CHECK_IMAGE_INITIALIZED(*linear, rect);
85
0
  return true;
86
0
}
Unexecuted instantiation: jxl::N_SSE4::OpsinToLinear(jxl::Image3<float> const&, jxl::RectT<unsigned long> const&, jxl::ThreadPool*, jxl::Image3<float>*, jxl::OpsinParams const&)
Unexecuted instantiation: jxl::N_AVX2::OpsinToLinear(jxl::Image3<float> const&, jxl::RectT<unsigned long> const&, jxl::ThreadPool*, jxl::Image3<float>*, jxl::OpsinParams const&)
Unexecuted instantiation: jxl::N_AVX3::OpsinToLinear(jxl::Image3<float> const&, jxl::RectT<unsigned long> const&, jxl::ThreadPool*, jxl::Image3<float>*, jxl::OpsinParams const&)
Unexecuted instantiation: jxl::N_AVX3_ZEN4::OpsinToLinear(jxl::Image3<float> const&, jxl::RectT<unsigned long> const&, jxl::ThreadPool*, jxl::Image3<float>*, jxl::OpsinParams const&)
Unexecuted instantiation: jxl::N_AVX3_SPR::OpsinToLinear(jxl::Image3<float> const&, jxl::RectT<unsigned long> const&, jxl::ThreadPool*, jxl::Image3<float>*, jxl::OpsinParams const&)
Unexecuted instantiation: jxl::N_SSE2::OpsinToLinear(jxl::Image3<float> const&, jxl::RectT<unsigned long> const&, jxl::ThreadPool*, jxl::Image3<float>*, jxl::OpsinParams const&)
87
88
// NOLINTNEXTLINE(google-readability-namespace-comments)
89
}  // namespace HWY_NAMESPACE
90
}  // namespace jxl
91
HWY_AFTER_NAMESPACE();
92
93
#if HWY_ONCE
94
namespace jxl {
95
96
HWY_EXPORT(OpsinToLinear);
97
Status OpsinToLinear(const Image3F& opsin, const Rect& rect, ThreadPool* pool,
98
                     Image3F* JXL_RESTRICT linear,
99
0
                     const OpsinParams& opsin_params) {
100
0
  return HWY_DYNAMIC_DISPATCH(OpsinToLinear)(opsin, rect, pool, linear,
101
0
                                             opsin_params);
102
0
}
103
104
#if !JXL_HIGH_PRECISION
105
HWY_EXPORT(HasFastXYBTosRGB8);
106
bool HasFastXYBTosRGB8() { return HWY_DYNAMIC_DISPATCH(HasFastXYBTosRGB8)(); }
107
108
HWY_EXPORT(FastXYBTosRGB8);
109
Status FastXYBTosRGB8(const float* input[4], uint8_t* output, bool is_rgba,
110
                      size_t xsize) {
111
  return HWY_DYNAMIC_DISPATCH(FastXYBTosRGB8)(input, output, is_rgba, xsize);
112
}
113
#endif  // !JXL_HIGH_PRECISION
114
115
0
void OpsinParams::Init(float intensity_target) {
116
0
  InitSIMDInverseMatrix(GetOpsinAbsorbanceInverseMatrix(), inverse_opsin_matrix,
117
0
                        intensity_target);
118
0
  memcpy(opsin_biases, jxl::cms::kNegOpsinAbsorbanceBiasRGB.data(),
119
0
         sizeof(jxl::cms::kNegOpsinAbsorbanceBiasRGB));
120
0
  memcpy(quant_biases, kDefaultQuantBias, sizeof(kDefaultQuantBias));
121
0
  for (size_t c = 0; c < 4; c++) {
122
0
    opsin_biases_cbrt[c] = cbrtf(opsin_biases[c]);
123
0
  }
124
0
}
125
126
23.0k
bool CanOutputToColorEncoding(const ColorEncoding& c_desired) {
127
23.0k
  if (!c_desired.HaveFields()) {
128
9
    return false;
129
9
  }
130
  // TODO(veluca): keep in sync with dec_reconstruct.cc
131
23.0k
  const auto& tf = c_desired.Tf();
132
23.0k
  if (!tf.IsPQ() && !tf.IsSRGB() && !tf.have_gamma && !tf.IsLinear() &&
133
23.0k
      !tf.IsHLG() && !tf.IsDCI() && !tf.Is709()) {
134
0
    return false;
135
0
  }
136
23.0k
  if (c_desired.IsGray() && c_desired.GetWhitePointType() != WhitePoint::kD65) {
137
    // TODO(veluca): figure out what should happen here.
138
79
    return false;
139
79
  }
140
22.9k
  return true;
141
23.0k
}
142
143
23.0k
Status OutputEncodingInfo::SetFromMetadata(const CodecMetadata& metadata) {
144
23.0k
  orig_color_encoding = metadata.m.color_encoding;
145
23.0k
  orig_intensity_target = metadata.m.IntensityTarget();
146
23.0k
  desired_intensity_target = orig_intensity_target;
147
23.0k
  const auto& im = metadata.transform_data.opsin_inverse_matrix;
148
23.0k
  orig_inverse_matrix = im.inverse_matrix;
149
23.0k
  default_transform = im.all_default;
150
23.0k
  xyb_encoded = metadata.m.xyb_encoded;
151
23.0k
  std::copy(std::begin(im.opsin_biases), std::end(im.opsin_biases),
152
23.0k
            opsin_params.opsin_biases);
153
92.2k
  for (int i = 0; i < 3; ++i) {
154
69.1k
    opsin_params.opsin_biases_cbrt[i] = cbrtf(opsin_params.opsin_biases[i]);
155
69.1k
  }
156
23.0k
  opsin_params.opsin_biases_cbrt[3] = opsin_params.opsin_biases[3] = 1;
157
23.0k
  std::copy(std::begin(im.quant_biases), std::end(im.quant_biases),
158
23.0k
            opsin_params.quant_biases);
159
23.0k
  bool orig_ok = CanOutputToColorEncoding(orig_color_encoding);
160
23.0k
  bool orig_grey = orig_color_encoding.IsGray();
161
23.0k
  return SetColorEncoding(!xyb_encoded || orig_ok
162
23.0k
                              ? orig_color_encoding
163
23.0k
                              : ColorEncoding::LinearSRGB(orig_grey));
164
23.0k
}
165
166
Status OutputEncodingInfo::MaybeSetColorEncoding(
167
6.87k
    const ColorEncoding& c_desired) {
168
6.87k
  if (c_desired.GetColorSpace() == ColorSpace::kXYB &&
169
6.87k
      ((color_encoding.GetColorSpace() == ColorSpace::kRGB &&
170
0
        color_encoding.GetPrimariesType() != Primaries::kSRGB) ||
171
0
       color_encoding.Tf().IsPQ())) {
172
0
    return false;
173
0
  }
174
6.87k
  if (!xyb_encoded && !CanOutputToColorEncoding(c_desired)) {
175
0
    return false;
176
0
  }
177
6.87k
  return SetColorEncoding(c_desired);
178
6.87k
}
179
180
29.9k
Status OutputEncodingInfo::SetColorEncoding(const ColorEncoding& c_desired) {
181
29.9k
  color_encoding = c_desired;
182
29.9k
  linear_color_encoding = color_encoding;
183
29.9k
  linear_color_encoding.Tf().SetTransferFunction(TransferFunction::kLinear);
184
29.9k
  color_encoding_is_original = orig_color_encoding.SameColorEncoding(c_desired);
185
186
  // Compute the opsin inverse matrix and luminances based on primaries and
187
  // white point.
188
29.9k
  Matrix3x3 inverse_matrix;
189
29.9k
  bool inverse_matrix_is_default = default_transform;
190
29.9k
  inverse_matrix = orig_inverse_matrix;
191
29.9k
  constexpr Vector3 kSRGBLuminances{0.2126, 0.7152, 0.0722};
192
29.9k
  luminances = kSRGBLuminances;
193
29.9k
  if ((c_desired.GetPrimariesType() != Primaries::kSRGB ||
194
29.9k
       c_desired.GetWhitePointType() != WhitePoint::kD65) &&
195
29.9k
      !c_desired.IsGray()) {
196
198
    Matrix3x3 srgb_to_xyzd50;
197
198
    const auto& srgb = ColorEncoding::SRGB(/*is_gray=*/false);
198
198
    PrimariesCIExy p;
199
198
    JXL_RETURN_IF_ERROR(srgb.GetPrimaries(p));
200
198
    CIExy w = srgb.GetWhitePoint();
201
198
    JXL_RETURN_IF_ERROR(PrimariesToXYZD50(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x,
202
198
                                          p.b.y, w.x, w.y, srgb_to_xyzd50));
203
198
    Matrix3x3 original_to_xyz;
204
198
    JXL_RETURN_IF_ERROR(c_desired.GetPrimaries(p));
205
198
    w = c_desired.GetWhitePoint();
206
198
    if (!PrimariesToXYZ(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x, p.b.y, w.x, w.y,
207
198
                        original_to_xyz)) {
208
0
      return JXL_FAILURE("PrimariesToXYZ failed");
209
0
    }
210
198
    luminances = original_to_xyz[1];
211
198
    if (xyb_encoded) {
212
166
      Matrix3x3 adapt_to_d50;
213
166
      if (!AdaptToXYZD50(c_desired.GetWhitePoint().x,
214
166
                         c_desired.GetWhitePoint().y, adapt_to_d50)) {
215
0
        return JXL_FAILURE("AdaptToXYZD50 failed");
216
0
      }
217
166
      Matrix3x3 xyzd50_to_original;
218
166
      Mul3x3Matrix(adapt_to_d50, original_to_xyz, xyzd50_to_original);
219
166
      JXL_RETURN_IF_ERROR(Inv3x3Matrix(xyzd50_to_original));
220
166
      Matrix3x3 srgb_to_original;
221
166
      Mul3x3Matrix(xyzd50_to_original, srgb_to_xyzd50, srgb_to_original);
222
166
      Mul3x3Matrix(srgb_to_original, orig_inverse_matrix, inverse_matrix);
223
166
      inverse_matrix_is_default = false;
224
166
    }
225
198
  }
226
227
29.9k
  if (c_desired.IsGray()) {
228
271
    Matrix3x3 tmp_inv_matrix = inverse_matrix;
229
271
    Matrix3x3 srgb_to_luma{luminances, luminances, luminances};
230
271
    Mul3x3Matrix(srgb_to_luma, tmp_inv_matrix, inverse_matrix);
231
271
  }
232
233
  // The internal XYB color space uses absolute luminance, so we scale back the
234
  // opsin inverse matrix to relative luminance where 1.0 corresponds to the
235
  // original intensity target.
236
29.9k
  if (xyb_encoded) {
237
22.2k
    InitSIMDInverseMatrix(inverse_matrix, opsin_params.inverse_opsin_matrix,
238
22.2k
                          orig_intensity_target);
239
22.2k
    all_default_opsin = (std::abs(orig_intensity_target - 255.0) <= 0.1f &&
240
22.2k
                         inverse_matrix_is_default);
241
22.2k
  }
242
243
  // Set the inverse gamma based on color space transfer function.
244
29.9k
  const auto& tf = c_desired.Tf();
245
29.9k
  inverse_gamma = (tf.have_gamma ? tf.GetGamma()
246
29.9k
                   : tf.IsDCI()  ? 1.0f / 2.6f
247
28.8k
                                 : 1.0);
248
29.9k
  return true;
249
29.9k
}
250
251
}  // namespace jxl
252
#endif  // HWY_ONCE