1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/image/src/ClippedImage.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,424 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "gfxDrawable.h" 1.10 +#include "gfxPlatform.h" 1.11 +#include "gfxUtils.h" 1.12 +#include "mozilla/gfx/2D.h" 1.13 +#include "mozilla/RefPtr.h" 1.14 + 1.15 +#include "ClippedImage.h" 1.16 +#include "Orientation.h" 1.17 +#include "SVGImageContext.h" 1.18 + 1.19 +using namespace mozilla; 1.20 +using namespace mozilla::gfx; 1.21 +using mozilla::layers::LayerManager; 1.22 +using mozilla::layers::ImageContainer; 1.23 + 1.24 +namespace mozilla { 1.25 +namespace image { 1.26 + 1.27 +class ClippedImageCachedSurface 1.28 +{ 1.29 +public: 1.30 + ClippedImageCachedSurface(TemporaryRef<SourceSurface> aSurface, 1.31 + const nsIntSize& aViewportSize, 1.32 + const SVGImageContext* aSVGContext, 1.33 + float aFrame, 1.34 + uint32_t aFlags) 1.35 + : mSurface(aSurface) 1.36 + , mViewportSize(aViewportSize) 1.37 + , mFrame(aFrame) 1.38 + , mFlags(aFlags) 1.39 + { 1.40 + MOZ_ASSERT(mSurface, "Must have a valid surface"); 1.41 + if (aSVGContext) { 1.42 + mSVGContext.construct(*aSVGContext); 1.43 + } 1.44 + } 1.45 + 1.46 + bool Matches(const nsIntSize& aViewportSize, 1.47 + const SVGImageContext* aSVGContext, 1.48 + float aFrame, 1.49 + uint32_t aFlags) 1.50 + { 1.51 + bool matchesSVGContext = (!aSVGContext && mSVGContext.empty()) || 1.52 + *aSVGContext == mSVGContext.ref(); 1.53 + return mViewportSize == aViewportSize && 1.54 + matchesSVGContext && 1.55 + mFrame == aFrame && 1.56 + mFlags == aFlags; 1.57 + } 1.58 + 1.59 + TemporaryRef<SourceSurface> Surface() { 1.60 + return mSurface; 1.61 + } 1.62 + 1.63 +private: 1.64 + RefPtr<SourceSurface> mSurface; 1.65 + const nsIntSize mViewportSize; 1.66 + Maybe<SVGImageContext> mSVGContext; 1.67 + const float mFrame; 1.68 + const uint32_t mFlags; 1.69 +}; 1.70 + 1.71 +class DrawSingleTileCallback : public gfxDrawingCallback 1.72 +{ 1.73 +public: 1.74 + DrawSingleTileCallback(ClippedImage* aImage, 1.75 + const nsIntRect& aClip, 1.76 + const nsIntSize& aViewportSize, 1.77 + const SVGImageContext* aSVGContext, 1.78 + uint32_t aWhichFrame, 1.79 + uint32_t aFlags) 1.80 + : mImage(aImage) 1.81 + , mClip(aClip) 1.82 + , mViewportSize(aViewportSize) 1.83 + , mSVGContext(aSVGContext) 1.84 + , mWhichFrame(aWhichFrame) 1.85 + , mFlags(aFlags) 1.86 + { 1.87 + MOZ_ASSERT(mImage, "Must have an image to clip"); 1.88 + } 1.89 + 1.90 + virtual bool operator()(gfxContext* aContext, 1.91 + const gfxRect& aFillRect, 1.92 + const GraphicsFilter& aFilter, 1.93 + const gfxMatrix& aTransform) 1.94 + { 1.95 + // Draw the image. |gfxCallbackDrawable| always calls this function with 1.96 + // arguments that guarantee we never tile. 1.97 + mImage->DrawSingleTile(aContext, aFilter, aTransform, aFillRect, mClip, 1.98 + mViewportSize, mSVGContext, mWhichFrame, mFlags); 1.99 + 1.100 + return true; 1.101 + } 1.102 + 1.103 +private: 1.104 + nsRefPtr<ClippedImage> mImage; 1.105 + const nsIntRect mClip; 1.106 + const nsIntSize mViewportSize; 1.107 + const SVGImageContext* mSVGContext; 1.108 + const uint32_t mWhichFrame; 1.109 + const uint32_t mFlags; 1.110 +}; 1.111 + 1.112 +ClippedImage::ClippedImage(Image* aImage, 1.113 + nsIntRect aClip) 1.114 + : ImageWrapper(aImage) 1.115 + , mClip(aClip) 1.116 +{ 1.117 + MOZ_ASSERT(aImage != nullptr, "ClippedImage requires an existing Image"); 1.118 +} 1.119 + 1.120 +ClippedImage::~ClippedImage() 1.121 +{ } 1.122 + 1.123 +bool 1.124 +ClippedImage::ShouldClip() 1.125 +{ 1.126 + // We need to evaluate the clipping region against the image's width and height 1.127 + // once they're available to determine if it's valid and whether we actually 1.128 + // need to do any work. We may fail if the image's width and height aren't 1.129 + // available yet, in which case we'll try again later. 1.130 + if (mShouldClip.empty()) { 1.131 + int32_t width, height; 1.132 + nsRefPtr<imgStatusTracker> innerImageStatusTracker = 1.133 + InnerImage()->GetStatusTracker(); 1.134 + if (InnerImage()->HasError()) { 1.135 + // If there's a problem with the inner image we'll let it handle everything. 1.136 + mShouldClip.construct(false); 1.137 + } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&width)) && width > 0 && 1.138 + NS_SUCCEEDED(InnerImage()->GetHeight(&height)) && height > 0) { 1.139 + // Clamp the clipping region to the size of the underlying image. 1.140 + mClip = mClip.Intersect(nsIntRect(0, 0, width, height)); 1.141 + 1.142 + // If the clipping region is the same size as the underlying image we 1.143 + // don't have to do anything. 1.144 + mShouldClip.construct(!mClip.IsEqualInterior(nsIntRect(0, 0, width, height))); 1.145 + } else if (innerImageStatusTracker && 1.146 + innerImageStatusTracker->IsLoading()) { 1.147 + // The image just hasn't finished loading yet. We don't yet know whether 1.148 + // clipping with be needed or not for now. Just return without memoizing 1.149 + // anything. 1.150 + return false; 1.151 + } else { 1.152 + // We have a fully loaded image without a clearly defined width and 1.153 + // height. This can happen with SVG images. 1.154 + mShouldClip.construct(false); 1.155 + } 1.156 + } 1.157 + 1.158 + MOZ_ASSERT(!mShouldClip.empty(), "Should have computed a result"); 1.159 + return mShouldClip.ref(); 1.160 +} 1.161 + 1.162 +NS_IMPL_ISUPPORTS(ClippedImage, imgIContainer) 1.163 + 1.164 +nsIntRect 1.165 +ClippedImage::FrameRect(uint32_t aWhichFrame) 1.166 +{ 1.167 + if (!ShouldClip()) { 1.168 + return InnerImage()->FrameRect(aWhichFrame); 1.169 + } 1.170 + 1.171 + return nsIntRect(0, 0, mClip.width, mClip.height); 1.172 +} 1.173 + 1.174 +NS_IMETHODIMP 1.175 +ClippedImage::GetWidth(int32_t* aWidth) 1.176 +{ 1.177 + if (!ShouldClip()) { 1.178 + return InnerImage()->GetWidth(aWidth); 1.179 + } 1.180 + 1.181 + *aWidth = mClip.width; 1.182 + return NS_OK; 1.183 +} 1.184 + 1.185 +NS_IMETHODIMP 1.186 +ClippedImage::GetHeight(int32_t* aHeight) 1.187 +{ 1.188 + if (!ShouldClip()) { 1.189 + return InnerImage()->GetHeight(aHeight); 1.190 + } 1.191 + 1.192 + *aHeight = mClip.height; 1.193 + return NS_OK; 1.194 +} 1.195 + 1.196 +NS_IMETHODIMP 1.197 +ClippedImage::GetIntrinsicSize(nsSize* aSize) 1.198 +{ 1.199 + if (!ShouldClip()) { 1.200 + return InnerImage()->GetIntrinsicSize(aSize); 1.201 + } 1.202 + 1.203 + *aSize = nsSize(mClip.width, mClip.height); 1.204 + return NS_OK; 1.205 +} 1.206 + 1.207 +NS_IMETHODIMP 1.208 +ClippedImage::GetIntrinsicRatio(nsSize* aRatio) 1.209 +{ 1.210 + if (!ShouldClip()) { 1.211 + return InnerImage()->GetIntrinsicRatio(aRatio); 1.212 + } 1.213 + 1.214 + *aRatio = nsSize(mClip.width, mClip.height); 1.215 + return NS_OK; 1.216 +} 1.217 + 1.218 +NS_IMETHODIMP_(TemporaryRef<SourceSurface>) 1.219 +ClippedImage::GetFrame(uint32_t aWhichFrame, 1.220 + uint32_t aFlags) 1.221 +{ 1.222 + return GetFrameInternal(mClip.Size(), nullptr, aWhichFrame, aFlags); 1.223 +} 1.224 + 1.225 +TemporaryRef<SourceSurface> 1.226 +ClippedImage::GetFrameInternal(const nsIntSize& aViewportSize, 1.227 + const SVGImageContext* aSVGContext, 1.228 + uint32_t aWhichFrame, 1.229 + uint32_t aFlags) 1.230 +{ 1.231 + if (!ShouldClip()) { 1.232 + return InnerImage()->GetFrame(aWhichFrame, aFlags); 1.233 + } 1.234 + 1.235 + float frameToDraw = InnerImage()->GetFrameIndex(aWhichFrame); 1.236 + if (!mCachedSurface || !mCachedSurface->Matches(aViewportSize, 1.237 + aSVGContext, 1.238 + frameToDraw, 1.239 + aFlags)) { 1.240 + // Create a surface to draw into. 1.241 + RefPtr<DrawTarget> target = gfxPlatform::GetPlatform()-> 1.242 + CreateOffscreenContentDrawTarget(IntSize(mClip.width, mClip.height), 1.243 + SurfaceFormat::B8G8R8A8); 1.244 + 1.245 + nsRefPtr<gfxContext> ctx = new gfxContext(target); 1.246 + 1.247 + // Create our callback. 1.248 + nsRefPtr<gfxDrawingCallback> drawTileCallback = 1.249 + new DrawSingleTileCallback(this, mClip, aViewportSize, aSVGContext, aWhichFrame, aFlags); 1.250 + nsRefPtr<gfxDrawable> drawable = 1.251 + new gfxCallbackDrawable(drawTileCallback, mClip.Size()); 1.252 + 1.253 + // Actually draw. The callback will end up invoking DrawSingleTile. 1.254 + gfxRect imageRect(0, 0, mClip.width, mClip.height); 1.255 + gfxUtils::DrawPixelSnapped(ctx, drawable, gfxMatrix(), 1.256 + imageRect, imageRect, imageRect, imageRect, 1.257 + gfxImageFormat::ARGB32, 1.258 + GraphicsFilter::FILTER_FAST); 1.259 + 1.260 + // Cache the resulting surface. 1.261 + mCachedSurface = new ClippedImageCachedSurface(target->Snapshot(), 1.262 + aViewportSize, 1.263 + aSVGContext, 1.264 + frameToDraw, 1.265 + aFlags); 1.266 + } 1.267 + 1.268 + MOZ_ASSERT(mCachedSurface, "Should have a cached surface now"); 1.269 + return mCachedSurface->Surface(); 1.270 +} 1.271 + 1.272 +NS_IMETHODIMP 1.273 +ClippedImage::GetImageContainer(LayerManager* aManager, ImageContainer** _retval) 1.274 +{ 1.275 + // XXX(seth): We currently don't have a way of clipping the result of 1.276 + // GetImageContainer. We work around this by always returning null, but if it 1.277 + // ever turns out that ClippedImage is widely used on codepaths that can 1.278 + // actually benefit from GetImageContainer, it would be a good idea to fix 1.279 + // that method for performance reasons. 1.280 + 1.281 + if (!ShouldClip()) { 1.282 + return InnerImage()->GetImageContainer(aManager, _retval); 1.283 + } 1.284 + 1.285 + *_retval = nullptr; 1.286 + return NS_OK; 1.287 +} 1.288 + 1.289 +bool 1.290 +ClippedImage::MustCreateSurface(gfxContext* aContext, 1.291 + const gfxMatrix& aTransform, 1.292 + const gfxRect& aSourceRect, 1.293 + const nsIntRect& aSubimage, 1.294 + const uint32_t aFlags) const 1.295 +{ 1.296 + gfxRect gfxImageRect(0, 0, mClip.width, mClip.height); 1.297 + nsIntRect intImageRect(0, 0, mClip.width, mClip.height); 1.298 + bool willTile = !gfxImageRect.Contains(aSourceRect) && 1.299 + !(aFlags & imgIContainer::FLAG_CLAMP); 1.300 + bool willResample = (aContext->CurrentMatrix().HasNonIntegerTranslation() || 1.301 + aTransform.HasNonIntegerTranslation()) && 1.302 + (willTile || !aSubimage.Contains(intImageRect)); 1.303 + return willTile || willResample; 1.304 +} 1.305 + 1.306 +NS_IMETHODIMP 1.307 +ClippedImage::Draw(gfxContext* aContext, 1.308 + GraphicsFilter aFilter, 1.309 + const gfxMatrix& aUserSpaceToImageSpace, 1.310 + const gfxRect& aFill, 1.311 + const nsIntRect& aSubimage, 1.312 + const nsIntSize& aViewportSize, 1.313 + const SVGImageContext* aSVGContext, 1.314 + uint32_t aWhichFrame, 1.315 + uint32_t aFlags) 1.316 +{ 1.317 + if (!ShouldClip()) { 1.318 + return InnerImage()->Draw(aContext, aFilter, aUserSpaceToImageSpace, 1.319 + aFill, aSubimage, aViewportSize, aSVGContext, 1.320 + aWhichFrame, aFlags); 1.321 + } 1.322 + 1.323 + // Check for tiling. If we need to tile then we need to create a 1.324 + // gfxCallbackDrawable to handle drawing for us. 1.325 + gfxRect sourceRect = aUserSpaceToImageSpace.Transform(aFill); 1.326 + if (MustCreateSurface(aContext, aUserSpaceToImageSpace, sourceRect, aSubimage, aFlags)) { 1.327 + // Create a temporary surface containing a single tile of this image. 1.328 + // GetFrame will call DrawSingleTile internally. 1.329 + RefPtr<SourceSurface> surface = 1.330 + GetFrameInternal(aViewportSize, aSVGContext, aWhichFrame, aFlags); 1.331 + NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); 1.332 + 1.333 + // Create a drawable from that surface. 1.334 + nsRefPtr<gfxSurfaceDrawable> drawable = 1.335 + new gfxSurfaceDrawable(surface, gfxIntSize(mClip.width, mClip.height)); 1.336 + 1.337 + // Draw. 1.338 + gfxRect imageRect(0, 0, mClip.width, mClip.height); 1.339 + gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height); 1.340 + gfxUtils::DrawPixelSnapped(aContext, drawable, aUserSpaceToImageSpace, 1.341 + subimage, sourceRect, imageRect, aFill, 1.342 + gfxImageFormat::ARGB32, aFilter); 1.343 + 1.344 + return NS_OK; 1.345 + } 1.346 + 1.347 + // Determine the appropriate subimage for the inner image. 1.348 + nsIntRect innerSubimage(aSubimage); 1.349 + innerSubimage.MoveBy(mClip.x, mClip.y); 1.350 + innerSubimage.Intersect(mClip); 1.351 + 1.352 + return DrawSingleTile(aContext, aFilter, aUserSpaceToImageSpace, aFill, innerSubimage, 1.353 + aViewportSize, aSVGContext, aWhichFrame, aFlags); 1.354 +} 1.355 + 1.356 +gfxFloat 1.357 +ClippedImage::ClampFactor(const gfxFloat aToClamp, const int aReference) const 1.358 +{ 1.359 + return aToClamp > aReference ? aReference / aToClamp 1.360 + : 1.0; 1.361 +} 1.362 + 1.363 +nsresult 1.364 +ClippedImage::DrawSingleTile(gfxContext* aContext, 1.365 + GraphicsFilter aFilter, 1.366 + const gfxMatrix& aUserSpaceToImageSpace, 1.367 + const gfxRect& aFill, 1.368 + const nsIntRect& aSubimage, 1.369 + const nsIntSize& aViewportSize, 1.370 + const SVGImageContext* aSVGContext, 1.371 + uint32_t aWhichFrame, 1.372 + uint32_t aFlags) 1.373 +{ 1.374 + MOZ_ASSERT(!MustCreateSurface(aContext, aUserSpaceToImageSpace, 1.375 + aUserSpaceToImageSpace.Transform(aFill), 1.376 + aSubimage - nsIntPoint(mClip.x, mClip.y), aFlags), 1.377 + "DrawSingleTile shouldn't need to create a surface"); 1.378 + 1.379 + // Make the viewport reflect the original image's size. 1.380 + nsIntSize viewportSize(aViewportSize); 1.381 + int32_t imgWidth, imgHeight; 1.382 + if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) && 1.383 + NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) { 1.384 + viewportSize = nsIntSize(imgWidth, imgHeight); 1.385 + } else { 1.386 + MOZ_ASSERT(false, "If ShouldClip() led us to draw then we should never get here"); 1.387 + } 1.388 + 1.389 + // Add a translation to the transform to reflect the clipping region. 1.390 + gfxMatrix transform(aUserSpaceToImageSpace); 1.391 + transform.Multiply(gfxMatrix().Translate(gfxPoint(mClip.x, mClip.y))); 1.392 + 1.393 + // "Clamp the source rectangle" to the clipping region's width and height. 1.394 + // Really, this means modifying the transform to get the results we want. 1.395 + gfxRect sourceRect = transform.Transform(aFill); 1.396 + if (sourceRect.width > mClip.width || sourceRect.height > mClip.height) { 1.397 + gfxMatrix clampSource; 1.398 + clampSource.Translate(gfxPoint(sourceRect.x, sourceRect.y)); 1.399 + clampSource.Scale(ClampFactor(sourceRect.width, mClip.width), 1.400 + ClampFactor(sourceRect.height, mClip.height)); 1.401 + clampSource.Translate(gfxPoint(-sourceRect.x, -sourceRect.y)); 1.402 + transform.Multiply(clampSource); 1.403 + } 1.404 + 1.405 + return InnerImage()->Draw(aContext, aFilter, transform, aFill, aSubimage, 1.406 + viewportSize, aSVGContext, aWhichFrame, aFlags); 1.407 +} 1.408 + 1.409 +NS_IMETHODIMP 1.410 +ClippedImage::RequestDiscard() 1.411 +{ 1.412 + // We're very aggressive about discarding. 1.413 + mCachedSurface = nullptr; 1.414 + 1.415 + return InnerImage()->RequestDiscard(); 1.416 +} 1.417 + 1.418 +NS_IMETHODIMP_(Orientation) 1.419 +ClippedImage::GetOrientation() 1.420 +{ 1.421 + // XXX(seth): This should not actually be here; this is just to work around a 1.422 + // what appears to be a bug in MSVC's linker. 1.423 + return InnerImage()->GetOrientation(); 1.424 +} 1.425 + 1.426 +} // namespace image 1.427 +} // namespace mozilla