blob: cfeb83de017cef8b2abf4de08095a5f1c06a04fd [file] [log] [blame] [edit]
//========================================================================
//
// GfxState.cc
//
// Copyright 1996-2003 Glyph & Cog, LLC
//
//========================================================================
//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2005 Kristian Høgsberg <krh@redhat.com>
// Copyright (C) 2006, 2007 Jeff Muizelaar <jeff@infidigm.net>
// Copyright (C) 2006, 2010 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2006-2022, 2024 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2009, 2012 Koji Otani <sho@bbr.jp>
// Copyright (C) 2009, 2011-2016, 2020, 2023 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2009, 2019 Christian Persch <chpe@gnome.org>
// Copyright (C) 2010 Paweł Wiejacha <pawel.wiejacha@gmail.com>
// Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com>
// Copyright (C) 2011 Andrea Canciani <ranma42@gmail.com>
// Copyright (C) 2012, 2020 William Bader <williambader@hotmail.com>
// Copyright (C) 2013 Lu Wang <coolwanglu@gmail.com>
// Copyright (C) 2013 Hib Eris <hib@hiberis.nl>
// Copyright (C) 2013 Fabio D'Urso <fabiodurso@hotmail.it>
// Copyright (C) 2015, 2020 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2016 Marek Kasik <mkasik@redhat.com>
// Copyright (C) 2017, 2019, 2022 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
// Copyright (C) 2018 Volker Krause <vkrause@kde.org>
// Copyright (C) 2018, 2019 Adam Reichold <adam.reichold@t-online.de>
// Copyright (C) 2019 LE GARREC Vincent <legarrec.vincent@gmail.com>
// Copyright (C) 2020, 2021 Philipp Knechtges <philipp-dev@knechtges.com>
// Copyright (C) 2020 Lluís Batlle i Rossell <viric@viric.name>
// Copyright (C) 2024 Athul Raj Kollareth <krathul3152@gmail.com>
// Copyright (C) 2024 Nelson Benítez León <nbenitezl@gmail.com>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================
#include <config.h>
#include <algorithm>
#include <memory>
#include <cstddef>
#include <cmath>
#include <cstring>
#include "goo/gfile.h"
#include "goo/gmem.h"
#include "Error.h"
#include "Object.h"
#include "Array.h"
#include "Page.h"
#include "Gfx.h"
#include "GfxState.h"
#include "GfxState_helpers.h"
#include "GfxFont.h"
#include "GlobalParams.h"
#include "PopplerCache.h"
#include "OutputDev.h"
#include "splash/SplashTypes.h"
//------------------------------------------------------------------------
// Max depth of nested color spaces. This is used to catch infinite
// loops in the color space object structure.
#define colorSpaceRecursionLimit 8
//------------------------------------------------------------------------
bool Matrix::invertTo(Matrix *other) const
{
const double det_denominator = determinant();
if (unlikely(det_denominator == 0)) {
*other = { 1, 0, 0, 1, 0, 0 };
return false;
}
const double det = 1 / det_denominator;
other->m[0] = m[3] * det;
other->m[1] = -m[1] * det;
other->m[2] = -m[2] * det;
other->m[3] = m[0] * det;
other->m[4] = (m[2] * m[5] - m[3] * m[4]) * det;
other->m[5] = (m[1] * m[4] - m[0] * m[5]) * det;
return true;
}
void Matrix::translate(double tx, double ty)
{
double x0 = tx * m[0] + ty * m[2] + m[4];
double y0 = tx * m[1] + ty * m[3] + m[5];
m[4] = x0;
m[5] = y0;
}
void Matrix::scale(double sx, double sy)
{
m[0] *= sx;
m[1] *= sx;
m[2] *= sy;
m[3] *= sy;
}
void Matrix::transform(double x, double y, double *tx, double *ty) const
{
double temp_x, temp_y;
temp_x = m[0] * x + m[2] * y + m[4];
temp_y = m[1] * x + m[3] * y + m[5];
*tx = temp_x;
*ty = temp_y;
}
// Matrix norm, taken from _cairo_matrix_transformed_circle_major_axis
double Matrix::norm() const
{
double f, g, h, i, j;
i = m[0] * m[0] + m[1] * m[1];
j = m[2] * m[2] + m[3] * m[3];
f = 0.5 * (i + j);
g = 0.5 * (i - j);
h = m[0] * m[2] + m[1] * m[3];
return sqrt(f + hypot(g, h));
}
//------------------------------------------------------------------------
struct GfxBlendModeInfo
{
const char *name;
GfxBlendMode mode;
};
static const GfxBlendModeInfo gfxBlendModeNames[] = { { "Normal", gfxBlendNormal }, { "Compatible", gfxBlendNormal },
{ "Multiply", gfxBlendMultiply }, { "Screen", gfxBlendScreen },
{ "Overlay", gfxBlendOverlay }, { "Darken", gfxBlendDarken },
{ "Lighten", gfxBlendLighten }, { "ColorDodge", gfxBlendColorDodge },
{ "ColorBurn", gfxBlendColorBurn }, { "HardLight", gfxBlendHardLight },
{ "SoftLight", gfxBlendSoftLight }, { "Difference", gfxBlendDifference },
{ "Exclusion", gfxBlendExclusion }, { "Hue", gfxBlendHue },
{ "Saturation", gfxBlendSaturation }, { "Color", gfxBlendColor },
{ "Luminosity", gfxBlendLuminosity } };
#define nGfxBlendModeNames ((int)((sizeof(gfxBlendModeNames) / sizeof(GfxBlendModeInfo))))
//------------------------------------------------------------------------
//
// NB: This must match the GfxColorSpaceMode enum defined in
// GfxState.h
static const char *gfxColorSpaceModeNames[] = { "DeviceGray", "CalGray", "DeviceRGB", "CalRGB", "DeviceCMYK", "Lab", "ICCBased", "Indexed", "Separation", "DeviceN", "Pattern", "DeviceRGBA" };
#define nGfxColorSpaceModes ((sizeof(gfxColorSpaceModeNames) / sizeof(char *)))
#ifdef USE_CMS
static const std::map<unsigned int, unsigned int>::size_type CMSCACHE_LIMIT = 2048;
# include <lcms2.h>
# define LCMS_FLAGS cmsFLAGS_NOOPTIMIZE | cmsFLAGS_BLACKPOINTCOMPENSATION
static void lcmsprofiledeleter(void *profile)
{
cmsCloseProfile(profile);
}
GfxLCMSProfilePtr make_GfxLCMSProfilePtr(void *profile)
{
if (profile == nullptr) {
return GfxLCMSProfilePtr();
}
return GfxLCMSProfilePtr(profile, lcmsprofiledeleter);
}
void GfxColorTransform::doTransform(void *in, void *out, unsigned int size)
{
cmsDoTransform(transform, in, out, size);
}
// transformA should be a cmsHTRANSFORM
GfxColorTransform::GfxColorTransform(void *transformA, int cmsIntentA, unsigned int inputPixelTypeA, unsigned int transformPixelTypeA)
{
transform = transformA;
cmsIntent = cmsIntentA;
inputPixelType = inputPixelTypeA;
transformPixelType = transformPixelTypeA;
}
GfxColorTransform::~GfxColorTransform()
{
cmsDeleteTransform(transform);
}
// convert color space signature to cmsColor type
static unsigned int getCMSColorSpaceType(cmsColorSpaceSignature cs);
static unsigned int getCMSNChannels(cmsColorSpaceSignature cs);
#endif
//------------------------------------------------------------------------
// GfxColorSpace
//------------------------------------------------------------------------
GfxColorSpace::GfxColorSpace()
{
overprintMask = 0x0f;
mapping = nullptr;
}
GfxColorSpace::~GfxColorSpace() { }
std::unique_ptr<GfxColorSpace> GfxColorSpace::parse(GfxResources *res, Object *csObj, OutputDev *out, GfxState *state, int recursion)
{
Object obj1;
if (recursion > colorSpaceRecursionLimit) {
error(errSyntaxError, -1, "Loop detected in color space objects");
return {};
}
if (csObj->isName()) {
if (csObj->isName("DeviceGray") || csObj->isName("G")) {
if (res != nullptr) {
Object objCS = res->lookupColorSpace("DefaultGray");
if (objCS.isNull()) {
return state->copyDefaultGrayColorSpace();
} else {
return GfxColorSpace::parse(nullptr, &objCS, out, state);
}
} else {
return state->copyDefaultGrayColorSpace();
}
} else if (csObj->isName("DeviceRGB") || csObj->isName("RGB")) {
if (res != nullptr) {
Object objCS = res->lookupColorSpace("DefaultRGB");
if (objCS.isNull()) {
return state->copyDefaultRGBColorSpace();
} else {
return GfxColorSpace::parse(nullptr, &objCS, out, state);
}
} else {
return state->copyDefaultRGBColorSpace();
}
} else if (csObj->isName("DeviceCMYK") || csObj->isName("CMYK")) {
if (res != nullptr) {
Object objCS = res->lookupColorSpace("DefaultCMYK");
if (objCS.isNull()) {
return state->copyDefaultCMYKColorSpace();
} else {
return GfxColorSpace::parse(nullptr, &objCS, out, state);
}
} else {
return state->copyDefaultCMYKColorSpace();
}
} else if (csObj->isName("Pattern")) {
return std::make_unique<GfxPatternColorSpace>(nullptr);
} else {
error(errSyntaxWarning, -1, "Bad color space '{0:s}'", csObj->getName());
}
} else if (csObj->isArray() && csObj->arrayGetLength() > 0) {
obj1 = csObj->arrayGet(0);
if (obj1.isName("DeviceGray") || obj1.isName("G")) {
if (res != nullptr) {
Object objCS = res->lookupColorSpace("DefaultGray");
if (objCS.isNull()) {
return state->copyDefaultGrayColorSpace();
} else {
return GfxColorSpace::parse(nullptr, &objCS, out, state);
}
} else {
return state->copyDefaultGrayColorSpace();
}
} else if (obj1.isName("DeviceRGB") || obj1.isName("RGB")) {
if (res != nullptr) {
Object objCS = res->lookupColorSpace("DefaultRGB");
if (objCS.isNull()) {
return state->copyDefaultRGBColorSpace();
} else {
return GfxColorSpace::parse(nullptr, &objCS, out, state);
}
} else {
return state->copyDefaultRGBColorSpace();
}
} else if (obj1.isName("DeviceCMYK") || obj1.isName("CMYK")) {
if (res != nullptr) {
Object objCS = res->lookupColorSpace("DefaultCMYK");
if (objCS.isNull()) {
return state->copyDefaultCMYKColorSpace();
} else {
return GfxColorSpace::parse(nullptr, &objCS, out, state);
}
} else {
return state->copyDefaultCMYKColorSpace();
}
} else if (obj1.isName("CalGray")) {
return GfxCalGrayColorSpace::parse(csObj->getArray(), state);
} else if (obj1.isName("CalRGB")) {
return GfxCalRGBColorSpace::parse(csObj->getArray(), state);
} else if (obj1.isName("Lab")) {
return GfxLabColorSpace::parse(csObj->getArray(), state);
} else if (obj1.isName("ICCBased")) {
return GfxICCBasedColorSpace::parse(csObj->getArray(), out, state, recursion);
} else if (obj1.isName("Indexed") || obj1.isName("I")) {
return GfxIndexedColorSpace::parse(res, csObj->getArray(), out, state, recursion);
} else if (obj1.isName("Separation")) {
return GfxSeparationColorSpace::parse(res, csObj->getArray(), out, state, recursion);
} else if (obj1.isName("DeviceN")) {
return GfxDeviceNColorSpace::parse(res, csObj->getArray(), out, state, recursion);
} else if (obj1.isName("Pattern")) {
return GfxPatternColorSpace::parse(res, csObj->getArray(), out, state, recursion);
} else {
error(errSyntaxWarning, -1, "Bad color space");
}
} else if (csObj->isDict()) {
obj1 = csObj->dictLookup("ColorSpace");
if (obj1.isName("DeviceGray")) {
if (res != nullptr) {
Object objCS = res->lookupColorSpace("DefaultGray");
if (objCS.isNull()) {
return state->copyDefaultGrayColorSpace();
} else {
return GfxColorSpace::parse(nullptr, &objCS, out, state);
}
} else {
return state->copyDefaultGrayColorSpace();
}
} else if (obj1.isName("DeviceRGB")) {
if (res != nullptr) {
Object objCS = res->lookupColorSpace("DefaultRGB");
if (objCS.isNull()) {
return state->copyDefaultRGBColorSpace();
} else {
return GfxColorSpace::parse(nullptr, &objCS, out, state);
}
} else {
return state->copyDefaultRGBColorSpace();
}
} else if (obj1.isName("DeviceCMYK")) {
if (res != nullptr) {
Object objCS = res->lookupColorSpace("DefaultCMYK");
if (objCS.isNull()) {
return state->copyDefaultCMYKColorSpace();
} else {
return GfxColorSpace::parse(nullptr, &objCS, out, state);
}
} else {
return state->copyDefaultCMYKColorSpace();
}
} else {
error(errSyntaxWarning, -1, "Bad color space dict'");
}
} else {
error(errSyntaxWarning, -1, "Bad color space - expected name or array or dict");
}
return {};
}
void GfxColorSpace::createMapping(std::vector<std::unique_ptr<GfxSeparationColorSpace>> *separationList, int maxSepComps)
{
return;
}
void GfxColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const
{
int i;
for (i = 0; i < getNComps(); ++i) {
decodeLow[i] = 0;
decodeRange[i] = 1;
}
}
int GfxColorSpace::getNumColorSpaceModes()
{
return nGfxColorSpaceModes;
}
const char *GfxColorSpace::getColorSpaceModeName(int idx)
{
return gfxColorSpaceModeNames[idx];
}
#ifdef USE_CMS
static void CMSError(cmsContext /*contextId*/, cmsUInt32Number /*ecode*/, const char *text)
{
error(errSyntaxWarning, -1, "{0:s}", text);
}
unsigned int getCMSColorSpaceType(cmsColorSpaceSignature cs)
{
switch (cs) {
case cmsSigXYZData:
return PT_XYZ;
break;
case cmsSigLabData:
return PT_Lab;
break;
case cmsSigLuvData:
return PT_YUV;
break;
case cmsSigYCbCrData:
return PT_YCbCr;
break;
case cmsSigYxyData:
return PT_Yxy;
break;
case cmsSigRgbData:
return PT_RGB;
break;
case cmsSigGrayData:
return PT_GRAY;
break;
case cmsSigHsvData:
return PT_HSV;
break;
case cmsSigHlsData:
return PT_HLS;
break;
case cmsSigCmykData:
return PT_CMYK;
break;
case cmsSigCmyData:
return PT_CMY;
break;
case cmsSig2colorData:
case cmsSig3colorData:
case cmsSig4colorData:
case cmsSig5colorData:
case cmsSig6colorData:
case cmsSig7colorData:
case cmsSig8colorData:
case cmsSig9colorData:
case cmsSig10colorData:
case cmsSig11colorData:
case cmsSig12colorData:
case cmsSig13colorData:
case cmsSig14colorData:
case cmsSig15colorData:
default:
break;
}
return PT_RGB;
}
unsigned int getCMSNChannels(cmsColorSpaceSignature cs)
{
switch (cs) {
case cmsSigXYZData:
case cmsSigLuvData:
case cmsSigLabData:
case cmsSigYCbCrData:
case cmsSigYxyData:
case cmsSigRgbData:
case cmsSigHsvData:
case cmsSigHlsData:
case cmsSigCmyData:
case cmsSig3colorData:
return 3;
break;
case cmsSigGrayData:
return 1;
break;
case cmsSigCmykData:
case cmsSig4colorData:
return 4;
break;
case cmsSig2colorData:
return 2;
break;
case cmsSig5colorData:
return 5;
break;
case cmsSig6colorData:
return 6;
break;
case cmsSig7colorData:
return 7;
break;
case cmsSig8colorData:
return 8;
break;
case cmsSig9colorData:
return 9;
break;
case cmsSig10colorData:
return 10;
break;
case cmsSig11colorData:
return 11;
break;
case cmsSig12colorData:
return 12;
break;
case cmsSig13colorData:
return 13;
break;
case cmsSig14colorData:
return 14;
break;
case cmsSig15colorData:
return 15;
default:
break;
}
return 3;
}
#endif
//------------------------------------------------------------------------
// GfxDeviceGrayColorSpace
//------------------------------------------------------------------------
GfxDeviceGrayColorSpace::GfxDeviceGrayColorSpace() { }
GfxDeviceGrayColorSpace::~GfxDeviceGrayColorSpace() { }
std::unique_ptr<GfxColorSpace> GfxDeviceGrayColorSpace::copy() const
{
return std::make_unique<GfxDeviceGrayColorSpace>();
}
void GfxDeviceGrayColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
{
*gray = clip01(color->c[0]);
}
void GfxDeviceGrayColorSpace::getGrayLine(unsigned char *in, unsigned char *out, int length)
{
memcpy(out, in, length);
}
void GfxDeviceGrayColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
{
rgb->r = rgb->g = rgb->b = clip01(color->c[0]);
}
void GfxDeviceGrayColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
{
int i;
for (i = 0; i < length; i++) {
out[i] = (in[i] << 16) | (in[i] << 8) | (in[i] << 0);
}
}
void GfxDeviceGrayColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
{
for (int i = 0; i < length; i++) {
*out++ = in[i];
*out++ = in[i];
*out++ = in[i];
}
}
void GfxDeviceGrayColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
{
for (int i = 0; i < length; i++) {
*out++ = in[i];
*out++ = in[i];
*out++ = in[i];
*out++ = 255;
}
}
void GfxDeviceGrayColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
{
for (int i = 0; i < length; i++) {
*out++ = 0;
*out++ = 0;
*out++ = 0;
*out++ = in[i];
}
}
void GfxDeviceGrayColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
{
for (int i = 0; i < length; i++) {
for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
out[j] = 0;
}
out[4] = in[i];
out += (SPOT_NCOMPS + 4);
}
}
void GfxDeviceGrayColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
{
cmyk->c = cmyk->m = cmyk->y = 0;
cmyk->k = clip01(gfxColorComp1 - color->c[0]);
}
void GfxDeviceGrayColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
{
clearGfxColor(deviceN);
deviceN->c[3] = clip01(gfxColorComp1 - color->c[0]);
}
void GfxDeviceGrayColorSpace::getDefaultColor(GfxColor *color) const
{
color->c[0] = 0;
}
//------------------------------------------------------------------------
// GfxCalGrayColorSpace
//------------------------------------------------------------------------
GfxCalGrayColorSpace::GfxCalGrayColorSpace()
{
whiteX = whiteY = whiteZ = 1;
blackX = blackY = blackZ = 0;
gamma = 1;
}
GfxCalGrayColorSpace::~GfxCalGrayColorSpace() { }
std::unique_ptr<GfxColorSpace> GfxCalGrayColorSpace::copy() const
{
auto cs = std::make_unique<GfxCalGrayColorSpace>();
cs->whiteX = whiteX;
cs->whiteY = whiteY;
cs->whiteZ = whiteZ;
cs->blackX = blackX;
cs->blackY = blackY;
cs->blackZ = blackZ;
cs->gamma = gamma;
#ifdef USE_CMS
cs->transform = transform;
#endif
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 } };
// 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;
#ifdef USE_CMS
// 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;
}
#endif
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;
}
std::unique_ptr<GfxColorSpace> GfxCalGrayColorSpace::parse(Array *arr, GfxState *state)
{
Object obj1, obj2;
obj1 = arr->get(1);
if (!obj1.isDict()) {
error(errSyntaxWarning, -1, "Bad CalGray color space");
return {};
}
auto cs = std::make_unique<GfxCalGrayColorSpace>();
obj2 = obj1.dictLookup("WhitePoint");
if (obj2.isArray() && obj2.arrayGetLength() == 3) {
cs->whiteX = obj2.arrayGet(0).getNumWithDefaultValue(1);
cs->whiteY = obj2.arrayGet(1).getNumWithDefaultValue(1);
cs->whiteZ = obj2.arrayGet(2).getNumWithDefaultValue(1);
}
obj2 = obj1.dictLookup("BlackPoint");
if (obj2.isArray() && obj2.arrayGetLength() == 3) {
cs->blackX = obj2.arrayGet(0).getNumWithDefaultValue(0);
cs->blackY = obj2.arrayGet(1).getNumWithDefaultValue(0);
cs->blackZ = obj2.arrayGet(2).getNumWithDefaultValue(0);
}
cs->gamma = obj1.dictLookup("Gamma").getNumWithDefaultValue(1);
#ifdef USE_CMS
cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr;
#endif
return cs;
}
// convert CalGray to media XYZ color space
// (not multiply by the white point)
void GfxCalGrayColorSpace::getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const
{
const double A = colToDbl(color->c[0]);
const double xyzColor = pow(A, gamma);
*pX = xyzColor;
*pY = xyzColor;
*pZ = xyzColor;
}
void GfxCalGrayColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
{
GfxRGB rgb;
#ifdef USE_CMS
if (transform && transform->getTransformPixelType() == PT_GRAY) {
unsigned char out[gfxColorMaxComps];
double in[gfxColorMaxComps];
double X, Y, Z;
getXYZ(color, &X, &Y, &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;
}
#endif
getRGB(color, &rgb);
*gray = clip01((GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b + 0.5));
}
void GfxCalGrayColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
{
double X, Y, Z;
double r, g, b;
getXYZ(color, &X, &Y, &Z);
#ifdef USE_CMS
if (transform && transform->getTransformPixelType() == PT_RGB) {
unsigned char out[gfxColorMaxComps];
double in[gfxColorMaxComps];
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(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
{
GfxRGB rgb;
GfxColorComp c, m, y, k;
#ifdef USE_CMS
if (transform && transform->getTransformPixelType() == PT_CMYK) {
double in[gfxColorMaxComps];
unsigned char out[gfxColorMaxComps];
double X, Y, Z;
getXYZ(color, &X, &Y, &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]);
cmyk->y = byteToCol(out[2]);
cmyk->k = byteToCol(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::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
{
GfxCMYK cmyk;
clearGfxColor(deviceN);
getCMYK(color, &cmyk);
deviceN->c[0] = cmyk.c;
deviceN->c[1] = cmyk.m;
deviceN->c[2] = cmyk.y;
deviceN->c[3] = cmyk.k;
}
void GfxCalGrayColorSpace::getDefaultColor(GfxColor *color) const
{
color->c[0] = 0;
}
//------------------------------------------------------------------------
// GfxDeviceRGBColorSpace
//------------------------------------------------------------------------
GfxDeviceRGBColorSpace::GfxDeviceRGBColorSpace() { }
GfxDeviceRGBColorSpace::~GfxDeviceRGBColorSpace() { }
std::unique_ptr<GfxColorSpace> GfxDeviceRGBColorSpace::copy() const
{
return std::make_unique<GfxDeviceRGBColorSpace>();
}
void GfxDeviceRGBColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
{
*gray = clip01((GfxColorComp)(0.3 * color->c[0] + 0.59 * color->c[1] + 0.11 * color->c[2] + 0.5));
}
void GfxDeviceRGBColorSpace::getGrayLine(unsigned char *in, unsigned char *out, int length)
{
int i;
for (i = 0; i < length; i++) {
out[i] = (in[i * 3 + 0] * 19595 + in[i * 3 + 1] * 38469 + in[i * 3 + 2] * 7472) / 65536;
}
}
void GfxDeviceRGBColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
{
rgb->r = clip01(color->c[0]);
rgb->g = clip01(color->c[1]);
rgb->b = clip01(color->c[2]);
}
void GfxDeviceRGBColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
{
unsigned char *p;
int i;
for (i = 0, p = in; i < length; i++, p += 3) {
out[i] = (p[0] << 16) | (p[1] << 8) | (p[2] << 0);
}
}
void GfxDeviceRGBColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
{
for (int i = 0; i < length; i++) {
*out++ = *in++;
*out++ = *in++;
*out++ = *in++;
}
}
void GfxDeviceRGBColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
{
for (int i = 0; i < length; i++) {
*out++ = *in++;
*out++ = *in++;
*out++ = *in++;
*out++ = 255;
}
}
void GfxDeviceRGBColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
{
GfxColorComp c, m, y, k;
for (int i = 0; i < length; i++) {
c = byteToCol(255 - *in++);
m = byteToCol(255 - *in++);
y = byteToCol(255 - *in++);
k = c;
if (m < k) {
k = m;
}
if (y < k) {
k = y;
}
*out++ = colToByte(c - k);
*out++ = colToByte(m - k);
*out++ = colToByte(y - k);
*out++ = colToByte(k);
}
}
void GfxDeviceRGBColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
{
GfxColorComp c, m, y, k;
for (int i = 0; i < length; i++) {
for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
out[j] = 0;
}
c = byteToCol(255 - *in++);
m = byteToCol(255 - *in++);
y = byteToCol(255 - *in++);
k = c;
if (m < k) {
k = m;
}
if (y < k) {
k = y;
}
out[0] = colToByte(c - k);
out[1] = colToByte(m - k);
out[2] = colToByte(y - k);
out[3] = colToByte(k);
out += (SPOT_NCOMPS + 4);
}
}
void GfxDeviceRGBColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
{
GfxColorComp c, m, y, k;
c = clip01(gfxColorComp1 - color->c[0]);
m = clip01(gfxColorComp1 - color->c[1]);
y = clip01(gfxColorComp1 - color->c[2]);
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 GfxDeviceRGBColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
{
GfxCMYK cmyk;
clearGfxColor(deviceN);
getCMYK(color, &cmyk);
deviceN->c[0] = cmyk.c;
deviceN->c[1] = cmyk.m;
deviceN->c[2] = cmyk.y;
deviceN->c[3] = cmyk.k;
}
void GfxDeviceRGBColorSpace::getDefaultColor(GfxColor *color) const
{
color->c[0] = 0;
color->c[1] = 0;
color->c[2] = 0;
}
//------------------------------------------------------------------------
// GfxDeviceRGBAColorSpace
//------------------------------------------------------------------------
GfxDeviceRGBAColorSpace::GfxDeviceRGBAColorSpace() { }
GfxDeviceRGBAColorSpace::~GfxDeviceRGBAColorSpace() { }
std::unique_ptr<GfxColorSpace> GfxDeviceRGBAColorSpace::copy() const
{
return std::make_unique<GfxDeviceRGBAColorSpace>();
}
void GfxDeviceRGBAColorSpace::getARGBPremultipliedLine(unsigned char *in, unsigned int *out, int length)
{
unsigned char *p;
int i;
// Conversion from 'in' RGBA to 'out' ARGB32_PREMULTIPLIED (used by Cairo)
for (i = 0, p = in; i < length; i++, p += 4) {
// This applies alpha component p[3] to each RGB values (using bitwise division)
// so final result in out[i] is ARGB32 with premultiplied alpha.
out[i] = p[3] << 24 | (p[0] * p[3] >> 8) << 16 | (p[1] * p[3] >> 8) << 8 | (p[2] * p[3] >> 8) << 0;
}
}
//------------------------------------------------------------------------
// GfxCalRGBColorSpace
//------------------------------------------------------------------------
GfxCalRGBColorSpace::GfxCalRGBColorSpace()
{
whiteX = whiteY = whiteZ = 1;
blackX = blackY = blackZ = 0;
gammaR = gammaG = gammaB = 1;
mat[0] = 1;
mat[1] = 0;
mat[2] = 0;
mat[3] = 0;
mat[4] = 1;
mat[5] = 0;
mat[6] = 0;
mat[7] = 0;
mat[8] = 1;
}
GfxCalRGBColorSpace::~GfxCalRGBColorSpace() { }
std::unique_ptr<GfxColorSpace> GfxCalRGBColorSpace::copy() const
{
int i;
auto cs = std::make_unique<GfxCalRGBColorSpace>();
cs->whiteX = whiteX;
cs->whiteY = whiteY;
cs->whiteZ = whiteZ;
cs->blackX = blackX;
cs->blackY = blackY;
cs->blackZ = blackZ;
cs->gammaR = gammaR;
cs->gammaG = gammaG;
cs->gammaB = gammaB;
for (i = 0; i < 9; ++i) {
cs->mat[i] = mat[i];
}
#ifdef USE_CMS
cs->transform = transform;
#endif
return cs;
}
std::unique_ptr<GfxColorSpace> GfxCalRGBColorSpace::parse(Array *arr, GfxState *state)
{
Object obj1, obj2;
int i;
obj1 = arr->get(1);
if (!obj1.isDict()) {
error(errSyntaxWarning, -1, "Bad CalRGB color space");
return {};
}
auto cs = std::make_unique<GfxCalRGBColorSpace>();
obj2 = obj1.dictLookup("WhitePoint");
if (obj2.isArray() && obj2.arrayGetLength() == 3) {
cs->whiteX = obj2.arrayGet(0).getNumWithDefaultValue(1);
cs->whiteY = obj2.arrayGet(1).getNumWithDefaultValue(1);
cs->whiteZ = obj2.arrayGet(2).getNumWithDefaultValue(1);
}
obj2 = obj1.dictLookup("BlackPoint");
if (obj2.isArray() && obj2.arrayGetLength() == 3) {
cs->blackX = obj2.arrayGet(0).getNumWithDefaultValue(0);
cs->blackY = obj2.arrayGet(1).getNumWithDefaultValue(0);
cs->blackZ = obj2.arrayGet(2).getNumWithDefaultValue(0);
}
obj2 = obj1.dictLookup("Gamma");
if (obj2.isArray() && obj2.arrayGetLength() == 3) {
cs->gammaR = obj2.arrayGet(0).getNumWithDefaultValue(1);
cs->gammaG = obj2.arrayGet(1).getNumWithDefaultValue(1);
cs->gammaB = obj2.arrayGet(2).getNumWithDefaultValue(1);
}
obj2 = obj1.dictLookup("Matrix");
if (obj2.isArray() && obj2.arrayGetLength() == 9) {
for (i = 0; i < 9; ++i) {
Object obj3 = obj2.arrayGet(i);
if (likely(obj3.isNum())) {
cs->mat[i] = obj3.getNum();
}
}
}
#ifdef USE_CMS
cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr;
#endif
return cs;
}
// convert CalRGB to XYZ color space
void GfxCalRGBColorSpace::getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const
{
double A, B, C;
A = pow(colToDbl(color->c[0]), gammaR);
B = pow(colToDbl(color->c[1]), gammaG);
C = pow(colToDbl(color->c[2]), gammaB);
*pX = mat[0] * A + mat[3] * B + mat[6] * C;
*pY = mat[1] * A + mat[4] * B + mat[7] * C;
*pZ = mat[2] * A + mat[5] * B + mat[8] * C;
}
void GfxCalRGBColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
{
GfxRGB rgb;
#ifdef USE_CMS
if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
unsigned char out[gfxColorMaxComps];
double in[gfxColorMaxComps];
double X, Y, Z;
getXYZ(color, &X, &Y, &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;
}
#endif
getRGB(color, &rgb);
*gray = clip01((GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b + 0.5));
}
void GfxCalRGBColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
{
double X, Y, Z;
double r, g, b;
getXYZ(color, &X, &Y, &Z);
#ifdef USE_CMS
if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
unsigned char out[gfxColorMaxComps];
double in[gfxColorMaxComps];
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(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
{
GfxRGB rgb;
GfxColorComp c, m, y, k;
#ifdef USE_CMS
if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
double in[gfxColorMaxComps];
unsigned char out[gfxColorMaxComps];
double X, Y, Z;
getXYZ(color, &X, &Y, &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]);
cmyk->y = byteToCol(out[2]);
cmyk->k = byteToCol(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 GfxCalRGBColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
{
GfxCMYK cmyk;
clearGfxColor(deviceN);
getCMYK(color, &cmyk);
deviceN->c[0] = cmyk.c;
deviceN->c[1] = cmyk.m;
deviceN->c[2] = cmyk.y;
deviceN->c[3] = cmyk.k;
}
void GfxCalRGBColorSpace::getDefaultColor(GfxColor *color) const
{
color->c[0] = 0;
color->c[1] = 0;
color->c[2] = 0;
}
//------------------------------------------------------------------------
// GfxDeviceCMYKColorSpace
//------------------------------------------------------------------------
GfxDeviceCMYKColorSpace::GfxDeviceCMYKColorSpace() { }
GfxDeviceCMYKColorSpace::~GfxDeviceCMYKColorSpace() { }
std::unique_ptr<GfxColorSpace> GfxDeviceCMYKColorSpace::copy() const
{
return std::make_unique<GfxDeviceCMYKColorSpace>();
}
void GfxDeviceCMYKColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
{
*gray = clip01((GfxColorComp)(gfxColorComp1 - color->c[3] - 0.3 * color->c[0] - 0.59 * color->c[1] - 0.11 * color->c[2] + 0.5));
}
void GfxDeviceCMYKColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
{
double c, m, y, k, c1, m1, y1, k1, r, g, b;
c = colToDbl(color->c[0]);
m = colToDbl(color->c[1]);
y = colToDbl(color->c[2]);
k = colToDbl(color->c[3]);
c1 = 1 - c;
m1 = 1 - m;
y1 = 1 - y;
k1 = 1 - k;
cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
rgb->r = clip01(dblToCol(r));
rgb->g = clip01(dblToCol(g));
rgb->b = clip01(dblToCol(b));
}
static inline void GfxDeviceCMYKColorSpacegetRGBLineHelper(unsigned char *&in, double &r, double &g, double &b)
{
double c, m, y, k, c1, m1, y1, k1;
c = byteToDbl(*in++);
m = byteToDbl(*in++);
y = byteToDbl(*in++);
k = byteToDbl(*in++);
c1 = 1 - c;
m1 = 1 - m;
y1 = 1 - y;
k1 = 1 - k;
cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
}
void GfxDeviceCMYKColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
{
double r, g, b;
for (int i = 0; i < length; i++) {
GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b);
*out++ = (dblToByte(clip01(r)) << 16) | (dblToByte(clip01(g)) << 8) | dblToByte(clip01(b));
}
}
void GfxDeviceCMYKColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
{
double r, g, b;
for (int i = 0; i < length; i++) {
GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b);
*out++ = dblToByte(clip01(r));
*out++ = dblToByte(clip01(g));
*out++ = dblToByte(clip01(b));
}
}
void GfxDeviceCMYKColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
{
double r, g, b;
for (int i = 0; i < length; i++) {
GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b);
*out++ = dblToByte(clip01(r));
*out++ = dblToByte(clip01(g));
*out++ = dblToByte(clip01(b));
*out++ = 255;
}
}
void GfxDeviceCMYKColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
{
for (int i = 0; i < length; i++) {
*out++ = *in++;
*out++ = *in++;
*out++ = *in++;
*out++ = *in++;
}
}
void GfxDeviceCMYKColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
{
for (int i = 0; i < length; i++) {
for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
out[j] = 0;
}
out[0] = *in++;
out[1] = *in++;
out[2] = *in++;
out[3] = *in++;
out += (SPOT_NCOMPS + 4);
}
}
void GfxDeviceCMYKColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
{
cmyk->c = clip01(color->c[0]);
cmyk->m = clip01(color->c[1]);
cmyk->y = clip01(color->c[2]);
cmyk->k = clip01(color->c[3]);
}
void GfxDeviceCMYKColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
{
clearGfxColor(deviceN);
deviceN->c[0] = clip01(color->c[0]);
deviceN->c[1] = clip01(color->c[1]);
deviceN->c[2] = clip01(color->c[2]);
deviceN->c[3] = clip01(color->c[3]);
}
void GfxDeviceCMYKColorSpace::getDefaultColor(GfxColor *color) const
{
color->c[0] = 0;
color->c[1] = 0;
color->c[2] = 0;
color->c[3] = gfxColorComp1;
}
//------------------------------------------------------------------------
// GfxLabColorSpace
//------------------------------------------------------------------------
GfxLabColorSpace::GfxLabColorSpace()
{
whiteX = whiteY = whiteZ = 1;
blackX = blackY = blackZ = 0;
aMin = bMin = -100;
aMax = bMax = 100;
}
GfxLabColorSpace::~GfxLabColorSpace() { }
std::unique_ptr<GfxColorSpace> GfxLabColorSpace::copy() const
{
auto cs = std::make_unique<GfxLabColorSpace>();
cs->whiteX = whiteX;
cs->whiteY = whiteY;
cs->whiteZ = whiteZ;
cs->blackX = blackX;
cs->blackY = blackY;
cs->blackZ = blackZ;
cs->aMin = aMin;
cs->aMax = aMax;
cs->bMin = bMin;
cs->bMax = bMax;
#ifdef USE_CMS
cs->transform = transform;
#endif
return cs;
}
std::unique_ptr<GfxColorSpace> GfxLabColorSpace::parse(Array *arr, GfxState *state)
{
Object obj1, obj2;
obj1 = arr->get(1);
if (!obj1.isDict()) {
error(errSyntaxWarning, -1, "Bad Lab color space");
return {};
}
auto cs = std::make_unique<GfxLabColorSpace>();
bool ok = true;
obj2 = obj1.dictLookup("WhitePoint");
if (obj2.isArray() && obj2.arrayGetLength() == 3) {
cs->whiteX = obj2.arrayGet(0).getNum(&ok);
cs->whiteY = obj2.arrayGet(1).getNum(&ok);
cs->whiteZ = obj2.arrayGet(2).getNum(&ok);
}
obj2 = obj1.dictLookup("BlackPoint");
if (obj2.isArray() && obj2.arrayGetLength() == 3) {
cs->blackX = obj2.arrayGet(0).getNum(&ok);
cs->blackY = obj2.arrayGet(1).getNum(&ok);
cs->blackZ = obj2.arrayGet(2).getNum(&ok);
}
obj2 = obj1.dictLookup("Range");
if (obj2.isArray() && obj2.arrayGetLength() == 4) {
cs->aMin = obj2.arrayGet(0).getNum(&ok);
cs->aMax = obj2.arrayGet(1).getNum(&ok);
cs->bMin = obj2.arrayGet(2).getNum(&ok);
cs->bMax = obj2.arrayGet(3).getNum(&ok);
}
if (!ok) {
error(errSyntaxWarning, -1, "Bad Lab color space");
#ifdef USE_CMS
cs->transform = nullptr;
#endif
return {};
}
#ifdef USE_CMS
cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr;
#endif
return cs;
}
void GfxLabColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
{
GfxRGB rgb;
#ifdef USE_CMS
if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
unsigned char out[gfxColorMaxComps];
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;
}
#endif
getRGB(color, &rgb);
*gray = clip01((GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b + 0.5));
}
// convert L*a*b* to media XYZ color space
// (not multiply by the white point)
void GfxLabColorSpace::getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const
{
double X, Y, Z;
double t1, t2;
t1 = (colToDbl(color->c[0]) + 16) / 116;
t2 = t1 + colToDbl(color->c[1]) / 500;
if (t2 >= (6.0 / 29.0)) {
X = t2 * t2 * t2;
} else {
X = (108.0 / 841.0) * (t2 - (4.0 / 29.0));
}
if (t1 >= (6.0 / 29.0)) {
Y = t1 * t1 * t1;
} else {
Y = (108.0 / 841.0) * (t1 - (4.0 / 29.0));
}
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));
}
*pX = X;
*pY = Y;
*pZ = Z;
}
void GfxLabColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
{
double X, Y, Z;
getXYZ(color, &X, &Y, &Z);
X *= whiteX;
Y *= whiteY;
Z *= whiteZ;
#ifdef USE_CMS
if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
unsigned char out[gfxColorMaxComps];
double in[gfxColorMaxComps];
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;
} else if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
unsigned char out[gfxColorMaxComps];
double in[gfxColorMaxComps];
double c, m, y, k, c1, m1, y1, k1, r, g, b;
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]);
y = byteToDbl(out[2]);
k = byteToDbl(out[3]);
c1 = 1 - c;
m1 = 1 - m;
y1 = 1 - y;
k1 = 1 - k;
cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
rgb->r = clip01(dblToCol(r));
rgb->g = clip01(dblToCol(g));
rgb->b = clip01(dblToCol(b));
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(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
{
GfxRGB rgb;
GfxColorComp c, m, y, k;
#ifdef USE_CMS
if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
double in[gfxColorMaxComps];
unsigned char out[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);
cmyk->c = byteToCol(out[0]);
cmyk->m = byteToCol(out[1]);
cmyk->y = byteToCol(out[2]);
cmyk->k = byteToCol(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 GfxLabColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
{
GfxCMYK cmyk;
clearGfxColor(deviceN);
getCMYK(color, &cmyk);
deviceN->c[0] = cmyk.c;
deviceN->c[1] = cmyk.m;
deviceN->c[2] = cmyk.y;
deviceN->c[3] = cmyk.k;
}
void GfxLabColorSpace::getDefaultColor(GfxColor *color) const
{
color->c[0] = 0;
if (aMin > 0) {
color->c[1] = dblToCol(aMin);
} else if (aMax < 0) {
color->c[1] = dblToCol(aMax);
} else {
color->c[1] = 0;
}
if (bMin > 0) {
color->c[2] = dblToCol(bMin);
} else if (bMax < 0) {
color->c[2] = dblToCol(bMax);
} else {
color->c[2] = 0;
}
}
void GfxLabColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const
{
decodeLow[0] = 0;
decodeRange[0] = 100;
decodeLow[1] = aMin;
decodeRange[1] = aMax - aMin;
decodeLow[2] = bMin;
decodeRange[2] = bMax - bMin;
}
//------------------------------------------------------------------------
// GfxICCBasedColorSpace
//------------------------------------------------------------------------
GfxICCBasedColorSpace::GfxICCBasedColorSpace(int nCompsA, std::unique_ptr<GfxColorSpace> &&altA, const Ref *iccProfileStreamA) : alt(std::move(altA))
{
nComps = nCompsA;
iccProfileStream = *iccProfileStreamA;
rangeMin[0] = rangeMin[1] = rangeMin[2] = rangeMin[3] = 0;
rangeMax[0] = rangeMax[1] = rangeMax[2] = rangeMax[3] = 1;
#ifdef USE_CMS
transform = nullptr;
lineTransform = nullptr;
psCSA = nullptr;
#endif
}
GfxICCBasedColorSpace::~GfxICCBasedColorSpace()
{
#ifdef USE_CMS
if (psCSA) {
gfree(psCSA);
}
#endif
}
std::unique_ptr<GfxColorSpace> GfxICCBasedColorSpace::copy() const
{
return copyAsOwnType();
}
std::unique_ptr<GfxICCBasedColorSpace> GfxICCBasedColorSpace::copyAsOwnType() const
{
int i;
auto cs = std::make_unique<GfxICCBasedColorSpace>(nComps, alt->copy(), &iccProfileStream);
for (i = 0; i < 4; ++i) {
cs->rangeMin[i] = rangeMin[i];
cs->rangeMax[i] = rangeMax[i];
}
#ifdef USE_CMS
cs->profile = profile;
cs->transform = transform;
cs->lineTransform = lineTransform;
#endif
return cs;
}
std::unique_ptr<GfxColorSpace> GfxICCBasedColorSpace::parse(Array *arr, OutputDev *out, GfxState *state, int recursion)
{
int nCompsA;
Dict *dict;
Object obj1, obj2;
int i;
if (arr->getLength() < 2) {
error(errSyntaxError, -1, "Bad ICCBased color space");
return {};
}
const Object &obj1Ref = arr->getNF(1);
const Ref iccProfileStreamA = obj1Ref.isRef() ? obj1Ref.getRef() : Ref::INVALID();
#ifdef USE_CMS
// check cache
if (out && iccProfileStreamA != Ref::INVALID()) {
if (auto *item = out->getIccColorSpaceCache()->lookup(iccProfileStreamA)) {
std::unique_ptr<GfxICCBasedColorSpace> cs = item->copyAsOwnType();
int transformIntent = cs->getIntent();
int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
if (state != nullptr) {
cmsIntent = state->getCmsRenderingIntent();
}
if (transformIntent == cmsIntent) {
return cs;
}
}
}
#endif
obj1 = arr->get(1);
if (!obj1.isStream()) {
error(errSyntaxWarning, -1, "Bad ICCBased color space (stream)");
return nullptr;
}
dict = obj1.streamGetDict();
obj2 = dict->lookup("N");
if (!obj2.isInt()) {
error(errSyntaxWarning, -1, "Bad ICCBased color space (N)");
return nullptr;
}
nCompsA = obj2.getInt();
if (nCompsA > 4) {
error(errSyntaxError, -1, "ICCBased color space with too many ({0:d} > 4) components", nCompsA);
nCompsA = 4;
}
obj2 = dict->lookup("Alternate");
std::unique_ptr<GfxColorSpace> altA;
if (obj2.isNull() || !(altA = GfxColorSpace::parse(nullptr, &obj2, out, state, recursion + 1))) {
switch (nCompsA) {
case 1:
altA = std::make_unique<GfxDeviceGrayColorSpace>();
break;
case 3:
altA = std::make_unique<GfxDeviceRGBColorSpace>();
break;
case 4:
altA = std::make_unique<GfxDeviceCMYKColorSpace>();
break;
default:
error(errSyntaxWarning, -1, "Bad ICCBased color space - invalid N");
return nullptr;
}
}
if (altA->getNComps() != nCompsA) {
error(errSyntaxWarning, -1, "Bad ICCBased color space - N doesn't match alt color space");
return {};
}
auto cs = std::make_unique<GfxICCBasedColorSpace>(nCompsA, std::move(altA), &iccProfileStreamA);
obj2 = dict->lookup("Range");
if (obj2.isArray() && obj2.arrayGetLength() == 2 * nCompsA) {
for (i = 0; i < nCompsA; ++i) {
cs->rangeMin[i] = obj2.arrayGet(2 * i).getNumWithDefaultValue(0);
cs->rangeMax[i] = obj2.arrayGet(2 * i + 1).getNumWithDefaultValue(1);
}
}
#ifdef USE_CMS
obj1 = arr->get(1);
if (!obj1.isStream()) {
error(errSyntaxWarning, -1, "Bad ICCBased color space (stream)");
return {};
}
Stream *iccStream = obj1.getStream();
const std::vector<unsigned char> profBuf = iccStream->toUnsignedChars(65536, 65536);
auto hp = make_GfxLCMSProfilePtr(cmsOpenProfileFromMem(profBuf.data(), profBuf.size()));
cs->profile = hp;
if (!hp) {
error(errSyntaxWarning, -1, "read ICCBased color space profile error");
} else {
cs->buildTransforms(state);
}
// put this colorSpace into cache
if (out && iccProfileStreamA != Ref::INVALID()) {
out->getIccColorSpaceCache()->put(iccProfileStreamA, cs->copyAsOwnType());
}
#endif
return cs;
}
#ifdef USE_CMS
void GfxICCBasedColorSpace::buildTransforms(GfxState *state)
{
auto dhp = (state != nullptr && state->getDisplayProfile() != nullptr) ? state->getDisplayProfile() : nullptr;
if (!dhp) {
dhp = GfxState::sRGBProfile;
}
unsigned int cst = getCMSColorSpaceType(cmsGetColorSpace(profile.get()));
unsigned int dNChannels = getCMSNChannels(cmsGetColorSpace(dhp.get()));
unsigned int dcst = getCMSColorSpaceType(cmsGetColorSpace(dhp.get()));
cmsHTRANSFORM transformA;
int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
if (state != nullptr) {
cmsIntent = state->getCmsRenderingIntent();
}
if ((transformA = cmsCreateTransform(profile.get(), COLORSPACE_SH(cst) | CHANNELS_SH(nComps) | BYTES_SH(1), dhp.get(), COLORSPACE_SH(dcst) | CHANNELS_SH(dNChannels) | BYTES_SH(1), cmsIntent, LCMS_FLAGS)) == nullptr) {
error(errSyntaxWarning, -1, "Can't create transform");
transform = nullptr;
} else {
transform = std::make_shared<GfxColorTransform>(transformA, cmsIntent, cst, dcst);
}
if (dcst == PT_RGB || dcst == PT_CMYK) {
// create line transform only when the display is RGB type color space
if ((transformA = cmsCreateTransform(profile.get(), CHANNELS_SH(nComps) | BYTES_SH(1), dhp.get(), (dcst == PT_RGB) ? TYPE_RGB_8 : TYPE_CMYK_8, cmsIntent, LCMS_FLAGS)) == nullptr) {
error(errSyntaxWarning, -1, "Can't create transform");
lineTransform = nullptr;
} else {
lineTransform = std::make_shared<GfxColorTransform>(transformA, cmsIntent, cst, dcst);
}
}
}
#endif
void GfxICCBasedColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
{
#ifdef USE_CMS
if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
unsigned char in[gfxColorMaxComps];
unsigned char out[gfxColorMaxComps];
if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
} else {
for (int i = 0; i < nComps; i++) {
in[i] = colToByte(color->c[i]);
}
}
if (nComps <= 4) {
unsigned int key = 0;
for (int j = 0; j < nComps; j++) {
key = (key << 8) + in[j];
}
std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
if (it != cmsCache.end()) {
unsigned int value = it->second;
*gray = byteToCol(value & 0xff);
return;
}
}
transform->doTransform(in, out, 1);
*gray = byteToCol(out[0]);
if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
unsigned int key = 0;
for (int j = 0; j < nComps; j++) {
key = (key << 8) + in[j];
}
unsigned int value = out[0];
cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
}
} 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(const GfxColor *color, GfxRGB *rgb) const
{
#ifdef USE_CMS
if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
unsigned char in[gfxColorMaxComps];
unsigned char out[gfxColorMaxComps];
if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
} else {
for (int i = 0; i < nComps; i++) {
in[i] = colToByte(color->c[i]);
}
}
if (nComps <= 4) {
unsigned int key = 0;
for (int j = 0; j < nComps; j++) {
key = (key << 8) + in[j];
}
std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
if (it != cmsCache.end()) {
unsigned int value = it->second;
rgb->r = byteToCol(value >> 16);
rgb->g = byteToCol((value >> 8) & 0xff);
rgb->b = byteToCol(value & 0xff);
return;
}
}
transform->doTransform(in, out, 1);
rgb->r = byteToCol(out[0]);
rgb->g = byteToCol(out[1]);
rgb->b = byteToCol(out[2]);
if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
unsigned int key = 0;
for (int j = 0; j < nComps; j++) {
key = (key << 8) + in[j];
}
unsigned int value = (out[0] << 16) + (out[1] << 8) + out[2];
cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
}
} else if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
unsigned char in[gfxColorMaxComps];
unsigned char out[gfxColorMaxComps];
double c, m, y, k, c1, m1, y1, k1, r, g, b;
if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
} else {
for (int i = 0; i < nComps; i++) {
in[i] = colToByte(color->c[i]);
}
}
if (nComps <= 4) {
unsigned int key = 0;
for (int j = 0; j < nComps; j++) {
key = (key << 8) + in[j];
}
std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
if (it != cmsCache.end()) {
unsigned int value = it->second;
rgb->r = byteToCol(value >> 16);
rgb->g = byteToCol((value >> 8) & 0xff);
rgb->b = byteToCol(value & 0xff);
return;
}
}
transform->doTransform(in, out, 1);
c = byteToDbl(out[0]);
m = byteToDbl(out[1]);
y = byteToDbl(out[2]);
k = byteToDbl(out[3]);
c1 = 1 - c;
m1 = 1 - m;
y1 = 1 - y;
k1 = 1 - k;
cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
rgb->r = clip01(dblToCol(r));
rgb->g = clip01(dblToCol(g));
rgb->b = clip01(dblToCol(b));
if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
unsigned int key = 0;
for (int j = 0; j < nComps; j++) {
key = (key << 8) + in[j];
}
unsigned int value = (dblToByte(r) << 16) + (dblToByte(g) << 8) + dblToByte(b);
cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
}
} else {
alt->getRGB(color, rgb);
}
#else
alt->getRGB(color, rgb);
#endif
}
void GfxICCBasedColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
{
#ifdef USE_CMS
if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) {
unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
lineTransform->doTransform(in, tmp, length);
for (int i = 0; i < length; ++i) {
unsigned char *current = tmp + (i * 3);
out[i] = (current[0] << 16) | (current[1] << 8) | current[2];
}
gfree(tmp);
} else {
alt->getRGBLine(in, out, length);
}
#else
alt->getRGBLine(in, out, length);
#endif
}
void GfxICCBasedColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
{
#ifdef USE_CMS
if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) {
unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
lineTransform->doTransform(in, tmp, length);
unsigned char *current = tmp;
for (int i = 0; i < length; ++i) {
*out++ = *current++;
*out++ = *current++;
*out++ = *current++;
}
gfree(tmp);
} else if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) {
unsigned char *tmp = (unsigned char *)gmallocn(4 * length, sizeof(unsigned char));
lineTransform->doTransform(in, tmp, length);
unsigned char *current = tmp;
double c, m, y, k, c1, m1, y1, k1, r, g, b;
for (int i = 0; i < length; ++i) {
c = byteToDbl(*current++);
m = byteToDbl(*current++);
y = byteToDbl(*current++);
k = byteToDbl(*current++);
c1 = 1 - c;
m1 = 1 - m;
y1 = 1 - y;
k1 = 1 - k;
cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
*out++ = dblToByte(r);
*out++ = dblToByte(g);
*out++ = dblToByte(b);
}
gfree(tmp);
} else {
alt->getRGBLine(in, out, length);
}
#else
alt->getRGBLine(in, out, length);
#endif
}
void GfxICCBasedColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
{
#ifdef USE_CMS
if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) {
unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
lineTransform->doTransform(in, tmp, length);
unsigned char *current = tmp;
for (int i = 0; i < length; ++i) {
*out++ = *current++;
*out++ = *current++;
*out++ = *current++;
*out++ = 255;
}
gfree(tmp);
} else {
alt->getRGBXLine(in, out, length);
}
#else
alt->getRGBXLine(in, out, length);
#endif
}
void GfxICCBasedColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
{
#ifdef USE_CMS
if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) {
transform->doTransform(in, out, length);
} else if (lineTransform != nullptr && nComps != 4) {
GfxColorComp c, m, y, k;
unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
getRGBLine(in, tmp, length);
unsigned char *p = tmp;
for (int i = 0; i < length; i++) {
c = byteToCol(255 - *p++);
m = byteToCol(255 - *p++);
y = byteToCol(255 - *p++);
k = c;
if (m < k) {
k = m;
}
if (y < k) {
k = y;
}
*out++ = colToByte(c - k);
*out++ = colToByte(m - k);
*out++ = colToByte(y - k);
*out++ = colToByte(k);
}
gfree(tmp);
} else {
alt->getCMYKLine(in, out, length);
}
#else
alt->getCMYKLine(in, out, length);
#endif
}
void GfxICCBasedColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
{
#ifdef USE_CMS
if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) {
unsigned char *tmp = (unsigned char *)gmallocn(4 * length, sizeof(unsigned char));
transform->doTransform(in, tmp, length);
unsigned char *p = tmp;
for (int i = 0; i < length; i++) {
for (int j = 0; j < 4; j++) {
*out++ = *p++;
}
for (int j = 4; j < SPOT_NCOMPS + 4; j++) {
*out++ = 0;
}
}
gfree(tmp);
} else if (lineTransform != nullptr && nComps != 4) {
GfxColorComp c, m, y, k;
unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
getRGBLine(in, tmp, length);
unsigned char *p = tmp;
for (int i = 0; i < length; i++) {
for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
out[j] = 0;
}
c = byteToCol(255 - *p++);
m = byteToCol(255 - *p++);
y = byteToCol(255 - *p++);
k = c;
if (m < k) {
k = m;
}
if (y < k) {
k = y;
}
out[0] = colToByte(c - k);
out[1] = colToByte(m - k);
out[2] = colToByte(y - k);
out[3] = colToByte(k);
out += (SPOT_NCOMPS + 4);
}
gfree(tmp);
} else {
alt->getDeviceNLine(in, out, length);
}
#else
alt->getDeviceNLine(in, out, length);
#endif
}
void GfxICCBasedColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
{
#ifdef USE_CMS
if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
unsigned char in[gfxColorMaxComps];
unsigned char out[gfxColorMaxComps];
if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
} else {
for (int i = 0; i < nComps; i++) {
in[i] = colToByte(color->c[i]);
}
}
if (nComps <= 4) {
unsigned int key = 0;
for (int j = 0; j < nComps; j++) {
key = (key << 8) + in[j];
}
std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
if (it != cmsCache.end()) {
unsigned int value = it->second;
cmyk->c = byteToCol(value >> 24);
cmyk->m = byteToCol((value >> 16) & 0xff);
cmyk->y = byteToCol((value >> 8) & 0xff);
cmyk->k = byteToCol(value & 0xff);
return;
}
}
transform->doTransform(in, out, 1);
cmyk->c = byteToCol(out[0]);
cmyk->m = byteToCol(out[1]);
cmyk->y = byteToCol(out[2]);
cmyk->k = byteToCol(out[3]);
if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
unsigned int key = 0;
for (int j = 0; j < nComps; j++) {
key = (key << 8) + in[j];
}
unsigned int value = (out[0] << 24) + (out[1] << 16) + (out[2] << 8) + out[3];
cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
}
} else if (nComps != 4 && transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
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);
}
#else
alt->getCMYK(color, cmyk);
#endif
}
bool GfxICCBasedColorSpace::useGetRGBLine() const
{
#ifdef USE_CMS
return lineTransform != nullptr || alt->useGetRGBLine();
#else
return alt->useGetRGBLine();
#endif
}
bool GfxICCBasedColorSpace::useGetCMYKLine() const
{
#ifdef USE_CMS
return lineTransform != nullptr || alt->useGetCMYKLine();
#else
return alt->useGetCMYKLine();
#endif
}
bool GfxICCBasedColorSpace::useGetDeviceNLine() const
{
#ifdef USE_CMS
return lineTransform != nullptr || alt->useGetDeviceNLine();
#else
return alt->useGetDeviceNLine();
#endif
}
void GfxICCBasedColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
{
GfxCMYK cmyk;
clearGfxColor(deviceN);
getCMYK(color, &cmyk);
deviceN->c[0] = cmyk.c;
deviceN->c[1] = cmyk.m;
deviceN->c[2] = cmyk.y;
deviceN->c[3] = cmyk.k;
}
void GfxICCBasedColorSpace::getDefaultColor(GfxColor *color) const
{
int i;
for (i = 0; i < nComps; ++i) {
if (rangeMin[i] > 0) {
color->c[i] = dblToCol(rangeMin[i]);
} else if (rangeMax[i] < 0) {
color->c[i] = dblToCol(rangeMax[i]);
} else {
color->c[i] = 0;
}
}
}
void GfxICCBasedColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const
{
alt->getDefaultRanges(decodeLow, decodeRange, maxImgPixel);
#if 0
// this is nominally correct, but some PDF files don't set the
// correct ranges in the ICCBased dict
int i;
for (i = 0; i < nComps; ++i) {
decodeLow[i] = rangeMin[i];
decodeRange[i] = rangeMax[i] - rangeMin[i];
}
#endif
}
#ifdef USE_CMS
char *GfxICCBasedColorSpace::getPostScriptCSA()
{
# if LCMS_VERSION >= 2070
// The runtime version check of lcms2 is only available from release 2.7 upwards.
// The generation of the CSA code only works reliably for version 2.10 and upwards.
// Cf. the explanation in the corresponding lcms2 merge request [1], and the original mail thread [2].
// [1] https://github.com/mm2/Little-CMS/pull/214
// [2] https://sourceforge.net/p/lcms/mailman/message/33182987/
if (cmsGetEncodedCMMversion() < 2100) {
return nullptr;
}
int size;
if (psCSA) {
return psCSA;
}
if (!profile) {
error(errSyntaxWarning, -1, "profile is nullptr");
return nullptr;
}
void *rawprofile = profile.get();
size = cmsGetPostScriptCSA(cmsGetProfileContextID(rawprofile), rawprofile, getIntent(), 0, nullptr, 0);
if (size == 0) {
error(errSyntaxWarning, -1, "PostScript CSA is nullptr");
return nullptr;
}
psCSA = (char *)gmalloc(size + 1);
cmsGetPostScriptCSA(cmsGetProfileContextID(rawprofile), rawprofile, getIntent(), 0, psCSA, size);
psCSA[size] = 0;
// TODO REMOVE-ME-IN-THE-FUTURE
// until we can depend on https://github.com/mm2/Little-CMS/issues/223 being fixed
// lcms returns ps code with , instead of . for some locales. The lcms author says
// that there's no room for any , in the rest of the ps code, so replacing all the , with .
// is an "acceptable" workaround
for (int i = 0; i < size; ++i) {
if (psCSA[i] == ',') {
psCSA[i] = '.';
}
}
return psCSA;
# else
return nullptr;
# endif
}
#endif
//------------------------------------------------------------------------
// GfxIndexedColorSpace
//------------------------------------------------------------------------
GfxIndexedColorSpace::GfxIndexedColorSpace(std::unique_ptr<GfxColorSpace> &&baseA, int indexHighA) : base(std::move(baseA))
{
indexHigh = indexHighA;
lookup = (unsigned char *)gmallocn((indexHigh + 1) * base->getNComps(), sizeof(unsigned char));
overprintMask = base->getOverprintMask();
}
GfxIndexedColorSpace::~GfxIndexedColorSpace()
{
gfree(lookup);
}
std::unique_ptr<GfxColorSpace> GfxIndexedColorSpace::copy() const
{
auto cs = std::make_unique<GfxIndexedColorSpace>(base->copy(), indexHigh);
memcpy(cs->lookup, lookup, (indexHigh + 1) * base->getNComps() * sizeof(unsigned char));
return cs;
}
std::unique_ptr<GfxColorSpace> GfxIndexedColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion)
{
std::unique_ptr<GfxColorSpace> baseA;
int indexHighA;
Object obj1;
const char *s;
int i, j;
if (arr->getLength() != 4) {
error(errSyntaxWarning, -1, "Bad Indexed color space");
return nullptr;
}
obj1 = arr->get(1);
if (!(baseA = GfxColorSpace::parse(res, &obj1, out, state, recursion + 1))) {
error(errSyntaxWarning, -1, "Bad Indexed color space (base color space)");
return {};
}
obj1 = arr->get(2);
if (!obj1.isInt()) {
error(errSyntaxWarning, -1, "Bad Indexed color space (hival)");
return {};
}
indexHighA = obj1.getInt();
if (indexHighA < 0 || indexHighA > 255) {
// the PDF spec requires indexHigh to be in [0,255] -- allowing
// values larger than 255 creates a security hole: if nComps *
// indexHigh is greater than 2^31, the loop below may overwrite
// past the end of the array
int previousValue = indexHighA;
if (indexHighA < 0) {
indexHighA = 0;
} else {
indexHighA = 255;
}
error(errSyntaxWarning, -1, "Bad Indexed color space (invalid indexHigh value, was {0:d} using {1:d} to try to recover)", previousValue, indexHighA);
}
auto cs = std::make_unique<GfxIndexedColorSpace>(std::move(baseA), indexHighA);
obj1 = arr->get(3);
const int n = cs->getBase()->getNComps();
if (obj1.isStream()) {
obj1.streamReset();
for (i = 0; i <= indexHighA; ++i) {
const int readChars = obj1.streamGetChars(n, &cs->lookup[i * n]);
for (j = readChars; j < n; ++j) {
error(errSyntaxWarning, -1, "Bad Indexed color space (lookup table stream too short) padding with zeroes");
cs->lookup[i * n + j] = 0;
}
}
obj1.streamClose();
} else if (obj1.isString()) {
if (obj1.getString()->getLength() < (indexHighA + 1) * n) {
error(errSyntaxWarning, -1, "Bad Indexed color space (lookup table string too short)");
goto err3;
}
s = obj1.getString()->c_str();
for (i = 0; i <= indexHighA; ++i) {
for (j = 0; j < n; ++j) {
cs->lookup[i * n + j] = (unsigned char)*s++;
}
}
} else {
error(errSyntaxWarning, -1, "Bad Indexed color space (lookup table)");
goto err3;
}
return cs;
err3:
return {};
}
GfxColor *GfxIndexedColorSpace::mapColorToBase(const GfxColor *color, GfxColor *baseColor) const
{
unsigned char *p;
double low[gfxColorMaxComps], range[gfxColorMaxComps];
int n, i;
n = base->getNComps();
base->getDefaultRanges(low, range, indexHigh);
const int idx = (int)(colToDbl(color->c[0]) + 0.5) * n;
if (likely((idx + n - 1 < (indexHigh + 1) * base->getNComps()) && idx >= 0)) {
p = &lookup[idx];
for (i = 0; i < n; ++i) {
baseColor->c[i] = dblToCol(low[i] + (p[i] / 255.0) * range[i]);
}
} else {
for (i = 0; i < n; ++i) {
baseColor->c[i] = 0;
}
}
return baseColor;
}
void GfxIndexedColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
{
GfxColor color2;
base->getGray(mapColorToBase(color, &color2), gray);
}
void GfxIndexedColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
{
GfxColor color2;
base->getRGB(mapColorToBase(color, &color2), rgb);
}
void GfxIndexedColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
{
unsigned char *line;
int i, j, n;
n = base->getNComps();
line = (unsigned char *)gmallocn(length, n);
for (i = 0; i < length; i++) {
for (j = 0; j < n; j++) {
line[i * n + j] = lookup[in[i] * n + j];
}
}
base->getRGBLine(line, out, length);
gfree(line);
}
void GfxIndexedColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
{
unsigned char *line;
int i, j, n;
n = base->getNComps();
line = (unsigned char