//======================================================================== // // Splash.cpp // //======================================================================== #include #ifdef USE_GCC_PRAGMAS #pragma implementation #endif #include #include #include #include "gmem.h" #include "SplashErrorCodes.h" #include "SplashMath.h" #include "SplashBitmap.h" #include "SplashState.h" #include "SplashPath.h" #include "SplashXPath.h" #include "SplashXPathScanner.h" #include "SplashPattern.h" #include "SplashScreen.h" #include "SplashFont.h" #include "SplashGlyphBitmap.h" #include "Splash.h" //------------------------------------------------------------------------ // distance of Bezier control point from center for circle approximation // = (4 * (sqrt(2) - 1) / 3) * r #define bezierCircle ((SplashCoord)0.55228475) #define bezierCircle2 ((SplashCoord)(0.5 * 0.55228475)) // Divide a 16-bit value (in [0, 255*255]) by 255, returning an 8-bit result. static inline Guchar div255(int x) { return (Guchar)((x + (x >> 8) + 0x80) >> 8); } //------------------------------------------------------------------------ // SplashPipe //------------------------------------------------------------------------ #define splashPipeMaxStages 9 struct SplashPipe { // pixel coordinates int x, y; // source pattern SplashPattern *pattern; // source alpha and color SplashCoord aInput; GBool usesShape; Guchar aSrc; SplashColorPtr cSrc; SplashColor cSrcVal; // non-isolated group alpha0 Guchar *alpha0Ptr; // soft mask SplashColorPtr softMaskPtr; // destination alpha and color SplashColorPtr destColorPtr; int destColorMask; Guchar *destAlphaPtr; // shape SplashCoord shape; // result alpha and color GBool noTransparency; SplashPipeResultColorCtrl resultColorCtrl; // non-isolated group correction int nonIsolatedGroup; }; SplashPipeResultColorCtrl Splash::pipeResultColorNoAlphaBlend[] = { splashPipeResultColorNoAlphaBlendMono, splashPipeResultColorNoAlphaBlendMono, splashPipeResultColorNoAlphaBlendRGB, splashPipeResultColorNoAlphaBlendRGB #if SPLASH_CMYK , splashPipeResultColorNoAlphaBlendCMYK #endif }; SplashPipeResultColorCtrl Splash::pipeResultColorAlphaNoBlend[] = { splashPipeResultColorAlphaNoBlendMono, splashPipeResultColorAlphaNoBlendMono, splashPipeResultColorAlphaNoBlendRGB, splashPipeResultColorAlphaNoBlendRGB #if SPLASH_CMYK , splashPipeResultColorAlphaNoBlendCMYK #endif }; SplashPipeResultColorCtrl Splash::pipeResultColorAlphaBlend[] = { splashPipeResultColorAlphaBlendMono, splashPipeResultColorAlphaBlendMono, splashPipeResultColorAlphaBlendRGB, splashPipeResultColorAlphaBlendRGB #if SPLASH_CMYK , splashPipeResultColorAlphaBlendCMYK #endif }; //------------------------------------------------------------------------ static void blendXor(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { int i; for (i = 0; i < splashColorModeNComps[cm]; ++i) { blend[i] = src[i] ^ dest[i]; } } //------------------------------------------------------------------------ // modified region //------------------------------------------------------------------------ void Splash::clearModRegion() { modXMin = bitmap->getWidth(); modYMin = bitmap->getHeight(); modXMax = -1; modYMax = -1; } inline void Splash::updateModX(int x) { if (x < modXMin) { modXMin = x; } if (x > modXMax) { modXMax = x; } } inline void Splash::updateModY(int y) { if (y < modYMin) { modYMin = y; } if (y > modYMax) { modYMax = y; } } //------------------------------------------------------------------------ // pipeline //------------------------------------------------------------------------ inline void Splash::pipeInit(SplashPipe *pipe, int x, int y, SplashPattern *pattern, SplashColorPtr cSrc, SplashCoord aInput, GBool usesShape, GBool nonIsolatedGroup) { pipeSetXY(pipe, x, y); pipe->pattern = NULL; // source color if (pattern) { if (pattern->isStatic()) { pattern->getColor(x, y, pipe->cSrcVal); } else { pipe->pattern = pattern; } pipe->cSrc = pipe->cSrcVal; } else { pipe->cSrc = cSrc; } // source alpha pipe->aInput = aInput; if (!state->softMask) { if (usesShape) { pipe->aInput *= 255; } else { pipe->aSrc = (Guchar)splashRound(pipe->aInput * 255); } } pipe->usesShape = usesShape; // result alpha if (aInput == 1 && !state->softMask && !usesShape && !state->inNonIsolatedGroup) { pipe->noTransparency = gTrue; } else { pipe->noTransparency = gFalse; } // result color if (pipe->noTransparency) { // the !state->blendFunc case is handled separately in pipeRun pipe->resultColorCtrl = pipeResultColorNoAlphaBlend[bitmap->mode]; } else if (!state->blendFunc) { pipe->resultColorCtrl = pipeResultColorAlphaNoBlend[bitmap->mode]; } else { pipe->resultColorCtrl = pipeResultColorAlphaBlend[bitmap->mode]; } // non-isolated group correction if (nonIsolatedGroup) { pipe->nonIsolatedGroup = splashColorModeNComps[bitmap->mode]; } else { pipe->nonIsolatedGroup = 0; } } inline void Splash::pipeRun(SplashPipe *pipe) { Guchar aSrc, aDest, alpha2, alpha0, aResult; SplashColor cDest, cBlend; Guchar cResult0, cResult1, cResult2, cResult3; //----- source color // static pattern: handled in pipeInit // fixed color: handled in pipeInit // dynamic pattern if (pipe->pattern) { pipe->pattern->getColor(pipe->x, pipe->y, pipe->cSrcVal); } if (pipe->noTransparency && !state->blendFunc) { //----- write destination pixel switch (bitmap->mode) { case splashModeMono1: cResult0 = pipe->cSrc[0]; if (state->screen->test(pipe->x, pipe->y, cResult0)) { *pipe->destColorPtr |= pipe->destColorMask; } else { *pipe->destColorPtr &= ~pipe->destColorMask; } if (!(pipe->destColorMask >>= 1)) { pipe->destColorMask = 0x80; ++pipe->destColorPtr; } break; case splashModeMono8: *pipe->destColorPtr++ = pipe->cSrc[0]; break; case splashModeRGB8: *pipe->destColorPtr++ = pipe->cSrc[0]; *pipe->destColorPtr++ = pipe->cSrc[1]; *pipe->destColorPtr++ = pipe->cSrc[2]; break; case splashModeBGR8: *pipe->destColorPtr++ = pipe->cSrc[2]; *pipe->destColorPtr++ = pipe->cSrc[1]; *pipe->destColorPtr++ = pipe->cSrc[0]; break; #if SPLASH_CMYK case splashModeCMYK8: *pipe->destColorPtr++ = pipe->cSrc[0]; *pipe->destColorPtr++ = pipe->cSrc[1]; *pipe->destColorPtr++ = pipe->cSrc[2]; *pipe->destColorPtr++ = pipe->cSrc[3]; break; #endif } if (pipe->destAlphaPtr) { *pipe->destAlphaPtr++ = 255; } } else { //----- read destination pixel switch (bitmap->mode) { case splashModeMono1: cDest[0] = (*pipe->destColorPtr & pipe->destColorMask) ? 0xff : 0x00; break; case splashModeMono8: cDest[0] = *pipe->destColorPtr; break; case splashModeRGB8: cDest[0] = pipe->destColorPtr[0]; cDest[1] = pipe->destColorPtr[1]; cDest[2] = pipe->destColorPtr[2]; break; case splashModeBGR8: cDest[0] = pipe->destColorPtr[2]; cDest[1] = pipe->destColorPtr[1]; cDest[2] = pipe->destColorPtr[0]; break; #if SPLASH_CMYK case splashModeCMYK8: cDest[0] = pipe->destColorPtr[0]; cDest[1] = pipe->destColorPtr[1]; cDest[2] = pipe->destColorPtr[2]; cDest[3] = pipe->destColorPtr[3]; break; #endif } if (pipe->destAlphaPtr) { aDest = *pipe->destAlphaPtr; } else { aDest = 0xff; } //----- blend function if (state->blendFunc) { (*state->blendFunc)(pipe->cSrc, cDest, cBlend, bitmap->mode); } //----- source alpha if (state->softMask) { if (pipe->usesShape) { aSrc = (Guchar)splashRound(pipe->aInput * *pipe->softMaskPtr++ * pipe->shape); } else { aSrc = (Guchar)splashRound(pipe->aInput * *pipe->softMaskPtr++); } } else if (pipe->usesShape) { // pipe->aInput is premultiplied by 255 in pipeInit aSrc = (Guchar)splashRound(pipe->aInput * pipe->shape); } else { // precomputed in pipeInit aSrc = pipe->aSrc; } //----- result alpha and non-isolated group element correction if (pipe->noTransparency) { alpha2 = aResult = 255; } else { aResult = aSrc + aDest - div255(aSrc * aDest); if (pipe->alpha0Ptr) { alpha0 = *pipe->alpha0Ptr++; alpha2 = aResult + alpha0 - div255(aResult * alpha0); } else { alpha2 = aResult; } } //----- result color cResult0 = cResult1 = cResult2 = cResult3 = 0; // make gcc happy switch (pipe->resultColorCtrl) { #if SPLASH_CMYK case splashPipeResultColorNoAlphaBlendCMYK: cResult3 = div255((255 - aDest) * pipe->cSrc[3] + aDest * cBlend[3]); #endif case splashPipeResultColorNoAlphaBlendRGB: cResult2 = div255((255 - aDest) * pipe->cSrc[2] + aDest * cBlend[2]); cResult1 = div255((255 - aDest) * pipe->cSrc[1] + aDest * cBlend[1]); case splashPipeResultColorNoAlphaBlendMono: cResult0 = div255((255 - aDest) * pipe->cSrc[0] + aDest * cBlend[0]); break; case splashPipeResultColorAlphaNoBlendMono: if (alpha2 == 0) { cResult0 = 0; } else { cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] + aSrc * pipe->cSrc[0]) / alpha2); } break; case splashPipeResultColorAlphaNoBlendRGB: if (alpha2 == 0) { cResult0 = 0; cResult1 = 0; cResult2 = 0; } else { cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] + aSrc * pipe->cSrc[0]) / alpha2); cResult1 = (Guchar)(((alpha2 - aSrc) * cDest[1] + aSrc * pipe->cSrc[1]) / alpha2); cResult2 = (Guchar)(((alpha2 - aSrc) * cDest[2] + aSrc * pipe->cSrc[2]) / alpha2); } break; #if SPLASH_CMYK case splashPipeResultColorAlphaNoBlendCMYK: if (alpha2 == 0) { cResult0 = 0; cResult1 = 0; cResult2 = 0; cResult3 = 0; } else { cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] + aSrc * pipe->cSrc[0]) / alpha2); cResult1 = (Guchar)(((alpha2 - aSrc) * cDest[1] + aSrc * pipe->cSrc[1]) / alpha2); cResult2 = (Guchar)(((alpha2 - aSrc) * cDest[2] + aSrc * pipe->cSrc[2]) / alpha2); cResult3 = (Guchar)(((alpha2 - aSrc) * cDest[3] + aSrc * pipe->cSrc[3]) / alpha2); } break; #endif case splashPipeResultColorAlphaBlendMono: if (alpha2 == 0) { cResult0 = 0; } else { cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] + aSrc * ((255 - aDest) * pipe->cSrc[0] + aDest * cBlend[0]) / 255) / alpha2); } break; case splashPipeResultColorAlphaBlendRGB: if (alpha2 == 0) { cResult0 = 0; cResult1 = 0; cResult2 = 0; } else { cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] + aSrc * ((255 - aDest) * pipe->cSrc[0] + aDest * cBlend[0]) / 255) / alpha2); cResult1 = (Guchar)(((alpha2 - aSrc) * cDest[1] + aSrc * ((255 - aDest) * pipe->cSrc[1] + aDest * cBlend[1]) / 255) / alpha2); cResult2 = (Guchar)(((alpha2 - aSrc) * cDest[2] + aSrc * ((255 - aDest) * pipe->cSrc[2] + aDest * cBlend[2]) / 255) / alpha2); } break; #if SPLASH_CMYK case splashPipeResultColorAlphaBlendCMYK: if (alpha2 == 0) { cResult0 = 0; cResult1 = 0; cResult2 = 0; cResult3 = 0; } else { cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] + aSrc * ((255 - aDest) * pipe->cSrc[0] + aDest * cBlend[0]) / 255) / alpha2); cResult1 = (Guchar)(((alpha2 - aSrc) * cDest[1] + aSrc * ((255 - aDest) * pipe->cSrc[1] + aDest * cBlend[1]) / 255) / alpha2); cResult2 = (Guchar)(((alpha2 - aSrc) * cDest[2] + aSrc * ((255 - aDest) * pipe->cSrc[2] + aDest * cBlend[2]) / 255) / alpha2); cResult3 = (Guchar)(((alpha2 - aSrc) * cDest[3] + aSrc * ((255 - aDest) * pipe->cSrc[3] + aDest * cBlend[3]) / 255) / alpha2); } break; #endif } //----- non-isolated group correction if (aResult != 0) { switch (pipe->nonIsolatedGroup) { #if SPLASH_CMYK case 4: cResult3 += (cResult3 - cDest[3]) * aDest * (255 - aResult) / (255 * aResult); #endif case 3: cResult2 += (cResult2 - cDest[2]) * aDest * (255 - aResult) / (255 * aResult); cResult1 += (cResult1 - cDest[1]) * aDest * (255 - aResult) / (255 * aResult); case 1: cResult0 += (cResult0 - cDest[0]) * aDest * (255 - aResult) / (255 * aResult); case 0: break; } } //----- write destination pixel switch (bitmap->mode) { case splashModeMono1: if (state->screen->test(pipe->x, pipe->y, cResult0)) { *pipe->destColorPtr |= pipe->destColorMask; } else { *pipe->destColorPtr &= ~pipe->destColorMask; } if (!(pipe->destColorMask >>= 1)) { pipe->destColorMask = 0x80; ++pipe->destColorPtr; } break; case splashModeMono8: *pipe->destColorPtr++ = cResult0; break; case splashModeRGB8: *pipe->destColorPtr++ = cResult0; *pipe->destColorPtr++ = cResult1; *pipe->destColorPtr++ = cResult2; break; case splashModeBGR8: *pipe->destColorPtr++ = cResult2; *pipe->destColorPtr++ = cResult1; *pipe->destColorPtr++ = cResult0; break; #if SPLASH_CMYK case splashModeCMYK8: *pipe->destColorPtr++ = cResult0; *pipe->destColorPtr++ = cResult1; *pipe->destColorPtr++ = cResult2; *pipe->destColorPtr++ = cResult3; break; #endif } if (pipe->destAlphaPtr) { *pipe->destAlphaPtr++ = aResult; } } ++pipe->x; } inline void Splash::pipeSetXY(SplashPipe *pipe, int x, int y) { pipe->x = x; pipe->y = y; if (state->softMask) { pipe->softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x]; } switch (bitmap->mode) { case splashModeMono1: pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + (x >> 3)]; pipe->destColorMask = 0x80 >> (x & 7); break; case splashModeMono8: pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + x]; break; case splashModeRGB8: case splashModeBGR8: pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x]; break; #if SPLASH_CMYK case splashModeCMYK8: pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x]; break; #endif } if (bitmap->alpha) { pipe->destAlphaPtr = &bitmap->alpha[y * bitmap->width + x]; } else { pipe->destAlphaPtr = NULL; } if (state->inNonIsolatedGroup && alpha0Bitmap->alpha) { pipe->alpha0Ptr = &alpha0Bitmap->alpha[(alpha0Y + y) * alpha0Bitmap->width + (alpha0X + x)]; } else { pipe->alpha0Ptr = NULL; } } inline void Splash::pipeIncX(SplashPipe *pipe) { ++pipe->x; if (state->softMask) { ++pipe->softMaskPtr; } switch (bitmap->mode) { case splashModeMono1: if (!(pipe->destColorMask >>= 1)) { pipe->destColorMask = 0x80; ++pipe->destColorPtr; } break; case splashModeMono8: ++pipe->destColorPtr; break; case splashModeRGB8: case splashModeBGR8: pipe->destColorPtr += 3; break; #if SPLASH_CMYK case splashModeCMYK8: pipe->destColorPtr += 4; break; #endif } if (pipe->destAlphaPtr) { ++pipe->destAlphaPtr; } if (pipe->alpha0Ptr) { ++pipe->alpha0Ptr; } } inline void Splash::drawPixel(SplashPipe *pipe, int x, int y, GBool noClip) { if (noClip || state->clip->test(x, y)) { pipeSetXY(pipe, x, y); pipeRun(pipe); updateModX(x); updateModY(y); } } inline void Splash::drawAAPixelInit() { aaBufY = -1; } inline void Splash::drawAAPixel(SplashPipe *pipe, int x, int y) { #if splashAASize == 4 static int bitCount4[16] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 }; int w; #else int xx, yy; #endif SplashColorPtr p; int x0, x1, t; if (x < 0 || x >= bitmap->width || y < state->clip->getYMinI() || y > state->clip->getYMaxI()) { return; } // update aaBuf if (y != aaBufY) { memset(aaBuf->getDataPtr(), 0xff, aaBuf->getRowSize() * aaBuf->getHeight()); x0 = 0; x1 = bitmap->width - 1; state->clip->clipAALine(aaBuf, &x0, &x1, y); aaBufY = y; } // compute the shape value #if splashAASize == 4 p = aaBuf->getDataPtr() + (x >> 1); w = aaBuf->getRowSize(); if (x & 1) { t = bitCount4[*p & 0x0f] + bitCount4[p[w] & 0x0f] + bitCount4[p[2*w] & 0x0f] + bitCount4[p[3*w] & 0x0f]; } else { t = bitCount4[*p >> 4] + bitCount4[p[w] >> 4] + bitCount4[p[2*w] >> 4] + bitCount4[p[3*w] >> 4]; } #else t = 0; for (yy = 0; yy < splashAASize; ++yy) { for (xx = 0; xx < splashAASize; ++xx) { p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + ((x * splashAASize + xx) >> 3); t += (*p >> (7 - ((x * splashAASize + xx) & 7))) & 1; } } #endif // draw the pixel if (t != 0) { pipeSetXY(pipe, x, y); pipe->shape *= aaGamma[t]; pipeRun(pipe); updateModX(x); updateModY(y); } } inline void Splash::drawSpan(SplashPipe *pipe, int x0, int x1, int y, GBool noClip) { int x; pipeSetXY(pipe, x0, y); if (noClip) { for (x = x0; x <= x1; ++x) { pipeRun(pipe); } updateModX(x0); updateModX(x1); updateModY(y); } else { for (x = x0; x <= x1; ++x) { if (state->clip->test(x, y)) { pipeRun(pipe); updateModX(x); updateModY(y); } else { pipeIncX(pipe); } } } } inline void Splash::drawAALine(SplashPipe *pipe, int x0, int x1, int y) { #if splashAASize == 4 static int bitCount4[16] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 }; SplashColorPtr p0, p1, p2, p3; int t; #else SplashColorPtr p; int xx, yy, t; #endif int x; #if splashAASize == 4 p0 = aaBuf->getDataPtr() + (x0 >> 1); p1 = p0 + aaBuf->getRowSize(); p2 = p1 + aaBuf->getRowSize(); p3 = p2 + aaBuf->getRowSize(); #endif pipeSetXY(pipe, x0, y); for (x = x0; x <= x1; ++x) { // compute the shape value #if splashAASize == 4 if (x & 1) { t = bitCount4[*p0 & 0x0f] + bitCount4[*p1 & 0x0f] + bitCount4[*p2 & 0x0f] + bitCount4[*p3 & 0x0f]; ++p0; ++p1; ++p2; ++p3; } else { t = bitCount4[*p0 >> 4] + bitCount4[*p1 >> 4] + bitCount4[*p2 >> 4] + bitCount4[*p3 >> 4]; } #else t = 0; for (yy = 0; yy < splashAASize; ++yy) { for (xx = 0; xx < splashAASize; ++xx) { p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + ((x * splashAASize + xx) >> 3); t += (*p >> (7 - ((x * splashAASize + xx) & 7))) & 1; } } #endif if (t != 0) { pipe->shape = aaGamma[t]; pipeRun(pipe); updateModX(x); updateModY(y); } else { pipeIncX(pipe); } } } //------------------------------------------------------------------------ // Transform a point from user space to device space. inline void Splash::transform(SplashCoord *matrix, SplashCoord xi, SplashCoord yi, SplashCoord *xo, SplashCoord *yo) { // [ m[0] m[1] 0 ] // [xo yo 1] = [xi yi 1] * [ m[2] m[3] 0 ] // [ m[4] m[5] 1 ] *xo = xi * matrix[0] + yi * matrix[2] + matrix[4]; *yo = xi * matrix[1] + yi * matrix[3] + matrix[5]; } //------------------------------------------------------------------------ // Splash //------------------------------------------------------------------------ Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA, SplashScreenParams *screenParams) { int i; bitmap = bitmapA; vectorAntialias = vectorAntialiasA; state = new SplashState(bitmap->width, bitmap->height, vectorAntialias, screenParams); if (vectorAntialias) { aaBuf = new SplashBitmap(splashAASize * bitmap->width, splashAASize, 1, splashModeMono1, gFalse); for (i = 0; i <= splashAASize * splashAASize; ++i) { aaGamma[i] = splashPow((SplashCoord)i / (SplashCoord)(splashAASize * splashAASize), 1.5); } } else { aaBuf = NULL; } clearModRegion(); debugMode = gFalse; } Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA, SplashScreen *screenA) { int i; bitmap = bitmapA; vectorAntialias = vectorAntialiasA; state = new SplashState(bitmap->width, bitmap->height, vectorAntialias, screenA); if (vectorAntialias) { aaBuf = new SplashBitmap(splashAASize * bitmap->width, splashAASize, 1, splashModeMono1, gFalse); for (i = 0; i <= splashAASize * splashAASize; ++i) { aaGamma[i] = splashPow((SplashCoord)i / (SplashCoord)(splashAASize * splashAASize), 1.5); } } else { aaBuf = NULL; } clearModRegion(); debugMode = gFalse; } Splash::~Splash() { while (state->next) { restoreState(); } delete state; if (vectorAntialias) { delete aaBuf; } } //------------------------------------------------------------------------ // state read //------------------------------------------------------------------------ SplashCoord *Splash::getMatrix() { return state->matrix; } SplashPattern *Splash::getStrokePattern() { return state->strokePattern; } SplashPattern *Splash::getFillPattern() { return state->fillPattern; } SplashScreen *Splash::getScreen() { return state->screen; } SplashBlendFunc Splash::getBlendFunc() { return state->blendFunc; } SplashCoord Splash::getStrokeAlpha() { return state->strokeAlpha; } SplashCoord Splash::getFillAlpha() { return state->fillAlpha; } SplashCoord Splash::getLineWidth() { return state->lineWidth; } int Splash::getLineCap() { return state->lineCap; } int Splash::getLineJoin() { return state->lineJoin; } SplashCoord Splash::getMiterLimit() { return state->miterLimit; } SplashCoord Splash::getFlatness() { return state->flatness; } SplashCoord *Splash::getLineDash() { return state->lineDash; } int Splash::getLineDashLength() { return state->lineDashLength; } SplashCoord Splash::getLineDashPhase() { return state->lineDashPhase; } SplashClip *Splash::getClip() { return state->clip; } SplashBitmap *Splash::getSoftMask() { return state->softMask; } GBool Splash::getInNonIsolatedGroup() { return state->inNonIsolatedGroup; } //------------------------------------------------------------------------ // state write //------------------------------------------------------------------------ void Splash::setMatrix(SplashCoord *matrix) { memcpy(state->matrix, matrix, 6 * sizeof(SplashCoord)); } void Splash::setStrokePattern(SplashPattern *strokePattern) { state->setStrokePattern(strokePattern); } void Splash::setFillPattern(SplashPattern *fillPattern) { state->setFillPattern(fillPattern); } void Splash::setScreen(SplashScreen *screen) { state->setScreen(screen); } void Splash::setBlendFunc(SplashBlendFunc func) { state->blendFunc = func; } void Splash::setStrokeAlpha(SplashCoord alpha) { state->strokeAlpha = alpha; } void Splash::setFillAlpha(SplashCoord alpha) { state->fillAlpha = alpha; } void Splash::setLineWidth(SplashCoord lineWidth) { state->lineWidth = lineWidth; } void Splash::setLineCap(int lineCap) { state->lineCap = lineCap; } void Splash::setLineJoin(int lineJoin) { state->lineJoin = lineJoin; } void Splash::setMiterLimit(SplashCoord miterLimit) { state->miterLimit = miterLimit; } void Splash::setFlatness(SplashCoord flatness) { if (flatness < 1) { state->flatness = 1; } else { state->flatness = flatness; } } void Splash::setLineDash(SplashCoord *lineDash, int lineDashLength, SplashCoord lineDashPhase) { state->setLineDash(lineDash, lineDashLength, lineDashPhase); } void Splash::setStrokeAdjust(GBool strokeAdjust) { state->strokeAdjust = strokeAdjust; } void Splash::clipResetToRect(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord y1) { state->clip->resetToRect(x0, y0, x1, y1); } SplashError Splash::clipToRect(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord y1) { return state->clip->clipToRect(x0, y0, x1, y1); } SplashError Splash::clipToPath(SplashPath *path, GBool eo) { return state->clip->clipToPath(path, state->matrix, state->flatness, eo); } void Splash::setSoftMask(SplashBitmap *softMask) { state->setSoftMask(softMask); } void Splash::setInNonIsolatedGroup(SplashBitmap *alpha0BitmapA, int alpha0XA, int alpha0YA) { alpha0Bitmap = alpha0BitmapA; alpha0X = alpha0XA; alpha0Y = alpha0YA; state->inNonIsolatedGroup = gTrue; } //------------------------------------------------------------------------ // state save/restore //------------------------------------------------------------------------ void Splash::saveState() { SplashState *newState; newState = state->copy(); newState->next = state; state = newState; } SplashError Splash::restoreState() { SplashState *oldState; if (!state->next) { return splashErrNoSave; } oldState = state; state = state->next; delete oldState; return splashOk; } //------------------------------------------------------------------------ // drawing operations //------------------------------------------------------------------------ void Splash::clear(SplashColorPtr color, Guchar alpha) { SplashColorPtr row, p; Guchar mono; int x, y; switch (bitmap->mode) { case splashModeMono1: mono = (color[0] & 0x80) ? 0xff : 0x00; if (bitmap->rowSize < 0) { memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), mono, -bitmap->rowSize * bitmap->height); } else { memset(bitmap->data, mono, bitmap->rowSize * bitmap->height); } break; case splashModeMono8: if (bitmap->rowSize < 0) { memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), color[0], -bitmap->rowSize * bitmap->height); } else { memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height); } break; case splashModeRGB8: if (color[0] == color[1] && color[1] == color[2]) { if (bitmap->rowSize < 0) { memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), color[0], -bitmap->rowSize * bitmap->height); } else { memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height); } } else { row = bitmap->data; for (y = 0; y < bitmap->height; ++y) { p = row; for (x = 0; x < bitmap->width; ++x) { *p++ = color[2]; *p++ = color[1]; *p++ = color[0]; } row += bitmap->rowSize; } } break; case splashModeBGR8: if (color[0] == color[1] && color[1] == color[2]) { if (bitmap->rowSize < 0) { memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), color[0], -bitmap->rowSize * bitmap->height); } else { memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height); } } else { row = bitmap->data; for (y = 0; y < bitmap->height; ++y) { p = row; for (x = 0; x < bitmap->width; ++x) { *p++ = color[0]; *p++ = color[1]; *p++ = color[2]; } row += bitmap->rowSize; } } break; #if SPLASH_CMYK case splashModeCMYK8: if (color[0] == color[1] && color[1] == color[2] && color[2] == color[3]) { if (bitmap->rowSize < 0) { memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), color[0], -bitmap->rowSize * bitmap->height); } else { memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height); } } else { row = bitmap->data; for (y = 0; y < bitmap->height; ++y) { p = row; for (x = 0; x < bitmap->width; ++x) { *p++ = color[0]; *p++ = color[1]; *p++ = color[2]; *p++ = color[3]; } row += bitmap->rowSize; } } break; #endif } if (bitmap->alpha) { memset(bitmap->alpha, alpha, bitmap->width * bitmap->height); } updateModX(0); updateModY(0); updateModX(bitmap->width - 1); updateModY(bitmap->height - 1); } SplashError Splash::stroke(SplashPath *path) { SplashPath *path2, *dPath; if (debugMode) { printf("stroke [dash:%d] [width:%.2f]:\n", state->lineDashLength, (double)state->lineWidth); dumpPath(path); } opClipRes = splashClipAllOutside; if (path->length == 0) { return splashErrEmptyPath; } path2 = flattenPath(path, state->matrix, state->flatness); if (state->lineDashLength > 0) { dPath = makeDashedPath(path2); delete path2; path2 = dPath; } if (state->lineWidth == 0) { strokeNarrow(path2); } else { strokeWide(path2); } delete path2; return splashOk; } void Splash::strokeNarrow(SplashPath *path) { SplashPipe pipe; SplashXPath *xPath; SplashXPathSeg *seg; int x0, x1, x2, x3, y0, y1, x, y, t; SplashCoord dx, dy, dxdy; SplashClipResult clipRes; int nClipRes[3]; int i; nClipRes[0] = nClipRes[1] = nClipRes[2] = 0; xPath = new SplashXPath(path, state->matrix, state->flatness, gFalse); pipeInit(&pipe, 0, 0, state->strokePattern, NULL, state->strokeAlpha, gFalse, gFalse); for (i = 0, seg = xPath->segs; i < xPath->length; ++i, ++seg) { x0 = splashFloor(seg->x0); x1 = splashFloor(seg->x1); y0 = splashFloor(seg->y0); y1 = splashFloor(seg->y1); // horizontal segment if (y0 == y1) { if (x0 > x1) { t = x0; x0 = x1; x1 = t; } if ((clipRes = state->clip->testSpan(x0, x1, y0)) != splashClipAllOutside) { drawSpan(&pipe, x0, x1, y0, clipRes == splashClipAllInside); } // segment with |dx| > |dy| } else if (splashAbs(seg->dxdy) > 1) { dx = seg->x1 - seg->x0; dy = seg->y1 - seg->y0; dxdy = seg->dxdy; if (y0 > y1) { t = y0; y0 = y1; y1 = t; t = x0; x0 = x1; x1 = t; dx = -dx; dy = -dy; } if ((clipRes = state->clip->testRect(x0 <= x1 ? x0 : x1, y0, x0 <= x1 ? x1 : x0, y1)) != splashClipAllOutside) { if (dx > 0) { x2 = x0; x3 = splashFloor(seg->x0 + ((SplashCoord)y0 + 1 - seg->y0) * dxdy); drawSpan(&pipe, x2, (x2 <= x3 - 1) ? x3 - 1 : x2, y0, clipRes == splashClipAllInside); x2 = x3; for (y = y0 + 1; y <= y1 - 1; ++y) { x3 = splashFloor(seg->x0 + ((SplashCoord)y + 1 - seg->y0) * dxdy); drawSpan(&pipe, x2, x3 - 1, y, clipRes == splashClipAllInside); x2 = x3; } drawSpan(&pipe, x2, x2 <= x1 ? x1 : x2, y1, clipRes == splashClipAllInside); } else { x2 = x0; x3 = splashFloor(seg->x0 + ((SplashCoord)y0 + 1 - seg->y0) * dxdy); drawSpan(&pipe, (x3 + 1 <= x2) ? x3 + 1 : x2, x2, y0, clipRes == splashClipAllInside); x2 = x3; for (y = y0 + 1; y <= y1 - 1; ++y) { x3 = splashFloor(seg->x0 + ((SplashCoord)y + 1 - seg->y0) * dxdy); drawSpan(&pipe, x3 + 1, x2, y, clipRes == splashClipAllInside); x2 = x3; } drawSpan(&pipe, x1, (x1 <= x2) ? x2 : x1, y1, clipRes == splashClipAllInside); } } // segment with |dy| > |dx| } else { dxdy = seg->dxdy; if (y0 > y1) { t = x0; x0 = x1; x1 = t; t = y0; y0 = y1; y1 = t; } if ((clipRes = state->clip->testRect(x0 <= x1 ? x0 : x1, y0, x0 <= x1 ? x1 : x0, y1)) != splashClipAllOutside) { drawPixel(&pipe, x0, y0, clipRes == splashClipAllInside); for (y = y0 + 1; y <= y1 - 1; ++y) { x = splashFloor(seg->x0 + ((SplashCoord)y - seg->y0) * dxdy); drawPixel(&pipe, x, y, clipRes == splashClipAllInside); } drawPixel(&pipe, x1, y1, clipRes == splashClipAllInside); } } ++nClipRes[clipRes]; } if (nClipRes[splashClipPartial] || (nClipRes[splashClipAllInside] && nClipRes[splashClipAllOutside])) { opClipRes = splashClipPartial; } else if (nClipRes[splashClipAllInside]) { opClipRes = splashClipAllInside; } else { opClipRes = splashClipAllOutside; } delete xPath; } void Splash::strokeWide(SplashPath *path) { SplashPath *path2; path2 = makeStrokePath(path, gFalse); fillWithPattern(path2, gFalse, state->strokePattern, state->strokeAlpha); delete path2; } SplashPath *Splash::flattenPath(SplashPath *path, SplashCoord *matrix, SplashCoord flatness) { SplashPath *fPath; SplashCoord flatness2; Guchar flag; int i; fPath = new SplashPath(); flatness2 = flatness * flatness; i = 0; while (i < path->length) { flag = path->flags[i]; if (flag & splashPathFirst) { fPath->moveTo(path->pts[i].x, path->pts[i].y); ++i; } else { if (flag & splashPathCurve) { flattenCurve(path->pts[i-1].x, path->pts[i-1].y, path->pts[i ].x, path->pts[i ].y, path->pts[i+1].x, path->pts[i+1].y, path->pts[i+2].x, path->pts[i+2].y, matrix, flatness2, fPath); i += 3; } else { fPath->lineTo(path->pts[i].x, path->pts[i].y); ++i; } if (path->flags[i-1] & splashPathClosed) { fPath->close(); } } } return fPath; } void Splash::flattenCurve(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord y1, SplashCoord x2, SplashCoord y2, SplashCoord x3, SplashCoord y3, SplashCoord *matrix, SplashCoord flatness2, SplashPath *fPath) { SplashCoord cx[splashMaxCurveSplits + 1][3]; SplashCoord cy[splashMaxCurveSplits + 1][3]; int cNext[splashMaxCurveSplits + 1]; SplashCoord xl0, xl1, xl2, xr0, xr1, xr2, xr3, xx1, xx2, xh; SplashCoord yl0, yl1, yl2, yr0, yr1, yr2, yr3, yy1, yy2, yh; SplashCoord dx, dy, mx, my, tx, ty, d1, d2; int p1, p2, p3; // initial segment p1 = 0; p2 = splashMaxCurveSplits; cx[p1][0] = x0; cy[p1][0] = y0; cx[p1][1] = x1; cy[p1][1] = y1; cx[p1][2] = x2; cy[p1][2] = y2; cx[p2][0] = x3; cy[p2][0] = y3; cNext[p1] = p2; while (p1 < splashMaxCurveSplits) { // get the next segment xl0 = cx[p1][0]; yl0 = cy[p1][0]; xx1 = cx[p1][1]; yy1 = cy[p1][1]; xx2 = cx[p1][2]; yy2 = cy[p1][2]; p2 = cNext[p1]; xr3 = cx[p2][0]; yr3 = cy[p2][0]; // compute the distances (in device space) from the control points // to the midpoint of the straight line (this is a bit of a hack, // but it's much faster than computing the actual distances to the // line) transform(matrix, (xl0 + xr3) * 0.5, (yl0 + yr3) * 0.5, &mx, &my); transform(matrix, xx1, yy1, &tx, &ty); dx = tx - mx; dy = ty - my; d1 = dx*dx + dy*dy; transform(matrix, xx2, yy2, &tx, &ty); dx = tx - mx; dy = ty - my; d2 = dx*dx + dy*dy; // if the curve is flat enough, or no more subdivisions are // allowed, add the straight line segment if (p2 - p1 == 1 || (d1 <= flatness2 && d2 <= flatness2)) { fPath->lineTo(xr3, yr3); p1 = p2; // otherwise, subdivide the curve } else { xl1 = (xl0 + xx1) * 0.5; yl1 = (yl0 + yy1) * 0.5; xh = (xx1 + xx2) * 0.5; yh = (yy1 + yy2) * 0.5; xl2 = (xl1 + xh) * 0.5; yl2 = (yl1 + yh) * 0.5; xr2 = (xx2 + xr3) * 0.5; yr2 = (yy2 + yr3) * 0.5; xr1 = (xh + xr2) * 0.5; yr1 = (yh + yr2) * 0.5; xr0 = (xl2 + xr1) * 0.5; yr0 = (yl2 + yr1) * 0.5; // add the new subdivision points p3 = (p1 + p2) / 2; cx[p1][1] = xl1; cy[p1][1] = yl1; cx[p1][2] = xl2; cy[p1][2] = yl2; cNext[p1] = p3; cx[p3][0] = xr0; cy[p3][0] = yr0; cx[p3][1] = xr1; cy[p3][1] = yr1; cx[p3][2] = xr2; cy[p3][2] = yr2; cNext[p3] = p2; } } } SplashPath *Splash::makeDashedPath(SplashPath *path) { SplashPath *dPath; SplashCoord lineDashTotal; SplashCoord lineDashStartPhase, lineDashDist, segLen; SplashCoord x0, y0, x1, y1, xa, ya; GBool lineDashStartOn, lineDashOn, newPath; int lineDashStartIdx, lineDashIdx; int i, j, k; lineDashTotal = 0; for (i = 0; i < state->lineDashLength; ++i) { lineDashTotal += state->lineDash[i]; } lineDashStartPhase = state->lineDashPhase; i = splashFloor(lineDashStartPhase / lineDashTotal); lineDashStartPhase -= (SplashCoord)i * lineDashTotal; lineDashStartOn = gTrue; lineDashStartIdx = 0; while (lineDashStartPhase >= state->lineDash[lineDashStartIdx]) { lineDashStartOn = !lineDashStartOn; lineDashStartPhase -= state->lineDash[lineDashStartIdx]; ++lineDashStartIdx; } dPath = new SplashPath(); // process each subpath i = 0; while (i < path->length) { // find the end of the subpath for (j = i; j < path->length - 1 && !(path->flags[j] & splashPathLast); ++j) ; // initialize the dash parameters lineDashOn = lineDashStartOn; lineDashIdx = lineDashStartIdx; lineDashDist = state->lineDash[lineDashIdx] - lineDashStartPhase; // process each segment of the subpath newPath = gTrue; for (k = i; k < j; ++k) { // grab the segment x0 = path->pts[k].x; y0 = path->pts[k].y; x1 = path->pts[k+1].x; y1 = path->pts[k+1].y; segLen = splashDist(x0, y0, x1, y1); // process the segment while (segLen > 0) { if (lineDashDist >= segLen) { if (lineDashOn) { if (newPath) { dPath->moveTo(x0, y0); newPath = gFalse; } dPath->lineTo(x1, y1); } lineDashDist -= segLen; segLen = 0; } else { xa = x0 + (lineDashDist / segLen) * (x1 - x0); ya = y0 + (lineDashDist / segLen) * (y1 - y0); if (lineDashOn) { if (newPath) { dPath->moveTo(x0, y0); newPath = gFalse; } dPath->lineTo(xa, ya); } x0 = xa; y0 = ya; segLen -= lineDashDist; lineDashDist = 0; } // get the next entry in the dash array if (lineDashDist <= 0) { lineDashOn = !lineDashOn; if (++lineDashIdx == state->lineDashLength) { lineDashIdx = 0; } lineDashDist = state->lineDash[lineDashIdx]; newPath = gTrue; } } } i = j + 1; } return dPath; } SplashError Splash::fill(SplashPath *path, GBool eo) { if (debugMode) { printf("fill [eo:%d]:\n", eo); dumpPath(path); } return fillWithPattern(path, eo, state->fillPattern, state->fillAlpha); } SplashError Splash::fillWithPattern(SplashPath *path, GBool eo, SplashPattern *pattern, SplashCoord alpha) { SplashPipe pipe; SplashXPath *xPath; SplashXPathScanner *scanner; int xMinI, yMinI, xMaxI, yMaxI, x0, x1, y; SplashClipResult clipRes, clipRes2; if (path->length == 0) { return splashErrEmptyPath; } xPath = new SplashXPath(path, state->matrix, state->flatness, gTrue); if (vectorAntialias) { xPath->aaScale(); } xPath->sort(); if (!&xPath->segs[0]) { delete xPath; return splashErrEmptyPath; } scanner = new SplashXPathScanner(xPath, eo); // get the min and max x and y values if (vectorAntialias) { scanner->getBBoxAA(&xMinI, &yMinI, &xMaxI, &yMaxI); } else { scanner->getBBox(&xMinI, &yMinI, &xMaxI, &yMaxI); } // check clipping if ((clipRes = state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI)) != splashClipAllOutside) { // limit the y range if (yMinI < state->clip->getYMinI()) { yMinI = state->clip->getYMinI(); } if (yMaxI > state->clip->getYMaxI()) { yMaxI = state->clip->getYMaxI(); } pipeInit(&pipe, 0, yMinI, pattern, NULL, alpha, vectorAntialias, gFalse); // draw the spans if (vectorAntialias) { for (y = yMinI; y <= yMaxI; ++y) { scanner->renderAALine(aaBuf, &x0, &x1, y); if (clipRes != splashClipAllInside) { state->clip->clipAALine(aaBuf, &x0, &x1, y); } drawAALine(&pipe, x0, x1, y); } } else { for (y = yMinI; y <= yMaxI; ++y) { while (scanner->getNextSpan(y, &x0, &x1)) { if (clipRes == splashClipAllInside) { drawSpan(&pipe, x0, x1, y, gTrue); } else { // limit the x range if (x0 < state->clip->getXMinI()) { x0 = state->clip->getXMinI(); } if (x1 > state->clip->getXMaxI()) { x1 = state->clip->getXMaxI(); } clipRes2 = state->clip->testSpan(x0, x1, y); drawSpan(&pipe, x0, x1, y, clipRes2 == splashClipAllInside); } } } } } opClipRes = clipRes; delete scanner; delete xPath; return splashOk; } SplashError Splash::xorFill(SplashPath *path, GBool eo) { SplashPipe pipe; SplashXPath *xPath; SplashXPathScanner *scanner; int xMinI, yMinI, xMaxI, yMaxI, x0, x1, y; SplashClipResult clipRes, clipRes2; SplashBlendFunc origBlendFunc; if (path->length == 0) { return splashErrEmptyPath; } xPath = new SplashXPath(path, state->matrix, state->flatness, gTrue); xPath->sort(); scanner = new SplashXPathScanner(xPath, eo); // get the min and max x and y values scanner->getBBox(&xMinI, &yMinI, &xMaxI, &yMaxI); // check clipping if ((clipRes = state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI)) != splashClipAllOutside) { // limit the y range if (yMinI < state->clip->getYMinI()) { yMinI = state->clip->getYMinI(); } if (yMaxI > state->clip->getYMaxI()) { yMaxI = state->clip->getYMaxI(); } origBlendFunc = state->blendFunc; state->blendFunc = &blendXor; pipeInit(&pipe, 0, yMinI, state->fillPattern, NULL, 1, gFalse, gFalse); // draw the spans for (y = yMinI; y <= yMaxI; ++y) { while (scanner->getNextSpan(y, &x0, &x1)) { if (clipRes == splashClipAllInside) { drawSpan(&pipe, x0, x1, y, gTrue); } else { // limit the x range if (x0 < state->clip->getXMinI()) { x0 = state->clip->getXMinI(); } if (x1 > state->clip->getXMaxI()) { x1 = state->clip->getXMaxI(); } clipRes2 = state->clip->testSpan(x0, x1, y); drawSpan(&pipe, x0, x1, y, clipRes2 == splashClipAllInside); } } } state->blendFunc = origBlendFunc; } opClipRes = clipRes; delete scanner; delete xPath; return splashOk; } SplashError Splash::fillChar(SplashCoord x, SplashCoord y, int c, SplashFont *font) { SplashGlyphBitmap glyph; SplashCoord xt, yt; int x0, y0, xFrac, yFrac; SplashClipResult clipRes; if (debugMode) { printf("fillChar: x=%.2f y=%.2f c=%3d=0x%02x='%c'\n", (double)x, (double)y, c, c, c); } transform(state->matrix, x, y, &xt, &yt); x0 = splashFloor(xt); xFrac = splashFloor((xt - x0) * splashFontFraction); y0 = splashFloor(yt); yFrac = splashFloor((yt - y0) * splashFontFraction); if (!font->getGlyph(c, xFrac, yFrac, &glyph, x0, y0, state->clip, &clipRes)) { return splashErrNoGlyph; } if (clipRes != splashClipAllOutside) { fillGlyph2(x0, y0, &glyph, clipRes == splashClipAllInside); } opClipRes = clipRes; if (glyph.freeData) { gfree(glyph.data); } return splashOk; } void Splash::fillGlyph(SplashCoord x, SplashCoord y, SplashGlyphBitmap *glyph) { SplashCoord xt, yt; int x0, y0; transform(state->matrix, x, y, &xt, &yt); x0 = splashFloor(xt); y0 = splashFloor(yt); SplashClipResult clipRes = state->clip->testRect(x0 - glyph->x, y0 - glyph->y, x0 - glyph->x + glyph->w - 1, y0 - glyph->y + glyph->h - 1); if (clipRes != splashClipAllOutside) { fillGlyph2(x0, y0, glyph, clipRes == splashClipAllInside); } opClipRes = clipRes; } void Splash::fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph, GBool noClip) { SplashPipe pipe; int alpha0, alpha; Guchar *p; int x1, y1, xx, xx1, yy; p = glyph->data; int xStart = x0 - glyph->x; int yStart = y0 - glyph->y; int xxLimit = glyph->w; int yyLimit = glyph->h; if (yStart < 0) { p += glyph->w * -yStart; // move p to the beginning of the first painted row yyLimit += yStart; yStart = 0; } if (xStart < 0) { p += -xStart; // move p to the first painted pixel xxLimit += xStart; xStart = 0; } if (xxLimit + xStart >= bitmap->width) xxLimit = bitmap->width - xStart; if (yyLimit + yStart >= bitmap->height) yyLimit = bitmap->height - yStart; if (noClip) { if (glyph->aa) { pipeInit(&pipe, xStart, yStart, state->fillPattern, NULL, state->fillAlpha, gTrue, gFalse); for (yy = 0, y1 = yStart; yy < yyLimit; ++yy, ++y1) { pipeSetXY(&pipe, xStart, y1); for (xx = 0, x1 = xStart; xx < xxLimit; ++xx, ++x1) { alpha = p[xx]; if (alpha != 0) { pipe.shape = (SplashCoord)(alpha / 255.0); pipeRun(&pipe); updateModX(x1); updateModY(y1); } else { pipeIncX(&pipe); } } p += glyph->w; } } else { const int widthEight = (int)ceil(glyph->w / 8.0); pipeInit(&pipe, xStart, yStart, state->fillPattern, NULL, state->fillAlpha, gFalse, gFalse); for (yy = 0, y1 = yStart; yy < yyLimit; ++yy, ++y1) { pipeSetXY(&pipe, xStart, y1); for (xx = 0, x1 = xStart; xx < xxLimit; xx += 8) { alpha0 = p[xx / 8]; for (xx1 = 0; xx1 < 8 && xx + xx1 < xxLimit; ++xx1, ++x1) { if (alpha0 & 0x80) { pipeRun(&pipe); updateModX(x1); updateModY(y1); } else { pipeIncX(&pipe); } alpha0 <<= 1; } } p += widthEight; } } } else { if (glyph->aa) { pipeInit(&pipe, xStart, yStart, state->fillPattern, NULL, state->fillAlpha, gTrue, gFalse); for (yy = 0, y1 = yStart; yy < yyLimit; ++yy, ++y1) { pipeSetXY(&pipe, xStart, y1); for (xx = 0, x1 = xStart; xx < xxLimit; ++xx, ++x1) { if (state->clip->test(x1, y1)) { alpha = p[xx]; if (alpha != 0) { pipe.shape = (SplashCoord)(alpha / 255.0); pipeRun(&pipe); updateModX(x1); updateModY(y1); } else { pipeIncX(&pipe); } } else { pipeIncX(&pipe); } } p += glyph->w; } } else { const int widthEight = (int)ceil(glyph->w / 8.0); pipeInit(&pipe, xStart, yStart, state->fillPattern, NULL, state->fillAlpha, gFalse, gFalse); for (yy = 0, y1 = yStart; yy < yyLimit; ++yy, ++y1) { pipeSetXY(&pipe, xStart, y1); for (xx = 0, x1 = xStart; xx < xxLimit; xx += 8) { alpha0 = p[xx / 8]; for (xx1 = 0; xx1 < 8 && xx + xx1 < xxLimit; ++xx1, ++x1) { if (state->clip->test(x1, y1)) { if (alpha0 & 0x80) { pipeRun(&pipe); updateModX(x1); updateModY(y1); } else { pipeIncX(&pipe); } } else { pipeIncX(&pipe); } alpha0 <<= 1; } } p += widthEight; } } } } SplashError Splash::fillImageMask(SplashImageMaskSource src, void *srcData, int w, int h, SplashCoord *mat, GBool glyphMode) { SplashPipe pipe; GBool rot; SplashCoord xScale, yScale, xShear, yShear, yShear1; int tx, tx2, ty, ty2, scaledWidth, scaledHeight, xSign, ySign; int ulx, uly, llx, lly, urx, ury, lrx, lry; int ulx1, uly1, llx1, lly1, urx1, ury1, lrx1, lry1; int xMin, xMax, yMin, yMax; SplashClipResult clipRes, clipRes2; int yp, yq, yt, yStep, lastYStep; int xp, xq, xt, xStep, xSrc; int k1, spanXMin, spanXMax, spanY; SplashColorPtr pixBuf, p; int pixAcc; int x, y, x1, x2, y2; SplashCoord y1; int n, m, i, j; if (debugMode) { printf("fillImageMask: w=%d h=%d mat=[%.2f %.2f %.2f %.2f %.2f %.2f]\n", w, h, (double)mat[0], (double)mat[1], (double)mat[2], (double)mat[3], (double)mat[4], (double)mat[5]); } if (w == 0 && h == 0) return splashErrZeroImage; // check for singular matrix if (splashAbs(mat[0] * mat[3] - mat[1] * mat[2]) < 0.000001) { return splashErrSingularMatrix; } // compute scale, shear, rotation, translation parameters rot = splashAbs(mat[1]) > splashAbs(mat[0]); if (rot) { xScale = -mat[1]; yScale = mat[2] - (mat[0] * mat[3]) / mat[1]; xShear = -mat[3] / yScale; yShear = -mat[0] / mat[1]; } else { xScale = mat[0]; yScale = mat[3] - (mat[1] * mat[2]) / mat[0]; xShear = mat[2] / yScale; yShear = mat[1] / mat[0]; } // Note 1: The PDF spec says that all pixels whose *centers* lie // within the region get painted -- but that doesn't seem to match // up with what Acrobat actually does: it ends up leaving gaps // between image stripes. So we use the same rule here as for // fills: any pixel that overlaps the region gets painted. // Note 2: The "glyphMode" flag is a kludge: it switches back to // "correct" behavior (matching the spec), for use in rendering Type // 3 fonts. // Note 3: The +/-0.01 in these computations is to avoid floating // point precision problems which can lead to gaps between image // stripes (it can cause image stripes to overlap, but that's a much // less visible problem). if (glyphMode) { if (xScale >= 0) { tx = splashRound(mat[4]); tx2 = splashRound(mat[4] + xScale) - 1; } else { tx = splashRound(mat[4]) - 1; tx2 = splashRound(mat[4] + xScale); } } else { if (xScale >= 0) { tx = splashFloor(mat[4] - 0.01); tx2 = splashFloor(mat[4] + xScale + 0.01); } else { tx = splashFloor(mat[4] + 0.01); tx2 = splashFloor(mat[4] + xScale - 0.01); } } scaledWidth = abs(tx2 - tx) + 1; if (glyphMode) { if (yScale >= 0) { ty = splashRound(mat[5]); ty2 = splashRound(mat[5] + yScale) - 1; } else { ty = splashRound(mat[5]) - 1; ty2 = splashRound(mat[5] + yScale); } } else { if (yScale >= 0) { ty = splashFloor(mat[5] - 0.01); ty2 = splashFloor(mat[5] + yScale + 0.01); } else { ty = splashFloor(mat[5] + 0.01); ty2 = splashFloor(mat[5] + yScale - 0.01); } } scaledHeight = abs(ty2 - ty) + 1; xSign = (xScale < 0) ? -1 : 1; ySign = (yScale < 0) ? -1 : 1; yShear1 = (SplashCoord)xSign * yShear; // clipping ulx1 = 0; uly1 = 0; urx1 = xSign * (scaledWidth - 1); ury1 = (int)(yShear * urx1); llx1 = splashRound(xShear * ySign * (scaledHeight - 1)); lly1 = ySign * (scaledHeight - 1) + (int)(yShear * llx1); lrx1 = xSign * (scaledWidth - 1) + splashRound(xShear * ySign * (scaledHeight - 1)); lry1 = ySign * (scaledHeight - 1) + (int)(yShear * lrx1); if (rot) { ulx = tx + uly1; uly = ty - ulx1; urx = tx + ury1; ury = ty - urx1; llx = tx + lly1; lly = ty - llx1; lrx = tx + lry1; lry = ty - lrx1; } else { ulx = tx + ulx1; uly = ty + uly1; urx = tx + urx1; ury = ty + ury1; llx = tx + llx1; lly = ty + lly1; lrx = tx + lrx1; lry = ty + lry1; } xMin = (ulx < urx) ? (ulx < llx) ? (ulx < lrx) ? ulx : lrx : (llx < lrx) ? llx : lrx : (urx < llx) ? (urx < lrx) ? urx : lrx : (llx < lrx) ? llx : lrx; xMax = (ulx > urx) ? (ulx > llx) ? (ulx > lrx) ? ulx : lrx : (llx > lrx) ? llx : lrx : (urx > llx) ? (urx > lrx) ? urx : lrx : (llx > lrx) ? llx : lrx; yMin = (uly < ury) ? (uly < lly) ? (uly < lry) ? uly : lry : (lly < lry) ? lly : lry : (ury < lly) ? (ury < lry) ? ury : lry : (lly < lry) ? lly : lry; yMax = (uly > ury) ? (uly > lly) ? (uly > lry) ? uly : lry : (lly > lry) ? lly : lry : (ury > lly) ? (ury > lry) ? ury : lry : (lly > lry) ? lly : lry; clipRes = state->clip->testRect(xMin, yMin, xMax, yMax); opClipRes = clipRes; // compute Bresenham parameters for x and y scaling yp = h / scaledHeight; yq = h % scaledHeight; xp = w / scaledWidth; xq = w % scaledWidth; // allocate pixel buffer if (yp < 0 || yp > INT_MAX - 1) { return splashErrBadArg; } pixBuf = (SplashColorPtr)gmallocn(yp + 1, w); // initialize the pixel pipe pipeInit(&pipe, 0, 0, state->fillPattern, NULL, state->fillAlpha, gTrue, gFalse); if (vectorAntialias) { drawAAPixelInit(); } // init y scale Bresenham yt = 0; lastYStep = 1; for (y = 0; y < scaledHeight; ++y) { // y scale Bresenham yStep = yp; yt += yq; if (yt >= scaledHeight) { yt -= scaledHeight; ++yStep; } // read row(s) from image n = (yp > 0) ? yStep : lastYStep; if (n > 0) { p = pixBuf; for (i = 0; i < n; ++i) { (*src)(srcData, p); p += w; } } lastYStep = yStep; // loop-invariant constants k1 = splashRound(xShear * ySign * y); // clipping test if (clipRes != splashClipAllInside && !rot && (int)(yShear * k1) == (int)(yShear * (xSign * (scaledWidth - 1) + k1))) { if (xSign > 0) { spanXMin = tx + k1; spanXMax = spanXMin + (scaledWidth - 1); } else { spanXMax = tx + k1; spanXMin = spanXMax - (scaledWidth - 1); } spanY = ty + ySign * y + (int)(yShear * k1); clipRes2 = state->clip->testSpan(spanXMin, spanXMax, spanY); if (clipRes2 == splashClipAllOutside) { continue; } } else { clipRes2 = clipRes; } // init x scale Bresenham xt = 0; xSrc = 0; // x shear x1 = k1; // y shear y1 = (SplashCoord)ySign * y + yShear * x1; // this is a kludge: if yShear1 is negative, then (int)y1 would // change immediately after the first pixel, which is not what we // want if (yShear1 < 0) { y1 += 0.999; } // loop-invariant constants n = yStep > 0 ? yStep : 1; for (x = 0; x < scaledWidth; ++x) { // x scale Bresenham xStep = xp; xt += xq; if (xt >= scaledWidth) { xt -= scaledWidth; ++xStep; } // rotation if (rot) { x2 = (int)y1; y2 = -x1; } else { x2 = x1; y2 = (int)y1; } // compute the alpha value for (x,y) after the x and y scaling // operations m = xStep > 0 ? xStep : 1; p = pixBuf + xSrc; pixAcc = 0; for (i = 0; i < n; ++i) { for (j = 0; j < m; ++j) { pixAcc += *p++; } p += w - m; } // blend fill color with background if (pixAcc != 0) { pipe.shape = (pixAcc == n * m) ? (SplashCoord)1 : (SplashCoord)pixAcc / (SplashCoord)(n * m); if (vectorAntialias && clipRes2 != splashClipAllInside) { drawAAPixel(&pipe, tx + x2, ty + y2); } else { drawPixel(&pipe, tx + x2, ty + y2, clipRes2 == splashClipAllInside); } } // x scale Bresenham xSrc += xStep; // x shear x1 += xSign; // y shear y1 += yShear1; } } // free memory gfree(pixBuf); return splashOk; } SplashError Splash::drawImage(SplashImageSource src, void *srcData, SplashColorMode srcMode, GBool srcAlpha, int w, int h, SplashCoord *mat) { SplashPipe pipe; GBool ok, rot; SplashCoord xScale, yScale, xShear, yShear, yShear1; int tx, tx2, ty, ty2, scaledWidth, scaledHeight, xSign, ySign; int ulx, uly, llx, lly, urx, ury, lrx, lry; int ulx1, uly1, llx1, lly1, urx1, ury1, lrx1, lry1; int xMin, xMax, yMin, yMax; SplashClipResult clipRes, clipRes2; int yp, yq, yt, yStep, lastYStep; int xp, xq, xt, xStep, xSrc; int k1, spanXMin, spanXMax, spanY; SplashColorPtr colorBuf, p; SplashColor pix; Guchar *alphaBuf, *q; #if SPLASH_CMYK int pixAcc0, pixAcc1, pixAcc2, pixAcc3; #else int pixAcc0, pixAcc1, pixAcc2; #endif int alphaAcc; SplashCoord pixMul, alphaMul, alpha; int x, y, x1, x2, y2; SplashCoord y1; int nComps, n, m, i, j; if (debugMode) { printf("drawImage: srcMode=%d srcAlpha=%d w=%d h=%d mat=[%.2f %.2f %.2f %.2f %.2f %.2f]\n", srcMode, srcAlpha, w, h, (double)mat[0], (double)mat[1], (double)mat[2], (double)mat[3], (double)mat[4], (double)mat[5]); } // check color modes ok = gFalse; // make gcc happy nComps = 0; // make gcc happy switch (bitmap->mode) { case splashModeMono1: case splashModeMono8: ok = srcMode == splashModeMono8; nComps = 1; break; case splashModeRGB8: ok = srcMode == splashModeRGB8; nComps = 3; break; case splashModeBGR8: ok = srcMode == splashModeBGR8; nComps = 3; break; #if SPLASH_CMYK case splashModeCMYK8: ok = srcMode == splashModeCMYK8; nComps = 4; break; #endif } if (!ok) { return splashErrModeMismatch; } // check for singular matrix if (splashAbs(mat[0] * mat[3] - mat[1] * mat[2]) < 0.000001) { return splashErrSingularMatrix; } // compute scale, shear, rotation, translation parameters rot = splashAbs(mat[1]) > splashAbs(mat[0]); if (rot) { xScale = -mat[1]; yScale = mat[2] - (mat[0] * mat[3]) / mat[1]; xShear = -mat[3] / yScale; yShear = -mat[0] / mat[1]; } else { xScale = mat[0]; yScale = mat[3] - (mat[1] * mat[2]) / mat[0]; xShear = mat[2] / yScale; yShear = mat[1] / mat[0]; } // Note 1: The PDF spec says that all pixels whose *centers* lie // within the region get painted -- but that doesn't seem to match // up with what Acrobat actually does: it ends up leaving gaps // between image stripes. So we use the same rule here as for // fills: any pixel that overlaps the region gets painted. // Note 2: The +/-0.01 in these computations is to avoid floating // point precision problems which can lead to gaps between image // stripes (it can cause image stripes to overlap, but that's a much // less visible problem). if (xScale >= 0) { tx = splashFloor(mat[4] - 0.01); tx2 = splashFloor(mat[4] + xScale + 0.01); } else { tx = splashFloor(mat[4] + 0.01); tx2 = splashFloor(mat[4] + xScale - 0.01); } scaledWidth = abs(tx2 - tx) + 1; if (yScale >= 0) { ty = splashFloor(mat[5] - 0.01); ty2 = splashFloor(mat[5] + yScale + 0.01); } else { ty = splashFloor(mat[5] + 0.01); ty2 = splashFloor(mat[5] + yScale - 0.01); } scaledHeight = abs(ty2 - ty) + 1; xSign = (xScale < 0) ? -1 : 1; ySign = (yScale < 0) ? -1 : 1; yShear1 = (SplashCoord)xSign * yShear; // clipping ulx1 = 0; uly1 = 0; urx1 = xSign * (scaledWidth - 1); ury1 = (int)(yShear * urx1); llx1 = splashRound(xShear * ySign * (scaledHeight - 1)); lly1 = ySign * (scaledHeight - 1) + (int)(yShear * llx1); lrx1 = xSign * (scaledWidth - 1) + splashRound(xShear * ySign * (scaledHeight - 1)); lry1 = ySign * (scaledHeight - 1) + (int)(yShear * lrx1); if (rot) { ulx = tx + uly1; uly = ty - ulx1; urx = tx + ury1; ury = ty - urx1; llx = tx + lly1; lly = ty - llx1; lrx = tx + lry1; lry = ty - lrx1; } else { ulx = tx + ulx1; uly = ty + uly1; urx = tx + urx1; ury = ty + ury1; llx = tx + llx1; lly = ty + lly1; lrx = tx + lrx1; lry = ty + lry1; } xMin = (ulx < urx) ? (ulx < llx) ? (ulx < lrx) ? ulx : lrx : (llx < lrx) ? llx : lrx : (urx < llx) ? (urx < lrx) ? urx : lrx : (llx < lrx) ? llx : lrx; xMax = (ulx > urx) ? (ulx > llx) ? (ulx > lrx) ? ulx : lrx : (llx > lrx) ? llx : lrx : (urx > llx) ? (urx > lrx) ? urx : lrx : (llx > lrx) ? llx : lrx; yMin = (uly < ury) ? (uly < lly) ? (uly < lry) ? uly : lry : (lly < lry) ? lly : lry : (ury < lly) ? (ury < lry) ? ury : lry : (lly < lry) ? lly : lry; yMax = (uly > ury) ? (uly > lly) ? (uly > lry) ? uly : lry : (lly > lry) ? lly : lry : (ury > lly) ? (ury > lry) ? ury : lry : (lly > lry) ? lly : lry; clipRes = state->clip->testRect(xMin, yMin, xMax, yMax); opClipRes = clipRes; if (clipRes == splashClipAllOutside) { return splashOk; } // compute Bresenham parameters for x and y scaling yp = h / scaledHeight; yq = h % scaledHeight; xp = w / scaledWidth; xq = w % scaledWidth; // allocate pixel buffers if (yp < 0 || yp > INT_MAX - 1 || w > INT_MAX / nComps) { return splashErrBadArg; } colorBuf = (SplashColorPtr)gmallocn(yp + 1, w * nComps); if (srcAlpha) { alphaBuf = (Guchar *)gmallocn(yp + 1, w); } else { alphaBuf = NULL; } pixAcc0 = pixAcc1 = pixAcc2 = 0; // make gcc happy #if SPLASH_CMYK pixAcc3 = 0; // make gcc happy #endif // initialize the pixel pipe pipeInit(&pipe, 0, 0, NULL, pix, state->fillAlpha, srcAlpha || (vectorAntialias && clipRes != splashClipAllInside), gFalse); if (vectorAntialias) { drawAAPixelInit(); } if (srcAlpha) { // init y scale Bresenham yt = 0; lastYStep = 1; for (y = 0; y < scaledHeight; ++y) { // y scale Bresenham yStep = yp; yt += yq; if (yt >= scaledHeight) { yt -= scaledHeight; ++yStep; } // read row(s) from image n = (yp > 0) ? yStep : lastYStep; if (n > 0) { p = colorBuf; q = alphaBuf; for (i = 0; i < n; ++i) { (*src)(srcData, p, q); p += w * nComps; q += w; } } lastYStep = yStep; // loop-invariant constants k1 = splashRound(xShear * ySign * y); // clipping test if (clipRes != splashClipAllInside && !rot && (int)(yShear * k1) == (int)(yShear * (xSign * (scaledWidth - 1) + k1))) { if (xSign > 0) { spanXMin = tx + k1; spanXMax = spanXMin + (scaledWidth - 1); } else { spanXMax = tx + k1; spanXMin = spanXMax - (scaledWidth - 1); } spanY = ty + ySign * y + (int)(yShear * k1); clipRes2 = state->clip->testSpan(spanXMin, spanXMax, spanY); if (clipRes2 == splashClipAllOutside) { continue; } } else { clipRes2 = clipRes; } // init x scale Bresenham xt = 0; xSrc = 0; // x shear x1 = k1; // y shear y1 = (SplashCoord)ySign * y + yShear * x1; // this is a kludge: if yShear1 is negative, then (int)y1 would // change immediately after the first pixel, which is not what // we want if (yShear1 < 0) { y1 += 0.999; } // loop-invariant constants n = yStep > 0 ? yStep : 1; switch (srcMode) { case splashModeMono1: case splashModeMono8: for (x = 0; x < scaledWidth; ++x) { // x scale Bresenham xStep = xp; xt += xq; if (xt >= scaledWidth) { xt -= scaledWidth; ++xStep; } // rotation if (rot) { x2 = (int)y1; y2 = -x1; } else { x2 = x1; y2 = (int)y1; } // compute the filtered pixel at (x,y) after the x and y scaling // operations m = xStep > 0 ? xStep : 1; alphaAcc = 0; p = colorBuf + xSrc; q = alphaBuf + xSrc; pixAcc0 = 0; for (i = 0; i < n; ++i) { for (j = 0; j < m; ++j) { pixAcc0 += *p++; alphaAcc += *q++; } p += w - m; q += w - m; } pixMul = (SplashCoord)1 / (SplashCoord)(n * m); alphaMul = pixMul * (1.0 / 255.0); alpha = (SplashCoord)alphaAcc * alphaMul; if (alpha > 0) { pix[0] = (int)((SplashCoord)pixAcc0 * pixMul); // set pixel pipe.shape = alpha; if (vectorAntialias && clipRes != splashClipAllInside) { drawAAPixel(&pipe, tx + x2, ty + y2); } else { drawPixel(&pipe, tx + x2, ty + y2, clipRes2 == splashClipAllInside); } } // x scale Bresenham xSrc += xStep; // x shear x1 += xSign; // y shear y1 += yShear1; } break; case splashModeRGB8: case splashModeBGR8: for (x = 0; x < scaledWidth; ++x) { // x scale Bresenham xStep = xp; xt += xq; if (xt >= scaledWidth) { xt -= scaledWidth; ++xStep; } // rotation if (rot) { x2 = (int)y1; y2 = -x1; } else { x2 = x1; y2 = (int)y1; } // compute the filtered pixel at (x,y) after the x and y scaling // operations m = xStep > 0 ? xStep : 1; alphaAcc = 0; p = colorBuf + xSrc * 3; q = alphaBuf + xSrc; pixAcc0 = pixAcc1 = pixAcc2 = 0; for (i = 0; i < n; ++i) { for (j = 0; j < m; ++j) { pixAcc0 += *p++; pixAcc1 += *p++; pixAcc2 += *p++; alphaAcc += *q++; } p += 3 * (w - m); q += w - m; } pixMul = (SplashCoord)1 / (SplashCoord)(n * m); alphaMul = pixMul * (1.0 / 255.0); alpha = (SplashCoord)alphaAcc * alphaMul; if (alpha > 0) { pix[0] = (int)((SplashCoord)pixAcc0 * pixMul); pix[1] = (int)((SplashCoord)pixAcc1 * pixMul); pix[2] = (int)((SplashCoord)pixAcc2 * pixMul); // set pixel pipe.shape = alpha; if (vectorAntialias && clipRes != splashClipAllInside) { drawAAPixel(&pipe, tx + x2, ty + y2); } else { drawPixel(&pipe, tx + x2, ty + y2, clipRes2 == splashClipAllInside); } } // x scale Bresenham xSrc += xStep; // x shear x1 += xSign; // y shear y1 += yShear1; } break; #if SPLASH_CMYK case splashModeCMYK8: for (x = 0; x < scaledWidth; ++x) { // x scale Bresenham xStep = xp; xt += xq; if (xt >= scaledWidth) { xt -= scaledWidth; ++xStep; } // rotation if (rot) { x2 = (int)y1; y2 = -x1; } else { x2 = x1; y2 = (int)y1; } // compute the filtered pixel at (x,y) after the x and y scaling // operations m = xStep > 0 ? xStep : 1; alphaAcc = 0; p = colorBuf + xSrc * 4; q = alphaBuf + xSrc; pixAcc0 = pixAcc1 = pixAcc2 = pixAcc3 = 0; for (i = 0; i < n; ++i) { for (j = 0; j < m; ++j) { pixAcc0 += *p++; pixAcc1 += *p++; pixAcc2 += *p++; pixAcc3 += *p++; alphaAcc += *q++; } p += 4 * (w - m); q += w - m; } pixMul = (SplashCoord)1 / (SplashCoord)(n * m); alphaMul = pixMul * (1.0 / 255.0); alpha = (SplashCoord)alphaAcc * alphaMul; if (alpha > 0) { pix[0] = (int)((SplashCoord)pixAcc0 * pixMul); pix[1] = (int)((SplashCoord)pixAcc1 * pixMul); pix[2] = (int)((SplashCoord)pixAcc2 * pixMul); pix[3] = (int)((SplashCoord)pixAcc3 * pixMul); // set pixel pipe.shape = alpha; if (vectorAntialias && clipRes != splashClipAllInside) { drawAAPixel(&pipe, tx + x2, ty + y2); } else { drawPixel(&pipe, tx + x2, ty + y2, clipRes2 == splashClipAllInside); } } // x scale Bresenham xSrc += xStep; // x shear x1 += xSign; // y shear y1 += yShear1; } break; #endif // SPLASH_CMYK } } } else { // init y scale Bresenham yt = 0; lastYStep = 1; for (y = 0; y < scaledHeight; ++y) { // y scale Bresenham yStep = yp; yt += yq; if (yt >= scaledHeight) { yt -= scaledHeight; ++yStep; } // read row(s) from image n = (yp > 0) ? yStep : lastYStep; if (n > 0) { p = colorBuf; for (i = 0; i < n; ++i) { (*src)(srcData, p, NULL); p += w * nComps; } } lastYStep = yStep; // loop-invariant constants k1 = splashRound(xShear * ySign * y); // clipping test if (clipRes != splashClipAllInside && !rot && (int)(yShear * k1) == (int)(yShear * (xSign * (scaledWidth - 1) + k1))) { if (xSign > 0) { spanXMin = tx + k1; spanXMax = spanXMin + (scaledWidth - 1); } else { spanXMax = tx + k1; spanXMin = spanXMax - (scaledWidth - 1); } spanY = ty + ySign * y + (int)(yShear * k1); clipRes2 = state->clip->testSpan(spanXMin, spanXMax, spanY); if (clipRes2 == splashClipAllOutside) { continue; } } else { clipRes2 = clipRes; } // init x scale Bresenham xt = 0; xSrc = 0; // x shear x1 = k1; // y shear y1 = (SplashCoord)ySign * y + yShear * x1; // this is a kludge: if yShear1 is negative, then (int)y1 would // change immediately after the first pixel, which is not what // we want if (yShear1 < 0) { y1 += 0.999; } // loop-invariant constants n = yStep > 0 ? yStep : 1; switch (srcMode) { case splashModeMono1: case splashModeMono8: for (x = 0; x < scaledWidth; ++x) { // x scale Bresenham xStep = xp; xt += xq; if (xt >= scaledWidth) { xt -= scaledWidth; ++xStep; } // rotation if (rot) { x2 = (int)y1; y2 = -x1; } else { x2 = x1; y2 = (int)y1; } // compute the filtered pixel at (x,y) after the x and y scaling // operations m = xStep > 0 ? xStep : 1; p = colorBuf + xSrc; pixAcc0 = 0; for (i = 0; i < n; ++i) { for (j = 0; j < m; ++j) { pixAcc0 += *p++; } p += w - m; } pixMul = (SplashCoord)1 / (SplashCoord)(n * m); pix[0] = (int)((SplashCoord)pixAcc0 * pixMul); // set pixel if (vectorAntialias && clipRes != splashClipAllInside) { pipe.shape = (SplashCoord)1; drawAAPixel(&pipe, tx + x2, ty + y2); } else { drawPixel(&pipe, tx + x2, ty + y2, clipRes2 == splashClipAllInside); } // x scale Bresenham xSrc += xStep; // x shear x1 += xSign; // y shear y1 += yShear1; } break; case splashModeRGB8: case splashModeBGR8: for (x = 0; x < scaledWidth; ++x) { // x scale Bresenham xStep = xp; xt += xq; if (xt >= scaledWidth) { xt -= scaledWidth; ++xStep; } // rotation if (rot) { x2 = (int)y1; y2 = -x1; } else { x2 = x1; y2 = (int)y1; } // compute the filtered pixel at (x,y) after the x and y scaling // operations m = xStep > 0 ? xStep : 1; p = colorBuf + xSrc * 3; pixAcc0 = pixAcc1 = pixAcc2 = 0; for (i = 0; i < n; ++i) { for (j = 0; j < m; ++j) { pixAcc0 += *p++; pixAcc1 += *p++; pixAcc2 += *p++; } p += 3 * (w - m); } pixMul = (SplashCoord)1 / (SplashCoord)(n * m); pix[0] = (int)((SplashCoord)pixAcc0 * pixMul); pix[1] = (int)((SplashCoord)pixAcc1 * pixMul); pix[2] = (int)((SplashCoord)pixAcc2 * pixMul); // set pixel if (vectorAntialias && clipRes != splashClipAllInside) { pipe.shape = (SplashCoord)1; drawAAPixel(&pipe, tx + x2, ty + y2); } else { drawPixel(&pipe, tx + x2, ty + y2, clipRes2 == splashClipAllInside); } // x scale Bresenham xSrc += xStep; // x shear x1 += xSign; // y shear y1 += yShear1; } break; #if SPLASH_CMYK case splashModeCMYK8: for (x = 0; x < scaledWidth; ++x) { // x scale Bresenham xStep = xp; xt += xq; if (xt >= scaledWidth) { xt -= scaledWidth; ++xStep; } // rotation if (rot) { x2 = (int)y1; y2 = -x1; } else { x2 = x1; y2 = (int)y1; } // compute the filtered pixel at (x,y) after the x and y scaling // operations m = xStep > 0 ? xStep : 1; p = colorBuf + xSrc * 4; pixAcc0 = pixAcc1 = pixAcc2 = pixAcc3 = 0; for (i = 0; i < n; ++i) { for (j = 0; j < m; ++j) { pixAcc0 += *p++; pixAcc1 += *p++; pixAcc2 += *p++; pixAcc3 += *p++; } p += 4 * (w - m); } pixMul = (SplashCoord)1 / (SplashCoord)(n * m); pix[0] = (int)((SplashCoord)pixAcc0 * pixMul); pix[1] = (int)((SplashCoord)pixAcc1 * pixMul); pix[2] = (int)((SplashCoord)pixAcc2 * pixMul); pix[3] = (int)((SplashCoord)pixAcc3 * pixMul); // set pixel if (vectorAntialias && clipRes != splashClipAllInside) { pipe.shape = (SplashCoord)1; drawAAPixel(&pipe, tx + x2, ty + y2); } else { drawPixel(&pipe, tx + x2, ty + y2, clipRes2 == splashClipAllInside); } // x scale Bresenham xSrc += xStep; // x shear x1 += xSign; // y shear y1 += yShear1; } break; #endif // SPLASH_CMYK } } } gfree(colorBuf); gfree(alphaBuf); return splashOk; } SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc, int xDest, int yDest, int w, int h, GBool noClip, GBool nonIsolated) { SplashPipe pipe; SplashColor pixel; Guchar alpha; Guchar *ap; int x, y; if (src->mode != bitmap->mode) { return splashErrModeMismatch; } if (src->alpha) { pipeInit(&pipe, xDest, yDest, NULL, pixel, state->fillAlpha, gTrue, nonIsolated); for (y = 0; y < h; ++y) { pipeSetXY(&pipe, xDest, yDest + y); ap = src->getAlphaPtr() + (ySrc + y) * src->getWidth() + xSrc; for (x = 0; x < w; ++x) { src->getPixel(xSrc + x, ySrc + y, pixel); alpha = *ap++; if (noClip || state->clip->test(xDest + x, yDest + y)) { // this uses shape instead of alpha, which isn't technically // correct, but works out the same pipe.shape = (SplashCoord)(alpha / 255.0); pipeRun(&pipe); updateModX(xDest + x); updateModY(yDest + y); } else { pipeIncX(&pipe); } } } } else { pipeInit(&pipe, xDest, yDest, NULL, pixel, state->fillAlpha, gFalse, nonIsolated); for (y = 0; y < h; ++y) { pipeSetXY(&pipe, xDest, yDest + y); for (x = 0; x < w; ++x) { src->getPixel(xSrc + x, ySrc + y, pixel); if (noClip || state->clip->test(xDest + x, yDest + y)) { pipeRun(&pipe); updateModX(xDest + x); updateModY(yDest + y); } else { pipeIncX(&pipe); } } } } return splashOk; } void Splash::compositeBackground(SplashColorPtr color) { SplashColorPtr p; Guchar *q; Guchar alpha, alpha1, c, color0, color1, color2, color3; int x, y, mask; switch (bitmap->mode) { case splashModeMono1: color0 = color[0]; for (y = 0; y < bitmap->height; ++y) { p = &bitmap->data[y * bitmap->rowSize]; q = &bitmap->alpha[y * bitmap->width]; mask = 0x80; for (x = 0; x < bitmap->width; ++x) { alpha = *q++; alpha1 = 255 - alpha; c = (*p & mask) ? 0xff : 0x00; c = div255(alpha1 * color0 + alpha * c); if (c & 0x80) { *p |= mask; } else { *p &= ~mask; } if (!(mask >>= 1)) { mask = 0x80; ++p; } } } break; case splashModeMono8: color0 = color[0]; for (y = 0; y < bitmap->height; ++y) { p = &bitmap->data[y * bitmap->rowSize]; q = &bitmap->alpha[y * bitmap->width]; for (x = 0; x < bitmap->width; ++x) { alpha = *q++; alpha1 = 255 - alpha; p[0] = div255(alpha1 * color0 + alpha * p[0]); ++p; } } break; case splashModeRGB8: case splashModeBGR8: color0 = color[0]; color1 = color[1]; color2 = color[2]; for (y = 0; y < bitmap->height; ++y) { p = &bitmap->data[y * bitmap->rowSize]; q = &bitmap->alpha[y * bitmap->width]; for (x = 0; x < bitmap->width; ++x) { alpha = *q++; alpha1 = 255 - alpha; p[0] = div255(alpha1 * color0 + alpha * p[0]); p[1] = div255(alpha1 * color1 + alpha * p[1]); p[2] = div255(alpha1 * color2 + alpha * p[2]); p += 3; } } break; #if SPLASH_CMYK case splashModeCMYK8: color0 = color[0]; color1 = color[1]; color2 = color[2]; color3 = color[3]; for (y = 0; y < bitmap->height; ++y) { p = &bitmap->data[y * bitmap->rowSize]; q = &bitmap->alpha[y * bitmap->width]; for (x = 0; x < bitmap->width; ++x) { alpha = *q++; alpha1 = 255 - alpha; p[0] = div255(alpha1 * color0 + alpha * p[0]); p[1] = div255(alpha1 * color1 + alpha * p[1]); p[2] = div255(alpha1 * color2 + alpha * p[2]); p[3] = div255(alpha1 * color3 + alpha * p[3]); p += 4; } } break; #endif } memset(bitmap->alpha, 255, bitmap->width * bitmap->height); } SplashError Splash::blitTransparent(SplashBitmap *src, int xSrc, int ySrc, int xDest, int yDest, int w, int h) { SplashColor pixel; SplashColorPtr p; Guchar *q; int x, y, mask; if (src->mode != bitmap->mode) { return splashErrModeMismatch; } switch (bitmap->mode) { case splashModeMono1: for (y = 0; y < h; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + (xDest >> 3)]; mask = 0x80 >> (xDest & 7); for (x = 0; x < w; ++x) { src->getPixel(xSrc + x, ySrc + y, pixel); if (pixel[0]) { *p |= mask; } else { *p &= ~mask; } if (!(mask >>= 1)) { mask = 0x80; ++p; } } } break; case splashModeMono8: for (y = 0; y < h; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + xDest]; for (x = 0; x < w; ++x) { src->getPixel(xSrc + x, ySrc + y, pixel); *p++ = pixel[0]; } } break; case splashModeRGB8: case splashModeBGR8: for (y = 0; y < h; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + 3 * xDest]; for (x = 0; x < w; ++x) { src->getPixel(xSrc + x, ySrc + y, pixel); *p++ = pixel[0]; *p++ = pixel[1]; *p++ = pixel[2]; } } break; #if SPLASH_CMYK case splashModeCMYK8: for (y = 0; y < h; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + 4 * xDest]; for (x = 0; x < w; ++x) { src->getPixel(xSrc + x, ySrc + y, pixel); *p++ = pixel[0]; *p++ = pixel[1]; *p++ = pixel[2]; *p++ = pixel[3]; } } break; #endif } if (bitmap->alpha) { for (y = 0; y < h; ++y) { q = &bitmap->alpha[(yDest + y) * bitmap->width + xDest]; for (x = 0; x < w; ++x) { *q++ = 0x00; } } } return splashOk; } SplashPath *Splash::makeStrokePath(SplashPath *path, GBool flatten) { SplashPath *pathIn, *pathOut; SplashCoord w, d, dx, dy, wdx, wdy, dxNext, dyNext, wdxNext, wdyNext; SplashCoord crossprod, dotprod, miter, m; GBool first, last, closed; int subpathStart, next, i; int left0, left1, left2, right0, right1, right2, join0, join1, join2; int leftFirst, rightFirst, firstPt; if (flatten) { pathIn = flattenPath(path, state->matrix, state->flatness); if (state->lineDashLength > 0) { pathOut = makeDashedPath(pathIn); delete pathIn; pathIn = pathOut; } } else { pathIn = path; } subpathStart = 0; // make gcc happy closed = gFalse; // make gcc happy left0 = left1 = right0 = right1 = join0 = join1 = 0; // make gcc happy leftFirst = rightFirst = firstPt = 0; // make gcc happy pathOut = new SplashPath(); w = state->lineWidth; for (i = 0; i < pathIn->length - 1; ++i) { if (pathIn->flags[i] & splashPathLast) { continue; } if ((first = pathIn->flags[i] & splashPathFirst)) { subpathStart = i; closed = pathIn->flags[i] & splashPathClosed; } last = pathIn->flags[i+1] & splashPathLast; // compute the deltas for segment (i, i+1) d = splashDist(pathIn->pts[i].x, pathIn->pts[i].y, pathIn->pts[i+1].x, pathIn->pts[i+1].y); if (d == 0) { // we need to draw end caps on zero-length lines //~ not clear what the behavior should be for splashLineCapButt //~ with d==0 dx = 0; dy = 1; } else { d = (SplashCoord)1 / d; dx = d * (pathIn->pts[i+1].x - pathIn->pts[i].x); dy = d * (pathIn->pts[i+1].y - pathIn->pts[i].y); } wdx = (SplashCoord)0.5 * w * dx; wdy = (SplashCoord)0.5 * w * dy; // compute the deltas for segment (i+1, next) next = last ? subpathStart + 1 : i + 2; d = splashDist(pathIn->pts[i+1].x, pathIn->pts[i+1].y, pathIn->pts[next].x, pathIn->pts[next].y); if (d == 0) { // we need to draw end caps on zero-length lines //~ not clear what the behavior should be for splashLineCapButt //~ with d==0 dxNext = 0; dyNext = 1; } else { d = (SplashCoord)1 / d; dxNext = d * (pathIn->pts[next].x - pathIn->pts[i+1].x); dyNext = d * (pathIn->pts[next].y - pathIn->pts[i+1].y); } wdxNext = (SplashCoord)0.5 * w * dxNext; wdyNext = (SplashCoord)0.5 * w * dyNext; // draw the start cap pathOut->moveTo(pathIn->pts[i].x - wdy, pathIn->pts[i].y + wdx); if (i == subpathStart) { firstPt = pathOut->length - 1; } if (first && !closed) { switch (state->lineCap) { case splashLineCapButt: pathOut->lineTo(pathIn->pts[i].x + wdy, pathIn->pts[i].y - wdx); break; case splashLineCapRound: pathOut->curveTo(pathIn->pts[i].x - wdy - bezierCircle * wdx, pathIn->pts[i].y + wdx - bezierCircle * wdy, pathIn->pts[i].x - wdx - bezierCircle * wdy, pathIn->pts[i].y - wdy + bezierCircle * wdx, pathIn->pts[i].x - wdx, pathIn->pts[i].y - wdy); pathOut->curveTo(pathIn->pts[i].x - wdx + bezierCircle * wdy, pathIn->pts[i].y - wdy - bezierCircle * wdx, pathIn->pts[i].x + wdy - bezierCircle * wdx, pathIn->pts[i].y - wdx - bezierCircle * wdy, pathIn->pts[i].x + wdy, pathIn->pts[i].y - wdx); break; case splashLineCapProjecting: pathOut->lineTo(pathIn->pts[i].x - wdx - wdy, pathIn->pts[i].y + wdx - wdy); pathOut->lineTo(pathIn->pts[i].x - wdx + wdy, pathIn->pts[i].y - wdx - wdy); pathOut->lineTo(pathIn->pts[i].x + wdy, pathIn->pts[i].y - wdx); break; } } else { pathOut->lineTo(pathIn->pts[i].x + wdy, pathIn->pts[i].y - wdx); } // draw the left side of the segment rectangle left2 = pathOut->length - 1; pathOut->lineTo(pathIn->pts[i+1].x + wdy, pathIn->pts[i+1].y - wdx); // draw the end cap if (last && !closed) { switch (state->lineCap) { case splashLineCapButt: pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx); break; case splashLineCapRound: pathOut->curveTo(pathIn->pts[i+1].x + wdy + bezierCircle * wdx, pathIn->pts[i+1].y - wdx + bezierCircle * wdy, pathIn->pts[i+1].x + wdx + bezierCircle * wdy, pathIn->pts[i+1].y + wdy - bezierCircle * wdx, pathIn->pts[i+1].x + wdx, pathIn->pts[i+1].y + wdy); pathOut->curveTo(pathIn->pts[i+1].x + wdx - bezierCircle * wdy, pathIn->pts[i+1].y + wdy + bezierCircle * wdx, pathIn->pts[i+1].x - wdy + bezierCircle * wdx, pathIn->pts[i+1].y + wdx + bezierCircle * wdy, pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx); break; case splashLineCapProjecting: pathOut->lineTo(pathIn->pts[i+1].x + wdy + wdx, pathIn->pts[i+1].y - wdx + wdy); pathOut->lineTo(pathIn->pts[i+1].x - wdy + wdx, pathIn->pts[i+1].y + wdx + wdy); pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx); break; } } else { pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx); } // draw the right side of the segment rectangle right2 = pathOut->length - 1; pathOut->close(); // draw the join join2 = pathOut->length; if (!last || closed) { crossprod = dx * dyNext - dy * dxNext; dotprod = -(dx * dxNext + dy * dyNext); if (dotprod > 0.99999) { // avoid a divide-by-zero -- set miter to something arbitrary // such that sqrt(miter) will exceed miterLimit (and m is never // used in that situation) miter = (state->miterLimit + 1) * (state->miterLimit + 1); m = 0; } else { miter = (SplashCoord)2 / ((SplashCoord)1 - dotprod); if (miter < 1) { // this can happen because of floating point inaccuracies miter = 1; } m = splashSqrt(miter - 1); } // round join if (state->lineJoin == splashLineJoinRound) { pathOut->moveTo(pathIn->pts[i+1].x + (SplashCoord)0.5 * w, pathIn->pts[i+1].y); pathOut->curveTo(pathIn->pts[i+1].x + (SplashCoord)0.5 * w, pathIn->pts[i+1].y + bezierCircle2 * w, pathIn->pts[i+1].x + bezierCircle2 * w, pathIn->pts[i+1].y + (SplashCoord)0.5 * w, pathIn->pts[i+1].x, pathIn->pts[i+1].y + (SplashCoord)0.5 * w); pathOut->curveTo(pathIn->pts[i+1].x - bezierCircle2 * w, pathIn->pts[i+1].y + (SplashCoord)0.5 * w, pathIn->pts[i+1].x - (SplashCoord)0.5 * w, pathIn->pts[i+1].y + bezierCircle2 * w, pathIn->pts[i+1].x - (SplashCoord)0.5 * w, pathIn->pts[i+1].y); pathOut->curveTo(pathIn->pts[i+1].x - (SplashCoord)0.5 * w, pathIn->pts[i+1].y - bezierCircle2 * w, pathIn->pts[i+1].x - bezierCircle2 * w, pathIn->pts[i+1].y - (SplashCoord)0.5 * w, pathIn->pts[i+1].x, pathIn->pts[i+1].y - (SplashCoord)0.5 * w); pathOut->curveTo(pathIn->pts[i+1].x + bezierCircle2 * w, pathIn->pts[i+1].y - (SplashCoord)0.5 * w, pathIn->pts[i+1].x + (SplashCoord)0.5 * w, pathIn->pts[i+1].y - bezierCircle2 * w, pathIn->pts[i+1].x + (SplashCoord)0.5 * w, pathIn->pts[i+1].y); } else { pathOut->moveTo(pathIn->pts[i+1].x, pathIn->pts[i+1].y); // angle < 180 if (crossprod < 0) { pathOut->lineTo(pathIn->pts[i+1].x - wdyNext, pathIn->pts[i+1].y + wdxNext); // miter join inside limit if (state->lineJoin == splashLineJoinMiter && splashSqrt(miter) <= state->miterLimit) { pathOut->lineTo(pathIn->pts[i+1].x - wdy + wdx * m, pathIn->pts[i+1].y + wdx + wdy * m); pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx); // bevel join or miter join outside limit } else { pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx); } // angle >= 180 } else { pathOut->lineTo(pathIn->pts[i+1].x + wdy, pathIn->pts[i+1].y - wdx); // miter join inside limit if (state->lineJoin == splashLineJoinMiter && splashSqrt(miter) <= state->miterLimit) { pathOut->lineTo(pathIn->pts[i+1].x + wdy + wdx * m, pathIn->pts[i+1].y - wdx + wdy * m); pathOut->lineTo(pathIn->pts[i+1].x + wdyNext, pathIn->pts[i+1].y - wdxNext); // bevel join or miter join outside limit } else { pathOut->lineTo(pathIn->pts[i+1].x + wdyNext, pathIn->pts[i+1].y - wdxNext); } } } pathOut->close(); } // add stroke adjustment hints if (state->strokeAdjust) { if (i >= subpathStart + 1) { if (i >= subpathStart + 2) { pathOut->addStrokeAdjustHint(left1, right1, left0 + 1, right0); pathOut->addStrokeAdjustHint(left1, right1, join0, left2); } else { pathOut->addStrokeAdjustHint(left1, right1, firstPt, left2); } pathOut->addStrokeAdjustHint(left1, right1, right2 + 1, right2 + 1); } left0 = left1; left1 = left2; right0 = right1; right1 = right2; join0 = join1; join1 = join2; if (i == subpathStart) { leftFirst = left2; rightFirst = right2; } if (last) { if (i >= subpathStart + 2) { pathOut->addStrokeAdjustHint(left1, right1, left0 + 1, right0); pathOut->addStrokeAdjustHint(left1, right1, join0, pathOut->length - 1); } else { pathOut->addStrokeAdjustHint(left1, right1, firstPt, pathOut->length - 1); } if (closed) { pathOut->addStrokeAdjustHint(left1, right1, firstPt, leftFirst); pathOut->addStrokeAdjustHint(left1, right1, rightFirst + 1, rightFirst + 1); pathOut->addStrokeAdjustHint(leftFirst, rightFirst, left1 + 1, right1); pathOut->addStrokeAdjustHint(leftFirst, rightFirst, join1, pathOut->length - 1); } } } } if (pathIn != path) { delete pathIn; } return pathOut; } void Splash::dumpPath(SplashPath *path) { int i; for (i = 0; i < path->length; ++i) { printf(" %3d: x=%8.2f y=%8.2f%s%s%s%s\n", i, (double)path->pts[i].x, (double)path->pts[i].y, (path->flags[i] & splashPathFirst) ? " first" : "", (path->flags[i] & splashPathLast) ? " last" : "", (path->flags[i] & splashPathClosed) ? " closed" : "", (path->flags[i] & splashPathCurve) ? " curve" : ""); } } void Splash::dumpXPath(SplashXPath *path) { int i; for (i = 0; i < path->length; ++i) { printf(" %4d: x0=%8.2f y0=%8.2f x1=%8.2f y1=%8.2f %s%s%s%s%s%s%s\n", i, (double)path->segs[i].x0, (double)path->segs[i].y0, (double)path->segs[i].x1, (double)path->segs[i].y1, (path->segs[i].flags & splashXPathFirst) ? "F" : " ", (path->segs[i].flags & splashXPathLast) ? "L" : " ", (path->segs[i].flags & splashXPathEnd0) ? "0" : " ", (path->segs[i].flags & splashXPathEnd1) ? "1" : " ", (path->segs[i].flags & splashXPathHoriz) ? "H" : " ", (path->segs[i].flags & splashXPathVert) ? "V" : " ", (path->segs[i].flags & splashXPathFlip) ? "P" : " "); } }