diff options
author | Philipp Knechtges <philipp-dev@knechtges.com> | 2020-05-27 22:24:57 +0200 |
---|---|---|
committer | Albert Astals Cid <tsdgeos@yahoo.es> | 2021-01-20 22:47:24 +0000 |
commit | 129905529ddfe926b252d278b7a55dd83f432f55 (patch) | |
tree | f7ae37ba7bc0c8110445e7bf9faf74b3a9d90d7c | |
parent | 222f96edc379d94661f5cab4c22c8cc9122d6e21 (diff) |
GfxCal*ColorSpace: introduce Bradford transform for chromatic adaptation
This brings the lcms2 code path and the non-lcms2 code path closer to each
other in terms of color reproduction.
So far the following points were missing, which are now added in this commit:
- Both code paths did either not adjust for a different source white point at all (lcms2 code path)
or did some simple scaling. The code has now been adjusted to use the Bradford transform
to adapt to either the D50 or D65 white point, depending on the code path.
- The non-lcms2 code path so far used the square root as an approximate gamma function.
The correct sRGB gamma function has now been added.
-rw-r--r-- | poppler/GfxState.cc | 182 | ||||
-rw-r--r-- | poppler/GfxState.h | 3 |
2 files changed, 115 insertions, 70 deletions
diff --git a/poppler/GfxState.cc b/poppler/GfxState.cc index e42585ed..424e7dad 100644 --- a/poppler/GfxState.cc +++ b/poppler/GfxState.cc @@ -635,9 +635,6 @@ GfxColorSpace *GfxCalGrayColorSpace::copy() const cs->blackY = blackY; cs->blackZ = blackZ; cs->gamma = gamma; - cs->kr = kr; - cs->kg = kg; - cs->kb = kb; #ifdef USE_CMS cs->transform = transform; #endif @@ -648,6 +645,74 @@ GfxColorSpace *GfxCalGrayColorSpace::copy() const // Language Reference, Third Edition. static const double xyzrgb[3][3] = { { 3.240449, -1.537136, -0.498531 }, { -0.969265, 1.876011, 0.041556 }, { 0.055643, -0.204026, 1.057229 } }; +// From the same reference as above, the inverse of the DecodeLMN function. +// This is essentially the gamma function of the sRGB profile. +static double srgb_gamma_function(double x) +{ + // 0.04045 is what lcms2 uses, but the PS Reference Example 4.10 specifies 0.03928??? + // if (x <= 0.04045 / 12.92321) { + if (x <= 0.03928 / 12.92321) { + return x * 12.92321; + } + return 1.055 * pow(x, 1.0 / 2.4) - 0.055; +} + +// D65 is the white point of the sRGB profile as it is specified above in the xyzrgb array +static const double white_d65_X = 0.9505; +static const double white_d65_Y = 1.0; +static const double white_d65_Z = 1.0890; + +// D50 is the default white point as used in ICC profiles and in the lcms2 library +static const double white_d50_X = 0.96422; +static const double white_d50_Y = 1.0; +static const double white_d50_Z = 0.82521; + +static void inline bradford_transform_to_d50(double &X, double &Y, double &Z, const double source_whiteX, const double source_whiteY, const double source_whiteZ) +{ + if (source_whiteX == white_d50_X && source_whiteY == white_d50_Y && source_whiteZ == white_d50_Z) { + // early exit if noop + return; + } + // at first apply Bradford matrix + double rho_in = 0.8951000 * X + 0.2664000 * Y - 0.1614000 * Z; + double gamma_in = -0.7502000 * X + 1.7135000 * Y + 0.0367000 * Z; + double beta_in = 0.0389000 * X - 0.0685000 * Y + 1.0296000 * Z; + + // apply a diagonal matrix with the diagonal entries being the inverse bradford-transformed white point + rho_in /= 0.8951000 * source_whiteX + 0.2664000 * source_whiteY - 0.1614000 * source_whiteZ; + gamma_in /= -0.7502000 * source_whiteX + 1.7135000 * source_whiteY + 0.0367000 * source_whiteZ; + beta_in /= 0.0389000 * source_whiteX - 0.0685000 * source_whiteY + 1.0296000 * source_whiteZ; + + // now revert the two steps above, but substituting the source white point by the device white point (D50) + // Since the white point is known a priori this has been combined into a single operation. + X = 0.98332566 * rho_in - 0.15005819 * gamma_in + 0.13095252 * beta_in; + Y = 0.43069901 * rho_in + 0.52894900 * gamma_in + 0.04035199 * beta_in; + Z = 0.00849698 * rho_in + 0.04086079 * gamma_in + 0.79284618 * beta_in; +} + +static void inline bradford_transform_to_d65(double &X, double &Y, double &Z, const double source_whiteX, const double source_whiteY, const double source_whiteZ) +{ + if (source_whiteX == white_d65_X && source_whiteY == white_d65_Y && source_whiteZ == white_d65_Z) { + // early exit if noop + return; + } + // at first apply Bradford matrix + double rho_in = 0.8951000 * X + 0.2664000 * Y - 0.1614000 * Z; + double gamma_in = -0.7502000 * X + 1.7135000 * Y + 0.0367000 * Z; + double beta_in = 0.0389000 * X - 0.0685000 * Y + 1.0296000 * Z; + + // apply a diagonal matrix with the diagonal entries being the inverse bradford-transformed white point + rho_in /= 0.8951000 * source_whiteX + 0.2664000 * source_whiteY - 0.1614000 * source_whiteZ; + gamma_in /= -0.7502000 * source_whiteX + 1.7135000 * source_whiteY + 0.0367000 * source_whiteZ; + beta_in /= 0.0389000 * source_whiteX - 0.0685000 * source_whiteY + 1.0296000 * source_whiteZ; + + // now revert the two steps above, but substituting the source white point by the device white point (D65) + // Since the white point is known a priori this has been combined into a single operation. + X = 0.92918329 * rho_in - 0.15299782 * gamma_in + 0.17428453 * beta_in; + Y = 0.40698452 * rho_in + 0.53931108 * gamma_in + 0.05370440 * beta_in; + Z = -0.00802913 * rho_in + 0.04166125 * gamma_in + 1.05519788 * beta_in; +} + GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr, GfxState *state) { GfxCalGrayColorSpace *cs; @@ -674,9 +739,6 @@ GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr, GfxState *state) cs->gamma = obj1.dictLookup("Gamma").getNumWithDefaultValue(1); - cs->kr = 1 / (xyzrgb[0][0] * cs->whiteX + xyzrgb[0][1] * cs->whiteY + xyzrgb[0][2] * cs->whiteZ); - cs->kg = 1 / (xyzrgb[1][0] * cs->whiteX + xyzrgb[1][1] * cs->whiteY + xyzrgb[1][2] * cs->whiteZ); - cs->kb = 1 / (xyzrgb[2][0] * cs->whiteX + xyzrgb[2][1] * cs->whiteY + xyzrgb[2][2] * cs->whiteZ); #ifdef USE_CMS cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr; #endif @@ -705,9 +767,10 @@ void GfxCalGrayColorSpace::getGray(const GfxColor *color, GfxGray *gray) const double X, Y, Z; getXYZ(color, &X, &Y, &Z); - in[0] = clip01(X); - in[1] = clip01(Y); - in[2] = clip01(Z); + bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ); + in[0] = X; + in[1] = Y; + in[2] = Z; transform->doTransform(in, out, 1); *gray = byteToCol(out[0]); return; @@ -728,9 +791,10 @@ void GfxCalGrayColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const unsigned char out[gfxColorMaxComps]; double in[gfxColorMaxComps]; - in[0] = clip01(X); - in[1] = clip01(Y); - in[2] = clip01(Z); + bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ); + in[0] = X; + in[1] = Y; + in[2] = Z; transform->doTransform(in, out, 1); rgb->r = byteToCol(out[0]); rgb->g = byteToCol(out[1]); @@ -738,16 +802,14 @@ void GfxCalGrayColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const return; } #endif - X *= whiteX; - Y *= whiteY; - Z *= whiteZ; + bradford_transform_to_d65(X, Y, Z, whiteX, whiteY, whiteZ); // convert XYZ to RGB, including gamut mapping and gamma correction r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z; g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z; b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z; - rgb->r = dblToCol(sqrt(clip01(r * kr))); - rgb->g = dblToCol(sqrt(clip01(g * kg))); - rgb->b = dblToCol(sqrt(clip01(b * kb))); + rgb->r = dblToCol(srgb_gamma_function(clip01(r))); + rgb->g = dblToCol(srgb_gamma_function(clip01(g))); + rgb->b = dblToCol(srgb_gamma_function(clip01(b))); } void GfxCalGrayColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const @@ -762,10 +824,10 @@ void GfxCalGrayColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const double X, Y, Z; getXYZ(color, &X, &Y, &Z); - in[0] = clip01(X); - in[1] = clip01(Y); - in[2] = clip01(Z); - + bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ); + in[0] = X; + in[1] = Y; + in[2] = Z; transform->doTransform(in, out, 1); cmyk->c = byteToCol(out[0]); cmyk->m = byteToCol(out[1]); @@ -991,9 +1053,6 @@ GfxColorSpace *GfxCalRGBColorSpace::copy() const cs->gammaR = gammaR; cs->gammaG = gammaG; cs->gammaB = gammaB; - cs->kr = kr; - cs->kg = kg; - cs->kb = kb; for (i = 0; i < 9; ++i) { cs->mat[i] = mat[i]; } @@ -1042,10 +1101,6 @@ GfxColorSpace *GfxCalRGBColorSpace::parse(Array *arr, GfxState *state) } } - cs->kr = 1 / (xyzrgb[0][0] * cs->whiteX + xyzrgb[0][1] * cs->whiteY + xyzrgb[0][2] * cs->whiteZ); - cs->kg = 1 / (xyzrgb[1][0] * cs->whiteX + xyzrgb[1][1] * cs->whiteY + xyzrgb[1][2] * cs->whiteZ); - cs->kb = 1 / (xyzrgb[2][0] * cs->whiteX + xyzrgb[2][1] * cs->whiteY + xyzrgb[2][2] * cs->whiteZ); - #ifdef USE_CMS cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr; #endif @@ -1076,9 +1131,10 @@ void GfxCalRGBColorSpace::getGray(const GfxColor *color, GfxGray *gray) const double X, Y, Z; getXYZ(color, &X, &Y, &Z); - in[0] = clip01(X); - in[1] = clip01(Y); - in[2] = clip01(Z); + bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ); + in[0] = X; + in[1] = Y; + in[2] = Z; transform->doTransform(in, out, 1); *gray = byteToCol(out[0]); return; @@ -1099,23 +1155,26 @@ void GfxCalRGBColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const unsigned char out[gfxColorMaxComps]; double in[gfxColorMaxComps]; - in[0] = clip01(X / whiteX); - in[1] = clip01(Y / whiteY); - in[2] = clip01(Z / whiteZ); + bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ); + in[0] = X; + in[1] = Y; + in[2] = Z; transform->doTransform(in, out, 1); rgb->r = byteToCol(out[0]); rgb->g = byteToCol(out[1]); rgb->b = byteToCol(out[2]); + return; } #endif + bradford_transform_to_d65(X, Y, Z, whiteX, whiteY, whiteZ); // convert XYZ to RGB, including gamut mapping and gamma correction r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z; g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z; b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z; - rgb->r = dblToCol(sqrt(clip01(r))); - rgb->g = dblToCol(sqrt(clip01(g))); - rgb->b = dblToCol(sqrt(clip01(b))); + rgb->r = dblToCol(srgb_gamma_function(clip01(r))); + rgb->g = dblToCol(srgb_gamma_function(clip01(g))); + rgb->b = dblToCol(srgb_gamma_function(clip01(b))); } void GfxCalRGBColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const @@ -1130,9 +1189,10 @@ void GfxCalRGBColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const double X, Y, Z; getXYZ(color, &X, &Y, &Z); - in[0] = clip01(X); - in[1] = clip01(Y); - in[2] = clip01(Z); + bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ); + in[0] = X; + in[1] = Y; + in[2] = Z; transform->doTransform(in, out, 1); cmyk->c = byteToCol(out[0]); cmyk->m = byteToCol(out[1]); @@ -1338,9 +1398,6 @@ GfxColorSpace *GfxLabColorSpace::copy() const cs->aMax = aMax; cs->bMin = bMin; cs->bMax = bMax; - cs->kr = kr; - cs->kg = kg; - cs->kb = kb; #ifdef USE_CMS cs->transform = transform; #endif @@ -1388,17 +1445,6 @@ GfxColorSpace *GfxLabColorSpace::parse(Array *arr, GfxState *state) return nullptr; } - const auto krDenominator = (xyzrgb[0][0] * cs->whiteX + xyzrgb[0][1] * cs->whiteY + xyzrgb[0][2] * cs->whiteZ); - const auto kgDenominator = (xyzrgb[1][0] * cs->whiteX + xyzrgb[1][1] * cs->whiteY + xyzrgb[1][2] * cs->whiteZ); - const auto kbDenominator = (xyzrgb[2][0] * cs->whiteX + xyzrgb[2][1] * cs->whiteY + xyzrgb[2][2] * cs->whiteZ); - if (unlikely(krDenominator == 0 || kgDenominator == 0 || kbDenominator == 0)) { - delete cs; - return nullptr; - } - cs->kr = 1 / krDenominator; - cs->kg = 1 / kgDenominator; - cs->kb = 1 / kbDenominator; - #ifdef USE_CMS cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr; #endif @@ -1415,6 +1461,7 @@ void GfxLabColorSpace::getGray(const GfxColor *color, GfxGray *gray) const double in[gfxColorMaxComps]; getXYZ(color, &in[0], &in[1], &in[2]); + bradford_transform_to_d50(in[0], in[1], in[2], whiteX, whiteY, whiteZ); transform->doTransform(in, out, 1); *gray = byteToCol(out[0]); return; @@ -1467,9 +1514,10 @@ void GfxLabColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const unsigned char out[gfxColorMaxComps]; double in[gfxColorMaxComps]; - in[0] = clip01(X); - in[1] = clip01(Y); - in[2] = clip01(Z); + bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ); + in[0] = X; + in[1] = Y; + in[2] = Z; transform->doTransform(in, out, 1); rgb->r = byteToCol(out[0]); rgb->g = byteToCol(out[1]); @@ -1480,9 +1528,10 @@ void GfxLabColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const double in[gfxColorMaxComps]; double c, m, y, k, c1, m1, y1, k1, r, g, b; - in[0] = clip01(X); - in[1] = clip01(Y); - in[2] = clip01(Z); + bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ); + in[0] = X; + in[1] = Y; + in[2] = Z; transform->doTransform(in, out, 1); c = byteToDbl(out[0]); m = byteToDbl(out[1]); @@ -1499,13 +1548,14 @@ void GfxLabColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const return; } #endif + bradford_transform_to_d65(X, Y, Z, whiteX, whiteY, whiteZ); // convert XYZ to RGB, including gamut mapping and gamma correction const double r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z; const double g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z; const double b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z; - rgb->r = dblToCol(sqrt(clip01(r * kr))); - rgb->g = dblToCol(sqrt(clip01(g * kg))); - rgb->b = dblToCol(sqrt(clip01(b * kb))); + rgb->r = dblToCol(srgb_gamma_function(clip01(r))); + rgb->g = dblToCol(srgb_gamma_function(clip01(g))); + rgb->b = dblToCol(srgb_gamma_function(clip01(b))); } void GfxLabColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const @@ -1519,9 +1569,7 @@ void GfxLabColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const unsigned char out[gfxColorMaxComps]; getXYZ(color, &in[0], &in[1], &in[2]); - in[0] *= whiteX; - in[1] *= whiteY; - in[2] *= whiteZ; + bradford_transform_to_d50(in[0], in[1], in[2], whiteX, whiteY, whiteZ); transform->doTransform(in, out, 1); cmyk->c = byteToCol(out[0]); cmyk->m = byteToCol(out[1]); diff --git a/poppler/GfxState.h b/poppler/GfxState.h index f607dade..6c666882 100644 --- a/poppler/GfxState.h +++ b/poppler/GfxState.h @@ -379,7 +379,6 @@ private: double whiteX, whiteY, whiteZ; // white point double blackX, blackY, blackZ; // black point double gamma; // gamma value - double kr, kg, kb; // gamut mapping mulitpliers void getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const; #ifdef USE_CMS std::shared_ptr<GfxColorTransform> transform; @@ -460,7 +459,6 @@ private: double blackX, blackY, blackZ; // black point double gammaR, gammaG, gammaB; // gamma values double mat[9]; // ABC -> XYZ transform matrix - double kr, kg, kb; // gamut mapping mulitpliers void getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const; #ifdef USE_CMS std::shared_ptr<GfxColorTransform> transform; @@ -539,7 +537,6 @@ private: double whiteX, whiteY, whiteZ; // white point double blackX, blackY, blackZ; // black point double aMin, aMax, bMin, bMax; // range for the a and b components - double kr, kg, kb; // gamut mapping mulitpliers void getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const; #ifdef USE_CMS std::shared_ptr<GfxColorTransform> transform; |