Commit 1f698b44 authored by Adrian Johnson's avatar Adrian Johnson Committed by Albert Astals Cid

Use ICC profiles in PS output

When printing PDFs that use ICC based colors, Poppler always uses the alternate color space
in the PostScript output (usually DeviceRGB or DeviceCMYK). The attached patch will use the
ICC profile color space in the PS output. Most of the patch is modifying GfxColorTransform
and callers to store the source profile as well as the transform. The GfxICCBasedColorSpace
class has a new method, getPostScriptCSA(), which uses the LCMS function cmsGetPostScriptCSA()
to generate the CIEBased color space dictionary equivalent to the ICC profile.

Based on patch from issue #125.
parent d5efac76
......@@ -196,16 +196,22 @@ void GfxColorTransform::doTransform(void *in, void *out, unsigned int size) {
}
// transformA should be a cmsHTRANSFORM
GfxColorTransform::GfxColorTransform(void *transformA, int cmsIntentA, unsigned int inputPixelTypeA, unsigned int transformPixelTypeA) {
GfxColorTransform::GfxColorTransform(void *sourceProfileA, void *transformA, int cmsIntentA, unsigned int inputPixelTypeA, unsigned int transformPixelTypeA) {
sourceProfile = sourceProfileA;
transform = transformA;
refCount = 1;
cmsIntent = cmsIntentA;
inputPixelType = inputPixelTypeA;
transformPixelType = transformPixelTypeA;
psCSA = NULL;
}
GfxColorTransform::~GfxColorTransform() {
if (sourceProfile)
cmsCloseProfile(sourceProfile);
cmsDeleteTransform(transform);
if (psCSA)
gfree(psCSA);
}
void GfxColorTransform::ref() {
......@@ -216,6 +222,31 @@ unsigned int GfxColorTransform::unref() {
return --refCount;
}
char *GfxColorTransform::getPostScriptCSA()
{
int size;
if (psCSA)
return psCSA;
if (sourceProfile == NULL) {
error(errSyntaxWarning, -1, "profile is NULL");
return NULL;
}
size = cmsGetPostScriptCSA(cmsGetProfileContextID(sourceProfile), sourceProfile, cmsIntent, 0, NULL, 0);
if (size == 0) {
error(errSyntaxWarning, -1, "PostScript CSA is NULL");
return NULL;
}
psCSA = (char*)gmalloc(size+1);
cmsGetPostScriptCSA(cmsGetProfileContextID(sourceProfile), sourceProfile, cmsIntent, 0, psCSA, size);
psCSA[size] = 0;
return psCSA;
}
static cmsHPROFILE RGBProfile = nullptr;
static GooString *displayProfileName = nullptr; // display profile file Name
static cmsHPROFILE displayProfile = nullptr; // display profile
......@@ -248,9 +279,8 @@ void GfxColorSpace::setDisplayProfile(void *displayProfileA) {
INTENT_RELATIVE_COLORIMETRIC,LCMS_FLAGS)) == nullptr) {
error(errSyntaxWarning, -1, "Can't create Lab transform");
} else {
XYZ2DisplayTransform = new GfxColorTransform(transform, INTENT_RELATIVE_COLORIMETRIC, PT_XYZ, displayPixelType);
XYZ2DisplayTransform = new GfxColorTransform(displayProfile, transform, INTENT_RELATIVE_COLORIMETRIC, PT_XYZ, displayPixelType);
}
cmsCloseProfile(XYZProfile);
}
}
......@@ -525,10 +555,10 @@ int GfxColorSpace::setupColorProfiles()
CHANNELS_SH(nChannels) | BYTES_SH(1),
INTENT_RELATIVE_COLORIMETRIC,LCMS_FLAGS)) == nullptr) {
error(errSyntaxWarning, -1, "Can't create Lab transform");
cmsCloseProfile(XYZProfile);
} else {
XYZ2DisplayTransform = new GfxColorTransform(transform, INTENT_RELATIVE_COLORIMETRIC, PT_XYZ, displayPixelType);
XYZ2DisplayTransform = new GfxColorTransform(XYZProfile, transform, INTENT_RELATIVE_COLORIMETRIC, PT_XYZ, displayPixelType);
}
cmsCloseProfile(XYZProfile);
}
return 0;
}
......@@ -1789,16 +1819,7 @@ GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, OutputDev *out, GfxState
int transformIntent = cs->getIntent();
int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
if (state != nullptr) {
const char *intent = state->getRenderingIntent();
if (intent != nullptr) {
if (strcmp(intent, "AbsoluteColorimetric") == 0) {
cmsIntent = INTENT_ABSOLUTE_COLORIMETRIC;
} else if (strcmp(intent, "Saturation") == 0) {
cmsIntent = INTENT_SATURATION;
} else if (strcmp(intent, "Perceptual") == 0) {
cmsIntent = INTENT_PERCEPTUAL;
}
}
cmsIntent = state->getCmsRenderingIntent();
}
if (transformIntent == cmsIntent) {
return cs;
......@@ -1883,16 +1904,7 @@ GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, OutputDev *out, GfxState
int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
if (state != nullptr) {
const char *intent = state->getRenderingIntent();
if (intent != nullptr) {
if (strcmp(intent, "AbsoluteColorimetric") == 0) {
cmsIntent = INTENT_ABSOLUTE_COLORIMETRIC;
} else if (strcmp(intent, "Saturation") == 0) {
cmsIntent = INTENT_SATURATION;
} else if (strcmp(intent, "Perceptual") == 0) {
cmsIntent = INTENT_PERCEPTUAL;
}
}
cmsIntent = state->getCmsRenderingIntent();
}
if ((transform = cmsCreateTransform(hp,
COLORSPACE_SH(cst) |CHANNELS_SH(nCompsA) | BYTES_SH(1),
......@@ -1903,7 +1915,7 @@ GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, OutputDev *out, GfxState
error(errSyntaxWarning, -1, "Can't create transform");
cs->transform = nullptr;
} else {
cs->transform = new GfxColorTransform(transform, cmsIntent, cst, dcst);
cs->transform = new GfxColorTransform(hp, transform, cmsIntent, cst, dcst);
}
if (dcst == PT_RGB || dcst == PT_CMYK) {
// create line transform only when the display is RGB type color space
......@@ -1913,10 +1925,12 @@ GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, OutputDev *out, GfxState
error(errSyntaxWarning, -1, "Can't create transform");
cs->lineTransform = nullptr;
} else {
cs->lineTransform = new GfxColorTransform(transform, cmsIntent, cst, dcst);
cs->lineTransform = new GfxColorTransform(NULL, transform, cmsIntent, cst, dcst);
}
}
cmsCloseProfile(hp);
if (cs->transform == nullptr) {
cmsCloseProfile(hp);
}
}
// put this colorSpace into cache
if (out && iccProfileStreamA != Ref::INVALID()) {
......@@ -2368,6 +2382,16 @@ void GfxICCBasedColorSpace::getDefaultRanges(double *decodeLow,
#endif
}
#ifdef USE_CMS
char *GfxICCBasedColorSpace::getPostScriptCSA()
{
if (transform)
return transform->getPostScriptCSA();
else
return NULL;
}
#endif
//------------------------------------------------------------------------
// GfxIndexedColorSpace
//------------------------------------------------------------------------
......@@ -6597,37 +6621,46 @@ void GfxState::setDisplayProfile(cmsHPROFILE localDisplayProfileA) {
CHANNELS_SH(nChannels) | BYTES_SH(1),
INTENT_RELATIVE_COLORIMETRIC,LCMS_FLAGS)) == nullptr) {
error(errSyntaxWarning, -1, "Can't create Lab transform");
cmsCloseProfile(XYZProfile);
} else {
XYZ2DisplayTransformRelCol = new GfxColorTransform(transform, INTENT_RELATIVE_COLORIMETRIC, PT_XYZ, localDisplayPixelType);
XYZ2DisplayTransformRelCol = new GfxColorTransform(XYZProfile, transform, INTENT_RELATIVE_COLORIMETRIC, PT_XYZ, localDisplayPixelType);
}
XYZProfile = cmsCreateXYZProfile();
if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
localDisplayProfile,
COLORSPACE_SH(localDisplayPixelType) |
CHANNELS_SH(nChannels) | BYTES_SH(1),
INTENT_ABSOLUTE_COLORIMETRIC,LCMS_FLAGS)) == nullptr) {
error(errSyntaxWarning, -1, "Can't create Lab transform");
cmsCloseProfile(XYZProfile);
} else {
XYZ2DisplayTransformAbsCol = new GfxColorTransform(transform, INTENT_ABSOLUTE_COLORIMETRIC, PT_XYZ, localDisplayPixelType);
XYZ2DisplayTransformAbsCol = new GfxColorTransform(XYZProfile, transform, INTENT_ABSOLUTE_COLORIMETRIC, PT_XYZ, localDisplayPixelType);
}
XYZProfile = cmsCreateXYZProfile();
if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
localDisplayProfile,
COLORSPACE_SH(localDisplayPixelType) |
CHANNELS_SH(nChannels) | BYTES_SH(1),
INTENT_SATURATION,LCMS_FLAGS)) == nullptr) {
error(errSyntaxWarning, -1, "Can't create Lab transform");
cmsCloseProfile(XYZProfile);
} else {
XYZ2DisplayTransformSat = new GfxColorTransform(transform, INTENT_SATURATION, PT_XYZ, localDisplayPixelType);
XYZ2DisplayTransformSat = new GfxColorTransform(XYZProfile, transform, INTENT_SATURATION, PT_XYZ, localDisplayPixelType);
}
XYZProfile = cmsCreateXYZProfile();
if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
localDisplayProfile,
COLORSPACE_SH(localDisplayPixelType) |
CHANNELS_SH(nChannels) | BYTES_SH(1),
INTENT_PERCEPTUAL,LCMS_FLAGS)) == nullptr) {
error(errSyntaxWarning, -1, "Can't create Lab transform");
cmsCloseProfile(XYZProfile);
} else {
XYZ2DisplayTransformPerc = new GfxColorTransform(transform, INTENT_PERCEPTUAL, PT_XYZ, localDisplayPixelType);
XYZ2DisplayTransformPerc = new GfxColorTransform(XYZProfile, transform, INTENT_PERCEPTUAL, PT_XYZ, localDisplayPixelType);
}
cmsCloseProfile(XYZProfile);
}
}
......@@ -6648,6 +6681,21 @@ GfxColorTransform *GfxState::getXYZ2DisplayTransform() {
return transform;
}
int GfxState::getCmsRenderingIntent() {
const char *intent = getRenderingIntent();
int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
if (intent) {
if (strcmp(intent, "AbsoluteColorimetric") == 0) {
cmsIntent = INTENT_ABSOLUTE_COLORIMETRIC;
} else if (strcmp(intent, "Saturation") == 0) {
cmsIntent = INTENT_SATURATION;
} else if (strcmp(intent, "Perceptual") == 0) {
cmsIntent = INTENT_PERCEPTUAL;
}
}
return cmsIntent;
}
#endif
void GfxState::setPath(GfxPath *pathA) {
......
......@@ -193,7 +193,8 @@ class GfxColorTransform {
public:
void doTransform(void *in, void *out, unsigned int size);
// transformA should be a cmsHTRANSFORM
GfxColorTransform(void *transformA, int cmsIntent, unsigned int inputPixelType, unsigned int transformPixelType);
GfxColorTransform(void *sourceProfileA, void *transformA, int cmsIntent,
unsigned int inputPixelType, unsigned int transformPixelType);
~GfxColorTransform();
GfxColorTransform(const GfxColorTransform &) = delete;
GfxColorTransform& operator=(const GfxColorTransform &) = delete;
......@@ -202,13 +203,17 @@ public:
int getTransformPixelType() const { return transformPixelType; }
void ref();
unsigned int unref();
void *getSourceProfile() { return sourceProfile; }
char *getPostScriptCSA();
private:
GfxColorTransform() {}
void *sourceProfile;
void *transform;
unsigned int refCount;
int cmsIntent;
unsigned int inputPixelType;
unsigned int transformPixelType;
char *psCSA;
};
class GfxColorSpace {
......@@ -569,6 +574,10 @@ public:
// ICCBased-specific access.
GfxColorSpace *getAlt() { return alt; }
Ref getRef() { return iccProfileStream; }
#ifdef USE_CMS
char *getPostScriptCSA();
#endif
private:
......@@ -1594,6 +1603,7 @@ public:
void setDisplayProfile(void *localDisplayProfileA);
void *getDisplayProfile() { return localDisplayProfile; }
GfxColorTransform *getXYZ2DisplayTransform();
int getCmsRenderingIntent();
#endif
// Add to path.
......
......@@ -3945,6 +3945,10 @@ void PSOutputDev::endPage() {
(*overlayCbk)(this, overlayCbkData);
}
for (const auto& item : iccEmitted) {
writePSFmt("userdict /{0:s} undef\n", item.c_str());
}
iccEmitted.clear();
if (mode == psModeForm) {
writePS("pdfEndPage\n");
......@@ -3955,8 +3959,8 @@ void PSOutputDev::endPage() {
if (!manualCtrl) {
writePS("showpage\n");
}
writePS("%%PageTrailer\n");
writePageTrailer();
writePS("%%PageTrailer\n");
writePageTrailer();
}
}
......@@ -4022,7 +4026,7 @@ void PSOutputDev::updateFillColorSpace(GfxState *state) {
case psLevel2:
case psLevel3:
if (state->getFillColorSpace()->getMode() != csPattern) {
dumpColorSpaceL2(state->getFillColorSpace(), true, false, false);
dumpColorSpaceL2(state, state->getFillColorSpace(), true, false, false);
writePS(" cs\n");
}
break;
......@@ -4043,7 +4047,7 @@ void PSOutputDev::updateStrokeColorSpace(GfxState *state) {
case psLevel2:
case psLevel3:
if (state->getStrokeColorSpace()->getMode() != csPattern) {
dumpColorSpaceL2(state->getStrokeColorSpace(), true, false, false);
dumpColorSpaceL2(state, state->getStrokeColorSpace(), true, false, false);
writePS(" CS\n");
}
break;
......@@ -4940,7 +4944,7 @@ bool PSOutputDev::patchMeshShadedFill(GfxState *state,
writePS("<<\n");
writePS(" /ShadingType 7\n");
writePS(" /ColorSpace ");
dumpColorSpaceL2(shading->getColorSpace(), false, false, false);
dumpColorSpaceL2(state, shading->getColorSpace(), false, false, false);
writePS("\n");
writePS(" /DataSource [\n");
......@@ -5233,12 +5237,12 @@ void PSOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
break;
case psLevel2:
case psLevel2Sep:
doImageL2(ref, nullptr, invert, inlineImg, str, width, height, len,
doImageL2(state, ref, nullptr, invert, inlineImg, str, width, height, len,
nullptr, nullptr, 0, 0, false);
break;
case psLevel3:
case psLevel3Sep:
doImageL3(ref, nullptr, invert, inlineImg, str, width, height, len,
doImageL3(state, ref, nullptr, invert, inlineImg, str, width, height, len,
nullptr, nullptr, 0, 0, false);
break;
}
......@@ -5277,12 +5281,12 @@ void PSOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
break;
case psLevel2:
case psLevel2Sep:
doImageL2(ref, colorMap, false, inlineImg, str,
doImageL2(state, ref, colorMap, false, inlineImg, str,
width, height, len, maskColors, nullptr, 0, 0, false);
break;
case psLevel3:
case psLevel3Sep:
doImageL3(ref, colorMap, false, inlineImg, str,
doImageL3(state, ref, colorMap, false, inlineImg, str,
width, height, len, maskColors, nullptr, 0, 0, false);
break;
}
......@@ -5312,12 +5316,12 @@ void PSOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
break;
case psLevel2:
case psLevel2Sep:
doImageL2(ref, colorMap, false, false, str, width, height, len,
doImageL2(state, ref, colorMap, false, false, str, width, height, len,
nullptr, maskStr, maskWidth, maskHeight, maskInvert);
break;
case psLevel3:
case psLevel3Sep:
doImageL3(ref, colorMap, false, false, str, width, height, len,
doImageL3(state, ref, colorMap, false, false, str, width, height, len,
nullptr, maskStr, maskWidth, maskHeight, maskInvert);
break;
}
......@@ -5769,7 +5773,7 @@ void PSOutputDev::maskToClippingPath(Stream *maskStr, int maskWidth, int maskHei
maskStr->close();
}
void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap,
void PSOutputDev::doImageL2(GfxState* state, Object *ref, GfxImageColorMap *colorMap,
bool invert, bool inlineImg,
Stream *str, int width, int height, int len,
const int *maskColors, Stream *maskStr,
......@@ -5961,7 +5965,7 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap,
bool isCustomColor =
(level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) &&
colorMap->getColorSpace()->getMode() == csDeviceN;
dumpColorSpaceL2(colorMap->getColorSpace(), false, !isCustomColor, false);
dumpColorSpaceL2(state, colorMap->getColorSpace(), false, !isCustomColor, false);
writePS(" setcolorspace\n");
}
......@@ -6249,7 +6253,7 @@ void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap,
}
//~ this doesn't currently support OPI
void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap,
void PSOutputDev::doImageL3(GfxState *state, Object *ref, GfxImageColorMap *colorMap,
bool invert, bool inlineImg,
Stream *str, int width, int height, int len,
const int *maskColors, Stream *maskStr,
......@@ -6364,7 +6368,7 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap,
bool isCustomColor =
(level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) &&
colorMap->getColorSpace()->getMode() == csDeviceN;
dumpColorSpaceL2(colorMap->getColorSpace(), false, !isCustomColor, false);
dumpColorSpaceL2(state, colorMap->getColorSpace(), false, !isCustomColor, false);
writePS(" setcolorspace\n");
}
......@@ -6663,7 +6667,7 @@ void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap,
}
}
void PSOutputDev::dumpColorSpaceL2(GfxColorSpace *colorSpace,
void PSOutputDev::dumpColorSpaceL2(GfxState *state, GfxColorSpace *colorSpace,
bool genXform, bool updateColors,
bool map01) {
GfxCalGrayColorSpace *calGrayCS;
......@@ -6804,17 +6808,48 @@ void PSOutputDev::dumpColorSpaceL2(GfxColorSpace *colorSpace,
break;
case csICCBased:
#if USE_CMS
{
GfxICCBasedColorSpace *iccBasedCS;
iccBasedCS = (GfxICCBasedColorSpace *)colorSpace;
Ref ref = iccBasedCS->getRef();
int intent = state->getCmsRenderingIntent();
GooString *name = GooString::format("ICCBased-{0:d}-{1:d}-{2:d}", ref.num, ref.gen, intent);
const auto& it = iccEmitted.find(name->toStr());
if (it != iccEmitted.end()) {
writePSFmt("{0:t}", name);
if (genXform) {
writePS(" {}");
}
} else {
char *csa = iccBasedCS->getPostScriptCSA();
if (csa) {
writePSFmt("userdict /{0:t} {1:s} put\n", name, csa);
iccEmitted.emplace(name->toStr());
writePSFmt("{0:t}", name);
if (genXform) {
writePS(" {}");
}
} else {
dumpColorSpaceL2(state, ((GfxICCBasedColorSpace *)colorSpace)->getAlt(),
genXform, updateColors, false);
}
}
delete name;
}
#else
// there is no transform function to the alternate color space, so
// we can use it directly
dumpColorSpaceL2(((GfxICCBasedColorSpace *)colorSpace)->getAlt(),
dumpColorSpaceL2(state, ((GfxICCBasedColorSpace *)colorSpace)->getAlt(),
genXform, updateColors, false);
#endif
break;
case csIndexed:
indexedCS = (GfxIndexedColorSpace *)colorSpace;
baseCS = indexedCS->getBase();
writePS("[/Indexed ");
dumpColorSpaceL2(baseCS, false, false, true);
dumpColorSpaceL2(state, baseCS, false, false, true);
n = indexedCS->getIndexHigh();
numComps = baseCS->getNComps();
lookup = indexedCS->getLookup();
......@@ -6889,7 +6924,7 @@ void PSOutputDev::dumpColorSpaceL2(GfxColorSpace *colorSpace,
writePS("[/Separation ");
writePSString(separationCS->getName()->toStr());
writePS(" ");
dumpColorSpaceL2(separationCS->getAlt(), false, false, false);
dumpColorSpaceL2(state, separationCS->getAlt(), false, false, false);
writePS("\n");
cvtFunction(separationCS->getFunc());
writePS("]");
......@@ -6911,7 +6946,7 @@ void PSOutputDev::dumpColorSpaceL2(GfxColorSpace *colorSpace,
writePS(" ");
}
writePS("]\n");
dumpColorSpaceL2(deviceNCS->getAlt(), false, updateColors, false);
dumpColorSpaceL2(state, deviceNCS->getAlt(), false, updateColors, false);
writePS("\n");
cvtFunction(deviceNCS->getTintTransformFunc(), map01 && deviceNCS->getAlt()->getMode() == csLab);
writePS("]\n");
......@@ -6920,7 +6955,7 @@ void PSOutputDev::dumpColorSpaceL2(GfxColorSpace *colorSpace,
}
} else {
// DeviceN color spaces are a Level 3 PostScript feature.
dumpColorSpaceL2(deviceNCS->getAlt(), false, updateColors, map01);
dumpColorSpaceL2(state, deviceNCS->getAlt(), false, updateColors, map01);
if (genXform) {
writePS(" ");
cvtFunction(deviceNCS->getTintTransformFunc());
......
......@@ -395,17 +395,17 @@ private:
Stream *str, int width, int height, int len,
const int *maskColors, Stream *maskStr,
int maskWidth, int maskHeight, bool maskInvert);
void doImageL2(Object *ref, GfxImageColorMap *colorMap,
void doImageL2(GfxState *state, Object *ref, GfxImageColorMap *colorMap,
bool invert, bool inlineImg,
Stream *str, int width, int height, int len,
const int *maskColors, Stream *maskStr,
int maskWidth, int maskHeight, bool maskInvert);
void doImageL3(Object *ref, GfxImageColorMap *colorMap,
void doImageL3(GfxState *state, Object *ref, GfxImageColorMap *colorMap,
bool invert, bool inlineImg,
Stream *str, int width, int height, int len,
const int *maskColors, Stream *maskStr,
int maskWidth, int maskHeight, bool maskInvert);
void dumpColorSpaceL2(GfxColorSpace *colorSpace,
void dumpColorSpaceL2(GfxState *state, GfxColorSpace *colorSpace,
bool genXform, bool updateColors,
bool map01);
bool tilingPatternFillL1(GfxState *state, Catalog *cat, Object *str,
......@@ -557,6 +557,8 @@ private:
bool enableLZW; // enable LZW compression
bool enableFlate; // enable Flate compression
std::unordered_set<std::string> iccEmitted; // contains ICCBased CSAs that have been emitted
#ifdef OPI_SUPPORT
int opi13Nest; // nesting level of OPI 1.3 objects
int opi20Nest; // nesting level of OPI 2.0 objects
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment