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