image/src/ClippedImage.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:c9cd0ea34926
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "gfxDrawable.h"
7 #include "gfxPlatform.h"
8 #include "gfxUtils.h"
9 #include "mozilla/gfx/2D.h"
10 #include "mozilla/RefPtr.h"
11
12 #include "ClippedImage.h"
13 #include "Orientation.h"
14 #include "SVGImageContext.h"
15
16 using namespace mozilla;
17 using namespace mozilla::gfx;
18 using mozilla::layers::LayerManager;
19 using mozilla::layers::ImageContainer;
20
21 namespace mozilla {
22 namespace image {
23
24 class ClippedImageCachedSurface
25 {
26 public:
27 ClippedImageCachedSurface(TemporaryRef<SourceSurface> aSurface,
28 const nsIntSize& aViewportSize,
29 const SVGImageContext* aSVGContext,
30 float aFrame,
31 uint32_t aFlags)
32 : mSurface(aSurface)
33 , mViewportSize(aViewportSize)
34 , mFrame(aFrame)
35 , mFlags(aFlags)
36 {
37 MOZ_ASSERT(mSurface, "Must have a valid surface");
38 if (aSVGContext) {
39 mSVGContext.construct(*aSVGContext);
40 }
41 }
42
43 bool Matches(const nsIntSize& aViewportSize,
44 const SVGImageContext* aSVGContext,
45 float aFrame,
46 uint32_t aFlags)
47 {
48 bool matchesSVGContext = (!aSVGContext && mSVGContext.empty()) ||
49 *aSVGContext == mSVGContext.ref();
50 return mViewportSize == aViewportSize &&
51 matchesSVGContext &&
52 mFrame == aFrame &&
53 mFlags == aFlags;
54 }
55
56 TemporaryRef<SourceSurface> Surface() {
57 return mSurface;
58 }
59
60 private:
61 RefPtr<SourceSurface> mSurface;
62 const nsIntSize mViewportSize;
63 Maybe<SVGImageContext> mSVGContext;
64 const float mFrame;
65 const uint32_t mFlags;
66 };
67
68 class DrawSingleTileCallback : public gfxDrawingCallback
69 {
70 public:
71 DrawSingleTileCallback(ClippedImage* aImage,
72 const nsIntRect& aClip,
73 const nsIntSize& aViewportSize,
74 const SVGImageContext* aSVGContext,
75 uint32_t aWhichFrame,
76 uint32_t aFlags)
77 : mImage(aImage)
78 , mClip(aClip)
79 , mViewportSize(aViewportSize)
80 , mSVGContext(aSVGContext)
81 , mWhichFrame(aWhichFrame)
82 , mFlags(aFlags)
83 {
84 MOZ_ASSERT(mImage, "Must have an image to clip");
85 }
86
87 virtual bool operator()(gfxContext* aContext,
88 const gfxRect& aFillRect,
89 const GraphicsFilter& aFilter,
90 const gfxMatrix& aTransform)
91 {
92 // Draw the image. |gfxCallbackDrawable| always calls this function with
93 // arguments that guarantee we never tile.
94 mImage->DrawSingleTile(aContext, aFilter, aTransform, aFillRect, mClip,
95 mViewportSize, mSVGContext, mWhichFrame, mFlags);
96
97 return true;
98 }
99
100 private:
101 nsRefPtr<ClippedImage> mImage;
102 const nsIntRect mClip;
103 const nsIntSize mViewportSize;
104 const SVGImageContext* mSVGContext;
105 const uint32_t mWhichFrame;
106 const uint32_t mFlags;
107 };
108
109 ClippedImage::ClippedImage(Image* aImage,
110 nsIntRect aClip)
111 : ImageWrapper(aImage)
112 , mClip(aClip)
113 {
114 MOZ_ASSERT(aImage != nullptr, "ClippedImage requires an existing Image");
115 }
116
117 ClippedImage::~ClippedImage()
118 { }
119
120 bool
121 ClippedImage::ShouldClip()
122 {
123 // We need to evaluate the clipping region against the image's width and height
124 // once they're available to determine if it's valid and whether we actually
125 // need to do any work. We may fail if the image's width and height aren't
126 // available yet, in which case we'll try again later.
127 if (mShouldClip.empty()) {
128 int32_t width, height;
129 nsRefPtr<imgStatusTracker> innerImageStatusTracker =
130 InnerImage()->GetStatusTracker();
131 if (InnerImage()->HasError()) {
132 // If there's a problem with the inner image we'll let it handle everything.
133 mShouldClip.construct(false);
134 } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&width)) && width > 0 &&
135 NS_SUCCEEDED(InnerImage()->GetHeight(&height)) && height > 0) {
136 // Clamp the clipping region to the size of the underlying image.
137 mClip = mClip.Intersect(nsIntRect(0, 0, width, height));
138
139 // If the clipping region is the same size as the underlying image we
140 // don't have to do anything.
141 mShouldClip.construct(!mClip.IsEqualInterior(nsIntRect(0, 0, width, height)));
142 } else if (innerImageStatusTracker &&
143 innerImageStatusTracker->IsLoading()) {
144 // The image just hasn't finished loading yet. We don't yet know whether
145 // clipping with be needed or not for now. Just return without memoizing
146 // anything.
147 return false;
148 } else {
149 // We have a fully loaded image without a clearly defined width and
150 // height. This can happen with SVG images.
151 mShouldClip.construct(false);
152 }
153 }
154
155 MOZ_ASSERT(!mShouldClip.empty(), "Should have computed a result");
156 return mShouldClip.ref();
157 }
158
159 NS_IMPL_ISUPPORTS(ClippedImage, imgIContainer)
160
161 nsIntRect
162 ClippedImage::FrameRect(uint32_t aWhichFrame)
163 {
164 if (!ShouldClip()) {
165 return InnerImage()->FrameRect(aWhichFrame);
166 }
167
168 return nsIntRect(0, 0, mClip.width, mClip.height);
169 }
170
171 NS_IMETHODIMP
172 ClippedImage::GetWidth(int32_t* aWidth)
173 {
174 if (!ShouldClip()) {
175 return InnerImage()->GetWidth(aWidth);
176 }
177
178 *aWidth = mClip.width;
179 return NS_OK;
180 }
181
182 NS_IMETHODIMP
183 ClippedImage::GetHeight(int32_t* aHeight)
184 {
185 if (!ShouldClip()) {
186 return InnerImage()->GetHeight(aHeight);
187 }
188
189 *aHeight = mClip.height;
190 return NS_OK;
191 }
192
193 NS_IMETHODIMP
194 ClippedImage::GetIntrinsicSize(nsSize* aSize)
195 {
196 if (!ShouldClip()) {
197 return InnerImage()->GetIntrinsicSize(aSize);
198 }
199
200 *aSize = nsSize(mClip.width, mClip.height);
201 return NS_OK;
202 }
203
204 NS_IMETHODIMP
205 ClippedImage::GetIntrinsicRatio(nsSize* aRatio)
206 {
207 if (!ShouldClip()) {
208 return InnerImage()->GetIntrinsicRatio(aRatio);
209 }
210
211 *aRatio = nsSize(mClip.width, mClip.height);
212 return NS_OK;
213 }
214
215 NS_IMETHODIMP_(TemporaryRef<SourceSurface>)
216 ClippedImage::GetFrame(uint32_t aWhichFrame,
217 uint32_t aFlags)
218 {
219 return GetFrameInternal(mClip.Size(), nullptr, aWhichFrame, aFlags);
220 }
221
222 TemporaryRef<SourceSurface>
223 ClippedImage::GetFrameInternal(const nsIntSize& aViewportSize,
224 const SVGImageContext* aSVGContext,
225 uint32_t aWhichFrame,
226 uint32_t aFlags)
227 {
228 if (!ShouldClip()) {
229 return InnerImage()->GetFrame(aWhichFrame, aFlags);
230 }
231
232 float frameToDraw = InnerImage()->GetFrameIndex(aWhichFrame);
233 if (!mCachedSurface || !mCachedSurface->Matches(aViewportSize,
234 aSVGContext,
235 frameToDraw,
236 aFlags)) {
237 // Create a surface to draw into.
238 RefPtr<DrawTarget> target = gfxPlatform::GetPlatform()->
239 CreateOffscreenContentDrawTarget(IntSize(mClip.width, mClip.height),
240 SurfaceFormat::B8G8R8A8);
241
242 nsRefPtr<gfxContext> ctx = new gfxContext(target);
243
244 // Create our callback.
245 nsRefPtr<gfxDrawingCallback> drawTileCallback =
246 new DrawSingleTileCallback(this, mClip, aViewportSize, aSVGContext, aWhichFrame, aFlags);
247 nsRefPtr<gfxDrawable> drawable =
248 new gfxCallbackDrawable(drawTileCallback, mClip.Size());
249
250 // Actually draw. The callback will end up invoking DrawSingleTile.
251 gfxRect imageRect(0, 0, mClip.width, mClip.height);
252 gfxUtils::DrawPixelSnapped(ctx, drawable, gfxMatrix(),
253 imageRect, imageRect, imageRect, imageRect,
254 gfxImageFormat::ARGB32,
255 GraphicsFilter::FILTER_FAST);
256
257 // Cache the resulting surface.
258 mCachedSurface = new ClippedImageCachedSurface(target->Snapshot(),
259 aViewportSize,
260 aSVGContext,
261 frameToDraw,
262 aFlags);
263 }
264
265 MOZ_ASSERT(mCachedSurface, "Should have a cached surface now");
266 return mCachedSurface->Surface();
267 }
268
269 NS_IMETHODIMP
270 ClippedImage::GetImageContainer(LayerManager* aManager, ImageContainer** _retval)
271 {
272 // XXX(seth): We currently don't have a way of clipping the result of
273 // GetImageContainer. We work around this by always returning null, but if it
274 // ever turns out that ClippedImage is widely used on codepaths that can
275 // actually benefit from GetImageContainer, it would be a good idea to fix
276 // that method for performance reasons.
277
278 if (!ShouldClip()) {
279 return InnerImage()->GetImageContainer(aManager, _retval);
280 }
281
282 *_retval = nullptr;
283 return NS_OK;
284 }
285
286 bool
287 ClippedImage::MustCreateSurface(gfxContext* aContext,
288 const gfxMatrix& aTransform,
289 const gfxRect& aSourceRect,
290 const nsIntRect& aSubimage,
291 const uint32_t aFlags) const
292 {
293 gfxRect gfxImageRect(0, 0, mClip.width, mClip.height);
294 nsIntRect intImageRect(0, 0, mClip.width, mClip.height);
295 bool willTile = !gfxImageRect.Contains(aSourceRect) &&
296 !(aFlags & imgIContainer::FLAG_CLAMP);
297 bool willResample = (aContext->CurrentMatrix().HasNonIntegerTranslation() ||
298 aTransform.HasNonIntegerTranslation()) &&
299 (willTile || !aSubimage.Contains(intImageRect));
300 return willTile || willResample;
301 }
302
303 NS_IMETHODIMP
304 ClippedImage::Draw(gfxContext* aContext,
305 GraphicsFilter aFilter,
306 const gfxMatrix& aUserSpaceToImageSpace,
307 const gfxRect& aFill,
308 const nsIntRect& aSubimage,
309 const nsIntSize& aViewportSize,
310 const SVGImageContext* aSVGContext,
311 uint32_t aWhichFrame,
312 uint32_t aFlags)
313 {
314 if (!ShouldClip()) {
315 return InnerImage()->Draw(aContext, aFilter, aUserSpaceToImageSpace,
316 aFill, aSubimage, aViewportSize, aSVGContext,
317 aWhichFrame, aFlags);
318 }
319
320 // Check for tiling. If we need to tile then we need to create a
321 // gfxCallbackDrawable to handle drawing for us.
322 gfxRect sourceRect = aUserSpaceToImageSpace.Transform(aFill);
323 if (MustCreateSurface(aContext, aUserSpaceToImageSpace, sourceRect, aSubimage, aFlags)) {
324 // Create a temporary surface containing a single tile of this image.
325 // GetFrame will call DrawSingleTile internally.
326 RefPtr<SourceSurface> surface =
327 GetFrameInternal(aViewportSize, aSVGContext, aWhichFrame, aFlags);
328 NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
329
330 // Create a drawable from that surface.
331 nsRefPtr<gfxSurfaceDrawable> drawable =
332 new gfxSurfaceDrawable(surface, gfxIntSize(mClip.width, mClip.height));
333
334 // Draw.
335 gfxRect imageRect(0, 0, mClip.width, mClip.height);
336 gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height);
337 gfxUtils::DrawPixelSnapped(aContext, drawable, aUserSpaceToImageSpace,
338 subimage, sourceRect, imageRect, aFill,
339 gfxImageFormat::ARGB32, aFilter);
340
341 return NS_OK;
342 }
343
344 // Determine the appropriate subimage for the inner image.
345 nsIntRect innerSubimage(aSubimage);
346 innerSubimage.MoveBy(mClip.x, mClip.y);
347 innerSubimage.Intersect(mClip);
348
349 return DrawSingleTile(aContext, aFilter, aUserSpaceToImageSpace, aFill, innerSubimage,
350 aViewportSize, aSVGContext, aWhichFrame, aFlags);
351 }
352
353 gfxFloat
354 ClippedImage::ClampFactor(const gfxFloat aToClamp, const int aReference) const
355 {
356 return aToClamp > aReference ? aReference / aToClamp
357 : 1.0;
358 }
359
360 nsresult
361 ClippedImage::DrawSingleTile(gfxContext* aContext,
362 GraphicsFilter aFilter,
363 const gfxMatrix& aUserSpaceToImageSpace,
364 const gfxRect& aFill,
365 const nsIntRect& aSubimage,
366 const nsIntSize& aViewportSize,
367 const SVGImageContext* aSVGContext,
368 uint32_t aWhichFrame,
369 uint32_t aFlags)
370 {
371 MOZ_ASSERT(!MustCreateSurface(aContext, aUserSpaceToImageSpace,
372 aUserSpaceToImageSpace.Transform(aFill),
373 aSubimage - nsIntPoint(mClip.x, mClip.y), aFlags),
374 "DrawSingleTile shouldn't need to create a surface");
375
376 // Make the viewport reflect the original image's size.
377 nsIntSize viewportSize(aViewportSize);
378 int32_t imgWidth, imgHeight;
379 if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) &&
380 NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) {
381 viewportSize = nsIntSize(imgWidth, imgHeight);
382 } else {
383 MOZ_ASSERT(false, "If ShouldClip() led us to draw then we should never get here");
384 }
385
386 // Add a translation to the transform to reflect the clipping region.
387 gfxMatrix transform(aUserSpaceToImageSpace);
388 transform.Multiply(gfxMatrix().Translate(gfxPoint(mClip.x, mClip.y)));
389
390 // "Clamp the source rectangle" to the clipping region's width and height.
391 // Really, this means modifying the transform to get the results we want.
392 gfxRect sourceRect = transform.Transform(aFill);
393 if (sourceRect.width > mClip.width || sourceRect.height > mClip.height) {
394 gfxMatrix clampSource;
395 clampSource.Translate(gfxPoint(sourceRect.x, sourceRect.y));
396 clampSource.Scale(ClampFactor(sourceRect.width, mClip.width),
397 ClampFactor(sourceRect.height, mClip.height));
398 clampSource.Translate(gfxPoint(-sourceRect.x, -sourceRect.y));
399 transform.Multiply(clampSource);
400 }
401
402 return InnerImage()->Draw(aContext, aFilter, transform, aFill, aSubimage,
403 viewportSize, aSVGContext, aWhichFrame, aFlags);
404 }
405
406 NS_IMETHODIMP
407 ClippedImage::RequestDiscard()
408 {
409 // We're very aggressive about discarding.
410 mCachedSurface = nullptr;
411
412 return InnerImage()->RequestDiscard();
413 }
414
415 NS_IMETHODIMP_(Orientation)
416 ClippedImage::GetOrientation()
417 {
418 // XXX(seth): This should not actually be here; this is just to work around a
419 // what appears to be a bug in MSVC's linker.
420 return InnerImage()->GetOrientation();
421 }
422
423 } // namespace image
424 } // namespace mozilla

mercurial