[poppler] Color Management
Koji Otani
sho at bbr.jp
Thu Nov 27 00:57:37 PST 2008
Hi, All
I made a patch to add Color Management to poppler.
I attached it to this mail.
This patch is one for current source in the GIT repository.
I'm not a CM expert. And this patch doesn't resolve all problems.
But, I hope to make some progress.
Please try this.
------------
Koji Otani.
--------------
This patch adds Color Management feature to polper:
(1) Handling ICCBasedColorSpace (both V2 and V4 profiles)
(2) Handling CalRGB and CalGray
This doesn't handle Rendering Intent.
This always renders with relative colorimetric intent (default in PDF).
This doesn't handle transparency.
This uses Little cms library, so when compiling this, lcms-devel package is
needed. if lcms-devel package is not installed, disable this feature.
In default, ICCBased uses builin Standard RGB,
Lab, CalRGB and CalGray don't use profile.
If you want to use other profile, put profile file to
$HOME/.xpdf/ColorProfiles/display.icc
or
/usr/share/poppler/ColorProfiles/display.icc
This profile is used by ICCBased, Lab, CalRGB, CalGray.
You can also change display profile from your application.
Before calling PDFDoc::displayPage, call
GfxColorSpace::setDisplayProfileName
or
GfxColorSpace::setDisplayProfile
static void setDisplayProfile(cmsHPROFILE displayProfileA)
set profile handle
static void setDisplayProfileName(GooString *name)
set profile file name
If file name started with '/', it is handled as absolute path name.
Otherwise, it is handled as relative path to
$HOME/.xpdf/ColorProfiles/
and
/usr/share/poppler/ColorProfiles/
-----------
From: "Hal V. Engel" <hvengel at astound.net>
Subject: Re: [poppler] Color Management
Date: Thu, 21 Aug 2008 18:51:59 -0700
Message-ID: <200808211852.00098.hvengel at astound.net>
hvengel> On Thursday 07 August 2008 06:58:13 pm Leonard Rosenthol wrote:
hvengel> > Correct. You'd need to make MAJOR changes to the rendering
hvengel> > architecture of Poppler to do proper color management - especially
hvengel> > where transparency is involved.
hvengel> >
hvengel> > But it would be a very good thing...
hvengel> >
hvengel> > Leonard
hvengel>
hvengel> (This is kind of long please bear with me).
hvengel>
hvengel> I have been looking at the PDF spec. and at the poppler code. Leonard is
hvengel> correct that the code in it's present form is not designed to support color
hvengel> management and it will require major changes.
hvengel>
hvengel> Some exmples:
hvengel>
hvengel> The PDF spec calls for all CIE based colorspaces (Lab, calRGB, calCMYK and
hvengel> ICCBased) to undergo the following transformations:
hvengel>
hvengel> original color space -> XYZ -> output color space
hvengel>
hvengel> This implies that there is clear separation between the first conversion and
hvengel> the second conversion and that one set of routines can handle the second
hvengel> conversion for all CIE based color transforms.
hvengel>
hvengel> It also implies that there is some way for calling applications to specifiy a
hvengel> CIE based output color space such as an ICC profile and related information
hvengel> (rendering intents, black point comp. and output channel depth). This is
hvengel> currently not possible.
hvengel>
hvengel> The code as it exists only does a direct conversion to XYZ in one location
hvengel> GfxLabColorSpace::getRGB() and then does a second conversion to an arbitrary
hvengel> RGB** color space in the same function. None of the other CIE based color
hvengel> space conversions produce an intermeadiate XYZ conversion and of course there
hvengel> are no generic XYZ to output color space routines.
hvengel>
hvengel> The gray and cmyk code for Lab conversions uses the getRGB() function and then
hvengel> does some kind of generic conversion using the RGB values. Since these RGB
hvengel> values are in an arbitrary RGB** color space the conversion to gray and CMYK
hvengel> is also arbitrary.
hvengel>
hvengel> CalGray does not do gamma compensation in getGray() as called for in the PDF
hvengel> spec. In addition the CalGray functions only support 8 bit depth. What
hvengel> happens if there is a 16bit/channel gray image in a PDF file or if a user
hvengel> wants 16 bit/channel output for his/her printer?
hvengel>
hvengel> CalRGB passes RGB values directly through to the output and makes no attempt
hvengel> to convert these into an intermeadiate XYZ color space or into the actual
hvengel> output colorspace. It also does not apply a gamma correction to the data as
hvengel> per the PDF spec.
hvengel>
hvengel> End examples:
hvengel>
hvengel> I don't think that I am pointing out anything new to most on this list. When
hvengel> I first started to look at the code I was hoping that it would not be too
hvengel> difficult to find the places where CM hooks could be put in place and that
hvengel> perhaps I could spend a few days putting together a set of patches that would
hvengel> provide a starting place. But it appears that the code needs significant
hvengel> restructuring in order to even start doing the actaul color management
hvengel> specific work. Fortunately the PDF specification has enough detail that it
hvengel> should be possible for the poppler team to do much of the restructuring work
hvengel> without too much involvement from someone with color management expertise.
hvengel>
hvengel> Mostly what is needed is:
hvengel>
hvengel> 1. An API for applications to specify the output color space, rendering
hvengel> intent, black point compensation and channel depth for a document. These are
hvengel> needed to correctly setup the XYZ to output color space transform.
hvengel>
hvengel> 2. The CIE related code (calRGB, calGray, Lab and ICCBased) needs to be
hvengel> restructured so that it has a clean division between producing the
hvengel> intermeadeate XYZ values and the code that does the XYZ to output color space
hvengel> conversion. See the diagram on pages 238 and 239 of the version 1.7 PDF
hvengel> Reference.
hvengel>
hvengel> Initially the ICCBased code could be left alone and calRGB, calGray and Lab
hvengel> would be setup to convert to XYZ but would still do the current simplistic
hvengel> conversions to the output color space (IE. these would all look some what like
hvengel> GfxLabColorSpace::getRGB() & friends) .
hvengel>
hvengel> Then the XYZ to output color space routines would be added and the calRGB,
hvengel> calGray and Lab routines would call them to handle the output conversion in a
hvengel> more correct way. It is at this point where the code would start making use
hvengel> of a CMS like LCMS as well as the API that was created in #1. This
hvengel> functionality is documented in the diagram on page 239 of the PDF version 1.7
hvengel> spec as "Conversion from CIE-based to device color space (not specified by
hvengel> PDF)".
hvengel>
hvengel> 3. At this point setting up the ICCBased routines to create XYZ intermeadiate
hvengel> results would be added. Once this is in place poppler would have a
hvengel> functioning color managed system for all of the CIE based color space types.
hvengel>
hvengel> I have not looked at the PDF specs for "Special color spaces" such as
hvengel> Separation, DeviceN, Indexed and Pattern so I do not know what implications
hvengel> there are for these in a color managed system. I have also not looked at
hvengel> transparancy issues but I didn't see much in the spec that related to ICCBased
hvengel> objects other than that it was nessassary to have to AtoB and BtoA tables in
hvengel> the profiles to support this and that all blending must be either in device
hvengel> space or in CIE space and that the spec advises that CIE is prefered.
hvengel>
hvengel> Many of you are probably asking "Why should we give a rats behind about this?"
hvengel> The reason I am looking at this is that the printing community is in the
hvengel> process of converting from PostScript to PDF as the standard document type for
hvengel> printing for *nix systems. Because of this they are in the process of writing
hvengel> a new pdftoraster filter for CUPS as well as putting together a PDF based
hvengel> "Common Printing Dialog" (this is a Google Summer of Code project). I asked
hvengel> on the printing email lists about color management in the new printing
hvengel> workflow. Specifcally did the new pdftoraster filter have CM support? I
hvengel> didn't get an answer and so I found the code and had a look. Guess what (most
hvengel> of you probably know this) it is using poppler and as a result it does not
hvengel> support color management.
hvengel>
hvengel> Printing is an important piece of the infastructure in general and
hvengel> particularly for anyone doing color critical work. It is currently badly
hvengel> broken with respect to color managemnt and it appears that until poppler has
hvengel> CM support or another similar library with CM support becomes available it
hvengel> will remain broken. So now you know what the story is and why this is
hvengel> important.
hvengel>
hvengel> I have color management expertise (I am the maintainer of LProf) and I have
hvengel> connections to the open source color management community where there are many
hvengel> others with CM expertise some of them with way more expertise than I have. As
hvengel> a community we have been working with the printing community, Xorg and the
hvengel> DE's trying to get the missing CM components in place.
hvengel>
hvengel> There has been major progress with color management on monitors and we now
hvengel> have calibration and profiling software for these devices as well as support
hvengel> in XOrg (with more on the way) and work is underway to extend this to include
hvengel> better user tools in KDE and GNOME. We also have good open source tools for
hvengel> profiling input devices like cameras and scanners and these profiles are now
hvengel> well supported by things like XSane, UFRAW and other software in this area.
hvengel>
hvengel> We also have high quality profiling software for output devices like printers.
hvengel> One of the major things that is missing is a viable color managed path for
hvengel> printing. That is I can create very high quality profiles for my printers but
hvengel> using them with the existing printing tools is at best difficult even for
hvengel> someone who really understands CM and nearly imposible for a normal user. If
hvengel> the CUPS pdftoraster filter had CM support much of the printing part of this
hvengel> would start falling in place and would become accessable to a much wider
hvengel> audiance. So from the point of view of the CM community poppler is now a very
hvengel> important piece of software.
hvengel>
hvengel> Hal
hvengel>
hvengel> PS For those interested this web page from the ICC has a link to a PDF file
hvengel> from Adobe that demonstrates CM or a lack of CM as the case may be:
hvengel>
hvengel> http://www.color.org/version4html.xalter
hvengel>
hvengel>
hvengel> **It looks like it might end up being sort of sRGB since it uses a matrix
hvengel> conversion and then a gamma conversion that is too simple to give actual sRGB
hvengel> results since sRGB has a compound gamma curve.
hvengel>
-------------- next part --------------
diff --git a/configure.ac b/configure.ac
index 5fa5832..0ee2813 100644
--- a/configure.ac
+++ b/configure.ac
@@ -425,6 +425,24 @@ AC_ARG_ENABLE(compile-warnings,
[Turn on compiler warnings.]),,
[enable_compile_warnings="yes"])
+dnl
+dnl Color Management
+dnl
+
+AC_ARG_ENABLE(cms,
+ AC_HELP_STRING([--disable-cms],
+ [Don't use color management system.]),
+ enable_cms=$enableval,
+ enable_cms="yes")
+if test x$enable_cms = xyes; then
+ AC_CHECK_LIB([lcms],cmsOpenProfileFromFile,,
+ AC_MSG_ERROR("*** lcms library not found ***"))
+ AC_CHECK_HEADERS([lcms.h],,
+ AC_MSG_ERROR("*** lcms headers not found ***"))
+ AC_DEFINE(USE_CMS, 1, [Defines if use cms])
+fi
+AM_CONDITIONAL(USE_CMS, test x$enable_cms = xyes)
+
if test "x$GCC" != xyes; then
enable_compile_warnings=no
fi
@@ -489,6 +507,7 @@ echo " use gtk-doc: $enable_gtk_doc"
echo " use libjpeg: $enable_libjpeg"
echo " use zlib: $enable_zlib"
echo " use libopenjpeg: $enable_libopenjpeg"
+echo " use cms: $enable_cms"
echo " command line utils: $enable_utils"
echo ""
diff --git a/poppler/GfxState.cc b/poppler/GfxState.cc
index 7bb0667..5b35f13 100644
--- a/poppler/GfxState.cc
+++ b/poppler/GfxState.cc
@@ -39,6 +39,7 @@
#include "Page.h"
#include "GfxState.h"
#include "GfxFont.h"
+#include "GlobalParams.h"
//------------------------------------------------------------------------
@@ -224,6 +225,225 @@ void GfxColorSpace::getRGBLine(Guchar *in, unsigned int *out, int length) {
}
}
+#ifdef USE_CMS
+cmsHPROFILE GfxColorSpace::RGBProfile = NULL;
+cmsHPROFILE GfxColorSpace::displayProfile = NULL;
+GooString *GfxColorSpace::displayProfileName = NULL;
+unsigned int GfxColorSpace::displayPixelType = 0;
+GfxColorTransform *GfxColorSpace::XYZ2DisplayTransform = NULL;
+
+cmsHPROFILE GfxColorSpace::loadColorProfile(const char *fileName)
+{
+ cmsHPROFILE hp = NULL;
+ FILE *fp;
+
+ if (fileName[0] == '/') {
+ // full path
+ // check if open the file
+ if ((fp = fopen(fileName,"r")) != NULL) {
+ fclose(fp);
+ hp = cmsOpenProfileFromFile(fileName,"r");
+ }
+ return hp;
+ }
+ // try to load from user directory
+ GooString *path = globalParams->getBaseDir();
+ path->append(COLOR_PROFILE_DIR);
+ path->append(fileName);
+ // check if open the file
+ if ((fp = fopen(path->getCString(),"r")) != NULL) {
+ fclose(fp);
+ hp = cmsOpenProfileFromFile(path->getCString(),"r");
+ }
+ delete path;
+ if (hp == NULL) {
+ // load from global directory
+ path = new GooString(GLOBAL_COLOR_PROFILE_DIR);
+ path->append(fileName);
+ // check if open the file
+ if ((fp = fopen(path->getCString(),"r")) != NULL) {
+ fclose(fp);
+ hp = cmsOpenProfileFromFile(path->getCString(),"r");
+ }
+ delete path;
+ }
+ return hp;
+}
+
+static int CMSError(int ecode, const char *msg)
+{
+ error(-1,const_cast<char *>(msg));
+ return 1;
+}
+
+int GfxColorSpace::setupColorProfiles()
+{
+ static GBool initialized = gFalse;
+ cmsHTRANSFORM transform;
+ unsigned int nChannels;
+
+ // do only once
+ if (initialized) return 0;
+ initialized = gTrue;
+
+ // set error handlor
+ cmsSetErrorHandler(CMSError);
+
+ if (displayProfile == NULL) {
+ // load display profile if it was not already loaded.
+ if (displayProfileName == NULL) {
+ displayProfile = loadColorProfile("display.icc");
+ } else if (displayProfileName->getLength() > 0) {
+ displayProfile = loadColorProfile(displayProfileName->getCString());
+ }
+ }
+ // load RGB profile
+ RGBProfile = loadColorProfile("RGB.icc");
+ if (RGBProfile == NULL) {
+ /* use built in sRGB profile */
+ RGBProfile = cmsCreate_sRGBProfile();
+ }
+ // create transforms
+ if (displayProfile != NULL) {
+ displayPixelType = getCMSColorSpaceType(cmsGetColorSpace(displayProfile));
+ nChannels = getCMSNChannels(cmsGetColorSpace(displayProfile));
+ // create transform from XYZ
+ cmsHPROFILE XYZProfile = cmsCreateXYZProfile();
+ if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
+ displayProfile,
+ COLORSPACE_SH(displayPixelType) |
+ CHANNELS_SH(nChannels) | BYTES_SH(0),
+ INTENT_RELATIVE_COLORIMETRIC,0)) == 0) {
+ error(-1, "Can't create Lab transform");
+ } else {
+ XYZ2DisplayTransform = new GfxColorTransform(transform);
+ }
+ cmsCloseProfile(XYZProfile);
+ }
+ return 0;
+}
+
+unsigned int GfxColorSpace::getCMSColorSpaceType(icColorSpaceSignature cs)
+{
+ switch (cs) {
+ case icSigXYZData:
+ return PT_XYZ;
+ break;
+ case icSigLabData:
+ return PT_Lab;
+ break;
+ case icSigLuvData:
+ return PT_YUV;
+ break;
+ case icSigYCbCrData:
+ return PT_YCbCr;
+ break;
+ case icSigYxyData:
+ return PT_Yxy;
+ break;
+ case icSigRgbData:
+ return PT_RGB;
+ break;
+ case icSigGrayData:
+ return PT_GRAY;
+ break;
+ case icSigHsvData:
+ return PT_HSV;
+ break;
+ case icSigHlsData:
+ return PT_HLS;
+ break;
+ case icSigCmykData:
+ return PT_CMYK;
+ break;
+ case icSigCmyData:
+ return PT_CMY;
+ break;
+ case icSig2colorData:
+ case icSig3colorData:
+ case icSig4colorData:
+ case icSig5colorData:
+ case icSig6colorData:
+ case icSig7colorData:
+ case icSig8colorData:
+ case icSig9colorData:
+ case icSig10colorData:
+ case icSig11colorData:
+ case icSig12colorData:
+ case icSig13colorData:
+ case icSig14colorData:
+ case icSig15colorData:
+ default:
+ break;
+ }
+ return PT_RGB;
+}
+
+unsigned int GfxColorSpace::getCMSNChannels(icColorSpaceSignature cs)
+{
+ switch (cs) {
+ case icSigXYZData:
+ case icSigLuvData:
+ case icSigLabData:
+ case icSigYCbCrData:
+ case icSigYxyData:
+ case icSigRgbData:
+ case icSigHsvData:
+ case icSigHlsData:
+ case icSigCmyData:
+ case icSig3colorData:
+ return 3;
+ break;
+ case icSigGrayData:
+ return 1;
+ break;
+ case icSigCmykData:
+ case icSig4colorData:
+ return 4;
+ break;
+ case icSig2colorData:
+ return 2;
+ break;
+ case icSig5colorData:
+ return 5;
+ break;
+ case icSig6colorData:
+ return 6;
+ break;
+ case icSig7colorData:
+ return 7;
+ break;
+ case icSig8colorData:
+ return 8;
+ break;
+ case icSig9colorData:
+ return 9;
+ break;
+ case icSig10colorData:
+ return 10;
+ break;
+ case icSig11colorData:
+ return 11;
+ break;
+ case icSig12colorData:
+ return 12;
+ break;
+ case icSig13colorData:
+ return 13;
+ break;
+ case icSig14colorData:
+ return 14;
+ break;
+ case icSig15colorData:
+ return 15;
+ default:
+ break;
+ }
+ return 3;
+}
+
+#endif
+
void GfxColorSpace::getGrayLine(Guchar *in, unsigned char *out, int length) {
int i, j, n;
GfxColor color;
@@ -311,6 +531,14 @@ GfxColorSpace *GfxCalGrayColorSpace::copy() {
return cs;
}
+// This is the inverse of MatrixLMN in Example 4.10 from the PostScript
+// 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 }
+};
+
GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr) {
GfxCalGrayColorSpace *cs;
Object obj1, obj2, obj3;
@@ -353,32 +581,127 @@ GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr) {
}
obj2.free();
obj1.free();
+
+ 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);
+
return cs;
}
-void GfxCalGrayColorSpace::getGray(GfxColor *color, GfxGray *gray) {
- *gray = clip01(color->c[0]);
-}
+// convert CalGray to media XYZ color space
+// (not multiply by the white point)
+void GfxCalGrayColorSpace::getXYZ(GfxColor *color,
+ double *pX, double *pY, double *pZ) {
+ double A;
-void GfxCalGrayColorSpace::getGrayLine(Guchar *in, Guchar *out, int length) {
- memcpy (out, in, length);
+ A = colToDbl(color->c[0]);
+ *pX = pow(A,gamma);
+ *pY = pow(A,gamma);
+ *pZ = pow(A,gamma);
}
-void GfxCalGrayColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
- rgb->r = rgb->g = rgb->b = clip01(color->c[0]);
+void GfxCalGrayColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+ GfxRGB rgb;
+
+#ifdef USE_CMS
+ if (XYZ2DisplayTransform != NULL && displayPixelType == PT_GRAY) {
+ double out[gfxColorMaxComps];
+ double in[gfxColorMaxComps];
+ double X, Y, Z;
+
+ getXYZ(color,&X,&Y,&Z);
+ in[0] = clip01(X);
+ in[1] = clip01(Y);
+ in[2] = clip01(Z);
+ XYZ2DisplayTransform->doTransform(in,out,1);
+ *gray = dblToCol(out[0]);
+ return;
+ }
+#endif
+ getRGB(color, &rgb);
+ *gray = clip01((GfxColorComp)(0.299 * rgb.r +
+ 0.587 * rgb.g +
+ 0.114 * rgb.b + 0.5));
}
-void GfxCalGrayColorSpace::getRGBLine(Guchar *in, unsigned int *out,
- int length) {
- int i;
+void GfxCalGrayColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+ double X, Y, Z;
+ double r, g, b;
- for (i = 0; i < length; i++)
- out[i] = (in[i] << 16) | (in[i] << 8) | (in[i] << 0);
+ getXYZ(color,&X,&Y,&Z);
+#if USE_CMS
+ if (XYZ2DisplayTransform != NULL && displayPixelType == PT_RGB) {
+ double out[gfxColorMaxComps];
+ double in[gfxColorMaxComps];
+
+ in[0] = clip01(X);
+ in[1] = clip01(Y);
+ in[2] = clip01(Z);
+ XYZ2DisplayTransform->doTransform(in,out,1);
+ rgb->r = dblToCol(out[0]);
+ rgb->g = dblToCol(out[1]);
+ rgb->b = dblToCol(out[2]);
+ return;
+ }
+#endif
+ X *= whiteX;
+ Y *= whiteY;
+ Z *= 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(pow(clip01(r * kr), 0.5));
+ rgb->g = dblToCol(pow(clip01(g * kg), 0.5));
+ rgb->b = dblToCol(pow(clip01(b * kb), 0.5));
+ rgb->r = rgb->g = rgb->b = clip01(color->c[0]);
}
void GfxCalGrayColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
- cmyk->c = cmyk->m = cmyk->y = 0;
- cmyk->k = clip01(gfxColorComp1 - color->c[0]);
+ GfxRGB rgb;
+ GfxColorComp c, m, y, k;
+
+#ifdef USE_CMS
+ if (XYZ2DisplayTransform != NULL && displayPixelType == PT_CMYK) {
+ double in[gfxColorMaxComps];
+ double out[gfxColorMaxComps];
+ double X, Y, Z;
+
+ getXYZ(color,&X,&Y,&Z);
+ in[0] = clip01(X);
+ in[1] = clip01(Y);
+ in[2] = clip01(Z);
+
+ XYZ2DisplayTransform->doTransform(in,out,1);
+ cmyk->c = dblToCol(out[0]);
+ cmyk->m = dblToCol(out[1]);
+ cmyk->y = dblToCol(out[2]);
+ cmyk->k = dblToCol(out[3]);
+ return;
+ }
+#endif
+ getRGB(color, &rgb);
+ c = clip01(gfxColorComp1 - rgb.r);
+ m = clip01(gfxColorComp1 - rgb.g);
+ y = clip01(gfxColorComp1 - rgb.b);
+ k = c;
+ if (m < k) {
+ k = m;
+ }
+ if (y < k) {
+ k = y;
+ }
+ cmyk->c = c - k;
+ cmyk->m = m - k;
+ cmyk->y = y - k;
+ cmyk->k = k;
}
void GfxCalGrayColorSpace::getDefaultColor(GfxColor *color) {
@@ -553,47 +876,112 @@ GfxColorSpace *GfxCalRGBColorSpace::parse(Array *arr) {
}
obj2.free();
obj1.free();
+
+ 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);
+
return cs;
}
-void GfxCalRGBColorSpace::getGray(GfxColor *color, GfxGray *gray) {
- *gray = clip01((GfxColorComp)(0.299 * color->c[0] +
- 0.587 * color->c[1] +
- 0.114 * color->c[2] + 0.5));
+// convert CalRGB to XYZ color space
+void GfxCalRGBColorSpace::getXYZ(GfxColor *color,
+ double *pX, double *pY, double *pZ) {
+ double A, B, C;
+
+ A = colToDbl(color->c[0]);
+ B = colToDbl(color->c[1]);
+ C = colToDbl(color->c[2]);
+ *pX = mat[0]*pow(A,gammaR)+mat[3]*pow(B,gammaG)+mat[6]*pow(C,gammaB);
+ *pY = mat[1]*pow(A,gammaR)+mat[4]*pow(B,gammaG)+mat[7]*pow(C,gammaB);
+ *pZ = mat[2]*pow(A,gammaR)+mat[5]*pow(B,gammaG)+mat[8]*pow(C,gammaB);
}
-void GfxCalRGBColorSpace::getGrayLine(Guchar *in, Guchar *out, int length) {
- int i;
+void GfxCalRGBColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+ GfxRGB rgb;
- for (i = 0; i < length; i++) {
- out[i] =
- (in[i * 3 + 0] * 19595 +
- in[i * 3 + 1] * 38469 +
- in[i * 3 + 2] * 7472) / 65536;
+#ifdef USE_CMS
+ if (XYZ2DisplayTransform != NULL && displayPixelType == PT_GRAY) {
+ double out[gfxColorMaxComps];
+ double in[gfxColorMaxComps];
+ double X, Y, Z;
+
+ getXYZ(color,&X,&Y,&Z);
+ in[0] = clip01(X);
+ in[1] = clip01(Y);
+ in[2] = clip01(Z);
+ XYZ2DisplayTransform->doTransform(in,out,1);
+ *gray = dblToCol(out[0]);
+ return;
}
+#endif
+ getRGB(color, &rgb);
+ *gray = clip01((GfxColorComp)(0.299 * rgb.r +
+ 0.587 * rgb.g +
+ 0.114 * rgb.b + 0.5));
}
void GfxCalRGBColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
- rgb->r = clip01(color->c[0]);
- rgb->g = clip01(color->c[1]);
- rgb->b = clip01(color->c[2]);
-}
-
-void GfxCalRGBColorSpace::getRGBLine(Guchar *in, unsigned int *out,
- int length) {
- Guchar *p;
- int i;
+ double X, Y, Z;
+ double r, g, b;
- for (i = 0, p = in; i < length; i++, p += 3)
- out[i] = (p[0] << 16) | (p[1] << 8) | (p[2] << 0);
+ getXYZ(color,&X,&Y,&Z);
+#if USE_CMS
+ if (XYZ2DisplayTransform != NULL && displayPixelType == PT_RGB) {
+ double out[gfxColorMaxComps];
+ double in[gfxColorMaxComps];
+
+ in[0] = clip01(X/whiteX);
+ in[1] = clip01(Y/whiteY);
+ in[2] = clip01(Z/whiteZ);
+ XYZ2DisplayTransform->doTransform(in,out,1);
+ rgb->r = dblToCol(out[0]);
+ rgb->g = dblToCol(out[1]);
+ rgb->b = dblToCol(out[2]);
+ return;
+ }
+#endif
+ // 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(pow(clip01(r), 0.5));
+ rgb->g = dblToCol(pow(clip01(g), 0.5));
+ rgb->b = dblToCol(pow(clip01(b), 0.5));
}
void GfxCalRGBColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+ GfxRGB rgb;
GfxColorComp c, m, y, k;
- c = clip01(gfxColorComp1 - color->c[0]);
- m = clip01(gfxColorComp1 - color->c[1]);
- y = clip01(gfxColorComp1 - color->c[2]);
+#ifdef USE_CMS
+ if (XYZ2DisplayTransform != NULL && displayPixelType == PT_CMYK) {
+ double in[gfxColorMaxComps];
+ double out[gfxColorMaxComps];
+ double X, Y, Z;
+
+ getXYZ(color,&X,&Y,&Z);
+ in[0] = clip01(X);
+ in[1] = clip01(Y);
+ in[2] = clip01(Z);
+ XYZ2DisplayTransform->doTransform(in,out,1);
+ cmyk->c = dblToCol(out[0]);
+ cmyk->m = dblToCol(out[1]);
+ cmyk->y = dblToCol(out[2]);
+ cmyk->k = dblToCol(out[3]);
+ return;
+ }
+#endif
+ getRGB(color, &rgb);
+ c = clip01(gfxColorComp1 - rgb.r);
+ m = clip01(gfxColorComp1 - rgb.g);
+ y = clip01(gfxColorComp1 - rgb.b);
k = c;
if (m < k) {
k = m;
@@ -714,14 +1102,6 @@ void GfxDeviceCMYKColorSpace::getDefaultColor(GfxColor *color) {
// GfxLabColorSpace
//------------------------------------------------------------------------
-// This is the inverse of MatrixLMN in Example 4.10 from the PostScript
-// 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 }
-};
-
GfxLabColorSpace::GfxLabColorSpace() {
whiteX = whiteY = whiteZ = 1;
blackX = blackY = blackZ = 0;
@@ -823,18 +1203,30 @@ GfxColorSpace *GfxLabColorSpace::parse(Array *arr) {
void GfxLabColorSpace::getGray(GfxColor *color, GfxGray *gray) {
GfxRGB rgb;
+#ifdef USE_CMS
+ if (XYZ2DisplayTransform != NULL && displayPixelType == PT_GRAY) {
+ double out[gfxColorMaxComps];
+ double in[gfxColorMaxComps];
+
+ getXYZ(color, &in[0], &in[1], &in[2]);
+ XYZ2DisplayTransform->doTransform(in,out,1);
+ *gray = dblToCol(out[0]);
+ return;
+ }
+#endif
getRGB(color, &rgb);
*gray = clip01((GfxColorComp)(0.299 * rgb.r +
0.587 * rgb.g +
0.114 * rgb.b + 0.5));
}
-void GfxLabColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+// convert L*a*b* to media XYZ color space
+// (not multiply by the white point)
+void GfxLabColorSpace::getXYZ(GfxColor *color,
+ double *pX, double *pY, double *pZ) {
double X, Y, Z;
double t1, t2;
- double r, g, b;
- // convert L*a*b* to CIE 1931 XYZ color space
t1 = (colToDbl(color->c[0]) + 16) / 116;
t2 = t1 + colToDbl(color->c[1]) / 500;
if (t2 >= (6.0 / 29.0)) {
@@ -842,21 +1234,45 @@ void GfxLabColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
} else {
X = (108.0 / 841.0) * (t2 - (4.0 / 29.0));
}
- X *= whiteX;
if (t1 >= (6.0 / 29.0)) {
Y = t1 * t1 * t1;
} else {
Y = (108.0 / 841.0) * (t1 - (4.0 / 29.0));
}
- Y *= whiteY;
t2 = t1 - colToDbl(color->c[2]) / 200;
if (t2 >= (6.0 / 29.0)) {
Z = t2 * t2 * t2;
} else {
Z = (108.0 / 841.0) * (t2 - (4.0 / 29.0));
}
- Z *= whiteZ;
+ *pX = X;
+ *pY = Y;
+ *pZ = Z;
+}
+void GfxLabColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+ double X, Y, Z;
+ double r, g, b;
+
+ getXYZ(color, &X, &Y, &Z);
+#ifdef USE_CMS
+ if (XYZ2DisplayTransform != NULL && displayPixelType == PT_RGB) {
+ double out[gfxColorMaxComps];
+ double in[gfxColorMaxComps];
+
+ in[0] = clip01(X);
+ in[1] = clip01(Y);
+ in[2] = clip01(Z);
+ XYZ2DisplayTransform->doTransform(in,out,1);
+ rgb->r = dblToCol(out[0]);
+ rgb->g = dblToCol(out[1]);
+ rgb->b = dblToCol(out[2]);
+ return;
+ }
+#endif
+ X *= whiteX;
+ Y *= whiteY;
+ Z *= 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;
@@ -870,6 +1286,20 @@ void GfxLabColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
GfxRGB rgb;
GfxColorComp c, m, y, k;
+#ifdef USE_CMS
+ if (XYZ2DisplayTransform != NULL && displayPixelType == PT_CMYK) {
+ double in[gfxColorMaxComps];
+ double out[gfxColorMaxComps];
+
+ getXYZ(color, &in[0], &in[1], &in[2]);
+ XYZ2DisplayTransform->doTransform(in,out,1);
+ cmyk->c = dblToCol(out[0]);
+ cmyk->m = dblToCol(out[1]);
+ cmyk->y = dblToCol(out[2]);
+ cmyk->k = dblToCol(out[3]);
+ return;
+ }
+#endif
getRGB(color, &rgb);
c = clip01(gfxColorComp1 - rgb.r);
m = clip01(gfxColorComp1 - rgb.g);
@@ -926,10 +1356,22 @@ GfxICCBasedColorSpace::GfxICCBasedColorSpace(int nCompsA, GfxColorSpace *altA,
iccProfileStream = *iccProfileStreamA;
rangeMin[0] = rangeMin[1] = rangeMin[2] = rangeMin[3] = 0;
rangeMax[0] = rangeMax[1] = rangeMax[2] = rangeMax[3] = 1;
+#ifdef USE_CMS
+ transform = NULL;
+ lineTransform = NULL;
+#endif
}
GfxICCBasedColorSpace::~GfxICCBasedColorSpace() {
delete alt;
+#ifdef USE_CMS
+ if (transform != NULL) {
+ if (transform->unref() == 0) delete transform;
+ }
+ if (lineTransform != NULL) {
+ if (lineTransform->unref() == 0) delete lineTransform;
+ }
+#endif
}
GfxColorSpace *GfxICCBasedColorSpace::copy() {
@@ -941,6 +1383,12 @@ GfxColorSpace *GfxICCBasedColorSpace::copy() {
cs->rangeMin[i] = rangeMin[i];
cs->rangeMax[i] = rangeMax[i];
}
+#ifdef USE_CMS
+ cs->transform = transform;
+ if (transform != NULL) transform->ref();
+ cs->lineTransform = lineTransform;
+ if (lineTransform != NULL) lineTransform->ref();
+#endif
return cs;
}
@@ -1015,24 +1463,163 @@ GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr) {
}
obj2.free();
obj1.free();
+
+#ifdef USE_CMS
+ arr->get(1, &obj1);
+ dict = obj1.streamGetDict();
+ Guchar *profBuf;
+ unsigned int bufSize;
+ Stream *iccStream = obj1.getStream();
+ int c;
+ unsigned int size = 0;
+
+ bufSize = 65536;
+ profBuf = (Guchar *)gmallocn(bufSize,1);
+ iccStream->reset();
+ while ((c = iccStream->getChar()) != EOF) {
+ if (bufSize <= size) {
+ bufSize += 65536;
+ profBuf = (Guchar *)greallocn(profBuf,bufSize,1);
+ }
+ profBuf[size++] = c;
+ }
+ cmsHPROFILE hp = cmsOpenProfileFromMem(profBuf,size);
+ gfree(profBuf);
+ if (hp == 0) {
+ error(-1, "read ICCBased color space profile error");
+ } else {
+ cmsHPROFILE dhp = displayProfile;
+ if (dhp == NULL) dhp = RGBProfile;
+ unsigned int cst = getCMSColorSpaceType(cmsGetColorSpace(hp));
+ unsigned int dNChannels = getCMSNChannels(cmsGetColorSpace(dhp));
+ unsigned int dcst = getCMSColorSpaceType(cmsGetColorSpace(dhp));
+ cmsHTRANSFORM transform;
+ if ((transform = cmsCreateTransform(hp,
+ COLORSPACE_SH(cst) |CHANNELS_SH(nCompsA) | BYTES_SH(0),
+ dhp,
+ COLORSPACE_SH(dcst) |
+ CHANNELS_SH(dNChannels) | BYTES_SH(0),
+ INTENT_RELATIVE_COLORIMETRIC,0)) == 0) {
+ error(-1, "Can't create transform");
+ }
+ cs->transform = new GfxColorTransform(transform);
+ if (dcst == PT_RGB) {
+ // create line transform only when the display is RGB type color space
+ if ((transform = cmsCreateTransform(hp,
+ CHANNELS_SH(nCompsA) | BYTES_SH(1),dhp,
+ TYPE_RGB_8,INTENT_RELATIVE_COLORIMETRIC,0)) == 0) {
+ error(-1, "Can't create transform");
+ }
+ cs->lineTransform = new GfxColorTransform(transform);
+ }
+ cmsCloseProfile(hp);
+ }
+ obj1.free();
+#endif
return cs;
}
void GfxICCBasedColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+#ifdef USE_CMS
+ if (transform != 0 && displayPixelType == PT_GRAY) {
+ double in[gfxColorMaxComps];
+ double out[gfxColorMaxComps];
+
+ for (int i = 0;i < nComps;i++) {
+ in[i] = colToDbl(color->c[i]);
+ }
+ transform->doTransform(in,out,1);
+ *gray = dblToCol(out[0]);
+ } else {
+ GfxRGB rgb;
+ getRGB(color,&rgb);
+ *gray = clip01((GfxColorComp)(0.3 * rgb.r +
+ 0.59 * rgb.g +
+ 0.11 * rgb.b + 0.5));
+ }
+#else
alt->getGray(color, gray);
+#endif
}
void GfxICCBasedColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+#ifdef USE_CMS
+ if (transform != 0
+ && (displayProfile == NULL || displayPixelType == PT_RGB)) {
+ double in[gfxColorMaxComps];
+ double out[gfxColorMaxComps];
+
+ for (int i = 0;i < nComps;i++) {
+ in[i] = colToDbl(color->c[i]);
+ }
+ transform->doTransform(in,out,1);
+ rgb->r = dblToCol(out[0]);
+ rgb->g = dblToCol(out[1]);
+ rgb->b = dblToCol(out[2]);
+ } else {
+ alt->getRGB(color, rgb);
+ }
+#else
alt->getRGB(color, rgb);
+#endif
}
void GfxICCBasedColorSpace::getRGBLine(Guchar *in, unsigned int *out,
int length) {
+#ifdef USE_CMS
+ if (lineTransform != 0) {
+ for (int i = 0;i < length;i++) {
+ Guchar tmp[gfxColorMaxComps];
+
+ lineTransform->doTransform(in,tmp,1);
+ in += 3;
+ out[i] = (tmp[0] << 16) | (tmp[1] << 8) | tmp[2];
+ }
+ } else {
+ alt->getRGBLine(in, out, length);
+ }
+#else
alt->getRGBLine(in, out, length);
+#endif
}
void GfxICCBasedColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+#ifdef USE_CMS
+ if (transform != NULL && displayPixelType == PT_CMYK) {
+ double in[gfxColorMaxComps];
+ double out[gfxColorMaxComps];
+
+ for (int i = 0;i < nComps;i++) {
+ in[i] = colToDbl(color->c[i]);
+ }
+ transform->doTransform(in,out,1);
+ cmyk->c = dblToCol(out[0]);
+ cmyk->m = dblToCol(out[1]);
+ cmyk->y = dblToCol(out[2]);
+ cmyk->k = dblToCol(out[3]);
+ } else {
+ GfxRGB rgb;
+ GfxColorComp c, m, y, k;
+
+ getRGB(color,&rgb);
+ c = clip01(gfxColorComp1 - rgb.r);
+ m = clip01(gfxColorComp1 - rgb.g);
+ y = clip01(gfxColorComp1 - rgb.b);
+ k = c;
+ if (m < k) {
+ k = m;
+ }
+ if (y < k) {
+ k = y;
+ }
+ cmyk->c = c - k;
+ cmyk->m = m - k;
+ cmyk->y = y - k;
+ cmyk->k = k;
+ }
+#else
alt->getCMYK(color, cmyk);
+#endif
}
void GfxICCBasedColorSpace::getDefaultColor(GfxColor *color) {
@@ -3938,6 +4525,9 @@ GfxState::GfxState(double hDPIA, double vDPIA, PDFRectangle *pageBox,
clipYMax = pageHeight;
saved = NULL;
+#ifdef USE_CMS
+ GfxColorSpace::setupColorProfiles();
+#endif
}
GfxState::~GfxState() {
diff --git a/poppler/GfxState.h b/poppler/GfxState.h
index ac20d35..0ba8c6c 100644
--- a/poppler/GfxState.h
+++ b/poppler/GfxState.h
@@ -32,6 +32,9 @@
#include "goo/gtypes.h"
#include "Object.h"
#include "Function.h"
+#ifdef USE_CMS
+#include "lcms.h"
+#endif
class Array;
class GfxFont;
@@ -151,6 +154,37 @@ enum GfxColorSpaceMode {
csPattern
};
+#if USE_CMS
+
+#define COLOR_PROFILE_DIR "/ColorProfiles/"
+#define GLOBAL_COLOR_PROFILE_DIR POPPLER_DATADIR COLOR_PROFILE_DIR
+
+// wrapper of cmsHTRANSFORM to copy
+class GfxColorTransform {
+public:
+ void doTransform(void *in, void *out, unsigned int size) {
+ cmsDoTransform(transform, in, out, size);
+ }
+ GfxColorTransform(cmsHTRANSFORM transformA) {
+ transform = transformA;
+ refCount = 1;
+ }
+ ~GfxColorTransform() {
+ cmsDeleteTransform(transform);
+ }
+ void ref() {
+ refCount++;
+ }
+ unsigned int unref() {
+ return --refCount;
+ }
+private:
+ GfxColorTransform() {}
+ cmsHTRANSFORM transform;
+ unsigned int refCount;
+};
+#endif
+
class GfxColorSpace {
public:
@@ -191,6 +225,32 @@ public:
static char *getColorSpaceModeName(int idx);
private:
+#ifdef USE_CMS
+protected:
+ static cmsHPROFILE RGBProfile;
+ static GooString *displayProfileName; // display profile file Name
+ static cmsHPROFILE displayProfile; // display profile
+ static unsigned int displayPixelType;
+ static GfxColorTransform *XYZ2DisplayTransform;
+ // convert color space signature to cmsColor type
+ static unsigned int getCMSColorSpaceType(icColorSpaceSignature cs);
+ static unsigned int getCMSNChannels(icColorSpaceSignature cs);
+ static cmsHPROFILE loadColorProfile(const char *fileName);
+public:
+ static int setupColorProfiles();
+ static void setDisplayProfile(cmsHPROFILE displayProfileA) {
+ displayProfile = displayProfileA;
+ }
+ static void setDisplayProfileName(GooString *name) {
+ displayProfileName = name->copy();
+ }
+ static cmsHPROFILE getRGBProfile() {
+ return RGBProfile;
+ }
+ static cmsHPROFILE getDisplayProfile() {
+ return displayProfile;
+ }
+#endif
};
//------------------------------------------------------------------------
@@ -235,8 +295,6 @@ public:
virtual void getGray(GfxColor *color, GfxGray *gray);
virtual void getRGB(GfxColor *color, GfxRGB *rgb);
virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
- virtual void getGrayLine(Guchar *in, Guchar *out, int length);
- virtual void getRGBLine(Guchar *in, unsigned int *out, int length);
virtual int getNComps() { return 1; }
virtual void getDefaultColor(GfxColor *color);
@@ -255,6 +313,8 @@ 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(GfxColor *color, double *pX, double *pY, double *pZ);
};
//------------------------------------------------------------------------
@@ -299,8 +359,6 @@ public:
virtual void getGray(GfxColor *color, GfxGray *gray);
virtual void getRGB(GfxColor *color, GfxRGB *rgb);
virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
- virtual void getGrayLine(Guchar *in, Guchar *out, int length);
- virtual void getRGBLine(Guchar *in, unsigned int *out, int length);
virtual int getNComps() { return 3; }
virtual void getDefaultColor(GfxColor *color);
@@ -323,6 +381,8 @@ 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(GfxColor *color, double *pX, double *pY, double *pZ);
};
//------------------------------------------------------------------------
@@ -390,6 +450,7 @@ private:
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(GfxColor *color, double *pX, double *pY, double *pZ);
};
//------------------------------------------------------------------------
@@ -429,6 +490,10 @@ private:
double rangeMin[4]; // min values for each component
double rangeMax[4]; // max values for each component
Ref iccProfileStream; // the ICC profile
+#ifdef USE_CMS
+ GfxColorTransform *transform;
+ GfxColorTransform *lineTransform; // color transform for line
+#endif
};
//------------------------------------------------------------------------
diff --git a/poppler/Makefile.am b/poppler/Makefile.am
index db9fd74..9d8644f 100644
--- a/poppler/Makefile.am
+++ b/poppler/Makefile.am
@@ -105,6 +105,10 @@ abiword_libs = \
endif
+if USE_CMS
+cms_libs = -llcms
+endif
+
INCLUDES = \
-I$(top_srcdir) \
-I$(top_srcdir)/goo \
@@ -126,6 +130,7 @@ CXXFLAGS+=$(PTHREAD_CFLAGS)
libpoppler_la_LIBADD = \
$(top_builddir)/goo/libgoo.la \
$(top_builddir)/fofi/libfofi.la \
+ $(cms_libs) \
$(splash_libs) \
$(libjpeg_libs) \
$(zlib_libs) \
More information about the poppler
mailing list