Bug 421885. Make tiled image drawing sample only the correct subimage by manually padding if necessary. r=vlad

This commit is contained in:
roc+@cs.cmu.edu 2008-03-31 02:40:53 -07:00
parent 6ecfeccd3a
commit 421ea5a76e
13 changed files with 161 additions and 26 deletions

View File

@ -598,11 +598,14 @@ public:
* @param aXImageStart x location where the origin (0,0) of the image starts
* @param aYImageStart y location where the origin (0,0) of the image starts
* @param aTargetRect area to draw to
* @param aSubimageRect the subimage (in tile space) which we expect to
* sample from; may be null to indicate that the whole image is
* OK to sample from
*/
NS_IMETHOD DrawTile(imgIContainer *aImage,
nscoord aXImageStart, nscoord aYImageStart,
const nsRect * aTargetRect) = 0;
const nsRect * aTargetRect,
const nsIntRect * aSubimageRect) = 0;
/**
* Get cluster details for a chunk of text.

View File

@ -177,6 +177,10 @@ struct NS_GFX nsRect {
// Scale by aScale, converting coordinates to integers so that the result
// is the smallest integer-coordinate rectangle containing the unrounded result
nsRect& ScaleRoundOut(float aScale);
// Scale by the inverse of aScale, converting coordinates to integers so that the result
// is the smallest integer-coordinate rectangle containing the unrounded result.
// More accurate than ScaleRoundOut(1.0/aScale).
nsRect& ScaleRoundOutInverse(float aScale);
// Scale by aScale, converting coordinates to integers so that the result
// is the larges integer-coordinate rectangle contained in the unrounded result
nsRect& ScaleRoundIn(float aScale);

View File

@ -185,6 +185,17 @@ nsRect& nsRect::ScaleRoundOut(float aScale)
return *this;
}
nsRect& nsRect::ScaleRoundOutInverse(float aScale)
{
nscoord right = NSToCoordCeil(float(XMost()) / aScale);
nscoord bottom = NSToCoordCeil(float(YMost()) / aScale);
x = NSToCoordFloor(float(x) / aScale);
y = NSToCoordFloor(float(y) / aScale);
width = (right - x);
height = (bottom - y);
return *this;
}
// scale the rect but round to largest contained rect
nsRect& nsRect::ScaleRoundIn(float aScale)
{

View File

@ -620,6 +620,7 @@ nsThebesImage::ThebesDrawTile(gfxContext *thebesContext,
nsIDeviceContext* dx,
const gfxPoint& offset,
const gfxRect& targetRect,
const nsIntRect& aSubimageRect,
const PRInt32 xPadding,
const PRInt32 yPadding)
{
@ -634,8 +635,9 @@ nsThebesImage::ThebesDrawTile(gfxContext *thebesContext,
PRBool doSnap = !(thebesContext->CurrentMatrix().HasNonTranslation());
PRBool hasPadding = ((xPadding != 0) || (yPadding != 0));
nsRefPtr<gfxASurface> tmpSurfaceGrip;
gfxImageSurface::gfxImageFormat format = mFormat;
gfxPoint tmpOffset = offset;
if (mSinglePixel && !hasPadding) {
thebesContext->SetColor(mSinglePixelColor);
@ -653,14 +655,13 @@ nsThebesImage::ThebesDrawTile(gfxContext *thebesContext,
if (!AllowedImageSize(width, height))
return NS_ERROR_FAILURE;
surface = new gfxImageSurface(gfxIntSize(width, height),
gfxASurface::ImageFormatARGB32);
format = gfxASurface::ImageFormatARGB32;
surface = gfxPlatform::GetPlatform()->CreateOffscreenSurface(
gfxIntSize(width, height), format);
if (!surface || surface->CairoStatus()) {
return NS_ERROR_OUT_OF_MEMORY;
}
tmpSurfaceGrip = surface;
gfxContext tmpContext(surface);
if (mSinglePixel) {
tmpContext.SetColor(mSinglePixelColor);
@ -675,17 +676,105 @@ nsThebesImage::ThebesDrawTile(gfxContext *thebesContext,
height = mHeight;
surface = ThebesSurface();
}
gfxMatrix patMat;
gfxPoint p0;
p0.x = - floor(offset.x + 0.5);
p0.y = - floor(offset.y + 0.5);
// Scale factor to account for CSS pixels; note that the offset (and
// therefore p0) is in device pixels, while the width and height are in
// CSS pixels.
gfxFloat scale = gfxFloat(dx->AppUnitsPerDevPixel()) /
gfxFloat(nsIDeviceContext::AppUnitsPerCSSPixel());
if ((aSubimageRect.width < width || aSubimageRect.height < height) &&
(thebesContext->CurrentMatrix().HasNonTranslation() || scale != 1.0)) {
// Some of the source image should not be drawn, and we're going
// to be doing more than just translation, so we might accidentally
// sample the non-drawn pixels. Avoid that by creating a
// temporary image representing the portion that will be drawn,
// with built-in padding since we can't use EXTEND_PAD and
// EXTEND_REPEAT at the same time for different axes.
PRInt32 padX = aSubimageRect.width < width ? 1 : 0;
PRInt32 padY = aSubimageRect.height < height ? 1 : 0;
PRInt32 tileWidth = PR_MIN(aSubimageRect.width, width);
PRInt32 tileHeight = PR_MIN(aSubimageRect.height, height);
// This tmpSurface will contain a snapshot of the repeated
// tile image at (aSubimageRect.x, aSubimageRect.y,
// tileWidth, tileHeight), with padX padding added to the left
// and right sides and padY padding added to the top and bottom
// sides.
nsRefPtr<gfxASurface> tmpSurface;
tmpSurface = gfxPlatform::GetPlatform()->CreateOffscreenSurface(
gfxIntSize(tileWidth + 2*padX, tileHeight + 2*padY), format);
if (!tmpSurface || tmpSurface->CairoStatus()) {
return NS_ERROR_OUT_OF_MEMORY;
}
gfxContext tmpContext(tmpSurface);
tmpContext.SetOperator(gfxContext::OPERATOR_SOURCE);
gfxPattern pat(surface);
pat.SetExtend(gfxPattern::EXTEND_REPEAT);
// Copy the needed portion of the source image to the temporary
// surface. We also copy over horizontal and/or vertical padding
// strips one pixel wide, plus the corner pixels if necessary.
// So in the most general case the temporary surface ends up
// looking like
// P P P ... P P P
// P X X ... X X P
// P X X ... X X P
// ...............
// P X X ... X X P
// P X X ... X X P
// P P P ... P P P
// Where each P pixel has the color of its nearest source X
// pixel. We implement this as a loop over all nine possible
// areas, [padding, body, padding] x [padding, body, padding].
// Note that we will not need padding on both axes unless
// we are painting just a single tile, in which case this
// will hardly ever get called since nsCSSRendering converts
// the single-tile case to nsLayoutUtils::DrawImage. But this
// could be called on other paths (XUL trees?) and it's simpler
// and clearer to do it the general way.
PRInt32 destY = 0;
for (PRInt32 y = -1; y <= 1; ++y) {
PRInt32 stripHeight = y == 0 ? tileHeight : padY;
if (stripHeight == 0)
continue;
PRInt32 srcY = y == 1 ? aSubimageRect.YMost() - padY : aSubimageRect.y;
PRInt32 destX = 0;
for (PRInt32 x = -1; x <= 1; ++x) {
PRInt32 stripWidth = x == 0 ? tileWidth : padX;
if (stripWidth == 0)
continue;
PRInt32 srcX = x == 1 ? aSubimageRect.XMost() - padX : aSubimageRect.x;
gfxMatrix patMat;
patMat.Translate(gfxPoint(srcX - destX, srcY - destY));
pat.SetMatrix(patMat);
tmpContext.SetPattern(&pat);
tmpContext.Rectangle(gfxRect(destX, destY, stripWidth, stripHeight));
tmpContext.Fill();
tmpContext.NewPath();
destX += stripWidth;
}
destY += stripHeight;
}
// tmpOffset was the top-left of the old tile image. Make it
// the top-left of the new tile image. Note that tmpOffset is
// in destination coordinate space so we have to scale our
// CSS pixels.
tmpOffset += gfxPoint(aSubimageRect.x - padX, aSubimageRect.y - padY)/scale;
surface = tmpSurface;
}
gfxMatrix patMat;
gfxPoint p0;
p0.x = - floor(tmpOffset.x + 0.5);
p0.y = - floor(tmpOffset.y + 0.5);
patMat.Scale(scale, scale);
patMat.Translate(p0);
@ -705,7 +794,7 @@ nsThebesImage::ThebesDrawTile(gfxContext *thebesContext,
}
gfxContext::GraphicsOperator op = thebesContext->CurrentOperator();
if (op == gfxContext::OPERATOR_OVER && mFormat == gfxASurface::ImageFormatRGB24)
if (op == gfxContext::OPERATOR_OVER && format == gfxASurface::ImageFormatRGB24)
thebesContext->SetOperator(gfxContext::OPERATOR_SOURCE);
thebesContext->NewPath();

View File

@ -84,6 +84,7 @@ public:
nsIDeviceContext* dx,
const gfxPoint& aOffset,
const gfxRect& aTileRect,
const nsIntRect& aSubimageRect,
const PRInt32 aXPadding,
const PRInt32 aYPadding);

View File

@ -778,7 +778,8 @@ nsThebesRenderingContext::PopFilter()
NS_IMETHODIMP
nsThebesRenderingContext::DrawTile(imgIContainer *aImage,
nscoord twXOffset, nscoord twYOffset,
const nsRect *twTargetRect)
const nsRect *twTargetRect,
const nsIntRect *subimageRect)
{
PR_LOG(gThebesGFXLog, PR_LOG_DEBUG, ("## %p nsTRC::DrawTile %p %f %f [%f,%f,%f,%f]\n",
this, aImage, FROM_TWIPS(twXOffset), FROM_TWIPS(twYOffset),
@ -813,18 +814,34 @@ nsThebesRenderingContext::DrawTile(imgIContainer *aImage,
PRInt32 xPadding = 0;
PRInt32 yPadding = 0;
nsIntRect tmpSubimageRect;
if (subimageRect) {
tmpSubimageRect = *subimageRect;
} else {
tmpSubimageRect = nsIntRect(0, 0, containerWidth, containerHeight);
}
if (imgFrameRect.width != containerWidth ||
imgFrameRect.height != containerHeight)
{
xPadding = containerWidth - imgFrameRect.width;
yPadding = containerHeight - imgFrameRect.height;
// XXXroc shouldn't we be adding to 'phase' here? it's tbe origin
// at which the image origin should be drawn, and ThebesDrawTile
// just draws the origin of its "frame" there, so we should be
// adding imgFrameRect.x/y. so that the imgFrame draws in the
// right place.
phase.x -= imgFrameRect.x;
phase.y -= imgFrameRect.y;
tmpSubimageRect.x -= imgFrameRect.x;
tmpSubimageRect.y -= imgFrameRect.y;
}
return thebesImage->ThebesDrawTile (mThebes, mDeviceContext, phase,
GFX_RECT_FROM_TWIPS_RECT(*twTargetRect),
tmpSubimageRect,
xPadding, yPadding);
}

View File

@ -185,7 +185,7 @@ public:
NS_IMETHOD SetTranslation(nscoord aX, nscoord aY);
NS_IMETHOD DrawTile(imgIContainer *aImage, nscoord aXOffset, nscoord aYOffset,
const nsRect * aTargetRect);
const nsRect * aTargetRect, const nsIntRect * aSubimageRect);
NS_IMETHOD SetRightToLeftText(PRBool aIsRTL);
NS_IMETHOD GetRightToLeftText(PRBool* aIsRTL);
virtual void SetTextRunRTL(PRBool aIsRTL);

View File

@ -120,6 +120,11 @@ struct THEBES_API gfxPoint {
int operator!=(const gfxPoint& p) const {
return ((x != p.x) || (y != p.y));
}
const gfxPoint& operator+=(const gfxPoint& p) {
x += p.x;
y += p.y;
return *this;
}
gfxPoint operator+(const gfxPoint& p) const {
return gfxPoint(x + p.x, y + p.y);
}

View File

@ -3904,19 +3904,23 @@ nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext,
"Bogus y coord for draw rect");
// Figure out whether we can get away with not tiling at all.
nsRect sourceRect = drawRect - absTileRect.TopLeft();
// Compute the subimage rectangle that we expect to be sampled.
// This is the tile rectangle, clipped to the bgClipArea, and then
// passed in relative to the image top-left.
nsRect destRect; // The rectangle we would draw ignoring dirty-rect
destRect.IntersectRect(absTileRect, bgClipArea);
nsRect subimageRect = destRect - aBorderArea.TopLeft() - tileRect.TopLeft();
if (sourceRect.XMost() <= tileWidth && sourceRect.YMost() <= tileHeight) {
// The entire drawRect is contained inside a single tile; just
// draw the corresponding part of the image once.
// Pass in the subimage rectangle that we expect to be sampled.
// This is the tile rectangle, clipped to the bgClipArea, and then
// passed in relative to the image top-left.
nsRect destRect; // The rectangle we would draw ignoring dirty-rect
destRect.IntersectRect(absTileRect, bgClipArea);
nsRect subimageRect = destRect - aBorderArea.TopLeft() - tileRect.TopLeft();
nsLayoutUtils::DrawImage(&aRenderingContext, image,
destRect, drawRect, &subimageRect);
} else {
aRenderingContext.DrawTile(image, absTileRect.x, absTileRect.y, &drawRect);
// Note that the subimage is in tile space so it may cover
// multiple tiles of the image.
subimageRect.ScaleRoundOutInverse(nsIDeviceContext::AppUnitsPerCSSPixel());
aRenderingContext.DrawTile(image, absTileRect.x, absTileRect.y,
&drawRect, &subimageRect);
}
}

View File

@ -773,6 +773,7 @@ fails == 413027-3.html 413027-3-ref.html
== 421069-ref.html 421069-ref2.html
== 421234-1.html 421234-1-ref.html
== 421419-1.html 421419-1-ref.html
== 421885-1.xml 421885-1-ref.xml
== 421955-1.html 421955-1-ref.html
== 422394-1.html 422394-1-ref.html
== 423130-1.html 423130-1-ref.html

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

View File

@ -3657,7 +3657,7 @@ nsTreeBodyFrame::PaintProgressMeter(PRInt32 aRowIndex,
nsCOMPtr<imgIContainer> image;
GetImage(aRowIndex, aColumn, PR_TRUE, meterContext, useImageRegion, getter_AddRefs(image));
if (image)
aRenderingContext.DrawTile(image, 0, 0, &meterRect);
aRenderingContext.DrawTile(image, 0, 0, &meterRect, nsnull);
else
aRenderingContext.FillRect(meterRect);
}
@ -3669,7 +3669,7 @@ nsTreeBodyFrame::PaintProgressMeter(PRInt32 aRowIndex,
nsCOMPtr<imgIContainer> image;
GetImage(aRowIndex, aColumn, PR_TRUE, meterContext, useImageRegion, getter_AddRefs(image));
if (image)
aRenderingContext.DrawTile(image, 0, 0, &meterRect);
aRenderingContext.DrawTile(image, 0, 0, &meterRect, nsnull);
}
}