/src/mozilla-central/image/ClippedImage.cpp
Line | Count | Source (jump to first uncovered line) |
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 "ClippedImage.h" |
7 | | |
8 | | #include <algorithm> |
9 | | #include <new> // Workaround for bug in VS10; see bug 981264. |
10 | | #include <cmath> |
11 | | #include <utility> |
12 | | |
13 | | #include "gfxDrawable.h" |
14 | | #include "gfxPlatform.h" |
15 | | #include "gfxUtils.h" |
16 | | #include "mozilla/gfx/2D.h" |
17 | | #include "mozilla/Move.h" |
18 | | #include "mozilla/RefPtr.h" |
19 | | #include "mozilla/Pair.h" |
20 | | #include "mozilla/Tuple.h" |
21 | | |
22 | | #include "ImageRegion.h" |
23 | | #include "Orientation.h" |
24 | | #include "SVGImageContext.h" |
25 | | |
26 | | namespace mozilla { |
27 | | |
28 | | using namespace gfx; |
29 | | using layers::LayerManager; |
30 | | using layers::ImageContainer; |
31 | | using std::make_pair; |
32 | | using std::max; |
33 | | using std::modf; |
34 | | using std::pair; |
35 | | |
36 | | namespace image { |
37 | | |
38 | | class ClippedImageCachedSurface |
39 | | { |
40 | | public: |
41 | | ClippedImageCachedSurface(already_AddRefed<SourceSurface> aSurface, |
42 | | const nsIntSize& aSize, |
43 | | const Maybe<SVGImageContext>& aSVGContext, |
44 | | float aFrame, |
45 | | uint32_t aFlags, |
46 | | ImgDrawResult aDrawResult) |
47 | | : mSurface(aSurface) |
48 | | , mSize(aSize) |
49 | | , mSVGContext(aSVGContext) |
50 | | , mFrame(aFrame) |
51 | | , mFlags(aFlags) |
52 | | , mDrawResult(aDrawResult) |
53 | 0 | { |
54 | 0 | MOZ_ASSERT(mSurface, "Must have a valid surface"); |
55 | 0 | } |
56 | | |
57 | | bool Matches(const nsIntSize& aSize, |
58 | | const Maybe<SVGImageContext>& aSVGContext, |
59 | | float aFrame, |
60 | | uint32_t aFlags) const |
61 | 0 | { |
62 | 0 | return mSize == aSize && |
63 | 0 | mSVGContext == aSVGContext && |
64 | 0 | mFrame == aFrame && |
65 | 0 | mFlags == aFlags; |
66 | 0 | } |
67 | | |
68 | | already_AddRefed<SourceSurface> Surface() const |
69 | 0 | { |
70 | 0 | RefPtr<SourceSurface> surf(mSurface); |
71 | 0 | return surf.forget(); |
72 | 0 | } |
73 | | |
74 | | ImgDrawResult GetDrawResult() const |
75 | 0 | { |
76 | 0 | return mDrawResult; |
77 | 0 | } |
78 | | |
79 | | bool NeedsRedraw() const |
80 | 0 | { |
81 | 0 | return mDrawResult != ImgDrawResult::SUCCESS && |
82 | 0 | mDrawResult != ImgDrawResult::BAD_IMAGE; |
83 | 0 | } |
84 | | |
85 | | private: |
86 | | RefPtr<SourceSurface> mSurface; |
87 | | const nsIntSize mSize; |
88 | | Maybe<SVGImageContext> mSVGContext; |
89 | | const float mFrame; |
90 | | const uint32_t mFlags; |
91 | | const ImgDrawResult mDrawResult; |
92 | | }; |
93 | | |
94 | | class DrawSingleTileCallback : public gfxDrawingCallback |
95 | | { |
96 | | public: |
97 | | DrawSingleTileCallback(ClippedImage* aImage, |
98 | | const nsIntSize& aSize, |
99 | | const Maybe<SVGImageContext>& aSVGContext, |
100 | | uint32_t aWhichFrame, |
101 | | uint32_t aFlags, |
102 | | float aOpacity) |
103 | | : mImage(aImage) |
104 | | , mSize(aSize) |
105 | | , mSVGContext(aSVGContext) |
106 | | , mWhichFrame(aWhichFrame) |
107 | | , mFlags(aFlags) |
108 | | , mDrawResult(ImgDrawResult::NOT_READY) |
109 | | , mOpacity(aOpacity) |
110 | 0 | { |
111 | 0 | MOZ_ASSERT(mImage, "Must have an image to clip"); |
112 | 0 | } |
113 | | |
114 | | virtual bool operator()(gfxContext* aContext, |
115 | | const gfxRect& aFillRect, |
116 | | const SamplingFilter aSamplingFilter, |
117 | | const gfxMatrix& aTransform) override |
118 | 0 | { |
119 | 0 | MOZ_ASSERT(aTransform.IsIdentity(), |
120 | 0 | "Caller is probably CreateSamplingRestrictedDrawable, " |
121 | 0 | "which should not happen"); |
122 | 0 |
|
123 | 0 | // Draw the image. |gfxCallbackDrawable| always calls this function with |
124 | 0 | // arguments that guarantee we never tile. |
125 | 0 | mDrawResult = |
126 | 0 | mImage->DrawSingleTile(aContext, mSize, ImageRegion::Create(aFillRect), |
127 | 0 | mWhichFrame, aSamplingFilter, mSVGContext, mFlags, |
128 | 0 | mOpacity); |
129 | 0 |
|
130 | 0 | return true; |
131 | 0 | } |
132 | | |
133 | 0 | ImgDrawResult GetDrawResult() { return mDrawResult; } |
134 | | |
135 | | private: |
136 | | RefPtr<ClippedImage> mImage; |
137 | | const nsIntSize mSize; |
138 | | const Maybe<SVGImageContext>& mSVGContext; |
139 | | const uint32_t mWhichFrame; |
140 | | const uint32_t mFlags; |
141 | | ImgDrawResult mDrawResult; |
142 | | float mOpacity; |
143 | | }; |
144 | | |
145 | | ClippedImage::ClippedImage(Image* aImage, |
146 | | nsIntRect aClip, |
147 | | const Maybe<nsSize>& aSVGViewportSize) |
148 | | : ImageWrapper(aImage) |
149 | | , mClip(aClip) |
150 | 0 | { |
151 | 0 | MOZ_ASSERT(aImage != nullptr, "ClippedImage requires an existing Image"); |
152 | 0 | MOZ_ASSERT_IF(aSVGViewportSize, |
153 | 0 | aImage->GetType() == imgIContainer::TYPE_VECTOR); |
154 | 0 | if (aSVGViewportSize) { |
155 | 0 | mSVGViewportSize = Some(aSVGViewportSize->ToNearestPixels( |
156 | 0 | AppUnitsPerCSSPixel())); |
157 | 0 | } |
158 | 0 | } |
159 | | |
160 | | ClippedImage::~ClippedImage() |
161 | 0 | { } |
162 | | |
163 | | bool |
164 | | ClippedImage::ShouldClip() |
165 | 0 | { |
166 | 0 | // We need to evaluate the clipping region against the image's width and |
167 | 0 | // height once they're available to determine if it's valid and whether we |
168 | 0 | // actually need to do any work. We may fail if the image's width and height |
169 | 0 | // aren't available yet, in which case we'll try again later. |
170 | 0 | if (mShouldClip.isNothing()) { |
171 | 0 | int32_t width, height; |
172 | 0 | RefPtr<ProgressTracker> progressTracker = |
173 | 0 | InnerImage()->GetProgressTracker(); |
174 | 0 | if (InnerImage()->HasError()) { |
175 | 0 | // If there's a problem with the inner image we'll let it handle |
176 | 0 | // everything. |
177 | 0 | mShouldClip.emplace(false); |
178 | 0 | } else if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) { |
179 | 0 | // Clamp the clipping region to the size of the SVG viewport. |
180 | 0 | nsIntRect svgViewportRect(nsIntPoint(0,0), *mSVGViewportSize); |
181 | 0 |
|
182 | 0 | mClip = mClip.Intersect(svgViewportRect); |
183 | 0 |
|
184 | 0 | // If the clipping region is the same size as the SVG viewport size |
185 | 0 | // we don't have to do anything. |
186 | 0 | mShouldClip.emplace(!mClip.IsEqualInterior(svgViewportRect)); |
187 | 0 | } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&width)) && width > 0 && |
188 | 0 | NS_SUCCEEDED(InnerImage()->GetHeight(&height)) && height > 0) { |
189 | 0 | // Clamp the clipping region to the size of the underlying image. |
190 | 0 | mClip = mClip.Intersect(nsIntRect(0, 0, width, height)); |
191 | 0 |
|
192 | 0 | // If the clipping region is the same size as the underlying image we |
193 | 0 | // don't have to do anything. |
194 | 0 | mShouldClip.emplace(!mClip.IsEqualInterior(nsIntRect(0, 0, width, |
195 | 0 | height))); |
196 | 0 | } else if (progressTracker && |
197 | 0 | !(progressTracker->GetProgress() & FLAG_LOAD_COMPLETE)) { |
198 | 0 | // The image just hasn't finished loading yet. We don't yet know whether |
199 | 0 | // clipping with be needed or not for now. Just return without memorizing |
200 | 0 | // anything. |
201 | 0 | return false; |
202 | 0 | } else { |
203 | 0 | // We have a fully loaded image without a clearly defined width and |
204 | 0 | // height. This can happen with SVG images. |
205 | 0 | mShouldClip.emplace(false); |
206 | 0 | } |
207 | 0 | } |
208 | 0 |
|
209 | 0 | MOZ_ASSERT(mShouldClip.isSome(), "Should have computed a result"); |
210 | 0 | return *mShouldClip; |
211 | 0 | } |
212 | | |
213 | | NS_IMETHODIMP |
214 | | ClippedImage::GetWidth(int32_t* aWidth) |
215 | 0 | { |
216 | 0 | if (!ShouldClip()) { |
217 | 0 | return InnerImage()->GetWidth(aWidth); |
218 | 0 | } |
219 | 0 | |
220 | 0 | *aWidth = mClip.Width(); |
221 | 0 | return NS_OK; |
222 | 0 | } |
223 | | |
224 | | NS_IMETHODIMP |
225 | | ClippedImage::GetHeight(int32_t* aHeight) |
226 | 0 | { |
227 | 0 | if (!ShouldClip()) { |
228 | 0 | return InnerImage()->GetHeight(aHeight); |
229 | 0 | } |
230 | 0 | |
231 | 0 | *aHeight = mClip.Height(); |
232 | 0 | return NS_OK; |
233 | 0 | } |
234 | | |
235 | | NS_IMETHODIMP |
236 | | ClippedImage::GetIntrinsicSize(nsSize* aSize) |
237 | 0 | { |
238 | 0 | if (!ShouldClip()) { |
239 | 0 | return InnerImage()->GetIntrinsicSize(aSize); |
240 | 0 | } |
241 | 0 | |
242 | 0 | *aSize = nsSize(mClip.Width(), mClip.Height()); |
243 | 0 | return NS_OK; |
244 | 0 | } |
245 | | |
246 | | NS_IMETHODIMP |
247 | | ClippedImage::GetIntrinsicRatio(nsSize* aRatio) |
248 | 0 | { |
249 | 0 | if (!ShouldClip()) { |
250 | 0 | return InnerImage()->GetIntrinsicRatio(aRatio); |
251 | 0 | } |
252 | 0 | |
253 | 0 | *aRatio = nsSize(mClip.Width(), mClip.Height()); |
254 | 0 | return NS_OK; |
255 | 0 | } |
256 | | |
257 | | NS_IMETHODIMP_(already_AddRefed<SourceSurface>) |
258 | | ClippedImage::GetFrame(uint32_t aWhichFrame, |
259 | | uint32_t aFlags) |
260 | 0 | { |
261 | 0 | ImgDrawResult result; |
262 | 0 | RefPtr<SourceSurface> surface; |
263 | 0 | Tie(result, surface) = GetFrameInternal(mClip.Size(), Nothing(), aWhichFrame, aFlags, 1.0); |
264 | 0 | return surface.forget(); |
265 | 0 | } |
266 | | |
267 | | NS_IMETHODIMP_(already_AddRefed<SourceSurface>) |
268 | | ClippedImage::GetFrameAtSize(const IntSize& aSize, |
269 | | uint32_t aWhichFrame, |
270 | | uint32_t aFlags) |
271 | 0 | { |
272 | 0 | // XXX(seth): It'd be nice to support downscale-during-decode for this case, |
273 | 0 | // but right now we just fall back to the intrinsic size. |
274 | 0 | return GetFrame(aWhichFrame, aFlags); |
275 | 0 | } |
276 | | |
277 | | Pair<ImgDrawResult, RefPtr<SourceSurface>> |
278 | | ClippedImage::GetFrameInternal(const nsIntSize& aSize, |
279 | | const Maybe<SVGImageContext>& aSVGContext, |
280 | | uint32_t aWhichFrame, |
281 | | uint32_t aFlags, |
282 | | float aOpacity) |
283 | 0 | { |
284 | 0 | if (!ShouldClip()) { |
285 | 0 | RefPtr<SourceSurface> surface = InnerImage()->GetFrame(aWhichFrame, aFlags); |
286 | 0 | return MakePair(surface ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_READY, |
287 | 0 | std::move(surface)); |
288 | 0 | } |
289 | 0 |
|
290 | 0 | float frameToDraw = InnerImage()->GetFrameIndex(aWhichFrame); |
291 | 0 | if (!mCachedSurface || |
292 | 0 | !mCachedSurface->Matches(aSize, aSVGContext, frameToDraw, aFlags) || |
293 | 0 | mCachedSurface->NeedsRedraw()) { |
294 | 0 | // Create a surface to draw into. |
295 | 0 | RefPtr<DrawTarget> target = gfxPlatform::GetPlatform()-> |
296 | 0 | CreateOffscreenContentDrawTarget(IntSize(aSize.width, aSize.height), |
297 | 0 | SurfaceFormat::B8G8R8A8); |
298 | 0 | if (!target || !target->IsValid()) { |
299 | 0 | NS_ERROR("Could not create a DrawTarget"); |
300 | 0 | return MakePair(ImgDrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>()); |
301 | 0 | } |
302 | 0 |
|
303 | 0 | RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(target); |
304 | 0 | MOZ_ASSERT(ctx); // already checked the draw target above |
305 | 0 |
|
306 | 0 | // Create our callback. |
307 | 0 | RefPtr<DrawSingleTileCallback> drawTileCallback = |
308 | 0 | new DrawSingleTileCallback(this, aSize, aSVGContext, aWhichFrame, aFlags, |
309 | 0 | aOpacity); |
310 | 0 | RefPtr<gfxDrawable> drawable = |
311 | 0 | new gfxCallbackDrawable(drawTileCallback, aSize); |
312 | 0 |
|
313 | 0 | // Actually draw. The callback will end up invoking DrawSingleTile. |
314 | 0 | gfxUtils::DrawPixelSnapped(ctx, drawable, SizeDouble(aSize), |
315 | 0 | ImageRegion::Create(aSize), |
316 | 0 | SurfaceFormat::B8G8R8A8, |
317 | 0 | SamplingFilter::LINEAR, |
318 | 0 | imgIContainer::FLAG_CLAMP); |
319 | 0 |
|
320 | 0 | // Cache the resulting surface. |
321 | 0 | mCachedSurface = |
322 | 0 | MakeUnique<ClippedImageCachedSurface>(target->Snapshot(), aSize, aSVGContext, |
323 | 0 | frameToDraw, aFlags, |
324 | 0 | drawTileCallback->GetDrawResult()); |
325 | 0 | } |
326 | 0 |
|
327 | 0 | MOZ_ASSERT(mCachedSurface, "Should have a cached surface now"); |
328 | 0 | RefPtr<SourceSurface> surface = mCachedSurface->Surface(); |
329 | 0 | return MakePair(mCachedSurface->GetDrawResult(), std::move(surface)); |
330 | 0 | } |
331 | | |
332 | | NS_IMETHODIMP_(bool) |
333 | | ClippedImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags) |
334 | 0 | { |
335 | 0 | if (!ShouldClip()) { |
336 | 0 | return InnerImage()->IsImageContainerAvailable(aManager, aFlags); |
337 | 0 | } |
338 | 0 | return false; |
339 | 0 | } |
340 | | |
341 | | NS_IMETHODIMP_(already_AddRefed<ImageContainer>) |
342 | | ClippedImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags) |
343 | 0 | { |
344 | 0 | // XXX(seth): We currently don't have a way of clipping the result of |
345 | 0 | // GetImageContainer. We work around this by always returning null, but if it |
346 | 0 | // ever turns out that ClippedImage is widely used on codepaths that can |
347 | 0 | // actually benefit from GetImageContainer, it would be a good idea to fix |
348 | 0 | // that method for performance reasons. |
349 | 0 |
|
350 | 0 | if (!ShouldClip()) { |
351 | 0 | return InnerImage()->GetImageContainer(aManager, aFlags); |
352 | 0 | } |
353 | 0 | |
354 | 0 | return nullptr; |
355 | 0 | } |
356 | | |
357 | | NS_IMETHODIMP_(bool) |
358 | | ClippedImage::IsImageContainerAvailableAtSize(LayerManager* aManager, |
359 | | const IntSize& aSize, |
360 | | uint32_t aFlags) |
361 | 0 | { |
362 | 0 | if (!ShouldClip()) { |
363 | 0 | return InnerImage()->IsImageContainerAvailableAtSize(aManager, aSize, aFlags); |
364 | 0 | } |
365 | 0 | return false; |
366 | 0 | } |
367 | | |
368 | | NS_IMETHODIMP_(ImgDrawResult) |
369 | | ClippedImage::GetImageContainerAtSize(layers::LayerManager* aManager, |
370 | | const gfx::IntSize& aSize, |
371 | | const Maybe<SVGImageContext>& aSVGContext, |
372 | | uint32_t aFlags, |
373 | | layers::ImageContainer** aOutContainer) |
374 | 0 | { |
375 | 0 | // XXX(seth): We currently don't have a way of clipping the result of |
376 | 0 | // GetImageContainer. We work around this by always returning null, but if it |
377 | 0 | // ever turns out that ClippedImage is widely used on codepaths that can |
378 | 0 | // actually benefit from GetImageContainer, it would be a good idea to fix |
379 | 0 | // that method for performance reasons. |
380 | 0 |
|
381 | 0 | if (!ShouldClip()) { |
382 | 0 | return InnerImage()->GetImageContainerAtSize(aManager, aSize, aSVGContext, |
383 | 0 | aFlags, aOutContainer); |
384 | 0 | } |
385 | 0 | |
386 | 0 | return ImgDrawResult::NOT_SUPPORTED; |
387 | 0 | } |
388 | | |
389 | | static bool |
390 | | MustCreateSurface(gfxContext* aContext, |
391 | | const nsIntSize& aSize, |
392 | | const ImageRegion& aRegion, |
393 | | const uint32_t aFlags) |
394 | 0 | { |
395 | 0 | gfxRect imageRect(0, 0, aSize.width, aSize.height); |
396 | 0 | bool willTile = !imageRect.Contains(aRegion.Rect()) && |
397 | 0 | !(aFlags & imgIContainer::FLAG_CLAMP); |
398 | 0 | bool willResample = aContext->CurrentMatrix().HasNonIntegerTranslation() && |
399 | 0 | (willTile || !aRegion.RestrictionContains(imageRect)); |
400 | 0 | return willTile || willResample; |
401 | 0 | } |
402 | | |
403 | | NS_IMETHODIMP_(ImgDrawResult) |
404 | | ClippedImage::Draw(gfxContext* aContext, |
405 | | const nsIntSize& aSize, |
406 | | const ImageRegion& aRegion, |
407 | | uint32_t aWhichFrame, |
408 | | SamplingFilter aSamplingFilter, |
409 | | const Maybe<SVGImageContext>& aSVGContext, |
410 | | uint32_t aFlags, |
411 | | float aOpacity) |
412 | 0 | { |
413 | 0 | if (!ShouldClip()) { |
414 | 0 | return InnerImage()->Draw(aContext, aSize, aRegion, aWhichFrame, |
415 | 0 | aSamplingFilter, aSVGContext, aFlags, aOpacity); |
416 | 0 | } |
417 | 0 | |
418 | 0 | // Check for tiling. If we need to tile then we need to create a |
419 | 0 | // gfxCallbackDrawable to handle drawing for us. |
420 | 0 | if (MustCreateSurface(aContext, aSize, aRegion, aFlags)) { |
421 | 0 | // Create a temporary surface containing a single tile of this image. |
422 | 0 | // GetFrame will call DrawSingleTile internally. |
423 | 0 | ImgDrawResult result; |
424 | 0 | RefPtr<SourceSurface> surface; |
425 | 0 | Tie(result, surface) = |
426 | 0 | GetFrameInternal(aSize, aSVGContext, aWhichFrame, aFlags, aOpacity); |
427 | 0 | if (!surface) { |
428 | 0 | MOZ_ASSERT(result != ImgDrawResult::SUCCESS); |
429 | 0 | return result; |
430 | 0 | } |
431 | 0 |
|
432 | 0 | // Create a drawable from that surface. |
433 | 0 | RefPtr<gfxSurfaceDrawable> drawable = |
434 | 0 | new gfxSurfaceDrawable(surface, aSize); |
435 | 0 |
|
436 | 0 | // Draw. |
437 | 0 | gfxUtils::DrawPixelSnapped(aContext, drawable, SizeDouble(aSize), aRegion, |
438 | 0 | SurfaceFormat::B8G8R8A8, aSamplingFilter, |
439 | 0 | aOpacity); |
440 | 0 |
|
441 | 0 | return result; |
442 | 0 | } |
443 | 0 | |
444 | 0 | return DrawSingleTile(aContext, aSize, aRegion, aWhichFrame, |
445 | 0 | aSamplingFilter, aSVGContext, aFlags, aOpacity); |
446 | 0 | } |
447 | | |
448 | | ImgDrawResult |
449 | | ClippedImage::DrawSingleTile(gfxContext* aContext, |
450 | | const nsIntSize& aSize, |
451 | | const ImageRegion& aRegion, |
452 | | uint32_t aWhichFrame, |
453 | | SamplingFilter aSamplingFilter, |
454 | | const Maybe<SVGImageContext>& aSVGContext, |
455 | | uint32_t aFlags, |
456 | | float aOpacity) |
457 | 0 | { |
458 | 0 | MOZ_ASSERT(!MustCreateSurface(aContext, aSize, aRegion, aFlags), |
459 | 0 | "Shouldn't need to create a surface"); |
460 | 0 |
|
461 | 0 | gfxRect clip(mClip.X(), mClip.Y(), mClip.Width(), mClip.Height()); |
462 | 0 | nsIntSize size(aSize), innerSize(aSize); |
463 | 0 | bool needScale = false; |
464 | 0 | if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) { |
465 | 0 | innerSize = *mSVGViewportSize; |
466 | 0 | needScale = true; |
467 | 0 | } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&innerSize.width)) && |
468 | 0 | NS_SUCCEEDED(InnerImage()->GetHeight(&innerSize.height))) { |
469 | 0 | needScale = true; |
470 | 0 | } else { |
471 | 0 | MOZ_ASSERT_UNREACHABLE( |
472 | 0 | "If ShouldClip() led us to draw then we should never get here"); |
473 | 0 | } |
474 | 0 |
|
475 | 0 | if (needScale) { |
476 | 0 | double scaleX = aSize.width / clip.Width(); |
477 | 0 | double scaleY = aSize.height / clip.Height(); |
478 | 0 |
|
479 | 0 | // Map the clip and size to the scale requested by the caller. |
480 | 0 | clip.Scale(scaleX, scaleY); |
481 | 0 | size = innerSize; |
482 | 0 | size.Scale(scaleX, scaleY); |
483 | 0 | } |
484 | 0 |
|
485 | 0 | // We restrict our drawing to only the clipping region, and translate so that |
486 | 0 | // the clipping region is placed at the position the caller expects. |
487 | 0 | ImageRegion region(aRegion); |
488 | 0 | region.MoveBy(clip.X(), clip.Y()); |
489 | 0 | region = region.Intersect(clip); |
490 | 0 |
|
491 | 0 | gfxContextMatrixAutoSaveRestore saveMatrix(aContext); |
492 | 0 | aContext->Multiply(gfxMatrix::Translation(-clip.X(), -clip.Y())); |
493 | 0 |
|
494 | 0 | auto unclipViewport = [&](const SVGImageContext& aOldContext) { |
495 | 0 | // Map the viewport to the inner image. Note that we don't take the aSize |
496 | 0 | // parameter of imgIContainer::Draw into account, just the clipping region. |
497 | 0 | // The size in pixels at which the output will ultimately be drawn is |
498 | 0 | // irrelevant here since the purpose of the SVG viewport size is to |
499 | 0 | // determine what *region* of the SVG document will be drawn. |
500 | 0 | SVGImageContext context(aOldContext); |
501 | 0 | auto oldViewport = aOldContext.GetViewportSize(); |
502 | 0 | if (oldViewport) { |
503 | 0 | CSSIntSize newViewport; |
504 | 0 | newViewport.width = |
505 | 0 | ceil(oldViewport->width * double(innerSize.width) / mClip.Width()); |
506 | 0 | newViewport.height = |
507 | 0 | ceil(oldViewport->height * double(innerSize.height) / mClip.Height()); |
508 | 0 | context.SetViewportSize(Some(newViewport)); |
509 | 0 | } |
510 | 0 | return context; |
511 | 0 | }; |
512 | 0 |
|
513 | 0 | return InnerImage()->Draw(aContext, size, region, |
514 | 0 | aWhichFrame, aSamplingFilter, |
515 | 0 | aSVGContext.map(unclipViewport), |
516 | 0 | aFlags, aOpacity); |
517 | 0 | } |
518 | | |
519 | | NS_IMETHODIMP |
520 | | ClippedImage::RequestDiscard() |
521 | 0 | { |
522 | 0 | // We're very aggressive about discarding. |
523 | 0 | mCachedSurface = nullptr; |
524 | 0 |
|
525 | 0 | return InnerImage()->RequestDiscard(); |
526 | 0 | } |
527 | | |
528 | | NS_IMETHODIMP_(Orientation) |
529 | | ClippedImage::GetOrientation() |
530 | 0 | { |
531 | 0 | // XXX(seth): This should not actually be here; this is just to work around a |
532 | 0 | // what appears to be a bug in MSVC's linker. |
533 | 0 | return InnerImage()->GetOrientation(); |
534 | 0 | } |
535 | | |
536 | | nsIntSize |
537 | | ClippedImage::OptimalImageSizeForDest(const gfxSize& aDest, |
538 | | uint32_t aWhichFrame, |
539 | | SamplingFilter aSamplingFilter, |
540 | | uint32_t aFlags) |
541 | 0 | { |
542 | 0 | if (!ShouldClip()) { |
543 | 0 | return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame, |
544 | 0 | aSamplingFilter, aFlags); |
545 | 0 | } |
546 | 0 | |
547 | 0 | int32_t imgWidth, imgHeight; |
548 | 0 | bool needScale = false; |
549 | 0 | bool forceUniformScaling = false; |
550 | 0 | if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) { |
551 | 0 | imgWidth = mSVGViewportSize->width; |
552 | 0 | imgHeight = mSVGViewportSize->height; |
553 | 0 | needScale = true; |
554 | 0 | forceUniformScaling = (aFlags & imgIContainer::FLAG_FORCE_UNIFORM_SCALING); |
555 | 0 | } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) && |
556 | 0 | NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) { |
557 | 0 | needScale = true; |
558 | 0 | } |
559 | 0 |
|
560 | 0 | if (needScale) { |
561 | 0 | // To avoid ugly sampling artifacts, ClippedImage needs the image size to |
562 | 0 | // be chosen such that the clipping region lies on pixel boundaries. |
563 | 0 |
|
564 | 0 | // First, we select a scale that's good for ClippedImage. An integer |
565 | 0 | // multiple of the size of the clipping region is always fine. |
566 | 0 | IntSize scale = IntSize::Ceil(aDest.width / mClip.Width(), |
567 | 0 | aDest.height / mClip.Height()); |
568 | 0 |
|
569 | 0 | if (forceUniformScaling) { |
570 | 0 | scale.width = scale.height = max(scale.height, scale.width); |
571 | 0 | } |
572 | 0 |
|
573 | 0 | // Determine the size we'd prefer to render the inner image at, and ask the |
574 | 0 | // inner image what size we should actually use. |
575 | 0 | gfxSize desiredSize(imgWidth * scale.width, imgHeight * scale.height); |
576 | 0 | nsIntSize innerDesiredSize = |
577 | 0 | InnerImage()->OptimalImageSizeForDest(desiredSize, aWhichFrame, |
578 | 0 | aSamplingFilter, aFlags); |
579 | 0 |
|
580 | 0 | // To get our final result, we take the inner image's desired size and |
581 | 0 | // determine how large the clipped region would be at that scale. (Again, we |
582 | 0 | // ensure an integer multiple of the size of the clipping region.) |
583 | 0 | IntSize finalScale = IntSize::Ceil(double(innerDesiredSize.width) / imgWidth, |
584 | 0 | double(innerDesiredSize.height) / imgHeight); |
585 | 0 | return mClip.Size() * finalScale; |
586 | 0 | } |
587 | 0 |
|
588 | 0 | MOZ_ASSERT(false, |
589 | 0 | "If ShouldClip() led us to draw then we should never get here"); |
590 | 0 | return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame, |
591 | 0 | aSamplingFilter, aFlags); |
592 | 0 | } |
593 | | |
594 | | NS_IMETHODIMP_(nsIntRect) |
595 | | ClippedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect) |
596 | 0 | { |
597 | 0 | if (!ShouldClip()) { |
598 | 0 | return InnerImage()->GetImageSpaceInvalidationRect(aRect); |
599 | 0 | } |
600 | 0 | |
601 | 0 | nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect)); |
602 | 0 | rect = rect.Intersect(mClip); |
603 | 0 | rect.MoveBy(-mClip.X(), -mClip.Y()); |
604 | 0 | return rect; |
605 | 0 | } |
606 | | |
607 | | } // namespace image |
608 | | } // namespace mozilla |