From 71d94ea626f3a6b9e8c4afe86c69b184fc56979b Mon Sep 17 00:00:00 2001 From: Seth Fowler Date: Fri, 22 Aug 2014 13:12:38 -0700 Subject: [PATCH] Bug 1043560 - Refactor the imgIContainer::Draw API. r=tn,dholbert,jwatt,mwu,mattwoodrow,roc sr=jrmuizel --HG-- extra : rebase_source : b5ed02cb200ece12a07328613dca217e9d975703 --- content/html/document/reftests/reftests.list | 2 +- content/svg/content/src/SVGSVGElement.h | 7 +- dom/canvas/CanvasRenderingContext2D.cpp | 51 +-- dom/canvas/CanvasRenderingContext2D.h | 7 +- gfx/thebes/gfxUtils.cpp | 71 ++-- gfx/thebes/gfxUtils.h | 19 +- image/public/imgIContainer.idl | 133 ++++++-- image/src/ClippedImage.cpp | 223 ++++++------ image/src/ClippedImage.h | 30 +- image/src/DynamicImage.cpp | 42 ++- image/src/FrozenImage.cpp | 15 +- image/src/FrozenImage.h | 10 +- image/src/ImageRegion.h | 158 +++++++++ image/src/ImageWrapper.cpp | 15 +- image/src/OrientedImage.cpp | 259 ++++++++------ image/src/OrientedImage.h | 12 +- image/src/RasterImage.cpp | 101 +++--- image/src/RasterImage.h | 13 +- image/src/SurfaceCache.h | 35 +- image/src/VectorImage.cpp | 161 ++++----- image/src/imgFrame.cpp | 66 ++-- image/src/imgFrame.h | 15 +- image/src/imgRequestProxy.cpp | 2 +- image/src/imgStatusTracker.cpp | 2 +- image/src/moz.build | 1 + image/test/reftest/downscaling/reftest.list | 208 ++++++------ layout/base/nsLayoutUtils.cpp | 340 +++++++++++-------- layout/base/nsLayoutUtils.h | 43 ++- layout/reftests/border-image/reftest.list | 4 +- layout/reftests/bugs/reftest.list | 2 +- layout/reftests/image/reftest.list | 2 +- layout/reftests/svg/as-image/reftest.list | 5 +- layout/svg/SVGImageContext.h | 33 +- layout/svg/nsSVGImageFrame.cpp | 3 +- widget/cocoa/nsCocoaUtils.mm | 21 +- widget/xpwidgets/nsBaseDragService.cpp | 14 +- 36 files changed, 1209 insertions(+), 916 deletions(-) create mode 100644 image/src/ImageRegion.h diff --git a/content/html/document/reftests/reftests.list b/content/html/document/reftests/reftests.list index 530d55867fd..9684dfedc8d 100644 --- a/content/html/document/reftests/reftests.list +++ b/content/html/document/reftests/reftests.list @@ -9,4 +9,4 @@ # (Fuzzy necessary due to pixel-wise comparison of different JPEGs. # The vast majority of the fuzziness comes from Linux and WinXP.) fuzzy(1,149) == bug917595-iframe-1.html bug917595-1-ref.html -fuzzy(2,446) == bug917595-exif-rotated.jpg bug917595-pixel-rotated.jpg +fuzzy(2,640) == bug917595-exif-rotated.jpg bug917595-pixel-rotated.jpg diff --git a/content/svg/content/src/SVGSVGElement.h b/content/svg/content/src/SVGSVGElement.h index 0d9525627d2..dab2dbef313 100644 --- a/content/svg/content/src/SVGSVGElement.h +++ b/content/svg/content/src/SVGSVGElement.h @@ -401,11 +401,12 @@ private: class MOZ_STACK_CLASS AutoSVGRenderingState { public: - AutoSVGRenderingState(const SVGImageContext* aSVGContext, + AutoSVGRenderingState(const Maybe& aSVGContext, float aFrameTime, dom::SVGSVGElement* aRootElem MOZ_GUARD_OBJECT_NOTIFIER_PARAM) - : mHaveOverrides(!!aSVGContext) + : mHaveOverrides(aSVGContext.isSome() && + aSVGContext->GetPreserveAspectRatio().isSome()) , mRootElem(aRootElem) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; @@ -415,7 +416,7 @@ public: // XXXdholbert We should technically be overriding the helper doc's clip // and overflow properties here, too. See bug 272288 comment 36. mRootElem->SetImageOverridePreserveAspectRatio( - aSVGContext->GetPreserveAspectRatio()); + *aSVGContext->GetPreserveAspectRatio()); } mOriginalTime = mRootElem->GetCurrentTime(); diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 19e8e943252..52985f5a177 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -43,6 +43,7 @@ #include "nsTArray.h" #include "ImageEncoder.h" +#include "ImageRegion.h" #include "gfxContext.h" #include "gfxASurface.h" @@ -97,6 +98,7 @@ #include "GLContext.h" #include "GLContextProvider.h" #include "SVGContentUtils.h" +#include "SVGImageContext.h" #include "nsIScreenManager.h" #undef free // apparently defined by some windows header, clashing with a free() @@ -126,6 +128,7 @@ using namespace mozilla; using namespace mozilla::CanvasUtils; using namespace mozilla::css; using namespace mozilla::gfx; +using namespace mozilla::image; using namespace mozilla::ipc; using namespace mozilla::layers; @@ -3431,8 +3434,10 @@ CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image DrawSurfaceOptions(filter), DrawOptions(CurrentState().globalAlpha, UsedOperation())); } else { - DrawDirectlyToCanvas(drawInfo, &bounds, dx, dy, dw, dh, - sx, sy, sw, sh, imgSize); + DrawDirectlyToCanvas(drawInfo, &bounds, + mgfx::Rect(dx, dy, dw, dh), + mgfx::Rect(sx, sy, sw, sh), + imgSize); } RedrawUser(gfxRect(dx, dy, dw, dh)); @@ -3441,40 +3446,48 @@ CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image void CanvasRenderingContext2D::DrawDirectlyToCanvas( const nsLayoutUtils::DirectDrawInfo& image, - mgfx::Rect* bounds, double dx, double dy, - double dw, double dh, double sx, double sy, - double sw, double sh, gfxIntSize imgSize) + mgfx::Rect* bounds, + mgfx::Rect dest, + mgfx::Rect src, + gfxIntSize imgSize) { - gfxMatrix contextMatrix; + MOZ_ASSERT(src.width > 0 && src.height > 0, + "Need positive source width and height"); + gfxMatrix contextMatrix; AdjustedTarget tempTarget(this, bounds->IsEmpty() ? nullptr: bounds); - // get any already existing transforms on the context. Include transformations used for context shadow + // Get any existing transforms on the context, including transformations used + // for context shadow. if (tempTarget) { Matrix matrix = tempTarget->GetTransform(); contextMatrix = gfxMatrix(matrix._11, matrix._12, matrix._21, matrix._22, matrix._31, matrix._32); } + gfxSize contextScale(contextMatrix.ScaleFactors(true)); - gfxMatrix transformMatrix; - transformMatrix.Translate(gfxPoint(sx, sy)); - if (dw > 0 && dh > 0) { - transformMatrix.Scale(sw/dw, sh/dh); - } - transformMatrix.Translate(gfxPoint(-dx, -dy)); + // Scale the dest rect to include the context scale. + dest.Scale(contextScale.width, contextScale.height); + + // Scale the image size to the dest rect, and adjust the source rect to match. + gfxSize scale(dest.width / src.width, dest.height / src.height); + nsIntSize scaledImageSize(std::ceil(imgSize.width * scale.width), + std::ceil(imgSize.height * scale.height)); + src.Scale(scale.width, scale.height); nsRefPtr context = new gfxContext(tempTarget); context->SetMatrix(contextMatrix); + context->Scale(1.0 / contextScale.width, 1.0 / contextScale.height); + context->Translate(gfxPoint(dest.x - src.x, dest.y - src.y)); - // FLAG_CLAMP is added for increased performance + // FLAG_CLAMP is added for increased performance, since we never tile here. uint32_t modifiedFlags = image.mDrawingFlags | imgIContainer::FLAG_CLAMP; nsresult rv = image.mImgContainer-> - Draw(context, GraphicsFilter::FILTER_GOOD, transformMatrix, - gfxRect(gfxPoint(dx, dy), gfxIntSize(dw, dh)), - nsIntRect(nsIntPoint(0, 0), gfxIntSize(imgSize.width, imgSize.height)), - gfxIntSize(imgSize.width, imgSize.height), nullptr, image.mWhichFrame, - modifiedFlags); + Draw(context, scaledImageSize, + ImageRegion::Create(gfxRect(src.x, src.y, src.width, src.height)), + image.mWhichFrame, GraphicsFilter::FILTER_GOOD, + Nothing(), modifiedFlags); NS_ENSURE_SUCCESS_VOID(rv); } diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h index 94d66295abb..f5272fa3cdf 100644 --- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -665,9 +665,10 @@ protected: uint8_t optional_argc, mozilla::ErrorResult& error); void DrawDirectlyToCanvas(const nsLayoutUtils::DirectDrawInfo& image, - mozilla::gfx::Rect* bounds, double dx, double dy, - double dw, double dh, double sx, double sy, - double sw, double sh, gfxIntSize imgSize); + mozilla::gfx::Rect* bounds, + mozilla::gfx::Rect dest, + mozilla::gfx::Rect src, + gfxIntSize imgSize); nsString& GetFont() { diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp index b755cf5c4c7..5dd27742c7e 100644 --- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -14,6 +14,7 @@ #include "mozilla/Base64.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" #include "mozilla/Vector.h" #include "nsComponentManagerUtils.h" @@ -27,6 +28,7 @@ #include "ycbcr_to_rgb565.h" #include "GeckoProfiler.h" #include "ImageContainer.h" +#include "ImageRegion.h" #include "gfx2DGlue.h" #include "gfxPrefs.h" @@ -35,6 +37,7 @@ #endif using namespace mozilla; +using namespace mozilla::image; using namespace mozilla::layers; using namespace mozilla::gfx; @@ -367,28 +370,19 @@ OptimalFillOperator() static already_AddRefed CreateSamplingRestrictedDrawable(gfxDrawable* aDrawable, gfxContext* aContext, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aSourceRect, - const gfxRect& aSubimage, + const ImageRegion& aRegion, const SurfaceFormat aFormat) { PROFILER_LABEL("gfxUtils", "CreateSamplingRestricedDrawable", js::ProfileEntry::Category::GRAPHICS); - gfxRect userSpaceClipExtents = aContext->GetClipExtents(); - // This isn't optimal --- if aContext has a rotation then GetClipExtents - // will have to do a bounding-box computation, and TransformBounds might - // too, so we could get a better result if we computed image space clip - // extents in one go --- but it doesn't really matter and this is easier - // to understand. - gfxRect imageSpaceClipExtents = - aUserSpaceToImageSpace.TransformBounds(userSpaceClipExtents); + gfxRect clipExtents = aContext->GetClipExtents(); + // Inflate by one pixel because bilinear filtering will sample at most // one pixel beyond the computed image pixel coordinate. - imageSpaceClipExtents.Inflate(1.0); + clipExtents.Inflate(1.0); - gfxRect needed = imageSpaceClipExtents.Intersect(aSourceRect); - needed = needed.Intersect(aSubimage); + gfxRect needed = aRegion.IntersectAndRestrict(clipExtents); needed.RoundOut(); // if 'needed' is empty, nothing will be drawn since aFill @@ -483,19 +477,17 @@ private: }; static gfxMatrix -DeviceToImageTransform(gfxContext* aContext, - const gfxMatrix& aUserSpaceToImageSpace) +DeviceToImageTransform(gfxContext* aContext) { gfxFloat deviceX, deviceY; nsRefPtr currentTarget = aContext->CurrentSurface(&deviceX, &deviceY); - gfxMatrix currentMatrix = aContext->CurrentMatrix(); - gfxMatrix deviceToUser = currentMatrix; + gfxMatrix deviceToUser = aContext->CurrentMatrix(); if (!deviceToUser.Invert()) { return gfxMatrix(0, 0, 0, 0, 0, 0); // singular } deviceToUser.Translate(-gfxPoint(-deviceX, -deviceY)); - return deviceToUser * aUserSpaceToImageSpace; + return deviceToUser; } /* These heuristics are based on Source/WebCore/platform/graphics/skia/ImageSkia.cpp:computeResamplingMode() */ @@ -566,37 +558,36 @@ static GraphicsFilter ReduceResamplingFilter(GraphicsFilter aFilter, #endif /* static */ void -gfxUtils::DrawPixelSnapped(gfxContext* aContext, - gfxDrawable* aDrawable, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aSubimage, - const gfxRect& aSourceRect, - const gfxRect& aImageRect, - const gfxRect& aFill, +gfxUtils::DrawPixelSnapped(gfxContext* aContext, + gfxDrawable* aDrawable, + const gfxSize& aImageSize, + const ImageRegion& aRegion, const SurfaceFormat aFormat, - GraphicsFilter aFilter, - uint32_t aImageFlags) + GraphicsFilter aFilter, + uint32_t aImageFlags) { PROFILER_LABEL("gfxUtils", "DrawPixelSnapped", js::ProfileEntry::Category::GRAPHICS); - bool doTile = !aImageRect.Contains(aSourceRect) && + gfxRect imageRect(gfxPoint(0, 0), aImageSize); + gfxRect region(aRegion.Rect()); + + bool doTile = !imageRect.Contains(region) && !(aImageFlags & imgIContainer::FLAG_CLAMP); nsRefPtr currentTarget = aContext->CurrentSurface(); - gfxMatrix deviceSpaceToImageSpace = - DeviceToImageTransform(aContext, aUserSpaceToImageSpace); + gfxMatrix deviceSpaceToImageSpace = DeviceToImageTransform(aContext); AutoCairoPixmanBugWorkaround workaround(aContext, deviceSpaceToImageSpace, - aFill, currentTarget); + region, currentTarget); if (!workaround.Succeeded()) return; nsRefPtr drawable = aDrawable; - aFilter = ReduceResamplingFilter(aFilter, aImageRect.Width(), aImageRect.Height(), aSourceRect.Width(), aSourceRect.Height()); - - gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace; + aFilter = ReduceResamplingFilter(aFilter, + imageRect.Width(), imageRect.Height(), + region.Width(), region.Height()); // On Mobile, we don't ever want to do this; it has the potential for // allocating very large temporary surfaces, especially since we'll @@ -607,13 +598,11 @@ gfxUtils::DrawPixelSnapped(gfxContext* aContext, // translations, then we assume no resampling will occur so there's // nothing to do. // XXX if only we had source-clipping in cairo! - if (aContext->CurrentMatrix().HasNonIntegerTranslation() || - aUserSpaceToImageSpace.HasNonIntegerTranslation()) { - if (doTile || !aSubimage.Contains(aImageRect)) { + if (aContext->CurrentMatrix().HasNonIntegerTranslation()) { + if (doTile || !aRegion.RestrictionContains(imageRect)) { nsRefPtr restrictedDrawable = CreateSamplingRestrictedDrawable(aDrawable, aContext, - aUserSpaceToImageSpace, aSourceRect, - aSubimage, aFormat); + aRegion, aFormat); if (restrictedDrawable) { drawable.swap(restrictedDrawable); } @@ -625,7 +614,7 @@ gfxUtils::DrawPixelSnapped(gfxContext* aContext, } #endif - drawable->Draw(aContext, aFill, doTile, aFilter, userSpaceToImageSpace); + drawable->Draw(aContext, aRegion.Rect(), doTile, aFilter); } /* static */ int diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h index c42a1294d15..fc9cf759226 100644 --- a/gfx/thebes/gfxUtils.h +++ b/gfx/thebes/gfxUtils.h @@ -25,6 +25,9 @@ namespace mozilla { namespace layers { struct PlanarYCbCrData; } +namespace image { +class ImageRegion; +} } class gfxUtils { @@ -35,6 +38,7 @@ public: typedef mozilla::gfx::Matrix Matrix; typedef mozilla::gfx::SourceSurface SourceSurface; typedef mozilla::gfx::SurfaceFormat SurfaceFormat; + typedef mozilla::image::ImageRegion ImageRegion; /* * Premultiply or Unpremultiply aSourceSurface, writing the result @@ -71,16 +75,13 @@ public: * will tweak the rects and transforms that it gets from the pixel snapping * algorithm before passing them on to this method. */ - static void DrawPixelSnapped(gfxContext* aContext, - gfxDrawable* aDrawable, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aSubimage, - const gfxRect& aSourceRect, - const gfxRect& aImageRect, - const gfxRect& aFill, + static void DrawPixelSnapped(gfxContext* aContext, + gfxDrawable* aDrawable, + const gfxSize& aImageSize, + const ImageRegion& aRegion, const mozilla::gfx::SurfaceFormat aFormat, - GraphicsFilter aFilter, - uint32_t aImageFlags = imgIContainer::FLAG_NONE); + GraphicsFilter aFilter, + uint32_t aImageFlags = imgIContainer::FLAG_NONE); /** * Clip aContext to the region aRegion. diff --git a/image/public/imgIContainer.idl b/image/public/imgIContainer.idl index 32a09954a75..4005ffaec76 100644 --- a/image/public/imgIContainer.idl +++ b/image/public/imgIContainer.idl @@ -12,6 +12,7 @@ #include "gfxRect.h" #include "GraphicsFilter.h" #include "mozilla/gfx/2D.h" +#include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" #include "nsRect.h" #include "nsSize.h" @@ -33,6 +34,7 @@ class SVGImageContext; namespace mozilla { namespace image { +class ImageRegion; struct Orientation; } } @@ -50,10 +52,11 @@ native nsIntRectByVal(nsIntRect); native nsSize(nsSize); [ptr] native nsIFrame(nsIFrame); [ptr] native ImageContainer(mozilla::layers::ImageContainer); +[ref] native ImageRegion(mozilla::image::ImageRegion); [ptr] native LayerManager(mozilla::layers::LayerManager); native Orientation(mozilla::image::Orientation); [ref] native TimeStamp(mozilla::TimeStamp); -[ptr] native SVGImageContext(mozilla::SVGImageContext); +[ref] native MaybeSVGImageContext(mozilla::Maybe); native TempRefSourceSurface(mozilla::TemporaryRef); native TempRefImgIContainer(already_AddRefed); native nsIntSizeByVal(nsIntSize); @@ -66,7 +69,7 @@ native nsIntSizeByVal(nsIntSize); * * Internally, imgIContainer also manages animation of images. */ -[scriptable, builtinclass, uuid(9c0f1884-dad9-4d63-a0e5-dda7b3b3416b)] +[scriptable, builtinclass, uuid(f8bb7671-5f36-490b-b828-3f4c6ad38665)] interface imgIContainer : nsISupports { /** @@ -226,39 +229,113 @@ interface imgIContainer : nsISupports [noscript] ImageContainer getImageContainer(in LayerManager aManager); /** - * Draw a frame onto the context specified. + * Draw the requested frame of this image onto the context specified. + * + * Drawing an image involves scaling it to a certain size (which may be + * implemented as a "smart" scale by substituting an HQ-scaled frame or + * rendering at a high DPI), and then selecting a region of that image to + * draw. That region is drawn onto the graphics context and in the process + * transformed by the context matrix, which determines the final area that is + * filled. The basic process looks like this: + * + * +------------------+ + * | Image | + * | | + * | intrinsic width | + * | X | + * | intrinsic height | + * +------------------+ + * / \ + * / \ + * / (scale to aSize) \ + * / \ + * +----------------------------+ + * | | + * | Scaled Image | + * | aSize.width X aSize.height | + * | | + * | +---------+ | + * | | aRegion | | + * | +---------+ | + * +-------(---------(----------+ + * | | + * / \ + * | (transform | + * / by aContext \ + * | matrix) | + * / \ + * +---------------------+ + * | | + * | Fill Rect | + * | | + * +---------------------+ + * + * The region may extend outside of the scaled image's boundaries. It's + * actually a region in tiled image space, which is formed by tiling the + * scaled image infinitely in every direction. Drawing with a region larger + * than the scaled image thus causes the filled area to contain multiple tiled + * copies of the image, which looks like this: + * + * .................................................... + * : : : : + * : Tile : Tile : Tile : + * : +------------[aRegion]------------+ : + * :........|.......:................:........|.......: + * : | : : | : + * : Ti|le : Scaled Image : Ti|le : + * : | : : | : + * :........|.......:................:........|.......: + * : +---------------------------------+ : + * : Ti|le : Tile : Ti|le : + * : / : : \ : + * :......(.........:................:..........).....: + * | | + * / \ + * | (transform by aContext matrix) | + * / \ + * +---------------------------------------------+ + * | : : | + * |.....:.................................:.....| + * | : : | + * | : Tiled Fill : | + * | : : | + * |.....:.................................:.....| + * | : : | + * +---------------------------------------------+ + * * * @param aContext The Thebes context to draw the image to. - * @param aFilter The filter to be used if we're scaling the image. - * @param aUserSpaceToImageSpace The transformation from user space (e.g., - * appunits) to image space. - * @param aFill The area in the context to draw pixels to. When aFlags includes - * FLAG_CLAMP, the image will be extended to this area by clampling - * image sample coordinates. Otherwise, the image will be - * automatically tiled as necessary. - * @param aSubimage The area of the image, in pixels, that we are allowed to - * sample from. - * @param aViewportSize - * The size (in CSS pixels) of the viewport that would be available - * for the full image to occupy, if we were drawing the full image. - * (Note that we might not actually be drawing the full image -- we - * might be restricted by aSubimage -- but we still need the full - * image's viewport-size in order for SVG images with the "viewBox" - * attribute to position their content correctly.) - * @param aSVGContext If non-null, SVG-related rendering context such as - * overridden attributes on the image document's root - * node. Ignored for raster images. + * @param aSize The size to which the image should be scaled before drawing. + * This requirement may be satisfied using HQ scaled frames, + * selecting from different resolution layers, drawing at a + * higher DPI, or just performing additional scaling on the + * graphics context. Callers can use optimalImageSizeForDest() + * to determine the best choice for this parameter if they have + * no special size requirements. + * @param aRegion The region in tiled image space which will be drawn onto the + * graphics context. aRegion is in the coordinate space of the + * image after it has been scaled to aSize - that is, the image + * is scaled first, and then aRegion is applied. When aFlags + * includes FLAG_CLAMP, the image will be extended to this area + * by clamping image sample coordinates. Otherwise, the image + * will be automatically tiled as necessary. aRegion can also + * optionally contain a second region which restricts the set of + * pixels we're allowed to sample from when drawing; this is + * only of use to callers which need to draw with pixel snapping. * @param aWhichFrame Frame specifier of the FRAME_* variety. + * @param aFilter The filter to be used if we're scaling the image. + * @param aSVGContext If specified, SVG-related rendering context, such as + * overridden attributes on the image document's root + * node, and the size of the viewport that the full image + * would occupy. Ignored for raster images. * @param aFlags Flags of the FLAG_* variety */ [noscript] void draw(in gfxContext aContext, - in gfxGraphicsFilter aFilter, - [const] in gfxMatrix aUserSpaceToImageSpace, - [const] in gfxRect aFill, - [const] in nsIntRect aSubimage, - [const] in nsIntSize aViewportSize, - [const] in SVGImageContext aSVGContext, + [const] in nsIntSize aSize, + [const] in ImageRegion aRegion, in uint32_t aWhichFrame, + in gfxGraphicsFilter aFilter, + [const] in MaybeSVGImageContext aSVGContext, in uint32_t aFlags); /* diff --git a/image/src/ClippedImage.cpp b/image/src/ClippedImage.cpp index 195287bb8c2..767e3a5d382 100644 --- a/image/src/ClippedImage.cpp +++ b/image/src/ClippedImage.cpp @@ -3,7 +3,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include // Workaround for bug in VS10; see bug 981264. #include +#include #include "gfxDrawable.h" #include "gfxPlatform.h" @@ -11,17 +13,20 @@ #include "mozilla/gfx/2D.h" #include "mozilla/RefPtr.h" -#include "ClippedImage.h" +#include "ImageRegion.h" #include "Orientation.h" #include "SVGImageContext.h" +#include "ClippedImage.h" namespace mozilla { using namespace gfx; using layers::LayerManager; using layers::ImageContainer; +using std::make_pair; using std::modf; +using std::pair; namespace image { @@ -29,12 +34,12 @@ class ClippedImageCachedSurface { public: ClippedImageCachedSurface(TemporaryRef aSurface, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const Maybe& aSVGContext, float aFrame, uint32_t aFlags) : mSurface(aSurface) - , mViewportSize(aViewportSize) + , mSize(aSize) , mFrame(aFrame) , mFlags(aFlags) { @@ -44,15 +49,13 @@ public: } } - bool Matches(const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + bool Matches(const nsIntSize& aSize, + const Maybe& aSVGContext, float aFrame, uint32_t aFlags) { - bool matchesSVGContext = (!aSVGContext && !mSVGContext) || - (*aSVGContext == *mSVGContext); - return mViewportSize == aViewportSize && - matchesSVGContext && + return mSize == aSize && + mSVGContext == aSVGContext && mFrame == aFrame && mFlags == aFlags; } @@ -62,25 +65,23 @@ public: } private: - RefPtr mSurface; - const nsIntSize mViewportSize; - Maybe mSVGContext; - const float mFrame; - const uint32_t mFlags; + RefPtr mSurface; + const nsIntSize mSize; + Maybe mSVGContext; + const float mFrame; + const uint32_t mFlags; }; class DrawSingleTileCallback : public gfxDrawingCallback { public: DrawSingleTileCallback(ClippedImage* aImage, - const nsIntRect& aClip, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const Maybe& aSVGContext, uint32_t aWhichFrame, uint32_t aFlags) : mImage(aImage) - , mClip(aClip) - , mViewportSize(aViewportSize) + , mSize(aSize) , mSVGContext(aSVGContext) , mWhichFrame(aWhichFrame) , mFlags(aFlags) @@ -93,21 +94,24 @@ public: const GraphicsFilter& aFilter, const gfxMatrix& aTransform) { + MOZ_ASSERT(aTransform.IsIdentity(), + "Caller is probably CreateSamplingRestrictedDrawable, " + "which should not happen"); + // Draw the image. |gfxCallbackDrawable| always calls this function with // arguments that guarantee we never tile. - mImage->DrawSingleTile(aContext, aFilter, aTransform, aFillRect, mClip, - mViewportSize, mSVGContext, mWhichFrame, mFlags); + mImage->DrawSingleTile(aContext, mSize, ImageRegion::Create(aFillRect), + mWhichFrame, aFilter, mSVGContext, mFlags); return true; } private: - nsRefPtr mImage; - const nsIntRect mClip; - const nsIntSize mViewportSize; - const SVGImageContext* mSVGContext; - const uint32_t mWhichFrame; - const uint32_t mFlags; + nsRefPtr mImage; + const nsIntSize mSize; + const Maybe& mSVGContext; + const uint32_t mWhichFrame; + const uint32_t mFlags; }; ClippedImage::ClippedImage(Image* aImage, @@ -220,12 +224,12 @@ NS_IMETHODIMP_(TemporaryRef) ClippedImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) { - return GetFrameInternal(mClip.Size(), nullptr, aWhichFrame, aFlags); + return GetFrameInternal(mClip.Size(), Nothing(), aWhichFrame, aFlags); } TemporaryRef -ClippedImage::GetFrameInternal(const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, +ClippedImage::GetFrameInternal(const nsIntSize& aSize, + const Maybe& aSVGContext, uint32_t aWhichFrame, uint32_t aFlags) { @@ -234,13 +238,13 @@ ClippedImage::GetFrameInternal(const nsIntSize& aViewportSize, } float frameToDraw = InnerImage()->GetFrameIndex(aWhichFrame); - if (!mCachedSurface || !mCachedSurface->Matches(aViewportSize, + if (!mCachedSurface || !mCachedSurface->Matches(aSize, aSVGContext, frameToDraw, aFlags)) { // Create a surface to draw into. RefPtr target = gfxPlatform::GetPlatform()-> - CreateOffscreenContentDrawTarget(IntSize(mClip.width, mClip.height), + CreateOffscreenContentDrawTarget(IntSize(aSize.width, aSize.height), SurfaceFormat::B8G8R8A8); if (!target) { NS_ERROR("Could not create a DrawTarget"); @@ -251,22 +255,20 @@ ClippedImage::GetFrameInternal(const nsIntSize& aViewportSize, // Create our callback. nsRefPtr drawTileCallback = - new DrawSingleTileCallback(this, mClip, aViewportSize, aSVGContext, aWhichFrame, aFlags); + new DrawSingleTileCallback(this, aSize, aSVGContext, aWhichFrame, aFlags); nsRefPtr drawable = - new gfxCallbackDrawable(drawTileCallback, mClip.Size()); + new gfxCallbackDrawable(drawTileCallback, aSize); // Actually draw. The callback will end up invoking DrawSingleTile. - gfxRect imageRect(0, 0, mClip.width, mClip.height); - gfxUtils::DrawPixelSnapped(ctx, drawable, gfxMatrix(), - imageRect, imageRect, imageRect, imageRect, + gfxUtils::DrawPixelSnapped(ctx, drawable, aSize, + ImageRegion::Create(aSize), SurfaceFormat::B8G8R8A8, - GraphicsFilter::FILTER_FAST); + GraphicsFilter::FILTER_FAST, + imgIContainer::FLAG_CLAMP); // Cache the resulting surface. - mCachedSurface = new ClippedImageCachedSurface(target->Snapshot(), - aViewportSize, - aSVGContext, - frameToDraw, + mCachedSurface = new ClippedImageCachedSurface(target->Snapshot(), aSize, + aSVGContext, frameToDraw, aFlags); } @@ -291,123 +293,116 @@ ClippedImage::GetImageContainer(LayerManager* aManager, ImageContainer** _retval return NS_OK; } -bool -ClippedImage::MustCreateSurface(gfxContext* aContext, - const gfxMatrix& aTransform, - const gfxRect& aSourceRect, - const nsIntRect& aSubimage, - const uint32_t aFlags) const +static bool +MustCreateSurface(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, + const uint32_t aFlags) { - gfxRect gfxImageRect(0, 0, mClip.width, mClip.height); - nsIntRect intImageRect(0, 0, mClip.width, mClip.height); - bool willTile = !gfxImageRect.Contains(aSourceRect) && + gfxRect imageRect(0, 0, aSize.width, aSize.height); + bool willTile = !imageRect.Contains(aRegion.Rect()) && !(aFlags & imgIContainer::FLAG_CLAMP); - bool willResample = (aContext->CurrentMatrix().HasNonIntegerTranslation() || - aTransform.HasNonIntegerTranslation()) && - (willTile || !aSubimage.Contains(intImageRect)); + bool willResample = aContext->CurrentMatrix().HasNonIntegerTranslation() && + (willTile || !aRegion.RestrictionContains(imageRect)); return willTile || willResample; } NS_IMETHODIMP ClippedImage::Draw(gfxContext* aContext, - GraphicsFilter aFilter, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFill, - const nsIntRect& aSubimage, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t aWhichFrame, + GraphicsFilter aFilter, + const Maybe& aSVGContext, uint32_t aFlags) { if (!ShouldClip()) { - return InnerImage()->Draw(aContext, aFilter, aUserSpaceToImageSpace, - aFill, aSubimage, aViewportSize, aSVGContext, - aWhichFrame, aFlags); + return InnerImage()->Draw(aContext, aSize, aRegion, aWhichFrame, + aFilter, aSVGContext, aFlags); } // Check for tiling. If we need to tile then we need to create a // gfxCallbackDrawable to handle drawing for us. - gfxRect sourceRect = aUserSpaceToImageSpace.Transform(aFill); - if (MustCreateSurface(aContext, aUserSpaceToImageSpace, sourceRect, aSubimage, aFlags)) { + if (MustCreateSurface(aContext, aSize, aRegion, aFlags)) { // Create a temporary surface containing a single tile of this image. // GetFrame will call DrawSingleTile internally. RefPtr surface = - GetFrameInternal(aViewportSize, aSVGContext, aWhichFrame, aFlags); + GetFrameInternal(aSize, aSVGContext, aWhichFrame, aFlags); NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); // Create a drawable from that surface. nsRefPtr drawable = - new gfxSurfaceDrawable(surface, gfxIntSize(mClip.width, mClip.height)); + new gfxSurfaceDrawable(surface, aSize); // Draw. - gfxRect imageRect(0, 0, mClip.width, mClip.height); - gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height); - gfxUtils::DrawPixelSnapped(aContext, drawable, aUserSpaceToImageSpace, - subimage, sourceRect, imageRect, aFill, + gfxUtils::DrawPixelSnapped(aContext, drawable, aSize, aRegion, SurfaceFormat::B8G8R8A8, aFilter); return NS_OK; } - // Determine the appropriate subimage for the inner image. - nsIntRect innerSubimage(aSubimage); - innerSubimage.MoveBy(mClip.x, mClip.y); - innerSubimage.Intersect(mClip); - - return DrawSingleTile(aContext, aFilter, aUserSpaceToImageSpace, aFill, innerSubimage, - aViewportSize, aSVGContext, aWhichFrame, aFlags); + return DrawSingleTile(aContext, aSize, aRegion, aWhichFrame, + aFilter, aSVGContext, aFlags); } -gfxFloat -ClippedImage::ClampFactor(const gfxFloat aToClamp, const int aReference) const +static SVGImageContext +UnclipViewport(const SVGImageContext& aOldContext, + const pair& aInnerAndClipSize) { - return aToClamp > aReference ? aReference / aToClamp - : 1.0; + nsIntSize innerSize(aInnerAndClipSize.first); + nsIntSize clipSize(aInnerAndClipSize.second); + + // Map the viewport to the inner image. (Note that we don't take the aSize + // parameter of Draw into account, just the clipping region.) + nsIntSize vSize(aOldContext.GetViewportSize()); + vSize.width = ceil(vSize.width * double(innerSize.width) / clipSize.width); + vSize.height = ceil(vSize.height * double(innerSize.height) / clipSize.height); + + return SVGImageContext(vSize, + aOldContext.GetPreserveAspectRatio()); } nsresult ClippedImage::DrawSingleTile(gfxContext* aContext, - GraphicsFilter aFilter, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFill, - const nsIntRect& aSubimage, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t aWhichFrame, + GraphicsFilter aFilter, + const Maybe& aSVGContext, uint32_t aFlags) { - MOZ_ASSERT(!MustCreateSurface(aContext, aUserSpaceToImageSpace, - aUserSpaceToImageSpace.Transform(aFill), - aSubimage - nsIntPoint(mClip.x, mClip.y), aFlags), - "DrawSingleTile shouldn't need to create a surface"); + MOZ_ASSERT(!MustCreateSurface(aContext, aSize, aRegion, aFlags), + "Shouldn't need to create a surface"); - // Make the viewport reflect the original image's size. - nsIntSize viewportSize(aViewportSize); - int32_t imgWidth, imgHeight; - if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) && - NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) { - viewportSize = nsIntSize(imgWidth, imgHeight); + gfxRect clip(mClip.x, mClip.y, mClip.width, mClip.height); + nsIntSize size(aSize), innerSize(aSize); + if (NS_SUCCEEDED(InnerImage()->GetWidth(&innerSize.width)) && + NS_SUCCEEDED(InnerImage()->GetHeight(&innerSize.height))) { + double scaleX = aSize.width / clip.width; + double scaleY = aSize.height / clip.height; + + // Map the clip and size to the scale requested by the caller. + clip.Scale(scaleX, scaleY); + size = innerSize; + size.Scale(scaleX, scaleY); } else { MOZ_ASSERT(false, "If ShouldClip() led us to draw then we should never get here"); } - // Add a translation to the transform to reflect the clipping region. - gfxMatrix transform = - aUserSpaceToImageSpace * gfxMatrix::Translation(mClip.x, mClip.y); + // We restrict our drawing to only the clipping region, and translate so that + // the clipping region is placed at the position the caller expects. + ImageRegion region(aRegion); + region.MoveBy(clip.x, clip.y); + region = region.Intersect(clip); - // "Clamp the source rectangle" to the clipping region's width and height. - // Really, this means modifying the transform to get the results we want. - gfxRect sourceRect = transform.Transform(aFill); - if (sourceRect.width > mClip.width || sourceRect.height > mClip.height) { - gfxMatrix clampSource = gfxMatrix::Translation(sourceRect.TopLeft()); - clampSource.Scale(ClampFactor(sourceRect.width, mClip.width), - ClampFactor(sourceRect.height, mClip.height)); - clampSource.Translate(-sourceRect.TopLeft()); - transform *= clampSource; - } + gfxContextMatrixAutoSaveRestore saveMatrix(aContext); + aContext->Multiply(gfxMatrix::Translation(-clip.x, -clip.y)); - return InnerImage()->Draw(aContext, aFilter, transform, aFill, aSubimage, - viewportSize, aSVGContext, aWhichFrame, aFlags); + return InnerImage()->Draw(aContext, size, region, + aWhichFrame, aFilter, + aSVGContext.map(UnclipViewport, + make_pair(innerSize, mClip.Size())), + aFlags); } NS_IMETHODIMP diff --git a/image/src/ClippedImage.h b/image/src/ClippedImage.h index 4b03b233bf2..dcf46908056 100644 --- a/image/src/ClippedImage.h +++ b/image/src/ClippedImage.h @@ -42,13 +42,11 @@ public: NS_IMETHOD GetImageContainer(layers::LayerManager* aManager, layers::ImageContainer** _retval) MOZ_OVERRIDE; NS_IMETHOD Draw(gfxContext* aContext, - GraphicsFilter aFilter, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFill, - const nsIntRect& aSubimage, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t aWhichFrame, + GraphicsFilter aFilter, + const Maybe& aSVGContext, uint32_t aFlags) MOZ_OVERRIDE; NS_IMETHOD RequestDiscard() MOZ_OVERRIDE; NS_IMETHOD_(Orientation) GetOrientation() MOZ_OVERRIDE; @@ -65,25 +63,17 @@ protected: private: TemporaryRef - GetFrameInternal(const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + GetFrameInternal(const nsIntSize& aSize, + const Maybe& aSVGContext, uint32_t aWhichFrame, uint32_t aFlags); bool ShouldClip(); - bool MustCreateSurface(gfxContext* aContext, - const gfxMatrix& aTransform, - const gfxRect& aSourceRect, - const nsIntRect& aSubimage, - const uint32_t aFlags) const; - gfxFloat ClampFactor(const gfxFloat aToClamp, const int aReference) const; nsresult DrawSingleTile(gfxContext* aContext, - GraphicsFilter aFilter, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFill, - const nsIntRect& aSubimage, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t aWhichFrame, + GraphicsFilter aFilter, + const Maybe& aSVGContext, uint32_t aFlags); // If we are forced to draw a temporary surface, we cache it here. diff --git a/image/src/DynamicImage.cpp b/image/src/DynamicImage.cpp index 86e1eda8419..15f1befa83f 100644 --- a/image/src/DynamicImage.cpp +++ b/image/src/DynamicImage.cpp @@ -8,7 +8,9 @@ #include "gfxUtils.h" #include "mozilla/gfx/2D.h" #include "mozilla/RefPtr.h" +#include "ImageRegion.h" #include "Orientation.h" +#include "SVGImageContext.h" #include "mozilla/MemoryReporting.h" @@ -217,10 +219,9 @@ DynamicImage::GetFrame(uint32_t aWhichFrame, SurfaceFormat::B8G8R8A8); nsRefPtr context = new gfxContext(dt); - nsresult rv = Draw(context, GraphicsFilter::FILTER_NEAREST, gfxMatrix(), - gfxRect(0, 0, size.width, size.height), - nsIntRect(0, 0, size.width, size.height), - size, nullptr, aWhichFrame, aFlags); + nsresult rv = Draw(context, size, ImageRegion::Create(size), + aWhichFrame, GraphicsFilter::FILTER_NEAREST, + Nothing(), aFlags); NS_ENSURE_SUCCESS(rv, nullptr); return dt->Snapshot(); @@ -243,23 +244,34 @@ DynamicImage::GetImageContainer(LayerManager* aManager, ImageContainer** _retval NS_IMETHODIMP DynamicImage::Draw(gfxContext* aContext, - GraphicsFilter aFilter, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFill, - const nsIntRect& aSubimage, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t aWhichFrame, + GraphicsFilter aFilter, + const Maybe& aSVGContext, uint32_t aFlags) { + MOZ_ASSERT(!aSize.IsEmpty(), "Unexpected empty size"); + gfxIntSize drawableSize(mDrawable->Size()); - gfxRect imageRect(0, 0, drawableSize.width, drawableSize.height); - gfxRect sourceRect = aUserSpaceToImageSpace.TransformBounds(aFill); - gfxUtils::DrawPixelSnapped(aContext, mDrawable, aUserSpaceToImageSpace, - aSubimage, sourceRect, imageRect, aFill, - SurfaceFormat::B8G8R8A8, aFilter, aFlags); + if (aSize == drawableSize) { + gfxUtils::DrawPixelSnapped(aContext, mDrawable, drawableSize, aRegion, + SurfaceFormat::B8G8R8A8, aFilter); + return NS_OK; + } + gfxSize scale(double(aSize.width) / drawableSize.width, + double(aSize.height) / drawableSize.height); + + ImageRegion region(aRegion); + region.Scale(1.0 / scale.width, 1.0 / scale.height); + + gfxContextMatrixAutoSaveRestore saveMatrix(aContext); + aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height)); + + gfxUtils::DrawPixelSnapped(aContext, mDrawable, drawableSize, region, + SurfaceFormat::B8G8R8A8, aFilter); return NS_OK; } diff --git a/image/src/FrozenImage.cpp b/image/src/FrozenImage.cpp index add73dd38fe..923b58a5d0f 100644 --- a/image/src/FrozenImage.cpp +++ b/image/src/FrozenImage.cpp @@ -72,18 +72,15 @@ FrozenImage::GetImageContainer(layers::LayerManager* aManager, NS_IMETHODIMP FrozenImage::Draw(gfxContext* aContext, - GraphicsFilter aFilter, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFill, - const nsIntRect& aSubimage, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t /* aWhichFrame - ignored */, + GraphicsFilter aFilter, + const Maybe& aSVGContext, uint32_t aFlags) { - return InnerImage()->Draw(aContext, aFilter, aUserSpaceToImageSpace, - aFill, aSubimage, aViewportSize, aSVGContext, - FRAME_FIRST, aFlags); + return InnerImage()->Draw(aContext, aSize, aRegion, FRAME_FIRST, + aFilter, aSVGContext, aFlags); } NS_IMETHODIMP_(void) diff --git a/image/src/FrozenImage.h b/image/src/FrozenImage.h index 8fc99faf573..32b0f466408 100644 --- a/image/src/FrozenImage.h +++ b/image/src/FrozenImage.h @@ -42,13 +42,11 @@ public: NS_IMETHOD GetImageContainer(layers::LayerManager* aManager, layers::ImageContainer** _retval) MOZ_OVERRIDE; NS_IMETHOD Draw(gfxContext* aContext, - GraphicsFilter aFilter, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFill, - const nsIntRect& aSubimage, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t aWhichFrame, + GraphicsFilter aFilter, + const Maybe& aSVGContext, uint32_t aFlags) MOZ_OVERRIDE; NS_IMETHOD_(void) RequestRefresh(const TimeStamp& aTime) MOZ_OVERRIDE; NS_IMETHOD GetAnimationMode(uint16_t* aAnimationMode) MOZ_OVERRIDE; diff --git a/image/src/ImageRegion.h b/image/src/ImageRegion.h new file mode 100644 index 00000000000..c66d0e1f858 --- /dev/null +++ b/image/src/ImageRegion.h @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_IMAGELIB_IMAGE_REGION_H_ +#define MOZILLA_IMAGELIB_IMAGE_REGION_H_ + +#include "gfxRect.h" + +namespace mozilla { +namespace image { + +/** + * An axis-aligned rectangle in tiled image space, with an optional sampling + * restriction rect. The drawing code ensures that if a sampling restriction + * rect is present, any pixels sampled during the drawing process are found + * within that rect. + * + * The sampling restriction rect exists primarily for callers which perform + * pixel snapping. Other callers should generally use one of the Create() + * overloads. + */ +class ImageRegion +{ +public: + static ImageRegion Empty() + { + return ImageRegion(gfxRect()); + } + + static ImageRegion Create(const gfxRect& aRect) + { + return ImageRegion(aRect); + } + + static ImageRegion Create(const gfxSize& aSize) + { + return ImageRegion(gfxRect(0, 0, aSize.width, aSize.height)); + } + + static ImageRegion Create(const nsIntSize& aSize) + { + return ImageRegion(gfxRect(0, 0, aSize.width, aSize.height)); + } + + static ImageRegion CreateWithSamplingRestriction(const gfxRect& aRect, + const gfxRect& aRestriction) + { + return ImageRegion(aRect, aRestriction); + } + + bool IsRestricted() const { return mIsRestricted; } + const gfxRect& Rect() const { return mRect; } + + const gfxRect& Restriction() const + { + MOZ_ASSERT(mIsRestricted); + return mRestriction; + } + + bool RestrictionContains(const gfxRect& aRect) const + { + if (!mIsRestricted) { + return true; + } + return mRestriction.Contains(aRect); + } + + ImageRegion Intersect(const gfxRect& aRect) const + { + if (mIsRestricted) { + return CreateWithSamplingRestriction(aRect.Intersect(mRect), + aRect.Intersect(mRestriction)); + } + return Create(aRect.Intersect(mRect)); + } + + gfxRect IntersectAndRestrict(const gfxRect& aRect) const + { + gfxRect intersection = mRect.Intersect(aRect); + if (mIsRestricted) { + intersection = mRestriction.Intersect(intersection); + } + return intersection; + } + + void MoveBy(gfxFloat dx, gfxFloat dy) + { + mRect.MoveBy(dx, dy); + if (mIsRestricted) { + mRestriction.MoveBy(dx, dy); + } + } + + void Scale(gfxFloat sx, gfxFloat sy) + { + mRect.Scale(sx, sy); + if (mIsRestricted) { + mRestriction.Scale(sx, sy); + } + } + + void TransformBy(const gfxMatrix& aMatrix) + { + mRect = aMatrix.Transform(mRect); + if (mIsRestricted) { + mRestriction = aMatrix.Transform(mRestriction); + } + } + + void TransformBoundsBy(const gfxMatrix& aMatrix) + { + mRect = aMatrix.TransformBounds(mRect); + if (mIsRestricted) { + mRestriction = aMatrix.TransformBounds(mRestriction); + } + } + + ImageRegion operator-(const gfxPoint& aPt) const + { + if (mIsRestricted) { + return CreateWithSamplingRestriction(mRect - aPt, mRestriction - aPt); + } + return Create(mRect - aPt); + } + + ImageRegion operator+(const gfxPoint& aPt) const + { + if (mIsRestricted) { + return CreateWithSamplingRestriction(mRect + aPt, mRestriction + aPt); + } + return Create(mRect + aPt); + } + + /* ImageRegion() : mIsRestricted(false) { } */ + +private: + explicit ImageRegion(const gfxRect& aRect) + : mRect(aRect) + , mIsRestricted(false) + { } + + ImageRegion(const gfxRect& aRect, const gfxRect& aRestriction) + : mRect(aRect) + , mRestriction(aRestriction) + , mIsRestricted(true) + { } + + gfxRect mRect; + gfxRect mRestriction; + bool mIsRestricted; +}; + +} // namespace image +} // namespace mozilla + +#endif diff --git a/image/src/ImageWrapper.cpp b/image/src/ImageWrapper.cpp index aff2d933e87..03d3589f0b2 100644 --- a/image/src/ImageWrapper.cpp +++ b/image/src/ImageWrapper.cpp @@ -222,18 +222,15 @@ ImageWrapper::GetImageContainer(LayerManager* aManager, ImageContainer** _retval NS_IMETHODIMP ImageWrapper::Draw(gfxContext* aContext, - GraphicsFilter aFilter, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFill, - const nsIntRect& aSubimage, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t aWhichFrame, + GraphicsFilter aFilter, + const Maybe& aSVGContext, uint32_t aFlags) { - return mInnerImage->Draw(aContext, aFilter, aUserSpaceToImageSpace, aFill, - aSubimage, aViewportSize, aSVGContext, aWhichFrame, - aFlags); + return mInnerImage->Draw(aContext, aSize, aRegion, aWhichFrame, + aFilter, aSVGContext, aFlags); } NS_IMETHODIMP diff --git a/image/src/OrientedImage.cpp b/image/src/OrientedImage.cpp index 86ecfaa7a4f..bec3a43fb1c 100644 --- a/image/src/OrientedImage.cpp +++ b/image/src/OrientedImage.cpp @@ -5,9 +5,12 @@ #include +#include "gfx2DGlue.h" #include "gfxDrawable.h" #include "gfxPlatform.h" #include "gfxUtils.h" +#include "ImageRegion.h" +#include "SVGImageContext.h" #include "OrientedImage.h" @@ -26,12 +29,24 @@ NS_IMPL_ISUPPORTS_INHERITED0(OrientedImage, ImageWrapper) nsIntRect OrientedImage::FrameRect(uint32_t aWhichFrame) { - if (mOrientation.SwapsWidthAndHeight()) { - nsIntRect innerRect = InnerImage()->FrameRect(aWhichFrame); - return nsIntRect(innerRect.x, innerRect.y, innerRect.height, innerRect.width); - } else { - return InnerImage()->FrameRect(aWhichFrame); + nsresult rv; + + // Retrieve the frame rect of the inner image. + nsIntRect innerRect = InnerImage()->FrameRect(aWhichFrame); + if (mOrientation.IsIdentity()) { + return innerRect; } + + // Get the underlying image's dimensions. + nsIntSize size; + rv = InnerImage()->GetWidth(&size.width); + NS_ENSURE_SUCCESS(rv, innerRect); + rv = InnerImage()->GetHeight(&size.height); + NS_ENSURE_SUCCESS(rv, innerRect); + + // Transform the frame rect. + gfxRect finalRect = OrientationMatrix(size).TransformBounds(innerRect); + return nsIntRect(finalRect.x, finalRect.y, finalRect.width, finalRect.height); } NS_IMETHODIMP @@ -89,14 +104,10 @@ OrientedImage::GetFrame(uint32_t aWhichFrame, } // Get the underlying dimensions. - int32_t width, height; - rv = InnerImage()->GetWidth(&width); + gfxIntSize size; + rv = InnerImage()->GetWidth(&size.width); NS_ENSURE_SUCCESS(rv, nullptr); - rv = InnerImage()->GetHeight(&height); - NS_ENSURE_SUCCESS(rv, nullptr); - if (mOrientation.SwapsWidthAndHeight()) { - swap(width, height); - } + rv = InnerImage()->GetHeight(&size.height); NS_ENSURE_SUCCESS(rv, nullptr); // Determine an appropriate format for the surface. @@ -110,7 +121,7 @@ OrientedImage::GetFrame(uint32_t aWhichFrame, // Create a surface to draw into. RefPtr target = gfxPlatform::GetPlatform()-> - CreateOffscreenContentDrawTarget(IntSize(width, height), surfaceFormat); + CreateOffscreenContentDrawTarget(ToIntSize(size), surfaceFormat); if (!target) { NS_ERROR("Could not create a DrawTarget"); return nullptr; @@ -122,13 +133,13 @@ OrientedImage::GetFrame(uint32_t aWhichFrame, InnerImage()->GetFrame(aWhichFrame, aFlags); NS_ENSURE_TRUE(innerSurface, nullptr); nsRefPtr drawable = - new gfxSurfaceDrawable(innerSurface, gfxIntSize(width, height)); + new gfxSurfaceDrawable(innerSurface, size); // Draw. nsRefPtr ctx = new gfxContext(target); - gfxRect imageRect(0, 0, width, height); - gfxUtils::DrawPixelSnapped(ctx, drawable, OrientationMatrix(nsIntSize(width, height)), - imageRect, imageRect, imageRect, imageRect, + ctx->Multiply(OrientationMatrix(size)); + gfxUtils::DrawPixelSnapped(ctx, drawable, size, + ImageRegion::Create(size), surfaceFormat, GraphicsFilter::FILTER_FAST); return target->Snapshot(); @@ -151,104 +162,151 @@ OrientedImage::GetImageContainer(LayerManager* aManager, ImageContainer** _retva return NS_OK; } -gfxMatrix -OrientedImage::OrientationMatrix(const nsIntSize& aViewportSize) +struct MatrixBuilder { - gfxMatrix matrix; + MatrixBuilder(bool aInvert) : mInvert(aInvert) { } - int32_t width, height; - if (InnerImage()->GetType() == imgIContainer::TYPE_VECTOR) { - width = mOrientation.SwapsWidthAndHeight() ? aViewportSize.height : aViewportSize.width; - height = mOrientation.SwapsWidthAndHeight() ? aViewportSize.width : aViewportSize.height; + gfxMatrix Build() { return mMatrix; } + + void Scale(gfxFloat aX, gfxFloat aY) { + if (mInvert) { + mMatrix *= gfxMatrix::Scaling(1.0 / aX, 1.0 / aY); } else { - nsresult rv = InnerImage()->GetWidth(&width); - rv = NS_FAILED(rv) ? rv : InnerImage()->GetHeight(&height); - if (NS_FAILED(rv)) { - // Fall back to identity if the width and height aren't available. - return matrix; + mMatrix.Scale(aX, aY); + } + } + + void Rotate(gfxFloat aPhi) { + if (mInvert) { + mMatrix *= gfxMatrix::Rotation(-aPhi); + } else { + mMatrix.Rotate(aPhi); + } + } + + void Translate(gfxPoint aDelta) { + if (mInvert) { + mMatrix *= gfxMatrix::Translation(-aDelta); + } else { + mMatrix.Translate(aDelta); + } + } + +private: + gfxMatrix mMatrix; + bool mInvert; +}; + +/* + * OrientationMatrix() computes a matrix that applies the rotation and + * reflection specified by mOrientation, or that matrix's inverse if aInvert is + * true. + * + * @param aSize The scaled size of the inner image. (When outside code specifies + * the scaled size, as with imgIContainer::Draw and its aSize + * parameter, it's necessary to swap the width and height if + * mOrientation.SwapsWidthAndHeight() is true.) + * @param aInvert If true, compute the inverse of the orientation matrix. Prefer + * this approach to OrientationMatrix(..).Invert(), because it's + * more numerically accurate. + */ +gfxMatrix +OrientedImage::OrientationMatrix(const nsIntSize& aSize, + bool aInvert /* = false */) +{ + MatrixBuilder builder(aInvert); + + // Apply reflection, if present. (This logically happens second, but we + // apply it first because these transformations are all premultiplied.) A + // translation is necessary to place the image back in the first quadrant. + switch (mOrientation.flip) { + case Flip::Unflipped: + break; + case Flip::Horizontal: + if (mOrientation.SwapsWidthAndHeight()) { + builder.Translate(gfxPoint(aSize.height, 0)); + } else { + builder.Translate(gfxPoint(aSize.width, 0)); } - } + builder.Scale(-1.0, 1.0); + break; + default: + MOZ_ASSERT(false, "Invalid flip value"); + } - // Apply reflection, if present. (This logically happens second, but we - // apply it first because these transformations are all premultiplied.) A - // translation is necessary to place the image back in the first quadrant. - switch (mOrientation.flip) { - case Flip::Unflipped: - break; - case Flip::Horizontal: - if (mOrientation.SwapsWidthAndHeight()) { - // In the coordinate system after the rotation the reflection we want - // is across the x-axis rather than the y-axis. - matrix.Translate(gfxPoint(0, height)); - matrix.Scale(1.0, -1.0); - } else { - matrix.Translate(gfxPoint(width, 0)); - matrix.Scale(-1.0, 1.0); - } - break; - default: - MOZ_ASSERT(false, "Invalid flip value"); - } + // Apply rotation, if present. Again, a translation is used to place the + // image back in the first quadrant. + switch (mOrientation.rotation) { + case Angle::D0: + break; + case Angle::D90: + builder.Translate(gfxPoint(aSize.height, 0)); + builder.Rotate(-1.5 * M_PI); + break; + case Angle::D180: + builder.Translate(gfxPoint(aSize.width, aSize.height)); + builder.Rotate(-1.0 * M_PI); + break; + case Angle::D270: + builder.Translate(gfxPoint(0, aSize.width)); + builder.Rotate(-0.5 * M_PI); + break; + default: + MOZ_ASSERT(false, "Invalid rotation value"); + } - // Apply rotation, if present. Again, a translation is used to place the - // image back in the first quadrant. - switch (mOrientation.rotation) { - case Angle::D0: - break; - case Angle::D90: - matrix.Translate(gfxPoint(0, height)); - matrix.Rotate(-0.5 * M_PI); - break; - case Angle::D180: - matrix.Translate(gfxPoint(width, height)); - matrix.Rotate(-1.0 * M_PI); - break; - case Angle::D270: - matrix.Translate(gfxPoint(width, 0)); - matrix.Rotate(-1.5 * M_PI); - break; - default: - MOZ_ASSERT(false, "Invalid rotation value"); - } + return builder.Build(); +} - return matrix; +static SVGImageContext +OrientViewport(const SVGImageContext& aOldContext, + const Orientation& aOrientation) +{ + nsIntSize viewportSize(aOldContext.GetViewportSize()); + if (aOrientation.SwapsWidthAndHeight()) { + swap(viewportSize.width, viewportSize.height); + } + return SVGImageContext(viewportSize, + aOldContext.GetPreserveAspectRatio()); } NS_IMETHODIMP OrientedImage::Draw(gfxContext* aContext, - GraphicsFilter aFilter, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFill, - const nsIntRect& aSubimage, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t aWhichFrame, + GraphicsFilter aFilter, + const Maybe& aSVGContext, uint32_t aFlags) { if (mOrientation.IsIdentity()) { - return InnerImage()->Draw(aContext, aFilter, aUserSpaceToImageSpace, - aFill, aSubimage, aViewportSize, aSVGContext, - aWhichFrame, aFlags); + return InnerImage()->Draw(aContext, aSize, aRegion, + aWhichFrame, aFilter, aSVGContext, aFlags); } - // Update the matrix to reorient the image. - gfxMatrix matrix(OrientationMatrix(aViewportSize)); - gfxMatrix userSpaceToImageSpace(aUserSpaceToImageSpace * matrix); - - // Update the subimage. - gfxRect gfxSubimage(matrix.TransformBounds(gfxRect(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height))); - nsIntRect subimage(gfxSubimage.x, gfxSubimage.y, gfxSubimage.width, gfxSubimage.height); - - // Update the viewport size. (This could be done using TransformBounds but - // since it's only a size a swap is enough.) - nsIntSize viewportSize(aViewportSize); + // Update the image size to match the image's coordinate system. (This could + // be done using TransformBounds but since it's only a size a swap is enough.) + nsIntSize size(aSize); if (mOrientation.SwapsWidthAndHeight()) { - swap(viewportSize.width, viewportSize.height); + swap(size.width, size.height); } - return InnerImage()->Draw(aContext, aFilter, userSpaceToImageSpace, - aFill, subimage, viewportSize, aSVGContext, - aWhichFrame, aFlags); + // Update the matrix so that we transform the image into the orientation + // expected by the caller before drawing. + gfxMatrix matrix(OrientationMatrix(size)); + gfxContextMatrixAutoSaveRestore saveMatrix(aContext); + aContext->Multiply(matrix); + + // The region is already in the orientation expected by the caller, but we + // need it to be in the image's coordinate system, so we transform it using + // the inverse of the orientation matrix. + gfxMatrix inverseMatrix(OrientationMatrix(size, /* aInvert = */ true)); + ImageRegion region(aRegion); + region.TransformBoundsBy(inverseMatrix); + + return InnerImage()->Draw(aContext, size, region, aWhichFrame, aFilter, + aSVGContext.map(OrientViewport, mOrientation), + aFlags); } nsIntSize @@ -277,19 +335,16 @@ OrientedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) return rect; } - int32_t width, height; - nsresult rv = InnerImage()->GetWidth(&width); - rv = NS_FAILED(rv) ? rv : InnerImage()->GetHeight(&height); + nsIntSize innerSize; + nsresult rv = InnerImage()->GetWidth(&innerSize.width); + rv = NS_FAILED(rv) ? rv : InnerImage()->GetHeight(&innerSize.height); if (NS_FAILED(rv)) { // Fall back to identity if the width and height aren't available. return rect; } // Transform the invalidation rect into the correct orientation. - gfxMatrix matrix(OrientationMatrix(nsIntSize(width, height))); - if (!matrix.Invert()) { - return nsIntRect(); - } + gfxMatrix matrix(OrientationMatrix(innerSize, /* aInvert = */ true)); gfxRect invalidRect(matrix.TransformBounds(gfxRect(rect.x, rect.y, rect.width, rect.height))); invalidRect.RoundOut(); diff --git a/image/src/OrientedImage.h b/image/src/OrientedImage.h index 880bf446ae0..128d5002d88 100644 --- a/image/src/OrientedImage.h +++ b/image/src/OrientedImage.h @@ -39,13 +39,11 @@ public: NS_IMETHOD GetImageContainer(layers::LayerManager* aManager, layers::ImageContainer** _retval) MOZ_OVERRIDE; NS_IMETHOD Draw(gfxContext* aContext, - GraphicsFilter aFilter, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFill, - const nsIntRect& aSubimage, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t aWhichFrame, + GraphicsFilter aFilter, + const Maybe& aSVGContext, uint32_t aFlags) MOZ_OVERRIDE; NS_IMETHOD_(nsIntRect) GetImageSpaceInvalidationRect(const nsIntRect& aRect) MOZ_OVERRIDE; nsIntSize OptimalImageSizeForDest(const gfxSize& aDest, @@ -61,7 +59,7 @@ protected: virtual ~OrientedImage() { } - gfxMatrix OrientationMatrix(const nsIntSize& aViewportSize); + gfxMatrix OrientationMatrix(const nsIntSize& aSize, bool aInvert = false); private: Orientation mOrientation; diff --git a/image/src/RasterImage.cpp b/image/src/RasterImage.cpp index 56e890146e7..65b831841f0 100644 --- a/image/src/RasterImage.cpp +++ b/image/src/RasterImage.cpp @@ -18,6 +18,7 @@ #include "prenv.h" #include "prsystem.h" #include "ImageContainer.h" +#include "ImageRegion.h" #include "Layers.h" #include "nsPresContext.h" #include "nsIThreadPool.h" @@ -185,21 +186,19 @@ DiscardingEnabled() class ScaleRequest { public: - ScaleRequest(RasterImage* aImage, const gfxSize& aScale, imgFrame* aSrcFrame) - : scale(aScale) + ScaleRequest(RasterImage* aImage, + const nsIntSize& aSize, + imgFrame* aSrcFrame) + : dstSize(aSize) , dstLocked(false) , done(false) , stopped(false) { MOZ_ASSERT(!aSrcFrame->GetIsPaletted()); - MOZ_ASSERT(aScale.width > 0 && aScale.height > 0); + MOZ_ASSERT(aSize.width > 0 && aSize.height > 0); weakImage = aImage; srcRect = aSrcFrame->GetRect(); - - nsIntRect dstRect = srcRect; - dstRect.ScaleRoundOut(scale.width, scale.height); - dstSize = dstRect.Size(); } // This can only be called on the main thread. @@ -277,11 +276,10 @@ public: RefPtr dstSurface; // Below are the values that may be touched on the scaling thread. - gfxSize scale; uint8_t* srcData; uint8_t* dstData; nsIntRect srcRect; - gfxIntSize dstSize; + nsIntSize dstSize; uint32_t srcStride; uint32_t dstStride; SurfaceFormat srcFormat; @@ -328,9 +326,11 @@ private: /* members */ class ScaleRunner : public nsRunnable { public: - ScaleRunner(RasterImage* aImage, const gfxSize& aScale, imgFrame* aSrcFrame) + ScaleRunner(RasterImage* aImage, + const nsIntSize& aSize, + imgFrame* aSrcFrame) { - nsAutoPtr request(new ScaleRequest(aImage, aScale, aSrcFrame)); + nsAutoPtr request(new ScaleRequest(aImage, aSize, aSrcFrame)); // Destination is unconditionally ARGB32 because that's what the scaler // outputs. @@ -2508,7 +2508,7 @@ RasterImage::SyncDecode() } bool -RasterImage::CanQualityScale(const gfxSize& scale) +RasterImage::CanQualityScale(const gfx::Size& scale) { // If target size is 1:1 with original, don't scale. if (scale.width == 1.0 && scale.height == 1.0) @@ -2525,8 +2525,7 @@ RasterImage::CanQualityScale(const gfxSize& scale) } bool -RasterImage::CanScale(GraphicsFilter aFilter, - gfxSize aScale, uint32_t aFlags) +RasterImage::CanScale(GraphicsFilter aFilter, gfx::Size aScale, uint32_t aFlags) { // The high-quality scaler requires Skia. #ifdef MOZ_ENABLE_SKIA @@ -2550,7 +2549,7 @@ void RasterImage::ScalingStart(ScaleRequest* request) { MOZ_ASSERT(request); - mScaleResult.scale = request->scale; + mScaleResult.scaledSize = request->dstSize; mScaleResult.status = SCALE_PENDING; mScaleRequest = request; } @@ -2573,7 +2572,7 @@ RasterImage::ScalingDone(ScaleRequest* request, ScaleStatus status) mScaleResult.status = SCALE_DONE; mScaleResult.frame = scaledFrame; - mScaleResult.scale = request->scale; + mScaleResult.scaledSize = request->dstSize; } else { mScaleResult.status = SCALE_INVALID; mScaleResult.frame = nullptr; @@ -2588,7 +2587,7 @@ RasterImage::ScalingDone(ScaleRequest* request, ScaleStatus status) } void -RasterImage::RequestScale(imgFrame* aFrame, gfxSize aScale) +RasterImage::RequestScale(imgFrame* aFrame, nsIntSize aSize) { // We can't store more than one scaled version of an image right now, so if // there's more than one instance of this image, bail. @@ -2607,7 +2606,7 @@ RasterImage::RequestScale(imgFrame* aFrame, gfxSize aScale) mScaleRequest->stopped = true; } - nsRefPtr runner = new ScaleRunner(this, aScale, aFrame); + nsRefPtr runner = new ScaleRunner(this, aSize, aFrame); if (runner->IsOK()) { if (!sScaleWorkerThread) { NS_NewNamedThread("Image Scaler", getter_AddRefs(sScaleWorkerThread)); @@ -2623,22 +2622,18 @@ RasterImage::RequestScale(imgFrame* aFrame, gfxSize aScale) bool RasterImage::DrawWithPreDownscaleIfNeeded(imgFrame *aFrame, gfxContext *aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, GraphicsFilter aFilter, - const gfxMatrix &aUserSpaceToImageSpace, - const gfxRect &aFill, - const nsIntRect &aSubimage, uint32_t aFlags) { imgFrame *frame = aFrame; nsIntRect framerect = frame->GetRect(); - gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace; - gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace; - if (!imageSpaceToUserSpace.Invert()) { - return false; - } - gfxSize scale = imageSpaceToUserSpace.ScaleFactors(true); - nsIntRect subimage = aSubimage; + + gfxContextMatrixAutoSaveRestore saveMatrix(aContext); RefPtr surf; + gfx::Size scale(double(aSize.width) / mSize.width, + double(aSize.height) / mSize.height); if (CanScale(aFilter, scale, aFlags) && !frame->IsSinglePixel()) { // If scale factor is still the same that we scaled for and @@ -2650,38 +2645,41 @@ RasterImage::DrawWithPreDownscaleIfNeeded(imgFrame *aFrame, // The solution is to cache more than one scaled image frame // for each RasterImage. bool needScaleReq; - if (mScaleResult.status == SCALE_DONE && mScaleResult.scale == scale) { + if (mScaleResult.status == SCALE_DONE && mScaleResult.scaledSize == aSize) { // Grab and hold the surface to make sure the OS didn't destroy it surf = mScaleResult.frame->GetSurface(); needScaleReq = !surf; if (surf) { frame = mScaleResult.frame; - userSpaceToImageSpace *= gfxMatrix::Scaling(scale.width, scale.height); - - // Since we're switching to a scaled image, we need to transform the - // area of the subimage to draw accordingly, since imgFrame::Draw() - // doesn't know about scaled frames. - subimage.ScaleRoundOut(scale.width, scale.height); } } else { needScaleReq = !(mScaleResult.status == SCALE_PENDING && - mScaleResult.scale == scale); + mScaleResult.scaledSize == aSize); } // If we're not waiting for exactly this result, and there's only one // instance of this image on this page, ask for a scale. if (needScaleReq) { - RequestScale(frame, scale); + RequestScale(frame, aSize); } } + ImageRegion region(aRegion); + + // By now we may have a frame with the requested size. If not, we need to + // adjust the drawing parameters accordingly. + nsIntSize finalFrameSize(frame->GetRect().Size()); + if (finalFrameSize != aSize) { + aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height)); + region.Scale(1.0 / scale.width, 1.0 / scale.height); + } + nsIntMargin padding(framerect.y, mSize.width - framerect.XMost(), mSize.height - framerect.YMost(), framerect.x); - return frame->Draw(aContext, aFilter, userSpaceToImageSpace, - aFill, padding, subimage, aFlags); + return frame->Draw(aContext, region, padding, aFilter, aFlags); } //****************************************************************************** @@ -2695,14 +2693,12 @@ RasterImage::DrawWithPreDownscaleIfNeeded(imgFrame *aFrame, * in uint32_t aWhichFrame, * in uint32_t aFlags); */ NS_IMETHODIMP -RasterImage::Draw(gfxContext *aContext, - GraphicsFilter aFilter, - const gfxMatrix &aUserSpaceToImageSpace, - const gfxRect &aFill, - const nsIntRect &aSubimage, - const nsIntSize& /*aViewportSize - ignored*/, - const SVGImageContext* /*aSVGContext - ignored*/, +RasterImage::Draw(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t aWhichFrame, + GraphicsFilter aFilter, + const Maybe& /*aSVGContext - ignored*/, uint32_t aFlags) { if (aWhichFrame > FRAME_MAX_VALUE) @@ -2773,9 +2769,8 @@ RasterImage::Draw(gfxContext *aContext, return NS_OK; // Getting the frame (above) touches the image and kicks off decoding } - bool drawn = DrawWithPreDownscaleIfNeeded(frame, aContext, aFilter, - aUserSpaceToImageSpace, aFill, - aSubimage, aFlags); + bool drawn = DrawWithPreDownscaleIfNeeded(frame, aContext, aSize, + aRegion, aFilter, aFlags); if (!drawn) { // The OS threw out some or all of our buffer. Start decoding again. ForceDiscard(); @@ -3684,11 +3679,11 @@ RasterImage::OptimalImageSizeForDest(const gfxSize& aDest, uint32_t aWhichFrame, } nsIntSize destSize(ceil(aDest.width), ceil(aDest.height)); - gfxSize scale(double(destSize.width) / mSize.width, - double(destSize.height) / mSize.height); + gfx::Size scale(double(destSize.width) / mSize.width, + double(destSize.height) / mSize.height); if (CanScale(aFilter, scale, aFlags)) { - if (mScaleResult.scale == scale) { + if (mScaleResult.scaledSize == destSize) { if (mScaleResult.status == SCALE_DONE) { return destSize; // We have an existing HQ scale for this size. } else if (mScaleResult.status == SCALE_PENDING) { @@ -3702,7 +3697,7 @@ RasterImage::OptimalImageSizeForDest(const gfxSize& aDest, uint32_t aWhichFrame, imgFrame* frame = GetDrawableImgFrame(frameIndex); if (frame) { - RequestScale(frame, scale); + RequestScale(frame, destSize); } } diff --git a/image/src/RasterImage.h b/image/src/RasterImage.h index 515836edd65..f7cc5bb95b6 100644 --- a/image/src/RasterImage.h +++ b/image/src/RasterImage.h @@ -330,7 +330,7 @@ public: private: // Initiates an HQ scale for the given frame, if possible. - void RequestScale(imgFrame* aFrame, gfxSize aScale); + void RequestScale(imgFrame* aFrame, nsIntSize aScale); already_AddRefed CurrentStatusTracker() { @@ -558,10 +558,9 @@ private: bool DrawWithPreDownscaleIfNeeded(imgFrame *aFrame, gfxContext *aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, GraphicsFilter aFilter, - const gfxMatrix &aUserSpaceToImageSpace, - const gfxRect &aFill, - const nsIntRect &aSubimage, uint32_t aFlags); TemporaryRef CopyFrame(uint32_t aWhichFrame, @@ -739,8 +738,8 @@ private: // data bool IsDecodeFinished(); TimeStamp mDrawStartTime; - inline bool CanQualityScale(const gfxSize& scale); - inline bool CanScale(GraphicsFilter aFilter, gfxSize aScale, uint32_t aFlags); + inline bool CanQualityScale(const gfx::Size& scale); + inline bool CanScale(GraphicsFilter aFilter, gfx::Size aScale, uint32_t aFlags); struct ScaleResult { @@ -748,7 +747,7 @@ private: // data : status(SCALE_INVALID) {} - gfxSize scale; + nsIntSize scaledSize; nsAutoPtr frame; ScaleStatus status; }; diff --git a/image/src/SurfaceCache.h b/image/src/SurfaceCache.h index b75b39fb191..9f9ed78e048 100644 --- a/image/src/SurfaceCache.h +++ b/image/src/SurfaceCache.h @@ -10,6 +10,7 @@ #ifndef MOZILLA_IMAGELIB_SURFACECACHE_H_ #define MOZILLA_IMAGELIB_SURFACECACHE_H_ +#include "mozilla/Maybe.h" // for Maybe #include "mozilla/HashFunctions.h" // for HashGeneric and AddToHash #include "gfxPoint.h" // for gfxSize #include "nsCOMPtr.h" // for already_AddRefed @@ -46,28 +47,19 @@ class SurfaceKey typedef gfx::IntSize IntSize; public: SurfaceKey(const IntSize& aSize, - const gfxSize aScale, - const SVGImageContext* aSVGContext, + const Maybe& aSVGContext, const float aAnimationTime, const uint32_t aFlags) : mSize(aSize) - , mScale(aScale) - , mSVGContextIsValid(aSVGContext != nullptr) + , mSVGContext(aSVGContext) , mAnimationTime(aAnimationTime) , mFlags(aFlags) - { - // XXX(seth): Would love to use Maybe here, but see bug 913586. - if (mSVGContextIsValid) - mSVGContext = *aSVGContext; - } + { } bool operator==(const SurfaceKey& aOther) const { - bool matchesSVGContext = aOther.mSVGContextIsValid == mSVGContextIsValid && - (!mSVGContextIsValid || aOther.mSVGContext == mSVGContext); return aOther.mSize == mSize && - aOther.mScale == mScale && - matchesSVGContext && + aOther.mSVGContext == mSVGContext && aOther.mAnimationTime == mAnimationTime && aOther.mFlags == mFlags; } @@ -75,8 +67,7 @@ public: uint32_t Hash() const { uint32_t hash = HashGeneric(mSize.width, mSize.height); - hash = AddToHash(hash, mScale.width, mScale.height); - hash = AddToHash(hash, mSVGContextIsValid, mSVGContext.Hash()); + hash = AddToHash(hash, mSVGContext.map(HashSIC).valueOr(0)); hash = AddToHash(hash, mAnimationTime, mFlags); return hash; } @@ -84,12 +75,14 @@ public: IntSize Size() const { return mSize; } private: - IntSize mSize; - gfxSize mScale; - SVGImageContext mSVGContext; - bool mSVGContextIsValid; - float mAnimationTime; - uint32_t mFlags; + static uint32_t HashSIC(const SVGImageContext& aSIC) { + return aSIC.Hash(); + } + + IntSize mSize; + Maybe mSVGContext; + float mAnimationTime; + uint32_t mFlags; }; /** diff --git a/image/src/VectorImage.cpp b/image/src/VectorImage.cpp index 8dbd0c89d76..7820e2c5416 100644 --- a/image/src/VectorImage.cpp +++ b/image/src/VectorImage.cpp @@ -26,6 +26,7 @@ #include "nsStubDocumentObserver.h" #include "nsSVGEffects.h" // for nsSVGRenderingObserver #include "nsWindowMemoryReporter.h" +#include "ImageRegion.h" #include "Orientation.h" #include "SVGDocumentWrapper.h" #include "nsIDOMEventListener.h" @@ -241,12 +242,12 @@ class SVGDrawingCallback : public gfxDrawingCallback { public: SVGDrawingCallback(SVGDocumentWrapper* aSVGDocumentWrapper, const nsIntRect& aViewport, - const gfxSize& aScale, - uint32_t aImageFlags) : - mSVGDocumentWrapper(aSVGDocumentWrapper), - mViewport(aViewport), - mScale(aScale), - mImageFlags(aImageFlags) + const IntSize& aSize, + uint32_t aImageFlags) + : mSVGDocumentWrapper(aSVGDocumentWrapper) + , mViewport(aViewport) + , mSize(aSize) + , mImageFlags(aImageFlags) {} virtual bool operator()(gfxContext* aContext, const gfxRect& aFillRect, @@ -254,9 +255,9 @@ public: const gfxMatrix& aTransform); private: nsRefPtr mSVGDocumentWrapper; - const nsIntRect mViewport; - const gfxSize mScale; - uint32_t mImageFlags; + const nsIntRect mViewport; + const IntSize mSize; + uint32_t mImageFlags; }; // Based loosely on nsSVGIntegrationUtils' PaintFrameCallback::operator() @@ -283,13 +284,13 @@ SVGDrawingCallback::operator()(gfxContext* aContext, aContext->Rectangle(aFillRect); aContext->Clip(); - gfxContextMatrixAutoSaveRestore contextMatrixRestorer(aContext); gfxMatrix matrix = aTransform; if (!matrix.Invert()) { return false; } aContext->Multiply(matrix); - aContext->Scale(1.0 / mScale.width, 1.0 / mScale.height); + aContext->Scale(double(mSize.width) / mViewport.width, + double(mSize.height) / mViewport.height); nsPresContext* presContext = presShell->GetPresContext(); MOZ_ASSERT(presContext, "pres shell w/out pres context"); @@ -741,11 +742,10 @@ VectorImage::GetFrame(uint32_t aWhichFrame, nsRefPtr context = new gfxContext(dt); - nsresult rv = Draw(context, GraphicsFilter::FILTER_NEAREST, gfxMatrix(), - gfxRect(gfxPoint(0,0), gfxIntSize(imageIntSize.width, - imageIntSize.height)), - nsIntRect(nsIntPoint(0,0), imageIntSize), - imageIntSize, nullptr, aWhichFrame, aFlags); + nsresult rv = Draw(context, imageIntSize, + ImageRegion::Create(imageIntSize), + aWhichFrame, GraphicsFilter::FILTER_NEAREST, + Nothing(), aFlags); NS_ENSURE_SUCCESS(rv, nullptr); return dt->Snapshot(); @@ -764,60 +764,32 @@ VectorImage::GetImageContainer(LayerManager* aManager, struct SVGDrawingParameters { SVGDrawingParameters(gfxContext* aContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, GraphicsFilter aFilter, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFill, - const nsIntRect& aSubimage, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const Maybe& aSVGContext, float aAnimationTime, uint32_t aFlags) : context(aContext) + , size(aSize.width, aSize.height) + , imageRect(0, 0, aSize.width, aSize.height) + , region(aRegion) , filter(aFilter) - , fill(aFill) - , viewportSize(aViewportSize) - , animationTime(aAnimationTime) , svgContext(aSVGContext) + , viewportSize(aSVGContext ? aSVGContext->GetViewportSize() : aSize) + , animationTime(aAnimationTime) , flags(aFlags) - { - // gfxUtils::DrawPixelSnapped may rasterize this image to a temporary surface - // if we hit the tiling path. Unfortunately, the temporary surface isn't - // created at the size at which we'll ultimately draw, causing fuzzy output. - // To fix this we pre-apply the transform's scaling to the drawing parameters - // and remove the scaling from the transform, so the fact that temporary - // surfaces won't take the scaling into account doesn't matter. (Bug 600207.) - scale = aUserSpaceToImageSpace.ScaleFactors(true); - gfxPoint translation(aUserSpaceToImageSpace.GetTranslation()); + { } - // Remove the scaling from the transform. - gfxMatrix unscale; - unscale.Translate(gfxPoint(translation.x / scale.width, - translation.y / scale.height)); - unscale.Scale(1.0 / scale.width, 1.0 / scale.height); - unscale.Translate(-translation); - userSpaceToImageSpace = aUserSpaceToImageSpace * unscale; - - // Rescale drawing parameters. - IntSize drawableSize(aViewportSize.width / scale.width, - aViewportSize.height / scale.height); - sourceRect = userSpaceToImageSpace.Transform(aFill); - imageRect = IntRect(IntPoint(0, 0), drawableSize); - subimage = gfxRect(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height); - subimage.ScaleRoundOut(1.0 / scale.width, 1.0 / scale.height); - } - - gfxContext* context; - GraphicsFilter filter; - gfxMatrix userSpaceToImageSpace; - gfxRect fill; - gfxRect subimage; - gfxRect sourceRect; - IntRect imageRect; - nsIntSize viewportSize; - gfxSize scale; - float animationTime; - const SVGImageContext* svgContext; - uint32_t flags; + gfxContext* context; + IntSize size; + IntRect imageRect; + ImageRegion region; + GraphicsFilter filter; + const Maybe& svgContext; + nsIntSize viewportSize; + float animationTime; + uint32_t flags; }; //****************************************************************************** @@ -832,13 +804,11 @@ struct SVGDrawingParameters * in uint32_t aFlags); */ NS_IMETHODIMP VectorImage::Draw(gfxContext* aContext, - GraphicsFilter aFilter, - const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFill, - const nsIntRect& aSubimage, - const nsIntSize& aViewportSize, - const SVGImageContext* aSVGContext, + const nsIntSize& aSize, + const ImageRegion& aRegion, uint32_t aWhichFrame, + GraphicsFilter aFilter, + const Maybe& aSVGContext, uint32_t aFlags) { if (aWhichFrame > FRAME_MAX_VALUE) @@ -866,20 +836,19 @@ VectorImage::Draw(gfxContext* aContext, mSVGDocumentWrapper->GetRootSVGElem()); // Pack up the drawing parameters. - SVGDrawingParameters params(aContext, aFilter, aUserSpaceToImageSpace, aFill, - aSubimage, aViewportSize, aSVGContext, animTime, aFlags); + SVGDrawingParameters params(aContext, aSize, aRegion, aFilter, + aSVGContext, animTime, aFlags); - // Check the cache. (The FLAG_BYPASS_SURFACE_CACHE check here is just an - // optimization since the flags are part of the cache key and we never put - // surfaces in the cache if the flags contain FLAG_BYPASS_SURFACE_CACHE.) - nsRefPtr drawable; - if (!(aFlags & FLAG_BYPASS_SURFACE_CACHE)) { - drawable = - SurfaceCache::Lookup(ImageKey(this), - SurfaceKey(params.imageRect.Size(), params.scale, - aSVGContext, animTime, aFlags)); + if (aFlags & FLAG_BYPASS_SURFACE_CACHE) { + CreateDrawableAndShow(params); + return NS_OK; } + nsRefPtr drawable = + SurfaceCache::Lookup(ImageKey(this), + SurfaceKey(params.size, aSVGContext, + animTime, aFlags)); + // Draw. if (drawable) { Show(drawable, params); @@ -899,24 +868,26 @@ VectorImage::CreateDrawableAndShow(const SVGDrawingParameters& aParams) nsRefPtr cb = new SVGDrawingCallback(mSVGDocumentWrapper, nsIntRect(nsIntPoint(0, 0), aParams.viewportSize), - aParams.scale, + aParams.size, aParams.flags); nsRefPtr svgDrawable = - new gfxCallbackDrawable(cb, ThebesIntSize(aParams.imageRect.Size())); + new gfxCallbackDrawable(cb, ThebesIntSize(aParams.size)); bool bypassCache = bool(aParams.flags & FLAG_BYPASS_SURFACE_CACHE) || // Refuse to cache animated images: // XXX(seth): We may remove this restriction in bug 922893. mHaveAnimations || // The image is too big to fit in the cache: - !SurfaceCache::CanHold(aParams.imageRect.Size()); + !SurfaceCache::CanHold(aParams.size); if (bypassCache) return Show(svgDrawable, aParams); // Try to create an offscreen surface. RefPtr target = - gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(aParams.imageRect.Size(), gfx::SurfaceFormat::B8G8R8A8); + gfxPlatform::GetPlatform()-> + CreateOffscreenContentDrawTarget(aParams.size, + gfx::SurfaceFormat::B8G8R8A8); // If we couldn't create the draw target, it was probably because it would end // up way too big. Generally it also wouldn't fit in the cache, but the prefs @@ -927,28 +898,25 @@ VectorImage::CreateDrawableAndShow(const SVGDrawingParameters& aParams) nsRefPtr ctx = new gfxContext(target); // Actually draw. (We use FILTER_NEAREST since we never scale here.) - gfxUtils::DrawPixelSnapped(ctx, svgDrawable, gfxMatrix(), - ThebesIntRect(aParams.imageRect), - ThebesIntRect(aParams.imageRect), - ThebesIntRect(aParams.imageRect), - ThebesIntRect(aParams.imageRect), + nsIntRect imageRect(ThebesIntRect(aParams.imageRect)); + gfxUtils::DrawPixelSnapped(ctx, svgDrawable, + ThebesIntSize(aParams.size), + ImageRegion::Create(imageRect), SurfaceFormat::B8G8R8A8, GraphicsFilter::FILTER_NEAREST, aParams.flags); RefPtr surface = target->Snapshot(); // Attempt to cache the resulting surface. - SurfaceCache::Insert(surface, - ImageKey(this), - SurfaceKey(aParams.imageRect.Size(), aParams.scale, - aParams.svgContext, aParams.animationTime, - aParams.flags)); + SurfaceCache::Insert(surface, ImageKey(this), + SurfaceKey(aParams.size, aParams.svgContext, + aParams.animationTime, aParams.flags)); // Draw. Note that if SurfaceCache::Insert failed for whatever reason, // then |target| is all that is keeping the pixel data alive, so we have // to draw before returning from this function. nsRefPtr drawable = - new gfxSurfaceDrawable(surface, ThebesIntSize(aParams.imageRect.Size())); + new gfxSurfaceDrawable(surface, ThebesIntSize(aParams.size)); Show(drawable, aParams); } @@ -958,9 +926,8 @@ VectorImage::Show(gfxDrawable* aDrawable, const SVGDrawingParameters& aParams) { MOZ_ASSERT(aDrawable, "Should have a gfxDrawable by now"); gfxUtils::DrawPixelSnapped(aParams.context, aDrawable, - aParams.userSpaceToImageSpace, - aParams.subimage, aParams.sourceRect, - ThebesIntRect(aParams.imageRect), aParams.fill, + ThebesIntSize(aParams.size), + aParams.region, SurfaceFormat::B8G8R8A8, aParams.filter, aParams.flags); diff --git a/image/src/imgFrame.cpp b/image/src/imgFrame.cpp index b46b942f342..a8a7d2f5e6e 100644 --- a/image/src/imgFrame.cpp +++ b/image/src/imgFrame.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "imgFrame.h" +#include "ImageRegion.h" #include "DiscardTracker.h" #include "prenv.h" @@ -315,12 +316,10 @@ imgFrame::SurfaceWithFormat imgFrame::SurfaceForDrawing(bool aDoPadding, bool aDoPartialDecode, bool aDoTile, + gfxContext* aContext, const nsIntMargin& aPadding, - gfxMatrix& aUserSpaceToImageSpace, - gfxRect& aFill, - gfxRect& aSubimage, - gfxRect& aSourceRect, gfxRect& aImageRect, + ImageRegion& aRegion, SourceSurface* aSurface) { IntSize size(int32_t(aImageRect.Width()), int32_t(aImageRect.Height())); @@ -341,23 +340,15 @@ imgFrame::SurfaceForDrawing(bool aDoPadding, if (!target) return SurfaceWithFormat(); - Rect fillRect(aFill.x, aFill.y, aFill.width, aFill.height); // Fill 'available' with whatever we've got if (mSinglePixel) { - target->FillRect(fillRect, ColorPattern(mSinglePixelColor), + target->FillRect(ToRect(aRegion.Rect()), ColorPattern(mSinglePixelColor), DrawOptions(1.0f, CompositionOp::OP_SOURCE)); } else { - gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace; - imageSpaceToUserSpace.Invert(); SurfacePattern pattern(aSurface, ExtendMode::REPEAT, - Matrix(imageSpaceToUserSpace._11, - imageSpaceToUserSpace._21, - imageSpaceToUserSpace._12, - imageSpaceToUserSpace._22, - imageSpaceToUserSpace._31, - imageSpaceToUserSpace._32)); - target->FillRect(fillRect, pattern); + ToMatrix(aContext->CurrentMatrix())); + target->FillRect(ToRect(aRegion.Rect()), pattern); } RefPtr newsurf = target->Snapshot(); @@ -366,15 +357,9 @@ imgFrame::SurfaceForDrawing(bool aDoPadding, // Not tiling, and we have a surface, so we can account for // padding and/or a partial decode just by twiddling parameters. - // First, update our user-space fill rect. - aSourceRect = aSourceRect.Intersect(available); - gfxMatrix imageSpaceToUserSpace = aUserSpaceToImageSpace; - imageSpaceToUserSpace.Invert(); - aFill = imageSpaceToUserSpace.Transform(aSourceRect); - - aSubimage = aSubimage.Intersect(available) - gfxPoint(aPadding.left, aPadding.top); - aUserSpaceToImageSpace *= gfxMatrix::Translation(-aPadding.left, -aPadding.top); - aSourceRect = aSourceRect - gfxPoint(aPadding.left, aPadding.top); + gfxPoint paddingTopLeft(aPadding.left, aPadding.top); + aRegion = aRegion.Intersect(available) - paddingTopLeft; + aContext->Multiply(gfxMatrix::Translation(paddingTopLeft)); aImageRect = gfxRect(0, 0, mSize.width, mSize.height); gfxIntSize availableSize(mDecoded.width, mDecoded.height); @@ -382,16 +367,17 @@ imgFrame::SurfaceForDrawing(bool aDoPadding, mFormat); } -bool imgFrame::Draw(gfxContext *aContext, GraphicsFilter aFilter, - const gfxMatrix &aUserSpaceToImageSpace, const gfxRect& aFill, - const nsIntMargin &aPadding, const nsIntRect &aSubimage, +bool imgFrame::Draw(gfxContext* aContext, const ImageRegion& aRegion, + const nsIntMargin& aPadding, GraphicsFilter aFilter, uint32_t aImageFlags) { PROFILER_LABEL("imgFrame", "Draw", js::ProfileEntry::Category::GRAPHICS); - NS_ASSERTION(!aFill.IsEmpty(), "zero dest size --- fix caller"); - NS_ASSERTION(!aSubimage.IsEmpty(), "zero source size --- fix caller"); + NS_ASSERTION(!aRegion.Rect().IsEmpty(), "Drawing empty region!"); + NS_ASSERTION(!aRegion.IsRestricted() || + !aRegion.Rect().Intersect(aRegion.Restriction()).IsEmpty(), + "We must be allowed to sample *some* source pixels!"); NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!"); bool doPadding = aPadding != nsIntMargin(0,0,0,0); @@ -402,40 +388,32 @@ bool imgFrame::Draw(gfxContext *aContext, GraphicsFilter aFilter, return true; } RefPtr dt = aContext->GetDrawTarget(); - dt->FillRect(ToRect(aFill), + dt->FillRect(ToRect(aRegion.Rect()), ColorPattern(mSinglePixelColor), DrawOptions(1.0f, CompositionOpForOp(aContext->CurrentOperator()))); return true; } - gfxMatrix userSpaceToImageSpace = aUserSpaceToImageSpace; - gfxRect sourceRect = userSpaceToImageSpace.TransformBounds(aFill); gfxRect imageRect(0, 0, mSize.width + aPadding.LeftRight(), mSize.height + aPadding.TopBottom()); - gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height); - gfxRect fill = aFill; - - NS_ASSERTION(!sourceRect.Intersect(subimage).IsEmpty(), - "We must be allowed to sample *some* source pixels!"); RefPtr surf = GetSurface(); if (!surf) { return false; } - bool doTile = !imageRect.Contains(sourceRect) && + bool doTile = !imageRect.Contains(aRegion.Rect()) && !(aImageFlags & imgIContainer::FLAG_CLAMP); + ImageRegion region(aRegion); SurfaceWithFormat surfaceResult = - SurfaceForDrawing(doPadding, doPartialDecode, doTile, aPadding, - userSpaceToImageSpace, fill, subimage, sourceRect, - imageRect, surf); + SurfaceForDrawing(doPadding, doPartialDecode, doTile, aContext, + aPadding, imageRect, region, surf); if (surfaceResult.IsValid()) { gfxUtils::DrawPixelSnapped(aContext, surfaceResult.mDrawable, - userSpaceToImageSpace, - subimage, sourceRect, imageRect, fill, - surfaceResult.mFormat, aFilter, aImageFlags); + imageRect.Size(), region, surfaceResult.mFormat, + aFilter, aImageFlags); } return true; } diff --git a/image/src/imgFrame.h b/image/src/imgFrame.h index 6012e741e17..1af1058f3f9 100644 --- a/image/src/imgFrame.h +++ b/image/src/imgFrame.h @@ -16,6 +16,8 @@ namespace mozilla { namespace image { +class ImageRegion; + class imgFrame { typedef gfx::Color Color; @@ -31,10 +33,9 @@ public: nsresult Init(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight, SurfaceFormat aFormat, uint8_t aPaletteDepth = 0); nsresult Optimize(); - bool Draw(gfxContext *aContext, GraphicsFilter aFilter, - const gfxMatrix &aUserSpaceToImageSpace, const gfxRect& aFill, - const nsIntMargin &aPadding, const nsIntRect &aSubimage, - uint32_t aImageFlags = imgIContainer::FLAG_NONE); + bool Draw(gfxContext* aContext, const ImageRegion& aRegion, + const nsIntMargin& aPadding, GraphicsFilter aFilter, + uint32_t aImageFlags); nsresult ImageUpdated(const nsIntRect &aUpdateRect); @@ -111,12 +112,10 @@ private: // methods SurfaceWithFormat SurfaceForDrawing(bool aDoPadding, bool aDoPartialDecode, bool aDoTile, + gfxContext* aContext, const nsIntMargin& aPadding, - gfxMatrix& aUserSpaceToImageSpace, - gfxRect& aFill, - gfxRect& aSubimage, - gfxRect& aSourceRect, gfxRect& aImageRect, + ImageRegion& aRegion, SourceSurface* aSurface); private: // data diff --git a/image/src/imgRequestProxy.cpp b/image/src/imgRequestProxy.cpp index a47de1e52e7..967c01d331a 100644 --- a/image/src/imgRequestProxy.cpp +++ b/image/src/imgRequestProxy.cpp @@ -4,13 +4,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "ImageLogging.h" #include "imgRequestProxy.h" #include "imgIOnloadBlocker.h" #include "Image.h" #include "ImageOps.h" #include "nsError.h" -#include "ImageLogging.h" #include "nsCRTGlue.h" #include "imgINotificationObserver.h" #include "nsNetUtil.h" diff --git a/image/src/imgStatusTracker.cpp b/image/src/imgStatusTracker.cpp index cda73581c06..0a9af6bb6b2 100644 --- a/image/src/imgStatusTracker.cpp +++ b/image/src/imgStatusTracker.cpp @@ -4,13 +4,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "ImageLogging.h" #include "imgStatusTracker.h" #include "imgIContainer.h" #include "imgRequestProxy.h" #include "imgDecoderObserver.h" #include "Image.h" -#include "ImageLogging.h" #include "nsNetUtil.h" #include "nsIObserverService.h" diff --git a/image/src/moz.build b/image/src/moz.build index 0ab0033b267..1b3529fce03 100644 --- a/image/src/moz.build +++ b/image/src/moz.build @@ -6,6 +6,7 @@ EXPORTS += [ 'ImageOps.h', + 'ImageRegion.h', 'imgLoader.h', 'imgRequest.h', 'imgRequestProxy.h', diff --git a/image/test/reftest/downscaling/reftest.list b/image/test/reftest/downscaling/reftest.list index c087dd520c7..5c28efdcabb 100644 --- a/image/test/reftest/downscaling/reftest.list +++ b/image/test/reftest/downscaling/reftest.list @@ -20,78 +20,80 @@ # # Also note that Mac OS X has its own system-level downscaling algorithm, so # tests here may need Mac-specific "fuzzy-if(cocoaWidget,...)" annotations. +# Similarly, modern versions of Windows have slightly different downscaling +# behavior than other platforms, and may require "fuzzy-if(winWidget,...)". # RUN TESTS NOT AFFECTED BY HIGH QUALITY DOWNSCALING: # =================================================== -fails == downscale-svg-1a.html downscale-svg-1-ref.html?80 -fails == downscale-svg-1b.html downscale-svg-1-ref.html?72 -fails == downscale-svg-1c.html downscale-svg-1-ref.html?64 -fails == downscale-svg-1d.html downscale-svg-1-ref.html?53 -fails == downscale-svg-1e.html downscale-svg-1-ref.html?40 -fails == downscale-svg-1f.html downscale-svg-1-ref.html?24 +== downscale-svg-1a.html downscale-svg-1-ref.html?80 +fuzzy(80,468) == downscale-svg-1b.html downscale-svg-1-ref.html?72 +== downscale-svg-1c.html downscale-svg-1-ref.html?64 +fuzzy(17,208) == downscale-svg-1d.html downscale-svg-1-ref.html?53 +fuzzy(78,216) == downscale-svg-1e.html downscale-svg-1-ref.html?40 +fuzzy(51,90) == downscale-svg-1f.html downscale-svg-1-ref.html?24 # RUN TESTS WITH HIGH QUALITY DOWNSCALING DISABLED: # ================================================= default-preferences pref(image.high_quality_downscaling.enabled,false) -fuzzy-if(cocoaWidget,106,31) == downscale-1.html downscale-1-ref.html +fuzzy-if(winWidget,16,20) fuzzy-if(cocoaWidget,106,31) == downscale-1.html downscale-1-ref.html -fuzzy(5,999) random != downscale-2a.html?203,52,left about:blank -fuzzy(5,999) random != downscale-2b.html?203,52,left about:blank -fuzzy(5,999) random != downscale-2c.html?203,52,left about:blank -fuzzy(5,999) random != downscale-2d.html?203,52,left about:blank -fuzzy(5,999) random != downscale-2e.html?203,52,left about:blank -fuzzy(5,999) random != downscale-2f.html?203,52,left about:blank +fuzzy(20,999) != downscale-2a.html?203,52,left about:blank +fuzzy(20,999) != downscale-2b.html?203,52,left about:blank +fuzzy(20,999) != downscale-2c.html?203,52,left about:blank +fuzzy(20,999) != downscale-2d.html?203,52,left about:blank +fuzzy(20,999) != downscale-2e.html?203,52,left about:blank +fuzzy(20,999) fails-if(!cocoaWidget) != downscale-2f.html?203,52,left about:blank -fuzzy(5,999) random != downscale-2a.html?205,53,left about:blank -fuzzy(5,999) random != downscale-2b.html?205,53,left about:blank -fuzzy(5,999) random != downscale-2c.html?205,53,left about:blank -fuzzy(5,999) random != downscale-2d.html?205,53,left about:blank -fuzzy(5,999) random != downscale-2e.html?205,53,left about:blank -fuzzy(5,999) random != downscale-2f.html?205,53,left about:blank +fuzzy(20,999) != downscale-2a.html?205,53,left about:blank +fuzzy(20,999) != downscale-2b.html?205,53,left about:blank +fuzzy(20,999) != downscale-2c.html?205,53,left about:blank +fuzzy(20,999) != downscale-2d.html?205,53,left about:blank +fuzzy(20,999) != downscale-2e.html?205,53,left about:blank +fuzzy(20,999) fails-if(!cocoaWidget) != downscale-2f.html?205,53,left about:blank -fuzzy(5,999) random != downscale-2a.html?203,52,right about:blank -fuzzy(5,999) random != downscale-2b.html?203,52,right about:blank -fuzzy(5,999) random != downscale-2c.html?203,52,right about:blank -fuzzy(5,999) random != downscale-2d.html?203,52,right about:blank -fuzzy(5,999) random != downscale-2e.html?203,52,right about:blank -fuzzy(5,999) random != downscale-2f.html?203,52,right about:blank +fuzzy(20,999) != downscale-2a.html?203,52,right about:blank +fuzzy(20,999) != downscale-2b.html?203,52,right about:blank +fuzzy(20,999) != downscale-2c.html?203,52,right about:blank +fuzzy(20,999) != downscale-2d.html?203,52,right about:blank +fuzzy(20,999) != downscale-2e.html?203,52,right about:blank +fuzzy(20,999) random-if(!cocoaWidget) != downscale-2f.html?203,52,right about:blank -fuzzy(5,999) random != downscale-2a.html?205,53,right about:blank -fuzzy(5,999) random != downscale-2b.html?205,53,right about:blank -fuzzy(5,999) random != downscale-2c.html?205,53,right about:blank -fuzzy(5,999) random != downscale-2d.html?205,53,right about:blank -fuzzy(5,999) random != downscale-2e.html?205,53,right about:blank -fuzzy(5,999) random != downscale-2f.html?205,53,right about:blank +fuzzy(20,999) != downscale-2a.html?205,53,right about:blank +fuzzy(20,999) != downscale-2b.html?205,53,right about:blank +fuzzy(20,999) != downscale-2c.html?205,53,right about:blank +fuzzy(20,999) != downscale-2d.html?205,53,right about:blank +fuzzy(20,999) != downscale-2e.html?205,53,right about:blank +fuzzy(20,999) random-if(!cocoaWidget) != downscale-2f.html?205,53,right about:blank -fuzzy(5,999) random != downscale-2a.html?203,52,top about:blank -fuzzy(5,999) random != downscale-2b.html?203,52,top about:blank -fuzzy(5,999) random != downscale-2c.html?203,52,top about:blank -fuzzy(5,999) random != downscale-2d.html?203,52,top about:blank -fuzzy(5,999) random != downscale-2e.html?203,52,top about:blank -fuzzy(5,999) random != downscale-2f.html?203,52,top about:blank +fuzzy(20,999) != downscale-2a.html?203,52,top about:blank +fuzzy(20,999) != downscale-2b.html?203,52,top about:blank +fuzzy(20,999) != downscale-2c.html?203,52,top about:blank +fuzzy(20,999) != downscale-2d.html?203,52,top about:blank +fuzzy(20,999) != downscale-2e.html?203,52,top about:blank +fuzzy(20,999) fails-if(!cocoaWidget) != downscale-2f.html?203,52,top about:blank -fuzzy(5,999) random != downscale-2a.html?205,53,top about:blank -fuzzy(5,999) random != downscale-2b.html?205,53,top about:blank -fuzzy(5,999) random != downscale-2c.html?205,53,top about:blank -fuzzy(5,999) random != downscale-2d.html?205,53,top about:blank -fuzzy(5,999) random != downscale-2e.html?205,53,top about:blank -fuzzy(5,999) random != downscale-2f.html?205,53,top about:blank +fuzzy(20,999) != downscale-2a.html?205,53,top about:blank +fuzzy(20,999) != downscale-2b.html?205,53,top about:blank +fuzzy(20,999) != downscale-2c.html?205,53,top about:blank +fuzzy(20,999) != downscale-2d.html?205,53,top about:blank +fuzzy(20,999) != downscale-2e.html?205,53,top about:blank +fuzzy(20,999) fails-if(!cocoaWidget) != downscale-2f.html?205,53,top about:blank -fuzzy(5,999) random != downscale-2a.html?203,52,bottom about:blank -fuzzy(5,999) random != downscale-2b.html?203,52,bottom about:blank -fuzzy(5,999) random != downscale-2c.html?203,52,bottom about:blank -fuzzy(5,999) random != downscale-2d.html?203,52,bottom about:blank -fuzzy(5,999) random != downscale-2e.html?203,52,bottom about:blank -fuzzy(5,999) random != downscale-2f.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2a.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2b.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2c.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2d.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2e.html?203,52,bottom about:blank +fuzzy(20,999) fails-if(!cocoaWidget) != downscale-2f.html?203,52,bottom about:blank -fuzzy(5,999) random != downscale-2a.html?205,53,bottom about:blank -fuzzy(5,999) random != downscale-2b.html?205,53,bottom about:blank -fuzzy(5,999) random != downscale-2c.html?205,53,bottom about:blank -fuzzy(5,999) random != downscale-2d.html?205,53,bottom about:blank -fuzzy(5,999) random != downscale-2e.html?205,53,bottom about:blank -fuzzy(5,999) random != downscale-2f.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2a.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2b.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2c.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2d.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2e.html?205,53,bottom about:blank +fuzzy(20,999) fails-if(!cocoaWidget) != downscale-2f.html?205,53,bottom about:blank # RUN TESTS WITH HIGH QUALITY DOWNSCALING ENABLED: # ================================================ @@ -100,58 +102,58 @@ default-preferences pref(image.high_quality_downscaling.enabled,true) fuzzy(31,127) == downscale-1.html downscale-1-ref.html -fuzzy(5,999) random != downscale-2a.html?203,52,left about:blank -fuzzy(5,999) random != downscale-2b.html?203,52,left about:blank -fuzzy(5,999) random != downscale-2c.html?203,52,left about:blank -fuzzy(5,999) random != downscale-2d.html?203,52,left about:blank -fuzzy(5,999) random != downscale-2e.html?203,52,left about:blank -fuzzy(5,999) random != downscale-2f.html?203,52,left about:blank +fuzzy(20,999) != downscale-2a.html?203,52,left about:blank +fuzzy(20,999) != downscale-2b.html?203,52,left about:blank +fuzzy(20,999) != downscale-2c.html?203,52,left about:blank +fuzzy(20,999) != downscale-2d.html?203,52,left about:blank +fuzzy(20,999) != downscale-2e.html?203,52,left about:blank +fuzzy(20,999) fails-if(B2G) != downscale-2f.html?203,52,left about:blank -fuzzy(5,999) random != downscale-2a.html?205,53,left about:blank -fuzzy(5,999) random != downscale-2b.html?205,53,left about:blank -fuzzy(5,999) random != downscale-2c.html?205,53,left about:blank -fuzzy(5,999) random != downscale-2d.html?205,53,left about:blank -fuzzy(5,999) random != downscale-2e.html?205,53,left about:blank -fuzzy(5,999) random != downscale-2f.html?205,53,left about:blank +fuzzy(20,999) != downscale-2a.html?205,53,left about:blank +fuzzy(20,999) != downscale-2b.html?205,53,left about:blank +fuzzy(20,999) != downscale-2c.html?205,53,left about:blank +fuzzy(20,999) != downscale-2d.html?205,53,left about:blank +fuzzy(20,999) != downscale-2e.html?205,53,left about:blank +fuzzy(20,999) fails-if(B2G) != downscale-2f.html?205,53,left about:blank -fuzzy(5,999) random != downscale-2a.html?203,52,right about:blank -fuzzy(5,999) random != downscale-2b.html?203,52,right about:blank -fuzzy(5,999) random != downscale-2c.html?203,52,right about:blank -fuzzy(5,999) random != downscale-2d.html?203,52,right about:blank -fuzzy(5,999) random != downscale-2e.html?203,52,right about:blank -fuzzy(5,999) random != downscale-2f.html?203,52,right about:blank +fuzzy(20,999) != downscale-2a.html?203,52,right about:blank +fuzzy(20,999) != downscale-2b.html?203,52,right about:blank +fuzzy(20,999) != downscale-2c.html?203,52,right about:blank +fuzzy(20,999) != downscale-2d.html?203,52,right about:blank +fuzzy(20,999) != downscale-2e.html?203,52,right about:blank +fuzzy(20,999) fails-if(B2G) != downscale-2f.html?203,52,right about:blank -fuzzy(5,999) random != downscale-2a.html?205,53,right about:blank -fuzzy(5,999) random != downscale-2b.html?205,53,right about:blank -fuzzy(5,999) random != downscale-2c.html?205,53,right about:blank -fuzzy(5,999) random != downscale-2d.html?205,53,right about:blank -fuzzy(5,999) random != downscale-2e.html?205,53,right about:blank -fuzzy(5,999) random != downscale-2f.html?205,53,right about:blank +fuzzy(20,999) != downscale-2a.html?205,53,right about:blank +fuzzy(20,999) != downscale-2b.html?205,53,right about:blank +fuzzy(20,999) != downscale-2c.html?205,53,right about:blank +fuzzy(20,999) != downscale-2d.html?205,53,right about:blank +fuzzy(20,999) != downscale-2e.html?205,53,right about:blank +fuzzy(20,999) fails-if(B2G) != downscale-2f.html?205,53,right about:blank -fuzzy(5,999) random != downscale-2a.html?203,52,top about:blank -fuzzy(5,999) random != downscale-2b.html?203,52,top about:blank -fuzzy(5,999) random != downscale-2c.html?203,52,top about:blank -fuzzy(5,999) random != downscale-2d.html?203,52,top about:blank -fuzzy(5,999) random != downscale-2e.html?203,52,top about:blank -fuzzy(5,999) random != downscale-2f.html?203,52,top about:blank +fuzzy(20,999) != downscale-2a.html?203,52,top about:blank +fuzzy(20,999) != downscale-2b.html?203,52,top about:blank +fuzzy(20,999) != downscale-2c.html?203,52,top about:blank +fuzzy(20,999) != downscale-2d.html?203,52,top about:blank +fuzzy(20,999) != downscale-2e.html?203,52,top about:blank +fuzzy(20,999) fails-if(B2G) != downscale-2f.html?203,52,top about:blank -fuzzy(5,999) random != downscale-2a.html?205,53,top about:blank -fuzzy(5,999) random != downscale-2b.html?205,53,top about:blank -fuzzy(5,999) random != downscale-2c.html?205,53,top about:blank -fuzzy(5,999) random != downscale-2d.html?205,53,top about:blank -fuzzy(5,999) random != downscale-2e.html?205,53,top about:blank -fuzzy(5,999) random != downscale-2f.html?205,53,top about:blank +fuzzy(20,999) != downscale-2a.html?205,53,top about:blank +fuzzy(20,999) != downscale-2b.html?205,53,top about:blank +fuzzy(20,999) != downscale-2c.html?205,53,top about:blank +fuzzy(20,999) != downscale-2d.html?205,53,top about:blank +fuzzy(20,999) != downscale-2e.html?205,53,top about:blank +fuzzy(20,999) fails-if(B2G) != downscale-2f.html?205,53,top about:blank -fuzzy(5,999) random != downscale-2a.html?203,52,bottom about:blank -fuzzy(5,999) random != downscale-2b.html?203,52,bottom about:blank -fuzzy(5,999) random != downscale-2c.html?203,52,bottom about:blank -fuzzy(5,999) random != downscale-2d.html?203,52,bottom about:blank -fuzzy(5,999) random != downscale-2e.html?203,52,bottom about:blank -fuzzy(5,999) random != downscale-2f.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2a.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2b.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2c.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2d.html?203,52,bottom about:blank +fuzzy(20,999) != downscale-2e.html?203,52,bottom about:blank +fuzzy(20,999) fails-if(B2G) != downscale-2f.html?203,52,bottom about:blank -fuzzy(5,999) random != downscale-2a.html?205,53,bottom about:blank -fuzzy(5,999) random != downscale-2b.html?205,53,bottom about:blank -fuzzy(5,999) random != downscale-2c.html?205,53,bottom about:blank -fuzzy(5,999) random != downscale-2d.html?205,53,bottom about:blank -fuzzy(5,999) random != downscale-2e.html?205,53,bottom about:blank -fuzzy(5,999) random != downscale-2f.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2a.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2b.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2c.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2d.html?205,53,bottom about:blank +fuzzy(20,999) != downscale-2e.html?205,53,bottom about:blank +fuzzy(20,999) fails-if(B2G) != downscale-2f.html?205,53,bottom about:blank diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 490abc8f313..f8ce85d13b9 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -8,6 +8,7 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/BasicEvents.h" +#include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "nsPresContext.h" #include "nsIContent.h" @@ -31,6 +32,7 @@ #include "nsBidiPresUtils.h" #include "imgIContainer.h" #include "ImageOps.h" +#include "ImageRegion.h" #include "gfxRect.h" #include "gfxContext.h" #include "nsRenderingContext.h" @@ -66,6 +68,7 @@ #include "nsFontFaceList.h" #include "nsFontInflationData.h" #include "nsSVGUtils.h" +#include "SVGImageContext.h" #include "SVGTextFrame.h" #include "nsStyleStructInlines.h" #include "nsStyleTransformMatrix.h" @@ -103,15 +106,11 @@ using namespace mozilla; using namespace mozilla::dom; +using namespace mozilla::image; using namespace mozilla::layers; using namespace mozilla::layout; using namespace mozilla::gfx; -using mozilla::image::Angle; -using mozilla::image::Flip; -using mozilla::image::ImageOps; -using mozilla::image::Orientation; - #define GRID_ENABLED_PREF_NAME "layout.css.grid.enabled" #define RUBY_ENABLED_PREF_NAME "layout.css.ruby.enabled" #define STICKY_ENABLED_PREF_NAME "layout.css.sticky.enabled" @@ -4928,37 +4927,50 @@ nsLayoutUtils::RectToGfxRect(const nsRect& aRect, int32_t aAppUnitsPerDevPixel) } struct SnappedImageDrawingParameters { - // A transform from either device space or user space (depending on mResetCTM) - // to image space - gfxMatrix mUserSpaceToImageSpace; - // A device-space, pixel-aligned rectangle to fill - gfxRect mFillRect; - // A pixel rectangle in tiled image space outside of which gfx should not - // sample (using EXTEND_PAD as necessary) - nsIntRect mSubimage; - // Whether there's anything to draw at all - bool mShouldDraw; - // true iff the CTM of the rendering context needs to be reset to the - // identity matrix before drawing - bool mResetCTM; + // A transform from image space to device space. + gfxMatrix imageSpaceToDeviceSpace; + // The size at which the image should be drawn (which may not be its + // intrinsic size due to, for example, HQ scaling). + nsIntSize size; + // The region in tiled image space which will be drawn, with an associated + // region to which sampling should be restricted. + ImageRegion region; + // Whether there's anything to draw at all. + bool shouldDraw; SnappedImageDrawingParameters() - : mShouldDraw(false) - , mResetCTM(false) + : region(ImageRegion::Empty()) + , shouldDraw(false) {} - SnappedImageDrawingParameters(const gfxMatrix& aUserSpaceToImageSpace, - const gfxRect& aFillRect, - const nsIntRect& aSubimage, - bool aResetCTM) - : mUserSpaceToImageSpace(aUserSpaceToImageSpace) - , mFillRect(aFillRect) - , mSubimage(aSubimage) - , mShouldDraw(true) - , mResetCTM(aResetCTM) + SnappedImageDrawingParameters(const gfxMatrix& aImageSpaceToDeviceSpace, + const nsIntSize& aSize, + const ImageRegion& aRegion) + : imageSpaceToDeviceSpace(aImageSpaceToDeviceSpace) + , size(aSize) + , region(aRegion) + , shouldDraw(true) {} }; +/** + * Given two axis-aligned rectangles, returns the transformation that maps the + * first onto the second. + * + * @param aFrom The rect to be transformed. + * @param aTo The rect that aFrom should be mapped onto by the transformation. + */ +static gfxMatrix +TransformBetweenRects(const gfxRect& aFrom, const gfxRect& aTo) +{ + gfxSize scale(aTo.width / aFrom.width, + aTo.height / aFrom.height); + gfxPoint translation(aTo.x - aFrom.x * scale.width, + aTo.y - aFrom.y * scale.height); + return gfxMatrix(scale.width, 0, 0, scale.height, + translation.x, translation.y); +} + static nsRect TileNearRect(const nsRect& aAnyTile, const nsRect& aTargetRect) { @@ -4981,10 +4993,11 @@ ComputeSnappedImageDrawingParameters(gfxContext* aCtx, const nsRect aFill, const nsPoint aAnchor, const nsRect aDirty, - const nsIntSize aImageSize) - + imgIContainer* aImage, + GraphicsFilter aGraphicsFilter, + uint32_t aImageFlags) { - if (aDest.IsEmpty() || aFill.IsEmpty() || !aImageSize.width || !aImageSize.height) + if (aDest.IsEmpty() || aFill.IsEmpty()) return SnappedImageDrawingParameters(); // Avoid unnecessarily large offsets. @@ -5017,68 +5030,97 @@ ComputeSnappedImageDrawingParameters(gfxContext* aCtx, fill = devPixelFill; } - gfxSize imageSize(aImageSize.width, aImageSize.height); + gfxSize destScale = didSnap ? gfxSize(currentMatrix._11, currentMatrix._22) + : gfxSize(1.0, 1.0); + gfxSize snappedDest(NSAppUnitsToIntPixels(dest.width * destScale.width, + aAppUnitsPerDevPixel), + NSAppUnitsToIntPixels(dest.height * destScale.height, + aAppUnitsPerDevPixel)); + + nsIntSize intImageSize = + aImage->OptimalImageSizeForDest(snappedDest, + imgIContainer::FRAME_CURRENT, + aGraphicsFilter, aImageFlags); + gfxSize imageSize(intImageSize.width, intImageSize.height); // Compute the set of pixels that would be sampled by an ideal rendering gfxPoint subimageTopLeft = MapToFloatImagePixels(imageSize, devPixelDest, devPixelFill.TopLeft()); gfxPoint subimageBottomRight = MapToFloatImagePixels(imageSize, devPixelDest, devPixelFill.BottomRight()); - nsIntRect intSubimage; - intSubimage.MoveTo(NSToIntFloor(subimageTopLeft.x), - NSToIntFloor(subimageTopLeft.y)); - intSubimage.SizeTo(NSToIntCeil(subimageBottomRight.x) - intSubimage.x, - NSToIntCeil(subimageBottomRight.y) - intSubimage.y); + gfxRect subimage; + subimage.MoveTo(NSToIntFloor(subimageTopLeft.x), + NSToIntFloor(subimageTopLeft.y)); + subimage.SizeTo(NSToIntCeil(subimageBottomRight.x) - subimage.x, + NSToIntCeil(subimageBottomRight.y) - subimage.y); - // Compute the anchor point and compute final fill rect. - // This code assumes that pixel-based devices have one pixel per - // device unit! - gfxPoint anchorPoint(gfxFloat(anchor.x)/aAppUnitsPerDevPixel, - gfxFloat(anchor.y)/aAppUnitsPerDevPixel); - gfxPoint imageSpaceAnchorPoint = - MapToFloatImagePixels(imageSize, devPixelDest, anchorPoint); + gfxMatrix transform; + gfxMatrix invTransform; - if (didSnap) { - imageSpaceAnchorPoint.Round(); - anchorPoint = imageSpaceAnchorPoint; - anchorPoint = MapToFloatUserPixels(imageSize, devPixelDest, anchorPoint); - anchorPoint = currentMatrix.Transform(anchorPoint); - anchorPoint.Round(); + bool anchorAtUpperLeft = anchor.x == dest.x && anchor.y == dest.y; + bool exactlyOneImageCopy = aFill.IsEqualEdges(dest); + if (anchorAtUpperLeft && exactlyOneImageCopy) { + // The simple case: we can ignore the anchor point and compute the + // transformation from the sampled region (the subimage) to the fill rect. + // This approach is preferable when it works since it tends to produce + // less numerical error. + transform = TransformBetweenRects(subimage, fill); + invTransform = TransformBetweenRects(fill, subimage); + } else { + // The more complicated case: we compute the transformation from the + // image rect positioned at the image space anchor point to the dest rect + // positioned at the device space anchor point. + // Compute the anchor point in both device space and image space. This + // code assumes that pixel-based devices have one pixel per device unit! + gfxPoint anchorPoint(gfxFloat(anchor.x)/aAppUnitsPerDevPixel, + gfxFloat(anchor.y)/aAppUnitsPerDevPixel); + gfxPoint imageSpaceAnchorPoint = + MapToFloatImagePixels(imageSize, devPixelDest, anchorPoint); + + if (didSnap) { + imageSpaceAnchorPoint.Round(); + anchorPoint = imageSpaceAnchorPoint; + anchorPoint = MapToFloatUserPixels(imageSize, devPixelDest, anchorPoint); + anchorPoint = currentMatrix.Transform(anchorPoint); + anchorPoint.Round(); + } + + gfxRect anchoredDestRect(anchorPoint, snappedDest); + gfxRect anchoredImageRect(imageSpaceAnchorPoint, imageSize); + transform = TransformBetweenRects(anchoredImageRect, anchoredDestRect); + invTransform = TransformBetweenRects(anchoredDestRect, anchoredImageRect); + } + + // If the transform is not a straight translation by integers, then + // filtering will occur, and restricting the fill rect to the dirty rect + // would change the values computed for edge pixels, which we can't allow. + // Also, if 'didSnap' is false then rounding out 'devPixelDirty' might not + // produce pixel-aligned coordinates, which would also break the values + // computed for edge pixels. + if (didSnap && !invTransform.HasNonIntegerTranslation()) { // This form of Transform is safe to call since non-axis-aligned // transforms wouldn't be snapped. devPixelDirty = currentMatrix.Transform(devPixelDirty); - } - - gfxFloat scaleX = imageSize.width*aAppUnitsPerDevPixel/aDest.width; - gfxFloat scaleY = imageSize.height*aAppUnitsPerDevPixel/aDest.height; - if (didSnap) { - // We'll reset aCTX to the identity matrix before drawing, so we need to - // adjust our scales to match. - scaleX /= currentMatrix._11; - scaleY /= currentMatrix._22; - } - gfxFloat translateX = imageSpaceAnchorPoint.x - anchorPoint.x*scaleX; - gfxFloat translateY = imageSpaceAnchorPoint.y - anchorPoint.y*scaleY; - gfxMatrix transform(scaleX, 0, 0, scaleY, translateX, translateY); - - gfxRect finalFillRect = fill; - // If the user-space-to-image-space transform is not a straight - // translation by integers, then filtering will occur, and - // restricting the fill rect to the dirty rect would change the values - // computed for edge pixels, which we can't allow. - // Also, if didSnap is false then rounding out 'devPixelDirty' might not - // produce pixel-aligned coordinates, which would also break the values - // computed for edge pixels. - if (didSnap && !transform.HasNonIntegerTranslation()) { devPixelDirty.RoundOut(); - finalFillRect = fill.Intersect(devPixelDirty); + fill = fill.Intersect(devPixelDirty); } - if (finalFillRect.IsEmpty()) + if (fill.IsEmpty()) return SnappedImageDrawingParameters(); - return SnappedImageDrawingParameters(transform, finalFillRect, intSubimage, - didSnap); + gfxRect imageSpaceFill(didSnap ? invTransform.Transform(fill) + : invTransform.TransformBounds(fill)); + + // If we didn't snap, we need to post-multiply the matrix on the context to + // get the final matrix we'll draw with, because we didn't take it into + // account when computing the matrices above. + if (!didSnap) { + transform = transform * currentMatrix; + } + + ImageRegion region = + ImageRegion::CreateWithSamplingRestriction(imageSpaceFill, subimage); + return SnappedImageDrawingParameters(transform, intImageSize, region); } @@ -5091,7 +5133,6 @@ DrawImageInternal(nsRenderingContext* aRenderingContext, const nsRect& aFill, const nsPoint& aAnchor, const nsRect& aDirty, - const nsIntSize& aImageSize, const SVGImageContext* aSVGContext, uint32_t aImageFlags) { @@ -5107,21 +5148,20 @@ DrawImageInternal(nsRenderingContext* aRenderingContext, aPresContext->AppUnitsPerDevPixel(); gfxContext* ctx = aRenderingContext->ThebesContext(); - SnappedImageDrawingParameters drawingParams = - ComputeSnappedImageDrawingParameters(ctx, appUnitsPerDevPixel, aDest, aFill, - aAnchor, aDirty, aImageSize); + SnappedImageDrawingParameters params = + ComputeSnappedImageDrawingParameters(ctx, appUnitsPerDevPixel, aDest, + aFill, aAnchor, aDirty, aImage, + aGraphicsFilter, aImageFlags); - if (!drawingParams.mShouldDraw) + if (!params.shouldDraw) return NS_OK; - gfxContextMatrixAutoSaveRestore saveMatrix(ctx); - if (drawingParams.mResetCTM) { - ctx->IdentityMatrix(); - } + gfxContextMatrixAutoSaveRestore contextMatrixRestorer(ctx); + ctx->SetMatrix(params.imageSpaceToDeviceSpace); + + aImage->Draw(ctx, params.size, params.region, imgIContainer::FRAME_CURRENT, + aGraphicsFilter, ToMaybe(aSVGContext), aImageFlags); - aImage->Draw(ctx, aGraphicsFilter, drawingParams.mUserSpaceToImageSpace, - drawingParams.mFillRect, drawingParams.mSubimage, aImageSize, - aSVGContext, imgIContainer::FRAME_CURRENT, aImageFlags); return NS_OK; } @@ -5160,7 +5200,7 @@ nsLayoutUtils::DrawSingleUnscaledImage(nsRenderingContext* aRenderingContext, return DrawImageInternal(aRenderingContext, aPresContext, aImage, aGraphicsFilter, dest, fill, aDest, aDirty ? *aDirty : dest, - imageSize, nullptr, aImageFlags); + nullptr, aImageFlags); } /* static */ nsresult @@ -5174,29 +5214,21 @@ nsLayoutUtils::DrawSingleImage(nsRenderingContext* aRenderingContext, uint32_t aImageFlags, const nsRect* aSourceArea) { - nsIntSize imageSize; - if (aImage->GetType() == imgIContainer::TYPE_VECTOR) { - // We choose a size for vector images that emulates a raster image which - // is perfectly sized for the destination rect: each pixel in the image - // maps exactly to a single pixel on-screen. - nscoord appUnitsPerDevPx = - aPresContext->AppUnitsPerDevPixel(); - imageSize.width = NSAppUnitsToIntPixels(aDest.width, appUnitsPerDevPx); - imageSize.height = NSAppUnitsToIntPixels(aDest.height, appUnitsPerDevPx); - } else { - // Raster images have an intrinsic size, so we just use that. - aImage->GetWidth(&imageSize.width); - aImage->GetHeight(&imageSize.height); - } + nsIntSize imageSize(ComputeSizeForDrawingWithFallback(aImage, aDest.Size())); NS_ENSURE_TRUE(imageSize.width > 0 && imageSize.height > 0, NS_ERROR_FAILURE); nsRect source; + nsCOMPtr image; if (aSourceArea) { source = *aSourceArea; + nsIntRect subRect(source.x, source.y, source.width, source.height); + subRect.ScaleInverseRoundOut(nsDeviceContext::AppUnitsPerCSSPixel()); + image = ImageOps::Clip(aImage, subRect); } else { nscoord appUnitsPerCSSPixel = nsDeviceContext::AppUnitsPerCSSPixel(); source.SizeTo(imageSize.width*appUnitsPerCSSPixel, imageSize.height*appUnitsPerCSSPixel); + image = aImage; } nsRect dest = nsLayoutUtils::GetWholeImageDestination(imageSize, source, @@ -5207,8 +5239,8 @@ nsLayoutUtils::DrawSingleImage(nsRenderingContext* aRenderingContext, nsRect fill; fill.IntersectRect(aDest, dest); return DrawImageInternal(aRenderingContext, aPresContext, aImage, - aGraphicsFilter, dest, fill, - fill.TopLeft(), aDirty, imageSize, aSVGContext, aImageFlags); + aGraphicsFilter, dest, fill, fill.TopLeft(), + aDirty, aSVGContext, aImageFlags); } /* static */ void @@ -5231,50 +5263,17 @@ nsLayoutUtils::ComputeSizeForDrawing(imgIContainer *aImage, } } - -/* static */ nsresult -nsLayoutUtils::DrawBackgroundImage(nsRenderingContext* aRenderingContext, - nsPresContext* aPresContext, - imgIContainer* aImage, - const nsIntSize& aImageSize, - GraphicsFilter aGraphicsFilter, - const nsRect& aDest, - const nsRect& aFill, - const nsPoint& aAnchor, - const nsRect& aDirty, - uint32_t aImageFlags) -{ - PROFILER_LABEL("nsLayoutUtils", "DrawBackgroundImage", - js::ProfileEntry::Category::GRAPHICS); - - if (UseBackgroundNearestFiltering()) { - aGraphicsFilter = GraphicsFilter::FILTER_NEAREST; - } - - return DrawImageInternal(aRenderingContext, aPresContext, aImage, - aGraphicsFilter, - aDest, aFill, aAnchor, aDirty, - aImageSize, nullptr, aImageFlags); -} - -/* static */ nsresult -nsLayoutUtils::DrawImage(nsRenderingContext* aRenderingContext, - nsPresContext* aPresContext, - imgIContainer* aImage, - GraphicsFilter aGraphicsFilter, - const nsRect& aDest, - const nsRect& aFill, - const nsPoint& aAnchor, - const nsRect& aDirty, - uint32_t aImageFlags) +/* static */ nsIntSize +nsLayoutUtils::ComputeSizeForDrawingWithFallback(imgIContainer* aImage, + const nsSize& aFallbackSize) { nsIntSize imageSize; nsSize imageRatio; bool gotHeight, gotWidth; ComputeSizeForDrawing(aImage, imageSize, imageRatio, gotWidth, gotHeight); - // XXX Dimensionless images shouldn't fall back to filled-area size -- the - // caller should provide the image size, a la DrawBackgroundImage. + // If we didn't get both width and height, try to compute them using the + // intrinsic ratio of the image. if (gotWidth != gotHeight) { if (!gotWidth) { if (imageRatio.height != 0) { @@ -5295,17 +5294,58 @@ nsLayoutUtils::DrawImage(nsRenderingContext* aRenderingContext, } } + // If we still don't have a width or height, just use the fallback size the + // caller provided. if (!gotWidth) { - imageSize.width = nsPresContext::AppUnitsToIntCSSPixels(aFill.width); + imageSize.width = nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.width); } if (!gotHeight) { - imageSize.height = nsPresContext::AppUnitsToIntCSSPixels(aFill.height); + imageSize.height = nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.height); } + return imageSize; +} + +/* static */ nsresult +nsLayoutUtils::DrawBackgroundImage(nsRenderingContext* aRenderingContext, + nsPresContext* aPresContext, + imgIContainer* aImage, + const nsIntSize& aImageSize, + GraphicsFilter aGraphicsFilter, + const nsRect& aDest, + const nsRect& aFill, + const nsPoint& aAnchor, + const nsRect& aDirty, + uint32_t aImageFlags) +{ + PROFILER_LABEL("layout", "nsLayoutUtils::DrawBackgroundImage", + js::ProfileEntry::Category::GRAPHICS); + + if (UseBackgroundNearestFiltering()) { + aGraphicsFilter = GraphicsFilter::FILTER_NEAREST; + } + + SVGImageContext svgContext(aImageSize, Nothing()); + return DrawImageInternal(aRenderingContext, aPresContext, aImage, - aGraphicsFilter, - aDest, aFill, aAnchor, aDirty, - imageSize, nullptr, aImageFlags); + aGraphicsFilter, aDest, aFill, aAnchor, + aDirty, &svgContext, aImageFlags); +} + +/* static */ nsresult +nsLayoutUtils::DrawImage(nsRenderingContext* aRenderingContext, + nsPresContext* aPresContext, + imgIContainer* aImage, + GraphicsFilter aGraphicsFilter, + const nsRect& aDest, + const nsRect& aFill, + const nsPoint& aAnchor, + const nsRect& aDirty, + uint32_t aImageFlags) +{ + return DrawImageInternal(aRenderingContext, aPresContext, aImage, + aGraphicsFilter, aDest, aFill, aAnchor, + aDirty, nullptr, aImageFlags); } /* static */ nsRect diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index e841964eb31..6d05d66b637 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -1455,14 +1455,14 @@ public: * @param aImageFlags Image flags of the imgIContainer::FLAG_* variety */ static nsresult DrawImage(nsRenderingContext* aRenderingContext, - nsPresContext* aPresContext, - imgIContainer* aImage, - GraphicsFilter aGraphicsFilter, - const nsRect& aDest, - const nsRect& aFill, - const nsPoint& aAnchor, - const nsRect& aDirty, - uint32_t aImageFlags); + nsPresContext* aPresContext, + imgIContainer* aImage, + GraphicsFilter aGraphicsFilter, + const nsRect& aDest, + const nsRect& aFill, + const nsPoint& aAnchor, + const nsRect& aDirty, + uint32_t aImageFlags); /** * Convert an nsRect to a gfxRect. @@ -1515,15 +1515,15 @@ public: * in appunits. For best results it should * be aligned with image pixels. */ - static nsresult DrawSingleImage(nsRenderingContext* aRenderingContext, - nsPresContext* aPresContext, - imgIContainer* aImage, - GraphicsFilter aGraphicsFilter, - const nsRect& aDest, - const nsRect& aDirty, + static nsresult DrawSingleImage(nsRenderingContext* aRenderingContext, + nsPresContext* aPresContext, + imgIContainer* aImage, + GraphicsFilter aGraphicsFilter, + const nsRect& aDest, + const nsRect& aDirty, const mozilla::SVGImageContext* aSVGContext, - uint32_t aImageFlags, - const nsRect* aSourceArea = nullptr); + uint32_t aImageFlags, + const nsRect* aSourceArea = nullptr); /** * Given an imgIContainer, this method attempts to obtain an intrinsic @@ -1547,6 +1547,17 @@ public: bool& aGotWidth, bool& aGotHeight); + /** + * Given an imgIContainer, this method attempts to obtain an intrinsic + * px-valued height & width for it. If the imgIContainer has a non-pixel + * value for either height or width, this method tries to generate a pixel + * value for that dimension using the intrinsic ratio (if available). If, + * after trying all these methods, no value is available for one or both + * dimensions, the corresponding dimension of aFallbackSize is used instead. + */ + static nsIntSize ComputeSizeForDrawingWithFallback(imgIContainer* aImage, + const nsSize& aFallbackSize); + /** * Given a source area of an image (in appunits) and a destination area * that we want to map that source area too, computes the area that diff --git a/layout/reftests/border-image/reftest.list b/layout/reftests/border-image/reftest.list index e3122914dbe..06123622974 100644 --- a/layout/reftests/border-image/reftest.list +++ b/layout/reftests/border-image/reftest.list @@ -3,7 +3,9 @@ == solid-image-2.html solid-image-2-ref.html == solid-image-2a.html solid-image-2-ref.html == multicolor-image-1.html multicolor-image-1-ref.html -== multicolor-image-2.html multicolor-image-2-ref.html +# This is fuzzy temporarily until bug 1044702 makes it possible to use source +# clipping on Windows. (Any other fix would have a significant perf cost.) +fuzzy-if(winWidget,1,1) == multicolor-image-2.html multicolor-image-2-ref.html == multicolor-image-3.html multicolor-image-3-ref.html == multicolor-image-4.html multicolor-image-4-ref.html == multicolor-image-5.html multicolor-image-5-ref.html diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index 2dbb6c6b394..69b8e7c0caf 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -1720,7 +1720,7 @@ skip-if(B2G) == 751012-1b.html 751012-1-ref.html random-if(Android) == 753329-1.html about:blank == 758561-1.html 758561-1-ref.html fuzzy-if(true,1,90) random-if(Android&&AndroidVersion<15) == 759036-1.html 759036-1-ref.html -fuzzy-if(true,17,5885) random-if(Android&&AndroidVersion<15) == 759036-2.html 759036-2-ref.html +fuzzy-if(true,17,5886) random-if(Android&&AndroidVersion<15) == 759036-2.html 759036-2-ref.html random-if(Android&&AndroidVersion<15) == 776265-1a.html 776265-1-ref.html == 776265-1b.html 776265-1-ref.html == 776265-1c.html 776265-1-ref.html diff --git a/layout/reftests/image/reftest.list b/layout/reftests/image/reftest.list index 944d08b4713..7aeecb4229c 100644 --- a/layout/reftests/image/reftest.list +++ b/layout/reftests/image/reftest.list @@ -2,7 +2,7 @@ fuzzy-if(Android,8,30) == background-image-zoom-1.html background-image-zoom-1-r == background-image-zoom-2.html about:blank == image-seam-1a.html image-seam-1-ref.html == image-seam-1b.html image-seam-1-ref.html -fails-if(cocoaWidget) == image-seam-2.html image-seam-2-ref.html # Quartz doesn't support EXTEND_PAD (bug 567370) +== image-seam-2.html image-seam-2-ref.html skip-if(B2G&&browserIsRemote) == image-zoom-1.html image-zoom-1-ref.html skip-if(B2G&&browserIsRemote) == image-zoom-2.html image-zoom-1-ref.html == invalid-url-image-1.html invalid-url-image-1-ref.html diff --git a/layout/reftests/svg/as-image/reftest.list b/layout/reftests/svg/as-image/reftest.list index c92dedc0a57..22df641ee84 100644 --- a/layout/reftests/svg/as-image/reftest.list +++ b/layout/reftests/svg/as-image/reftest.list @@ -41,9 +41,10 @@ skip-if(B2G) == canvas-drawImage-scale-1a.html lime100x100-ref.html skip-if(B2G) == canvas-drawImage-scale-1b.html lime100x100-ref.html skip-if(B2G) == canvas-drawImage-scale-1c.html lime100x100-ref.html -# Fuzzyness and fails for scaling should be fixed by bug 941467 +# Fails on Android versions where we apply a zoom by default, because the +# resolution of a canvas element is fixed regardless of zoom level. fuzzy(1,2) fails-if(Android&&AndroidVersion<15&&AndroidVersion!=10) == canvas-drawImage-scale-2a.html canvas-drawImage-scale-2-ref.html -fails == canvas-drawImage-scale-2b.html canvas-drawImage-scale-2-ref.html +fuzzy(1,2) fails-if(Android&&AndroidVersion<15&&AndroidVersion!=10) == canvas-drawImage-scale-2b.html canvas-drawImage-scale-2-ref.html skip-if(B2G) == canvas-drawImage-slice-1a.html lime100x100-ref.html == canvas-drawImage-slice-1b.html lime100x100-ref.html diff --git a/layout/svg/SVGImageContext.h b/layout/svg/SVGImageContext.h index 22560620b07..a78c764c0ae 100644 --- a/layout/svg/SVGImageContext.h +++ b/layout/svg/SVGImageContext.h @@ -6,28 +6,38 @@ #ifndef MOZILLA_SVGCONTEXT_H_ #define MOZILLA_SVGCONTEXT_H_ +#include "mozilla/Maybe.h" #include "SVGPreserveAspectRatio.h" namespace mozilla { // SVG image-specific rendering context. For imgIContainer::Draw. -// Used to pass information about overridden attributes from an SVG -// element to the image's internal SVG document when it's drawn. +// Used to pass information such as +// - viewport information from CSS, and +// - overridden attributes from an SVG element +// to the image's internal SVG document when it's drawn. class SVGImageContext { public: SVGImageContext() { } - SVGImageContext(SVGPreserveAspectRatio aPreserveAspectRatio) - : mPreserveAspectRatio(aPreserveAspectRatio) + SVGImageContext(nsIntSize aViewportSize, + Maybe aPreserveAspectRatio) + : mViewportSize(aViewportSize) + , mPreserveAspectRatio(aPreserveAspectRatio) { } - const SVGPreserveAspectRatio& GetPreserveAspectRatio() const { + const nsIntSize& GetViewportSize() const { + return mViewportSize; + } + + const Maybe& GetPreserveAspectRatio() const { return mPreserveAspectRatio; } bool operator==(const SVGImageContext& aOther) const { - return mPreserveAspectRatio == aOther.mPreserveAspectRatio; + return mViewportSize == aOther.mViewportSize && + mPreserveAspectRatio == aOther.mPreserveAspectRatio; } bool operator!=(const SVGImageContext& aOther) const { @@ -35,11 +45,18 @@ public: } uint32_t Hash() const { - return mPreserveAspectRatio.Hash(); + return HashGeneric(mViewportSize.width, + mViewportSize.height, + mPreserveAspectRatio.map(HashPAR).valueOr(0)); } private: - SVGPreserveAspectRatio mPreserveAspectRatio; + static uint32_t HashPAR(const SVGPreserveAspectRatio& aPAR) { + return aPAR.Hash(); + } + + nsIntSize mViewportSize; + Maybe mPreserveAspectRatio; }; } // namespace mozilla diff --git a/layout/svg/nsSVGImageFrame.cpp b/layout/svg/nsSVGImageFrame.cpp index d403cdacddf..85931495ed5 100644 --- a/layout/svg/nsSVGImageFrame.cpp +++ b/layout/svg/nsSVGImageFrame.cpp @@ -377,7 +377,8 @@ nsSVGImageFrame::PaintSVG(nsRenderingContext *aContext, if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) { // Package up the attributes of this image element which can override the // attributes of mImageContainer's internal SVG document. - SVGImageContext context(imgElem->mPreserveAspectRatio.GetAnimValue()); + SVGImageContext context(nsIntSize(width, height), + Some(imgElem->mPreserveAspectRatio.GetAnimValue())); nsRect destRect(0, 0, appUnitsPerDevPx * width, diff --git a/widget/cocoa/nsCocoaUtils.mm b/widget/cocoa/nsCocoaUtils.mm index bac8a1a3d06..732fb4ed097 100644 --- a/widget/cocoa/nsCocoaUtils.mm +++ b/widget/cocoa/nsCocoaUtils.mm @@ -3,8 +3,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#include "gfx2DGlue.h" #include "gfxPlatform.h" #include "gfxUtils.h" +#include "ImageRegion.h" #include "nsCocoaUtils.h" #include "nsChildView.h" #include "nsMenuBarX.h" @@ -18,6 +22,7 @@ #include "nsMenuUtilsX.h" #include "nsToolkit.h" #include "nsCRT.h" +#include "SVGImageContext.h" #include "mozilla/gfx/2D.h" #include "mozilla/MiscEvents.h" #include "mozilla/Preferences.h" @@ -35,6 +40,8 @@ using mozilla::gfx::IntRect; using mozilla::gfx::IntSize; using mozilla::gfx::SurfaceFormat; using mozilla::gfx::SourceSurface; +using mozilla::image::ImageRegion; +using std::ceil; static float MenuBarScreenHeight() @@ -473,11 +480,11 @@ nsresult nsCocoaUtils::CreateNSImageFromImageContainer(imgIContainer *aImage, ui // Render a vector image at the correct resolution on a retina display if (aImage->GetType() == imgIContainer::TYPE_VECTOR && scaleFactor != 1.0f) { - int scaledWidth = (int)ceilf(width * scaleFactor); - int scaledHeight = (int)ceilf(height * scaleFactor); + gfxIntSize scaledSize(ceil(width * scaleFactor), + ceil(height * scaleFactor)); RefPtr drawTarget = gfxPlatform::GetPlatform()-> - CreateOffscreenContentDrawTarget(IntSize(scaledWidth, scaledHeight), + CreateOffscreenContentDrawTarget(ToIntSize(scaledSize), SurfaceFormat::B8G8R8A8); if (!drawTarget) { NS_ERROR("Failed to create DrawTarget"); @@ -490,11 +497,9 @@ nsresult nsCocoaUtils::CreateNSImageFromImageContainer(imgIContainer *aImage, ui return NS_ERROR_FAILURE; } - aImage->Draw(context, GraphicsFilter::FILTER_NEAREST, gfxMatrix(), - gfxRect(0.0f, 0.0f, scaledWidth, scaledHeight), - nsIntRect(0, 0, width, height), - nsIntSize(scaledWidth, scaledHeight), - nullptr, aWhichFrame, imgIContainer::FLAG_SYNC_DECODE); + aImage->Draw(context, scaledSize, ImageRegion::Create(scaledSize), + aWhichFrame, GraphicsFilter::FILTER_NEAREST, Nothing(), + imgIContainer::FLAG_SYNC_DECODE); surface = drawTarget->Snapshot(); } else { diff --git a/widget/xpwidgets/nsBaseDragService.cpp b/widget/xpwidgets/nsBaseDragService.cpp index 0a404df18b4..5b3d89938fa 100644 --- a/widget/xpwidgets/nsBaseDragService.cpp +++ b/widget/xpwidgets/nsBaseDragService.cpp @@ -28,9 +28,11 @@ #include "nsIImageLoadingContent.h" #include "imgIContainer.h" #include "imgIRequest.h" +#include "ImageRegion.h" #include "nsRegion.h" #include "nsXULPopupManager.h" #include "nsMenuPopupFrame.h" +#include "SVGImageContext.h" #include "mozilla/MouseEvents.h" #include "mozilla/Preferences.h" #include "mozilla/gfx/2D.h" @@ -40,8 +42,9 @@ #include using namespace mozilla; -using namespace mozilla::gfx; using namespace mozilla::dom; +using namespace mozilla::gfx; +using namespace mozilla::image; #define DRAGIMAGES_PREF "nglayout.enable_drag_images" @@ -629,12 +632,9 @@ nsBaseDragService::DrawDragForImage(nsPresContext* aPresContext, if (!ctx) return NS_ERROR_FAILURE; - gfxRect outRect(0, 0, destSize.width, destSize.height); - gfxMatrix scale = - gfxMatrix().Scale(srcSize.width/outRect.Width(), srcSize.height/outRect.Height()); - nsIntRect imgSize(0, 0, srcSize.width, srcSize.height); - imgContainer->Draw(ctx, GraphicsFilter::FILTER_GOOD, scale, outRect, imgSize, - destSize, nullptr, imgIContainer::FRAME_CURRENT, + imgContainer->Draw(ctx, destSize, ImageRegion::Create(destSize), + imgIContainer::FRAME_CURRENT, + GraphicsFilter::FILTER_GOOD, Nothing(), imgIContainer::FLAG_SYNC_DECODE); *aSurface = dt->Snapshot(); } else {