/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_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_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 | 10.0k | bool CanOutputToColorEncoding(const ColorEncoding& c_desired) { |
127 | 10.0k | if (!c_desired.HaveFields()) { |
128 | 9 | return false; |
129 | 9 | } |
130 | | // TODO(veluca): keep in sync with dec_reconstruct.cc |
131 | 10.0k | const auto& tf = c_desired.Tf(); |
132 | 10.0k | if (!tf.IsPQ() && !tf.IsSRGB() && !tf.have_gamma && !tf.IsLinear() && |
133 | 10.0k | !tf.IsHLG() && !tf.IsDCI() && !tf.Is709()) { |
134 | 0 | return false; |
135 | 0 | } |
136 | 10.0k | if (c_desired.IsGray() && c_desired.GetWhitePointType() != WhitePoint::kD65) { |
137 | | // TODO(veluca): figure out what should happen here. |
138 | 13 | return false; |
139 | 13 | } |
140 | 10.0k | return true; |
141 | 10.0k | } |
142 | | |
143 | 10.0k | Status OutputEncodingInfo::SetFromMetadata(const CodecMetadata& metadata) { |
144 | 10.0k | orig_color_encoding = metadata.m.color_encoding; |
145 | 10.0k | orig_intensity_target = metadata.m.IntensityTarget(); |
146 | 10.0k | desired_intensity_target = orig_intensity_target; |
147 | 10.0k | const auto& im = metadata.transform_data.opsin_inverse_matrix; |
148 | 10.0k | orig_inverse_matrix = im.inverse_matrix; |
149 | 10.0k | default_transform = im.all_default; |
150 | 10.0k | xyb_encoded = metadata.m.xyb_encoded; |
151 | 10.0k | std::copy(std::begin(im.opsin_biases), std::end(im.opsin_biases), |
152 | 10.0k | opsin_params.opsin_biases); |
153 | 40.2k | for (int i = 0; i < 3; ++i) { |
154 | 30.2k | opsin_params.opsin_biases_cbrt[i] = cbrtf(opsin_params.opsin_biases[i]); |
155 | 30.2k | } |
156 | 10.0k | opsin_params.opsin_biases_cbrt[3] = opsin_params.opsin_biases[3] = 1; |
157 | 10.0k | std::copy(std::begin(im.quant_biases), std::end(im.quant_biases), |
158 | 10.0k | opsin_params.quant_biases); |
159 | 10.0k | bool orig_ok = CanOutputToColorEncoding(orig_color_encoding); |
160 | 10.0k | bool orig_grey = orig_color_encoding.IsGray(); |
161 | 10.0k | return SetColorEncoding(!xyb_encoded || orig_ok |
162 | 10.0k | ? orig_color_encoding |
163 | 10.0k | : ColorEncoding::LinearSRGB(orig_grey)); |
164 | 10.0k | } |
165 | | |
166 | | Status OutputEncodingInfo::MaybeSetColorEncoding( |
167 | 558 | const ColorEncoding& c_desired) { |
168 | 558 | if (c_desired.GetColorSpace() == ColorSpace::kXYB && |
169 | 558 | ((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 | 558 | if (!xyb_encoded && !CanOutputToColorEncoding(c_desired)) { |
175 | 0 | return false; |
176 | 0 | } |
177 | 558 | return SetColorEncoding(c_desired); |
178 | 558 | } |
179 | | |
180 | 10.6k | Status OutputEncodingInfo::SetColorEncoding(const ColorEncoding& c_desired) { |
181 | 10.6k | color_encoding = c_desired; |
182 | 10.6k | linear_color_encoding = color_encoding; |
183 | 10.6k | linear_color_encoding.Tf().SetTransferFunction(TransferFunction::kLinear); |
184 | 10.6k | 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 | 10.6k | Matrix3x3 inverse_matrix; |
189 | 10.6k | bool inverse_matrix_is_default = default_transform; |
190 | 10.6k | inverse_matrix = orig_inverse_matrix; |
191 | 10.6k | constexpr Vector3 kSRGBLuminances{0.2126, 0.7152, 0.0722}; |
192 | 10.6k | luminances = kSRGBLuminances; |
193 | 10.6k | if ((c_desired.GetPrimariesType() != Primaries::kSRGB || |
194 | 10.6k | c_desired.GetWhitePointType() != WhitePoint::kD65) && |
195 | 10.6k | !c_desired.IsGray()) { |
196 | 72 | Matrix3x3 srgb_to_xyzd50; |
197 | 72 | const auto& srgb = ColorEncoding::SRGB(/*is_gray=*/false); |
198 | 72 | PrimariesCIExy p; |
199 | 72 | JXL_RETURN_IF_ERROR(srgb.GetPrimaries(p)); |
200 | 72 | CIExy w = srgb.GetWhitePoint(); |
201 | 72 | JXL_RETURN_IF_ERROR(PrimariesToXYZD50(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x, |
202 | 72 | p.b.y, w.x, w.y, srgb_to_xyzd50)); |
203 | 72 | Matrix3x3 original_to_xyz; |
204 | 72 | JXL_RETURN_IF_ERROR(c_desired.GetPrimaries(p)); |
205 | 72 | w = c_desired.GetWhitePoint(); |
206 | 72 | 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 | 72 | original_to_xyz)) { |
208 | 0 | return JXL_FAILURE("PrimariesToXYZ failed"); |
209 | 0 | } |
210 | 72 | luminances = original_to_xyz[1]; |
211 | 72 | if (xyb_encoded) { |
212 | 43 | Matrix3x3 adapt_to_d50; |
213 | 43 | if (!AdaptToXYZD50(c_desired.GetWhitePoint().x, |
214 | 43 | c_desired.GetWhitePoint().y, adapt_to_d50)) { |
215 | 0 | return JXL_FAILURE("AdaptToXYZD50 failed"); |
216 | 0 | } |
217 | 43 | Matrix3x3 xyzd50_to_original; |
218 | 43 | Mul3x3Matrix(adapt_to_d50, original_to_xyz, xyzd50_to_original); |
219 | 43 | JXL_RETURN_IF_ERROR(Inv3x3Matrix(xyzd50_to_original)); |
220 | 43 | Matrix3x3 srgb_to_original; |
221 | 43 | Mul3x3Matrix(xyzd50_to_original, srgb_to_xyzd50, srgb_to_original); |
222 | 43 | Mul3x3Matrix(srgb_to_original, orig_inverse_matrix, inverse_matrix); |
223 | 43 | inverse_matrix_is_default = false; |
224 | 43 | } |
225 | 72 | } |
226 | | |
227 | 10.6k | if (c_desired.IsGray()) { |
228 | 176 | Matrix3x3 tmp_inv_matrix = inverse_matrix; |
229 | 176 | Matrix3x3 srgb_to_luma{luminances, luminances, luminances}; |
230 | 176 | Mul3x3Matrix(srgb_to_luma, tmp_inv_matrix, inverse_matrix); |
231 | 176 | } |
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 | 10.6k | if (xyb_encoded) { |
237 | 9.65k | InitSIMDInverseMatrix(inverse_matrix, opsin_params.inverse_opsin_matrix, |
238 | 9.65k | orig_intensity_target); |
239 | 9.65k | all_default_opsin = (std::abs(orig_intensity_target - 255.0) <= 0.1f && |
240 | 9.65k | inverse_matrix_is_default); |
241 | 9.65k | } |
242 | | |
243 | | // Set the inverse gamma based on color space transfer function. |
244 | 10.6k | const auto& tf = c_desired.Tf(); |
245 | 10.6k | inverse_gamma = (tf.have_gamma ? tf.GetGamma() |
246 | 10.6k | : tf.IsDCI() ? 1.0f / 2.6f |
247 | 10.5k | : 1.0); |
248 | 10.6k | return true; |
249 | 10.6k | } |
250 | | |
251 | | } // namespace jxl |
252 | | #endif // HWY_ONCE |