Spaces:
Running
Running
//======================================================================== | |
// | |
// 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-2023 Albert Astals Cid <[email protected]> | |
// Copyright (C) 2005 Marco Pesenti Gritti <[email protected]> | |
// Copyright (C) 2010-2016 Thomas Freitag <[email protected]> | |
// Copyright (C) 2010 Christian Feuersänger <[email protected]> | |
// Copyright (C) 2011-2013, 2015 William Bader <[email protected]> | |
// Copyright (C) 2012 Markus Trippelsdorf <[email protected]> | |
// Copyright (C) 2012, 2017 Adrian Johnson <[email protected]> | |
// Copyright (C) 2012 Matthias Kramm <[email protected]> | |
// Copyright (C) 2018, 2019 Stefan Brüns <[email protected]> | |
// Copyright (C) 2018 Adam Reichold <[email protected]> | |
// Copyright (C) 2019, 2020 Oliver Sander <[email protected]> | |
// Copyright (C) 2019 Marek Kasik <[email protected]> | |
// Copyright (C) 2020 Tobias Deiminger <[email protected]> | |
// Copyright (C) 2021, 2024 Even Rouault <[email protected]> | |
// | |
// 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 | |
// | |
//======================================================================== | |
// the MSVC math.h doesn't define this | |
//------------------------------------------------------------------------ | |
// distance of Bezier control point from center for circle approximation | |
// = (4 * (sqrt(2) - 1) / 3) * r | |
// 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<typename T> | |
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. | |
static inline int imgCoordMungeLower(SplashCoord x) { | |
return splashCeil(x + 0.5) - 1; | |
} | |
static inline int imgCoordMungeUpper(SplashCoord x) { | |
return splashCeil(x + 0.5) - 1; | |
} | |
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); | |
} | |
// 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 | |
//------------------------------------------------------------------------ | |
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<int>(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<int>(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<int>(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<int>(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<int>(pipe->destColorPtr[0] + cResult0, 255) : cResult0; | |
} | |
if (state->overprintMask & 2) { | |
pipe->destColorPtr[1] = (state->overprintAdditive) ? std::min<int>(pipe->destColorPtr[1] + cResult1, 255) : cResult1; | |
} | |
if (state->overprintMask & 4) { | |
pipe->destColorPtr[2] = (state->overprintAdditive) ? std::min<int>(pipe->destColorPtr[2] + cResult2, 255) : cResult2; | |
} | |
if (state->overprintMask & 8) { | |
pipe->destColorPtr[3] = (state->overprintAdditive) ? std::min<int>(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<int>(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<int>(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<int>(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<int>(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<int>(pipe->destColorPtr[0] + cResult0, 255) : cResult0; | |
} | |
if (state->overprintMask & 2) { | |
pipe->destColorPtr[1] = (state->overprintAdditive && pipe->shape != 0) ? std::min<int>(pipe->destColorPtr[1] + cResult1, 255) : cResult1; | |
} | |
if (state->overprintMask & 4) { | |
pipe->destColorPtr[2] = (state->overprintAdditive && pipe->shape != 0) ? std::min<int>(pipe->destColorPtr[2] + cResult2, 255) : cResult2; | |
} | |
if (state->overprintMask & 8) { | |
pipe->destColorPtr[3] = (state->overprintAdditive && pipe->shape != 0) ? std::min<int>(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) | |
{ | |
static const int bitCount4[16] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 }; | |
int w; | |
int xx, yy; | |
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 | |
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]; | |
} | |
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; | |
} | |
} | |
// draw the pixel | |
if (t != 0) { | |
pipeSetXY(pipe, x, y); | |
pipe->shape = div255(static_cast<int>(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) | |
{ | |
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; | |
SplashColorPtr p; | |
int xx, yy, t; | |
int x; | |
p0 = aaBuf->getDataPtr() + (x0 >> 1); | |
p1 = p0 + aaBuf->getRowSize(); | |
p2 = p1 + aaBuf->getRowSize(); | |
p3 = p2 + aaBuf->getRowSize(); | |
pipeSetXY(pipe, x0, y); | |
for (x = x0; x <= x1; ++x) { | |
// compute the shape value | |
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]; | |
} | |
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; | |
} | |
} | |
if (t != 0) { | |
pipe->shape = (adjustLine) ? div255(static_cast<int>((int)lineOpacity * (double)aaGamma[t])) : (int)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::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(std::vector<SplashCoord> &&lineDash, SplashCoord lineDashPhase) | |
{ | |
state->setLineDash(std::move(lineDash), 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:%zu] [width:%.2f]:\n", state->lineDash.size(), (double)state->lineWidth); | |
dumpPath(path); | |
} | |
opClipRes = splashClipAllOutside; | |
if (path->length == 0) { | |
return splashErrEmptyPath; | |
} | |
path2 = flattenPath(path, state->matrix, state->flatness); | |
if (!state->lineDash.empty()) { | |
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 i, j, k; | |
lineDashTotal = 0; | |
for (SplashCoord dash : state->lineDash) { | |
lineDashTotal += dash; | |
} | |
// 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; | |
size_t lineDashStartIdx = 0; | |
if (lineDashStartPhase > 0) { | |
while (lineDashStartIdx < state->lineDash.size() && lineDashStartPhase >= state->lineDash[lineDashStartIdx]) { | |
lineDashStartOn = !lineDashStartOn; | |
lineDashStartPhase -= state->lineDash[lineDashStartIdx]; | |
++lineDashStartIdx; | |
} | |
if (unlikely(lineDashStartIdx == state->lineDash.size())) { | |
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; | |
size_t 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->lineDash.size()) { | |
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(static_cast<int>((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_checkoverflow(srcWidth); | |
if (unlikely(!lineBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for lineBuf in Splash::scaleMaskYdownXdown"); | |
return; | |
} | |
pixBuf = (unsigned int *)gmallocn_checkoverflow(srcWidth, sizeof(int)); | |
if (unlikely(!pixBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for pixBuf 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_checkoverflow(srcWidth); | |
if (unlikely(!lineBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for lineBuf in Splash::scaleMaskYdownXup"); | |
return; | |
} | |
pixBuf = (unsigned int *)gmallocn_checkoverflow(srcWidth, sizeof(int)); | |
if (unlikely(!pixBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for pixBuf in Splash::scaleMaskYdownXup"); | |
gfree(lineBuf); | |
return; | |
} | |
// 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_checkoverflow(srcWidth); | |
if (unlikely(!lineBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for lineBuf in Splash::scaleMaskYupXdown"); | |
return; | |
} | |
// 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_checkoverflow(srcWidth); | |
if (unlikely(!lineBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for lineBuf in Splash::scaleMaskYupXup"); | |
return; | |
} | |
// 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 (unlikely(clipRes == splashClipAllInside && xb > bitmap->getWidth())) { | |
xb = bitmap->getWidth(); | |
} | |
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) { | |
bool success = true; | |
if (scaledHeight < srcHeight) { | |
if (scaledWidth < srcWidth) { | |
success = scaleImageYdownXdown(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); | |
} else { | |
success = scaleImageYdownXup(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); | |
} | |
} else { | |
if (scaledWidth < srcWidth) { | |
success = scaleImageYupXdown(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); | |
} else { | |
if (!tilingPattern && isImageInterpolationRequired(srcWidth, srcHeight, scaledWidth, scaledHeight, interpolate)) { | |
success = scaleImageYupXupBilinear(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); | |
} else { | |
success = scaleImageYupXup(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); | |
} | |
} | |
} | |
if (unlikely(!success)) { | |
delete dest; | |
dest = nullptr; | |
} | |
} else { | |
delete dest; | |
dest = nullptr; | |
} | |
return dest; | |
} | |
bool 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 false; | |
} | |
pixBuf = (unsigned int *)gmallocn_checkoverflow(srcWidth, nComps * sizeof(int)); | |
if (unlikely(!pixBuf)) { | |
gfree(lineBuf); | |
return false; | |
} | |
if (srcAlpha) { | |
alphaLineBuf = (unsigned char *)gmalloc_checkoverflow(srcWidth); | |
if (unlikely(!alphaLineBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for alphaLineBuf in Splash::scaleImageYdownXdown"); | |
gfree(lineBuf); | |
gfree(pixBuf); | |
return false; | |
} | |
alphaPixBuf = (unsigned int *)gmallocn_checkoverflow(srcWidth, sizeof(int)); | |
if (unlikely(!alphaPixBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for alphaPixBuf in Splash::scaleImageYdownXdown"); | |
gfree(lineBuf); | |
gfree(pixBuf); | |
gfree(alphaLineBuf); | |
return false; | |
} | |
} 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); | |
return true; | |
} | |
bool 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 false; | |
} | |
lineBuf = (unsigned char *)gmallocn_checkoverflow(srcWidth, nComps); | |
if (unlikely(!lineBuf)) { | |
error(errInternal, -1, "Splash::scaleImageYdownXup. Couldn't allocate lineBuf memory"); | |
gfree(pixBuf); | |
return false; | |
} | |
if (srcAlpha) { | |
alphaLineBuf = (unsigned char *)gmalloc_checkoverflow(srcWidth); | |
if (unlikely(!alphaLineBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for alphaLineBuf in Splash::scaleImageYdownXup"); | |
gfree(lineBuf); | |
gfree(pixBuf); | |
return false; | |
} | |
alphaPixBuf = (unsigned int *)gmallocn_checkoverflow(srcWidth, sizeof(int)); | |
if (unlikely(!alphaPixBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for alphaPixBuf in Splash::scaleImageYdownXup"); | |
gfree(lineBuf); | |
gfree(pixBuf); | |
gfree(alphaLineBuf); | |
return false; | |
} | |
} 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); | |
return true; | |
} | |
bool 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 false; | |
} | |
if (srcAlpha) { | |
alphaLineBuf = (unsigned char *)gmalloc_checkoverflow(srcWidth); | |
if (unlikely(!alphaLineBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for alphaLineBuf in Splash::scaleImageYupXdown"); | |
gfree(lineBuf); | |
return false; | |
} | |
} 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); | |
return true; | |
} | |
bool 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 (unlikely(!lineBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for lineBuf in Splash::scaleImageYupXup"); | |
return false; | |
} | |
if (srcAlpha) { | |
alphaLineBuf = (unsigned char *)gmalloc_checkoverflow(srcWidth); | |
if (unlikely(!alphaLineBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for alphaLineBuf in Splash::scaleImageYupXup"); | |
gfree(lineBuf); | |
return false; | |
} | |
} 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); | |
return true; | |
} | |
// 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] = static_cast<unsigned char>(srcBuf[nComps * p + c] * (1.0 - xFrac) + srcBuf[nComps * (p + 1) + c] * xFrac); | |
} | |
xSrc += xStep; | |
} | |
} | |
// Scale up image using bilinear interpolation | |
bool 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 false; | |
} | |
// allocate buffers | |
srcBuf = (unsigned char *)gmallocn_checkoverflow(srcWidth + 1, nComps); // + 1 pixel of padding | |
if (unlikely(!srcBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for srcBuf in Splash::scaleImageYupXupBilinear"); | |
return false; | |
} | |
lineBuf1 = (unsigned char *)gmallocn_checkoverflow(scaledWidth, nComps); | |
if (unlikely(!lineBuf1)) { | |
error(errInternal, -1, "Couldn't allocate memory for lineBuf1 in Splash::scaleImageYupXupBilinear"); | |
gfree(srcBuf); | |
return false; | |
} | |
lineBuf2 = (unsigned char *)gmallocn_checkoverflow(scaledWidth, nComps); | |
if (unlikely(!lineBuf2)) { | |
error(errInternal, -1, "Couldn't allocate memory for lineBuf2 in Splash::scaleImageYupXupBilinear"); | |
gfree(srcBuf); | |
gfree(lineBuf1); | |
return false; | |
} | |
if (srcAlpha) { | |
alphaSrcBuf = (unsigned char *)gmalloc_checkoverflow(srcWidth + 1); // + 1 pixel of padding | |
if (unlikely(!alphaSrcBuf)) { | |
error(errInternal, -1, "Couldn't allocate memory for alphaSrcBuf in Splash::scaleImageYupXupBilinear"); | |
gfree(srcBuf); | |
gfree(lineBuf1); | |
gfree(lineBuf2); | |
return false; | |
} | |
alphaLineBuf1 = (unsigned char *)gmalloc_checkoverflow(scaledWidth); | |
if (unlikely(!alphaLineBuf1)) { | |
error(errInternal, -1, "Couldn't allocate memory for alphaLineBuf1 in Splash::scaleImageYupXupBilinear"); | |
gfree(srcBuf); | |
gfree(lineBuf1); | |
gfree(lineBuf2); | |
gfree(alphaSrcBuf); | |
return false; | |
} | |
alphaLineBuf2 = (unsigned char *)gmalloc_checkoverflow(scaledWidth); | |
if (unlikely(!alphaLineBuf2)) { | |
error(errInternal, -1, "Couldn't allocate memory for alphaLineBuf2 in Splash::scaleImageYupXupBilinear"); | |
gfree(srcBuf); | |
gfree(lineBuf1); | |
gfree(lineBuf2); | |
gfree(alphaSrcBuf); | |
gfree(alphaLineBuf1); | |
return false; | |
} | |
} 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] = static_cast<unsigned char>(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 = static_cast<unsigned char>(alphaLineBuf1[x] * (1.0 - yFrac) + alphaLineBuf2[x] * yFrac); | |
} | |
} | |
ySrc += yStep; | |
} | |
gfree(alphaSrcBuf); | |
gfree(alphaLineBuf1); | |
gfree(alphaLineBuf2); | |
gfree(srcBuf); | |
gfree(lineBuf1); | |
gfree(lineBuf2); | |
return true; | |
} | |
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-7); | |
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)) { | |
if (!bDirectBlit) { | |
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) * src->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->lineDash.empty()) { | |
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); | |
} | |
// hasangle == false means that the current and and the next segment | |
// are parallel. In that case no join needs to be drawn. | |
// round join | |
if (hasangle && state->lineJoin == splashLineJoinRound) { | |
// join angle < 180 | |
if (crossprod < 0) { | |
SplashCoord angle = atan2((double)dx, (double)-dy); | |
SplashCoord angleNext = atan2((double)dxNext, (double)-dyNext); | |
if (angle < angleNext) { | |
angle += 2 * M_PI; | |
} | |
SplashCoord dAngle = (angle - angleNext) / M_PI; | |
if (dAngle < 0.501) { | |
// span angle is <= 90 degrees -> draw a single arc | |
SplashCoord kappa = dAngle * bezierCircle * w; | |
SplashCoord cx1 = pathIn->pts[j0].x - wdy + kappa * dx; | |
SplashCoord cy1 = pathIn->pts[j0].y + wdx + kappa * dy; | |
SplashCoord cx2 = pathIn->pts[j0].x - wdyNext - kappa * dxNext; | |
SplashCoord cy2 = pathIn->pts[j0].y + wdxNext - kappa * dyNext; | |
pathOut->moveTo(pathIn->pts[j0].x, pathIn->pts[j0].y); | |
pathOut->lineTo(pathIn->pts[j0].x - wdyNext, pathIn->pts[j0].y + wdxNext); | |
pathOut->curveTo(cx2, cy2, cx1, cy1, pathIn->pts[j0].x - wdy, pathIn->pts[j0].y + wdx); | |
} else { | |
// span angle is > 90 degrees -> split into two arcs | |
SplashCoord dJoin = splashDist(-wdy, wdx, -wdyNext, wdxNext); | |
if (dJoin > 0) { | |
SplashCoord dxJoin = (-wdyNext + wdy) / dJoin; | |
SplashCoord dyJoin = (wdxNext - wdx) / dJoin; | |
SplashCoord xc = pathIn->pts[j0].x + (SplashCoord)0.5 * w * cos((double)((SplashCoord)0.5 * (angle + angleNext))); | |
SplashCoord yc = pathIn->pts[j0].y + (SplashCoord)0.5 * w * sin((double)((SplashCoord)0.5 * (angle + angleNext))); | |
SplashCoord kappa = dAngle * bezierCircle2 * w; | |
SplashCoord cx1 = pathIn->pts[j0].x - wdy + kappa * dx; | |
SplashCoord cy1 = pathIn->pts[j0].y + wdx + kappa * dy; | |
SplashCoord cx2 = xc - kappa * dxJoin; | |
SplashCoord cy2 = yc - kappa * dyJoin; | |
SplashCoord cx3 = xc + kappa * dxJoin; | |
SplashCoord cy3 = yc + kappa * dyJoin; | |
SplashCoord cx4 = pathIn->pts[j0].x - wdyNext - kappa * dxNext; | |
SplashCoord cy4 = pathIn->pts[j0].y + wdxNext - kappa * dyNext; | |
pathOut->moveTo(pathIn->pts[j0].x, pathIn->pts[j0].y); | |
pathOut->lineTo(pathIn->pts[j0].x - wdyNext, pathIn->pts[j0].y + wdxNext); | |
pathOut->curveTo(cx4, cy4, cx3, cy3, xc, yc); | |
pathOut->curveTo(cx2, cy2, cx1, cy1, pathIn->pts[j0].x - wdy, pathIn->pts[j0].y + wdx); | |
} | |
} | |
// join angle >= 180 | |
} else { | |
SplashCoord angle = atan2((double)-dx, (double)dy); | |
SplashCoord angleNext = atan2((double)-dxNext, (double)dyNext); | |
if (angleNext < angle) { | |
angleNext += 2 * M_PI; | |
} | |
SplashCoord dAngle = (angleNext - angle) / M_PI; | |
if (dAngle < 0.501) { | |
// span angle is <= 90 degrees -> draw a single arc | |
SplashCoord kappa = dAngle * bezierCircle * w; | |
SplashCoord cx1 = pathIn->pts[j0].x + wdy + kappa * dx; | |
SplashCoord cy1 = pathIn->pts[j0].y - wdx + kappa * dy; | |
SplashCoord cx2 = pathIn->pts[j0].x + wdyNext - kappa * dxNext; | |
SplashCoord cy2 = pathIn->pts[j0].y - wdxNext - kappa * dyNext; | |
pathOut->moveTo(pathIn->pts[j0].x, pathIn->pts[j0].y); | |
pathOut->lineTo(pathIn->pts[j0].x + wdy, pathIn->pts[j0].y - wdx); | |
pathOut->curveTo(cx1, cy1, cx2, cy2, pathIn->pts[j0].x + wdyNext, pathIn->pts[j0].y - wdxNext); | |
} else { | |
// span angle is > 90 degrees -> split into two arcs | |
SplashCoord dJoin = splashDist(wdy, -wdx, wdyNext, -wdxNext); | |
if (dJoin > 0) { | |
SplashCoord dxJoin = (wdyNext - wdy) / dJoin; | |
SplashCoord dyJoin = (-wdxNext + wdx) / dJoin; | |
SplashCoord xc = pathIn->pts[j0].x + (SplashCoord)0.5 * w * cos((double)((SplashCoord)0.5 * (angle + angleNext))); | |
SplashCoord yc = pathIn->pts[j0].y + (SplashCoord)0.5 * w * sin((double)((SplashCoord)0.5 * (angle + angleNext))); | |
SplashCoord kappa = dAngle * bezierCircle2 * w; | |
SplashCoord cx1 = pathIn->pts[j0].x + wdy + kappa * dx; | |
SplashCoord cy1 = pathIn->pts[j0].y - wdx + kappa * dy; | |
SplashCoord cx2 = xc - kappa * dxJoin; | |
SplashCoord cy2 = yc - kappa * dyJoin; | |
SplashCoord cx3 = xc + kappa * dxJoin; | |
SplashCoord cy3 = yc + kappa * dyJoin; | |
SplashCoord cx4 = pathIn->pts[j0].x + wdyNext - kappa * dxNext; | |
SplashCoord cy4 = pathIn->pts[j0].y - wdxNext - kappa * dyNext; | |
pathOut->moveTo(pathIn->pts[j0].x, pathIn->pts[j0].y); | |
pathOut->lineTo(pathIn->pts[j0].x + wdy, pathIn->pts[j0].y - wdx); | |
pathOut->curveTo(cx1, cy1, cx2, cy2, xc, yc); | |
pathOut->curveTo(cx3, cy3, cx4, cy4, pathIn->pts[j0].x + wdyNext, pathIn->pts[j0].y - wdxNext); | |
} | |
} | |
} | |
} 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 (!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; | |
} | |
} | |
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; | |
} | |