/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 |