Coverage Report

Created: 2026-10-17 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
0
{
28
0
    M44f m = RGBtoXYZ (cr, 1);
29
0
    return V3f (m[0][1], m[1][1], m[2][1]) / (m[0][1] + m[1][1] + m[2][1]);
30
0
}
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
0
{
193
#ifdef DEBUG
194
    assert (ycaIn != ycaOut);
195
#endif
196
197
0
    int begin = N2;
198
0
    int end   = begin + n;
199
200
0
    for (int i = begin, j = 0; i < end; ++i, ++j)
201
0
    {
202
0
        if (j & 1)
203
0
        {
204
0
            ycaOut[j].r =
205
0
                ycaIn[i - 13].r * 0.002128f + ycaIn[i - 11].r * -0.007540f +
206
0
                ycaIn[i - 9].r * 0.019597f + ycaIn[i - 7].r * -0.043159f +
207
0
                ycaIn[i - 5].r * 0.087929f + ycaIn[i - 3].r * -0.186077f +
208
0
                ycaIn[i - 1].r * 0.627123f + ycaIn[i + 1].r * 0.627123f +
209
0
                ycaIn[i + 3].r * -0.186077f + ycaIn[i + 5].r * 0.087929f +
210
0
                ycaIn[i + 7].r * -0.043159f + ycaIn[i + 9].r * 0.019597f +
211
0
                ycaIn[i + 11].r * -0.007540f + ycaIn[i + 13].r * 0.002128f;
212
213
0
            ycaOut[j].b =
214
0
                ycaIn[i - 13].b * 0.002128f + ycaIn[i - 11].b * -0.007540f +
215
0
                ycaIn[i - 9].b * 0.019597f + ycaIn[i - 7].b * -0.043159f +
216
0
                ycaIn[i - 5].b * 0.087929f + ycaIn[i - 3].b * -0.186077f +
217
0
                ycaIn[i - 1].b * 0.627123f + ycaIn[i + 1].b * 0.627123f +
218
0
                ycaIn[i + 3].b * -0.186077f + ycaIn[i + 5].b * 0.087929f +
219
0
                ycaIn[i + 7].b * -0.043159f + ycaIn[i + 9].b * 0.019597f +
220
0
                ycaIn[i + 11].b * -0.007540f + ycaIn[i + 13].b * 0.002128f;
221
0
        }
222
0
        else
223
0
        {
224
0
            ycaOut[j].r = ycaIn[i].r;
225
0
            ycaOut[j].b = ycaIn[i].b;
226
0
        }
227
228
0
        ycaOut[j].g = ycaIn[i].g;
229
0
        ycaOut[j].a = ycaIn[i].a;
230
0
    }
231
0
}
232
233
void
234
reconstructChromaVert (int n, const Rgba* const ycaIn[N], Rgba ycaOut[/*n*/])
235
0
{
236
0
    for (int i = 0; i < n; ++i)
237
0
    {
238
0
        ycaOut[i].r = ycaIn[0][i].r * 0.002128f + ycaIn[2][i].r * -0.007540f +
239
0
                      ycaIn[4][i].r * 0.019597f + ycaIn[6][i].r * -0.043159f +
240
0
                      ycaIn[8][i].r * 0.087929f + ycaIn[10][i].r * -0.186077f +
241
0
                      ycaIn[12][i].r * 0.627123f + ycaIn[14][i].r * 0.627123f +
242
0
                      ycaIn[16][i].r * -0.186077f + ycaIn[18][i].r * 0.087929f +
243
0
                      ycaIn[20][i].r * -0.043159f + ycaIn[22][i].r * 0.019597f +
244
0
                      ycaIn[24][i].r * -0.007540f + ycaIn[26][i].r * 0.002128f;
245
246
0
        ycaOut[i].b = ycaIn[0][i].b * 0.002128f + ycaIn[2][i].b * -0.007540f +
247
0
                      ycaIn[4][i].b * 0.019597f + ycaIn[6][i].b * -0.043159f +
248
0
                      ycaIn[8][i].b * 0.087929f + ycaIn[10][i].b * -0.186077f +
249
0
                      ycaIn[12][i].b * 0.627123f + ycaIn[14][i].b * 0.627123f +
250
0
                      ycaIn[16][i].b * -0.186077f + ycaIn[18][i].b * 0.087929f +
251
0
                      ycaIn[20][i].b * -0.043159f + ycaIn[22][i].b * 0.019597f +
252
0
                      ycaIn[24][i].b * -0.007540f + ycaIn[26][i].b * 0.002128f;
253
254
0
        ycaOut[i].g = ycaIn[13][i].g;
255
0
        ycaOut[i].a = ycaIn[13][i].a;
256
0
    }
257
0
}
258
259
void
260
YCAtoRGBA (
261
    const IMATH_NAMESPACE::V3f& yw,
262
    int                         n,
263
    const Rgba                  ycaIn[/*n*/],
264
    Rgba                        rgbaOut[/*n*/])
265
0
{
266
0
    for (int i = 0; i < n; ++i)
267
0
    {
268
0
        const Rgba& in  = ycaIn[i];
269
0
        Rgba&       out = rgbaOut[i];
270
271
0
        if (in.r == 0 && in.b == 0)
272
0
        {
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
0
            out.r = in.g;
284
0
            out.g = in.g;
285
0
            out.b = in.g;
286
0
            out.a = in.a;
287
0
        }
288
0
        else
289
0
        {
290
0
            float Y = in.g;
291
0
            float r = (in.r + 1) * Y;
292
0
            float b = (in.b + 1) * Y;
293
0
            float g = (Y - r * yw.x - b * yw.z) / yw.y;
294
295
0
            out.r = r;
296
0
            out.g = g;
297
0
            out.b = b;
298
0
            out.a = in.a;
299
0
        }
300
0
    }
301
0
}
302
303
namespace
304
{
305
306
inline float
307
saturation (const Rgba& in)
308
0
{
309
0
    float rgbMax = max (in.r, max (in.g, in.b));
310
0
    float rgbMin = min (in.r, min (in.g, in.b));
311
312
0
    if (rgbMax > 0)
313
0
        return 1 - rgbMin / rgbMax;
314
0
    else
315
0
        return 0;
316
0
}
317
318
void
319
desaturate (const Rgba& in, float f, const V3f& yw, Rgba& out)
320
0
{
321
0
    float rgbMax = max (in.r, max (in.g, in.b));
322
323
0
    out.r = max (float (rgbMax - (rgbMax - in.r) * f), 0.0f);
324
0
    out.g = max (float (rgbMax - (rgbMax - in.g) * f), 0.0f);
325
0
    out.b = max (float (rgbMax - (rgbMax - in.b) * f), 0.0f);
326
0
    out.a = in.a;
327
328
0
    float Yin  = in.r * yw.x + in.g * yw.y + in.b * yw.z;
329
0
    float Yout = out.r * yw.x + out.g * yw.y + out.b * yw.z;
330
331
0
    if (Yout > 0)
332
0
    {
333
0
        out.r *= Yin / Yout;
334
0
        out.g *= Yin / Yout;
335
0
        out.b *= Yin / Yout;
336
0
    }
337
0
}
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
0
{
348
0
    float neighborA2 = saturation (rgbaIn[0][0]);
349
0
    float neighborA1 = neighborA2;
350
351
0
    float neighborB2 = saturation (rgbaIn[2][0]);
352
0
    float neighborB1 = neighborB2;
353
354
0
    for (int i = 0; i < n; ++i)
355
0
    {
356
0
        float neighborA0 = neighborA1;
357
0
        neighborA1       = neighborA2;
358
359
0
        float neighborB0 = neighborB1;
360
0
        neighborB1       = neighborB2;
361
362
0
        if (i < n - 1)
363
0
        {
364
0
            neighborA2 = saturation (rgbaIn[0][i + 1]);
365
0
            neighborB2 = saturation (rgbaIn[2][i + 1]);
366
0
        }
367
368
        //
369
        // A0       A1       A2
370
        //      rgbaOut[i]
371
        // B0       B1       B2
372
        //
373
374
0
        float sMean = min (
375
0
            1.0f, 0.25f * (neighborA0 + neighborA2 + neighborB0 + neighborB2));
376
377
0
        const Rgba& in  = rgbaIn[1][i];
378
0
        Rgba&       out = rgbaOut[i];
379
380
0
        float s = saturation (in);
381
382
0
        if (s > sMean)
383
0
        {
384
0
            float sMax = min (1.0f, 1 - (1 - sMean) * 0.25f);
385
386
0
            if (s > sMax)
387
0
            {
388
0
                desaturate (in, sMax / s, yw, out);
389
0
                continue;
390
0
            }
391
0
        }
392
393
0
        out = in;
394
0
    }
395
0
}
396
397
} // namespace RgbaYca
398
OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT