/src/openexr/src/lib/OpenEXR/ImfRgbaYca.cpp
Line | Count | Source |
1 | | // |
2 | | // SPDX-License-Identifier: BSD-3-Clause |
3 | | // Copyright (c) Contributors to the OpenEXR Project. |
4 | | // |
5 | | |
6 | | //----------------------------------------------------------------------------- |
7 | | // |
8 | | // Conversion between RGBA and YCA data. |
9 | | // |
10 | | //----------------------------------------------------------------------------- |
11 | | |
12 | | #include <ImfRgbaYca.h> |
13 | | #include <algorithm> |
14 | | #include <assert.h> |
15 | | |
16 | | using namespace IMATH_NAMESPACE; |
17 | | using namespace std; |
18 | | #include "ImfNamespace.h" |
19 | | |
20 | | OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER |
21 | | |
22 | | namespace RgbaYca |
23 | | { |
24 | | |
25 | | V3f |
26 | | computeYw (const Chromaticities& cr) |
27 | 309 | { |
28 | 309 | M44f m = RGBtoXYZ (cr, 1); |
29 | 309 | return V3f (m[0][1], m[1][1], m[2][1]) / (m[0][1] + m[1][1] + m[2][1]); |
30 | 309 | } |
31 | | |
32 | | void |
33 | | RGBAtoYCA ( |
34 | | const V3f& yw, |
35 | | int n, |
36 | | bool aIsValid, |
37 | | const Rgba rgbaIn[/*n*/], |
38 | | Rgba ycaOut[/*n*/]) |
39 | 0 | { |
40 | 0 | for (int i = 0; i < n; ++i) |
41 | 0 | { |
42 | 0 | Rgba in = rgbaIn[i]; |
43 | 0 | Rgba& out = ycaOut[i]; |
44 | | |
45 | | // |
46 | | // Conversion to YCA and subsequent chroma subsampling |
47 | | // work only if R, G and B are finite and non-negative. |
48 | | // |
49 | |
|
50 | 0 | if (!in.r.isFinite () || in.r < 0) in.r = 0; |
51 | |
|
52 | 0 | if (!in.g.isFinite () || in.g < 0) in.g = 0; |
53 | |
|
54 | 0 | if (!in.b.isFinite () || in.b < 0) in.b = 0; |
55 | |
|
56 | 0 | if (in.r == in.g && in.g == in.b) |
57 | 0 | { |
58 | | // |
59 | | // Special case -- R, G and B are equal. To avoid rounding |
60 | | // errors, we explicitly set the output luminance channel |
61 | | // to G, and the chroma channels to 0. |
62 | | // |
63 | | // The special cases here and in YCAtoRGBA() ensure that |
64 | | // converting black-and white images from RGBA to YCA and |
65 | | // back is lossless. |
66 | | // |
67 | |
|
68 | 0 | out.r = 0; |
69 | 0 | out.g = in.g; |
70 | 0 | out.b = 0; |
71 | 0 | } |
72 | 0 | else |
73 | 0 | { |
74 | 0 | out.g = in.r * yw.x + in.g * yw.y + in.b * yw.z; |
75 | |
|
76 | 0 | float Y = out.g; |
77 | |
|
78 | 0 | if (abs (in.r - Y) < HALF_MAX * Y) |
79 | 0 | out.r = (in.r - Y) / Y; |
80 | 0 | else |
81 | 0 | out.r = 0; |
82 | |
|
83 | 0 | if (abs (in.b - Y) < HALF_MAX * Y) |
84 | 0 | out.b = (in.b - Y) / Y; |
85 | 0 | else |
86 | 0 | out.b = 0; |
87 | 0 | } |
88 | |
|
89 | 0 | if (aIsValid) |
90 | 0 | out.a = in.a; |
91 | 0 | else |
92 | 0 | out.a = 1; |
93 | 0 | } |
94 | 0 | } |
95 | | |
96 | | void |
97 | | decimateChromaHoriz (int n, const Rgba ycaIn[/*n+N-1*/], Rgba ycaOut[/*n*/]) |
98 | 0 | { |
99 | | #ifdef DEBUG |
100 | | assert (ycaIn != ycaOut); |
101 | | #endif |
102 | |
|
103 | 0 | int begin = N2; |
104 | 0 | int end = begin + n; |
105 | |
|
106 | 0 | for (int i = begin, j = 0; i < end; ++i, ++j) |
107 | 0 | { |
108 | 0 | if ((j & 1) == 0) |
109 | 0 | { |
110 | 0 | ycaOut[j].r = |
111 | 0 | ycaIn[i - 13].r * 0.001064f + ycaIn[i - 11].r * -0.003771f + |
112 | 0 | ycaIn[i - 9].r * 0.009801f + ycaIn[i - 7].r * -0.021586f + |
113 | 0 | ycaIn[i - 5].r * 0.043978f + ycaIn[i - 3].r * -0.093067f + |
114 | 0 | ycaIn[i - 1].r * 0.313659f + ycaIn[i].r * 0.499846f + |
115 | 0 | ycaIn[i + 1].r * 0.313659f + ycaIn[i + 3].r * -0.093067f + |
116 | 0 | ycaIn[i + 5].r * 0.043978f + ycaIn[i + 7].r * -0.021586f + |
117 | 0 | ycaIn[i + 9].r * 0.009801f + ycaIn[i + 11].r * -0.003771f + |
118 | 0 | ycaIn[i + 13].r * 0.001064f; |
119 | |
|
120 | 0 | ycaOut[j].b = |
121 | 0 | ycaIn[i - 13].b * 0.001064f + ycaIn[i - 11].b * -0.003771f + |
122 | 0 | ycaIn[i - 9].b * 0.009801f + ycaIn[i - 7].b * -0.021586f + |
123 | 0 | ycaIn[i - 5].b * 0.043978f + ycaIn[i - 3].b * -0.093067f + |
124 | 0 | ycaIn[i - 1].b * 0.313659f + ycaIn[i].b * 0.499846f + |
125 | 0 | ycaIn[i + 1].b * 0.313659f + ycaIn[i + 3].b * -0.093067f + |
126 | 0 | ycaIn[i + 5].b * 0.043978f + ycaIn[i + 7].b * -0.021586f + |
127 | 0 | ycaIn[i + 9].b * 0.009801f + ycaIn[i + 11].b * -0.003771f + |
128 | 0 | ycaIn[i + 13].b * 0.001064f; |
129 | 0 | } |
130 | |
|
131 | 0 | ycaOut[j].g = ycaIn[i].g; |
132 | 0 | ycaOut[j].a = ycaIn[i].a; |
133 | 0 | } |
134 | 0 | } |
135 | | |
136 | | void |
137 | | decimateChromaVert (int n, const Rgba* const ycaIn[N], Rgba ycaOut[/*n*/]) |
138 | 0 | { |
139 | 0 | for (int i = 0; i < n; ++i) |
140 | 0 | { |
141 | 0 | if ((i & 1) == 0) |
142 | 0 | { |
143 | 0 | ycaOut[i].r = |
144 | 0 | ycaIn[0][i].r * 0.001064f + ycaIn[2][i].r * -0.003771f + |
145 | 0 | ycaIn[4][i].r * 0.009801f + ycaIn[6][i].r * -0.021586f + |
146 | 0 | ycaIn[8][i].r * 0.043978f + ycaIn[10][i].r * -0.093067f + |
147 | 0 | ycaIn[12][i].r * 0.313659f + ycaIn[13][i].r * 0.499846f + |
148 | 0 | ycaIn[14][i].r * 0.313659f + ycaIn[16][i].r * -0.093067f + |
149 | 0 | ycaIn[18][i].r * 0.043978f + ycaIn[20][i].r * -0.021586f + |
150 | 0 | ycaIn[22][i].r * 0.009801f + ycaIn[24][i].r * -0.003771f + |
151 | 0 | ycaIn[26][i].r * 0.001064f; |
152 | |
|
153 | 0 | ycaOut[i].b = |
154 | 0 | ycaIn[0][i].b * 0.001064f + ycaIn[2][i].b * -0.003771f + |
155 | 0 | ycaIn[4][i].b * 0.009801f + ycaIn[6][i].b * -0.021586f + |
156 | 0 | ycaIn[8][i].b * 0.043978f + ycaIn[10][i].b * -0.093067f + |
157 | 0 | ycaIn[12][i].b * 0.313659f + ycaIn[13][i].b * 0.499846f + |
158 | 0 | ycaIn[14][i].b * 0.313659f + ycaIn[16][i].b * -0.093067f + |
159 | 0 | ycaIn[18][i].b * 0.043978f + ycaIn[20][i].b * -0.021586f + |
160 | 0 | ycaIn[22][i].b * 0.009801f + ycaIn[24][i].b * -0.003771f + |
161 | 0 | ycaIn[26][i].b * 0.001064f; |
162 | 0 | } |
163 | |
|
164 | 0 | ycaOut[i].g = ycaIn[13][i].g; |
165 | 0 | ycaOut[i].a = ycaIn[13][i].a; |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | | void |
170 | | roundYCA ( |
171 | | int n, |
172 | | unsigned int roundY, |
173 | | unsigned int roundC, |
174 | | const Rgba ycaIn[/*n*/], |
175 | | Rgba ycaOut[/*n*/]) |
176 | 0 | { |
177 | 0 | for (int i = 0; i < n; ++i) |
178 | 0 | { |
179 | 0 | ycaOut[i].g = ycaIn[i].g.round (roundY); |
180 | 0 | ycaOut[i].a = ycaIn[i].a; |
181 | |
|
182 | 0 | if ((i & 1) == 0) |
183 | 0 | { |
184 | 0 | ycaOut[i].r = ycaIn[i].r.round (roundC); |
185 | 0 | ycaOut[i].b = ycaIn[i].b.round (roundC); |
186 | 0 | } |
187 | 0 | } |
188 | 0 | } |
189 | | |
190 | | void |
191 | | reconstructChromaHoriz (int n, const Rgba ycaIn[/*n+N-1*/], Rgba ycaOut[/*n*/]) |
192 | 155k | { |
193 | | #ifdef DEBUG |
194 | | assert (ycaIn != ycaOut); |
195 | | #endif |
196 | | |
197 | 155k | int begin = N2; |
198 | 155k | int end = begin + n; |
199 | | |
200 | 16.3M | for (int i = begin, j = 0; i < end; ++i, ++j) |
201 | 16.2M | { |
202 | 16.2M | if (j & 1) |
203 | 8.10M | { |
204 | 8.10M | ycaOut[j].r = |
205 | 8.10M | ycaIn[i - 13].r * 0.002128f + ycaIn[i - 11].r * -0.007540f + |
206 | 8.10M | ycaIn[i - 9].r * 0.019597f + ycaIn[i - 7].r * -0.043159f + |
207 | 8.10M | ycaIn[i - 5].r * 0.087929f + ycaIn[i - 3].r * -0.186077f + |
208 | 8.10M | ycaIn[i - 1].r * 0.627123f + ycaIn[i + 1].r * 0.627123f + |
209 | 8.10M | ycaIn[i + 3].r * -0.186077f + ycaIn[i + 5].r * 0.087929f + |
210 | 8.10M | ycaIn[i + 7].r * -0.043159f + ycaIn[i + 9].r * 0.019597f + |
211 | 8.10M | ycaIn[i + 11].r * -0.007540f + ycaIn[i + 13].r * 0.002128f; |
212 | | |
213 | 8.10M | ycaOut[j].b = |
214 | 8.10M | ycaIn[i - 13].b * 0.002128f + ycaIn[i - 11].b * -0.007540f + |
215 | 8.10M | ycaIn[i - 9].b * 0.019597f + ycaIn[i - 7].b * -0.043159f + |
216 | 8.10M | ycaIn[i - 5].b * 0.087929f + ycaIn[i - 3].b * -0.186077f + |
217 | 8.10M | ycaIn[i - 1].b * 0.627123f + ycaIn[i + 1].b * 0.627123f + |
218 | 8.10M | ycaIn[i + 3].b * -0.186077f + ycaIn[i + 5].b * 0.087929f + |
219 | 8.10M | ycaIn[i + 7].b * -0.043159f + ycaIn[i + 9].b * 0.019597f + |
220 | 8.10M | ycaIn[i + 11].b * -0.007540f + ycaIn[i + 13].b * 0.002128f; |
221 | 8.10M | } |
222 | 8.11M | else |
223 | 8.11M | { |
224 | 8.11M | ycaOut[j].r = ycaIn[i].r; |
225 | 8.11M | ycaOut[j].b = ycaIn[i].b; |
226 | 8.11M | } |
227 | | |
228 | 16.2M | ycaOut[j].g = ycaIn[i].g; |
229 | 16.2M | ycaOut[j].a = ycaIn[i].a; |
230 | 16.2M | } |
231 | 155k | } |
232 | | |
233 | | void |
234 | | reconstructChromaVert (int n, const Rgba* const ycaIn[N], Rgba ycaOut[/*n*/]) |
235 | 126k | { |
236 | 6.52M | for (int i = 0; i < n; ++i) |
237 | 6.39M | { |
238 | 6.39M | ycaOut[i].r = ycaIn[0][i].r * 0.002128f + ycaIn[2][i].r * -0.007540f + |
239 | 6.39M | ycaIn[4][i].r * 0.019597f + ycaIn[6][i].r * -0.043159f + |
240 | 6.39M | ycaIn[8][i].r * 0.087929f + ycaIn[10][i].r * -0.186077f + |
241 | 6.39M | ycaIn[12][i].r * 0.627123f + ycaIn[14][i].r * 0.627123f + |
242 | 6.39M | ycaIn[16][i].r * -0.186077f + ycaIn[18][i].r * 0.087929f + |
243 | 6.39M | ycaIn[20][i].r * -0.043159f + ycaIn[22][i].r * 0.019597f + |
244 | 6.39M | ycaIn[24][i].r * -0.007540f + ycaIn[26][i].r * 0.002128f; |
245 | | |
246 | 6.39M | ycaOut[i].b = ycaIn[0][i].b * 0.002128f + ycaIn[2][i].b * -0.007540f + |
247 | 6.39M | ycaIn[4][i].b * 0.019597f + ycaIn[6][i].b * -0.043159f + |
248 | 6.39M | ycaIn[8][i].b * 0.087929f + ycaIn[10][i].b * -0.186077f + |
249 | 6.39M | ycaIn[12][i].b * 0.627123f + ycaIn[14][i].b * 0.627123f + |
250 | 6.39M | ycaIn[16][i].b * -0.186077f + ycaIn[18][i].b * 0.087929f + |
251 | 6.39M | ycaIn[20][i].b * -0.043159f + ycaIn[22][i].b * 0.019597f + |
252 | 6.39M | ycaIn[24][i].b * -0.007540f + ycaIn[26][i].b * 0.002128f; |
253 | | |
254 | 6.39M | ycaOut[i].g = ycaIn[13][i].g; |
255 | 6.39M | ycaOut[i].a = ycaIn[13][i].a; |
256 | 6.39M | } |
257 | 126k | } |
258 | | |
259 | | void |
260 | | YCAtoRGBA ( |
261 | | const IMATH_NAMESPACE::V3f& yw, |
262 | | int n, |
263 | | const Rgba ycaIn[/*n*/], |
264 | | Rgba rgbaOut[/*n*/]) |
265 | 253k | { |
266 | 13.0M | for (int i = 0; i < n; ++i) |
267 | 12.7M | { |
268 | 12.7M | const Rgba& in = ycaIn[i]; |
269 | 12.7M | Rgba& out = rgbaOut[i]; |
270 | | |
271 | 12.7M | if (in.r == 0 && in.b == 0) |
272 | 7.16M | { |
273 | | // |
274 | | // Special case -- both chroma channels are 0. To avoid |
275 | | // rounding errors, we explicitly set the output R, G and B |
276 | | // channels equal to the input luminance. |
277 | | // |
278 | | // The special cases here and in RGBAtoYCA() ensure that |
279 | | // converting black-and white images from RGBA to YCA and |
280 | | // back is lossless. |
281 | | // |
282 | | |
283 | 7.16M | out.r = in.g; |
284 | 7.16M | out.g = in.g; |
285 | 7.16M | out.b = in.g; |
286 | 7.16M | out.a = in.a; |
287 | 7.16M | } |
288 | 5.62M | else |
289 | 5.62M | { |
290 | 5.62M | float Y = in.g; |
291 | 5.62M | float r = (in.r + 1) * Y; |
292 | 5.62M | float b = (in.b + 1) * Y; |
293 | 5.62M | float g = (Y - r * yw.x - b * yw.z) / yw.y; |
294 | | |
295 | 5.62M | out.r = r; |
296 | 5.62M | out.g = g; |
297 | 5.62M | out.b = b; |
298 | 5.62M | out.a = in.a; |
299 | 5.62M | } |
300 | 12.7M | } |
301 | 253k | } |
302 | | |
303 | | namespace |
304 | | { |
305 | | |
306 | | inline float |
307 | | saturation (const Rgba& in) |
308 | 36.1M | { |
309 | 36.1M | float rgbMax = max (in.r, max (in.g, in.b)); |
310 | 36.1M | float rgbMin = min (in.r, min (in.g, in.b)); |
311 | | |
312 | 36.1M | if (rgbMax > 0) |
313 | 4.53M | return 1 - rgbMin / rgbMax; |
314 | 31.6M | else |
315 | 31.6M | return 0; |
316 | 36.1M | } |
317 | | |
318 | | void |
319 | | desaturate (const Rgba& in, float f, const V3f& yw, Rgba& out) |
320 | 122k | { |
321 | 122k | float rgbMax = max (in.r, max (in.g, in.b)); |
322 | | |
323 | 122k | out.r = max (float (rgbMax - (rgbMax - in.r) * f), 0.0f); |
324 | 122k | out.g = max (float (rgbMax - (rgbMax - in.g) * f), 0.0f); |
325 | 122k | out.b = max (float (rgbMax - (rgbMax - in.b) * f), 0.0f); |
326 | 122k | out.a = in.a; |
327 | | |
328 | 122k | float Yin = in.r * yw.x + in.g * yw.y + in.b * yw.z; |
329 | 122k | float Yout = out.r * yw.x + out.g * yw.y + out.b * yw.z; |
330 | | |
331 | 122k | if (Yout > 0) |
332 | 114k | { |
333 | 114k | out.r *= Yin / Yout; |
334 | 114k | out.g *= Yin / Yout; |
335 | 114k | out.b *= Yin / Yout; |
336 | 114k | } |
337 | 122k | } |
338 | | |
339 | | } // namespace |
340 | | |
341 | | void |
342 | | fixSaturation ( |
343 | | const IMATH_NAMESPACE::V3f& yw, |
344 | | int n, |
345 | | const Rgba* const rgbaIn[3], |
346 | | Rgba rgbaOut[/*n*/]) |
347 | 249k | { |
348 | 249k | float neighborA2 = saturation (rgbaIn[0][0]); |
349 | 249k | float neighborA1 = neighborA2; |
350 | | |
351 | 249k | float neighborB2 = saturation (rgbaIn[2][0]); |
352 | 249k | float neighborB1 = neighborB2; |
353 | | |
354 | 12.3M | for (int i = 0; i < n; ++i) |
355 | 12.0M | { |
356 | 12.0M | float neighborA0 = neighborA1; |
357 | 12.0M | neighborA1 = neighborA2; |
358 | | |
359 | 12.0M | float neighborB0 = neighborB1; |
360 | 12.0M | neighborB1 = neighborB2; |
361 | | |
362 | 12.0M | if (i < n - 1) |
363 | 11.8M | { |
364 | 11.8M | neighborA2 = saturation (rgbaIn[0][i + 1]); |
365 | 11.8M | neighborB2 = saturation (rgbaIn[2][i + 1]); |
366 | 11.8M | } |
367 | | |
368 | | // |
369 | | // A0 A1 A2 |
370 | | // rgbaOut[i] |
371 | | // B0 B1 B2 |
372 | | // |
373 | | |
374 | 12.0M | float sMean = min ( |
375 | 12.0M | 1.0f, 0.25f * (neighborA0 + neighborA2 + neighborB0 + neighborB2)); |
376 | | |
377 | 12.0M | const Rgba& in = rgbaIn[1][i]; |
378 | 12.0M | Rgba& out = rgbaOut[i]; |
379 | | |
380 | 12.0M | float s = saturation (in); |
381 | | |
382 | 12.0M | if (s > sMean) |
383 | 165k | { |
384 | 165k | float sMax = min (1.0f, 1 - (1 - sMean) * 0.25f); |
385 | | |
386 | 165k | if (s > sMax) |
387 | 122k | { |
388 | 122k | desaturate (in, sMax / s, yw, out); |
389 | 122k | continue; |
390 | 122k | } |
391 | 165k | } |
392 | | |
393 | 11.9M | out = in; |
394 | 11.9M | } |
395 | 249k | } |
396 | | |
397 | | } // namespace RgbaYca |
398 | | OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT |