//======================================================================== // // Splash.cc // //======================================================================== //======================================================================== // // Modified under the Poppler project - http://poppler.freedesktop.org // // All changes made under the Poppler project to this file are licensed // under GPL version 2 or later // // Copyright (C) 2005-2020 Albert Astals Cid // Copyright (C) 2005 Marco Pesenti Gritti // Copyright (C) 2010-2016 Thomas Freitag // Copyright (C) 2010 Christian Feuersänger // Copyright (C) 2011-2013, 2015 William Bader // Copyright (C) 2012 Markus Trippelsdorf // Copyright (C) 2012, 2017 Adrian Johnson // Copyright (C) 2012 Matthias Kramm // Copyright (C) 2018, 2019 Stefan Brüns // Copyright (C) 2018 Adam Reichold // Copyright (C) 2019, 2020 Oliver Sander // Copyright (C) 2019 Marek Kasik // Copyright (C) 2020 Tobias Deiminger // // To see a description of the changes please see the Changelog file that // came with your tarball or type make ChangeLog if you are building from git // //======================================================================== #include #include #include #include #include #include #include "goo/gmem.h" #include "goo/GooLikely.h" #include "poppler/Error.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" #include //------------------------------------------------------------------------ #define splashAAGamma 1.5 // 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 unsigned char div255(int x) { return (unsigned char)((x + (x >> 8) + 0x80) >> 8); } // Clip x to lie in [0, 255]. static inline unsigned char clip255(int x) { return x < 0 ? 0 : x > 255 ? 255 : x; } template inline void Guswap(T &a, T &b) { T tmp = a; a = b; b = tmp; } // The PDF spec says that all pixels whose *centers* lie within the // image target region get painted, so we want to round n+0.5 down to // n. But this causes problems, e.g., with PDF files that fill a // rectangle with black and then draw an image to the exact same // rectangle, so we instead use the fill scan conversion rule. // However, the correct rule works better for glyphs, so we also // provide that option in fillImageMask. #if 0 static inline int imgCoordMungeLower(SplashCoord x) { return splashCeil(x + 0.5) - 1; } static inline int imgCoordMungeUpper(SplashCoord x) { return splashCeil(x + 0.5) - 1; } #else static inline int imgCoordMungeLower(SplashCoord x) { return splashFloor(x); } static inline int imgCoordMungeUpper(SplashCoord x) { return splashFloor(x) + 1; } static inline int imgCoordMungeLowerC(SplashCoord x, bool glyphMode) { return glyphMode ? (splashCeil(x + 0.5) - 1) : splashFloor(x); } static inline int imgCoordMungeUpperC(SplashCoord x, bool glyphMode) { return glyphMode ? (splashCeil(x + 0.5) - 1) : (splashFloor(x) + 1); } #endif // Used by drawImage and fillImageMask to divide the target // quadrilateral into sections. struct ImageSection { int y0, y1; // actual y range int ia0, ia1; // vertex indices for edge A int ib0, ib1; // vertex indices for edge A SplashCoord xa0, ya0, xa1, ya1; // edge A SplashCoord dxdya; // slope of edge A SplashCoord xb0, yb0, xb1, yb1; // edge B SplashCoord dxdyb; // slope of edge B }; //------------------------------------------------------------------------ // SplashPipe //------------------------------------------------------------------------ #define splashPipeMaxStages 9 struct SplashPipe { // pixel coordinates int x, y; // source pattern SplashPattern *pattern; // source alpha and color unsigned char aInput; bool usesShape; SplashColorPtr cSrc; SplashColor cSrcVal = {}; // non-isolated group alpha0 unsigned char *alpha0Ptr; // knockout groups bool knockout; unsigned char knockoutOpacity; // soft mask SplashColorPtr softMaskPtr; // destination alpha and color SplashColorPtr destColorPtr; int destColorMask; unsigned char *destAlphaPtr; // shape unsigned char shape; // result alpha and color bool noTransparency; SplashPipeResultColorCtrl resultColorCtrl; // non-isolated group correction bool nonIsolatedGroup; // the "run" function void (Splash::*run)(SplashPipe *pipe); }; SplashPipeResultColorCtrl Splash::pipeResultColorNoAlphaBlend[] = { splashPipeResultColorNoAlphaBlendMono, splashPipeResultColorNoAlphaBlendMono, splashPipeResultColorNoAlphaBlendRGB, splashPipeResultColorNoAlphaBlendRGB, splashPipeResultColorNoAlphaBlendRGB, splashPipeResultColorNoAlphaBlendCMYK, splashPipeResultColorNoAlphaBlendDeviceN }; SplashPipeResultColorCtrl Splash::pipeResultColorAlphaNoBlend[] = { splashPipeResultColorAlphaNoBlendMono, splashPipeResultColorAlphaNoBlendMono, splashPipeResultColorAlphaNoBlendRGB, splashPipeResultColorAlphaNoBlendRGB, splashPipeResultColorAlphaNoBlendRGB, splashPipeResultColorAlphaNoBlendCMYK, splashPipeResultColorAlphaNoBlendDeviceN }; SplashPipeResultColorCtrl Splash::pipeResultColorAlphaBlend[] = { splashPipeResultColorAlphaBlendMono, splashPipeResultColorAlphaBlendMono, splashPipeResultColorAlphaBlendRGB, splashPipeResultColorAlphaBlendRGB, splashPipeResultColorAlphaBlendRGB, splashPipeResultColorAlphaBlendCMYK, splashPipeResultColorAlphaBlendDeviceN }; //------------------------------------------------------------------------ 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]; } } //------------------------------------------------------------------------ // pipeline //------------------------------------------------------------------------ inline void Splash::pipeInit(SplashPipe *pipe, int x, int y, SplashPattern *pattern, SplashColorPtr cSrc, unsigned char aInput, bool usesShape, bool nonIsolatedGroup, bool knockout, unsigned char knockoutOpacity) { pipeSetXY(pipe, x, y); pipe->pattern = nullptr; // 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; pipe->usesShape = usesShape; pipe->shape = 0; // knockout pipe->knockout = knockout; pipe->knockoutOpacity = knockoutOpacity; // result alpha if (aInput == 255 && !state->softMask && !usesShape && !state->inNonIsolatedGroup && !nonIsolatedGroup) { pipe->noTransparency = true; } else { pipe->noTransparency = false; } // 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 pipe->nonIsolatedGroup = nonIsolatedGroup; // select the 'run' function pipe->run = &Splash::pipeRun; if (!pipe->pattern && pipe->noTransparency && !state->blendFunc) { if (bitmap->mode == splashModeMono1 && !pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunSimpleMono1; } else if (bitmap->mode == splashModeMono8 && pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunSimpleMono8; } else if (bitmap->mode == splashModeRGB8 && pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunSimpleRGB8; } else if (bitmap->mode == splashModeXBGR8 && pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunSimpleXBGR8; } else if (bitmap->mode == splashModeBGR8 && pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunSimpleBGR8; } else if (bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunSimpleCMYK8; } else if (bitmap->mode == splashModeDeviceN8 && pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunSimpleDeviceN8; } } else if (!pipe->pattern && !pipe->noTransparency && !state->softMask && pipe->usesShape && !(state->inNonIsolatedGroup && alpha0Bitmap->alpha) && !state->blendFunc && !pipe->nonIsolatedGroup) { if (bitmap->mode == splashModeMono1 && !pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunAAMono1; } else if (bitmap->mode == splashModeMono8 && pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunAAMono8; } else if (bitmap->mode == splashModeRGB8 && pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunAARGB8; } else if (bitmap->mode == splashModeXBGR8 && pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunAAXBGR8; } else if (bitmap->mode == splashModeBGR8 && pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunAABGR8; } else if (bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunAACMYK8; } else if (bitmap->mode == splashModeDeviceN8 && pipe->destAlphaPtr) { pipe->run = &Splash::pipeRunAADeviceN8; } } } // general case void Splash::pipeRun(SplashPipe *pipe) { unsigned char aSrc, aDest, alphaI, alphaIm1, alpha0, aResult; SplashColor cSrcNonIso, cDest, cBlend; SplashColorPtr cSrc; unsigned char cResult0, cResult1, cResult2, cResult3; int t; int cp, mask; unsigned char cResult[SPOT_NCOMPS + 4]; //----- source color // static pattern: handled in pipeInit // fixed color: handled in pipeInit // dynamic pattern if (pipe->pattern) { if (!pipe->pattern->getColor(pipe->x, pipe->y, pipe->cSrcVal)) { pipeIncX(pipe); return; } if (bitmap->mode == splashModeCMYK8 || bitmap->mode == splashModeDeviceN8) { if (state->fillOverprint && state->overprintMode && pipe->pattern->isCMYK()) { unsigned int overprintMask = 15; if (pipe->cSrcVal[0] == 0) { overprintMask &= ~1; } if (pipe->cSrcVal[1] == 0) { overprintMask &= ~2; } if (pipe->cSrcVal[2] == 0) { overprintMask &= ~4; } if (pipe->cSrcVal[3] == 0) { overprintMask &= ~8; } state->overprintMask = overprintMask; } } } if (pipe->noTransparency && !state->blendFunc) { //----- write destination pixel switch (bitmap->mode) { case splashModeMono1: cResult0 = state->grayTransfer[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++ = state->grayTransfer[pipe->cSrc[0]]; break; case splashModeRGB8: *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]]; *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]]; *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]]; break; case splashModeXBGR8: *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]]; *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]]; *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]]; *pipe->destColorPtr++ = 255; break; case splashModeBGR8: *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]]; *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]]; *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]]; break; case splashModeCMYK8: if (state->overprintMask & 1) { pipe->destColorPtr[0] = (state->overprintAdditive) ? std::min(pipe->destColorPtr[0] + state->cmykTransferC[pipe->cSrc[0]], 255) : state->cmykTransferC[pipe->cSrc[0]]; } if (state->overprintMask & 2) { pipe->destColorPtr[1] = (state->overprintAdditive) ? std::min(pipe->destColorPtr[1] + state->cmykTransferM[pipe->cSrc[1]], 255) : state->cmykTransferM[pipe->cSrc[1]]; } if (state->overprintMask & 4) { pipe->destColorPtr[2] = (state->overprintAdditive) ? std::min(pipe->destColorPtr[2] + state->cmykTransferY[pipe->cSrc[2]], 255) : state->cmykTransferY[pipe->cSrc[2]]; } if (state->overprintMask & 8) { pipe->destColorPtr[3] = (state->overprintAdditive) ? std::min(pipe->destColorPtr[3] + state->cmykTransferK[pipe->cSrc[3]], 255) : state->cmykTransferK[pipe->cSrc[3]]; } pipe->destColorPtr += 4; break; case splashModeDeviceN8: mask = 1; for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) { if (state->overprintMask & mask) { pipe->destColorPtr[cp] = state->deviceNTransfer[cp][pipe->cSrc[cp]]; } mask <<= 1; } pipe->destColorPtr += (SPOT_NCOMPS + 4); break; } if (pipe->destAlphaPtr) { *pipe->destAlphaPtr++ = 255; } } else { //----- read destination pixel unsigned char *destColorPtr; if (pipe->shape && state->blendFunc && pipe->knockout && alpha0Bitmap != nullptr) { destColorPtr = alpha0Bitmap->data + (alpha0Y + pipe->y) * alpha0Bitmap->rowSize; switch (bitmap->mode) { case splashModeMono1: destColorPtr += (alpha0X + pipe->x) / 8; break; case splashModeMono8: destColorPtr += (alpha0X + pipe->x); break; case splashModeRGB8: case splashModeBGR8: destColorPtr += (alpha0X + pipe->x) * 3; break; case splashModeXBGR8: case splashModeCMYK8: destColorPtr += (alpha0X + pipe->x) * 4; break; case splashModeDeviceN8: destColorPtr += (alpha0X + pipe->x) * (SPOT_NCOMPS + 4); break; } } else { destColorPtr = pipe->destColorPtr; } switch (bitmap->mode) { case splashModeMono1: cDest[0] = (*destColorPtr & pipe->destColorMask) ? 0xff : 0x00; break; case splashModeMono8: cDest[0] = *destColorPtr; break; case splashModeRGB8: cDest[0] = destColorPtr[0]; cDest[1] = destColorPtr[1]; cDest[2] = destColorPtr[2]; break; case splashModeXBGR8: cDest[0] = destColorPtr[2]; cDest[1] = destColorPtr[1]; cDest[2] = destColorPtr[0]; cDest[3] = 255; break; case splashModeBGR8: cDest[0] = destColorPtr[2]; cDest[1] = destColorPtr[1]; cDest[2] = destColorPtr[0]; break; case splashModeCMYK8: cDest[0] = destColorPtr[0]; cDest[1] = destColorPtr[1]; cDest[2] = destColorPtr[2]; cDest[3] = destColorPtr[3]; break; case splashModeDeviceN8: for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) cDest[cp] = destColorPtr[cp]; break; } if (pipe->destAlphaPtr) { aDest = *pipe->destAlphaPtr; } else { aDest = 0xff; } //----- source alpha if (state->softMask) { if (pipe->usesShape) { aSrc = div255(div255(pipe->aInput * *pipe->softMaskPtr++) * pipe->shape); } else { aSrc = div255(pipe->aInput * *pipe->softMaskPtr++); } } else if (pipe->usesShape) { aSrc = div255(pipe->aInput * pipe->shape); } else { aSrc = pipe->aInput; } //----- non-isolated group correction if (pipe->nonIsolatedGroup) { // This path is only used when Splash::composite() is called to // composite a non-isolated group onto the backdrop. In this // case, pipe->shape is the source (group) alpha. if (pipe->shape == 0) { // this value will be multiplied by zero later, so it doesn't // matter what we use cSrc = pipe->cSrc; } else { t = (aDest * 255) / pipe->shape - aDest; switch (bitmap->mode) { case splashModeDeviceN8: for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) cSrcNonIso[cp] = clip255(pipe->cSrc[cp] + ((pipe->cSrc[cp] - cDest[cp]) * t) / 255); break; case splashModeCMYK8: for (cp = 0; cp < 4; cp++) cSrcNonIso[cp] = clip255(pipe->cSrc[cp] + ((pipe->cSrc[cp] - cDest[cp]) * t) / 255); break; case splashModeXBGR8: cSrcNonIso[3] = 255; // fallthrough case splashModeRGB8: case splashModeBGR8: cSrcNonIso[2] = clip255(pipe->cSrc[2] + ((pipe->cSrc[2] - cDest[2]) * t) / 255); cSrcNonIso[1] = clip255(pipe->cSrc[1] + ((pipe->cSrc[1] - cDest[1]) * t) / 255); // fallthrough case splashModeMono1: case splashModeMono8: cSrcNonIso[0] = clip255(pipe->cSrc[0] + ((pipe->cSrc[0] - cDest[0]) * t) / 255); break; } cSrc = cSrcNonIso; // knockout: remove backdrop color if (pipe->knockout && pipe->shape >= pipe->knockoutOpacity) { aDest = 0; } } } else { cSrc = pipe->cSrc; } //----- blend function if (state->blendFunc) { if (bitmap->mode == splashModeDeviceN8) { for (int k = 4; k < 4 + SPOT_NCOMPS; k++) { cBlend[k] = 0; } } (*state->blendFunc)(cSrc, cDest, cBlend, bitmap->mode); } //----- result alpha and non-isolated group element correction if (pipe->noTransparency) { alphaI = alphaIm1 = aResult = 255; } else { aResult = aSrc + aDest - div255(aSrc * aDest); // alphaI = alpha_i // alphaIm1 = alpha_(i-1) if (pipe->alpha0Ptr) { alpha0 = *pipe->alpha0Ptr++; alphaI = aResult + alpha0 - div255(aResult * alpha0); alphaIm1 = alpha0 + aDest - div255(alpha0 * aDest); } else { alphaI = aResult; alphaIm1 = aDest; } } //----- result color cResult0 = cResult1 = cResult2 = cResult3 = 0; // make gcc happy switch (pipe->resultColorCtrl) { case splashPipeResultColorNoAlphaBlendMono: cResult0 = state->grayTransfer[div255((255 - aDest) * cSrc[0] + aDest * cBlend[0])]; break; case splashPipeResultColorNoAlphaBlendRGB: cResult0 = state->rgbTransferR[div255((255 - aDest) * cSrc[0] + aDest * cBlend[0])]; cResult1 = state->rgbTransferG[div255((255 - aDest) * cSrc[1] + aDest * cBlend[1])]; cResult2 = state->rgbTransferB[div255((255 - aDest) * cSrc[2] + aDest * cBlend[2])]; break; case splashPipeResultColorNoAlphaBlendCMYK: cResult0 = state->cmykTransferC[div255((255 - aDest) * cSrc[0] + aDest * cBlend[0])]; cResult1 = state->cmykTransferM[div255((255 - aDest) * cSrc[1] + aDest * cBlend[1])]; cResult2 = state->cmykTransferY[div255((255 - aDest) * cSrc[2] + aDest * cBlend[2])]; cResult3 = state->cmykTransferK[div255((255 - aDest) * cSrc[3] + aDest * cBlend[3])]; break; case splashPipeResultColorNoAlphaBlendDeviceN: for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) cResult[cp] = state->deviceNTransfer[cp][div255((255 - aDest) * cSrc[cp] + aDest * cBlend[cp])]; break; case splashPipeResultColorAlphaNoBlendMono: if (alphaI == 0) { cResult0 = 0; } else { cResult0 = state->grayTransfer[((alphaI - aSrc) * cDest[0] + aSrc * cSrc[0]) / alphaI]; } break; case splashPipeResultColorAlphaNoBlendRGB: if (alphaI == 0) { cResult0 = 0; cResult1 = 0; cResult2 = 0; } else { cResult0 = state->rgbTransferR[((alphaI - aSrc) * cDest[0] + aSrc * cSrc[0]) / alphaI]; cResult1 = state->rgbTransferG[((alphaI - aSrc) * cDest[1] + aSrc * cSrc[1]) / alphaI]; cResult2 = state->rgbTransferB[((alphaI - aSrc) * cDest[2] + aSrc * cSrc[2]) / alphaI]; } break; case splashPipeResultColorAlphaNoBlendCMYK: if (alphaI == 0) { cResult0 = 0; cResult1 = 0; cResult2 = 0; cResult3 = 0; } else { cResult0 = state->cmykTransferC[((alphaI - aSrc) * cDest[0] + aSrc * cSrc[0]) / alphaI]; cResult1 = state->cmykTransferM[((alphaI - aSrc) * cDest[1] + aSrc * cSrc[1]) / alphaI]; cResult2 = state->cmykTransferY[((alphaI - aSrc) * cDest[2] + aSrc * cSrc[2]) / alphaI]; cResult3 = state->cmykTransferK[((alphaI - aSrc) * cDest[3] + aSrc * cSrc[3]) / alphaI]; } break; case splashPipeResultColorAlphaNoBlendDeviceN: if (alphaI == 0) { for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) cResult[cp] = 0; } else { for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) cResult[cp] = state->deviceNTransfer[cp][((alphaI - aSrc) * cDest[cp] + aSrc * cSrc[cp]) / alphaI]; } break; case splashPipeResultColorAlphaBlendMono: if (alphaI == 0) { cResult0 = 0; } else { cResult0 = state->grayTransfer[((alphaI - aSrc) * cDest[0] + aSrc * ((255 - alphaIm1) * cSrc[0] + alphaIm1 * cBlend[0]) / 255) / alphaI]; } break; case splashPipeResultColorAlphaBlendRGB: if (alphaI == 0) { cResult0 = 0; cResult1 = 0; cResult2 = 0; } else { cResult0 = state->rgbTransferR[((alphaI - aSrc) * cDest[0] + aSrc * ((255 - alphaIm1) * cSrc[0] + alphaIm1 * cBlend[0]) / 255) / alphaI]; cResult1 = state->rgbTransferG[((alphaI - aSrc) * cDest[1] + aSrc * ((255 - alphaIm1) * cSrc[1] + alphaIm1 * cBlend[1]) / 255) / alphaI]; cResult2 = state->rgbTransferB[((alphaI - aSrc) * cDest[2] + aSrc * ((255 - alphaIm1) * cSrc[2] + alphaIm1 * cBlend[2]) / 255) / alphaI]; } break; case splashPipeResultColorAlphaBlendCMYK: if (alphaI == 0) { cResult0 = 0; cResult1 = 0; cResult2 = 0; cResult3 = 0; } else { cResult0 = state->cmykTransferC[((alphaI - aSrc) * cDest[0] + aSrc * ((255 - alphaIm1) * cSrc[0] + alphaIm1 * cBlend[0]) / 255) / alphaI]; cResult1 = state->cmykTransferM[((alphaI - aSrc) * cDest[1] + aSrc * ((255 - alphaIm1) * cSrc[1] + alphaIm1 * cBlend[1]) / 255) / alphaI]; cResult2 = state->cmykTransferY[((alphaI - aSrc) * cDest[2] + aSrc * ((255 - alphaIm1) * cSrc[2] + alphaIm1 * cBlend[2]) / 255) / alphaI]; cResult3 = state->cmykTransferK[((alphaI - aSrc) * cDest[3] + aSrc * ((255 - alphaIm1) * cSrc[3] + alphaIm1 * cBlend[3]) / 255) / alphaI]; } break; case splashPipeResultColorAlphaBlendDeviceN: if (alphaI == 0) { for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) cResult[cp] = 0; } else { for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) cResult[cp] = state->deviceNTransfer[cp][((alphaI - aSrc) * cDest[cp] + aSrc * ((255 - alphaIm1) * cSrc[cp] + alphaIm1 * cBlend[cp]) / 255) / alphaI]; } 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 splashModeXBGR8: *pipe->destColorPtr++ = cResult2; *pipe->destColorPtr++ = cResult1; *pipe->destColorPtr++ = cResult0; *pipe->destColorPtr++ = 255; break; case splashModeBGR8: *pipe->destColorPtr++ = cResult2; *pipe->destColorPtr++ = cResult1; *pipe->destColorPtr++ = cResult0; break; case splashModeCMYK8: if (state->overprintMask & 1) { pipe->destColorPtr[0] = (state->overprintAdditive) ? std::min(pipe->destColorPtr[0] + cResult0, 255) : cResult0; } if (state->overprintMask & 2) { pipe->destColorPtr[1] = (state->overprintAdditive) ? std::min(pipe->destColorPtr[1] + cResult1, 255) : cResult1; } if (state->overprintMask & 4) { pipe->destColorPtr[2] = (state->overprintAdditive) ? std::min(pipe->destColorPtr[2] + cResult2, 255) : cResult2; } if (state->overprintMask & 8) { pipe->destColorPtr[3] = (state->overprintAdditive) ? std::min(pipe->destColorPtr[3] + cResult3, 255) : cResult3; } pipe->destColorPtr += 4; break; case splashModeDeviceN8: mask = 1; for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) { if (state->overprintMask & mask) { pipe->destColorPtr[cp] = cResult[cp]; } mask <<= 1; } pipe->destColorPtr += (SPOT_NCOMPS + 4); break; } if (pipe->destAlphaPtr) { *pipe->destAlphaPtr++ = aResult; } } ++pipe->x; } // special case: // !pipe->pattern && pipe->noTransparency && !state->blendFunc && // bitmap->mode == splashModeMono1 && !pipe->destAlphaPtr) { void Splash::pipeRunSimpleMono1(SplashPipe *pipe) { unsigned char cResult0; //----- write destination pixel cResult0 = state->grayTransfer[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; } ++pipe->x; } // special case: // !pipe->pattern && pipe->noTransparency && !state->blendFunc && // bitmap->mode == splashModeMono8 && pipe->destAlphaPtr) { void Splash::pipeRunSimpleMono8(SplashPipe *pipe) { //----- write destination pixel *pipe->destColorPtr++ = state->grayTransfer[pipe->cSrc[0]]; *pipe->destAlphaPtr++ = 255; ++pipe->x; } // special case: // !pipe->pattern && pipe->noTransparency && !state->blendFunc && // bitmap->mode == splashModeRGB8 && pipe->destAlphaPtr) { void Splash::pipeRunSimpleRGB8(SplashPipe *pipe) { //----- write destination pixel *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]]; *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]]; *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]]; *pipe->destAlphaPtr++ = 255; ++pipe->x; } // special case: // !pipe->pattern && pipe->noTransparency && !state->blendFunc && // bitmap->mode == splashModeXBGR8 && pipe->destAlphaPtr) { void Splash::pipeRunSimpleXBGR8(SplashPipe *pipe) { //----- write destination pixel *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]]; *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]]; *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]]; *pipe->destColorPtr++ = 255; *pipe->destAlphaPtr++ = 255; ++pipe->x; } // special case: // !pipe->pattern && pipe->noTransparency && !state->blendFunc && // bitmap->mode == splashModeBGR8 && pipe->destAlphaPtr) { void Splash::pipeRunSimpleBGR8(SplashPipe *pipe) { //----- write destination pixel *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]]; *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]]; *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]]; *pipe->destAlphaPtr++ = 255; ++pipe->x; } // special case: // !pipe->pattern && pipe->noTransparency && !state->blendFunc && // bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr) { void Splash::pipeRunSimpleCMYK8(SplashPipe *pipe) { //----- write destination pixel if (state->overprintMask & 1) { pipe->destColorPtr[0] = (state->overprintAdditive) ? std::min(pipe->destColorPtr[0] + state->cmykTransferC[pipe->cSrc[0]], 255) : state->cmykTransferC[pipe->cSrc[0]]; } if (state->overprintMask & 2) { pipe->destColorPtr[1] = (state->overprintAdditive) ? std::min(pipe->destColorPtr[1] + state->cmykTransferM[pipe->cSrc[1]], 255) : state->cmykTransferM[pipe->cSrc[1]]; } if (state->overprintMask & 4) { pipe->destColorPtr[2] = (state->overprintAdditive) ? std::min(pipe->destColorPtr[2] + state->cmykTransferY[pipe->cSrc[2]], 255) : state->cmykTransferY[pipe->cSrc[2]]; } if (state->overprintMask & 8) { pipe->destColorPtr[3] = (state->overprintAdditive) ? std::min(pipe->destColorPtr[3] + state->cmykTransferK[pipe->cSrc[3]], 255) : state->cmykTransferK[pipe->cSrc[3]]; } pipe->destColorPtr += 4; *pipe->destAlphaPtr++ = 255; ++pipe->x; } // special case: // !pipe->pattern && pipe->noTransparency && !state->blendFunc && // bitmap->mode == splashModeDeviceN8 && pipe->destAlphaPtr) { void Splash::pipeRunSimpleDeviceN8(SplashPipe *pipe) { //----- write destination pixel int mask = 1; for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) { if (state->overprintMask & mask) { pipe->destColorPtr[cp] = state->deviceNTransfer[cp][pipe->cSrc[cp]]; } mask <<= 1; } pipe->destColorPtr += (SPOT_NCOMPS + 4); *pipe->destAlphaPtr++ = 255; ++pipe->x; } // special case: // !pipe->pattern && !pipe->noTransparency && !state->softMask && // pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && // !pipe->nonIsolatedGroup && // bitmap->mode == splashModeMono1 && !pipe->destAlphaPtr void Splash::pipeRunAAMono1(SplashPipe *pipe) { unsigned char aSrc; SplashColor cDest; unsigned char cResult0; //----- read destination pixel cDest[0] = (*pipe->destColorPtr & pipe->destColorMask) ? 0xff : 0x00; //----- source alpha aSrc = div255(pipe->aInput * pipe->shape); //----- result color // note: aDest = alpha2 = aResult = 0xff cResult0 = state->grayTransfer[(unsigned char)div255((0xff - aSrc) * cDest[0] + aSrc * pipe->cSrc[0])]; //----- write destination pixel 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; } ++pipe->x; } // special case: // !pipe->pattern && !pipe->noTransparency && !state->softMask && // pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && // !pipe->nonIsolatedGroup && // bitmap->mode == splashModeMono8 && pipe->destAlphaPtr void Splash::pipeRunAAMono8(SplashPipe *pipe) { unsigned char aSrc, aDest, alpha2, aResult; SplashColor cDest; unsigned char cResult0; //----- read destination pixel cDest[0] = *pipe->destColorPtr; aDest = *pipe->destAlphaPtr; //----- source alpha aSrc = div255(pipe->aInput * pipe->shape); //----- result alpha and non-isolated group element correction aResult = aSrc + aDest - div255(aSrc * aDest); alpha2 = aResult; //----- result color if (alpha2 == 0) { cResult0 = 0; } else { cResult0 = state->grayTransfer[(unsigned char)(((alpha2 - aSrc) * cDest[0] + aSrc * pipe->cSrc[0]) / alpha2)]; } //----- write destination pixel *pipe->destColorPtr++ = cResult0; *pipe->destAlphaPtr++ = aResult; ++pipe->x; } // special case: // !pipe->pattern && !pipe->noTransparency && !state->softMask && // pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && // !pipe->nonIsolatedGroup && // bitmap->mode == splashModeRGB8 && pipe->destAlphaPtr void Splash::pipeRunAARGB8(SplashPipe *pipe) { unsigned char aSrc, aDest, alpha2, aResult; SplashColor cDest; unsigned char cResult0, cResult1, cResult2; //----- read destination alpha aDest = *pipe->destAlphaPtr; //----- source alpha aSrc = div255(pipe->aInput * pipe->shape); //----- result color if (aSrc == 255) { cResult0 = state->rgbTransferR[pipe->cSrc[0]]; cResult1 = state->rgbTransferG[pipe->cSrc[1]]; cResult2 = state->rgbTransferB[pipe->cSrc[2]]; aResult = 255; } else if (aSrc == 0 && aDest == 0) { cResult0 = 0; cResult1 = 0; cResult2 = 0; aResult = 0; } else { //----- read destination pixel cDest[0] = pipe->destColorPtr[0]; cDest[1] = pipe->destColorPtr[1]; cDest[2] = pipe->destColorPtr[2]; //----- result alpha and non-isolated group element correction aResult = aSrc + aDest - div255(aSrc * aDest); alpha2 = aResult; cResult0 = state->rgbTransferR[(unsigned char)(((alpha2 - aSrc) * cDest[0] + aSrc * pipe->cSrc[0]) / alpha2)]; cResult1 = state->rgbTransferG[(unsigned char)(((alpha2 - aSrc) * cDest[1] + aSrc * pipe->cSrc[1]) / alpha2)]; cResult2 = state->rgbTransferB[(unsigned char)(((alpha2 - aSrc) * cDest[2] + aSrc * pipe->cSrc[2]) / alpha2)]; } //----- write destination pixel *pipe->destColorPtr++ = cResult0; *pipe->destColorPtr++ = cResult1; *pipe->destColorPtr++ = cResult2; *pipe->destAlphaPtr++ = aResult; ++pipe->x; } // special case: // !pipe->pattern && !pipe->noTransparency && !state->softMask && // pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && // !pipe->nonIsolatedGroup && // bitmap->mode == splashModeXBGR8 && pipe->destAlphaPtr void Splash::pipeRunAAXBGR8(SplashPipe *pipe) { unsigned char aSrc, aDest, alpha2, aResult; SplashColor cDest; unsigned char cResult0, cResult1, cResult2; //----- read destination alpha aDest = *pipe->destAlphaPtr; //----- source alpha aSrc = div255(pipe->aInput * pipe->shape); //----- result color if (aSrc == 255) { cResult0 = state->rgbTransferR[pipe->cSrc[0]]; cResult1 = state->rgbTransferG[pipe->cSrc[1]]; cResult2 = state->rgbTransferB[pipe->cSrc[2]]; aResult = 255; } else if (aSrc == 0 && aDest == 0) { cResult0 = 0; cResult1 = 0; cResult2 = 0; aResult = 0; } else { //----- read destination color cDest[0] = pipe->destColorPtr[2]; cDest[1] = pipe->destColorPtr[1]; cDest[2] = pipe->destColorPtr[0]; //----- result alpha and non-isolated group element correction aResult = aSrc + aDest - div255(aSrc * aDest); alpha2 = aResult; cResult0 = state->rgbTransferR[(unsigned char)(((alpha2 - aSrc) * cDest[0] + aSrc * pipe->cSrc[0]) / alpha2)]; cResult1 = state->rgbTransferG[(unsigned char)(((alpha2 - aSrc) * cDest[1] + aSrc * pipe->cSrc[1]) / alpha2)]; cResult2 = state->rgbTransferB[(unsigned char)(((alpha2 - aSrc) * cDest[2] + aSrc * pipe->cSrc[2]) / alpha2)]; } //----- write destination pixel *pipe->destColorPtr++ = cResult2; *pipe->destColorPtr++ = cResult1; *pipe->destColorPtr++ = cResult0; *pipe->destColorPtr++ = 255; *pipe->destAlphaPtr++ = aResult; ++pipe->x; } // special case: // !pipe->pattern && !pipe->noTransparency && !state->softMask && // pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && // !pipe->nonIsolatedGroup && // bitmap->mode == splashModeBGR8 && pipe->destAlphaPtr void Splash::pipeRunAABGR8(SplashPipe *pipe) { unsigned char aSrc, aDest, alpha2, aResult; SplashColor cDest; unsigned char cResult0, cResult1, cResult2; //----- read destination alpha aDest = *pipe->destAlphaPtr; //----- source alpha aSrc = div255(pipe->aInput * pipe->shape); //----- result color if (aSrc == 255) { cResult0 = state->rgbTransferR[pipe->cSrc[0]]; cResult1 = state->rgbTransferG[pipe->cSrc[1]]; cResult2 = state->rgbTransferB[pipe->cSrc[2]]; aResult = 255; } else if (aSrc == 0 && aDest == 0) { cResult0 = 0; cResult1 = 0; cResult2 = 0; aResult = 0; } else { //----- read destination color cDest[0] = pipe->destColorPtr[2]; cDest[1] = pipe->destColorPtr[1]; cDest[2] = pipe->destColorPtr[0]; //----- result alpha and non-isolated group element correction aResult = aSrc + aDest - div255(aSrc * aDest); alpha2 = aResult; cResult0 = state->rgbTransferR[(unsigned char)(((alpha2 - aSrc) * cDest[0] + aSrc * pipe->cSrc[0]) / alpha2)]; cResult1 = state->rgbTransferG[(unsigned char)(((alpha2 - aSrc) * cDest[1] + aSrc * pipe->cSrc[1]) / alpha2)]; cResult2 = state->rgbTransferB[(unsigned char)(((alpha2 - aSrc) * cDest[2] + aSrc * pipe->cSrc[2]) / alpha2)]; } //----- write destination pixel *pipe->destColorPtr++ = cResult2; *pipe->destColorPtr++ = cResult1; *pipe->destColorPtr++ = cResult0; *pipe->destAlphaPtr++ = aResult; ++pipe->x; } // special case: // !pipe->pattern && !pipe->noTransparency && !state->softMask && // pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && // !pipe->nonIsolatedGroup && // bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr void Splash::pipeRunAACMYK8(SplashPipe *pipe) { unsigned char aSrc, aDest, alpha2, aResult; SplashColor cDest; unsigned char cResult0, cResult1, cResult2, cResult3; //----- read destination pixel cDest[0] = pipe->destColorPtr[0]; cDest[1] = pipe->destColorPtr[1]; cDest[2] = pipe->destColorPtr[2]; cDest[3] = pipe->destColorPtr[3]; aDest = *pipe->destAlphaPtr; //----- source alpha aSrc = div255(pipe->aInput * pipe->shape); //----- result alpha and non-isolated group element correction aResult = aSrc + aDest - div255(aSrc * aDest); alpha2 = aResult; //----- result color if (alpha2 == 0) { cResult0 = 0; cResult1 = 0; cResult2 = 0; cResult3 = 0; } else { cResult0 = state->cmykTransferC[(unsigned char)(((alpha2 - aSrc) * cDest[0] + aSrc * pipe->cSrc[0]) / alpha2)]; cResult1 = state->cmykTransferM[(unsigned char)(((alpha2 - aSrc) * cDest[1] + aSrc * pipe->cSrc[1]) / alpha2)]; cResult2 = state->cmykTransferY[(unsigned char)(((alpha2 - aSrc) * cDest[2] + aSrc * pipe->cSrc[2]) / alpha2)]; cResult3 = state->cmykTransferK[(unsigned char)(((alpha2 - aSrc) * cDest[3] + aSrc * pipe->cSrc[3]) / alpha2)]; } //----- write destination pixel if (state->overprintMask & 1) { pipe->destColorPtr[0] = (state->overprintAdditive && pipe->shape != 0) ? std::min(pipe->destColorPtr[0] + cResult0, 255) : cResult0; } if (state->overprintMask & 2) { pipe->destColorPtr[1] = (state->overprintAdditive && pipe->shape != 0) ? std::min(pipe->destColorPtr[1] + cResult1, 255) : cResult1; } if (state->overprintMask & 4) { pipe->destColorPtr[2] = (state->overprintAdditive && pipe->shape != 0) ? std::min(pipe->destColorPtr[2] + cResult2, 255) : cResult2; } if (state->overprintMask & 8) { pipe->destColorPtr[3] = (state->overprintAdditive && pipe->shape != 0) ? std::min(pipe->destColorPtr[3] + cResult3, 255) : cResult3; } pipe->destColorPtr += 4; *pipe->destAlphaPtr++ = aResult; ++pipe->x; } // special case: // !pipe->pattern && !pipe->noTransparency && !state->softMask && // pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && // !pipe->nonIsolatedGroup && // bitmap->mode == splashModeDeviceN8 && pipe->destAlphaPtr void Splash::pipeRunAADeviceN8(SplashPipe *pipe) { unsigned char aSrc, aDest, alpha2, aResult; SplashColor cDest; unsigned char cResult[SPOT_NCOMPS + 4]; int cp, mask; //----- read destination pixel for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) cDest[cp] = pipe->destColorPtr[cp]; aDest = *pipe->destAlphaPtr; //----- source alpha aSrc = div255(pipe->aInput * pipe->shape); //----- result alpha and non-isolated group element correction aResult = aSrc + aDest - div255(aSrc * aDest); alpha2 = aResult; //----- result color if (alpha2 == 0) { for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) cResult[cp] = 0; } else { for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) cResult[cp] = state->deviceNTransfer[cp][(unsigned char)(((alpha2 - aSrc) * cDest[cp] + aSrc * pipe->cSrc[cp]) / alpha2)]; } //----- write destination pixel mask = 1; for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) { if (state->overprintMask & mask) { pipe->destColorPtr[cp] = cResult[cp]; } mask <<= 1; } pipe->destColorPtr += (SPOT_NCOMPS + 4); *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; case splashModeXBGR8: pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x]; break; case splashModeCMYK8: pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x]; break; case splashModeDeviceN8: pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + (SPOT_NCOMPS + 4) * x]; break; } if (bitmap->alpha) { pipe->destAlphaPtr = &bitmap->alpha[y * bitmap->width + x]; } else { pipe->destAlphaPtr = nullptr; } if (state->inNonIsolatedGroup && alpha0Bitmap->alpha) { pipe->alpha0Ptr = &alpha0Bitmap->alpha[(alpha0Y + y) * alpha0Bitmap->width + (alpha0X + x)]; } else { pipe->alpha0Ptr = nullptr; } } 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; case splashModeXBGR8: pipe->destColorPtr += 4; break; case splashModeCMYK8: pipe->destColorPtr += 4; break; case splashModeDeviceN8: pipe->destColorPtr += (SPOT_NCOMPS + 4); break; } if (pipe->destAlphaPtr) { ++pipe->destAlphaPtr; } if (pipe->alpha0Ptr) { ++pipe->alpha0Ptr; } } inline void Splash::drawPixel(SplashPipe *pipe, int x, int y, bool noClip) { if (unlikely(y < 0)) return; if (noClip || state->clip->test(x, y)) { pipeSetXY(pipe, x, y); (this->*pipe->run)(pipe); } } inline void Splash::drawAAPixelInit() { aaBufY = -1; } inline void Splash::drawAAPixel(SplashPipe *pipe, int x, int y) { #if splashAASize == 4 static const 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 = div255(aaGamma[t] * pipe->shape); (this->*pipe->run)(pipe); } } inline void Splash::drawSpan(SplashPipe *pipe, int x0, int x1, int y, bool noClip) { int x; if (noClip) { pipeSetXY(pipe, x0, y); for (x = x0; x <= x1; ++x) { (this->*pipe->run)(pipe); } } else { if (x0 < state->clip->getXMinI()) { x0 = state->clip->getXMinI(); } if (x1 > state->clip->getXMaxI()) { x1 = state->clip->getXMaxI(); } pipeSetXY(pipe, x0, y); for (x = x0; x <= x1; ++x) { if (state->clip->test(x, y)) { (this->*pipe->run)(pipe); } else { pipeIncX(pipe); } } } } inline void Splash::drawAALine(SplashPipe *pipe, int x0, int x1, int y, bool adjustLine, unsigned char lineOpacity) { #if splashAASize == 4 static const 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 = (adjustLine) ? div255((int)lineOpacity * (double)aaGamma[t]) : (double)aaGamma[t]; (this->*pipe->run)(pipe); } else { pipeIncX(pipe); } } } //------------------------------------------------------------------------ // Transform a point from user space to device space. inline void Splash::transform(const 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, bool vectorAntialiasA, SplashScreenParams *screenParams) { int i; bitmap = bitmapA; vectorAntialias = vectorAntialiasA; inShading = false; state = new SplashState(bitmap->width, bitmap->height, vectorAntialias, screenParams); if (vectorAntialias) { aaBuf = new SplashBitmap(splashAASize * bitmap->width, splashAASize, 1, splashModeMono1, false); for (i = 0; i <= splashAASize * splashAASize; ++i) { aaGamma[i] = (unsigned char)splashRound(splashPow((SplashCoord)i / (SplashCoord)(splashAASize * splashAASize), splashAAGamma) * 255); } } else { aaBuf = nullptr; } minLineWidth = 0; thinLineMode = splashThinLineDefault; debugMode = false; alpha0Bitmap = nullptr; } Splash::Splash(SplashBitmap *bitmapA, bool vectorAntialiasA, SplashScreen *screenA) { int i; bitmap = bitmapA; inShading = false; vectorAntialias = vectorAntialiasA; state = new SplashState(bitmap->width, bitmap->height, vectorAntialias, screenA); if (vectorAntialias) { aaBuf = new SplashBitmap(splashAASize * bitmap->width, splashAASize, 1, splashModeMono1, false); for (i = 0; i <= splashAASize * splashAASize; ++i) { aaGamma[i] = (unsigned char)splashRound(splashPow((SplashCoord)i / (SplashCoord)(splashAASize * splashAASize), splashAAGamma) * 255); } } else { aaBuf = nullptr; } minLineWidth = 0; thinLineMode = splashThinLineDefault; debugMode = false; alpha0Bitmap = nullptr; } Splash::~Splash() { while (state->next) { restoreState(); } delete state; 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; } bool Splash::getStrokeAdjust() { return state->strokeAdjust; } SplashClip *Splash::getClip() { return state->clip; } SplashBitmap *Splash::getSoftMask() { return state->softMask; } bool 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 = (state->multiplyPatternAlpha) ? alpha * state->patternStrokeAlpha : alpha; } void Splash::setFillAlpha(SplashCoord alpha) { state->fillAlpha = (state->multiplyPatternAlpha) ? alpha * state->patternFillAlpha : alpha; } void Splash::setPatternAlpha(SplashCoord strokeAlpha, SplashCoord fillAlpha) { state->patternStrokeAlpha = strokeAlpha; state->patternFillAlpha = fillAlpha; state->multiplyPatternAlpha = true; } void Splash::clearPatternAlpha() { state->patternStrokeAlpha = 1; state->patternFillAlpha = 1; state->multiplyPatternAlpha = false; } void Splash::setFillOverprint(bool fop) { state->fillOverprint = fop; } void Splash::setStrokeOverprint(bool sop) { state->strokeOverprint = sop; } void Splash::setOverprintMode(int opm) { state->overprintMode = opm; } 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(bool 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, bool 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 = true; } void Splash::setTransfer(unsigned char *red, unsigned char *green, unsigned char *blue, unsigned char *gray) { state->setTransfer(red, green, blue, gray); } void Splash::setOverprintMask(unsigned int overprintMask, bool additive) { state->overprintMask = overprintMask; state->overprintAdditive = additive; } //------------------------------------------------------------------------ // 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, unsigned char alpha) { SplashColorPtr row, p; unsigned char 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 splashModeXBGR8: 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]; *p++ = 255; } 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; 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; case splashModeDeviceN8: row = bitmap->data; for (y = 0; y < bitmap->height; ++y) { p = row; for (x = 0; x < bitmap->width; ++x) { for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) *p++ = color[cp]; } row += bitmap->rowSize; } break; } if (bitmap->alpha) { memset(bitmap->alpha, alpha, bitmap->width * bitmap->height); } } SplashError Splash::stroke(SplashPath *path) { SplashPath *path2, *dPath; SplashCoord d1, d2, t1, t2, w; 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 (path2->length == 0) { delete path2; return splashErrEmptyPath; } } // transform a unit square, and take the half the max of the two // diagonals; the product of this number and the line width is the // (approximate) transformed line width t1 = state->matrix[0] + state->matrix[2]; t2 = state->matrix[1] + state->matrix[3]; d1 = t1 * t1 + t2 * t2; t1 = state->matrix[0] - state->matrix[2]; t2 = state->matrix[1] - state->matrix[3]; d2 = t1 * t1 + t2 * t2; if (d2 > d1) { d1 = d2; } d1 *= 0.5; if (d1 > 0 && d1 * state->lineWidth * state->lineWidth < minLineWidth * minLineWidth) { w = minLineWidth / splashSqrt(d1); strokeWide(path2, w); } else if (bitmap->mode == splashModeMono1) { // this gets close to Adobe's behavior in mono mode if (d1 * state->lineWidth <= 2) { strokeNarrow(path2); } else { strokeWide(path2, state->lineWidth); } } else { if (state->lineWidth == 0) { strokeNarrow(path2); } else { strokeWide(path2, state->lineWidth); } } delete path2; return splashOk; } void Splash::strokeNarrow(SplashPath *path) { SplashPipe pipe; SplashXPathSeg *seg; int x0, x1, y0, y1, xa, xb, y; SplashCoord dxdy; SplashClipResult clipRes; int nClipRes[3]; int i; nClipRes[0] = nClipRes[1] = nClipRes[2] = 0; SplashXPath xPath(path, state->matrix, state->flatness, false); pipeInit(&pipe, 0, 0, state->strokePattern, nullptr, (unsigned char)splashRound(state->strokeAlpha * 255), false, false); for (i = 0, seg = xPath.segs; i < xPath.length; ++i, ++seg) { if (seg->y0 <= seg->y1) { y0 = splashFloor(seg->y0); y1 = splashFloor(seg->y1); x0 = splashFloor(seg->x0); x1 = splashFloor(seg->x1); } else { y0 = splashFloor(seg->y1); y1 = splashFloor(seg->y0); x0 = splashFloor(seg->x1); x1 = splashFloor(seg->x0); } if ((clipRes = state->clip->testRect(x0 <= x1 ? x0 : x1, y0, x0 <= x1 ? x1 : x0, y1)) != splashClipAllOutside) { if (y0 == y1) { if (x0 <= x1) { drawSpan(&pipe, x0, x1, y0, clipRes == splashClipAllInside); } else { drawSpan(&pipe, x1, x0, y0, clipRes == splashClipAllInside); } } else { dxdy = seg->dxdy; if (y0 < state->clip->getYMinI()) { y0 = state->clip->getYMinI(); x0 = splashFloor(seg->x0 + (state->clip->getYMin() - seg->y0) * dxdy); } if (y1 > state->clip->getYMaxI()) { y1 = state->clip->getYMaxI(); x1 = splashFloor(seg->x0 + (state->clip->getYMax() - seg->y0) * dxdy); } if (x0 <= x1) { xa = x0; for (y = y0; y <= y1; ++y) { if (y < y1) { xb = splashFloor(seg->x0 + ((SplashCoord)y + 1 - seg->y0) * dxdy); } else { xb = x1 + 1; } if (xa == xb) { drawPixel(&pipe, xa, y, clipRes == splashClipAllInside); } else { drawSpan(&pipe, xa, xb - 1, y, clipRes == splashClipAllInside); } xa = xb; } } else { xa = x0; for (y = y0; y <= y1; ++y) { if (y < y1) { xb = splashFloor(seg->x0 + ((SplashCoord)y + 1 - seg->y0) * dxdy); } else { xb = x1 - 1; } if (xa == xb) { drawPixel(&pipe, xa, y, clipRes == splashClipAllInside); } else { drawSpan(&pipe, xb + 1, xa, y, clipRes == splashClipAllInside); } xa = xb; } } } } ++nClipRes[clipRes]; } if (nClipRes[splashClipPartial] || (nClipRes[splashClipAllInside] && nClipRes[splashClipAllOutside])) { opClipRes = splashClipPartial; } else if (nClipRes[splashClipAllInside]) { opClipRes = splashClipAllInside; } else { opClipRes = splashClipAllOutside; } } void Splash::strokeWide(SplashPath *path, SplashCoord w) { SplashPath *path2; path2 = makeStrokePath(path, w, false); fillWithPattern(path2, false, state->strokePattern, state->strokeAlpha); delete path2; } SplashPath *Splash::flattenPath(SplashPath *path, SplashCoord *matrix, SplashCoord flatness) { SplashPath *fPath; SplashCoord flatness2; unsigned char 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 = splashAvg(xl0, xx1); yl1 = splashAvg(yl0, yy1); xh = splashAvg(xx1, xx2); yh = splashAvg(yy1, yy2); xl2 = splashAvg(xl1, xh); yl2 = splashAvg(yl1, yh); xr2 = splashAvg(xx2, xr3); yr2 = splashAvg(yy2, yr3); xr1 = splashAvg(xh, xr2); yr1 = splashAvg(yh, yr2); xr0 = splashAvg(xl2, xr1); yr0 = splashAvg(yl2, yr1); // 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; bool lineDashStartOn, lineDashOn, newPath; int lineDashStartIdx, lineDashIdx; int i, j, k; lineDashTotal = 0; for (i = 0; i < state->lineDashLength; ++i) { lineDashTotal += state->lineDash[i]; } // Acrobat simply draws nothing if the dash array is [0] if (lineDashTotal == 0) { return new SplashPath(); } lineDashStartPhase = state->lineDashPhase; i = splashFloor(lineDashStartPhase / lineDashTotal); lineDashStartPhase -= (SplashCoord)i * lineDashTotal; lineDashStartOn = true; lineDashStartIdx = 0; if (lineDashStartPhase > 0) { while (lineDashStartIdx < state->lineDashLength && lineDashStartPhase >= state->lineDash[lineDashStartIdx]) { lineDashStartOn = !lineDashStartOn; lineDashStartPhase -= state->lineDash[lineDashStartIdx]; ++lineDashStartIdx; } if (unlikely(lineDashStartIdx == state->lineDashLength)) { return new SplashPath(); } } 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 = true; 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 = false; } 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 = false; } 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 = true; } } } i = j + 1; } if (dPath->length == 0) { bool allSame = true; for (i = 0; allSame && i < path->length - 1; ++i) { allSame = path->pts[i].x == path->pts[i + 1].x && path->pts[i].y == path->pts[i + 1].y; } if (allSame) { x0 = path->pts[0].x; y0 = path->pts[0].y; dPath->moveTo(x0, y0); dPath->lineTo(x0, y0); } } return dPath; } SplashError Splash::fill(SplashPath *path, bool eo) { if (debugMode) { printf("fill [eo:%d]:\n", eo); dumpPath(path); } return fillWithPattern(path, eo, state->fillPattern, state->fillAlpha); } inline void Splash::getBBoxFP(SplashPath *path, SplashCoord *xMinA, SplashCoord *yMinA, SplashCoord *xMaxA, SplashCoord *yMaxA) { SplashCoord xMinFP, yMinFP, xMaxFP, yMaxFP, tx, ty; // make compiler happy: xMinFP = xMaxFP = yMinFP = yMaxFP = 0; for (int i = 0; i < path->length; ++i) { transform(state->matrix, path->pts[i].x, path->pts[i].y, &tx, &ty); if (i == 0) { xMinFP = xMaxFP = tx; yMinFP = yMaxFP = ty; } else { if (tx < xMinFP) xMinFP = tx; if (tx > xMaxFP) xMaxFP = tx; if (ty < yMinFP) yMinFP = ty; if (ty > yMaxFP) yMaxFP = ty; } } *xMinA = xMinFP; *yMinA = yMinFP; *xMaxA = xMaxFP; *yMaxA = yMaxFP; } SplashError Splash::fillWithPattern(SplashPath *path, bool eo, SplashPattern *pattern, SplashCoord alpha) { SplashPipe pipe = {}; int xMinI, yMinI, xMaxI, yMaxI, x0, x1, y; SplashClipResult clipRes, clipRes2; bool adjustLine = false; int linePosI = 0; if (path->length == 0) { return splashErrEmptyPath; } if (pathAllOutside(path)) { opClipRes = splashClipAllOutside; return splashOk; } // add stroke adjustment hints for filled rectangles -- this only // applies to paths that consist of a single subpath // (this appears to match Acrobat's behavior) if (state->strokeAdjust && !path->hints) { int n; n = path->getLength(); if (n == 4 && !(path->flags[0] & splashPathClosed) && !(path->flags[1] & splashPathLast) && !(path->flags[2] & splashPathLast)) { path->close(true); path->addStrokeAdjustHint(0, 2, 0, 4); path->addStrokeAdjustHint(1, 3, 0, 4); } else if (n == 5 && (path->flags[0] & splashPathClosed) && !(path->flags[1] & splashPathLast) && !(path->flags[2] & splashPathLast) && !(path->flags[3] & splashPathLast)) { path->addStrokeAdjustHint(0, 2, 0, 4); path->addStrokeAdjustHint(1, 3, 0, 4); } } if (thinLineMode != splashThinLineDefault) { if (state->clip->getXMinI() == state->clip->getXMaxI()) { linePosI = state->clip->getXMinI(); adjustLine = true; } else if (state->clip->getXMinI() == state->clip->getXMaxI() - 1) { adjustLine = true; linePosI = splashFloor(state->clip->getXMin() + state->lineWidth); } else if (state->clip->getYMinI() == state->clip->getYMaxI()) { linePosI = state->clip->getYMinI(); adjustLine = true; } else if (state->clip->getYMinI() == state->clip->getYMaxI() - 1) { adjustLine = true; linePosI = splashFloor(state->clip->getYMin() + state->lineWidth); } } SplashXPath xPath(path, state->matrix, state->flatness, true, adjustLine, linePosI); if (vectorAntialias && !inShading) { xPath.aaScale(); } xPath.sort(); yMinI = state->clip->getYMinI(); yMaxI = state->clip->getYMaxI(); if (vectorAntialias && !inShading) { yMinI = yMinI * splashAASize; yMaxI = (yMaxI + 1) * splashAASize - 1; } SplashXPathScanner scanner(&xPath, eo, yMinI, yMaxI); // get the min and max x and y values if (vectorAntialias && !inShading) { scanner.getBBoxAA(&xMinI, &yMinI, &xMaxI, &yMaxI); } else { scanner.getBBox(&xMinI, &yMinI, &xMaxI, &yMaxI); } if (eo && (yMinI == yMaxI || xMinI == xMaxI) && thinLineMode != splashThinLineDefault) { SplashCoord delta, xMinFP, yMinFP, xMaxFP, yMaxFP; getBBoxFP(path, &xMinFP, &yMinFP, &xMaxFP, &yMaxFP); delta = (yMinI == yMaxI) ? yMaxFP - yMinFP : xMaxFP - xMinFP; if (delta < 0.2) { opClipRes = splashClipAllOutside; return splashOk; } } // check clipping if ((clipRes = state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI)) != splashClipAllOutside) { if (scanner.hasPartialClip()) { clipRes = splashClipPartial; } pipeInit(&pipe, 0, yMinI, pattern, nullptr, (unsigned char)splashRound(alpha * 255), vectorAntialias && !inShading, false); // draw the spans if (vectorAntialias && !inShading) { for (y = yMinI; y <= yMaxI; ++y) { scanner.renderAALine(aaBuf, &x0, &x1, y, thinLineMode != splashThinLineDefault && xMinI == xMaxI); if (clipRes != splashClipAllInside) { state->clip->clipAALine(aaBuf, &x0, &x1, y, thinLineMode != splashThinLineDefault && xMinI == xMaxI); } unsigned char lineShape = 255; bool doAdjustLine = false; if (thinLineMode == splashThinLineShape && (xMinI == xMaxI || yMinI == yMaxI)) { // compute line shape for thin lines: SplashCoord mx, my, delta; transform(state->matrix, 0, 0, &mx, &my); transform(state->matrix, state->lineWidth, 0, &delta, &my); doAdjustLine = true; lineShape = clip255((delta - mx) * 255); } drawAALine(&pipe, x0, x1, y, doAdjustLine, lineShape); } } else { for (y = yMinI; y <= yMaxI; ++y) { SplashXPathScanIterator iterator(scanner, y); while (iterator.getNextSpan(&x0, &x1)) { if (clipRes == splashClipAllInside) { drawSpan(&pipe, x0, x1, y, true); } 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; return splashOk; } bool Splash::pathAllOutside(SplashPath *path) { SplashCoord xMin1, yMin1, xMax1, yMax1; SplashCoord xMin2, yMin2, xMax2, yMax2; SplashCoord x, y; int xMinI, yMinI, xMaxI, yMaxI; int i; xMin1 = xMax1 = path->pts[0].x; yMin1 = yMax1 = path->pts[0].y; for (i = 1; i < path->length; ++i) { if (path->pts[i].x < xMin1) { xMin1 = path->pts[i].x; } else if (path->pts[i].x > xMax1) { xMax1 = path->pts[i].x; } if (path->pts[i].y < yMin1) { yMin1 = path->pts[i].y; } else if (path->pts[i].y > yMax1) { yMax1 = path->pts[i].y; } } transform(state->matrix, xMin1, yMin1, &x, &y); xMin2 = xMax2 = x; yMin2 = yMax2 = y; transform(state->matrix, xMin1, yMax1, &x, &y); if (x < xMin2) { xMin2 = x; } else if (x > xMax2) { xMax2 = x; } if (y < yMin2) { yMin2 = y; } else if (y > yMax2) { yMax2 = y; } transform(state->matrix, xMax1, yMin1, &x, &y); if (x < xMin2) { xMin2 = x; } else if (x > xMax2) { xMax2 = x; } if (y < yMin2) { yMin2 = y; } else if (y > yMax2) { yMax2 = y; } transform(state->matrix, xMax1, yMax1, &x, &y); if (x < xMin2) { xMin2 = x; } else if (x > xMax2) { xMax2 = x; } if (y < yMin2) { yMin2 = y; } else if (y > yMax2) { yMax2 = y; } xMinI = splashFloor(xMin2); yMinI = splashFloor(yMin2); xMaxI = splashFloor(xMax2); yMaxI = splashFloor(yMax2); return state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI) == splashClipAllOutside; } SplashError Splash::xorFill(SplashPath *path, bool eo) { SplashPipe pipe; int xMinI, yMinI, xMaxI, yMaxI, x0, x1, y; SplashClipResult clipRes, clipRes2; SplashBlendFunc origBlendFunc; if (path->length == 0) { return splashErrEmptyPath; } SplashXPath xPath(path, state->matrix, state->flatness, true); xPath.sort(); SplashXPathScanner scanner(&xPath, eo, state->clip->getYMinI(), state->clip->getYMaxI()); // 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) { if (scanner.hasPartialClip()) { clipRes = splashClipPartial; } origBlendFunc = state->blendFunc; state->blendFunc = &blendXor; pipeInit(&pipe, 0, yMinI, state->fillPattern, nullptr, 255, false, false); // draw the spans for (y = yMinI; y <= yMaxI; ++y) { SplashXPathScanIterator iterator(scanner, y); while (iterator.getNextSpan(&x0, &x1)) { if (clipRes == splashClipAllInside) { drawSpan(&pipe, x0, x1, y, true); } 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; 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, bool noClip) { SplashPipe pipe; int alpha0; unsigned char alpha; unsigned char *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; int xShift = 0; if (yStart < 0) { p += (glyph->aa ? glyph->w : splashCeil(glyph->w / 8.0)) * -yStart; // move p to the beginning of the first painted row yyLimit += yStart; yStart = 0; } if (xStart < 0) { if (glyph->aa) { p += -xStart; } else { p += (-xStart) / 8; xShift = (-xStart) % 8; } 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, nullptr, (unsigned char)splashRound(state->fillAlpha * 255), true, false); 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 = alpha; (this->*pipe.run)(&pipe); } else { pipeIncX(&pipe); } } p += glyph->w; } } else { const int widthEight = splashCeil(glyph->w / 8.0); pipeInit(&pipe, xStart, yStart, state->fillPattern, nullptr, (unsigned char)splashRound(state->fillAlpha * 255), false, false); for (yy = 0, y1 = yStart; yy < yyLimit; ++yy, ++y1) { pipeSetXY(&pipe, xStart, y1); for (xx = 0, x1 = xStart; xx < xxLimit; xx += 8) { alpha0 = (xShift > 0 && xx < xxLimit - 8 ? (p[xx / 8] << xShift) | (p[xx / 8 + 1] >> (8 - xShift)) : p[xx / 8]); for (xx1 = 0; xx1 < 8 && xx + xx1 < xxLimit; ++xx1, ++x1) { if (alpha0 & 0x80) { (this->*pipe.run)(&pipe); } else { pipeIncX(&pipe); } alpha0 <<= 1; } } p += widthEight; } } } else { if (glyph->aa) { pipeInit(&pipe, xStart, yStart, state->fillPattern, nullptr, (unsigned char)splashRound(state->fillAlpha * 255), true, false); 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 = alpha; (this->*pipe.run)(&pipe); } else { pipeIncX(&pipe); } } else { pipeIncX(&pipe); } } p += glyph->w; } } else { const int widthEight = splashCeil(glyph->w / 8.0); pipeInit(&pipe, xStart, yStart, state->fillPattern, nullptr, (unsigned char)splashRound(state->fillAlpha * 255), false, false); for (yy = 0, y1 = yStart; yy < yyLimit; ++yy, ++y1) { pipeSetXY(&pipe, xStart, y1); for (xx = 0, x1 = xStart; xx < xxLimit; xx += 8) { alpha0 = (xShift > 0 && xx < xxLimit - 8 ? (p[xx / 8] << xShift) | (p[xx / 8 + 1] >> (8 - xShift)) : p[xx / 8]); for (xx1 = 0; xx1 < 8 && xx + xx1 < xxLimit; ++xx1, ++x1) { if (state->clip->test(x1, y1)) { if (alpha0 & 0x80) { (this->*pipe.run)(&pipe); } else { pipeIncX(&pipe); } } else { pipeIncX(&pipe); } alpha0 <<= 1; } } p += widthEight; } } } } SplashError Splash::fillImageMask(SplashImageMaskSource src, void *srcData, int w, int h, SplashCoord *mat, bool glyphMode) { SplashBitmap *scaledMask; SplashClipResult clipRes; bool minorAxisZero; int x0, y0, x1, y1, scaledWidth, scaledHeight; int yp; 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 (!splashCheckDet(mat[0], mat[1], mat[2], mat[3], 0.000001)) { return splashErrSingularMatrix; } minorAxisZero = mat[1] == 0 && mat[2] == 0; // scaling only if (mat[0] > 0 && minorAxisZero && mat[3] > 0) { x0 = imgCoordMungeLowerC(mat[4], glyphMode); y0 = imgCoordMungeLowerC(mat[5], glyphMode); x1 = imgCoordMungeUpperC(mat[0] + mat[4], glyphMode); y1 = imgCoordMungeUpperC(mat[3] + mat[5], glyphMode); // make sure narrow images cover at least one pixel if (x0 == x1) { ++x1; } if (y0 == y1) { ++y1; } clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1); opClipRes = clipRes; if (clipRes != splashClipAllOutside) { scaledWidth = x1 - x0; scaledHeight = y1 - y0; yp = h / scaledHeight; if (yp < 0 || yp > INT_MAX - 1) { return splashErrBadArg; } scaledMask = scaleMask(src, srcData, w, h, scaledWidth, scaledHeight); blitMask(scaledMask, x0, y0, clipRes); delete scaledMask; } // scaling plus vertical flip } else if (mat[0] > 0 && minorAxisZero && mat[3] < 0) { x0 = imgCoordMungeLowerC(mat[4], glyphMode); y0 = imgCoordMungeLowerC(mat[3] + mat[5], glyphMode); x1 = imgCoordMungeUpperC(mat[0] + mat[4], glyphMode); y1 = imgCoordMungeUpperC(mat[5], glyphMode); // make sure narrow images cover at least one pixel if (x0 == x1) { ++x1; } if (y0 == y1) { ++y1; } clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1); opClipRes = clipRes; if (clipRes != splashClipAllOutside) { scaledWidth = x1 - x0; scaledHeight = y1 - y0; yp = h / scaledHeight; if (yp < 0 || yp > INT_MAX - 1) { return splashErrBadArg; } scaledMask = scaleMask(src, srcData, w, h, scaledWidth, scaledHeight); vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1); blitMask(scaledMask, x0, y0, clipRes); delete scaledMask; } // all other cases } else { arbitraryTransformMask(src, srcData, w, h, mat, glyphMode); } return splashOk; } void Splash::arbitraryTransformMask(SplashImageMaskSource src, void *srcData, int srcWidth, int srcHeight, SplashCoord *mat, bool glyphMode) { SplashBitmap *scaledMask; SplashClipResult clipRes, clipRes2; SplashPipe pipe; int scaledWidth, scaledHeight, t0, t1; SplashCoord r00, r01, r10, r11, det, ir00, ir01, ir10, ir11; SplashCoord vx[4], vy[4]; int xMin, yMin, xMax, yMax; ImageSection section[3]; int nSections; int y, xa, xb, x, i, xx, yy; // compute the four vertices of the target quadrilateral vx[0] = mat[4]; vy[0] = mat[5]; vx[1] = mat[2] + mat[4]; vy[1] = mat[3] + mat[5]; vx[2] = mat[0] + mat[2] + mat[4]; vy[2] = mat[1] + mat[3] + mat[5]; vx[3] = mat[0] + mat[4]; vy[3] = mat[1] + mat[5]; // make sure vx/vy fit in integers since we're transforming them to in the next lines for (i = 0; i < 4; ++i) { if (unlikely(vx[i] < INT_MIN || vx[i] > INT_MAX || vy[i] < INT_MIN || vy[i] > INT_MAX)) { error(errInternal, -1, "arbitraryTransformMask vertices values don't fit in an integer"); return; } } // clipping xMin = imgCoordMungeLowerC(vx[0], glyphMode); xMax = imgCoordMungeUpperC(vx[0], glyphMode); yMin = imgCoordMungeLowerC(vy[0], glyphMode); yMax = imgCoordMungeUpperC(vy[0], glyphMode); for (i = 1; i < 4; ++i) { t0 = imgCoordMungeLowerC(vx[i], glyphMode); if (t0 < xMin) { xMin = t0; } t0 = imgCoordMungeUpperC(vx[i], glyphMode); if (t0 > xMax) { xMax = t0; } t1 = imgCoordMungeLowerC(vy[i], glyphMode); if (t1 < yMin) { yMin = t1; } t1 = imgCoordMungeUpperC(vy[i], glyphMode); if (t1 > yMax) { yMax = t1; } } clipRes = state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1); opClipRes = clipRes; if (clipRes == splashClipAllOutside) { return; } // compute the scale factors if (mat[0] >= 0) { t0 = imgCoordMungeUpperC(mat[0] + mat[4], glyphMode) - imgCoordMungeLowerC(mat[4], glyphMode); } else { t0 = imgCoordMungeUpperC(mat[4], glyphMode) - imgCoordMungeLowerC(mat[0] + mat[4], glyphMode); } if (mat[1] >= 0) { t1 = imgCoordMungeUpperC(mat[1] + mat[5], glyphMode) - imgCoordMungeLowerC(mat[5], glyphMode); } else { t1 = imgCoordMungeUpperC(mat[5], glyphMode) - imgCoordMungeLowerC(mat[1] + mat[5], glyphMode); } scaledWidth = t0 > t1 ? t0 : t1; if (mat[2] >= 0) { t0 = imgCoordMungeUpperC(mat[2] + mat[4], glyphMode) - imgCoordMungeLowerC(mat[4], glyphMode); } else { t0 = imgCoordMungeUpperC(mat[4], glyphMode) - imgCoordMungeLowerC(mat[2] + mat[4], glyphMode); } if (mat[3] >= 0) { t1 = imgCoordMungeUpperC(mat[3] + mat[5], glyphMode) - imgCoordMungeLowerC(mat[5], glyphMode); } else { t1 = imgCoordMungeUpperC(mat[5], glyphMode) - imgCoordMungeLowerC(mat[3] + mat[5], glyphMode); } scaledHeight = t0 > t1 ? t0 : t1; if (scaledWidth == 0) { scaledWidth = 1; } if (scaledHeight == 0) { scaledHeight = 1; } // compute the inverse transform (after scaling) matrix r00 = mat[0] / scaledWidth; r01 = mat[1] / scaledWidth; r10 = mat[2] / scaledHeight; r11 = mat[3] / scaledHeight; det = r00 * r11 - r01 * r10; if (splashAbs(det) < 1e-6) { // this should be caught by the singular matrix check in fillImageMask return; } ir00 = r11 / det; ir01 = -r01 / det; ir10 = -r10 / det; ir11 = r00 / det; // scale the input image scaledMask = scaleMask(src, srcData, srcWidth, srcHeight, scaledWidth, scaledHeight); if (scaledMask->data == nullptr) { error(errInternal, -1, "scaledMask->data is NULL in Splash::arbitraryTransformMask"); delete scaledMask; return; } // construct the three sections i = (vy[2] <= vy[3]) ? 2 : 3; if (vy[1] <= vy[i]) { i = 1; } if (vy[0] < vy[i] || (i != 3 && vy[0] == vy[i])) { i = 0; } if (vy[i] == vy[(i + 1) & 3]) { section[0].y0 = imgCoordMungeLowerC(vy[i], glyphMode); section[0].y1 = imgCoordMungeUpperC(vy[(i + 2) & 3], glyphMode) - 1; if (vx[i] < vx[(i + 1) & 3]) { section[0].ia0 = i; section[0].ia1 = (i + 3) & 3; section[0].ib0 = (i + 1) & 3; section[0].ib1 = (i + 2) & 3; } else { section[0].ia0 = (i + 1) & 3; section[0].ia1 = (i + 2) & 3; section[0].ib0 = i; section[0].ib1 = (i + 3) & 3; } nSections = 1; } else { section[0].y0 = imgCoordMungeLowerC(vy[i], glyphMode); section[2].y1 = imgCoordMungeUpperC(vy[(i + 2) & 3], glyphMode) - 1; section[0].ia0 = section[0].ib0 = i; section[2].ia1 = section[2].ib1 = (i + 2) & 3; if (vx[(i + 1) & 3] < vx[(i + 3) & 3]) { section[0].ia1 = section[2].ia0 = (i + 1) & 3; section[0].ib1 = section[2].ib0 = (i + 3) & 3; } else { section[0].ia1 = section[2].ia0 = (i + 3) & 3; section[0].ib1 = section[2].ib0 = (i + 1) & 3; } if (vy[(i + 1) & 3] < vy[(i + 3) & 3]) { section[1].y0 = imgCoordMungeLowerC(vy[(i + 1) & 3], glyphMode); section[2].y0 = imgCoordMungeUpperC(vy[(i + 3) & 3], glyphMode); if (vx[(i + 1) & 3] < vx[(i + 3) & 3]) { section[1].ia0 = (i + 1) & 3; section[1].ia1 = (i + 2) & 3; section[1].ib0 = i; section[1].ib1 = (i + 3) & 3; } else { section[1].ia0 = i; section[1].ia1 = (i + 3) & 3; section[1].ib0 = (i + 1) & 3; section[1].ib1 = (i + 2) & 3; } } else { section[1].y0 = imgCoordMungeLowerC(vy[(i + 3) & 3], glyphMode); section[2].y0 = imgCoordMungeUpperC(vy[(i + 1) & 3], glyphMode); if (vx[(i + 1) & 3] < vx[(i + 3) & 3]) { section[1].ia0 = i; section[1].ia1 = (i + 1) & 3; section[1].ib0 = (i + 3) & 3; section[1].ib1 = (i + 2) & 3; } else { section[1].ia0 = (i + 3) & 3; section[1].ia1 = (i + 2) & 3; section[1].ib0 = i; section[1].ib1 = (i + 1) & 3; } } section[0].y1 = section[1].y0 - 1; section[1].y1 = section[2].y0 - 1; nSections = 3; } for (i = 0; i < nSections; ++i) { section[i].xa0 = vx[section[i].ia0]; section[i].ya0 = vy[section[i].ia0]; section[i].xa1 = vx[section[i].ia1]; section[i].ya1 = vy[section[i].ia1]; section[i].xb0 = vx[section[i].ib0]; section[i].yb0 = vy[section[i].ib0]; section[i].xb1 = vx[section[i].ib1]; section[i].yb1 = vy[section[i].ib1]; section[i].dxdya = (section[i].xa1 - section[i].xa0) / (section[i].ya1 - section[i].ya0); section[i].dxdyb = (section[i].xb1 - section[i].xb0) / (section[i].yb1 - section[i].yb0); } // initialize the pixel pipe pipeInit(&pipe, 0, 0, state->fillPattern, nullptr, (unsigned char)splashRound(state->fillAlpha * 255), true, false); if (vectorAntialias) { drawAAPixelInit(); } // make sure narrow images cover at least one pixel if (nSections == 1) { if (section[0].y0 == section[0].y1) { ++section[0].y1; clipRes = opClipRes = splashClipPartial; } } else { if (section[0].y0 == section[2].y1) { ++section[1].y1; clipRes = opClipRes = splashClipPartial; } } // scan all pixels inside the target region for (i = 0; i < nSections; ++i) { for (y = section[i].y0; y <= section[i].y1; ++y) { xa = imgCoordMungeLowerC(section[i].xa0 + ((SplashCoord)y + 0.5 - section[i].ya0) * section[i].dxdya, glyphMode); xb = imgCoordMungeUpperC(section[i].xb0 + ((SplashCoord)y + 0.5 - section[i].yb0) * section[i].dxdyb, glyphMode); if (unlikely(xa < 0)) xa = 0; // make sure narrow images cover at least one pixel if (xa == xb) { ++xb; } if (clipRes != splashClipAllInside) { clipRes2 = state->clip->testSpan(xa, xb - 1, y); } else { clipRes2 = clipRes; } for (x = xa; x < xb; ++x) { // map (x+0.5, y+0.5) back to the scaled image xx = splashFloor(((SplashCoord)x + 0.5 - mat[4]) * ir00 + ((SplashCoord)y + 0.5 - mat[5]) * ir10); yy = splashFloor(((SplashCoord)x + 0.5 - mat[4]) * ir01 + ((SplashCoord)y + 0.5 - mat[5]) * ir11); // xx should always be within bounds, but floating point // inaccuracy can cause problems if (unlikely(xx < 0)) { xx = 0; clipRes2 = splashClipPartial; } else if (unlikely(xx >= scaledWidth)) { xx = scaledWidth - 1; clipRes2 = splashClipPartial; } if (unlikely(yy < 0)) { yy = 0; clipRes2 = splashClipPartial; } else if (unlikely(yy >= scaledHeight)) { yy = scaledHeight - 1; clipRes2 = splashClipPartial; } pipe.shape = scaledMask->data[yy * scaledWidth + xx]; if (vectorAntialias && clipRes2 != splashClipAllInside) { drawAAPixel(&pipe, x, y); } else { drawPixel(&pipe, x, y, clipRes2 == splashClipAllInside); } } } } delete scaledMask; } // Scale an image mask into a SplashBitmap. SplashBitmap *Splash::scaleMask(SplashImageMaskSource src, void *srcData, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight) { SplashBitmap *dest; dest = new SplashBitmap(scaledWidth, scaledHeight, 1, splashModeMono8, false); if (scaledHeight < srcHeight) { if (scaledWidth < srcWidth) { scaleMaskYdownXdown(src, srcData, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } else { scaleMaskYdownXup(src, srcData, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } } else { if (scaledWidth < srcWidth) { scaleMaskYupXdown(src, srcData, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } else { scaleMaskYupXup(src, srcData, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } } return dest; } void Splash::scaleMaskYdownXdown(SplashImageMaskSource src, void *srcData, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, SplashBitmap *dest) { unsigned char *lineBuf; unsigned int *pixBuf; unsigned int pix; unsigned char *destPtr; int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx, d, d0, d1; int i, j; // Bresenham parameters for y scale yp = srcHeight / scaledHeight; yq = srcHeight % scaledHeight; // Bresenham parameters for x scale xp = srcWidth / scaledWidth; xq = srcWidth % scaledWidth; // allocate buffers lineBuf = (unsigned char *)gmalloc(srcWidth); pixBuf = (unsigned int *)gmallocn_checkoverflow(srcWidth, sizeof(int)); if (unlikely(!pixBuf)) { error(errInternal, -1, "Couldn't allocate memory for pixBux in Splash::scaleMaskYdownXdown"); gfree(lineBuf); return; } // init y scale Bresenham yt = 0; destPtr = dest->data; for (y = 0; y < scaledHeight; ++y) { // y scale Bresenham if ((yt += yq) >= scaledHeight) { yt -= scaledHeight; yStep = yp + 1; } else { yStep = yp; } // read rows from image memset(pixBuf, 0, srcWidth * sizeof(int)); for (i = 0; i < yStep; ++i) { (*src)(srcData, lineBuf); for (j = 0; j < srcWidth; ++j) { pixBuf[j] += lineBuf[j]; } } // init x scale Bresenham xt = 0; d0 = (255 << 23) / (yStep * xp); d1 = (255 << 23) / (yStep * (xp + 1)); xx = 0; for (x = 0; x < scaledWidth; ++x) { // x scale Bresenham if ((xt += xq) >= scaledWidth) { xt -= scaledWidth; xStep = xp + 1; d = d1; } else { xStep = xp; d = d0; } // compute the final pixel pix = 0; for (i = 0; i < xStep; ++i) { pix += pixBuf[xx++]; } // (255 * pix) / xStep * yStep pix = (pix * d) >> 23; // store the pixel *destPtr++ = (unsigned char)pix; } } gfree(pixBuf); gfree(lineBuf); } void Splash::scaleMaskYdownXup(SplashImageMaskSource src, void *srcData, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, SplashBitmap *dest) { unsigned char *lineBuf; unsigned int *pixBuf; unsigned int pix; unsigned char *destPtr; int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, d; int i, j; destPtr = dest->data; if (destPtr == nullptr) { error(errInternal, -1, "dest->data is NULL in Splash::scaleMaskYdownXup"); return; } // Bresenham parameters for y scale yp = srcHeight / scaledHeight; yq = srcHeight % scaledHeight; // Bresenham parameters for x scale xp = scaledWidth / srcWidth; xq = scaledWidth % srcWidth; // allocate buffers lineBuf = (unsigned char *)gmalloc(srcWidth); pixBuf = (unsigned int *)gmallocn(srcWidth, sizeof(int)); // init y scale Bresenham yt = 0; for (y = 0; y < scaledHeight; ++y) { // y scale Bresenham if ((yt += yq) >= scaledHeight) { yt -= scaledHeight; yStep = yp + 1; } else { yStep = yp; } // read rows from image memset(pixBuf, 0, srcWidth * sizeof(int)); for (i = 0; i < yStep; ++i) { (*src)(srcData, lineBuf); for (j = 0; j < srcWidth; ++j) { pixBuf[j] += lineBuf[j]; } } // init x scale Bresenham xt = 0; d = (255 << 23) / yStep; for (x = 0; x < srcWidth; ++x) { // x scale Bresenham if ((xt += xq) >= srcWidth) { xt -= srcWidth; xStep = xp + 1; } else { xStep = xp; } // compute the final pixel pix = pixBuf[x]; // (255 * pix) / yStep pix = (pix * d) >> 23; // store the pixel for (i = 0; i < xStep; ++i) { *destPtr++ = (unsigned char)pix; } } } gfree(pixBuf); gfree(lineBuf); } void Splash::scaleMaskYupXdown(SplashImageMaskSource src, void *srcData, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, SplashBitmap *dest) { unsigned char *lineBuf; unsigned int pix; unsigned char *destPtr0, *destPtr; int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx, d, d0, d1; int i; destPtr0 = dest->data; if (destPtr0 == nullptr) { error(errInternal, -1, "dest->data is NULL in Splash::scaleMaskYupXdown"); return; } // Bresenham parameters for y scale yp = scaledHeight / srcHeight; yq = scaledHeight % srcHeight; // Bresenham parameters for x scale xp = srcWidth / scaledWidth; xq = srcWidth % scaledWidth; // allocate buffers lineBuf = (unsigned char *)gmalloc(srcWidth); // init y scale Bresenham yt = 0; for (y = 0; y < srcHeight; ++y) { // y scale Bresenham if ((yt += yq) >= srcHeight) { yt -= srcHeight; yStep = yp + 1; } else { yStep = yp; } // read row from image (*src)(srcData, lineBuf); // init x scale Bresenham xt = 0; d0 = (255 << 23) / xp; d1 = (255 << 23) / (xp + 1); xx = 0; for (x = 0; x < scaledWidth; ++x) { // x scale Bresenham if ((xt += xq) >= scaledWidth) { xt -= scaledWidth; xStep = xp + 1; d = d1; } else { xStep = xp; d = d0; } // compute the final pixel pix = 0; for (i = 0; i < xStep; ++i) { pix += lineBuf[xx++]; } // (255 * pix) / xStep pix = (pix * d) >> 23; // store the pixel for (i = 0; i < yStep; ++i) { destPtr = destPtr0 + i * scaledWidth + x; *destPtr = (unsigned char)pix; } } destPtr0 += yStep * scaledWidth; } gfree(lineBuf); } void Splash::scaleMaskYupXup(SplashImageMaskSource src, void *srcData, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, SplashBitmap *dest) { unsigned char *lineBuf; unsigned int pix; unsigned char *destPtr0, *destPtr; int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx; int i, j; destPtr0 = dest->data; if (destPtr0 == nullptr) { error(errInternal, -1, "dest->data is NULL in Splash::scaleMaskYupXup"); return; } if (unlikely(srcWidth <= 0 || srcHeight <= 0)) { error(errSyntaxError, -1, "srcWidth <= 0 || srcHeight <= 0 in Splash::scaleMaskYupXup"); gfree(dest->takeData()); return; } // Bresenham parameters for y scale yp = scaledHeight / srcHeight; yq = scaledHeight % srcHeight; // Bresenham parameters for x scale xp = scaledWidth / srcWidth; xq = scaledWidth % srcWidth; // allocate buffers lineBuf = (unsigned char *)gmalloc(srcWidth); // init y scale Bresenham yt = 0; for (y = 0; y < srcHeight; ++y) { // y scale Bresenham if ((yt += yq) >= srcHeight) { yt -= srcHeight; yStep = yp + 1; } else { yStep = yp; } // read row from image (*src)(srcData, lineBuf); // init x scale Bresenham xt = 0; xx = 0; for (x = 0; x < srcWidth; ++x) { // x scale Bresenham if ((xt += xq) >= srcWidth) { xt -= srcWidth; xStep = xp + 1; } else { xStep = xp; } // compute the final pixel pix = lineBuf[x] ? 255 : 0; // store the pixel for (i = 0; i < yStep; ++i) { for (j = 0; j < xStep; ++j) { destPtr = destPtr0 + i * scaledWidth + xx + j; *destPtr++ = (unsigned char)pix; } } xx += xStep; } destPtr0 += yStep * scaledWidth; } gfree(lineBuf); } void Splash::blitMask(SplashBitmap *src, int xDest, int yDest, SplashClipResult clipRes) { SplashPipe pipe; unsigned char *p; int w, h, x, y; w = src->getWidth(); h = src->getHeight(); p = src->getDataPtr(); if (p == nullptr) { error(errInternal, -1, "src->getDataPtr() is NULL in Splash::blitMask"); return; } if (vectorAntialias && clipRes != splashClipAllInside) { pipeInit(&pipe, xDest, yDest, state->fillPattern, nullptr, (unsigned char)splashRound(state->fillAlpha * 255), true, false); drawAAPixelInit(); for (y = 0; y < h; ++y) { for (x = 0; x < w; ++x) { pipe.shape = *p++; drawAAPixel(&pipe, xDest + x, yDest + y); } } } else { pipeInit(&pipe, xDest, yDest, state->fillPattern, nullptr, (unsigned char)splashRound(state->fillAlpha * 255), true, false); if (clipRes == splashClipAllInside) { for (y = 0; y < h; ++y) { pipeSetXY(&pipe, xDest, yDest + y); for (x = 0; x < w; ++x) { if (*p) { pipe.shape = *p; (this->*pipe.run)(&pipe); } else { pipeIncX(&pipe); } ++p; } } } else { for (y = 0; y < h; ++y) { pipeSetXY(&pipe, xDest, yDest + y); for (x = 0; x < w; ++x) { if (*p && state->clip->test(xDest + x, yDest + y)) { pipe.shape = *p; (this->*pipe.run)(&pipe); } else { pipeIncX(&pipe); } ++p; } } } } } SplashError Splash::drawImage(SplashImageSource src, SplashICCTransform tf, void *srcData, SplashColorMode srcMode, bool srcAlpha, int w, int h, SplashCoord *mat, bool interpolate, bool tilingPattern) { bool ok; SplashBitmap *scaledImg; SplashClipResult clipRes; bool minorAxisZero; int x0, y0, x1, y1, scaledWidth, scaledHeight; int nComps; int yp; 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 = false; // 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 splashModeXBGR8: ok = srcMode == splashModeXBGR8; nComps = 4; break; case splashModeBGR8: ok = srcMode == splashModeBGR8; nComps = 3; break; case splashModeCMYK8: ok = srcMode == splashModeCMYK8; nComps = 4; break; case splashModeDeviceN8: ok = srcMode == splashModeDeviceN8; nComps = SPOT_NCOMPS + 4; break; default: ok = false; break; } if (!ok) { return splashErrModeMismatch; } // check for singular matrix if (!splashCheckDet(mat[0], mat[1], mat[2], mat[3], 0.000001)) { return splashErrSingularMatrix; } minorAxisZero = mat[1] == 0 && mat[2] == 0; // scaling only if (mat[0] > 0 && minorAxisZero && mat[3] > 0) { x0 = imgCoordMungeLower(mat[4]); y0 = imgCoordMungeLower(mat[5]); x1 = imgCoordMungeUpper(mat[0] + mat[4]); y1 = imgCoordMungeUpper(mat[3] + mat[5]); // make sure narrow images cover at least one pixel if (x0 == x1) { ++x1; } if (y0 == y1) { ++y1; } clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1); opClipRes = clipRes; if (clipRes != splashClipAllOutside) { scaledWidth = x1 - x0; scaledHeight = y1 - y0; yp = h / scaledHeight; if (yp < 0 || yp > INT_MAX - 1) { return splashErrBadArg; } scaledImg = scaleImage(src, srcData, srcMode, nComps, srcAlpha, w, h, scaledWidth, scaledHeight, interpolate, tilingPattern); if (scaledImg == nullptr) { return splashErrBadArg; } if (tf != nullptr) { (*tf)(srcData, scaledImg); } blitImage(scaledImg, srcAlpha, x0, y0, clipRes); delete scaledImg; } // scaling plus vertical flip } else if (mat[0] > 0 && minorAxisZero && mat[3] < 0) { x0 = imgCoordMungeLower(mat[4]); y0 = imgCoordMungeLower(mat[3] + mat[5]); x1 = imgCoordMungeUpper(mat[0] + mat[4]); y1 = imgCoordMungeUpper(mat[5]); if (x0 == x1) { if (mat[4] + mat[0] * 0.5 < x0) { --x0; } else { ++x1; } } if (y0 == y1) { if (mat[5] + mat[1] * 0.5 < y0) { --y0; } else { ++y1; } } clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1); opClipRes = clipRes; if (clipRes != splashClipAllOutside) { scaledWidth = x1 - x0; scaledHeight = y1 - y0; yp = h / scaledHeight; if (yp < 0 || yp > INT_MAX - 1) { return splashErrBadArg; } scaledImg = scaleImage(src, srcData, srcMode, nComps, srcAlpha, w, h, scaledWidth, scaledHeight, interpolate, tilingPattern); if (scaledImg == nullptr) { return splashErrBadArg; } if (tf != nullptr) { (*tf)(srcData, scaledImg); } vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); blitImage(scaledImg, srcAlpha, x0, y0, clipRes); delete scaledImg; } // all other cases } else { return arbitraryTransformImage(src, tf, srcData, srcMode, nComps, srcAlpha, w, h, mat, interpolate, tilingPattern); } return splashOk; } SplashError Splash::arbitraryTransformImage(SplashImageSource src, SplashICCTransform tf, void *srcData, SplashColorMode srcMode, int nComps, bool srcAlpha, int srcWidth, int srcHeight, SplashCoord *mat, bool interpolate, bool tilingPattern) { SplashBitmap *scaledImg; SplashClipResult clipRes, clipRes2; SplashPipe pipe; SplashColor pixel = {}; int scaledWidth, scaledHeight, t0, t1, th; SplashCoord r00, r01, r10, r11, det, ir00, ir01, ir10, ir11; SplashCoord vx[4], vy[4]; int xMin, yMin, xMax, yMax; ImageSection section[3]; int nSections; int y, xa, xb, x, i, xx, yy, yp; // compute the four vertices of the target quadrilateral vx[0] = mat[4]; vy[0] = mat[5]; vx[1] = mat[2] + mat[4]; vy[1] = mat[3] + mat[5]; vx[2] = mat[0] + mat[2] + mat[4]; vy[2] = mat[1] + mat[3] + mat[5]; vx[3] = mat[0] + mat[4]; vy[3] = mat[1] + mat[5]; // clipping xMin = imgCoordMungeLower(vx[0]); xMax = imgCoordMungeUpper(vx[0]); yMin = imgCoordMungeLower(vy[0]); yMax = imgCoordMungeUpper(vy[0]); for (i = 1; i < 4; ++i) { t0 = imgCoordMungeLower(vx[i]); if (t0 < xMin) { xMin = t0; } t0 = imgCoordMungeUpper(vx[i]); if (t0 > xMax) { xMax = t0; } t1 = imgCoordMungeLower(vy[i]); if (t1 < yMin) { yMin = t1; } t1 = imgCoordMungeUpper(vy[i]); if (t1 > yMax) { yMax = t1; } } clipRes = state->clip->testRect(xMin, yMin, xMax, yMax); opClipRes = clipRes; if (clipRes == splashClipAllOutside) { return splashOk; } // compute the scale factors if (splashAbs(mat[0]) >= splashAbs(mat[1])) { scaledWidth = xMax - xMin; scaledHeight = yMax - yMin; } else { scaledWidth = yMax - yMin; scaledHeight = xMax - xMin; } if (scaledHeight <= 1 || scaledWidth <= 1 || tilingPattern) { if (mat[0] >= 0) { t0 = imgCoordMungeUpper(mat[0] + mat[4]) - imgCoordMungeLower(mat[4]); } else { t0 = imgCoordMungeUpper(mat[4]) - imgCoordMungeLower(mat[0] + mat[4]); } if (mat[1] >= 0) { t1 = imgCoordMungeUpper(mat[1] + mat[5]) - imgCoordMungeLower(mat[5]); } else { t1 = imgCoordMungeUpper(mat[5]) - imgCoordMungeLower(mat[1] + mat[5]); } scaledWidth = t0 > t1 ? t0 : t1; if (mat[2] >= 0) { t0 = imgCoordMungeUpper(mat[2] + mat[4]) - imgCoordMungeLower(mat[4]); if (splashAbs(mat[1]) >= 1) { th = imgCoordMungeUpper(mat[2]) - imgCoordMungeLower(mat[0] * mat[3] / mat[1]); if (th > t0) t0 = th; } } else { t0 = imgCoordMungeUpper(mat[4]) - imgCoordMungeLower(mat[2] + mat[4]); if (splashAbs(mat[1]) >= 1) { th = imgCoordMungeUpper(mat[0] * mat[3] / mat[1]) - imgCoordMungeLower(mat[2]); if (th > t0) t0 = th; } } if (mat[3] >= 0) { t1 = imgCoordMungeUpper(mat[3] + mat[5]) - imgCoordMungeLower(mat[5]); if (splashAbs(mat[0]) >= 1) { th = imgCoordMungeUpper(mat[3]) - imgCoordMungeLower(mat[1] * mat[2] / mat[0]); if (th > t1) t1 = th; } } else { t1 = imgCoordMungeUpper(mat[5]) - imgCoordMungeLower(mat[3] + mat[5]); if (splashAbs(mat[0]) >= 1) { th = imgCoordMungeUpper(mat[1] * mat[2] / mat[0]) - imgCoordMungeLower(mat[3]); if (th > t1) t1 = th; } } scaledHeight = t0 > t1 ? t0 : t1; } if (scaledWidth == 0) { scaledWidth = 1; } if (scaledHeight == 0) { scaledHeight = 1; } // compute the inverse transform (after scaling) matrix r00 = mat[0] / scaledWidth; r01 = mat[1] / scaledWidth; r10 = mat[2] / scaledHeight; r11 = mat[3] / scaledHeight; det = r00 * r11 - r01 * r10; if (splashAbs(det) < 1e-6) { // this should be caught by the singular matrix check in drawImage return splashErrBadArg; } ir00 = r11 / det; ir01 = -r01 / det; ir10 = -r10 / det; ir11 = r00 / det; // scale the input image yp = srcHeight / scaledHeight; if (yp < 0 || yp > INT_MAX - 1) { return splashErrBadArg; } scaledImg = scaleImage(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, interpolate); if (scaledImg == nullptr) { return splashErrBadArg; } if (tf != nullptr) { (*tf)(srcData, scaledImg); } // construct the three sections i = 0; if (vy[1] < vy[i]) { i = 1; } if (vy[2] < vy[i]) { i = 2; } if (vy[3] < vy[i]) { i = 3; } // NB: if using fixed point, 0.000001 will be truncated to zero, // so these two comparisons must be <=, not < if (splashAbs(vy[i] - vy[(i - 1) & 3]) <= 0.000001 && vy[(i - 1) & 3] < vy[(i + 1) & 3]) { i = (i - 1) & 3; } if (splashAbs(vy[i] - vy[(i + 1) & 3]) <= 0.000001) { section[0].y0 = imgCoordMungeLower(vy[i]); section[0].y1 = imgCoordMungeUpper(vy[(i + 2) & 3]) - 1; if (vx[i] < vx[(i + 1) & 3]) { section[0].ia0 = i; section[0].ia1 = (i + 3) & 3; section[0].ib0 = (i + 1) & 3; section[0].ib1 = (i + 2) & 3; } else { section[0].ia0 = (i + 1) & 3; section[0].ia1 = (i + 2) & 3; section[0].ib0 = i; section[0].ib1 = (i + 3) & 3; } nSections = 1; } else { section[0].y0 = imgCoordMungeLower(vy[i]); section[2].y1 = imgCoordMungeUpper(vy[(i + 2) & 3]) - 1; section[0].ia0 = section[0].ib0 = i; section[2].ia1 = section[2].ib1 = (i + 2) & 3; if (vx[(i + 1) & 3] < vx[(i + 3) & 3]) { section[0].ia1 = section[2].ia0 = (i + 1) & 3; section[0].ib1 = section[2].ib0 = (i + 3) & 3; } else { section[0].ia1 = section[2].ia0 = (i + 3) & 3; section[0].ib1 = section[2].ib0 = (i + 1) & 3; } if (vy[(i + 1) & 3] < vy[(i + 3) & 3]) { section[1].y0 = imgCoordMungeLower(vy[(i + 1) & 3]); section[2].y0 = imgCoordMungeUpper(vy[(i + 3) & 3]); if (vx[(i + 1) & 3] < vx[(i + 3) & 3]) { section[1].ia0 = (i + 1) & 3; section[1].ia1 = (i + 2) & 3; section[1].ib0 = i; section[1].ib1 = (i + 3) & 3; } else { section[1].ia0 = i; section[1].ia1 = (i + 3) & 3; section[1].ib0 = (i + 1) & 3; section[1].ib1 = (i + 2) & 3; } } else { section[1].y0 = imgCoordMungeLower(vy[(i + 3) & 3]); section[2].y0 = imgCoordMungeUpper(vy[(i + 1) & 3]); if (vx[(i + 1) & 3] < vx[(i + 3) & 3]) { section[1].ia0 = i; section[1].ia1 = (i + 1) & 3; section[1].ib0 = (i + 3) & 3; section[1].ib1 = (i + 2) & 3; } else { section[1].ia0 = (i + 3) & 3; section[1].ia1 = (i + 2) & 3; section[1].ib0 = i; section[1].ib1 = (i + 1) & 3; } } section[0].y1 = section[1].y0 - 1; section[1].y1 = section[2].y0 - 1; nSections = 3; } for (i = 0; i < nSections; ++i) { section[i].xa0 = vx[section[i].ia0]; section[i].ya0 = vy[section[i].ia0]; section[i].xa1 = vx[section[i].ia1]; section[i].ya1 = vy[section[i].ia1]; section[i].xb0 = vx[section[i].ib0]; section[i].yb0 = vy[section[i].ib0]; section[i].xb1 = vx[section[i].ib1]; section[i].yb1 = vy[section[i].ib1]; section[i].dxdya = (section[i].xa1 - section[i].xa0) / (section[i].ya1 - section[i].ya0); section[i].dxdyb = (section[i].xb1 - section[i].xb0) / (section[i].yb1 - section[i].yb0); } // initialize the pixel pipe pipeInit(&pipe, 0, 0, nullptr, pixel, (unsigned char)splashRound(state->fillAlpha * 255), srcAlpha || (vectorAntialias && clipRes != splashClipAllInside), false); if (vectorAntialias) { drawAAPixelInit(); } // make sure narrow images cover at least one pixel if (nSections == 1) { if (section[0].y0 == section[0].y1) { ++section[0].y1; clipRes = opClipRes = splashClipPartial; } } else { if (section[0].y0 == section[2].y1) { ++section[1].y1; clipRes = opClipRes = splashClipPartial; } } // scan all pixels inside the target region for (i = 0; i < nSections; ++i) { for (y = section[i].y0; y <= section[i].y1; ++y) { xa = imgCoordMungeLower(section[i].xa0 + ((SplashCoord)y + 0.5 - section[i].ya0) * section[i].dxdya); if (unlikely(xa < 0)) xa = 0; xb = imgCoordMungeUpper(section[i].xb0 + ((SplashCoord)y + 0.5 - section[i].yb0) * section[i].dxdyb); // make sure narrow images cover at least one pixel if (xa == xb) { ++xb; } if (clipRes != splashClipAllInside) { clipRes2 = state->clip->testSpan(xa, xb - 1, y); } else { clipRes2 = clipRes; } for (x = xa; x < xb; ++x) { // map (x+0.5, y+0.5) back to the scaled image xx = splashFloor(((SplashCoord)x + 0.5 - mat[4]) * ir00 + ((SplashCoord)y + 0.5 - mat[5]) * ir10); yy = splashFloor(((SplashCoord)x + 0.5 - mat[4]) * ir01 + ((SplashCoord)y + 0.5 - mat[5]) * ir11); // xx should always be within bounds, but floating point // inaccuracy can cause problems if (xx < 0) { xx = 0; } else if (xx >= scaledWidth) { xx = scaledWidth - 1; } if (yy < 0) { yy = 0; } else if (yy >= scaledHeight) { yy = scaledHeight - 1; } scaledImg->getPixel(xx, yy, pixel); if (srcAlpha) { pipe.shape = scaledImg->alpha[yy * scaledWidth + xx]; } else { pipe.shape = 255; } if (vectorAntialias && clipRes2 != splashClipAllInside) { drawAAPixel(&pipe, x, y); } else { drawPixel(&pipe, x, y, clipRes2 == splashClipAllInside); } } } } delete scaledImg; return splashOk; } // determine if a scaled image requires interpolation based on the scale and // the interpolate flag from the image dictionary static bool isImageInterpolationRequired(int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, bool interpolate) { if (interpolate || srcWidth == 0 || srcHeight == 0) return true; /* When scale factor is >= 400% we don't interpolate. See bugs #25268, #9860 */ if (scaledWidth / srcWidth >= 4 || scaledHeight / srcHeight >= 4) return false; return true; } // Scale an image into a SplashBitmap. SplashBitmap *Splash::scaleImage(SplashImageSource src, void *srcData, SplashColorMode srcMode, int nComps, bool srcAlpha, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, bool interpolate, bool tilingPattern) { SplashBitmap *dest; dest = new SplashBitmap(scaledWidth, scaledHeight, 1, srcMode, srcAlpha, true, bitmap->getSeparationList()); if (dest->getDataPtr() != nullptr && srcHeight > 0 && srcWidth > 0) { if (scaledHeight < srcHeight) { if (scaledWidth < srcWidth) { scaleImageYdownXdown(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } else { scaleImageYdownXup(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } } else { if (scaledWidth < srcWidth) { scaleImageYupXdown(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } else { if (!tilingPattern && isImageInterpolationRequired(srcWidth, srcHeight, scaledWidth, scaledHeight, interpolate)) { scaleImageYupXupBilinear(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } else { scaleImageYupXup(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } } } } else { delete dest; dest = nullptr; } return dest; } void Splash::scaleImageYdownXdown(SplashImageSource src, void *srcData, SplashColorMode srcMode, int nComps, bool srcAlpha, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, SplashBitmap *dest) { unsigned char *lineBuf, *alphaLineBuf; unsigned int *pixBuf, *alphaPixBuf; unsigned int pix0, pix1, pix2; unsigned int pix3; unsigned int pix[SPOT_NCOMPS + 4], cp; unsigned int alpha; unsigned char *destPtr, *destAlphaPtr; int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx, xxa, d, d0, d1; int i, j; // Bresenham parameters for y scale yp = srcHeight / scaledHeight; yq = srcHeight % scaledHeight; // Bresenham parameters for x scale xp = srcWidth / scaledWidth; xq = srcWidth % scaledWidth; // allocate buffers lineBuf = (unsigned char *)gmallocn_checkoverflow(srcWidth, nComps); if (unlikely(!lineBuf)) { return; } pixBuf = (unsigned int *)gmallocn_checkoverflow(srcWidth, nComps * sizeof(int)); if (unlikely(!pixBuf)) { gfree(lineBuf); return; } if (srcAlpha) { alphaLineBuf = (unsigned char *)gmalloc(srcWidth); alphaPixBuf = (unsigned int *)gmallocn(srcWidth, sizeof(int)); } else { alphaLineBuf = nullptr; alphaPixBuf = nullptr; } // init y scale Bresenham yt = 0; destPtr = dest->data; destAlphaPtr = dest->alpha; for (y = 0; y < scaledHeight; ++y) { // y scale Bresenham if ((yt += yq) >= scaledHeight) { yt -= scaledHeight; yStep = yp + 1; } else { yStep = yp; } // read rows from image memset(pixBuf, 0, srcWidth * nComps * sizeof(int)); if (srcAlpha) { memset(alphaPixBuf, 0, srcWidth * sizeof(int)); } for (i = 0; i < yStep; ++i) { (*src)(srcData, lineBuf, alphaLineBuf); for (j = 0; j < srcWidth * nComps; ++j) { pixBuf[j] += lineBuf[j]; } if (srcAlpha) { for (j = 0; j < srcWidth; ++j) { alphaPixBuf[j] += alphaLineBuf[j]; } } } // init x scale Bresenham xt = 0; d0 = (1 << 23) / (yStep * xp); d1 = (1 << 23) / (yStep * (xp + 1)); xx = xxa = 0; for (x = 0; x < scaledWidth; ++x) { // x scale Bresenham if ((xt += xq) >= scaledWidth) { xt -= scaledWidth; xStep = xp + 1; d = d1; } else { xStep = xp; d = d0; } switch (srcMode) { case splashModeMono8: // compute the final pixel pix0 = 0; for (i = 0; i < xStep; ++i) { pix0 += pixBuf[xx++]; } // pix / xStep * yStep pix0 = (pix0 * d) >> 23; // store the pixel *destPtr++ = (unsigned char)pix0; break; case splashModeRGB8: // compute the final pixel pix0 = pix1 = pix2 = 0; for (i = 0; i < xStep; ++i) { pix0 += pixBuf[xx]; pix1 += pixBuf[xx + 1]; pix2 += pixBuf[xx + 2]; xx += 3; } // pix / xStep * yStep pix0 = (pix0 * d) >> 23; pix1 = (pix1 * d) >> 23; pix2 = (pix2 * d) >> 23; // store the pixel *destPtr++ = (unsigned char)pix0; *destPtr++ = (unsigned char)pix1; *destPtr++ = (unsigned char)pix2; break; case splashModeXBGR8: // compute the final pixel pix0 = pix1 = pix2 = 0; for (i = 0; i < xStep; ++i) { pix0 += pixBuf[xx]; pix1 += pixBuf[xx + 1]; pix2 += pixBuf[xx + 2]; xx += 4; } // pix / xStep * yStep pix0 = (pix0 * d) >> 23; pix1 = (pix1 * d) >> 23; pix2 = (pix2 * d) >> 23; // store the pixel *destPtr++ = (unsigned char)pix2; *destPtr++ = (unsigned char)pix1; *destPtr++ = (unsigned char)pix0; *destPtr++ = (unsigned char)255; break; case splashModeBGR8: // compute the final pixel pix0 = pix1 = pix2 = 0; for (i = 0; i < xStep; ++i) { pix0 += pixBuf[xx]; pix1 += pixBuf[xx + 1]; pix2 += pixBuf[xx + 2]; xx += 3; } // pix / xStep * yStep pix0 = (pix0 * d) >> 23; pix1 = (pix1 * d) >> 23; pix2 = (pix2 * d) >> 23; // store the pixel *destPtr++ = (unsigned char)pix2; *destPtr++ = (unsigned char)pix1; *destPtr++ = (unsigned char)pix0; break; case splashModeCMYK8: // compute the final pixel pix0 = pix1 = pix2 = pix3 = 0; for (i = 0; i < xStep; ++i) { pix0 += pixBuf[xx]; pix1 += pixBuf[xx + 1]; pix2 += pixBuf[xx + 2]; pix3 += pixBuf[xx + 3]; xx += 4; } // pix / xStep * yStep pix0 = (pix0 * d) >> 23; pix1 = (pix1 * d) >> 23; pix2 = (pix2 * d) >> 23; pix3 = (pix3 * d) >> 23; // store the pixel *destPtr++ = (unsigned char)pix0; *destPtr++ = (unsigned char)pix1; *destPtr++ = (unsigned char)pix2; *destPtr++ = (unsigned char)pix3; break; case splashModeDeviceN8: // compute the final pixel for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) pix[cp] = 0; for (i = 0; i < xStep; ++i) { for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) { pix[cp] += pixBuf[xx + cp]; } xx += (SPOT_NCOMPS + 4); } // pix / xStep * yStep for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) pix[cp] = (pix[cp] * d) >> 23; // store the pixel for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) *destPtr++ = (unsigned char)pix[cp]; break; case splashModeMono1: // mono1 is not allowed default: break; } // process alpha if (srcAlpha) { alpha = 0; for (i = 0; i < xStep; ++i, ++xxa) { alpha += alphaPixBuf[xxa]; } // alpha / xStep * yStep alpha = (alpha * d) >> 23; *destAlphaPtr++ = (unsigned char)alpha; } } } gfree(alphaPixBuf); gfree(alphaLineBuf); gfree(pixBuf); gfree(lineBuf); } void Splash::scaleImageYdownXup(SplashImageSource src, void *srcData, SplashColorMode srcMode, int nComps, bool srcAlpha, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, SplashBitmap *dest) { unsigned char *lineBuf, *alphaLineBuf; unsigned int *pixBuf, *alphaPixBuf; unsigned int pix[splashMaxColorComps]; unsigned int alpha; unsigned char *destPtr, *destAlphaPtr; int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, d; int i, j; // Bresenham parameters for y scale yp = srcHeight / scaledHeight; yq = srcHeight % scaledHeight; // Bresenham parameters for x scale xp = scaledWidth / srcWidth; xq = scaledWidth % srcWidth; // allocate buffers pixBuf = (unsigned int *)gmallocn_checkoverflow(srcWidth, nComps * sizeof(int)); if (unlikely(!pixBuf)) { error(errInternal, -1, "Splash::scaleImageYdownXup. Couldn't allocate pixBuf memory"); return; } lineBuf = (unsigned char *)gmallocn(srcWidth, nComps); if (srcAlpha) { alphaLineBuf = (unsigned char *)gmalloc(srcWidth); alphaPixBuf = (unsigned int *)gmallocn(srcWidth, sizeof(int)); } else { alphaLineBuf = nullptr; alphaPixBuf = nullptr; } // init y scale Bresenham yt = 0; destPtr = dest->data; destAlphaPtr = dest->alpha; for (y = 0; y < scaledHeight; ++y) { // y scale Bresenham if ((yt += yq) >= scaledHeight) { yt -= scaledHeight; yStep = yp + 1; } else { yStep = yp; } // read rows from image memset(pixBuf, 0, srcWidth * nComps * sizeof(int)); if (srcAlpha) { memset(alphaPixBuf, 0, srcWidth * sizeof(int)); } for (i = 0; i < yStep; ++i) { (*src)(srcData, lineBuf, alphaLineBuf); for (j = 0; j < srcWidth * nComps; ++j) { pixBuf[j] += lineBuf[j]; } if (srcAlpha) { for (j = 0; j < srcWidth; ++j) { alphaPixBuf[j] += alphaLineBuf[j]; } } } // init x scale Bresenham xt = 0; d = (1 << 23) / yStep; for (x = 0; x < srcWidth; ++x) { // x scale Bresenham if ((xt += xq) >= srcWidth) { xt -= srcWidth; xStep = xp + 1; } else { xStep = xp; } // compute the final pixel for (i = 0; i < nComps; ++i) { // pixBuf[] / yStep pix[i] = (pixBuf[x * nComps + i] * d) >> 23; } // store the pixel switch (srcMode) { case splashModeMono1: // mono1 is not allowed break; case splashModeMono8: for (i = 0; i < xStep; ++i) { *destPtr++ = (unsigned char)pix[0]; } break; case splashModeRGB8: for (i = 0; i < xStep; ++i) { *destPtr++ = (unsigned char)pix[0]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[2]; } break; case splashModeXBGR8: for (i = 0; i < xStep; ++i) { *destPtr++ = (unsigned char)pix[2]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[0]; *destPtr++ = (unsigned char)255; } break; case splashModeBGR8: for (i = 0; i < xStep; ++i) { *destPtr++ = (unsigned char)pix[2]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[0]; } break; case splashModeCMYK8: for (i = 0; i < xStep; ++i) { *destPtr++ = (unsigned char)pix[0]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[2]; *destPtr++ = (unsigned char)pix[3]; } break; case splashModeDeviceN8: for (i = 0; i < xStep; ++i) { for (unsigned int cp : pix) *destPtr++ = (unsigned char)cp; } break; } // process alpha if (srcAlpha) { // alphaPixBuf[] / yStep alpha = (alphaPixBuf[x] * d) >> 23; for (i = 0; i < xStep; ++i) { *destAlphaPtr++ = (unsigned char)alpha; } } } } gfree(alphaPixBuf); gfree(alphaLineBuf); gfree(pixBuf); gfree(lineBuf); } void Splash::scaleImageYupXdown(SplashImageSource src, void *srcData, SplashColorMode srcMode, int nComps, bool srcAlpha, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, SplashBitmap *dest) { unsigned char *lineBuf, *alphaLineBuf; unsigned int pix[splashMaxColorComps]; unsigned int alpha; unsigned char *destPtr0, *destPtr, *destAlphaPtr0, *destAlphaPtr; int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx, xxa, d, d0, d1; int i, j; // Bresenham parameters for y scale yp = scaledHeight / srcHeight; yq = scaledHeight % srcHeight; // Bresenham parameters for x scale xp = srcWidth / scaledWidth; xq = srcWidth % scaledWidth; // allocate buffers lineBuf = (unsigned char *)gmallocn_checkoverflow(srcWidth, nComps); if (unlikely(!lineBuf)) { gfree(dest->takeData()); return; } if (srcAlpha) { alphaLineBuf = (unsigned char *)gmalloc(srcWidth); } else { alphaLineBuf = nullptr; } // init y scale Bresenham yt = 0; destPtr0 = dest->data; destAlphaPtr0 = dest->alpha; for (y = 0; y < srcHeight; ++y) { // y scale Bresenham if ((yt += yq) >= srcHeight) { yt -= srcHeight; yStep = yp + 1; } else { yStep = yp; } // read row from image (*src)(srcData, lineBuf, alphaLineBuf); // init x scale Bresenham xt = 0; d0 = (1 << 23) / xp; d1 = (1 << 23) / (xp + 1); xx = xxa = 0; for (x = 0; x < scaledWidth; ++x) { // x scale Bresenham if ((xt += xq) >= scaledWidth) { xt -= scaledWidth; xStep = xp + 1; d = d1; } else { xStep = xp; d = d0; } // compute the final pixel for (i = 0; i < nComps; ++i) { pix[i] = 0; } for (i = 0; i < xStep; ++i) { for (j = 0; j < nComps; ++j, ++xx) { pix[j] += lineBuf[xx]; } } for (i = 0; i < nComps; ++i) { // pix[] / xStep pix[i] = (pix[i] * d) >> 23; } // store the pixel switch (srcMode) { case splashModeMono1: // mono1 is not allowed break; case splashModeMono8: for (i = 0; i < yStep; ++i) { destPtr = destPtr0 + (i * scaledWidth + x) * nComps; *destPtr++ = (unsigned char)pix[0]; } break; case splashModeRGB8: for (i = 0; i < yStep; ++i) { destPtr = destPtr0 + (i * scaledWidth + x) * nComps; *destPtr++ = (unsigned char)pix[0]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[2]; } break; case splashModeXBGR8: for (i = 0; i < yStep; ++i) { destPtr = destPtr0 + (i * scaledWidth + x) * nComps; *destPtr++ = (unsigned char)pix[2]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[0]; *destPtr++ = (unsigned char)255; } break; case splashModeBGR8: for (i = 0; i < yStep; ++i) { destPtr = destPtr0 + (i * scaledWidth + x) * nComps; *destPtr++ = (unsigned char)pix[2]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[0]; } break; case splashModeCMYK8: for (i = 0; i < yStep; ++i) { destPtr = destPtr0 + (i * scaledWidth + x) * nComps; *destPtr++ = (unsigned char)pix[0]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[2]; *destPtr++ = (unsigned char)pix[3]; } break; case splashModeDeviceN8: for (i = 0; i < yStep; ++i) { destPtr = destPtr0 + (i * scaledWidth + x) * nComps; for (unsigned int cp : pix) *destPtr++ = (unsigned char)cp; } break; } // process alpha if (srcAlpha) { alpha = 0; for (i = 0; i < xStep; ++i, ++xxa) { alpha += alphaLineBuf[xxa]; } // alpha / xStep alpha = (alpha * d) >> 23; for (i = 0; i < yStep; ++i) { destAlphaPtr = destAlphaPtr0 + i * scaledWidth + x; *destAlphaPtr = (unsigned char)alpha; } } } destPtr0 += yStep * scaledWidth * nComps; if (srcAlpha) { destAlphaPtr0 += yStep * scaledWidth; } } gfree(alphaLineBuf); gfree(lineBuf); } void Splash::scaleImageYupXup(SplashImageSource src, void *srcData, SplashColorMode srcMode, int nComps, bool srcAlpha, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, SplashBitmap *dest) { unsigned char *lineBuf, *alphaLineBuf; unsigned int pix[splashMaxColorComps]; unsigned int alpha; unsigned char *destPtr0, *destPtr, *destAlphaPtr0, *destAlphaPtr; int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx; int i, j; // Bresenham parameters for y scale yp = scaledHeight / srcHeight; yq = scaledHeight % srcHeight; // Bresenham parameters for x scale xp = scaledWidth / srcWidth; xq = scaledWidth % srcWidth; // allocate buffers lineBuf = (unsigned char *)gmallocn(srcWidth, nComps); if (srcAlpha) { alphaLineBuf = (unsigned char *)gmalloc(srcWidth); } else { alphaLineBuf = nullptr; } // init y scale Bresenham yt = 0; destPtr0 = dest->data; destAlphaPtr0 = dest->alpha; for (y = 0; y < srcHeight; ++y) { // y scale Bresenham if ((yt += yq) >= srcHeight) { yt -= srcHeight; yStep = yp + 1; } else { yStep = yp; } // read row from image (*src)(srcData, lineBuf, alphaLineBuf); // init x scale Bresenham xt = 0; xx = 0; for (x = 0; x < srcWidth; ++x) { // x scale Bresenham if ((xt += xq) >= srcWidth) { xt -= srcWidth; xStep = xp + 1; } else { xStep = xp; } // compute the final pixel for (i = 0; i < nComps; ++i) { pix[i] = lineBuf[x * nComps + i]; } // store the pixel switch (srcMode) { case splashModeMono1: // mono1 is not allowed break; case splashModeMono8: for (i = 0; i < yStep; ++i) { for (j = 0; j < xStep; ++j) { destPtr = destPtr0 + (i * scaledWidth + xx + j) * nComps; *destPtr++ = (unsigned char)pix[0]; } } break; case splashModeRGB8: for (i = 0; i < yStep; ++i) { for (j = 0; j < xStep; ++j) { destPtr = destPtr0 + (i * scaledWidth + xx + j) * nComps; *destPtr++ = (unsigned char)pix[0]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[2]; } } break; case splashModeXBGR8: for (i = 0; i < yStep; ++i) { for (j = 0; j < xStep; ++j) { destPtr = destPtr0 + (i * scaledWidth + xx + j) * nComps; *destPtr++ = (unsigned char)pix[2]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[0]; *destPtr++ = (unsigned char)255; } } break; case splashModeBGR8: for (i = 0; i < yStep; ++i) { for (j = 0; j < xStep; ++j) { destPtr = destPtr0 + (i * scaledWidth + xx + j) * nComps; *destPtr++ = (unsigned char)pix[2]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[0]; } } break; case splashModeCMYK8: for (i = 0; i < yStep; ++i) { for (j = 0; j < xStep; ++j) { destPtr = destPtr0 + (i * scaledWidth + xx + j) * nComps; *destPtr++ = (unsigned char)pix[0]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[2]; *destPtr++ = (unsigned char)pix[3]; } } break; case splashModeDeviceN8: for (i = 0; i < yStep; ++i) { for (j = 0; j < xStep; ++j) { destPtr = destPtr0 + (i * scaledWidth + xx + j) * nComps; for (unsigned int cp : pix) *destPtr++ = (unsigned char)cp; } } break; } // process alpha if (srcAlpha) { alpha = alphaLineBuf[x]; for (i = 0; i < yStep; ++i) { for (j = 0; j < xStep; ++j) { destAlphaPtr = destAlphaPtr0 + i * scaledWidth + xx + j; *destAlphaPtr = (unsigned char)alpha; } } } xx += xStep; } destPtr0 += yStep * scaledWidth * nComps; if (srcAlpha) { destAlphaPtr0 += yStep * scaledWidth; } } gfree(alphaLineBuf); gfree(lineBuf); } // expand source row to scaledWidth using linear interpolation static void expandRow(unsigned char *srcBuf, unsigned char *dstBuf, int srcWidth, int scaledWidth, int nComps) { double xStep = (double)srcWidth / scaledWidth; double xSrc = 0.0; double xFrac, xInt; int p; // pad the source with an extra pixel equal to the last pixel // so that when xStep is inside the last pixel we still have two // pixels to interpolate between. for (int i = 0; i < nComps; i++) srcBuf[srcWidth * nComps + i] = srcBuf[(srcWidth - 1) * nComps + i]; for (int x = 0; x < scaledWidth; x++) { xFrac = modf(xSrc, &xInt); p = (int)xInt; for (int c = 0; c < nComps; c++) { dstBuf[nComps * x + c] = srcBuf[nComps * p + c] * (1.0 - xFrac) + srcBuf[nComps * (p + 1) + c] * xFrac; } xSrc += xStep; } } // Scale up image using bilinear interpolation void Splash::scaleImageYupXupBilinear(SplashImageSource src, void *srcData, SplashColorMode srcMode, int nComps, bool srcAlpha, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, SplashBitmap *dest) { unsigned char *srcBuf, *lineBuf1, *lineBuf2, *alphaSrcBuf, *alphaLineBuf1, *alphaLineBuf2; unsigned int pix[splashMaxColorComps]; unsigned char *destPtr0, *destPtr, *destAlphaPtr0, *destAlphaPtr; int i; if (srcWidth < 1 || srcHeight < 1) return; // allocate buffers srcBuf = (unsigned char *)gmallocn(srcWidth + 1, nComps); // + 1 pixel of padding lineBuf1 = (unsigned char *)gmallocn(scaledWidth, nComps); lineBuf2 = (unsigned char *)gmallocn(scaledWidth, nComps); if (srcAlpha) { alphaSrcBuf = (unsigned char *)gmalloc(srcWidth + 1); // + 1 pixel of padding alphaLineBuf1 = (unsigned char *)gmalloc(scaledWidth); alphaLineBuf2 = (unsigned char *)gmalloc(scaledWidth); } else { alphaSrcBuf = nullptr; alphaLineBuf1 = nullptr; alphaLineBuf2 = nullptr; } double ySrc = 0.0; double yStep = (double)srcHeight / scaledHeight; double yFrac, yInt; int currentSrcRow = -1; (*src)(srcData, srcBuf, alphaSrcBuf); expandRow(srcBuf, lineBuf2, srcWidth, scaledWidth, nComps); if (srcAlpha) expandRow(alphaSrcBuf, alphaLineBuf2, srcWidth, scaledWidth, 1); destPtr0 = dest->data; destAlphaPtr0 = dest->alpha; for (int y = 0; y < scaledHeight; y++) { yFrac = modf(ySrc, &yInt); if ((int)yInt > currentSrcRow) { currentSrcRow++; // Copy line2 data to line1 and get next line2 data. // If line2 already contains the last source row we don't touch it. // This effectively adds an extra row of padding for interpolating the // last source row with. memcpy(lineBuf1, lineBuf2, scaledWidth * nComps); if (srcAlpha) memcpy(alphaLineBuf1, alphaLineBuf2, scaledWidth); if (currentSrcRow < srcHeight - 1) { (*src)(srcData, srcBuf, alphaSrcBuf); expandRow(srcBuf, lineBuf2, srcWidth, scaledWidth, nComps); if (srcAlpha) expandRow(alphaSrcBuf, alphaLineBuf2, srcWidth, scaledWidth, 1); } } // write row y using linear interpolation on lineBuf1 and lineBuf2 for (int x = 0; x < scaledWidth; ++x) { // compute the final pixel for (i = 0; i < nComps; ++i) { pix[i] = lineBuf1[x * nComps + i] * (1.0 - yFrac) + lineBuf2[x * nComps + i] * yFrac; } // store the pixel destPtr = destPtr0 + (y * scaledWidth + x) * nComps; switch (srcMode) { case splashModeMono1: // mono1 is not allowed break; case splashModeMono8: *destPtr++ = (unsigned char)pix[0]; break; case splashModeRGB8: *destPtr++ = (unsigned char)pix[0]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[2]; break; case splashModeXBGR8: *destPtr++ = (unsigned char)pix[2]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[0]; *destPtr++ = (unsigned char)255; break; case splashModeBGR8: *destPtr++ = (unsigned char)pix[2]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[0]; break; case splashModeCMYK8: *destPtr++ = (unsigned char)pix[0]; *destPtr++ = (unsigned char)pix[1]; *destPtr++ = (unsigned char)pix[2]; *destPtr++ = (unsigned char)pix[3]; break; case splashModeDeviceN8: for (unsigned int cp : pix) *destPtr++ = (unsigned char)cp; break; } // process alpha if (srcAlpha) { destAlphaPtr = destAlphaPtr0 + y * scaledWidth + x; *destAlphaPtr = alphaLineBuf1[x] * (1.0 - yFrac) + alphaLineBuf2[x] * yFrac; } } ySrc += yStep; } gfree(alphaSrcBuf); gfree(alphaLineBuf1); gfree(alphaLineBuf2); gfree(srcBuf); gfree(lineBuf1); gfree(lineBuf2); } void Splash::vertFlipImage(SplashBitmap *img, int width, int height, int nComps) { unsigned char *lineBuf; unsigned char *p0, *p1; int w; if (unlikely(img->data == nullptr)) { error(errInternal, -1, "img->data is NULL in Splash::vertFlipImage"); return; } w = width * nComps; lineBuf = (unsigned char *)gmalloc(w); for (p0 = img->data, p1 = img->data + (height - 1) * w; p0 < p1; p0 += w, p1 -= w) { memcpy(lineBuf, p0, w); memcpy(p0, p1, w); memcpy(p1, lineBuf, w); } if (img->alpha) { for (p0 = img->alpha, p1 = img->alpha + (height - 1) * width; p0 < p1; p0 += width, p1 -= width) { memcpy(lineBuf, p0, width); memcpy(p0, p1, width); memcpy(p1, lineBuf, width); } } gfree(lineBuf); } void Splash::blitImage(SplashBitmap *src, bool srcAlpha, int xDest, int yDest) { SplashClipResult clipRes = state->clip->testRect(xDest, yDest, xDest + src->getWidth() - 1, yDest + src->getHeight() - 1); if (clipRes != splashClipAllOutside) { blitImage(src, srcAlpha, xDest, yDest, clipRes); } } void Splash::blitImage(SplashBitmap *src, bool srcAlpha, int xDest, int yDest, SplashClipResult clipRes) { SplashPipe pipe; SplashColor pixel = {}; unsigned char *ap; int w, h, x0, y0, x1, y1, x, y; // split the image into clipped and unclipped regions w = src->getWidth(); h = src->getHeight(); if (clipRes == splashClipAllInside) { x0 = 0; y0 = 0; x1 = w; y1 = h; } else { if (state->clip->getNumPaths()) { x0 = x1 = w; y0 = y1 = h; } else { if ((x0 = splashCeil(state->clip->getXMin()) - xDest) < 0) { x0 = 0; } if ((y0 = splashCeil(state->clip->getYMin()) - yDest) < 0) { y0 = 0; } if ((x1 = splashFloor(state->clip->getXMax()) - xDest) > w) { x1 = w; } if (x1 < x0) { x1 = x0; } if ((y1 = splashFloor(state->clip->getYMax()) - yDest) > h) { y1 = h; } if (y1 < y0) { y1 = y0; } } } // draw the unclipped region if (x0 < w && y0 < h && x0 < x1 && y0 < y1) { pipeInit(&pipe, xDest + x0, yDest + y0, nullptr, pixel, (unsigned char)splashRound(state->fillAlpha * 255), srcAlpha, false); if (srcAlpha) { for (y = y0; y < y1; ++y) { pipeSetXY(&pipe, xDest + x0, yDest + y); ap = src->getAlphaPtr() + y * w + x0; for (x = x0; x < x1; ++x) { src->getPixel(x, y, pixel); pipe.shape = *ap++; (this->*pipe.run)(&pipe); } } } else { for (y = y0; y < y1; ++y) { pipeSetXY(&pipe, xDest + x0, yDest + y); for (x = x0; x < x1; ++x) { src->getPixel(x, y, pixel); (this->*pipe.run)(&pipe); } } } } // draw the clipped regions if (y0 > 0) { blitImageClipped(src, srcAlpha, 0, 0, xDest, yDest, w, y0); } if (y1 < h) { blitImageClipped(src, srcAlpha, 0, y1, xDest, yDest + y1, w, h - y1); } if (x0 > 0 && y0 < y1) { blitImageClipped(src, srcAlpha, 0, y0, xDest, yDest + y0, x0, y1 - y0); } if (x1 < w && y0 < y1) { blitImageClipped(src, srcAlpha, x1, y0, xDest + x1, yDest + y0, w - x1, y1 - y0); } } void Splash::blitImageClipped(SplashBitmap *src, bool srcAlpha, int xSrc, int ySrc, int xDest, int yDest, int w, int h) { SplashPipe pipe; SplashColor pixel = {}; unsigned char *ap; int x, y; if (vectorAntialias) { pipeInit(&pipe, xDest, yDest, nullptr, pixel, (unsigned char)splashRound(state->fillAlpha * 255), true, false); drawAAPixelInit(); if (srcAlpha) { for (y = 0; y < h; ++y) { ap = src->getAlphaPtr() + (ySrc + y) * src->getWidth() + xSrc; for (x = 0; x < w; ++x) { src->getPixel(xSrc + x, ySrc + y, pixel); pipe.shape = *ap++; drawAAPixel(&pipe, xDest + x, yDest + y); } } } else { for (y = 0; y < h; ++y) { for (x = 0; x < w; ++x) { src->getPixel(xSrc + x, ySrc + y, pixel); pipe.shape = 255; drawAAPixel(&pipe, xDest + x, yDest + y); } } } } else { pipeInit(&pipe, xDest, yDest, nullptr, pixel, (unsigned char)splashRound(state->fillAlpha * 255), srcAlpha, false); if (srcAlpha) { for (y = 0; y < h; ++y) { ap = src->getAlphaPtr() + (ySrc + y) * src->getWidth() + xSrc; pipeSetXY(&pipe, xDest, yDest + y); for (x = 0; x < w; ++x) { if (state->clip->test(xDest + x, yDest + y)) { src->getPixel(xSrc + x, ySrc + y, pixel); pipe.shape = *ap++; (this->*pipe.run)(&pipe); } else { pipeIncX(&pipe); ++ap; } } } } else { for (y = 0; y < h; ++y) { pipeSetXY(&pipe, xDest, yDest + y); for (x = 0; x < w; ++x) { if (state->clip->test(xDest + x, yDest + y)) { src->getPixel(xSrc + x, ySrc + y, pixel); (this->*pipe.run)(&pipe); } else { pipeIncX(&pipe); } } } } } } SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc, int xDest, int yDest, int w, int h, bool noClip, bool nonIsolated, bool knockout, SplashCoord knockoutOpacity) { SplashPipe pipe; SplashColor pixel; unsigned char alpha; unsigned char *ap; int x, y; if (src->mode != bitmap->mode) { return splashErrModeMismatch; } if (unlikely(!bitmap->data)) { return splashErrZeroImage; } if (src->getSeparationList()->size() > bitmap->getSeparationList()->size()) { for (x = bitmap->getSeparationList()->size(); x < (int)src->getSeparationList()->size(); x++) bitmap->getSeparationList()->push_back((GfxSeparationColorSpace *)((*src->getSeparationList())[x])->copy()); } if (src->alpha) { pipeInit(&pipe, xDest, yDest, nullptr, pixel, (unsigned char)splashRound(state->fillAlpha * 255), true, nonIsolated, knockout, (unsigned char)splashRound(knockoutOpacity * 255)); if (noClip) { 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++; // this uses shape instead of alpha, which isn't technically // correct, but works out the same pipe.shape = alpha; (this->*pipe.run)(&pipe); } } } else { 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 (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 = alpha; (this->*pipe.run)(&pipe); } else { pipeIncX(&pipe); } } } } } else { pipeInit(&pipe, xDest, yDest, nullptr, pixel, (unsigned char)splashRound(state->fillAlpha * 255), false, nonIsolated); if (noClip) { for (y = 0; y < h; ++y) { pipeSetXY(&pipe, xDest, yDest + y); for (x = 0; x < w; ++x) { src->getPixel(xSrc + x, ySrc + y, pixel); (this->*pipe.run)(&pipe); } } } else { 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 (state->clip->test(xDest + x, yDest + y)) { (this->*pipe.run)(&pipe); } else { pipeIncX(&pipe); } } } } } return splashOk; } void Splash::compositeBackground(SplashColorConstPtr color) { SplashColorPtr p; unsigned char *q; unsigned char alpha, alpha1, c, color0, color1, color2; unsigned char color3; unsigned char colorsp[SPOT_NCOMPS + 4], cp; int x, y, mask; if (unlikely(bitmap->alpha == nullptr)) { error(errInternal, -1, "bitmap->alpha is NULL in Splash::compositeBackground"); return; } 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++; if (alpha == 0) { p[0] = color0; p[1] = color1; p[2] = color2; } else if (alpha != 255) { 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; case splashModeXBGR8: 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++; if (alpha == 0) { p[0] = color0; p[1] = color1; p[2] = color2; } else if (alpha != 255) { 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] = 255; p += 4; } } break; 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++; if (alpha == 0) { p[0] = color0; p[1] = color1; p[2] = color2; p[3] = color3; } else if (alpha != 255) { 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; case splashModeDeviceN8: for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) colorsp[cp] = color[cp]; 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++; if (alpha == 0) { for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) p[cp] = colorsp[cp]; } else if (alpha != 255) { alpha1 = 255 - alpha; for (cp = 0; cp < SPOT_NCOMPS + 4; cp++) p[cp] = div255(alpha1 * colorsp[cp] + alpha * p[cp]); } p += (SPOT_NCOMPS + 4); } } break; } memset(bitmap->alpha, 255, bitmap->width * bitmap->height); } bool Splash::gouraudTriangleShadedFill(SplashGouraudColor *shading) { double xdbl[3] = { 0., 0., 0. }; double ydbl[3] = { 0., 0., 0. }; int x[3] = { 0, 0, 0 }; int y[3] = { 0, 0, 0 }; double xt = 0., xa = 0., yt = 0.; const int bitmapWidth = bitmap->getWidth(); SplashClip *clip = getClip(); SplashBitmap *blitTarget = bitmap; SplashColorPtr bitmapData = bitmap->getDataPtr(); const int bitmapOffLimit = bitmap->getHeight() * bitmap->getRowSize(); SplashColorPtr bitmapAlpha = bitmap->getAlphaPtr(); SplashCoord *userToCanvasMatrix = getMatrix(); const SplashColorMode bitmapMode = bitmap->getMode(); bool hasAlpha = (bitmapAlpha != nullptr); const int rowSize = bitmap->getRowSize(); const int colorComps = splashColorModeNComps[bitmapMode]; SplashPipe pipe; SplashColor cSrcVal; pipeInit(&pipe, 0, 0, nullptr, cSrcVal, (unsigned char)splashRound(state->fillAlpha * 255), false, false); if (vectorAntialias) { if (aaBuf == nullptr) return false; // fall back to old behaviour drawAAPixelInit(); } // idea: // 1. If pipe->noTransparency && !state->blendFunc // -> blit directly into the drawing surface! // -> disable alpha manually. // 2. Otherwise: // - blit also directly, but into an intermediate surface. // Afterwards, blit the intermediate surface using the drawing pipeline. // This is necessary because triangle elements can be on top of each // other, so the complete shading needs to be drawn before opacity is // applied. // - 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); const bool bDirectBlit = vectorAntialias ? false : pipe.noTransparency && !state->blendFunc && !shading->isParameterized(); if (!bDirectBlit) { blitTarget = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), bitmap->getRowPad(), bitmap->getMode(), true, bitmap->getRowSize() >= 0); bitmapData = blitTarget->getDataPtr(); bitmapAlpha = blitTarget->getAlphaPtr(); // initialisation seems to be necessary: const int S = bitmap->getWidth() * bitmap->getHeight(); for (int i = 0; i < S; ++i) bitmapAlpha[i] = 0; hasAlpha = true; } if (shading->isParameterized()) { double color[3]; 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->getParametrizedTriangle(i, xdbl + 0, ydbl + 0, color + 0, xdbl + 1, ydbl + 1, color + 1, xdbl + 2, ydbl + 2, color + 2); 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]); Guswap(color[0], color[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]; const double tmpC = color[2]; x[2] = x[1]; y[2] = y[1]; color[2] = color[1]; if (y[0] > tmpY) { x[1] = x[0]; y[1] = y[0]; color[1] = color[0]; x[0] = tmpX; y[0] = tmpY; color[0] = tmpC; } else { x[1] = tmpX; y[1] = tmpY; color[1] = tmpC; } } // 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. } // Same game: we can linearly interpolate the color based on the // current y coordinate (that's correct for triangle // interpolation due to linearity. We could also have done it in // barycentric coordinates, but that's slightly more involved) scanColorMapL[0] = (color[scanEdgeL[1]] - color[scanEdgeL[0]]) / (y[scanEdgeL[1]] - y[scanEdgeL[0]]); scanColorMapL[1] = color[scanEdgeL[0]] - y[scanEdgeL[0]] * scanColorMapL[0]; scanColorMapR[0] = (color[scanEdgeR[1]] - color[scanEdgeR[0]]) / (y[scanEdgeR[1]] - y[scanEdgeR[0]]); scanColorMapR[1] = color[scanEdgeR[0]] - y[scanEdgeR[0]] * scanColorMapR[0]; 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]; scanColorMapL[0] = (color[scanEdgeL[1]] - color[scanEdgeL[0]]) / (y[scanEdgeL[1]] - y[scanEdgeL[0]]); scanColorMapL[1] = color[scanEdgeL[0]] - y[scanEdgeL[0]] * scanColorMapL[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]; scanColorMapR[0] = (color[scanEdgeR[1]] - color[scanEdgeR[0]]) / (y[scanEdgeR[1]] - y[scanEdgeR[0]]); scanColorMapR[1] = color[scanEdgeR[0]] - y[scanEdgeR[0]] * scanColorMapR[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 double ca = yt * scanColorMapL[0] + scanColorMapL[1]; const double ct = yt * scanColorMapR[0] + scanColorMapR[1]; 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: 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); double colorinterp = scanColorMap0 * scanLimitL + scanColorMap1; int bitmapOff = scanLineOff + scanLimitL * colorComps; if (likely(bitmapOff >= 0)) { 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 - (scanColorMap0 * X + scanColorMap1)) < 1e-10); assert(bitmapOff == Y * rowSize + colorComps * X && scanLineOff == Y * rowSize); shading->getParameterizedColor(colorinterp, bitmapMode, &bitmapData[bitmapOff]); // 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; } } } } } else { 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; } } } } } if (!bDirectBlit) { // ok. Finalize the stuff by blitting the shading into the final // geometry, this time respecting the rendering pipe. 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! const int bitmapOff = Y * rowSize + colorComps * X; for (int m = 0; m < colorComps; ++m) cur[m] = bitmapData[bitmapOff + m]; if (vectorAntialias) { drawAAPixel(&pipe, X, Y); } else { drawPixel(&pipe, X, Y, true); // no clipping - has already been done. } } } delete blitTarget; blitTarget = nullptr; } return true; } SplashError Splash::blitTransparent(SplashBitmap *src, int xSrc, int ySrc, int xDest, int yDest, int w, int h) { SplashColorPtr p, sp; unsigned char *q; int x, y, mask, srcMask, width = w, height = h; if (src->mode != bitmap->mode) { return splashErrModeMismatch; } if (unlikely(!bitmap->data)) { return splashErrZeroImage; } if (src->getWidth() - xSrc < width) width = src->getWidth() - xSrc; if (src->getHeight() - ySrc < height) height = src->getHeight() - ySrc; if (bitmap->getWidth() - xDest < width) width = bitmap->getWidth() - xDest; if (bitmap->getHeight() - yDest < height) height = bitmap->getHeight() - yDest; if (width < 0) width = 0; if (height < 0) height = 0; switch (bitmap->mode) { case splashModeMono1: for (y = 0; y < height; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + (xDest >> 3)]; mask = 0x80 >> (xDest & 7); sp = &src->data[(ySrc + y) * src->rowSize + (xSrc >> 3)]; srcMask = 0x80 >> (xSrc & 7); for (x = 0; x < width; ++x) { if (*sp & srcMask) { *p |= mask; } else { *p &= ~mask; } if (!(mask >>= 1)) { mask = 0x80; ++p; } if (!(srcMask >>= 1)) { srcMask = 0x80; ++sp; } } } break; case splashModeMono8: for (y = 0; y < height; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + xDest]; sp = &src->data[(ySrc + y) * bitmap->rowSize + xSrc]; for (x = 0; x < width; ++x) { *p++ = *sp++; } } break; case splashModeRGB8: case splashModeBGR8: for (y = 0; y < height; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + 3 * xDest]; sp = &src->data[(ySrc + y) * src->rowSize + 3 * xSrc]; for (x = 0; x < width; ++x) { *p++ = *sp++; *p++ = *sp++; *p++ = *sp++; } } break; case splashModeXBGR8: for (y = 0; y < height; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + 4 * xDest]; sp = &src->data[(ySrc + y) * src->rowSize + 4 * xSrc]; for (x = 0; x < width; ++x) { *p++ = *sp++; *p++ = *sp++; *p++ = *sp++; *p++ = 255; sp++; } } break; case splashModeCMYK8: for (y = 0; y < height; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + 4 * xDest]; sp = &src->data[(ySrc + y) * src->rowSize + 4 * xSrc]; for (x = 0; x < width; ++x) { *p++ = *sp++; *p++ = *sp++; *p++ = *sp++; *p++ = *sp++; } } break; case splashModeDeviceN8: for (y = 0; y < height; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + (SPOT_NCOMPS + 4) * xDest]; sp = &src->data[(ySrc + y) * src->rowSize + (SPOT_NCOMPS + 4) * xSrc]; for (x = 0; x < width; ++x) { for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++) *p++ = *sp++; } } break; } if (bitmap->alpha) { for (y = 0; y < height; ++y) { q = &bitmap->alpha[(yDest + y) * bitmap->width + xDest]; memset(q, 0x00, width); } } return splashOk; } SplashPath *Splash::makeStrokePath(SplashPath *path, SplashCoord w, bool flatten) { SplashPath *pathIn, *dashPath, *pathOut; SplashCoord d, dx, dy, wdx, wdy, dxNext, dyNext, wdxNext, wdyNext; SplashCoord crossprod, dotprod, miter, m; bool first, last, closed, hasangle; int subpathStart0, subpathStart1, seg, i0, i1, j0, j1, k0, k1; int left0, left1, left2, right0, right1, right2, join0, join1, join2; int leftFirst, rightFirst, firstPt; pathOut = new SplashPath(); if (path->length == 0) { return pathOut; } if (flatten) { pathIn = flattenPath(path, state->matrix, state->flatness); if (state->lineDashLength > 0) { dashPath = makeDashedPath(pathIn); delete pathIn; pathIn = dashPath; if (pathIn->length == 0) { delete pathIn; return pathOut; } } } else { pathIn = path; } subpathStart0 = subpathStart1 = 0; // make gcc happy seg = 0; // make gcc happy closed = false; // make gcc happy left0 = left1 = right0 = right1 = join0 = join1 = 0; // make gcc happy leftFirst = rightFirst = firstPt = 0; // make gcc happy i0 = 0; for (i1 = i0; !(pathIn->flags[i1] & splashPathLast) && i1 + 1 < pathIn->length && pathIn->pts[i1 + 1].x == pathIn->pts[i1].x && pathIn->pts[i1 + 1].y == pathIn->pts[i1].y; ++i1) ; while (i1 < pathIn->length) { if ((first = pathIn->flags[i0] & splashPathFirst)) { subpathStart0 = i0; subpathStart1 = i1; seg = 0; closed = pathIn->flags[i0] & splashPathClosed; } j0 = i1 + 1; if (j0 < pathIn->length) { for (j1 = j0; !(pathIn->flags[j1] & splashPathLast) && j1 + 1 < pathIn->length && pathIn->pts[j1 + 1].x == pathIn->pts[j1].x && pathIn->pts[j1 + 1].y == pathIn->pts[j1].y; ++j1) ; } else { j1 = j0; } if (pathIn->flags[i1] & splashPathLast) { if (first && state->lineCap == splashLineCapRound) { // special case: zero-length subpath with round line caps --> // draw a circle pathOut->moveTo(pathIn->pts[i0].x + (SplashCoord)0.5 * w, pathIn->pts[i0].y); pathOut->curveTo(pathIn->pts[i0].x + (SplashCoord)0.5 * w, pathIn->pts[i0].y + bezierCircle2 * w, pathIn->pts[i0].x + bezierCircle2 * w, pathIn->pts[i0].y + (SplashCoord)0.5 * w, pathIn->pts[i0].x, pathIn->pts[i0].y + (SplashCoord)0.5 * w); pathOut->curveTo(pathIn->pts[i0].x - bezierCircle2 * w, pathIn->pts[i0].y + (SplashCoord)0.5 * w, pathIn->pts[i0].x - (SplashCoord)0.5 * w, pathIn->pts[i0].y + bezierCircle2 * w, pathIn->pts[i0].x - (SplashCoord)0.5 * w, pathIn->pts[i0].y); pathOut->curveTo(pathIn->pts[i0].x - (SplashCoord)0.5 * w, pathIn->pts[i0].y - bezierCircle2 * w, pathIn->pts[i0].x - bezierCircle2 * w, pathIn->pts[i0].y - (SplashCoord)0.5 * w, pathIn->pts[i0].x, pathIn->pts[i0].y - (SplashCoord)0.5 * w); pathOut->curveTo(pathIn->pts[i0].x + bezierCircle2 * w, pathIn->pts[i0].y - (SplashCoord)0.5 * w, pathIn->pts[i0].x + (SplashCoord)0.5 * w, pathIn->pts[i0].y - bezierCircle2 * w, pathIn->pts[i0].x + (SplashCoord)0.5 * w, pathIn->pts[i0].y); pathOut->close(); } i0 = j0; i1 = j1; continue; } last = pathIn->flags[j1] & splashPathLast; if (last) { k0 = subpathStart1 + 1; } else { k0 = j1 + 1; } for (k1 = k0; !(pathIn->flags[k1] & splashPathLast) && k1 + 1 < pathIn->length && pathIn->pts[k1 + 1].x == pathIn->pts[k1].x && pathIn->pts[k1 + 1].y == pathIn->pts[k1].y; ++k1) ; // compute the deltas for segment (i1, j0) d = (SplashCoord)1 / splashDist(pathIn->pts[i1].x, pathIn->pts[i1].y, pathIn->pts[j0].x, pathIn->pts[j0].y); dx = d * (pathIn->pts[j0].x - pathIn->pts[i1].x); dy = d * (pathIn->pts[j0].y - pathIn->pts[i1].y); wdx = (SplashCoord)0.5 * w * dx; wdy = (SplashCoord)0.5 * w * dy; // draw the start cap if (pathOut->moveTo(pathIn->pts[i0].x - wdy, pathIn->pts[i0].y + wdx) != splashOk) { break; } if (i0 == subpathStart0) { firstPt = pathOut->length - 1; } if (first && !closed) { switch (state->lineCap) { case splashLineCapButt: pathOut->lineTo(pathIn->pts[i0].x + wdy, pathIn->pts[i0].y - wdx); break; case splashLineCapRound: pathOut->curveTo(pathIn->pts[i0].x - wdy - bezierCircle * wdx, pathIn->pts[i0].y + wdx - bezierCircle * wdy, pathIn->pts[i0].x - wdx - bezierCircle * wdy, pathIn->pts[i0].y - wdy + bezierCircle * wdx, pathIn->pts[i0].x - wdx, pathIn->pts[i0].y - wdy); pathOut->curveTo(pathIn->pts[i0].x - wdx + bezierCircle * wdy, pathIn->pts[i0].y - wdy - bezierCircle * wdx, pathIn->pts[i0].x + wdy - bezierCircle * wdx, pathIn->pts[i0].y - wdx - bezierCircle * wdy, pathIn->pts[i0].x + wdy, pathIn->pts[i0].y - wdx); break; case splashLineCapProjecting: pathOut->lineTo(pathIn->pts[i0].x - wdx - wdy, pathIn->pts[i0].y + wdx - wdy); pathOut->lineTo(pathIn->pts[i0].x - wdx + wdy, pathIn->pts[i0].y - wdx - wdy); pathOut->lineTo(pathIn->pts[i0].x + wdy, pathIn->pts[i0].y - wdx); break; } } else { pathOut->lineTo(pathIn->pts[i0].x + wdy, pathIn->pts[i0].y - wdx); } // draw the left side of the segment rectangle left2 = pathOut->length - 1; pathOut->lineTo(pathIn->pts[j0].x + wdy, pathIn->pts[j0].y - wdx); // draw the end cap if (last && !closed) { switch (state->lineCap) { case splashLineCapButt: pathOut->lineTo(pathIn->pts[j0].x - wdy, pathIn->pts[j0].y + wdx); break; case splashLineCapRound: pathOut->curveTo(pathIn->pts[j0].x + wdy + bezierCircle * wdx, pathIn->pts[j0].y - wdx + bezierCircle * wdy, pathIn->pts[j0].x + wdx + bezierCircle * wdy, pathIn->pts[j0].y + wdy - bezierCircle * wdx, pathIn->pts[j0].x + wdx, pathIn->pts[j0].y + wdy); pathOut->curveTo(pathIn->pts[j0].x + wdx - bezierCircle * wdy, pathIn->pts[j0].y + wdy + bezierCircle * wdx, pathIn->pts[j0].x - wdy + bezierCircle * wdx, pathIn->pts[j0].y + wdx + bezierCircle * wdy, pathIn->pts[j0].x - wdy, pathIn->pts[j0].y + wdx); break; case splashLineCapProjecting: pathOut->lineTo(pathIn->pts[j0].x + wdy + wdx, pathIn->pts[j0].y - wdx + wdy); pathOut->lineTo(pathIn->pts[j0].x - wdy + wdx, pathIn->pts[j0].y + wdx + wdy); pathOut->lineTo(pathIn->pts[j0].x - wdy, pathIn->pts[j0].y + wdx); break; } } else { pathOut->lineTo(pathIn->pts[j0].x - wdy, pathIn->pts[j0].y + wdx); } // draw the right side of the segment rectangle // (NB: if stroke adjustment is enabled, the closepath operation MUST // add a segment because this segment is used for a hint) right2 = pathOut->length - 1; pathOut->close(state->strokeAdjust); // draw the join join2 = pathOut->length; if (!last || closed) { // compute the deltas for segment (j1, k0) d = (SplashCoord)1 / splashDist(pathIn->pts[j1].x, pathIn->pts[j1].y, pathIn->pts[k0].x, pathIn->pts[k0].y); dxNext = d * (pathIn->pts[k0].x - pathIn->pts[j1].x); dyNext = d * (pathIn->pts[k0].y - pathIn->pts[j1].y); wdxNext = (SplashCoord)0.5 * w * dxNext; wdyNext = (SplashCoord)0.5 * w * dyNext; // compute the join parameters crossprod = dx * dyNext - dy * dxNext; dotprod = -(dx * dxNext + dy * dyNext); hasangle = crossprod != 0 || dx * dxNext < 0 || dy * dyNext < 0; if (dotprod > 0.9999) { // 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) // (note: the comparison value (0.9999) has to be less than // 1-epsilon, where epsilon is the smallest value // representable in the fixed point format) 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 (hasangle && state->lineJoin == splashLineJoinRound) { pathOut->moveTo(pathIn->pts[j0].x + (SplashCoord)0.5 * w, pathIn->pts[j0].y); pathOut->curveTo(pathIn->pts[j0].x + (SplashCoord)0.5 * w, pathIn->pts[j0].y + bezierCircle2 * w, pathIn->pts[j0].x + bezierCircle2 * w, pathIn->pts[j0].y + (SplashCoord)0.5 * w, pathIn->pts[j0].x, pathIn->pts[j0].y + (SplashCoord)0.5 * w); pathOut->curveTo(pathIn->pts[j0].x - bezierCircle2 * w, pathIn->pts[j0].y + (SplashCoord)0.5 * w, pathIn->pts[j0].x - (SplashCoord)0.5 * w, pathIn->pts[j0].y + bezierCircle2 * w, pathIn->pts[j0].x - (SplashCoord)0.5 * w, pathIn->pts[j0].y); pathOut->curveTo(pathIn->pts[j0].x - (SplashCoord)0.5 * w, pathIn->pts[j0].y - bezierCircle2 * w, pathIn->pts[j0].x - bezierCircle2 * w, pathIn->pts[j0].y - (SplashCoord)0.5 * w, pathIn->pts[j0].x, pathIn->pts[j0].y - (SplashCoord)0.5 * w); pathOut->curveTo(pathIn->pts[j0].x + bezierCircle2 * w, pathIn->pts[j0].y - (SplashCoord)0.5 * w, pathIn->pts[j0].x + (SplashCoord)0.5 * w, pathIn->pts[j0].y - bezierCircle2 * w, pathIn->pts[j0].x + (SplashCoord)0.5 * w, pathIn->pts[j0].y); } else if (hasangle) { pathOut->moveTo(pathIn->pts[j0].x, pathIn->pts[j0].y); // angle < 180 if (crossprod < 0) { pathOut->lineTo(pathIn->pts[j0].x - wdyNext, pathIn->pts[j0].y + wdxNext); // miter join inside limit if (state->lineJoin == splashLineJoinMiter && splashSqrt(miter) <= state->miterLimit) { pathOut->lineTo(pathIn->pts[j0].x - wdy + wdx * m, pathIn->pts[j0].y + wdx + wdy * m); pathOut->lineTo(pathIn->pts[j0].x - wdy, pathIn->pts[j0].y + wdx); // bevel join or miter join outside limit } else { pathOut->lineTo(pathIn->pts[j0].x - wdy, pathIn->pts[j0].y + wdx); } // angle >= 180 } else { pathOut->lineTo(pathIn->pts[j0].x + wdy, pathIn->pts[j0].y - wdx); // miter join inside limit if (state->lineJoin == splashLineJoinMiter && splashSqrt(miter) <= state->miterLimit) { pathOut->lineTo(pathIn->pts[j0].x + wdy + wdx * m, pathIn->pts[j0].y - wdx + wdy * m); pathOut->lineTo(pathIn->pts[j0].x + wdyNext, pathIn->pts[j0].y - wdxNext); // bevel join or miter join outside limit } else { pathOut->lineTo(pathIn->pts[j0].x + wdyNext, pathIn->pts[j0].y - wdxNext); } } } pathOut->close(); } // add stroke adjustment hints if (state->strokeAdjust) { if (seg == 0 && !closed) { if (state->lineCap == splashLineCapButt) { pathOut->addStrokeAdjustHint(firstPt, left2 + 1, firstPt, firstPt + 1); if (last) { pathOut->addStrokeAdjustHint(firstPt, left2 + 1, left2 + 1, left2 + 2); } } else if (state->lineCap == splashLineCapProjecting) { if (last) { pathOut->addStrokeAdjustHint(firstPt + 1, left2 + 2, firstPt + 1, firstPt + 2); pathOut->addStrokeAdjustHint(firstPt + 1, left2 + 2, left2 + 2, left2 + 3); } else { pathOut->addStrokeAdjustHint(firstPt + 1, left2 + 1, firstPt + 1, firstPt + 2); } } } if (seg >= 1) { if (seg >= 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 (seg == 0) { leftFirst = left2; rightFirst = right2; } if (last) { if (seg >= 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 (!closed && seg > 0) { if (state->lineCap == splashLineCapButt) { pathOut->addStrokeAdjustHint(left1 - 1, left1 + 1, left1 + 1, left1 + 2); } else if (state->lineCap == splashLineCapProjecting) { pathOut->addStrokeAdjustHint(left1 - 1, left1 + 2, left1 + 2, left1 + 3); } } } } i0 = j0; i1 = j1; ++seg; } 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\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 & splashXPathHoriz) ? "H" : " ", (path->segs[i].flags & splashXPathVert) ? "V" : " ", (path->segs[i].flags & splashXPathFlip) ? "P" : " "); } } SplashError Splash::shadedFill(SplashPath *path, bool hasBBox, SplashPattern *pattern, bool clipToStrokePath) { SplashPipe pipe; int xMinI, yMinI, xMaxI, yMaxI, x0, x1, y; SplashClipResult clipRes; if (vectorAntialias && aaBuf == nullptr) { // should not happen, but to be secure return splashErrGeneric; } if (path->length == 0) { return splashErrEmptyPath; } SplashXPath xPath(path, state->matrix, state->flatness, true); if (vectorAntialias) { xPath.aaScale(); } xPath.sort(); yMinI = state->clip->getYMinI(); yMaxI = state->clip->getYMaxI(); if (vectorAntialias && !inShading) { yMinI = yMinI * splashAASize; yMaxI = (yMaxI + 1) * splashAASize - 1; } SplashXPathScanner scanner(&xPath, false, yMinI, yMaxI); // 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(); } unsigned char alpha = splashRound((clipToStrokePath) ? state->strokeAlpha * 255 : state->fillAlpha * 255); pipeInit(&pipe, 0, yMinI, pattern, nullptr, alpha, vectorAntialias && !hasBBox, false); // 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); } #if splashAASize == 4 if (!hasBBox && y > yMinI && y < yMaxI) { // correct shape on left side if clip is // vertical through the middle of shading: unsigned char *p0, *p1, *p2, *p3; unsigned char c1, c2, c3, c4; p0 = aaBuf->getDataPtr() + (x0 >> 1); p1 = p0 + aaBuf->getRowSize(); p2 = p1 + aaBuf->getRowSize(); p3 = p2 + aaBuf->getRowSize(); if (x0 & 1) { c1 = (*p0 & 0x0f); c2 = (*p1 & 0x0f); c3 = (*p2 & 0x0f); c4 = (*p3 & 0x0f); } else { c1 = (*p0 >> 4); c2 = (*p1 >> 4); c3 = (*p2 >> 4); c4 = (*p3 >> 4); } if ((c1 & 0x03) == 0x03 && (c2 & 0x03) == 0x03 && (c3 & 0x03) == 0x03 && (c4 & 0x03) == 0x03 && c1 == c2 && c2 == c3 && c3 == c4 && pattern->testPosition(x0 - 1, y)) { unsigned char shapeCorrection = (x0 & 1) ? 0x0f : 0xf0; *p0 |= shapeCorrection; *p1 |= shapeCorrection; *p2 |= shapeCorrection; *p3 |= shapeCorrection; } // correct shape on right side if clip is // through the middle of shading: p0 = aaBuf->getDataPtr() + (x1 >> 1); p1 = p0 + aaBuf->getRowSize(); p2 = p1 + aaBuf->getRowSize(); p3 = p2 + aaBuf->getRowSize(); if (x1 & 1) { c1 = (*p0 & 0x0f); c2 = (*p1 & 0x0f); c3 = (*p2 & 0x0f); c4 = (*p3 & 0x0f); } else { c1 = (*p0 >> 4); c2 = (*p1 >> 4); c3 = (*p2 >> 4); c4 = (*p3 >> 4); } if ((c1 & 0xc) == 0x0c && (c2 & 0x0c) == 0x0c && (c3 & 0x0c) == 0x0c && (c4 & 0x0c) == 0x0c && c1 == c2 && c2 == c3 && c3 == c4 && pattern->testPosition(x1 + 1, y)) { unsigned char shapeCorrection = (x1 & 1) ? 0x0f : 0xf0; *p0 |= shapeCorrection; *p1 |= shapeCorrection; *p2 |= shapeCorrection; *p3 |= shapeCorrection; } } #endif drawAALine(&pipe, x0, x1, y); } } else { SplashClipResult clipRes2; for (y = yMinI; y <= yMaxI; ++y) { SplashXPathScanIterator iterator(scanner, y); while (iterator.getNextSpan(&x0, &x1)) { if (clipRes == splashClipAllInside) { drawSpan(&pipe, x0, x1, y, true); } 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; return splashOk; }