1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file.
6 #include "lib/jpegli/color_transform.h"
8 #undef HWY_TARGET_INCLUDE
9 #define HWY_TARGET_INCLUDE "lib/jpegli/color_transform.cc"
10 #include <hwy/foreach_target.h>
11 #include <hwy/highway.h>
13 #include "lib/jpegli/decode_internal.h"
14 #include "lib/jpegli/encode_internal.h"
15 #include "lib/jpegli/error.h"
16 #include "lib/jxl/base/compiler_specific.h"
18 HWY_BEFORE_NAMESPACE();
20 namespace HWY_NAMESPACE
{
22 // These templates are not found via ADL.
23 using hwy::HWY_NAMESPACE::Add
;
24 using hwy::HWY_NAMESPACE::Div
;
25 using hwy::HWY_NAMESPACE::Mul
;
26 using hwy::HWY_NAMESPACE::MulAdd
;
27 using hwy::HWY_NAMESPACE::Sub
;
29 void YCbCrToRGB(float* row
[kMaxComponents
], size_t xsize
) {
30 const HWY_CAPPED(float, 8) df
;
31 float* JXL_RESTRICT row0
= row
[0];
32 float* JXL_RESTRICT row1
= row
[1];
33 float* JXL_RESTRICT row2
= row
[2];
35 // Full-range BT.601 as defined by JFIF Clause 7:
36 // https://www.itu.int/rec/T-REC-T.871-201105-I/en
37 const auto crcr
= Set(df
, 1.402f
);
38 const auto cgcb
= Set(df
, -0.114f
* 1.772f
/ 0.587f
);
39 const auto cgcr
= Set(df
, -0.299f
* 1.402f
/ 0.587f
);
40 const auto cbcb
= Set(df
, 1.772f
);
42 for (size_t x
= 0; x
< xsize
; x
+= Lanes(df
)) {
43 const auto y_vec
= Load(df
, row0
+ x
);
44 const auto cb_vec
= Load(df
, row1
+ x
);
45 const auto cr_vec
= Load(df
, row2
+ x
);
46 const auto r_vec
= MulAdd(crcr
, cr_vec
, y_vec
);
47 const auto g_vec
= MulAdd(cgcr
, cr_vec
, MulAdd(cgcb
, cb_vec
, y_vec
));
48 const auto b_vec
= MulAdd(cbcb
, cb_vec
, y_vec
);
49 Store(r_vec
, df
, row0
+ x
);
50 Store(g_vec
, df
, row1
+ x
);
51 Store(b_vec
, df
, row2
+ x
);
55 void YCCKToCMYK(float* row
[kMaxComponents
], size_t xsize
) {
56 const HWY_CAPPED(float, 8) df
;
57 float* JXL_RESTRICT row0
= row
[0];
58 float* JXL_RESTRICT row1
= row
[1];
59 float* JXL_RESTRICT row2
= row
[2];
60 YCbCrToRGB(row
, xsize
);
61 const auto offset
= Set(df
, -1.0f
/ 255.0f
);
62 for (size_t x
= 0; x
< xsize
; x
+= Lanes(df
)) {
63 Store(Sub(offset
, Load(df
, row0
+ x
)), df
, row0
+ x
);
64 Store(Sub(offset
, Load(df
, row1
+ x
)), df
, row1
+ x
);
65 Store(Sub(offset
, Load(df
, row2
+ x
)), df
, row2
+ x
);
69 void RGBToYCbCr(float* row
[kMaxComponents
], size_t xsize
) {
70 const HWY_CAPPED(float, 8) df
;
71 float* JXL_RESTRICT row0
= row
[0];
72 float* JXL_RESTRICT row1
= row
[1];
73 float* JXL_RESTRICT row2
= row
[2];
74 // Full-range BT.601 as defined by JFIF Clause 7:
75 // https://www.itu.int/rec/T-REC-T.871-201105-I/en
76 const auto c128
= Set(df
, 128.0f
);
77 const auto kR
= Set(df
, 0.299f
); // NTSC luma
78 const auto kG
= Set(df
, 0.587f
);
79 const auto kB
= Set(df
, 0.114f
);
80 const auto kAmpR
= Set(df
, 0.701f
);
81 const auto kAmpB
= Set(df
, 0.886f
);
82 const auto kDiffR
= Add(kAmpR
, kR
);
83 const auto kDiffB
= Add(kAmpB
, kB
);
84 const auto kNormR
= Div(Set(df
, 1.0f
), (Add(kAmpR
, Add(kG
, kB
))));
85 const auto kNormB
= Div(Set(df
, 1.0f
), (Add(kR
, Add(kG
, kAmpB
))));
87 for (size_t x
= 0; x
< xsize
; x
+= Lanes(df
)) {
88 const auto r
= Load(df
, row0
+ x
);
89 const auto g
= Load(df
, row1
+ x
);
90 const auto b
= Load(df
, row2
+ x
);
91 const auto r_base
= Mul(r
, kR
);
92 const auto r_diff
= Mul(r
, kDiffR
);
93 const auto g_base
= Mul(g
, kG
);
94 const auto b_base
= Mul(b
, kB
);
95 const auto b_diff
= Mul(b
, kDiffB
);
96 const auto y_base
= Add(r_base
, Add(g_base
, b_base
));
97 const auto cb_vec
= MulAdd(Sub(b_diff
, y_base
), kNormB
, c128
);
98 const auto cr_vec
= MulAdd(Sub(r_diff
, y_base
), kNormR
, c128
);
99 Store(y_base
, df
, row0
+ x
);
100 Store(cb_vec
, df
, row1
+ x
);
101 Store(cr_vec
, df
, row2
+ x
);
105 void CMYKToYCCK(float* row
[kMaxComponents
], size_t xsize
) {
106 const HWY_CAPPED(float, 8) df
;
107 float* JXL_RESTRICT row0
= row
[0];
108 float* JXL_RESTRICT row1
= row
[1];
109 float* JXL_RESTRICT row2
= row
[2];
110 const auto unity
= Set(df
, 255.0f
);
111 for (size_t x
= 0; x
< xsize
; x
+= Lanes(df
)) {
112 Store(Sub(unity
, Load(df
, row0
+ x
)), df
, row0
+ x
);
113 Store(Sub(unity
, Load(df
, row1
+ x
)), df
, row1
+ x
);
114 Store(Sub(unity
, Load(df
, row2
+ x
)), df
, row2
+ x
);
116 RGBToYCbCr(row
, xsize
);
119 // NOLINTNEXTLINE(google-readability-namespace-comments)
120 } // namespace HWY_NAMESPACE
121 } // namespace jpegli
122 HWY_AFTER_NAMESPACE();
127 HWY_EXPORT(CMYKToYCCK
);
128 HWY_EXPORT(YCCKToCMYK
);
129 HWY_EXPORT(YCbCrToRGB
);
130 HWY_EXPORT(RGBToYCbCr
);
132 bool CheckColorSpaceComponents(int num_components
, J_COLOR_SPACE colorspace
) {
133 switch (colorspace
) {
135 return num_components
== 1;
140 return num_components
== 3;
151 return num_components
== 4;
153 // Unrecognized colorspaces can have any number of channels, since no
154 // color transform will be performed on them.
159 void NullTransform(float* row
[kMaxComponents
], size_t len
) {}
161 void GrayscaleToRGB(float* row
[kMaxComponents
], size_t len
) {
162 memcpy(row
[1], row
[0], len
* sizeof(row
[1][0]));
163 memcpy(row
[2], row
[0], len
* sizeof(row
[2][0]));
166 void GrayscaleToYCbCr(float* row
[kMaxComponents
], size_t len
) {
167 memset(row
[1], 0, len
* sizeof(row
[1][0]));
168 memset(row
[2], 0, len
* sizeof(row
[2][0]));
171 void ChooseColorTransform(j_compress_ptr cinfo
) {
172 jpeg_comp_master
* m
= cinfo
->master
;
173 if (!CheckColorSpaceComponents(cinfo
->input_components
,
174 cinfo
->in_color_space
)) {
175 JPEGLI_ERROR("Invalid number of input components %d for colorspace %d",
176 cinfo
->input_components
, cinfo
->in_color_space
);
178 if (!CheckColorSpaceComponents(cinfo
->num_components
,
179 cinfo
->jpeg_color_space
)) {
180 JPEGLI_ERROR("Invalid number of components %d for colorspace %d",
181 cinfo
->num_components
, cinfo
->jpeg_color_space
);
183 if (cinfo
->jpeg_color_space
== cinfo
->in_color_space
) {
184 if (cinfo
->num_components
!= cinfo
->input_components
) {
185 JPEGLI_ERROR("Input/output components mismatch: %d vs %d",
186 cinfo
->input_components
, cinfo
->num_components
);
188 // No color transform requested.
189 m
->color_transform
= NullTransform
;
193 if (cinfo
->in_color_space
== JCS_RGB
&& m
->xyb_mode
) {
194 JPEGLI_ERROR("Color transform on XYB colorspace is not supported.");
197 m
->color_transform
= nullptr;
198 if (cinfo
->jpeg_color_space
== JCS_GRAYSCALE
) {
199 if (cinfo
->in_color_space
== JCS_RGB
) {
200 m
->color_transform
= HWY_DYNAMIC_DISPATCH(RGBToYCbCr
);
201 } else if (cinfo
->in_color_space
== JCS_YCbCr
||
202 cinfo
->in_color_space
== JCS_YCCK
) {
203 // Since the first luminance channel is the grayscale version of the
204 // image, nothing to do here
205 m
->color_transform
= NullTransform
;
207 } else if (cinfo
->jpeg_color_space
== JCS_RGB
) {
208 if (cinfo
->in_color_space
== JCS_GRAYSCALE
) {
209 m
->color_transform
= GrayscaleToRGB
;
211 } else if (cinfo
->jpeg_color_space
== JCS_YCbCr
) {
212 if (cinfo
->in_color_space
== JCS_RGB
) {
213 m
->color_transform
= HWY_DYNAMIC_DISPATCH(RGBToYCbCr
);
214 } else if (cinfo
->in_color_space
== JCS_GRAYSCALE
) {
215 m
->color_transform
= GrayscaleToYCbCr
;
217 } else if (cinfo
->jpeg_color_space
== JCS_YCCK
) {
218 if (cinfo
->in_color_space
== JCS_CMYK
) {
219 m
->color_transform
= HWY_DYNAMIC_DISPATCH(CMYKToYCCK
);
223 if (m
->color_transform
== nullptr) {
224 // TODO(szabadka) Support more color transforms.
225 JPEGLI_ERROR("Unsupported color transform %d -> %d", cinfo
->in_color_space
,
226 cinfo
->jpeg_color_space
);
230 void ChooseColorTransform(j_decompress_ptr cinfo
) {
231 jpeg_decomp_master
* m
= cinfo
->master
;
232 if (!CheckColorSpaceComponents(cinfo
->out_color_components
,
233 cinfo
->out_color_space
)) {
234 JPEGLI_ERROR("Invalid number of output components %d for colorspace %d",
235 cinfo
->out_color_components
, cinfo
->out_color_space
);
237 if (!CheckColorSpaceComponents(cinfo
->num_components
,
238 cinfo
->jpeg_color_space
)) {
239 JPEGLI_ERROR("Invalid number of components %d for colorspace %d",
240 cinfo
->num_components
, cinfo
->jpeg_color_space
);
242 if (cinfo
->jpeg_color_space
== cinfo
->out_color_space
) {
243 if (cinfo
->num_components
!= cinfo
->out_color_components
) {
244 JPEGLI_ERROR("Input/output components mismatch: %d vs %d",
245 cinfo
->num_components
, cinfo
->out_color_components
);
247 // No color transform requested.
248 m
->color_transform
= NullTransform
;
252 m
->color_transform
= nullptr;
253 if (cinfo
->jpeg_color_space
== JCS_GRAYSCALE
) {
254 if (cinfo
->out_color_space
== JCS_RGB
) {
255 m
->color_transform
= GrayscaleToRGB
;
257 } else if (cinfo
->jpeg_color_space
== JCS_RGB
) {
258 if (cinfo
->out_color_space
== JCS_GRAYSCALE
) {
259 m
->color_transform
= HWY_DYNAMIC_DISPATCH(RGBToYCbCr
);
261 } else if (cinfo
->jpeg_color_space
== JCS_YCbCr
) {
262 if (cinfo
->out_color_space
== JCS_RGB
) {
263 m
->color_transform
= HWY_DYNAMIC_DISPATCH(YCbCrToRGB
);
264 } else if (cinfo
->out_color_space
== JCS_GRAYSCALE
) {
265 m
->color_transform
= NullTransform
;
267 } else if (cinfo
->jpeg_color_space
== JCS_YCCK
) {
268 if (cinfo
->out_color_space
== JCS_CMYK
) {
269 m
->color_transform
= HWY_DYNAMIC_DISPATCH(YCCKToCMYK
);
273 if (m
->color_transform
== nullptr) {
274 // TODO(szabadka) Support more color transforms.
275 JPEGLI_ERROR("Unsupported color transform %d -> %d",
276 cinfo
->jpeg_color_space
, cinfo
->out_color_space
);
280 } // namespace jpegli