1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /* utility code for drawing images as CSS borders, backgrounds, and shapes. */
8
9 #include "nsImageRenderer.h"
10
11 #include "mozilla/webrender/WebRenderAPI.h"
12
13 #include "gfxContext.h"
14 #include "gfxDrawable.h"
15 #include "ImageOps.h"
16 #include "ImageRegion.h"
17 #include "mozilla/layers/RenderRootStateManager.h"
18 #include "mozilla/layers/StackingContextHelper.h"
19 #include "mozilla/layers/WebRenderLayerManager.h"
20 #include "nsContentUtils.h"
21 #include "nsCSSRendering.h"
22 #include "nsCSSRenderingGradients.h"
23 #include "nsDeviceContext.h"
24 #include "nsIFrame.h"
25 #include "nsLayoutUtils.h"
26 #include "nsStyleStructInlines.h"
27 #include "mozilla/StaticPrefs_image.h"
28 #include "mozilla/ISVGDisplayableFrame.h"
29 #include "mozilla/SVGIntegrationUtils.h"
30 #include "mozilla/SVGPaintServerFrame.h"
31 #include "mozilla/SVGObserverUtils.h"
32
33 using namespace mozilla;
34 using namespace mozilla::gfx;
35 using namespace mozilla::image;
36 using namespace mozilla::layers;
37
ComputeConcreteSize() const38 nsSize CSSSizeOrRatio::ComputeConcreteSize() const {
39 NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
40 if (mHasWidth && mHasHeight) {
41 return nsSize(mWidth, mHeight);
42 }
43 if (mHasWidth) {
44 return nsSize(mWidth, mRatio.Inverted().ApplyTo(mWidth));
45 }
46
47 MOZ_ASSERT(mHasHeight);
48 return nsSize(mRatio.ApplyTo(mHeight), mHeight);
49 }
50
nsImageRenderer(nsIFrame * aForFrame,const StyleImage * aImage,uint32_t aFlags)51 nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame, const StyleImage* aImage,
52 uint32_t aFlags)
53 : mForFrame(aForFrame),
54 mImage(&aImage->FinalImage()),
55 mImageResolution(aImage->GetResolution()),
56 mType(mImage->tag),
57 mImageContainer(nullptr),
58 mGradientData(nullptr),
59 mPaintServerFrame(nullptr),
60 mPrepareResult(ImgDrawResult::NOT_READY),
61 mSize(0, 0),
62 mFlags(aFlags),
63 mExtendMode(ExtendMode::CLAMP),
64 mMaskOp(StyleMaskMode::MatchSource) {}
65
PrepareImage()66 bool nsImageRenderer::PrepareImage() {
67 if (mImage->IsNone()) {
68 mPrepareResult = ImgDrawResult::BAD_IMAGE;
69 return false;
70 }
71
72 const bool isImageRequest = mImage->IsImageRequestType();
73 MOZ_ASSERT_IF(!isImageRequest, !mImage->GetImageRequest());
74 imgRequestProxy* request = nullptr;
75 if (isImageRequest) {
76 request = mImage->GetImageRequest();
77 if (!request) {
78 // request could be null here if the StyleImage refused
79 // to load a same-document URL, or the url was invalid, for example.
80 mPrepareResult = ImgDrawResult::BAD_IMAGE;
81 return false;
82 }
83 }
84
85 if (!mImage->IsComplete()) {
86 MOZ_DIAGNOSTIC_ASSERT(isImageRequest);
87
88 // Make sure the image is actually decoding.
89 bool frameComplete =
90 request->StartDecodingWithResult(imgIContainer::FLAG_ASYNC_NOTIFY);
91
92 // Boost the loading priority since we know we want to draw the image.
93 if (mFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) {
94 request->BoostPriority(imgIRequest::CATEGORY_DISPLAY);
95 }
96
97 // Check again to see if we finished.
98 // We cannot prepare the image for rendering if it is not fully loaded.
99 if (!frameComplete && !mImage->IsComplete()) {
100 uint32_t imageStatus = 0;
101 request->GetImageStatus(&imageStatus);
102 if (imageStatus & imgIRequest::STATUS_ERROR) {
103 mPrepareResult = ImgDrawResult::BAD_IMAGE;
104 return false;
105 }
106
107 // Special case: If not errored, and we requested a sync decode, and the
108 // image has loaded, push on through because the Draw() will do a sync
109 // decode then.
110 const bool syncDecodeWillComplete =
111 (mFlags & FLAG_SYNC_DECODE_IMAGES) &&
112 (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE);
113 if (!syncDecodeWillComplete) {
114 mPrepareResult = ImgDrawResult::NOT_READY;
115 return false;
116 }
117 }
118 }
119
120 if (isImageRequest) {
121 nsCOMPtr<imgIContainer> srcImage;
122 DebugOnly<nsresult> rv = request->GetImage(getter_AddRefs(srcImage));
123 MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage,
124 "If GetImage() is failing, mImage->IsComplete() "
125 "should have returned false");
126
127 if (srcImage) {
128 srcImage = nsLayoutUtils::OrientImage(
129 srcImage, mForFrame->StyleVisibility()->mImageOrientation);
130 }
131
132 if (!mImage->IsRect()) {
133 mImageContainer.swap(srcImage);
134 } else {
135 auto croprect = mImage->ComputeActualCropRect();
136 if (!croprect || croprect->mRect.IsEmpty()) {
137 // The cropped image has zero size
138 mPrepareResult = ImgDrawResult::BAD_IMAGE;
139 return false;
140 }
141 if (croprect->mIsEntireImage) {
142 // The cropped image is identical to the source image
143 mImageContainer.swap(srcImage);
144 } else {
145 nsCOMPtr<imgIContainer> subImage =
146 ImageOps::Clip(srcImage, croprect->mRect, Nothing());
147 mImageContainer.swap(subImage);
148 }
149 }
150 mPrepareResult = ImgDrawResult::SUCCESS;
151 } else if (mImage->IsGradient()) {
152 mGradientData = &*mImage->AsGradient();
153 mPrepareResult = ImgDrawResult::SUCCESS;
154 } else if (mImage->IsElement()) {
155 dom::Element* paintElement = // may be null
156 SVGObserverUtils::GetAndObserveBackgroundImage(
157 mForFrame->FirstContinuation(), mImage->AsElement().AsAtom());
158 // If the referenced element is an <img>, <canvas>, or <video> element,
159 // prefer SurfaceFromElement as it's more reliable.
160 mImageElementSurface = nsLayoutUtils::SurfaceFromElement(paintElement);
161
162 if (!mImageElementSurface.GetSourceSurface()) {
163 nsIFrame* paintServerFrame =
164 paintElement ? paintElement->GetPrimaryFrame() : nullptr;
165 // If there's no referenced frame, or the referenced frame is
166 // non-displayable SVG, then we have nothing valid to paint.
167 if (!paintServerFrame ||
168 (paintServerFrame->IsFrameOfType(nsIFrame::eSVG) &&
169 !static_cast<SVGPaintServerFrame*>(
170 do_QueryFrame(paintServerFrame)) &&
171 !static_cast<ISVGDisplayableFrame*>(
172 do_QueryFrame(paintServerFrame)))) {
173 mPrepareResult = ImgDrawResult::BAD_IMAGE;
174 return false;
175 }
176 mPaintServerFrame = paintServerFrame;
177 }
178
179 mPrepareResult = ImgDrawResult::SUCCESS;
180 } else if (mImage->IsCrossFade()) {
181 // See bug 546052 - cross-fade implementation still being worked
182 // on.
183 mPrepareResult = ImgDrawResult::BAD_IMAGE;
184 return false;
185 } else {
186 MOZ_ASSERT(mImage->IsNone(), "Unknown image type?");
187 }
188
189 return IsReady();
190 }
191
ComputeIntrinsicSize()192 CSSSizeOrRatio nsImageRenderer::ComputeIntrinsicSize() {
193 NS_ASSERTION(IsReady(),
194 "Ensure PrepareImage() has returned true "
195 "before calling me");
196
197 CSSSizeOrRatio result;
198 switch (mType) {
199 case StyleImage::Tag::Rect:
200 case StyleImage::Tag::Url: {
201 bool haveWidth, haveHeight;
202 CSSIntSize imageIntSize;
203 nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, mImageResolution,
204 imageIntSize, result.mRatio,
205 haveWidth, haveHeight);
206 if (haveWidth) {
207 result.SetWidth(CSSPixel::ToAppUnits(imageIntSize.width));
208 }
209 if (haveHeight) {
210 result.SetHeight(CSSPixel::ToAppUnits(imageIntSize.height));
211 }
212
213 // If we know the aspect ratio and one of the dimensions,
214 // we can compute the other missing width or height.
215 if (!haveHeight && haveWidth && result.mRatio) {
216 nscoord intrinsicHeight =
217 result.mRatio.Inverted().ApplyTo(imageIntSize.width);
218 result.SetHeight(nsPresContext::CSSPixelsToAppUnits(intrinsicHeight));
219 } else if (haveHeight && !haveWidth && result.mRatio) {
220 nscoord intrinsicWidth = result.mRatio.ApplyTo(imageIntSize.height);
221 result.SetWidth(nsPresContext::CSSPixelsToAppUnits(intrinsicWidth));
222 }
223
224 break;
225 }
226 case StyleImage::Tag::Element: {
227 // XXX element() should have the width/height of the referenced element,
228 // and that element's ratio, if it matches. If it doesn't match, it
229 // should have no width/height or ratio. See element() in CSS images:
230 // <http://dev.w3.org/csswg/css-images-4/#element-notation>.
231 // Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize
232 // when fixing this!
233 if (mPaintServerFrame) {
234 // SVG images have no intrinsic size
235 if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) {
236 // The intrinsic image size for a generic nsIFrame paint server is
237 // the union of the border-box rects of all of its continuations,
238 // rounded to device pixels.
239 int32_t appUnitsPerDevPixel =
240 mForFrame->PresContext()->AppUnitsPerDevPixel();
241 result.SetSize(IntSizeToAppUnits(
242 SVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame)
243 .ToNearestPixels(appUnitsPerDevPixel),
244 appUnitsPerDevPixel));
245 }
246 } else {
247 NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
248 "Surface should be ready.");
249 IntSize surfaceSize = mImageElementSurface.mSize;
250 result.SetSize(
251 nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
252 nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
253 }
254 break;
255 }
256 case StyleImage::Tag::ImageSet:
257 MOZ_FALLTHROUGH_ASSERT("image-set should be resolved already");
258 // Bug 546052 cross-fade not yet implemented.
259 case StyleImage::Tag::CrossFade:
260 // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
261 // intrinsic dimensions.
262 case StyleImage::Tag::Gradient:
263 case StyleImage::Tag::None:
264 break;
265 }
266
267 return result;
268 }
269
270 /* static */
ComputeConcreteSize(const CSSSizeOrRatio & aSpecifiedSize,const CSSSizeOrRatio & aIntrinsicSize,const nsSize & aDefaultSize)271 nsSize nsImageRenderer::ComputeConcreteSize(
272 const CSSSizeOrRatio& aSpecifiedSize, const CSSSizeOrRatio& aIntrinsicSize,
273 const nsSize& aDefaultSize) {
274 // The specified size is fully specified, just use that
275 if (aSpecifiedSize.IsConcrete()) {
276 return aSpecifiedSize.ComputeConcreteSize();
277 }
278
279 MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight);
280
281 if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) {
282 // no specified size, try using the intrinsic size
283 if (aIntrinsicSize.CanComputeConcreteSize()) {
284 return aIntrinsicSize.ComputeConcreteSize();
285 }
286
287 if (aIntrinsicSize.mHasWidth) {
288 return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height);
289 }
290 if (aIntrinsicSize.mHasHeight) {
291 return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight);
292 }
293
294 // couldn't use the intrinsic size either, revert to using the default size
295 return ComputeConstrainedSize(aDefaultSize, aIntrinsicSize.mRatio, CONTAIN);
296 }
297
298 MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
299
300 // The specified height is partial, try to compute the missing part.
301 if (aSpecifiedSize.mHasWidth) {
302 nscoord height;
303 if (aIntrinsicSize.HasRatio()) {
304 height = aIntrinsicSize.mRatio.Inverted().ApplyTo(aSpecifiedSize.mWidth);
305 } else if (aIntrinsicSize.mHasHeight) {
306 height = aIntrinsicSize.mHeight;
307 } else {
308 height = aDefaultSize.height;
309 }
310 return nsSize(aSpecifiedSize.mWidth, height);
311 }
312
313 MOZ_ASSERT(aSpecifiedSize.mHasHeight);
314 nscoord width;
315 if (aIntrinsicSize.HasRatio()) {
316 width = aIntrinsicSize.mRatio.ApplyTo(aSpecifiedSize.mHeight);
317 } else if (aIntrinsicSize.mHasWidth) {
318 width = aIntrinsicSize.mWidth;
319 } else {
320 width = aDefaultSize.width;
321 }
322 return nsSize(width, aSpecifiedSize.mHeight);
323 }
324
325 /* static */
ComputeConstrainedSize(const nsSize & aConstrainingSize,const AspectRatio & aIntrinsicRatio,FitType aFitType)326 nsSize nsImageRenderer::ComputeConstrainedSize(
327 const nsSize& aConstrainingSize, const AspectRatio& aIntrinsicRatio,
328 FitType aFitType) {
329 if (!aIntrinsicRatio) {
330 return aConstrainingSize;
331 }
332
333 // Suppose we're doing a "contain" fit. If the image's aspect ratio has a
334 // "fatter" shape than the constraint area, then we need to use the
335 // constraint area's full width, and we need to use the aspect ratio to
336 // produce a height. On the other hand, if the aspect ratio is "skinnier", we
337 // use the constraint area's full height, and we use the aspect ratio to
338 // produce a width. (If instead we're doing a "cover" fit, then it can easily
339 // be seen that we should do precisely the opposite.)
340 //
341 // We check if the image's aspect ratio is "fatter" than the constraint area
342 // by simply applying the aspect ratio to the constraint area's height, to
343 // produce a "hypothetical width", and we check whether that
344 // aspect-ratio-provided "hypothetical width" is wider than the constraint
345 // area's actual width. If it is, then the aspect ratio is fatter than the
346 // constraint area.
347 //
348 // This is equivalent to the more descriptive alternative:
349 //
350 // AspectRatio::FromSize(aConstrainingSize) < aIntrinsicRatio
351 //
352 // But gracefully handling the case where one of the two dimensions from
353 // aConstrainingSize is zero. This is easy to prove since:
354 //
355 // aConstrainingSize.width / aConstrainingSize.height < aIntrinsicRatio
356 //
357 // Is trivially equivalent to:
358 //
359 // aIntrinsicRatio.width < aIntrinsicRatio * aConstrainingSize.height
360 //
361 // For the cases where height is not zero.
362 //
363 // We use float math here to avoid losing precision for very large backgrounds
364 // since we use saturating nscoord math otherwise.
365 const float constraintWidth = float(aConstrainingSize.width);
366 const float hypotheticalWidth =
367 aIntrinsicRatio.ApplyToFloat(aConstrainingSize.height);
368
369 nsSize size;
370 if ((aFitType == CONTAIN) == (constraintWidth < hypotheticalWidth)) {
371 size.width = aConstrainingSize.width;
372 size.height = aIntrinsicRatio.Inverted().ApplyTo(aConstrainingSize.width);
373 // If we're reducing the size by less than one css pixel, then just use the
374 // constraining size.
375 if (aFitType == CONTAIN &&
376 aConstrainingSize.height - size.height < AppUnitsPerCSSPixel()) {
377 size.height = aConstrainingSize.height;
378 }
379 } else {
380 size.height = aConstrainingSize.height;
381 size.width = aIntrinsicRatio.ApplyTo(aConstrainingSize.height);
382 if (aFitType == CONTAIN &&
383 aConstrainingSize.width - size.width < AppUnitsPerCSSPixel()) {
384 size.width = aConstrainingSize.width;
385 }
386 }
387 return size;
388 }
389
390 /**
391 * mSize is the image's "preferred" size for this particular rendering, while
392 * the drawn (aka concrete) size is the actual rendered size after accounting
393 * for background-size etc.. The preferred size is most often the image's
394 * intrinsic dimensions. But for images with incomplete intrinsic dimensions,
395 * the preferred size varies, depending on the specified and default sizes, see
396 * nsImageRenderer::Compute*Size.
397 *
398 * This distinction is necessary because the components of a vector image are
399 * specified with respect to its preferred size for a rendering situation, not
400 * to its actual rendered size. For example, consider a 4px wide background
401 * vector image with no height which contains a left-aligned
402 * 2px wide black rectangle with height 100%. If the background-size width is
403 * auto (or 4px), the vector image will render 4px wide, and the black rectangle
404 * will be 2px wide. If the background-size width is 8px, the vector image will
405 * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
406 * In both cases mSize.width will be 4px; but in the first case the returned
407 * width will be 4px, while in the second case the returned width will be 8px.
408 */
SetPreferredSize(const CSSSizeOrRatio & aIntrinsicSize,const nsSize & aDefaultSize)409 void nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize,
410 const nsSize& aDefaultSize) {
411 mSize.width =
412 aIntrinsicSize.mHasWidth ? aIntrinsicSize.mWidth : aDefaultSize.width;
413 mSize.height =
414 aIntrinsicSize.mHasHeight ? aIntrinsicSize.mHeight : aDefaultSize.height;
415 }
416
417 // Convert from nsImageRenderer flags to the flags we want to use for drawing in
418 // the imgIContainer namespace.
ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)419 static uint32_t ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags) {
420 uint32_t drawFlags = imgIContainer::FLAG_NONE;
421 if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
422 drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
423 }
424 if (aImageRendererFlags & (nsImageRenderer::FLAG_PAINTING_TO_WINDOW |
425 nsImageRenderer::FLAG_HIGH_QUALITY_SCALING)) {
426 drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
427 }
428 return drawFlags;
429 }
430
Draw(nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,const nsRect & aDest,const nsRect & aFill,const nsPoint & aAnchor,const nsSize & aRepeatSize,const CSSIntRect & aSrc,float aOpacity)431 ImgDrawResult nsImageRenderer::Draw(nsPresContext* aPresContext,
432 gfxContext& aRenderingContext,
433 const nsRect& aDirtyRect,
434 const nsRect& aDest, const nsRect& aFill,
435 const nsPoint& aAnchor,
436 const nsSize& aRepeatSize,
437 const CSSIntRect& aSrc, float aOpacity) {
438 if (!IsReady()) {
439 MOZ_ASSERT_UNREACHABLE(
440 "Ensure PrepareImage() has returned true before "
441 "calling me");
442 return ImgDrawResult::TEMPORARY_ERROR;
443 }
444
445 if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 ||
446 mSize.height <= 0) {
447 return ImgDrawResult::SUCCESS;
448 }
449
450 SamplingFilter samplingFilter =
451 nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
452 ImgDrawResult result = ImgDrawResult::SUCCESS;
453 RefPtr<gfxContext> ctx = &aRenderingContext;
454 IntRect tmpDTRect;
455
456 if (ctx->CurrentOp() != CompositionOp::OP_OVER ||
457 mMaskOp == StyleMaskMode::Luminance) {
458 gfxRect clipRect = ctx->GetClipExtents(gfxContext::eDeviceSpace);
459 tmpDTRect = RoundedOut(ToRect(clipRect));
460 if (tmpDTRect.IsEmpty()) {
461 return ImgDrawResult::SUCCESS;
462 }
463 RefPtr<DrawTarget> tempDT =
464 gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(
465 ctx->GetDrawTarget(), tmpDTRect.Size(), SurfaceFormat::B8G8R8A8);
466 if (!tempDT || !tempDT->IsValid()) {
467 gfxDevCrash(LogReason::InvalidContext)
468 << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
469 return ImgDrawResult::TEMPORARY_ERROR;
470 }
471 tempDT->SetTransform(ctx->GetDrawTarget()->GetTransform() *
472 Matrix::Translation(-tmpDTRect.TopLeft()));
473 ctx = gfxContext::CreatePreservingTransformOrNull(tempDT);
474 if (!ctx) {
475 gfxDevCrash(LogReason::InvalidContext)
476 << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
477 return ImgDrawResult::TEMPORARY_ERROR;
478 }
479 }
480
481 switch (mType) {
482 case StyleImage::Tag::Rect:
483 case StyleImage::Tag::Url: {
484 result = nsLayoutUtils::DrawBackgroundImage(
485 *ctx, mForFrame, aPresContext, mImageContainer, samplingFilter, aDest,
486 aFill, aRepeatSize, aAnchor, aDirtyRect,
487 ConvertImageRendererToDrawFlags(mFlags), mExtendMode, aOpacity);
488 break;
489 }
490 case StyleImage::Tag::Gradient: {
491 nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create(
492 aPresContext, mForFrame->Style(), *mGradientData, mSize);
493
494 renderer.Paint(*ctx, aDest, aFill, aRepeatSize, aSrc, aDirtyRect,
495 aOpacity);
496 break;
497 }
498 case StyleImage::Tag::Element: {
499 RefPtr<gfxDrawable> drawable = DrawableForElement(aDest, *ctx);
500 if (!drawable) {
501 NS_WARNING("Could not create drawable for element");
502 return ImgDrawResult::TEMPORARY_ERROR;
503 }
504
505 nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
506 result = nsLayoutUtils::DrawImage(
507 *ctx, mForFrame->Style(), aPresContext, image, samplingFilter, aDest,
508 aFill, aAnchor, aDirtyRect, ConvertImageRendererToDrawFlags(mFlags),
509 aOpacity);
510 break;
511 }
512 case StyleImage::Tag::ImageSet:
513 MOZ_FALLTHROUGH_ASSERT("image-set should be resolved already");
514 // See bug 546052 - cross-fade implementation still being worked
515 // on.
516 case StyleImage::Tag::CrossFade:
517 case StyleImage::Tag::None:
518 break;
519 }
520
521 if (!tmpDTRect.IsEmpty()) {
522 DrawTarget* dt = aRenderingContext.GetDrawTarget();
523 Matrix oldTransform = dt->GetTransform();
524 dt->SetTransform(Matrix());
525 if (mMaskOp == StyleMaskMode::Luminance) {
526 RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->IntoLuminanceSource(
527 LuminanceType::LUMINANCE, 1.0f);
528 dt->MaskSurface(ColorPattern(DeviceColor(0, 0, 0, 1.0f)), surf,
529 tmpDTRect.TopLeft(),
530 DrawOptions(1.0f, aRenderingContext.CurrentOp()));
531 } else {
532 RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
533 dt->DrawSurface(
534 surf,
535 Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height),
536 Rect(0, 0, tmpDTRect.width, tmpDTRect.height),
537 DrawSurfaceOptions(SamplingFilter::POINT),
538 DrawOptions(1.0f, aRenderingContext.CurrentOp()));
539 }
540
541 dt->SetTransform(oldTransform);
542 }
543
544 if (!mImage->IsComplete()) {
545 result &= ImgDrawResult::SUCCESS_NOT_COMPLETE;
546 }
547
548 return result;
549 }
550
BuildWebRenderDisplayItems(nsPresContext * aPresContext,mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayItem * aItem,const nsRect & aDirtyRect,const nsRect & aDest,const nsRect & aFill,const nsPoint & aAnchor,const nsSize & aRepeatSize,const CSSIntRect & aSrc,float aOpacity)551 ImgDrawResult nsImageRenderer::BuildWebRenderDisplayItems(
552 nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder,
553 mozilla::wr::IpcResourceUpdateQueue& aResources,
554 const mozilla::layers::StackingContextHelper& aSc,
555 mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
556 const nsRect& aDirtyRect, const nsRect& aDest, const nsRect& aFill,
557 const nsPoint& aAnchor, const nsSize& aRepeatSize, const CSSIntRect& aSrc,
558 float aOpacity) {
559 if (!IsReady()) {
560 MOZ_ASSERT_UNREACHABLE(
561 "Ensure PrepareImage() has returned true before "
562 "calling me");
563 return ImgDrawResult::NOT_READY;
564 }
565
566 if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 ||
567 mSize.height <= 0) {
568 return ImgDrawResult::SUCCESS;
569 }
570
571 ImgDrawResult drawResult = ImgDrawResult::SUCCESS;
572 switch (mType) {
573 case StyleImage::Tag::Gradient: {
574 nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create(
575 aPresContext, mForFrame->Style(), *mGradientData, mSize);
576
577 renderer.BuildWebRenderDisplayItems(aBuilder, aSc, aDest, aFill,
578 aRepeatSize, aSrc,
579 !aItem->BackfaceIsHidden(), aOpacity);
580 break;
581 }
582 case StyleImage::Tag::Rect:
583 case StyleImage::Tag::Url: {
584 ExtendMode extendMode = mExtendMode;
585 if (aDest.Contains(aFill)) {
586 extendMode = ExtendMode::CLAMP;
587 }
588
589 uint32_t containerFlags = imgIContainer::FLAG_ASYNC_NOTIFY;
590 if (mFlags & (nsImageRenderer::FLAG_PAINTING_TO_WINDOW |
591 nsImageRenderer::FLAG_HIGH_QUALITY_SCALING)) {
592 containerFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
593 }
594 if (mFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
595 containerFlags |= imgIContainer::FLAG_SYNC_DECODE;
596 }
597 if (extendMode == ExtendMode::CLAMP &&
598 StaticPrefs::image_svg_blob_image() &&
599 mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
600 containerFlags |= imgIContainer::FLAG_RECORD_BLOB;
601 }
602
603 CSSIntSize destCSSSize{
604 nsPresContext::AppUnitsToIntCSSPixels(aDest.width),
605 nsPresContext::AppUnitsToIntCSSPixels(aDest.height)};
606
607 Maybe<SVGImageContext> svgContext(
608 Some(SVGImageContext(Some(destCSSSize))));
609 Maybe<ImageIntRegion> region;
610
611 const int32_t appUnitsPerDevPixel =
612 mForFrame->PresContext()->AppUnitsPerDevPixel();
613 LayoutDeviceRect destRect =
614 LayoutDeviceRect::FromAppUnits(aDest, appUnitsPerDevPixel);
615 LayoutDeviceRect clipRect =
616 LayoutDeviceRect::FromAppUnits(aFill, appUnitsPerDevPixel);
617 auto stretchSize = wr::ToLayoutSize(destRect.Size());
618
619 gfx::IntSize decodeSize =
620 nsLayoutUtils::ComputeImageContainerDrawingParameters(
621 mImageContainer, mForFrame, destRect, clipRect, aSc,
622 containerFlags, svgContext, region);
623
624 if (extendMode != ExtendMode::CLAMP) {
625 region = Nothing();
626 }
627
628 RefPtr<layers::ImageContainer> container;
629 drawResult = mImageContainer->GetImageContainerAtSize(
630 aManager->LayerManager(), decodeSize, svgContext, region,
631 containerFlags, getter_AddRefs(container));
632 if (!container) {
633 NS_WARNING("Failed to get image container");
634 break;
635 }
636
637 if (containerFlags & imgIContainer::FLAG_RECORD_BLOB) {
638 MOZ_ASSERT(extendMode == ExtendMode::CLAMP);
639 aManager->CommandBuilder().PushBlobImage(
640 aItem, container, aBuilder, aResources, clipRect, clipRect);
641 break;
642 }
643
644 mozilla::wr::ImageRendering rendering = wr::ToImageRendering(
645 nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame()));
646 gfx::IntSize size;
647 Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageKey(
648 aItem, container, aBuilder, aResources, rendering, aSc, size,
649 Nothing());
650
651 if (key.isNothing()) {
652 break;
653 }
654
655 wr::LayoutRect dest = wr::ToLayoutRect(destRect);
656 wr::LayoutRect clip = wr::ToLayoutRect(clipRect);
657
658 if (extendMode == ExtendMode::CLAMP) {
659 // The image is not repeating. Just push as a regular image.
660 aBuilder.PushImage(dest, clip, !aItem->BackfaceIsHidden(), rendering,
661 key.value(), true,
662 wr::ColorF{1.0f, 1.0f, 1.0f, aOpacity});
663 } else {
664 nsPoint firstTilePos = nsLayoutUtils::GetBackgroundFirstTilePos(
665 aDest.TopLeft(), aFill.TopLeft(), aRepeatSize);
666 LayoutDeviceRect fillRect = LayoutDeviceRect::FromAppUnits(
667 nsRect(firstTilePos.x, firstTilePos.y,
668 aFill.XMost() - firstTilePos.x,
669 aFill.YMost() - firstTilePos.y),
670 appUnitsPerDevPixel);
671 wr::LayoutRect fill = wr::ToLayoutRect(fillRect);
672
673 switch (extendMode) {
674 case ExtendMode::REPEAT_Y:
675 fill.min.x = dest.min.x;
676 fill.max.x = dest.max.x;
677 stretchSize.width = dest.width();
678 break;
679 case ExtendMode::REPEAT_X:
680 fill.min.y = dest.min.y;
681 fill.max.y = dest.max.y;
682 stretchSize.height = dest.height();
683 break;
684 default:
685 break;
686 }
687
688 LayoutDeviceSize gapSize = LayoutDeviceSize::FromAppUnits(
689 aRepeatSize - aDest.Size(), appUnitsPerDevPixel);
690
691 aBuilder.PushRepeatingImage(fill, clip, !aItem->BackfaceIsHidden(),
692 stretchSize, wr::ToLayoutSize(gapSize),
693 rendering, key.value(), true,
694 wr::ColorF{1.0f, 1.0f, 1.0f, aOpacity});
695 }
696 break;
697 }
698 default:
699 break;
700 }
701
702 if (!mImage->IsComplete() && drawResult == ImgDrawResult::SUCCESS) {
703 return ImgDrawResult::SUCCESS_NOT_COMPLETE;
704 }
705 return drawResult;
706 }
707
DrawableForElement(const nsRect & aImageRect,gfxContext & aContext)708 already_AddRefed<gfxDrawable> nsImageRenderer::DrawableForElement(
709 const nsRect& aImageRect, gfxContext& aContext) {
710 NS_ASSERTION(mType == StyleImage::Tag::Element,
711 "DrawableForElement only makes sense if backed by an element");
712 if (mPaintServerFrame) {
713 // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here,
714 // DrawableFromPaintServer would have to return a ImgDrawResult indicating
715 // whether any images could not be painted because they weren't fully
716 // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all
717 // problems, as it won't help if there are image which haven't finished
718 // loading, but it's better than nothing.
719 int32_t appUnitsPerDevPixel =
720 mForFrame->PresContext()->AppUnitsPerDevPixel();
721 nsRect destRect = aImageRect - aImageRect.TopLeft();
722 nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
723 IntSize imageSize(roundedOut.width, roundedOut.height);
724
725 RefPtr<gfxDrawable> drawable;
726
727 SurfaceFormat format = aContext.GetDrawTarget()->GetFormat();
728 // Don't allow creating images that are too big
729 if (aContext.GetDrawTarget()->CanCreateSimilarDrawTarget(imageSize,
730 format)) {
731 drawable = SVGIntegrationUtils::DrawableFromPaintServer(
732 mPaintServerFrame, mForFrame, mSize, imageSize,
733 aContext.GetDrawTarget(), aContext.CurrentMatrixDouble(),
734 SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES);
735 }
736
737 return drawable.forget();
738 }
739 NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
740 "Surface should be ready.");
741 RefPtr<gfxDrawable> drawable =
742 new gfxSurfaceDrawable(mImageElementSurface.GetSourceSurface().get(),
743 mImageElementSurface.mSize);
744 return drawable.forget();
745 }
746
DrawLayer(nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDest,const nsRect & aFill,const nsPoint & aAnchor,const nsRect & aDirty,const nsSize & aRepeatSize,float aOpacity)747 ImgDrawResult nsImageRenderer::DrawLayer(
748 nsPresContext* aPresContext, gfxContext& aRenderingContext,
749 const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor,
750 const nsRect& aDirty, const nsSize& aRepeatSize, float aOpacity) {
751 if (!IsReady()) {
752 MOZ_ASSERT_UNREACHABLE(
753 "Ensure PrepareImage() has returned true before "
754 "calling me");
755 return ImgDrawResult::TEMPORARY_ERROR;
756 }
757
758 if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 ||
759 mSize.height <= 0) {
760 return ImgDrawResult::SUCCESS;
761 }
762
763 return Draw(
764 aPresContext, aRenderingContext, aDirty, aDest, aFill, aAnchor,
765 aRepeatSize,
766 CSSIntRect(0, 0, nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
767 nsPresContext::AppUnitsToIntCSSPixels(mSize.height)),
768 aOpacity);
769 }
770
BuildWebRenderDisplayItemsForLayer(nsPresContext * aPresContext,mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayItem * aItem,const nsRect & aDest,const nsRect & aFill,const nsPoint & aAnchor,const nsRect & aDirty,const nsSize & aRepeatSize,float aOpacity)771 ImgDrawResult nsImageRenderer::BuildWebRenderDisplayItemsForLayer(
772 nsPresContext* aPresContext, mozilla::wr::DisplayListBuilder& aBuilder,
773 mozilla::wr::IpcResourceUpdateQueue& aResources,
774 const mozilla::layers::StackingContextHelper& aSc,
775 mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
776 const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor,
777 const nsRect& aDirty, const nsSize& aRepeatSize, float aOpacity) {
778 if (!IsReady()) {
779 MOZ_ASSERT_UNREACHABLE(
780 "Ensure PrepareImage() has returned true before "
781 "calling me");
782 return mPrepareResult;
783 }
784
785 CSSIntRect srcRect(0, 0, nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
786 nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
787
788 if (aDest.IsEmpty() || aFill.IsEmpty() || srcRect.IsEmpty()) {
789 return ImgDrawResult::SUCCESS;
790 }
791 return BuildWebRenderDisplayItems(aPresContext, aBuilder, aResources, aSc,
792 aManager, aItem, aDirty, aDest, aFill,
793 aAnchor, aRepeatSize, srcRect, aOpacity);
794 }
795
796 /**
797 * Compute the size and position of the master copy of the image. I.e., a single
798 * tile used to fill the dest rect.
799 * aFill The destination rect to be filled
800 * aHFill and aVFill are the repeat patterns for the component -
801 * StyleBorderImageRepeat - i.e., how a tiling unit is used to fill aFill
802 * aUnitSize The size of the source rect in dest coords.
803 */
ComputeTile(nsRect & aFill,StyleBorderImageRepeat aHFill,StyleBorderImageRepeat aVFill,const nsSize & aUnitSize,nsSize & aRepeatSize)804 static nsRect ComputeTile(nsRect& aFill, StyleBorderImageRepeat aHFill,
805 StyleBorderImageRepeat aVFill,
806 const nsSize& aUnitSize, nsSize& aRepeatSize) {
807 nsRect tile;
808 switch (aHFill) {
809 case StyleBorderImageRepeat::Stretch:
810 tile.x = aFill.x;
811 tile.width = aFill.width;
812 aRepeatSize.width = tile.width;
813 break;
814 case StyleBorderImageRepeat::Repeat:
815 tile.x = aFill.x + aFill.width / 2 - aUnitSize.width / 2;
816 tile.width = aUnitSize.width;
817 aRepeatSize.width = tile.width;
818 break;
819 case StyleBorderImageRepeat::Round:
820 tile.x = aFill.x;
821 tile.width =
822 nsCSSRendering::ComputeRoundedSize(aUnitSize.width, aFill.width);
823 aRepeatSize.width = tile.width;
824 break;
825 case StyleBorderImageRepeat::Space: {
826 nscoord space;
827 aRepeatSize.width = nsCSSRendering::ComputeBorderSpacedRepeatSize(
828 aUnitSize.width, aFill.width, space);
829 tile.x = aFill.x + space;
830 tile.width = aUnitSize.width;
831 aFill.x = tile.x;
832 aFill.width = aFill.width - space * 2;
833 } break;
834 default:
835 MOZ_ASSERT_UNREACHABLE("unrecognized border-image fill style");
836 }
837
838 switch (aVFill) {
839 case StyleBorderImageRepeat::Stretch:
840 tile.y = aFill.y;
841 tile.height = aFill.height;
842 aRepeatSize.height = tile.height;
843 break;
844 case StyleBorderImageRepeat::Repeat:
845 tile.y = aFill.y + aFill.height / 2 - aUnitSize.height / 2;
846 tile.height = aUnitSize.height;
847 aRepeatSize.height = tile.height;
848 break;
849 case StyleBorderImageRepeat::Round:
850 tile.y = aFill.y;
851 tile.height =
852 nsCSSRendering::ComputeRoundedSize(aUnitSize.height, aFill.height);
853 aRepeatSize.height = tile.height;
854 break;
855 case StyleBorderImageRepeat::Space: {
856 nscoord space;
857 aRepeatSize.height = nsCSSRendering::ComputeBorderSpacedRepeatSize(
858 aUnitSize.height, aFill.height, space);
859 tile.y = aFill.y + space;
860 tile.height = aUnitSize.height;
861 aFill.y = tile.y;
862 aFill.height = aFill.height - space * 2;
863 } break;
864 default:
865 MOZ_ASSERT_UNREACHABLE("unrecognized border-image fill style");
866 }
867
868 return tile;
869 }
870
871 /**
872 * Returns true if the given set of arguments will require the tiles which fill
873 * the dest rect to be scaled from the source tile. See comment on ComputeTile
874 * for argument descriptions.
875 */
RequiresScaling(const nsRect & aFill,StyleBorderImageRepeat aHFill,StyleBorderImageRepeat aVFill,const nsSize & aUnitSize)876 static bool RequiresScaling(const nsRect& aFill, StyleBorderImageRepeat aHFill,
877 StyleBorderImageRepeat aVFill,
878 const nsSize& aUnitSize) {
879 // If we have no tiling in either direction, we can skip the intermediate
880 // scaling step.
881 return (aHFill != StyleBorderImageRepeat::Stretch ||
882 aVFill != StyleBorderImageRepeat::Stretch) &&
883 (aUnitSize.width != aFill.width || aUnitSize.height != aFill.height);
884 }
885
DrawBorderImageComponent(nsPresContext * aPresContext,gfxContext & aRenderingContext,const nsRect & aDirtyRect,const nsRect & aFill,const CSSIntRect & aSrc,StyleBorderImageRepeat aHFill,StyleBorderImageRepeat aVFill,const nsSize & aUnitSize,uint8_t aIndex,const Maybe<nsSize> & aSVGViewportSize,const bool aHasIntrinsicRatio)886 ImgDrawResult nsImageRenderer::DrawBorderImageComponent(
887 nsPresContext* aPresContext, gfxContext& aRenderingContext,
888 const nsRect& aDirtyRect, const nsRect& aFill, const CSSIntRect& aSrc,
889 StyleBorderImageRepeat aHFill, StyleBorderImageRepeat aVFill,
890 const nsSize& aUnitSize, uint8_t aIndex,
891 const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio) {
892 if (!IsReady()) {
893 MOZ_ASSERT_UNREACHABLE(
894 "Ensure PrepareImage() has returned true before "
895 "calling me");
896 return ImgDrawResult::BAD_ARGS;
897 }
898
899 if (aFill.IsEmpty() || aSrc.IsEmpty()) {
900 return ImgDrawResult::SUCCESS;
901 }
902
903 const bool isRequestBacked =
904 mType == StyleImage::Tag::Url || mType == StyleImage::Tag::Rect;
905 MOZ_ASSERT(isRequestBacked == mImage->IsImageRequestType());
906
907 if (isRequestBacked || mType == StyleImage::Tag::Element) {
908 nsCOMPtr<imgIContainer> subImage;
909
910 // To draw one portion of an image into a border component, we stretch that
911 // portion to match the size of that border component and then draw onto.
912 // However, preserveAspectRatio attribute of a SVG image may break this
913 // rule. To get correct rendering result, we add
914 // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore
915 // preserveAspectRatio attribute, and always do non-uniform stretch.
916 uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) |
917 imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE;
918 // For those SVG image sources which don't have fixed aspect ratio (i.e.
919 // without viewport size and viewBox), we should scale the source uniformly
920 // after the viewport size is decided by "Default Sizing Algorithm".
921 if (!aHasIntrinsicRatio) {
922 drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING;
923 }
924 // Retrieve or create the subimage we'll draw.
925 nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height);
926 if (isRequestBacked) {
927 CachedBorderImageData* cachedData =
928 mForFrame->GetProperty(nsIFrame::CachedBorderImageDataProperty());
929 if (!cachedData) {
930 cachedData = new CachedBorderImageData();
931 mForFrame->AddProperty(nsIFrame::CachedBorderImageDataProperty(),
932 cachedData);
933 }
934 if (!(subImage = cachedData->GetSubImage(aIndex))) {
935 subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize);
936 cachedData->SetSubImage(aIndex, subImage);
937 }
938 } else {
939 // This path, for eStyleImageType_Element, is currently slower than it
940 // needs to be because we don't cache anything. (In particular, if we have
941 // to draw to a temporary surface inside ClippedImage, we don't cache that
942 // temporary surface since we immediately throw the ClippedImage we create
943 // here away.) However, if we did cache, we'd need to know when to
944 // invalidate that cache, and it's not clear that it's worth the trouble
945 // since using border-image with -moz-element is rare.
946
947 RefPtr<gfxDrawable> drawable =
948 DrawableForElement(nsRect(nsPoint(), mSize), aRenderingContext);
949 if (!drawable) {
950 NS_WARNING("Could not create drawable for element");
951 return ImgDrawResult::TEMPORARY_ERROR;
952 }
953
954 nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
955 subImage = ImageOps::Clip(image, srcRect, aSVGViewportSize);
956 }
957
958 MOZ_ASSERT(!aSVGViewportSize ||
959 subImage->GetType() == imgIContainer::TYPE_VECTOR);
960
961 SamplingFilter samplingFilter =
962 nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
963
964 if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
965 ImgDrawResult result = nsLayoutUtils::DrawSingleImage(
966 aRenderingContext, aPresContext, subImage, samplingFilter, aFill,
967 aDirtyRect, /* no SVGImageContext */ Nothing(), drawFlags);
968
969 if (!mImage->IsComplete()) {
970 result &= ImgDrawResult::SUCCESS_NOT_COMPLETE;
971 }
972
973 return result;
974 }
975
976 nsSize repeatSize;
977 nsRect fillRect(aFill);
978 nsRect tile = ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize);
979
980 ImgDrawResult result = nsLayoutUtils::DrawBackgroundImage(
981 aRenderingContext, mForFrame, aPresContext, subImage, samplingFilter,
982 tile, fillRect, repeatSize, tile.TopLeft(), aDirtyRect, drawFlags,
983 ExtendMode::CLAMP, 1.0);
984
985 if (!mImage->IsComplete()) {
986 result &= ImgDrawResult::SUCCESS_NOT_COMPLETE;
987 }
988
989 return result;
990 }
991
992 nsSize repeatSize(aFill.Size());
993 nsRect fillRect(aFill);
994 nsRect destTile =
995 RequiresScaling(fillRect, aHFill, aVFill, aUnitSize)
996 ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize)
997 : fillRect;
998
999 return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile, fillRect,
1000 destTile.TopLeft(), repeatSize, aSrc);
1001 }
1002
DrawShapeImage(nsPresContext * aPresContext,gfxContext & aRenderingContext)1003 ImgDrawResult nsImageRenderer::DrawShapeImage(nsPresContext* aPresContext,
1004 gfxContext& aRenderingContext) {
1005 if (!IsReady()) {
1006 MOZ_ASSERT_UNREACHABLE(
1007 "Ensure PrepareImage() has returned true before "
1008 "calling me");
1009 return ImgDrawResult::NOT_READY;
1010 }
1011
1012 if (mSize.width <= 0 || mSize.height <= 0) {
1013 return ImgDrawResult::SUCCESS;
1014 }
1015
1016 if (mImage->IsImageRequestType()) {
1017 uint32_t drawFlags =
1018 ConvertImageRendererToDrawFlags(mFlags) | imgIContainer::FRAME_FIRST;
1019 nsRect dest(nsPoint(0, 0), mSize);
1020 // We have a tricky situation in our choice of SamplingFilter. Shape
1021 // images define a float area based on the alpha values in the rendered
1022 // pixels. When multiple device pixels are used for one css pixel, the
1023 // sampling can change crisp edges into aliased edges. For visual pixels,
1024 // that's usually the right choice. For defining a float area, it can
1025 // cause problems. If a style is using a shape-image-threshold value that
1026 // is less than the alpha of the edge pixels, any filtering may smear the
1027 // alpha into adjacent pixels and expand the float area in a confusing
1028 // way. Since the alpha threshold can be set precisely in CSS, and since a
1029 // web author may be counting on that threshold to define a precise float
1030 // area from an image, it is least confusing to have the rendered pixels
1031 // have unfiltered alpha. We use SamplingFilter::POINT to ensure that each
1032 // rendered pixel has an alpha that precisely matches the alpha of the
1033 // closest pixel in the image.
1034 return nsLayoutUtils::DrawSingleImage(
1035 aRenderingContext, aPresContext, mImageContainer, SamplingFilter::POINT,
1036 dest, dest, Nothing(), drawFlags);
1037 }
1038
1039 if (mImage->IsGradient()) {
1040 nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create(
1041 aPresContext, mForFrame->Style(), *mGradientData, mSize);
1042 nsRect dest(nsPoint(0, 0), mSize);
1043 renderer.Paint(aRenderingContext, dest, dest, mSize,
1044 CSSIntRect::FromAppUnitsRounded(dest), dest, 1.0);
1045 return ImgDrawResult::SUCCESS;
1046 }
1047
1048 // Unsupported image type.
1049 return ImgDrawResult::BAD_IMAGE;
1050 }
1051
IsRasterImage()1052 bool nsImageRenderer::IsRasterImage() {
1053 return mImageContainer &&
1054 mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
1055 }
1056
IsAnimatedImage()1057 bool nsImageRenderer::IsAnimatedImage() {
1058 bool animated = false;
1059 return mImageContainer &&
1060 NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated;
1061 }
1062
GetImage()1063 already_AddRefed<imgIContainer> nsImageRenderer::GetImage() {
1064 return do_AddRef(mImageContainer);
1065 }
1066
IsImageContainerAvailable(layers::LayerManager * aManager,uint32_t aFlags)1067 bool nsImageRenderer::IsImageContainerAvailable(layers::LayerManager* aManager,
1068 uint32_t aFlags) {
1069 return mImageContainer &&
1070 mImageContainer->IsImageContainerAvailable(aManager, aFlags);
1071 }
1072
PurgeCacheForViewportChange(const Maybe<nsSize> & aSVGViewportSize,const bool aHasIntrinsicRatio)1073 void nsImageRenderer::PurgeCacheForViewportChange(
1074 const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio) {
1075 // Check if we should flush the cached data - only vector images need to do
1076 // the check since they might not have fixed ratio.
1077 if (mImageContainer &&
1078 mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
1079 if (auto* cachedData =
1080 mForFrame->GetProperty(nsIFrame::CachedBorderImageDataProperty())) {
1081 cachedData->PurgeCacheForViewportChange(aSVGViewportSize,
1082 aHasIntrinsicRatio);
1083 }
1084 }
1085 }
1086