|
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 |