-
Notifications
You must be signed in to change notification settings - Fork 211
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add color conversion utilities. (#1861)
The color conversion matrix code is copied from apps/shared/iccmaker.c Add tests.
- Loading branch information
Showing
21 changed files
with
459 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -324,6 +324,7 @@ set(AVIF_SRCS | |
src/alpha.c | ||
src/avif.c | ||
src/colr.c | ||
src/colrconvert.c | ||
src/diag.c | ||
src/exif.c | ||
src/io.c | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
// Copyright 2023 Google LLC | ||
// SPDX-License-Identifier: BSD-2-Clause | ||
|
||
#include "avif/internal.h" | ||
|
||
#include <math.h> | ||
|
||
static const double epsilon = 1e-12; | ||
|
||
static avifBool avifXyToXYZ(const float xy[2], double XYZ[3]) | ||
{ | ||
if (fabsf(xy[1]) < epsilon) { | ||
return AVIF_FALSE; | ||
} | ||
|
||
const double factor = 1.0 / xy[1]; | ||
XYZ[0] = xy[0] * factor; | ||
XYZ[1] = 1; | ||
XYZ[2] = (1 - xy[0] - xy[1]) * factor; | ||
|
||
return AVIF_TRUE; | ||
} | ||
|
||
// Computes I = M^-1. Returns false if M seems to be singular. | ||
static avifBool avifMatInv(const double M[3][3], double I[3][3]) | ||
{ | ||
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]) + | ||
M[0][2] * (M[1][0] * M[2][1] - M[1][1] * M[2][0]); | ||
if (fabs(det) < epsilon) { | ||
return AVIF_FALSE; | ||
} | ||
det = 1.0 / det; | ||
|
||
I[0][0] = (M[1][1] * M[2][2] - M[2][1] * M[1][2]) * det; | ||
I[0][1] = (M[0][2] * M[2][1] - M[0][1] * M[2][2]) * det; | ||
I[0][2] = (M[0][1] * M[1][2] - M[0][2] * M[1][1]) * det; | ||
I[1][0] = (M[1][2] * M[2][0] - M[1][0] * M[2][2]) * det; | ||
I[1][1] = (M[0][0] * M[2][2] - M[0][2] * M[2][0]) * det; | ||
I[1][2] = (M[1][0] * M[0][2] - M[0][0] * M[1][2]) * det; | ||
I[2][0] = (M[1][0] * M[2][1] - M[2][0] * M[1][1]) * det; | ||
I[2][1] = (M[2][0] * M[0][1] - M[0][0] * M[2][1]) * det; | ||
I[2][2] = (M[0][0] * M[1][1] - M[1][0] * M[0][1]) * det; | ||
|
||
return AVIF_TRUE; | ||
} | ||
|
||
// Computes C = A*B | ||
static void avifMatMul(const double A[3][3], const double B[3][3], double C[3][3]) | ||
{ | ||
C[0][0] = A[0][0] * B[0][0] + A[0][1] * B[1][0] + A[0][2] * B[2][0]; | ||
C[0][1] = A[0][0] * B[0][1] + A[0][1] * B[1][1] + A[0][2] * B[2][1]; | ||
C[0][2] = A[0][0] * B[0][2] + A[0][1] * B[1][2] + A[0][2] * B[2][2]; | ||
C[1][0] = A[1][0] * B[0][0] + A[1][1] * B[1][0] + A[1][2] * B[2][0]; | ||
C[1][1] = A[1][0] * B[0][1] + A[1][1] * B[1][1] + A[1][2] * B[2][1]; | ||
C[1][2] = A[1][0] * B[0][2] + A[1][1] * B[1][2] + A[1][2] * B[2][2]; | ||
C[2][0] = A[2][0] * B[0][0] + A[2][1] * B[1][0] + A[2][2] * B[2][0]; | ||
C[2][1] = A[2][0] * B[0][1] + A[2][1] * B[1][1] + A[2][2] * B[2][1]; | ||
C[2][2] = A[2][0] * B[0][2] + A[2][1] * B[1][2] + A[2][2] * B[2][2]; | ||
} | ||
|
||
// Set M to have values of d on the leading diagonal, and zero elsewhere. | ||
static void avifMatDiag(const double d[3], double M[3][3]) | ||
{ | ||
M[0][0] = d[0]; | ||
M[0][1] = 0; | ||
M[0][2] = 0; | ||
M[1][0] = 0; | ||
M[1][1] = d[1]; | ||
M[1][2] = 0; | ||
M[2][0] = 0; | ||
M[2][1] = 0; | ||
M[2][2] = d[2]; | ||
} | ||
|
||
// Computes y = M.x | ||
static void avifVecMul(const double M[3][3], const double x[3], double y[3]) | ||
{ | ||
y[0] = M[0][0] * x[0] + M[0][1] * x[1] + M[0][2] * x[2]; | ||
y[1] = M[1][0] * x[0] + M[1][1] * x[1] + M[1][2] * x[2]; | ||
y[2] = M[2][0] * x[0] + M[2][1] * x[1] + M[2][2] * x[2]; | ||
} | ||
|
||
// Bradford chromatic adaptation matrix | ||
// from https://www.researchgate.net/publication/253799640_A_uniform_colour_space_based_upon_CIECAM97s | ||
static const double avifBradford[3][3] = { | ||
{ 0.8951, 0.2664, -0.1614 }, | ||
{ -0.7502, 1.7135, 0.0367 }, | ||
{ 0.0389, -0.0685, 1.0296 }, | ||
}; | ||
|
||
// LMS values for D50 whitepoint | ||
static const double avifLmsD50[3] = { 0.996284, 1.02043, 0.818644 }; | ||
|
||
avifBool avifColorPrimariesComputeRGBToXYZD50Matrix(avifColorPrimaries colorPrimaries, double coeffs[3][3]) | ||
{ | ||
float primaries[8]; | ||
avifColorPrimariesGetValues(colorPrimaries, primaries); | ||
|
||
double whitePointXYZ[3]; | ||
AVIF_CHECK(avifXyToXYZ(&primaries[6], whitePointXYZ)); | ||
|
||
const double rgbPrimaries[3][3] = { | ||
{ primaries[0], primaries[2], primaries[4] }, | ||
{ primaries[1], primaries[3], primaries[5] }, | ||
{ 1.0 - primaries[0] - primaries[1], 1.0 - primaries[2] - primaries[3], 1.0 - primaries[4] - primaries[5] } | ||
}; | ||
|
||
double rgbPrimariesInv[3][3]; | ||
AVIF_CHECK(avifMatInv(rgbPrimaries, rgbPrimariesInv)); | ||
|
||
double rgbCoefficients[3]; | ||
avifVecMul(rgbPrimariesInv, whitePointXYZ, rgbCoefficients); | ||
|
||
double rgbCoefficientsMat[3][3]; | ||
avifMatDiag(rgbCoefficients, rgbCoefficientsMat); | ||
|
||
double rgbXYZ[3][3]; | ||
avifMatMul(rgbPrimaries, rgbCoefficientsMat, rgbXYZ); | ||
|
||
// ICC stores primaries XYZ under PCS. | ||
// Adapt using linear bradford transform | ||
// from https://onlinelibrary.wiley.com/doi/pdf/10.1002/9781119021780.app3 | ||
double lms[3]; | ||
avifVecMul(avifBradford, whitePointXYZ, lms); | ||
for (int i = 0; i < 3; ++i) { | ||
if (fabs(lms[i]) < epsilon) { | ||
return AVIF_FALSE; | ||
} | ||
lms[i] = avifLmsD50[i] / lms[i]; | ||
} | ||
|
||
double adaptation[3][3]; | ||
avifMatDiag(lms, adaptation); | ||
|
||
double tmp[3][3]; | ||
avifMatMul(adaptation, avifBradford, tmp); | ||
|
||
double bradfordInv[3][3]; | ||
if (!avifMatInv(avifBradford, bradfordInv)) { | ||
return AVIF_FALSE; | ||
} | ||
avifMatMul(bradfordInv, tmp, adaptation); | ||
|
||
avifMatMul(adaptation, rgbXYZ, coeffs); | ||
|
||
return AVIF_TRUE; | ||
} | ||
|
||
avifBool avifColorPrimariesComputeXYZD50ToRGBMatrix(avifColorPrimaries colorPrimaries, double coeffs[3][3]) | ||
{ | ||
double rgbToXyz[3][3]; | ||
AVIF_CHECK(avifColorPrimariesComputeRGBToXYZD50Matrix(colorPrimaries, rgbToXyz)); | ||
AVIF_CHECK(avifMatInv(rgbToXyz, coeffs)); | ||
return AVIF_TRUE; | ||
} | ||
|
||
avifBool avifColorPrimariesComputeRGBToRGBMatrix(avifColorPrimaries srcColorPrimaries, | ||
avifColorPrimaries dstColorPrimaries, | ||
double coeffs[3][3]) | ||
{ | ||
// Note: no special casing for srcColorPrimaries == dstColorPrimaries to allow | ||
// testing that the computation actually produces the identity matrix. | ||
double srcRGBToXYZ[3][3]; | ||
AVIF_CHECK(avifColorPrimariesComputeRGBToXYZD50Matrix(srcColorPrimaries, srcRGBToXYZ)); | ||
double xyzToDstRGB[3][3]; | ||
AVIF_CHECK(avifColorPrimariesComputeXYZD50ToRGBMatrix(dstColorPrimaries, xyzToDstRGB)); | ||
// coeffs = xyzToDstRGB * srcRGBToXYZ | ||
// i.e. srcRGB -> XYZ -> dstRGB | ||
avifMatMul(xyzToDstRGB, srcRGBToXYZ, coeffs); | ||
return AVIF_TRUE; | ||
} | ||
|
||
// Converts a linear RGBA pixel to a different color space. This function actually works for gamma encoded | ||
// RGB as well but linear gives better results. Also, for gamma encoded values, it would be | ||
// better to clamp the output to [0, 1]. Linear values don't need clamping because values | ||
// > 1.0 are valid for HDR transfer curves, and the gamma compression function will do the | ||
// clamping as necessary. | ||
void avifLinearRGBConvertColorSpace(float rgb[4], const double coeffs[3][3]) | ||
{ | ||
const double rgbDouble[3] = { rgb[0], rgb[1], rgb[2] }; | ||
double converted[3]; | ||
avifVecMul(coeffs, rgbDouble, converted); | ||
rgb[0] = (float)converted[0]; | ||
rgb[1] = (float)converted[1]; | ||
rgb[2] = (float)converted[2]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Oops, something went wrong.