Coverage Report

Created: 2026-02-14 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libavif/src/colrconvert.c
Line
Count
Source
1
// Copyright 2023 Google LLC
2
// SPDX-License-Identifier: BSD-2-Clause
3
4
#include "avif/internal.h"
5
6
#include <math.h>
7
8
static const double epsilon = 1e-12;
9
10
static avifBool avifXyToXYZ(const float xy[2], double XYZ[3])
11
0
{
12
0
    if (fabsf(xy[1]) < epsilon) {
13
0
        return AVIF_FALSE;
14
0
    }
15
16
0
    const double factor = 1.0 / xy[1];
17
0
    XYZ[0] = xy[0] * factor;
18
0
    XYZ[1] = 1;
19
0
    XYZ[2] = (1 - xy[0] - xy[1]) * factor;
20
21
0
    return AVIF_TRUE;
22
0
}
23
24
// Computes I = M^-1. Returns false if M seems to be singular.
25
static avifBool avifMatInv(double M[3][3], double I[3][3])
26
0
{
27
0
    double det = M[0][0] * (M[1][1] * M[2][2] - M[2][1] * M[1][2]) - M[0][1] * (M[1][0] * M[2][2] - M[1][2] * M[2][0]) +
28
0
                 M[0][2] * (M[1][0] * M[2][1] - M[1][1] * M[2][0]);
29
0
    if (fabs(det) < epsilon) {
30
0
        return AVIF_FALSE;
31
0
    }
32
0
    det = 1.0 / det;
33
34
0
    I[0][0] = (M[1][1] * M[2][2] - M[2][1] * M[1][2]) * det;
35
0
    I[0][1] = (M[0][2] * M[2][1] - M[0][1] * M[2][2]) * det;
36
0
    I[0][2] = (M[0][1] * M[1][2] - M[0][2] * M[1][1]) * det;
37
0
    I[1][0] = (M[1][2] * M[2][0] - M[1][0] * M[2][2]) * det;
38
0
    I[1][1] = (M[0][0] * M[2][2] - M[0][2] * M[2][0]) * det;
39
0
    I[1][2] = (M[1][0] * M[0][2] - M[0][0] * M[1][2]) * det;
40
0
    I[2][0] = (M[1][0] * M[2][1] - M[2][0] * M[1][1]) * det;
41
0
    I[2][1] = (M[2][0] * M[0][1] - M[0][0] * M[2][1]) * det;
42
0
    I[2][2] = (M[0][0] * M[1][1] - M[1][0] * M[0][1]) * det;
43
44
0
    return AVIF_TRUE;
45
0
}
46
47
// Computes C = A*B
48
static void avifMatMul(double A[3][3], double B[3][3], double C[3][3])
49
0
{
50
0
    C[0][0] = A[0][0] * B[0][0] + A[0][1] * B[1][0] + A[0][2] * B[2][0];
51
0
    C[0][1] = A[0][0] * B[0][1] + A[0][1] * B[1][1] + A[0][2] * B[2][1];
52
0
    C[0][2] = A[0][0] * B[0][2] + A[0][1] * B[1][2] + A[0][2] * B[2][2];
53
0
    C[1][0] = A[1][0] * B[0][0] + A[1][1] * B[1][0] + A[1][2] * B[2][0];
54
0
    C[1][1] = A[1][0] * B[0][1] + A[1][1] * B[1][1] + A[1][2] * B[2][1];
55
0
    C[1][2] = A[1][0] * B[0][2] + A[1][1] * B[1][2] + A[1][2] * B[2][2];
56
0
    C[2][0] = A[2][0] * B[0][0] + A[2][1] * B[1][0] + A[2][2] * B[2][0];
57
0
    C[2][1] = A[2][0] * B[0][1] + A[2][1] * B[1][1] + A[2][2] * B[2][1];
58
0
    C[2][2] = A[2][0] * B[0][2] + A[2][1] * B[1][2] + A[2][2] * B[2][2];
59
0
}
60
61
// Set M to have values of d on the leading diagonal, and zero elsewhere.
62
static void avifMatDiag(const double d[3], double M[3][3])
63
0
{
64
0
    M[0][0] = d[0];
65
0
    M[0][1] = 0;
66
0
    M[0][2] = 0;
67
0
    M[1][0] = 0;
68
0
    M[1][1] = d[1];
69
0
    M[1][2] = 0;
70
0
    M[2][0] = 0;
71
0
    M[2][1] = 0;
72
0
    M[2][2] = d[2];
73
0
}
74
75
// Computes y = M.x
76
static void avifVecMul(double M[3][3], const double x[3], double y[3])
77
0
{
78
0
    y[0] = M[0][0] * x[0] + M[0][1] * x[1] + M[0][2] * x[2];
79
0
    y[1] = M[1][0] * x[0] + M[1][1] * x[1] + M[1][2] * x[2];
80
0
    y[2] = M[2][0] * x[0] + M[2][1] * x[1] + M[2][2] * x[2];
81
0
}
82
83
// Bradford chromatic adaptation matrix
84
// from https://www.researchgate.net/publication/253799640_A_uniform_colour_space_based_upon_CIECAM97s
85
static double avifBradford[3][3] = {
86
    { 0.8951, 0.2664, -0.1614 },
87
    { -0.7502, 1.7135, 0.0367 },
88
    { 0.0389, -0.0685, 1.0296 },
89
};
90
91
// LMS values for D50 whitepoint
92
static const double avifLmsD50[3] = { 0.996284, 1.02043, 0.818644 };
93
94
avifBool avifColorPrimariesComputeRGBToXYZD50Matrix(avifColorPrimaries colorPrimaries, double coeffs[3][3])
95
0
{
96
0
    float primaries[8];
97
0
    avifColorPrimariesGetValues(colorPrimaries, primaries);
98
99
0
    double whitePointXYZ[3];
100
0
    AVIF_CHECK(avifXyToXYZ(&primaries[6], whitePointXYZ));
101
102
0
    double rgbPrimaries[3][3] = {
103
0
        { primaries[0], primaries[2], primaries[4] },
104
0
        { primaries[1], primaries[3], primaries[5] },
105
0
        { 1.0 - primaries[0] - primaries[1], 1.0 - primaries[2] - primaries[3], 1.0 - primaries[4] - primaries[5] }
106
0
    };
107
108
0
    double rgbPrimariesInv[3][3];
109
0
    AVIF_CHECK(avifMatInv(rgbPrimaries, rgbPrimariesInv));
110
111
0
    double rgbCoefficients[3];
112
0
    avifVecMul(rgbPrimariesInv, whitePointXYZ, rgbCoefficients);
113
114
0
    double rgbCoefficientsMat[3][3];
115
0
    avifMatDiag(rgbCoefficients, rgbCoefficientsMat);
116
117
0
    double rgbXYZ[3][3];
118
0
    avifMatMul(rgbPrimaries, rgbCoefficientsMat, rgbXYZ);
119
120
    // ICC stores primaries XYZ under PCS.
121
    // Adapt using linear bradford transform
122
    // from https://onlinelibrary.wiley.com/doi/pdf/10.1002/9781119021780.app3
123
0
    double lms[3];
124
0
    avifVecMul(avifBradford, whitePointXYZ, lms);
125
0
    for (int i = 0; i < 3; ++i) {
126
0
        if (fabs(lms[i]) < epsilon) {
127
0
            return AVIF_FALSE;
128
0
        }
129
0
        lms[i] = avifLmsD50[i] / lms[i];
130
0
    }
131
132
0
    double adaptation[3][3];
133
0
    avifMatDiag(lms, adaptation);
134
135
0
    double tmp[3][3];
136
0
    avifMatMul(adaptation, avifBradford, tmp);
137
138
0
    double bradfordInv[3][3];
139
0
    if (!avifMatInv(avifBradford, bradfordInv)) {
140
0
        return AVIF_FALSE;
141
0
    }
142
0
    avifMatMul(bradfordInv, tmp, adaptation);
143
144
0
    avifMatMul(adaptation, rgbXYZ, coeffs);
145
146
0
    return AVIF_TRUE;
147
0
}
148
149
avifBool avifColorPrimariesComputeXYZD50ToRGBMatrix(avifColorPrimaries colorPrimaries, double coeffs[3][3])
150
0
{
151
0
    double rgbToXyz[3][3];
152
0
    AVIF_CHECK(avifColorPrimariesComputeRGBToXYZD50Matrix(colorPrimaries, rgbToXyz));
153
0
    AVIF_CHECK(avifMatInv(rgbToXyz, coeffs));
154
0
    return AVIF_TRUE;
155
0
}
156
157
avifBool avifColorPrimariesComputeRGBToRGBMatrix(avifColorPrimaries srcColorPrimaries,
158
                                                 avifColorPrimaries dstColorPrimaries,
159
                                                 double coeffs[3][3])
160
0
{
161
    // Note: no special casing for srcColorPrimaries == dstColorPrimaries to allow
162
    // testing that the computation actually produces the identity matrix.
163
0
    double srcRGBToXYZ[3][3];
164
0
    AVIF_CHECK(avifColorPrimariesComputeRGBToXYZD50Matrix(srcColorPrimaries, srcRGBToXYZ));
165
0
    double xyzToDstRGB[3][3];
166
0
    AVIF_CHECK(avifColorPrimariesComputeXYZD50ToRGBMatrix(dstColorPrimaries, xyzToDstRGB));
167
    // coeffs = xyzToDstRGB * srcRGBToXYZ
168
    // i.e. srcRGB -> XYZ -> dstRGB
169
0
    avifMatMul(xyzToDstRGB, srcRGBToXYZ, coeffs);
170
0
    return AVIF_TRUE;
171
0
}
172
173
// Converts a linear RGBA pixel to a different color space. This function actually works for gamma encoded
174
// RGB as well but linear gives better results. Also, for gamma encoded values, it would be
175
// better to clamp the output to [0, 1]. Linear values don't need clamping because values
176
// > 1.0 are valid for HDR transfer curves, and the gamma compression function will do the
177
// clamping as necessary.
178
void avifLinearRGBConvertColorSpace(float rgb[4], double coeffs[3][3])
179
0
{
180
0
    const double rgbDouble[3] = { rgb[0], rgb[1], rgb[2] };
181
0
    double converted[3];
182
0
    avifVecMul(coeffs, rgbDouble, converted);
183
0
    rgb[0] = (float)converted[0];
184
0
    rgb[1] = (float)converted[1];
185
0
    rgb[2] = (float)converted[2];
186
0
}