DCP: make chromatic adaptation of color matrix (as the DNG spec mandates), issue 2691

This commit is contained in:
torger
2015-02-24 20:24:43 +01:00
parent 0b76f06a40
commit a885e845ec
2 changed files with 76 additions and 96 deletions

View File

@@ -304,6 +304,35 @@ void DCPProfile::MakeXYZCAM(ColorTemp &wb, double pre_mul[3], double camWbMatrix
}
}
// Colormatrix
double mCol[3][3];
if (hasCol1 && hasCol2) {
// interpolate
if (mix >= 1.0) {
memcpy(mCol, mColorMatrix1, sizeof(mCol));
} else if (mix <= 0.0) {
memcpy(mCol, mColorMatrix2, sizeof(mCol));
} else {
Mix3x3(mColorMatrix1, mix, mColorMatrix2, 1.0 - mix, mCol);
}
} else if (hasCol1) {
memcpy(mCol, mColorMatrix1, sizeof(mCol));
} else {
memcpy(mCol, mColorMatrix2, sizeof(mCol));
}
/*
The exact position of the white XY coordinate affects the result very much, thus
it's important that the result is very similar or the same as DNG reference code.
Especially important is it that the raw-embedded "AsShot" multipliers is translated
to the same white XY coordinate as the DNG reference code, or else third party DCPs
will show incorrect color.
*/
double white_xyz[3];
XYtoXYZ(white_xy, white_xyz);
double cam_xyz[3][3];
if (hasFwd1 || hasFwd2) {
// always prefer ForwardMatrix ahead of ColorMatrix
double mFwd[3][3];
@@ -321,32 +350,54 @@ void DCPProfile::MakeXYZCAM(ColorTemp &wb, double pre_mul[3], double camWbMatrix
} else {
memcpy(mFwd, mForwardMatrix2, sizeof(mFwd));
}
/*
The exact position of the white XY coordinate affects the result very much, thus
it's important that the result is very similar or the same as DNG reference code.
Especially important is it that the raw-embedded "AsShot" multipliers is translated
to the same white XY coordinate as the DNG reference code, or else third party DCPs
will show incorrect color.
*/
ConvertDNGForwardMatrix2XYZCAM(mFwd,mXYZCAM,white_xy);
// adapted from dng_color_spec::SetWhiteXY
double CameraWhite[3];
Multiply3x3_v3(mCol, white_xyz, CameraWhite);
double whiteDiag[3][3] = {{CameraWhite[0], 0, 0}, {0, CameraWhite[1], 0}, {0, 0, CameraWhite[2]}};
double whiteDiagInv[3][3];
Invert3x3(whiteDiag, whiteDiagInv);
double xyz_cam[3][3];
Multiply3x3(mFwd, whiteDiagInv, xyz_cam);
Invert3x3(xyz_cam, cam_xyz);
} else {
// Colormatrix
double mCol[3][3];
if (hasCol1 && hasCol2) {
// interpolate
if (mix >= 1.0) {
memcpy(mCol, mColorMatrix1, sizeof(mCol));
} else if (mix <= 0.0) {
memcpy(mCol, mColorMatrix2, sizeof(mCol));
} else {
Mix3x3(mColorMatrix1, mix, mColorMatrix2, 1.0 - mix, mCol);
}
} else if (hasCol1) {
memcpy(mCol, mColorMatrix1, sizeof(mCol));
} else {
memcpy(mCol, mColorMatrix2, sizeof(mCol));
double whiteMatrix[3][3];
const double white_d50[3] = { 0.3457, 0.3585, 0.2958 }; // D50
MapWhiteMatrix(white_d50, white_xyz, whiteMatrix);
Multiply3x3(mCol, whiteMatrix, cam_xyz);
}
// convert cam_xyz (XYZ D50 to CameraRGB, "PCS to Camera" in DNG terminology) to mXYZCAM
{
// This block can probably be simplified, seems unnecessary to pass through the sRGB matrix
// (probably dcraw legacy), it does no harm though as we don't clip anything.
int i,j,k;
// Multiply out XYZ colorspace
double cam_rgb[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
for (i=0; i < 3; i++)
for (j=0; j < 3; j++)
for (k=0; k < 3; k++)
cam_rgb[i][j] += cam_xyz[i][k] * xyz_sRGB[k][j];
// Normalize cam_rgb so that: cam_rgb * (1,1,1) is (1,1,1,1)
double num;
for (i=0; i<3; i++) {
for (num=j=0; j<3; j++) num += cam_rgb[i][j];
for (j=0; j<3; j++) cam_rgb[i][j] /= num;
}
ConvertDNGMatrix2XYZCAM(mCol,mXYZCAM);
double rgb_cam[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
RawImageSource::inverse33 (cam_rgb, rgb_cam);
for (i=0; i<3; i++)
for (j=0; j<3; j++) mXYZCAM[i][j]=0;
for (i=0; i<3; i++)
for (j=0; j<3; j++)
for (k=0; k<3; k++)
mXYZCAM[i][j] += xyz_sRGB[i][k] * rgb_cam[k][j];
}
}
@@ -610,43 +661,6 @@ DCPProfile::~DCPProfile() {
delete[] aDeltas1; delete[] aDeltas2;
}
// Convert DNG color matrix to xyz_cam compatible matrix
void DCPProfile::ConvertDNGMatrix2XYZCAM(const double (*mColorMatrix)[3], double (*mXYZCAM)[3]) const {
int i,j,k;
double cam_xyz[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
for (i=0; i<3; i++)
for (j=0; j<3; j++)
for (k=0; k<3; k++)
cam_xyz[i][j] += mColorMatrix[k][j] * (i==k);
// Multiply out XYZ colorspace
double cam_rgb[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
for (i=0; i < 3; i++)
for (j=0; j < 3; j++)
for (k=0; k < 3; k++)
cam_rgb[i][j] += cam_xyz[i][k] * xyz_sRGB[k][j];
// Normalize cam_rgb so that: cam_rgb * (1,1,1) is (1,1,1,1)
double num;
for (i=0; i<3; i++) {
for (num=j=0; j<3; j++) num += cam_rgb[i][j];
for (j=0; j<3; j++) cam_rgb[i][j] /= num;
}
double rgb_cam[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
RawImageSource::inverse33 (cam_rgb, rgb_cam);
for (i=0; i<3; i++)
for (j=0; j<3; j++) mXYZCAM[i][j]=0;
for (i=0; i<3; i++)
for (j=0; j<3; j++)
for (k=0; k<3; k++)
mXYZCAM[i][j] += xyz_sRGB[i][k] * rgb_cam[k][j];
}
void DCPProfile::HSDApply(const HSDTableInfo &ti, const HSBModify *tableBase, const float hs, const float ss, const float vs, float &h, float &s, float &v) const {
// Apply the HueSatMap. Ported from Adobes reference implementation
@@ -976,43 +990,11 @@ void DCPProfile::dngref_NeutralToXY(double neutral[3], int preferredIlluminant,
XY[1] = lastXY[1];
}
// Convert DNG forward matrix to xyz_cam compatible matrix
void DCPProfile::ConvertDNGForwardMatrix2XYZCAM(const double (*mForwardMatrix)[3], double (*mXYZCAM)[3], const double whiteXY[2]) const {
// Convert ForwardMatrix (white-balanced CameraRGB -> XYZ D50 matrix)
// into a ColorMatrix (XYZ -> CameraRGB)
double white_xyz[3];
XYtoXYZ(whiteXY, white_xyz);
const double white_d50[3] = { 0.3457, 0.3585, 0.2958 }; // D50
// Cancel out the white balance to get a CameraRGB -> XYZ D50 matrix (CameraToPCS in dng terminology)
double whiteDiag[3][3] = {{white_xyz[0], 0, 0}, {0, white_xyz[1], 0}, {0, 0, white_xyz[2]}};
double whiteDiagInv[3][3];
Invert3x3(whiteDiag, whiteDiagInv);
double rgb2xyzD50[3][3];
Multiply3x3(mForwardMatrix, whiteDiagInv, rgb2xyzD50);
// Through chromatic adaptation convert XYZ D50 to XYZ camera white
double whiteMatrix[3][3];
MapWhiteMatrix(white_d50, white_xyz, whiteMatrix);
double rgb2xyz[3][3];
Multiply3x3(rgb2xyzD50, whiteMatrix, rgb2xyz);
// Now we have CameraRGB -> XYZ, invert so we get XYZ -> CameraRGB (ColorMatrix format)
double dngColorMatrix[3][3];
Invert3x3(rgb2xyz, dngColorMatrix);
// now we can run the ordinary ColorMatrix conversion
ConvertDNGMatrix2XYZCAM(dngColorMatrix, mXYZCAM);
}
void DCPProfile::Apply(Imagefloat *pImg, int preferredIlluminant, Glib::ustring workingSpace, ColorTemp &wb, double pre_mul[3], double camWbMatrix[3][3], float rawWhiteFac, bool useToneCurve) const {
TMatrix mWork = iccStore->workingSpaceInverseMatrix (workingSpace);
double mXYZCAM[3][3];
double mXYZCAM[3][3]; // Camera RGB to XYZ D50 matrix
MakeXYZCAM(wb, pre_mul, camWbMatrix, preferredIlluminant, mXYZCAM);
HSBModify *deleteTableHandle;
const HSBModify *deltaBase = MakeHueSatMap(wb, preferredIlluminant, &deleteTableHandle);

View File

@@ -64,8 +64,6 @@ namespace rtengine {
void dngref_NeutralToXY(double neutral[3], int preferredIlluminant, double XY[2]) const;
void MakeXYZCAM(ColorTemp &wb, double pre_mul[3], double camWbMatrix[3][3], int preferredIlluminant, double (*mXYZCAM)[3]) const;
const HSBModify* MakeHueSatMap(ColorTemp &wb, int preferredIlluminant, HSBModify **deleteHandle) const;
void ConvertDNGMatrix2XYZCAM(const double (*mColorMatrix)[3], double (*mXYZCAM)[3]) const;
void ConvertDNGForwardMatrix2XYZCAM(const double (*mForwardMatrix)[3], double (*mXYZCAM)[3], const double whiteXY[2]) const;
void HSDApply(const HSDTableInfo &ti, const HSBModify *tableBase, const float hs, const float ss, const float vs, float &h, float &s, float &v) const;
public: