summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--poppler/SplashOutputDev.cc51
-rw-r--r--poppler/SplashOutputDev.h11
-rw-r--r--splash/Splash.cc271
-rw-r--r--splash/SplashPattern.h7
-rw-r--r--splash/SplashTypes.h7
5 files changed, 246 insertions, 101 deletions
diff --git a/poppler/SplashOutputDev.cc b/poppler/SplashOutputDev.cc
index dbbd29a9..047b2d2d 100644
--- a/poppler/SplashOutputDev.cc
+++ b/poppler/SplashOutputDev.cc
@@ -82,9 +82,9 @@
static const double s_minLineWidth = 0.0;
static inline void convertGfxColor(SplashColorPtr dest,
- SplashColorMode colorMode,
- GfxColorSpace *colorSpace,
- GfxColor *src) {
+ const SplashColorMode colorMode,
+ const GfxColorSpace *colorSpace,
+ const GfxColor *src) {
SplashColor color;
GfxGray gray;
GfxRGB rgb;
@@ -134,9 +134,9 @@ static inline void convertGfxColor(SplashColorPtr dest,
// to ensure that everything is initialized.
static inline void convertGfxShortColor(SplashColorPtr dest,
- SplashColorMode colorMode,
- GfxColorSpace *colorSpace,
- GfxColor *src) {
+ const SplashColorMode colorMode,
+ const GfxColorSpace *colorSpace,
+ const GfxColor *src) {
switch (colorMode) {
case splashModeMono1:
case splashModeMono8:
@@ -194,21 +194,29 @@ SplashGouraudPattern::SplashGouraudPattern(bool bDirectColorTranslationA,
SplashGouraudPattern::~SplashGouraudPattern() {
}
+void SplashGouraudPattern::getNonParametrizedTriangle(int i, SplashColorMode mode, double *x0, double *y0, SplashColorPtr color0,
+ double *x1, double *y1, SplashColorPtr color1,
+ double *x2, double *y2, SplashColorPtr color2) {
+ GfxColor c0, c1, c2;
+ shading->getTriangle(i, x0, y0, &c0, x1, y1, &c1, x2, y2, &c2);
+
+ const GfxColorSpace* srcColorSpace = shading->getColorSpace();
+ convertGfxColor(color0, mode, srcColorSpace, &c0);
+ convertGfxColor(color1, mode, srcColorSpace, &c1);
+ convertGfxColor(color2, mode, srcColorSpace, &c2);
+}
+
+
void SplashGouraudPattern::getParameterizedColor(double colorinterp, SplashColorMode mode, SplashColorPtr dest) {
GfxColor src;
- GfxColorSpace* srcColorSpace = shading->getColorSpace();
- int colorComps = 3;
- if (mode == splashModeCMYK8)
- colorComps=4;
- else if (mode == splashModeDeviceN8)
- colorComps=4 + SPOT_NCOMPS;
-
shading->getParameterizedColor(colorinterp, &src);
if (bDirectColorTranslation) {
+ const int colorComps = splashColorModeNComps[mode];
for (int m = 0; m < colorComps; ++m)
dest[m] = colToByte(src.c[m]);
} else {
+ GfxColorSpace* srcColorSpace = shading->getColorSpace();
convertGfxShortColor(dest, mode, srcColorSpace, &src);
}
}
@@ -4540,17 +4548,12 @@ bool SplashOutputDev::gouraudTriangleShadedFill(GfxState *state, GfxGouraudTrian
break;
}
// restore vector antialias because we support it here
- if (shading->isParameterized()) {
- SplashGouraudColor *splashShading = new SplashGouraudPattern(bDirectColorTranslation, state, shading);
- bool vaa = getVectorAntialias();
- bool retVal = false;
- setVectorAntialias(true);
- retVal = splash->gouraudTriangleShadedFill(splashShading);
- setVectorAntialias(vaa);
- delete splashShading;
- return retVal;
- }
- return false;
+ SplashGouraudPattern splashShading(bDirectColorTranslation, state, shading);
+ const bool vaa = getVectorAntialias();
+ setVectorAntialias(true);
+ const bool retVal = splash->gouraudTriangleShadedFill(&splashShading);
+ setVectorAntialias(vaa);
+ return retVal;
}
bool SplashOutputDev::univariateShadedFill(GfxState *state, SplashUnivariatePattern *pattern, double tMin, double tMax) {
diff --git a/poppler/SplashOutputDev.h b/poppler/SplashOutputDev.h
index 7a670d1b..a42e724c 100644
--- a/poppler/SplashOutputDev.h
+++ b/poppler/SplashOutputDev.h
@@ -147,11 +147,16 @@ public:
bool isParameterized() override { return shading->isParameterized(); }
int getNTriangles() override { return shading->getNTriangles(); }
- void getTriangle(int i, double *x0, double *y0, double *color0,
- double *x1, double *y1, double *color1,
- double *x2, double *y2, double *color2) override
+ void getParametrizedTriangle(int i, double *x0, double *y0, double *color0,
+ double *x1, double *y1, double *color1,
+ double *x2, double *y2, double *color2) override
{ shading->getTriangle(i, x0, y0, color0, x1, y1, color1, x2, y2, color2); }
+ void getNonParametrizedTriangle(int i, SplashColorMode mode,
+ double *x0, double *y0, SplashColorPtr color0,
+ double *x1, double *y1, SplashColorPtr color1,
+ double *x2, double *y2, SplashColorPtr color2) override;
+
void getParameterizedColor(double colorinterp, SplashColorMode mode, SplashColorPtr dest) override;
private:
diff --git a/splash/Splash.cc b/splash/Splash.cc
index 3d4e6177..70584ec4 100644
--- a/splash/Splash.cc
+++ b/splash/Splash.cc
@@ -2385,7 +2385,7 @@ SplashError Splash::fillWithPattern(SplashPath *path, bool eo,
SplashPipe pipe = {};
int xMinI, yMinI, xMaxI, yMaxI, x0, x1, y;
SplashClipResult clipRes, clipRes2;
- bool adjustLine = false;
+ bool adjustLine = false;
int linePosI = 0;
if (path->length == 0) {
@@ -5381,62 +5381,23 @@ bool Splash::gouraudTriangleShadedFill(SplashGouraudColor *shading)
int x[3] = {0, 0, 0};
int y[3] = {0, 0, 0};
double xt=0., xa=0., yt=0.;
- double ca=0., ct=0.;
-
- // triangle interpolation:
- //
- double scanLimitMapL[2] = {0., 0.};
- double scanLimitMapR[2] = {0., 0.};
- double scanColorMapL[2] = {0., 0.};
- double scanColorMapR[2] = {0., 0.};
- double scanColorMap[2] = {0., 0.};
- int scanEdgeL[2] = { 0, 0 };
- int scanEdgeR[2] = { 0, 0 };
- bool hasFurtherSegment = false;
-
- int scanLineOff = 0;
- int bitmapOff = 0;
- int scanLimitR = 0, scanLimitL = 0;
-
- int bitmapWidth = bitmap->getWidth();
+
+ const int bitmapWidth = bitmap->getWidth();
SplashClip* clip = getClip();
SplashBitmap *blitTarget = bitmap;
SplashColorPtr bitmapData = bitmap->getDataPtr();
- int bitmapOffLimit = bitmap->getHeight() * bitmap->getRowSize();
+ const int bitmapOffLimit = bitmap->getHeight() * bitmap->getRowSize();
SplashColorPtr bitmapAlpha = bitmap->getAlphaPtr();
- SplashColorPtr cur = nullptr;
SplashCoord* userToCanvasMatrix = getMatrix();
- SplashColorMode bitmapMode = bitmap->getMode();
+ const SplashColorMode bitmapMode = bitmap->getMode();
bool hasAlpha = (bitmapAlpha != nullptr);
- int rowSize = bitmap->getRowSize();
- int colorComps = 0;
- switch (bitmapMode) {
- case splashModeMono1:
- break;
- case splashModeMono8:
- colorComps=1;
- break;
- case splashModeRGB8:
- colorComps=3;
- break;
- case splashModeBGR8:
- colorComps=3;
- break;
- case splashModeXBGR8:
- colorComps=4;
- break;
- case splashModeCMYK8:
- colorComps=4;
- break;
- case splashModeDeviceN8:
- colorComps=SPOT_NCOMPS+4;
- break;
- }
+ const int rowSize = bitmap->getRowSize();
+ const int colorComps = splashColorModeNComps[bitmapMode];
SplashPipe pipe;
SplashColor cSrcVal;
- pipeInit(&pipe, 0, 0, nullptr, cSrcVal, (unsigned char)splashRound(state->strokeAlpha * 255), false, false);
+ pipeInit(&pipe, 0, 0, nullptr, cSrcVal, (unsigned char)splashRound(state->fillAlpha * 255), false, false);
if (vectorAntialias) {
if (aaBuf == nullptr)
@@ -5457,7 +5418,7 @@ bool Splash::gouraudTriangleShadedFill(SplashGouraudColor *shading)
// - the final step, is performed using a SplashPipe:
// - assign the actual color into cSrcVal: pipe uses cSrcVal by reference
// - invoke drawPixel(&pipe,X,Y,bNoClip);
- bool bDirectBlit = vectorAntialias ? false : pipe.noTransparency && !state->blendFunc;
+ const bool bDirectBlit = vectorAntialias ? false : pipe.noTransparency && !state->blendFunc && !shading->isParameterized();
if (!bDirectBlit) {
blitTarget = new SplashBitmap(bitmap->getWidth(),
bitmap->getHeight(),
@@ -5469,7 +5430,7 @@ bool Splash::gouraudTriangleShadedFill(SplashGouraudColor *shading)
bitmapAlpha = blitTarget->getAlphaPtr();
// initialisation seems to be necessary:
- int S = bitmap->getWidth() * bitmap->getHeight();
+ const int S = bitmap->getWidth() * bitmap->getHeight();
for (int i = 0; i < S; ++i)
bitmapAlpha[i] = 0;
hasAlpha = true;
@@ -5477,10 +5438,15 @@ bool Splash::gouraudTriangleShadedFill(SplashGouraudColor *shading)
if (shading->isParameterized()) {
double color[3];
- double colorinterp;
+ double scanLimitMapL[2] = {0., 0.};
+ double scanLimitMapR[2] = {0., 0.};
+ double scanColorMapL[2] = {0., 0.};
+ double scanColorMapR[2] = {0., 0.};
+ int scanEdgeL[2] = { 0, 0 };
+ int scanEdgeR[2] = { 0, 0 };
for (int i = 0; i < shading->getNTriangles(); ++i) {
- shading->getTriangle(i,
+ shading->getParametrizedTriangle(i,
xdbl + 0, ydbl + 0, color + 0,
xdbl + 1, ydbl + 1, color + 1,
xdbl + 2, ydbl + 2, color + 2);
@@ -5504,9 +5470,9 @@ bool Splash::gouraudTriangleShadedFill(SplashGouraudColor *shading)
// first two are sorted.
assert(y[0] <= y[1]);
if (y[1] > y[2]) {
- int tmpX = x[2];
- int tmpY = y[2];
- double tmpC = color[2];
+ const int tmpX = x[2];
+ const int tmpY = y[2];
+ const double tmpC = color[2];
x[2] = x[1]; y[2] = y[1]; color[2] = color[1];
if (y[0] > tmpY) {
@@ -5578,8 +5544,8 @@ bool Splash::gouraudTriangleShadedFill(SplashGouraudColor *shading)
scanColorMapR[0] = (color[scanEdgeR[1]] - color[scanEdgeR[0]]) / (y[scanEdgeR[1]] - y[scanEdgeR[0]]);
scanColorMapR[1] = color[scanEdgeR[0]] - y[scanEdgeR[0]] * scanColorMapR[0];
- hasFurtherSegment = (y[1] < y[2]);
- scanLineOff = y[0] * rowSize;
+ bool hasFurtherSegment = (y[1] < y[2]);
+ int scanLineOff = y[0] * rowSize;
for (int Y = y[0]; Y <= y[2]; ++Y, scanLineOff += rowSize) {
if (hasFurtherSegment && Y == y[1]) {
@@ -5614,34 +5580,34 @@ bool Splash::gouraudTriangleShadedFill(SplashGouraudColor *shading)
xa = yt * scanLimitMapL[0] + scanLimitMapL[1];
xt = yt * scanLimitMapR[0] + scanLimitMapR[1];
- ca = yt * scanColorMapL[0] + scanColorMapL[1];
- ct = yt * scanColorMapR[0] + scanColorMapR[1];
+ const double ca = yt * scanColorMapL[0] + scanColorMapL[1];
+ const double ct = yt * scanColorMapR[0] + scanColorMapR[1];
- scanLimitL = splashRound(xa);
- scanLimitR = splashRound(xt);
+ const int scanLimitL = splashRound(xa);
+ const int scanLimitR = splashRound(xt);
// Ok. Now: init the color interpolation depending on the X
// coordinate inside of the current scanline:
- scanColorMap[0] = (scanLimitR == scanLimitL) ? 0. : ((ct - ca) / (scanLimitR - scanLimitL));
- scanColorMap[1] = ca - scanLimitL * scanColorMap[0];
+ const double scanColorMap0 = (scanLimitR == scanLimitL) ? 0. : ((ct - ca) / (scanLimitR - scanLimitL));
+ const double scanColorMap1 = ca - scanLimitL * scanColorMap0;
// handled by clipping:
// assert( scanLimitL >= 0 && scanLimitR < bitmap->getWidth() );
assert(scanLimitL <= scanLimitR || abs(scanLimitL - scanLimitR) <= 2); // allow rounding inaccuracies
assert(scanLineOff == Y * rowSize);
- colorinterp = scanColorMap[0] * scanLimitL + scanColorMap[1];
+ double colorinterp = scanColorMap0 * scanLimitL + scanColorMap1;
- bitmapOff = scanLineOff + scanLimitL * colorComps;
+ int bitmapOff = scanLineOff + scanLimitL * colorComps;
if (likely(bitmapOff >= 0)) {
- for (int X = scanLimitL; X <= scanLimitR && bitmapOff + colorComps <= bitmapOffLimit; ++X, colorinterp += scanColorMap[0], bitmapOff += colorComps) {
+ for (int X = scanLimitL; X <= scanLimitR && bitmapOff + colorComps <= bitmapOffLimit; ++X, colorinterp += scanColorMap0, bitmapOff += colorComps) {
// FIXME : standard rectangular clipping can be done for a
// complete scanline which is faster
// --> see SplashClip and its methods
if (!clip->test(X, Y))
continue;
- assert(fabs(colorinterp - (scanColorMap[0] * X + scanColorMap[1])) < 1e-10);
+ assert(fabs(colorinterp - (scanColorMap0 * X + scanColorMap1)) < 1e-10);
assert(bitmapOff == Y * rowSize + colorComps * X && scanLineOff == Y * rowSize);
shading->getParameterizedColor(colorinterp, bitmapMode, &bitmapData[bitmapOff]);
@@ -5656,24 +5622,183 @@ bool Splash::gouraudTriangleShadedFill(SplashGouraudColor *shading)
}
}
} else {
- if (!bDirectBlit) {
- delete blitTarget;
+ SplashColor color, auxColor1, auxColor2;
+ double scanLimitMapL[2] = {0., 0.};
+ double scanLimitMapR[2] = {0., 0.};
+ int scanEdgeL[2] = { 0, 0 };
+ int scanEdgeR[2] = { 0, 0 };
+
+ for (int i = 0; i < shading->getNTriangles(); ++i) {
+ // Sadly this current algorithm only supports shadings where the three triangle vertices have the same color
+ shading->getNonParametrizedTriangle(i, bitmapMode,
+ xdbl + 0, ydbl + 0, (SplashColorPtr)&color,
+ xdbl + 1, ydbl + 1, (SplashColorPtr)&auxColor1,
+ xdbl + 2, ydbl + 2, (SplashColorPtr)&auxColor2);
+ if (!splashColorEqual(color, auxColor1) ||
+ !splashColorEqual(color, auxColor2))
+ {
+ delete blitTarget;
+ return false;
+ }
+ for (int m = 0; m < 3; ++m) {
+ xt = xdbl[m] * (double)userToCanvasMatrix[0] + ydbl[m] * (double)userToCanvasMatrix[2] + (double)userToCanvasMatrix[4];
+ yt = xdbl[m] * (double)userToCanvasMatrix[1] + ydbl[m] * (double)userToCanvasMatrix[3] + (double)userToCanvasMatrix[5];
+ xdbl[m] = xt;
+ ydbl[m] = yt;
+ // we operate on scanlines which are integer offsets into the
+ // raster image. The double offsets are of no use here.
+ x[m] = splashRound(xt);
+ y[m] = splashRound(yt);
+ }
+ // sort according to y coordinate to simplify sweep through scanlines:
+ // INSERTION SORT.
+ if (y[0] > y[1]) {
+ Guswap(x[0], x[1]);
+ Guswap(y[0], y[1]);
+ }
+ // first two are sorted.
+ assert(y[0] <= y[1]);
+ if (y[1] > y[2]) {
+ const int tmpX = x[2];
+ const int tmpY = y[2];
+ x[2] = x[1]; y[2] = y[1];
+
+ if (y[0] > tmpY) {
+ x[1] = x[0]; y[1] = y[0];
+ x[0] = tmpX; y[0] = tmpY;
+ } else {
+ x[1] = tmpX; y[1] = tmpY;
+ }
+ }
+ // first three are sorted
+ assert(y[0] <= y[1]);
+ assert(y[1] <= y[2]);
+ /////
+
+ // this here is det( T ) == 0
+ // where T is the matrix to map to barycentric coordinates.
+ if ((x[0] - x[2]) * (y[1] - y[2]) - (x[1] - x[2]) * (y[0] - y[2]) == 0)
+ continue; // degenerate triangle.
+
+ // this here initialises the scanline generation.
+ // We start with low Y coordinates and sweep up to the large Y
+ // coordinates.
+ //
+ // scanEdgeL[m] in {0,1,2} m=0,1
+ // scanEdgeR[m] in {0,1,2} m=0,1
+ //
+ // are the two edges between which scanlines are (currently)
+ // sweeped. The values {0,1,2} are indices into 'x' and 'y'.
+ // scanEdgeL[0] = 0 means: the left scan edge has (x[0],y[0]) as vertex.
+ //
+ scanEdgeL[0] = 0;
+ scanEdgeR[0] = 0;
+ if (y[0] == y[1]) {
+ scanEdgeL[0] = 1;
+ scanEdgeL[1] = scanEdgeR[1] = 2;
+
+ } else {
+ scanEdgeL[1] = 1; scanEdgeR[1] = 2;
+ }
+ assert(y[scanEdgeL[0]] < y[scanEdgeL[1]]);
+ assert(y[scanEdgeR[0]] < y[scanEdgeR[1]]);
+
+ // Ok. Now prepare the linear maps which map the y coordinate of
+ // the current scanline to the corresponding LEFT and RIGHT x
+ // coordinate (which define the scanline).
+ scanLimitMapL[0] = double(x[scanEdgeL[1]] - x[scanEdgeL[0]]) / (y[scanEdgeL[1]] - y[scanEdgeL[0]]);
+ scanLimitMapL[1] = x[scanEdgeL[0]] - y[scanEdgeL[0]] * scanLimitMapL[0];
+ scanLimitMapR[0] = double(x[scanEdgeR[1]] - x[scanEdgeR[0]]) / (y[scanEdgeR[1]] - y[scanEdgeR[0]]);
+ scanLimitMapR[1] = x[scanEdgeR[0]] - y[scanEdgeR[0]] * scanLimitMapR[0];
+
+ xa = y[1] * scanLimitMapL[0] + scanLimitMapL[1];
+ xt = y[1] * scanLimitMapR[0] + scanLimitMapR[1];
+ if (xa > xt) {
+ // I have "left" is to the right of "right".
+ // Exchange sides!
+ Guswap(scanEdgeL[0], scanEdgeR[0]);
+ Guswap(scanEdgeL[1], scanEdgeR[1]);
+ Guswap(scanLimitMapL[0], scanLimitMapR[0]);
+ Guswap(scanLimitMapL[1], scanLimitMapR[1]);
+ // FIXME I'm sure there is a more efficient way to check this.
+ }
+
+ bool hasFurtherSegment = (y[1] < y[2]);
+ int scanLineOff = y[0] * rowSize;
+
+ for (int Y = y[0]; Y <= y[2]; ++Y, scanLineOff += rowSize) {
+ if (hasFurtherSegment && Y == y[1]) {
+ // SWEEP EVENT: we encountered the next segment.
+ //
+ // switch to next segment, either at left end or at right
+ // end:
+ if (scanEdgeL[1] == 1) {
+ scanEdgeL[0] = 1;
+ scanEdgeL[1] = 2;
+ scanLimitMapL[0] = double(x[scanEdgeL[1]] - x[scanEdgeL[0]]) / (y[scanEdgeL[1]] - y[scanEdgeL[0]]);
+ scanLimitMapL[1] = x[scanEdgeL[0]] - y[scanEdgeL[0]] * scanLimitMapL[0];
+ } else if (scanEdgeR[1] == 1) {
+ scanEdgeR[0] = 1;
+ scanEdgeR[1] = 2;
+ scanLimitMapR[0] = double(x[scanEdgeR[1]] - x[scanEdgeR[0]]) / (y[scanEdgeR[1]] - y[scanEdgeR[0]]);
+ scanLimitMapR[1] = x[scanEdgeR[0]] - y[scanEdgeR[0]] * scanLimitMapR[0];
+ }
+ assert( y[scanEdgeL[0]] < y[scanEdgeL[1]] );
+ assert( y[scanEdgeR[0]] < y[scanEdgeR[1]] );
+ hasFurtherSegment = false;
+ }
+
+ yt = Y;
+
+ xa = yt * scanLimitMapL[0] + scanLimitMapL[1];
+ xt = yt * scanLimitMapR[0] + scanLimitMapR[1];
+
+ const int scanLimitL = splashRound(xa);
+ const int scanLimitR = splashRound(xt);
+
+ // handled by clipping:
+ // assert( scanLimitL >= 0 && scanLimitR < bitmap->getWidth() );
+ assert(scanLimitL <= scanLimitR || abs(scanLimitL - scanLimitR) <= 2); // allow rounding inaccuracies
+ assert(scanLineOff == Y * rowSize);
+
+ int bitmapOff = scanLineOff + scanLimitL * colorComps;
+ if (likely(bitmapOff >= 0)) {
+ for (int X = scanLimitL; X <= scanLimitR && bitmapOff + colorComps <= bitmapOffLimit; ++X, bitmapOff += colorComps) {
+ // FIXME : standard rectangular clipping can be done for a
+ // complete scanline which is faster
+ // --> see SplashClip and its methods
+ if (!clip->test(X, Y))
+ continue;
+
+ assert(bitmapOff == Y * rowSize + colorComps * X && scanLineOff == Y * rowSize);
+
+ for (int k = 0; k < colorComps; ++k) {
+ bitmapData[bitmapOff + k] = color[k];
+ }
+
+ // make the shading visible.
+ // Note that opacity is handled by the bDirectBlit stuff, see
+ // above for comments and below for implementation.
+ if (hasAlpha)
+ bitmapAlpha[Y * bitmapWidth + X] = 255;
+ }
+ }
+ }
}
- return false;
}
if (!bDirectBlit) {
// ok. Finalize the stuff by blitting the shading into the final
// geometry, this time respecting the rendering pipe.
- int W = blitTarget->getWidth();
- int H = blitTarget->getHeight();
- cur = cSrcVal;
+ const int W = blitTarget->getWidth();
+ const int H = blitTarget->getHeight();
+ SplashColorPtr cur = cSrcVal;
for (int X = 0; X < W; ++X) {
for (int Y = 0; Y < H; ++Y) {
if (!bitmapAlpha[Y * bitmapWidth + X])
continue; // draw only parts of the shading!
- bitmapOff = Y * rowSize + colorComps * X;
+ const int bitmapOff = Y * rowSize + colorComps * X;
for (int m = 0; m < colorComps; ++m)
cur[m] = bitmapData[bitmapOff + m];
diff --git a/splash/SplashPattern.h b/splash/SplashPattern.h
index ce7eb446..1545c097 100644
--- a/splash/SplashPattern.h
+++ b/splash/SplashPattern.h
@@ -94,10 +94,15 @@ public:
virtual int getNTriangles() = 0;
- virtual void getTriangle(int i, double *x0, double *y0, double *color0,
+ virtual void getParametrizedTriangle(int i, double *x0, double *y0, double *color0,
double *x1, double *y1, double *color1,
double *x2, double *y2, double *color2) = 0;
+ virtual void getNonParametrizedTriangle(int i, SplashColorMode mode,
+ double *x0, double *y0, SplashColorPtr color0,
+ double *x1, double *y1, SplashColorPtr color1,
+ double *x2, double *y2, SplashColorPtr color2) = 0;
+
virtual void getParameterizedColor(double t, SplashColorMode mode, SplashColorPtr c) = 0;
};
diff --git a/splash/SplashTypes.h b/splash/SplashTypes.h
index 7500f588..c036a56e 100644
--- a/splash/SplashTypes.h
+++ b/splash/SplashTypes.h
@@ -128,6 +128,13 @@ static inline void splashColorCopy(SplashColorPtr dest, SplashColorConstPtr src)
dest[i] = src[i];
}
+static inline bool splashColorEqual(SplashColorConstPtr dest, SplashColorConstPtr src) {
+ for (int i = 0; i < SPOT_NCOMPS + 4; i++)
+ if (dest[i] != src[i])
+ return false;
+ return true;
+}
+
static inline void splashColorXor(SplashColorPtr dest, SplashColorConstPtr src) {
dest[0] ^= src[0];
dest[1] ^= src[1];