diff --git a/poppler/GfxState.h b/poppler/GfxState.h index 106b2c0..872fbef 100644 --- a/poppler/GfxState.h +++ b/poppler/GfxState.h @@ -105,7 +105,7 @@ static inline double colToDbl(GfxColorComp x) { } static inline Guchar dblToByte(double x) { - return (x * 255.0); + return (Guchar)(x * 255.0); } static inline double byteToDbl(Guchar x) { diff --git a/splash/Splash.cc b/splash/Splash.cc index 8d0765b..4b16fab 100644 --- a/splash/Splash.cc +++ b/splash/Splash.cc @@ -1451,7 +1451,7 @@ inline void Splash::drawAAPixel(SplashPipe *pipe, int x, int y) { // draw the pixel if (t != 0) { pipeSetXY(pipe, x, y); - pipe->shape = div255(aaGamma[t] * pipe->shape); + pipe->shape = div255((int)aaGamma[t] * pipe->shape); (this->*pipe->run)(pipe); updateModX(x); updateModY(y); @@ -1533,7 +1533,7 @@ inline void Splash::drawAALine(SplashPipe *pipe, int x0, int x1, int y, GBool ad #endif if (t != 0) { - pipe->shape = (adjustLine) ? div255((int) lineOpacity * (double)aaGamma[t]) : (double)aaGamma[t]; + pipe->shape = (adjustLine) ? div255(int((int)lineOpacity * (double)aaGamma[t])) : Guchar((double)aaGamma[t]); (this->*pipe->run)(pipe); updateModX(x); updateModY(y); @@ -2542,7 +2542,7 @@ SplashError Splash::fillWithPattern(SplashPath *path, GBool eo, transform(state->matrix, 0, 0, &mx, &my); transform(state->matrix, state->lineWidth, 0, &delta, &my); adjustLine = gTrue; - lineShape = clip255((delta - mx) * 255); + lineShape = clip255(int((delta - mx) * 255)); } drawAALine(&pipe, x0, x1, y, adjustLine, lineShape); } @@ -3708,7 +3708,37 @@ SplashError Splash::drawImage(SplashImageSource src, void *srcData, 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 + + // This is not an optimal way of calculating the rect into which to draw an image. + // However, there will be special situations in which the math above needs to be slightly corrected. + + // Consider a situation when we draw an image @ 300dpi on a device @300dpi (we will call it "at native resoltuion") + // In this case m[0] and m[3] scaling components of the CTM matrix should be integers as they correspong exactly to + // the width and height of the image in pixles. + // The m[4] and m[5] - the translation components of the CTM matrix don't have to be integers, and in fact there almost never are. + // Now let's consider the simplest example of a 1x1 image that needs to be drawn in this case (at native resolution). + // m[0] == 1, m[3] == 1 are the dimensions of the image. Let's say m[4] == 0.5, m[5] = 0. The pixel of the image then needs to be drawn + // over 2 pixels of the device with coordinates (0,0) and (0,1) since due to fractional offset m[4] == 0.5 the pixel of the image overlaps + // the above 2 pixels of the device. The right way to draw, say, a black pixel in such a situation would be to divide its intensity + // between the two device pixels evenly. This would result in 2 grey pixels being drawn. In other words, the fractional offset should result + // in smearing of the pixel when drawn on the device. When drawing a generic large image at native resolution it appears blurred. + + // Arithmetically, this will show in the form of scaledWidth == w + 1, scaledHeight == h + 1 (see below). That is, when drawing at image's + // native resolution the scaledWidth and scaledHeight are always bigger by 1 pixel then the width and height of the image. + // Thus the image is almost always (except when the offsets m[4] and m[5] are both integers) blurred when drawn at its native resolution. + + // This is not a problem by itself, but rather a place where we can do better. Namely, for the particular case of rendering at native + // resoltuion (and, of course, multiples of native resoltuion as well) we may choose to + // keep scaledWidth == n*w & scaledHeight == n*h, where n is an integer resolution multiplier, which is 1 for native resolution. + + // We implement this by checking if scaledWidth == n*w + 1 and scaledHeight == n*h + 1 and decrementing the scaled sizes accordingly. + + // Ref: bug #68360 + + if (w > 0 && (x1 - x0) % w == 1) --x1; + if (h > 0 && (y1 - y0) % h == 1) --y1; + + // make sure narrow images cover at least one pixel if (x0 == x1) { ++x1; } @@ -4095,8 +4125,13 @@ static GBool isImageInterpolationRequired(int srcWidth, int srcHeight, if (interpolate) return gTrue; - /* When scale factor is >= 400% we don't interpolate. See bugs #25268, #9860 */ - if (scaledWidth / srcWidth >= 4 || scaledHeight / srcHeight >= 4) + // Determine if we're scale by an integer scale factor. + bool isScaleFactorInteger = srcWidth > 0 && scaledWidth % srcWidth == 0 + && srcHeight > 0 && scaledHeight % srcHeight == 0; + + /* When scale factor is >= 400% or when scale factor is integer we don't interpolate. + See bugs #25268, #9860, #68360 */ + if (scaledWidth / srcWidth >= 4 || scaledHeight / srcHeight >= 4 || isScaleFactorInteger) return gFalse; return gTrue; @@ -4125,7 +4160,7 @@ SplashBitmap *Splash::scaleImage(SplashImageSource src, void *srcData, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } else { if (!tilingPattern && isImageInterpolationRequired(srcWidth, srcHeight, scaledWidth, scaledHeight, interpolate)) { - scaleImageYuXuBilinear(src, srcData, srcMode, nComps, srcAlpha, + scaleImageYuXuBilinearInt(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } else { scaleImageYuXu(src, srcData, srcMode, nComps, srcAlpha, @@ -4987,6 +5022,164 @@ void Splash::scaleImageYuXuBilinear(SplashImageSource src, void *srcData, gfree(lineBuf2); } +// expand source row to scaledWidth using linear interpolation +// This is a version of expandRow() that uses integer math instead of floating point math +static void expandRowInt(Guchar *srcBuf, Guchar *dstBuf, int srcWidth, int scaledWidth, int nComps) +{ + if (scaledWidth < srcWidth) return; + if (scaledWidth == srcWidth) { memcpy(dstBuf, srcBuf, scaledWidth * nComps); } + + int x, xSrc, xFrac, xFracInverse, xInt, c; + int srcSpan = srcWidth - 1; + int scaledSpan = scaledWidth - 1; + int srcZ, scaledZ, scaledZNext; + int scaledSpanHalf = scaledSpan/2; // Needed for a slightly more accurate center-point rounding + + // 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 (x = 0, xSrc = 0; x < scaledWidth; ++x, xSrc += srcSpan) { + xInt = xSrc / scaledSpan; + xFrac = xSrc % scaledSpan; + xFracInverse = scaledSpan - xFrac; + + for (c = 0, srcZ = nComps * x, scaledZ = nComps * xInt, scaledZNext = nComps * (xInt + 1); + c < nComps; + ++c, ++srcZ, ++scaledZ, ++scaledZNext) + dstBuf[srcZ] = (srcBuf[scaledZ] * xFracInverse + srcBuf[scaledZNext] * xFrac + scaledSpanHalf)/scaledSpan; // Center-point rounding + } +} + +// Scale up image using bilinear interpolation +// This is a version of scaleImageYuXuBilinear() that uses integer math instead of floating point math +void Splash::scaleImageYuXuBilinearInt(SplashImageSource src, void *srcData, + SplashColorMode srcMode, int nComps, + GBool srcAlpha, int srcWidth, int srcHeight, + int scaledWidth, int scaledHeight, + SplashBitmap *dest) { + Guchar *srcBuf, *lineBuf1, *lineBuf2, *alphaSrcBuf, *alphaLineBuf1, *alphaLineBuf2; + Guint pix[splashMaxColorComps]; + Guchar *destPtr0, *destPtr, *destAlphaPtr0, *destAlphaPtr; + + // allocate buffers + srcBuf = (Guchar *)gmallocn(srcWidth+1, nComps); // + 1 pixel of padding + lineBuf1 = (Guchar *)gmallocn(scaledWidth, nComps); + lineBuf2 = (Guchar *)gmallocn(scaledWidth, nComps); + if (srcAlpha) { + alphaSrcBuf = (Guchar *)gmalloc(srcWidth+1); // + 1 pixel of padding + alphaLineBuf1 = (Guchar *)gmalloc(scaledWidth); + alphaLineBuf2 = (Guchar *)gmalloc(scaledWidth); + } else { + alphaSrcBuf = NULL; + alphaLineBuf1 = NULL; + alphaLineBuf2 = NULL; + } + + // Begin + + int currentSrcRow = -1; + + int y, ySrc, yFrac, yFracInverse, yInt, c; + int srcSpan = srcHeight - 1; + int scaledSpan = scaledHeight - 1; + int scaledSpanHalf = scaledSpan/2; // Needed for a slightly more accurate center-point rounding + int z; + + (*src)(srcData, srcBuf, alphaSrcBuf); + + expandRowInt(srcBuf, lineBuf2, srcWidth, scaledWidth, nComps); + if (srcAlpha) + expandRowInt(alphaSrcBuf, alphaLineBuf2, srcWidth, scaledWidth, 1); + + destPtr0 = dest->data; + destAlphaPtr0 = dest->alpha; + + for (y = 0, ySrc = 0; y < scaledHeight; ++y, ySrc += srcSpan) { + yInt = ySrc / scaledSpan; + yFrac = ySrc % scaledSpan; + yFracInverse = scaledSpan - yFrac; + + if (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) { + (*src)(srcData, srcBuf, alphaSrcBuf); + expandRowInt(srcBuf, lineBuf2, srcWidth, scaledWidth, nComps); + if (srcAlpha) + expandRowInt(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 (c = 0, z = x*nComps; c < nComps; ++c, ++z) { + pix[c] = (lineBuf1[z]*yFracInverse + lineBuf2[z]*yFrac + scaledSpanHalf)/scaledSpan; // Center-point rounding + } + + // store the pixel + destPtr = destPtr0 + (y * scaledWidth + x) * nComps; + switch (srcMode) { + case splashModeMono1: // mono1 is not allowed + break; + case splashModeMono8: + *destPtr++ = (Guchar)pix[0]; + break; + case splashModeRGB8: + *destPtr++ = (Guchar)pix[0]; + *destPtr++ = (Guchar)pix[1]; + *destPtr++ = (Guchar)pix[2]; + break; + case splashModeXBGR8: + *destPtr++ = (Guchar)pix[2]; + *destPtr++ = (Guchar)pix[1]; + *destPtr++ = (Guchar)pix[0]; + *destPtr++ = (Guchar)255; + break; + case splashModeBGR8: + *destPtr++ = (Guchar)pix[2]; + *destPtr++ = (Guchar)pix[1]; + *destPtr++ = (Guchar)pix[0]; + break; +#if SPLASH_CMYK + case splashModeCMYK8: + *destPtr++ = (Guchar)pix[0]; + *destPtr++ = (Guchar)pix[1]; + *destPtr++ = (Guchar)pix[2]; + *destPtr++ = (Guchar)pix[3]; + break; + case splashModeDeviceN8: + for (int cp = 0; cp < SPOT_NCOMPS+4; cp++) + *destPtr++ = (Guchar)pix[cp]; + break; +#endif + } + + // process alpha + if (srcAlpha) { + destAlphaPtr = destAlphaPtr0 + y*scaledWidth + x; + *destAlphaPtr = (alphaLineBuf1[x]*yFracInverse + alphaLineBuf2[x]*yFrac + scaledSpanHalf)/scaledSpan; + } + } + } + + gfree(alphaSrcBuf); + gfree(alphaLineBuf1); + gfree(alphaLineBuf2); + gfree(srcBuf); + gfree(lineBuf1); + gfree(lineBuf2); +} + void Splash::vertFlipImage(SplashBitmap *img, int width, int height, int nComps) { Guchar *lineBuf; diff --git a/splash/Splash.h b/splash/Splash.h index cf98e6c..a9a3643 100644 --- a/splash/Splash.h +++ b/splash/Splash.h @@ -391,6 +391,14 @@ private: GBool srcAlpha, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, SplashBitmap *dest); + + // This is a version of scaleImageYuXuBilinear() that uses integer math instead of floating point math + void scaleImageYuXuBilinearInt(SplashImageSource src, void *srcData, + SplashColorMode srcMode, int nComps, + GBool srcAlpha, int srcWidth, int srcHeight, + int scaledWidth, int scaledHeight, + SplashBitmap *dest); + void vertFlipImage(SplashBitmap *img, int width, int height, int nComps); void blitImage(SplashBitmap *src, GBool srcAlpha, int xDest, int yDest,