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/. */
7 /* utility code for drawing images as CSS borders, backgrounds, and shapes. */
9 #include "nsImageRenderer.h"
11 #include "mozilla/webrender/WebRenderAPI.h"
13 #include "gfxContext.h"
14 #include "gfxDrawable.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"
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"
34 using namespace mozilla
;
35 using namespace mozilla::gfx
;
36 using namespace mozilla::image
;
37 using namespace mozilla::layers
;
39 nsSize
CSSSizeOrRatio::ComputeConcreteSize() const {
40 NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
41 if (mHasWidth
&& mHasHeight
) {
42 return nsSize(mWidth
, mHeight
);
45 return nsSize(mWidth
, mRatio
.Inverted().ApplyTo(mWidth
));
48 MOZ_ASSERT(mHasHeight
);
49 return nsSize(mRatio
.ApplyTo(mHeight
), mHeight
);
52 nsImageRenderer::nsImageRenderer(nsIFrame
* aForFrame
, const StyleImage
* aImage
,
54 : mForFrame(aForFrame
),
55 mImage(&aImage
->FinalImage()),
56 mImageResolution(aImage
->GetResolution(*aForFrame
->Style())),
58 mImageContainer(nullptr),
59 mGradientData(nullptr),
60 mPaintServerFrame(nullptr),
61 mPrepareResult(ImgDrawResult::NOT_READY
),
64 mExtendMode(ExtendMode::CLAMP
),
65 mMaskOp(StyleMaskMode::MatchSource
) {}
67 bool nsImageRenderer::PrepareImage() {
68 if (mImage
->IsNone()) {
69 mPrepareResult
= ImgDrawResult::BAD_IMAGE
;
73 const bool isImageRequest
= mImage
->IsImageRequestType();
74 MOZ_ASSERT_IF(!isImageRequest
, !mImage
->GetImageRequest());
75 imgRequestProxy
* request
= nullptr;
77 request
= mImage
->GetImageRequest();
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
;
86 if (!mImage
->IsComplete()) {
87 MOZ_DIAGNOSTIC_ASSERT(isImageRequest
);
89 // Make sure the image is actually decoding.
90 bool frameComplete
= request
->StartDecodingWithResult(
91 imgIContainer::FLAG_ASYNC_NOTIFY
|
92 imgIContainer::FLAG_AVOID_REDECODE_FOR_SIZE
);
94 // Boost the loading priority since we know we want to draw the image.
95 if (mFlags
& nsImageRenderer::FLAG_PAINTING_TO_WINDOW
) {
96 request
->BoostPriority(imgIRequest::CATEGORY_DISPLAY
);
99 // Check again to see if we finished.
100 // We cannot prepare the image for rendering if it is not fully loaded.
101 if (!frameComplete
&& !mImage
->IsComplete()) {
102 uint32_t imageStatus
= 0;
103 request
->GetImageStatus(&imageStatus
);
104 if (imageStatus
& imgIRequest::STATUS_ERROR
) {
105 mPrepareResult
= ImgDrawResult::BAD_IMAGE
;
109 // If not errored, and we requested a sync decode, and the image has
110 // loaded, push on through because the Draw() will do a sync decode then.
111 const bool syncDecodeWillComplete
=
112 (mFlags
& FLAG_SYNC_DECODE_IMAGES
) &&
113 (imageStatus
& imgIRequest::STATUS_LOAD_COMPLETE
);
115 bool canDrawPartial
=
116 (mFlags
& nsImageRenderer::FLAG_DRAW_PARTIAL_FRAMES
) &&
117 isImageRequest
&& mImage
->IsSizeAvailable();
119 // If we are drawing a partial frame then we want to make sure there are
120 // some pixels to draw, otherwise we waste effort pushing through to draw
122 if (!syncDecodeWillComplete
&& canDrawPartial
) {
123 nsCOMPtr
<imgIContainer
> image
;
126 NS_SUCCEEDED(request
->GetImage(getter_AddRefs(image
))) && image
&&
127 image
->GetType() == imgIContainer::TYPE_RASTER
&&
128 image
->HasDecodedPixels();
131 // If we can draw partial then proceed if we at least have the size
133 if (!(syncDecodeWillComplete
|| canDrawPartial
)) {
134 mPrepareResult
= ImgDrawResult::NOT_READY
;
140 if (isImageRequest
) {
141 nsCOMPtr
<imgIContainer
> srcImage
;
142 nsresult rv
= request
->GetImage(getter_AddRefs(srcImage
));
143 MOZ_ASSERT(NS_SUCCEEDED(rv
) && srcImage
,
144 "If GetImage() is failing, mImage->IsComplete() "
145 "should have returned false");
146 if (!NS_SUCCEEDED(rv
)) {
151 StyleImageOrientation orientation
=
152 mForFrame
->StyleVisibility()->UsedImageOrientation(request
);
153 srcImage
= nsLayoutUtils::OrientImage(srcImage
, orientation
);
156 mImageContainer
.swap(srcImage
);
157 mPrepareResult
= ImgDrawResult::SUCCESS
;
158 } else if (mImage
->IsGradient()) {
159 mGradientData
= &*mImage
->AsGradient();
160 mPrepareResult
= ImgDrawResult::SUCCESS
;
161 } else if (mImage
->IsElement()) {
162 dom::Element
* paintElement
= // may be null
163 SVGObserverUtils::GetAndObserveBackgroundImage(
164 mForFrame
->FirstContinuation(), mImage
->AsElement().AsAtom());
165 // If the referenced element is an <img>, <canvas>, or <video> element,
166 // prefer SurfaceFromElement as it's more reliable.
167 mImageElementSurface
= nsLayoutUtils::SurfaceFromElement(paintElement
);
169 if (!mImageElementSurface
.GetSourceSurface()) {
170 nsIFrame
* paintServerFrame
=
171 paintElement
? paintElement
->GetPrimaryFrame() : nullptr;
172 // If there's no referenced frame, or the referenced frame is
173 // non-displayable SVG, then we have nothing valid to paint.
174 if (!paintServerFrame
|| (paintServerFrame
->IsSVGFrame() &&
175 !static_cast<SVGPaintServerFrame
*>(
176 do_QueryFrame(paintServerFrame
)) &&
177 !static_cast<ISVGDisplayableFrame
*>(
178 do_QueryFrame(paintServerFrame
)))) {
179 mPrepareResult
= ImgDrawResult::BAD_IMAGE
;
182 mPaintServerFrame
= paintServerFrame
;
185 mPrepareResult
= ImgDrawResult::SUCCESS
;
186 } else if (mImage
->IsCrossFade()) {
187 // See bug 546052 - cross-fade implementation still being worked
189 mPrepareResult
= ImgDrawResult::BAD_IMAGE
;
192 MOZ_ASSERT(mImage
->IsNone(), "Unknown image type?");
198 CSSSizeOrRatio
nsImageRenderer::ComputeIntrinsicSize() {
199 NS_ASSERTION(IsReady(),
200 "Ensure PrepareImage() has returned true "
201 "before calling me");
203 CSSSizeOrRatio result
;
205 case StyleImage::Tag::Url
: {
206 bool haveWidth
, haveHeight
;
207 CSSIntSize imageIntSize
;
208 nsLayoutUtils::ComputeSizeForDrawing(mImageContainer
, mImageResolution
,
209 imageIntSize
, result
.mRatio
,
210 haveWidth
, haveHeight
);
212 result
.SetWidth(CSSPixel::ToAppUnits(imageIntSize
.width
));
215 result
.SetHeight(CSSPixel::ToAppUnits(imageIntSize
.height
));
218 // If we know the aspect ratio and one of the dimensions,
219 // we can compute the other missing width or height.
220 if (!haveHeight
&& haveWidth
&& result
.mRatio
) {
221 CSSIntCoord intrinsicHeight
=
222 result
.mRatio
.Inverted().ApplyTo(imageIntSize
.width
);
223 result
.SetHeight(nsPresContext::CSSPixelsToAppUnits(intrinsicHeight
));
224 } else if (haveHeight
&& !haveWidth
&& result
.mRatio
) {
225 CSSIntCoord intrinsicWidth
= result
.mRatio
.ApplyTo(imageIntSize
.height
);
226 result
.SetWidth(nsPresContext::CSSPixelsToAppUnits(intrinsicWidth
));
231 case StyleImage::Tag::Element
: {
232 // XXX element() should have the width/height of the referenced element,
233 // and that element's ratio, if it matches. If it doesn't match, it
234 // should have no width/height or ratio. See element() in CSS images:
235 // <http://dev.w3.org/csswg/css-images-4/#element-notation>.
236 // Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize
238 if (mPaintServerFrame
) {
239 // SVG images have no intrinsic size
240 if (!mPaintServerFrame
->IsSVGFrame()) {
241 // The intrinsic image size for a generic nsIFrame paint server is
242 // the union of the border-box rects of all of its continuations,
243 // rounded to device pixels.
244 int32_t appUnitsPerDevPixel
=
245 mForFrame
->PresContext()->AppUnitsPerDevPixel();
246 result
.SetSize(IntSizeToAppUnits(
247 SVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame
)
248 .ToNearestPixels(appUnitsPerDevPixel
),
249 appUnitsPerDevPixel
));
252 NS_ASSERTION(mImageElementSurface
.GetSourceSurface(),
253 "Surface should be ready.");
254 IntSize surfaceSize
= mImageElementSurface
.mSize
;
256 nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize
.width
),
257 nsPresContext::CSSPixelsToAppUnits(surfaceSize
.height
)));
261 case StyleImage::Tag::ImageSet
:
262 MOZ_FALLTHROUGH_ASSERT("image-set should be resolved already");
263 // Bug 546052 cross-fade not yet implemented.
264 case StyleImage::Tag::CrossFade
:
265 // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
266 // intrinsic dimensions.
267 case StyleImage::Tag::Gradient
:
268 case StyleImage::Tag::None
:
276 nsSize
nsImageRenderer::ComputeConcreteSize(
277 const CSSSizeOrRatio
& aSpecifiedSize
, const CSSSizeOrRatio
& aIntrinsicSize
,
278 const nsSize
& aDefaultSize
) {
279 // The specified size is fully specified, just use that
280 if (aSpecifiedSize
.IsConcrete()) {
281 return aSpecifiedSize
.ComputeConcreteSize();
284 MOZ_ASSERT(!aSpecifiedSize
.mHasWidth
|| !aSpecifiedSize
.mHasHeight
);
286 if (!aSpecifiedSize
.mHasWidth
&& !aSpecifiedSize
.mHasHeight
) {
287 // no specified size, try using the intrinsic size
288 if (aIntrinsicSize
.CanComputeConcreteSize()) {
289 return aIntrinsicSize
.ComputeConcreteSize();
292 if (aIntrinsicSize
.mHasWidth
) {
293 return nsSize(aIntrinsicSize
.mWidth
, aDefaultSize
.height
);
295 if (aIntrinsicSize
.mHasHeight
) {
296 return nsSize(aDefaultSize
.width
, aIntrinsicSize
.mHeight
);
299 // couldn't use the intrinsic size either, revert to using the default size
300 return ComputeConstrainedSize(aDefaultSize
, aIntrinsicSize
.mRatio
, CONTAIN
);
303 MOZ_ASSERT(aSpecifiedSize
.mHasWidth
|| aSpecifiedSize
.mHasHeight
);
305 // The specified height is partial, try to compute the missing part.
306 if (aSpecifiedSize
.mHasWidth
) {
308 if (aIntrinsicSize
.HasRatio()) {
309 height
= aIntrinsicSize
.mRatio
.Inverted().ApplyTo(aSpecifiedSize
.mWidth
);
310 } else if (aIntrinsicSize
.mHasHeight
) {
311 height
= aIntrinsicSize
.mHeight
;
313 height
= aDefaultSize
.height
;
315 return nsSize(aSpecifiedSize
.mWidth
, height
);
318 MOZ_ASSERT(aSpecifiedSize
.mHasHeight
);
320 if (aIntrinsicSize
.HasRatio()) {
321 width
= aIntrinsicSize
.mRatio
.ApplyTo(aSpecifiedSize
.mHeight
);
322 } else if (aIntrinsicSize
.mHasWidth
) {
323 width
= aIntrinsicSize
.mWidth
;
325 width
= aDefaultSize
.width
;
327 return nsSize(width
, aSpecifiedSize
.mHeight
);
331 nsSize
nsImageRenderer::ComputeConstrainedSize(
332 const nsSize
& aConstrainingSize
, const AspectRatio
& aIntrinsicRatio
,
334 if (!aIntrinsicRatio
) {
335 return aConstrainingSize
;
338 // Suppose we're doing a "contain" fit. If the image's aspect ratio has a
339 // "fatter" shape than the constraint area, then we need to use the
340 // constraint area's full width, and we need to use the aspect ratio to
341 // produce a height. On the other hand, if the aspect ratio is "skinnier", we
342 // use the constraint area's full height, and we use the aspect ratio to
343 // produce a width. (If instead we're doing a "cover" fit, then it can easily
344 // be seen that we should do precisely the opposite.)
346 // We check if the image's aspect ratio is "fatter" than the constraint area
347 // by simply applying the aspect ratio to the constraint area's height, to
348 // produce a "hypothetical width", and we check whether that
349 // aspect-ratio-provided "hypothetical width" is wider than the constraint
350 // area's actual width. If it is, then the aspect ratio is fatter than the
353 // This is equivalent to the more descriptive alternative:
355 // AspectRatio::FromSize(aConstrainingSize) < aIntrinsicRatio
357 // But gracefully handling the case where one of the two dimensions from
358 // aConstrainingSize is zero. This is easy to prove since:
360 // aConstrainingSize.width / aConstrainingSize.height < aIntrinsicRatio
362 // Is trivially equivalent to:
364 // aIntrinsicRatio.width < aIntrinsicRatio * aConstrainingSize.height
366 // For the cases where height is not zero.
368 // We use float math here to avoid losing precision for very large backgrounds
369 // since we use saturating nscoord math otherwise.
370 const float constraintWidth
= float(aConstrainingSize
.width
);
371 const float hypotheticalWidth
=
372 aIntrinsicRatio
.ApplyToFloat(aConstrainingSize
.height
);
375 if ((aFitType
== CONTAIN
) == (constraintWidth
< hypotheticalWidth
)) {
376 size
.width
= aConstrainingSize
.width
;
377 size
.height
= aIntrinsicRatio
.Inverted().ApplyTo(aConstrainingSize
.width
);
378 // If we're reducing the size by less than one css pixel, then just use the
379 // constraining size.
380 if (aFitType
== CONTAIN
&&
381 aConstrainingSize
.height
- size
.height
< AppUnitsPerCSSPixel()) {
382 size
.height
= aConstrainingSize
.height
;
385 size
.height
= aConstrainingSize
.height
;
386 size
.width
= aIntrinsicRatio
.ApplyTo(aConstrainingSize
.height
);
387 if (aFitType
== CONTAIN
&&
388 aConstrainingSize
.width
- size
.width
< AppUnitsPerCSSPixel()) {
389 size
.width
= aConstrainingSize
.width
;
396 * mSize is the image's "preferred" size for this particular rendering, while
397 * the drawn (aka concrete) size is the actual rendered size after accounting
398 * for background-size etc.. The preferred size is most often the image's
399 * intrinsic dimensions. But for images with incomplete intrinsic dimensions,
400 * the preferred size varies, depending on the specified and default sizes, see
401 * nsImageRenderer::Compute*Size.
403 * This distinction is necessary because the components of a vector image are
404 * specified with respect to its preferred size for a rendering situation, not
405 * to its actual rendered size. For example, consider a 4px wide background
406 * vector image with no height which contains a left-aligned
407 * 2px wide black rectangle with height 100%. If the background-size width is
408 * auto (or 4px), the vector image will render 4px wide, and the black rectangle
409 * will be 2px wide. If the background-size width is 8px, the vector image will
410 * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
411 * In both cases mSize.width will be 4px; but in the first case the returned
412 * width will be 4px, while in the second case the returned width will be 8px.
414 void nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio
& aIntrinsicSize
,
415 const nsSize
& aDefaultSize
) {
417 aIntrinsicSize
.mHasWidth
? aIntrinsicSize
.mWidth
: aDefaultSize
.width
;
419 aIntrinsicSize
.mHasHeight
? aIntrinsicSize
.mHeight
: aDefaultSize
.height
;
422 // Convert from nsImageRenderer flags to the flags we want to use for drawing in
423 // the imgIContainer namespace.
424 static uint32_t ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags
) {
425 uint32_t drawFlags
= imgIContainer::FLAG_ASYNC_NOTIFY
;
426 if (aImageRendererFlags
& nsImageRenderer::FLAG_SYNC_DECODE_IMAGES
) {
427 drawFlags
|= imgIContainer::FLAG_SYNC_DECODE
;
429 drawFlags
|= imgIContainer::FLAG_SYNC_DECODE_IF_FAST
;
431 if (aImageRendererFlags
& (nsImageRenderer::FLAG_PAINTING_TO_WINDOW
|
432 nsImageRenderer::FLAG_HIGH_QUALITY_SCALING
)) {
433 drawFlags
|= imgIContainer::FLAG_HIGH_QUALITY_SCALING
;
438 ImgDrawResult
nsImageRenderer::Draw(nsPresContext
* aPresContext
,
439 gfxContext
& aRenderingContext
,
440 const nsRect
& aDirtyRect
,
441 const nsRect
& aDest
, const nsRect
& aFill
,
442 const nsPoint
& aAnchor
,
443 const nsSize
& aRepeatSize
,
444 const CSSIntRect
& aSrc
, float aOpacity
) {
446 MOZ_ASSERT_UNREACHABLE(
447 "Ensure PrepareImage() has returned true before "
449 return ImgDrawResult::TEMPORARY_ERROR
;
452 if (aDest
.IsEmpty() || aFill
.IsEmpty() || mSize
.width
<= 0 ||
454 return ImgDrawResult::SUCCESS
;
457 SamplingFilter samplingFilter
=
458 nsLayoutUtils::GetSamplingFilterForFrame(mForFrame
);
459 ImgDrawResult result
= ImgDrawResult::SUCCESS
;
460 gfxContext
* ctx
= &aRenderingContext
;
461 Maybe
<gfxContext
> tempCtx
;
464 if (ctx
->CurrentOp() != CompositionOp::OP_OVER
||
465 mMaskOp
== StyleMaskMode::Luminance
) {
466 gfxRect clipRect
= ctx
->GetClipExtents(gfxContext::eDeviceSpace
);
467 tmpDTRect
= RoundedOut(ToRect(clipRect
));
468 if (tmpDTRect
.IsEmpty()) {
469 return ImgDrawResult::SUCCESS
;
471 RefPtr
<DrawTarget
> tempDT
= ctx
->GetDrawTarget()->CreateSimilarDrawTarget(
472 tmpDTRect
.Size(), SurfaceFormat::B8G8R8A8
);
473 if (!tempDT
|| !tempDT
->IsValid()) {
474 gfxDevCrash(LogReason::InvalidContext
)
475 << "ImageRenderer::Draw problem " << gfx::hexa(tempDT
);
476 return ImgDrawResult::TEMPORARY_ERROR
;
478 tempDT
->SetTransform(ctx
->GetDrawTarget()->GetTransform() *
479 Matrix::Translation(-tmpDTRect
.TopLeft()));
480 tempCtx
.emplace(tempDT
, /* aPreserveTransform */ true);
481 ctx
= &tempCtx
.ref();
483 gfxDevCrash(LogReason::InvalidContext
)
484 << "ImageRenderer::Draw problem " << gfx::hexa(tempDT
);
485 return ImgDrawResult::TEMPORARY_ERROR
;
490 case StyleImage::Tag::Url
: {
491 result
= nsLayoutUtils::DrawBackgroundImage(
492 *ctx
, mForFrame
, aPresContext
, mImageContainer
, samplingFilter
, aDest
,
493 aFill
, aRepeatSize
, aAnchor
, aDirtyRect
,
494 ConvertImageRendererToDrawFlags(mFlags
), mExtendMode
, aOpacity
);
497 case StyleImage::Tag::Gradient
: {
498 nsCSSGradientRenderer renderer
= nsCSSGradientRenderer::Create(
499 aPresContext
, mForFrame
->Style(), *mGradientData
, mSize
);
501 renderer
.Paint(*ctx
, aDest
, aFill
, aRepeatSize
, aSrc
, aDirtyRect
,
505 case StyleImage::Tag::Element
: {
506 RefPtr
<gfxDrawable
> drawable
= DrawableForElement(aDest
, *ctx
);
508 NS_WARNING("Could not create drawable for element");
509 return ImgDrawResult::TEMPORARY_ERROR
;
512 nsCOMPtr
<imgIContainer
> image(ImageOps::CreateFromDrawable(drawable
));
513 result
= nsLayoutUtils::DrawImage(
514 *ctx
, mForFrame
->Style(), aPresContext
, image
, samplingFilter
, aDest
,
515 aFill
, aAnchor
, aDirtyRect
, ConvertImageRendererToDrawFlags(mFlags
),
519 case StyleImage::Tag::ImageSet
:
520 MOZ_FALLTHROUGH_ASSERT("image-set should be resolved already");
521 // See bug 546052 - cross-fade implementation still being worked
523 case StyleImage::Tag::CrossFade
:
524 case StyleImage::Tag::None
:
528 if (!tmpDTRect
.IsEmpty()) {
529 DrawTarget
* dt
= aRenderingContext
.GetDrawTarget();
530 Matrix oldTransform
= dt
->GetTransform();
531 dt
->SetTransform(Matrix());
532 if (mMaskOp
== StyleMaskMode::Luminance
) {
533 RefPtr
<SourceSurface
> surf
= ctx
->GetDrawTarget()->IntoLuminanceSource(
534 LuminanceType::LUMINANCE
, 1.0f
);
535 dt
->MaskSurface(ColorPattern(DeviceColor(0, 0, 0, 1.0f
)), surf
,
537 DrawOptions(1.0f
, aRenderingContext
.CurrentOp()));
539 RefPtr
<SourceSurface
> surf
= ctx
->GetDrawTarget()->Snapshot();
542 Rect(tmpDTRect
.x
, tmpDTRect
.y
, tmpDTRect
.width
, tmpDTRect
.height
),
543 Rect(0, 0, tmpDTRect
.width
, tmpDTRect
.height
),
544 DrawSurfaceOptions(SamplingFilter::POINT
),
545 DrawOptions(1.0f
, aRenderingContext
.CurrentOp()));
548 dt
->SetTransform(oldTransform
);
551 if (!mImage
->IsComplete()) {
552 result
&= ImgDrawResult::SUCCESS_NOT_COMPLETE
;
558 ImgDrawResult
nsImageRenderer::BuildWebRenderDisplayItems(
559 nsPresContext
* aPresContext
, mozilla::wr::DisplayListBuilder
& aBuilder
,
560 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
561 const mozilla::layers::StackingContextHelper
& aSc
,
562 mozilla::layers::RenderRootStateManager
* aManager
, nsDisplayItem
* aItem
,
563 const nsRect
& aDirtyRect
, const nsRect
& aDest
, const nsRect
& aFill
,
564 const nsPoint
& aAnchor
, const nsSize
& aRepeatSize
, const CSSIntRect
& aSrc
,
567 MOZ_ASSERT_UNREACHABLE(
568 "Ensure PrepareImage() has returned true before "
570 return ImgDrawResult::NOT_READY
;
573 if (aDest
.IsEmpty() || aFill
.IsEmpty() || mSize
.width
<= 0 ||
575 return ImgDrawResult::SUCCESS
;
578 ImgDrawResult drawResult
= ImgDrawResult::SUCCESS
;
580 case StyleImage::Tag::Gradient
: {
581 nsCSSGradientRenderer renderer
= nsCSSGradientRenderer::Create(
582 aPresContext
, mForFrame
->Style(), *mGradientData
, mSize
);
584 renderer
.BuildWebRenderDisplayItems(aBuilder
, aSc
, aDest
, aFill
,
586 !aItem
->BackfaceIsHidden(), aOpacity
);
589 case StyleImage::Tag::Url
: {
590 ExtendMode extendMode
= mExtendMode
;
591 if (aDest
.Contains(aFill
)) {
592 extendMode
= ExtendMode::CLAMP
;
595 uint32_t containerFlags
= ConvertImageRendererToDrawFlags(mFlags
);
596 if (extendMode
== ExtendMode::CLAMP
&&
597 StaticPrefs::image_svg_blob_image() &&
598 mImageContainer
->GetType() == imgIContainer::TYPE_VECTOR
) {
599 containerFlags
|= imgIContainer::FLAG_RECORD_BLOB
;
602 CSSIntSize destCSSSize
{
603 nsPresContext::AppUnitsToIntCSSPixels(aDest
.width
),
604 nsPresContext::AppUnitsToIntCSSPixels(aDest
.height
)};
606 SVGImageContext
svgContext(Some(destCSSSize
));
607 Maybe
<ImageIntRegion
> region
;
609 const int32_t appUnitsPerDevPixel
=
610 mForFrame
->PresContext()->AppUnitsPerDevPixel();
611 LayoutDeviceRect destRect
=
612 LayoutDeviceRect::FromAppUnits(aDest
, appUnitsPerDevPixel
);
613 LayoutDeviceRect clipRect
=
614 LayoutDeviceRect::FromAppUnits(aFill
, appUnitsPerDevPixel
);
615 auto stretchSize
= wr::ToLayoutSize(destRect
.Size());
617 gfx::IntSize decodeSize
=
618 nsLayoutUtils::ComputeImageContainerDrawingParameters(
619 mImageContainer
, mForFrame
, destRect
, clipRect
, aSc
,
620 containerFlags
, svgContext
, region
);
622 RefPtr
<image::WebRenderImageProvider
> provider
;
623 drawResult
= mImageContainer
->GetImageProvider(
624 aManager
->LayerManager(), decodeSize
, svgContext
, region
,
625 containerFlags
, getter_AddRefs(provider
));
627 Maybe
<wr::ImageKey
> key
=
628 aManager
->CommandBuilder().CreateImageProviderKey(
629 aItem
, provider
, drawResult
, aResources
);
630 if (key
.isNothing()) {
635 wr::ToImageRendering(aItem
->Frame()->UsedImageRendering());
636 wr::LayoutRect clip
= wr::ToLayoutRect(clipRect
);
638 // If we provided a region to the provider, then it already took the
639 // dest rect into account when it did the recording.
640 wr::LayoutRect dest
= region
? clip
: wr::ToLayoutRect(destRect
);
642 if (extendMode
== ExtendMode::CLAMP
) {
643 // The image is not repeating. Just push as a regular image.
644 aBuilder
.PushImage(dest
, clip
, !aItem
->BackfaceIsHidden(), false,
645 rendering
, key
.value(), true,
646 wr::ColorF
{1.0f
, 1.0f
, 1.0f
, aOpacity
});
648 nsPoint firstTilePos
= nsLayoutUtils::GetBackgroundFirstTilePos(
649 aDest
.TopLeft(), aFill
.TopLeft(), aRepeatSize
);
650 LayoutDeviceRect fillRect
= LayoutDeviceRect::FromAppUnits(
651 nsRect(firstTilePos
.x
, firstTilePos
.y
,
652 aFill
.XMost() - firstTilePos
.x
,
653 aFill
.YMost() - firstTilePos
.y
),
654 appUnitsPerDevPixel
);
655 wr::LayoutRect fill
= wr::ToLayoutRect(fillRect
);
657 switch (extendMode
) {
658 case ExtendMode::REPEAT_Y
:
659 fill
.min
.x
= dest
.min
.x
;
660 fill
.max
.x
= dest
.max
.x
;
661 stretchSize
.width
= dest
.width();
663 case ExtendMode::REPEAT_X
:
664 fill
.min
.y
= dest
.min
.y
;
665 fill
.max
.y
= dest
.max
.y
;
666 stretchSize
.height
= dest
.height();
672 LayoutDeviceSize gapSize
= LayoutDeviceSize::FromAppUnits(
673 aRepeatSize
- aDest
.Size(), appUnitsPerDevPixel
);
675 aBuilder
.PushRepeatingImage(fill
, clip
, !aItem
->BackfaceIsHidden(),
676 stretchSize
, wr::ToLayoutSize(gapSize
),
677 rendering
, key
.value(), true,
678 wr::ColorF
{1.0f
, 1.0f
, 1.0f
, aOpacity
});
686 if (!mImage
->IsComplete() && drawResult
== ImgDrawResult::SUCCESS
) {
687 return ImgDrawResult::SUCCESS_NOT_COMPLETE
;
692 already_AddRefed
<gfxDrawable
> nsImageRenderer::DrawableForElement(
693 const nsRect
& aImageRect
, gfxContext
& aContext
) {
694 NS_ASSERTION(mType
== StyleImage::Tag::Element
,
695 "DrawableForElement only makes sense if backed by an element");
696 if (mPaintServerFrame
) {
697 // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here,
698 // DrawableFromPaintServer would have to return a ImgDrawResult indicating
699 // whether any images could not be painted because they weren't fully
700 // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all
701 // problems, as it won't help if there are image which haven't finished
702 // loading, but it's better than nothing.
703 int32_t appUnitsPerDevPixel
=
704 mForFrame
->PresContext()->AppUnitsPerDevPixel();
705 nsRect destRect
= aImageRect
- aImageRect
.TopLeft();
706 nsIntSize roundedOut
= destRect
.ToOutsidePixels(appUnitsPerDevPixel
).Size();
707 IntSize
imageSize(roundedOut
.width
, roundedOut
.height
);
709 RefPtr
<gfxDrawable
> drawable
;
711 SurfaceFormat format
= aContext
.GetDrawTarget()->GetFormat();
712 // Don't allow creating images that are too big
713 if (aContext
.GetDrawTarget()->CanCreateSimilarDrawTarget(imageSize
,
715 drawable
= SVGIntegrationUtils::DrawableFromPaintServer(
716 mPaintServerFrame
, mForFrame
, mSize
, imageSize
,
717 aContext
.GetDrawTarget(), aContext
.CurrentMatrixDouble(),
718 SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES
);
721 return drawable
.forget();
723 NS_ASSERTION(mImageElementSurface
.GetSourceSurface(),
724 "Surface should be ready.");
725 RefPtr
<gfxDrawable
> drawable
=
726 new gfxSurfaceDrawable(mImageElementSurface
.GetSourceSurface().get(),
727 mImageElementSurface
.mSize
);
728 return drawable
.forget();
731 ImgDrawResult
nsImageRenderer::DrawLayer(
732 nsPresContext
* aPresContext
, gfxContext
& aRenderingContext
,
733 const nsRect
& aDest
, const nsRect
& aFill
, const nsPoint
& aAnchor
,
734 const nsRect
& aDirty
, const nsSize
& aRepeatSize
, float aOpacity
) {
736 MOZ_ASSERT_UNREACHABLE(
737 "Ensure PrepareImage() has returned true before "
739 return ImgDrawResult::TEMPORARY_ERROR
;
742 if (aDest
.IsEmpty() || aFill
.IsEmpty() || mSize
.width
<= 0 ||
744 return ImgDrawResult::SUCCESS
;
748 aPresContext
, aRenderingContext
, aDirty
, aDest
, aFill
, aAnchor
,
750 CSSIntRect(0, 0, nsPresContext::AppUnitsToIntCSSPixels(mSize
.width
),
751 nsPresContext::AppUnitsToIntCSSPixels(mSize
.height
)),
755 ImgDrawResult
nsImageRenderer::BuildWebRenderDisplayItemsForLayer(
756 nsPresContext
* aPresContext
, mozilla::wr::DisplayListBuilder
& aBuilder
,
757 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
758 const mozilla::layers::StackingContextHelper
& aSc
,
759 mozilla::layers::RenderRootStateManager
* aManager
, nsDisplayItem
* aItem
,
760 const nsRect
& aDest
, const nsRect
& aFill
, const nsPoint
& aAnchor
,
761 const nsRect
& aDirty
, const nsSize
& aRepeatSize
, float aOpacity
) {
763 MOZ_ASSERT_UNREACHABLE(
764 "Ensure PrepareImage() has returned true before "
766 return mPrepareResult
;
769 CSSIntRect
srcRect(0, 0, nsPresContext::AppUnitsToIntCSSPixels(mSize
.width
),
770 nsPresContext::AppUnitsToIntCSSPixels(mSize
.height
));
772 if (aDest
.IsEmpty() || aFill
.IsEmpty() || srcRect
.IsEmpty()) {
773 return ImgDrawResult::SUCCESS
;
775 return BuildWebRenderDisplayItems(aPresContext
, aBuilder
, aResources
, aSc
,
776 aManager
, aItem
, aDirty
, aDest
, aFill
,
777 aAnchor
, aRepeatSize
, srcRect
, aOpacity
);
781 * Compute the size and position of the master copy of the image. I.e., a single
782 * tile used to fill the dest rect.
783 * aFill The destination rect to be filled
784 * aHFill and aVFill are the repeat patterns for the component -
785 * StyleBorderImageRepeat - i.e., how a tiling unit is used to fill aFill
786 * aUnitSize The size of the source rect in dest coords.
788 static nsRect
ComputeTile(nsRect
& aFill
, StyleBorderImageRepeat aHFill
,
789 StyleBorderImageRepeat aVFill
,
790 const nsSize
& aUnitSize
, nsSize
& aRepeatSize
) {
793 case StyleBorderImageRepeat::Stretch
:
795 tile
.width
= aFill
.width
;
796 aRepeatSize
.width
= tile
.width
;
798 case StyleBorderImageRepeat::Repeat
:
799 tile
.x
= aFill
.x
+ aFill
.width
/ 2 - aUnitSize
.width
/ 2;
800 tile
.width
= aUnitSize
.width
;
801 aRepeatSize
.width
= tile
.width
;
803 case StyleBorderImageRepeat::Round
:
806 nsCSSRendering::ComputeRoundedSize(aUnitSize
.width
, aFill
.width
);
807 aRepeatSize
.width
= tile
.width
;
809 case StyleBorderImageRepeat::Space
: {
811 aRepeatSize
.width
= nsCSSRendering::ComputeBorderSpacedRepeatSize(
812 aUnitSize
.width
, aFill
.width
, space
);
813 tile
.x
= aFill
.x
+ space
;
814 tile
.width
= aUnitSize
.width
;
816 aFill
.width
= aFill
.width
- space
* 2;
819 MOZ_ASSERT_UNREACHABLE("unrecognized border-image fill style");
823 case StyleBorderImageRepeat::Stretch
:
825 tile
.height
= aFill
.height
;
826 aRepeatSize
.height
= tile
.height
;
828 case StyleBorderImageRepeat::Repeat
:
829 tile
.y
= aFill
.y
+ aFill
.height
/ 2 - aUnitSize
.height
/ 2;
830 tile
.height
= aUnitSize
.height
;
831 aRepeatSize
.height
= tile
.height
;
833 case StyleBorderImageRepeat::Round
:
836 nsCSSRendering::ComputeRoundedSize(aUnitSize
.height
, aFill
.height
);
837 aRepeatSize
.height
= tile
.height
;
839 case StyleBorderImageRepeat::Space
: {
841 aRepeatSize
.height
= nsCSSRendering::ComputeBorderSpacedRepeatSize(
842 aUnitSize
.height
, aFill
.height
, space
);
843 tile
.y
= aFill
.y
+ space
;
844 tile
.height
= aUnitSize
.height
;
846 aFill
.height
= aFill
.height
- space
* 2;
849 MOZ_ASSERT_UNREACHABLE("unrecognized border-image fill style");
856 * Returns true if the given set of arguments will require the tiles which fill
857 * the dest rect to be scaled from the source tile. See comment on ComputeTile
858 * for argument descriptions.
860 static bool RequiresScaling(const nsRect
& aFill
, StyleBorderImageRepeat aHFill
,
861 StyleBorderImageRepeat aVFill
,
862 const nsSize
& aUnitSize
) {
863 // If we have no tiling in either direction, we can skip the intermediate
865 return (aHFill
!= StyleBorderImageRepeat::Stretch
||
866 aVFill
!= StyleBorderImageRepeat::Stretch
) &&
867 (aUnitSize
.width
!= aFill
.width
|| aUnitSize
.height
!= aFill
.height
);
870 ImgDrawResult
nsImageRenderer::DrawBorderImageComponent(
871 nsPresContext
* aPresContext
, gfxContext
& aRenderingContext
,
872 const nsRect
& aDirtyRect
, const nsRect
& aFill
, const CSSIntRect
& aSrc
,
873 StyleBorderImageRepeat aHFill
, StyleBorderImageRepeat aVFill
,
874 const nsSize
& aUnitSize
, uint8_t aIndex
,
875 const Maybe
<nsSize
>& aSVGViewportSize
, const bool aHasIntrinsicRatio
) {
877 MOZ_ASSERT_UNREACHABLE(
878 "Ensure PrepareImage() has returned true before "
880 return ImgDrawResult::BAD_ARGS
;
883 if (aFill
.IsEmpty() || aSrc
.IsEmpty()) {
884 return ImgDrawResult::SUCCESS
;
887 const bool isRequestBacked
= mType
== StyleImage::Tag::Url
;
888 MOZ_ASSERT(isRequestBacked
== mImage
->IsImageRequestType());
890 if (isRequestBacked
|| mType
== StyleImage::Tag::Element
) {
891 nsCOMPtr
<imgIContainer
> subImage
;
893 // To draw one portion of an image into a border component, we stretch that
894 // portion to match the size of that border component and then draw onto.
895 // However, preserveAspectRatio attribute of a SVG image may break this
896 // rule. To get correct rendering result, we add
897 // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore
898 // preserveAspectRatio attribute, and always do non-uniform stretch.
899 uint32_t drawFlags
= ConvertImageRendererToDrawFlags(mFlags
) |
900 imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE
;
901 // For those SVG image sources which don't have fixed aspect ratio (i.e.
902 // without viewport size and viewBox), we should scale the source uniformly
903 // after the viewport size is decided by "Default Sizing Algorithm".
904 if (!aHasIntrinsicRatio
) {
905 drawFlags
= drawFlags
| imgIContainer::FLAG_FORCE_UNIFORM_SCALING
;
907 // Retrieve or create the subimage we'll draw.
908 nsIntRect
srcRect(aSrc
.x
, aSrc
.y
, aSrc
.width
, aSrc
.height
);
909 if (isRequestBacked
) {
910 subImage
= ImageOps::Clip(mImageContainer
, srcRect
, aSVGViewportSize
);
912 // This path, for eStyleImageType_Element, is currently slower than it
913 // needs to be because we don't cache anything. (In particular, if we have
914 // to draw to a temporary surface inside ClippedImage, we don't cache that
915 // temporary surface since we immediately throw the ClippedImage we create
916 // here away.) However, if we did cache, we'd need to know when to
917 // invalidate that cache, and it's not clear that it's worth the trouble
918 // since using border-image with -moz-element is rare.
920 RefPtr
<gfxDrawable
> drawable
=
921 DrawableForElement(nsRect(nsPoint(), mSize
), aRenderingContext
);
923 NS_WARNING("Could not create drawable for element");
924 return ImgDrawResult::TEMPORARY_ERROR
;
927 nsCOMPtr
<imgIContainer
> image(ImageOps::CreateFromDrawable(drawable
));
928 subImage
= ImageOps::Clip(image
, srcRect
, aSVGViewportSize
);
931 MOZ_ASSERT(!aSVGViewportSize
||
932 subImage
->GetType() == imgIContainer::TYPE_VECTOR
);
934 SamplingFilter samplingFilter
=
935 nsLayoutUtils::GetSamplingFilterForFrame(mForFrame
);
937 if (!RequiresScaling(aFill
, aHFill
, aVFill
, aUnitSize
)) {
938 ImgDrawResult result
= nsLayoutUtils::DrawSingleImage(
939 aRenderingContext
, aPresContext
, subImage
, samplingFilter
, aFill
,
940 aDirtyRect
, SVGImageContext(), drawFlags
);
942 if (!mImage
->IsComplete()) {
943 result
&= ImgDrawResult::SUCCESS_NOT_COMPLETE
;
950 nsRect
fillRect(aFill
);
951 nsRect tile
= ComputeTile(fillRect
, aHFill
, aVFill
, aUnitSize
, repeatSize
);
953 ImgDrawResult result
= nsLayoutUtils::DrawBackgroundImage(
954 aRenderingContext
, mForFrame
, aPresContext
, subImage
, samplingFilter
,
955 tile
, fillRect
, repeatSize
, tile
.TopLeft(), aDirtyRect
, drawFlags
,
956 ExtendMode::CLAMP
, 1.0);
958 if (!mImage
->IsComplete()) {
959 result
&= ImgDrawResult::SUCCESS_NOT_COMPLETE
;
965 nsSize
repeatSize(aFill
.Size());
966 nsRect
fillRect(aFill
);
968 RequiresScaling(fillRect
, aHFill
, aVFill
, aUnitSize
)
969 ? ComputeTile(fillRect
, aHFill
, aVFill
, aUnitSize
, repeatSize
)
972 return Draw(aPresContext
, aRenderingContext
, aDirtyRect
, destTile
, fillRect
,
973 destTile
.TopLeft(), repeatSize
, aSrc
);
976 ImgDrawResult
nsImageRenderer::DrawShapeImage(nsPresContext
* aPresContext
,
977 gfxContext
& aRenderingContext
) {
979 MOZ_ASSERT_UNREACHABLE(
980 "Ensure PrepareImage() has returned true before "
982 return ImgDrawResult::NOT_READY
;
985 if (mSize
.width
<= 0 || mSize
.height
<= 0) {
986 return ImgDrawResult::SUCCESS
;
989 if (mImage
->IsImageRequestType()) {
991 ConvertImageRendererToDrawFlags(mFlags
) | imgIContainer::FRAME_FIRST
;
992 nsRect
dest(nsPoint(0, 0), mSize
);
993 // We have a tricky situation in our choice of SamplingFilter. Shape
994 // images define a float area based on the alpha values in the rendered
995 // pixels. When multiple device pixels are used for one css pixel, the
996 // sampling can change crisp edges into aliased edges. For visual pixels,
997 // that's usually the right choice. For defining a float area, it can
998 // cause problems. If a style is using a shape-image-threshold value that
999 // is less than the alpha of the edge pixels, any filtering may smear the
1000 // alpha into adjacent pixels and expand the float area in a confusing
1001 // way. Since the alpha threshold can be set precisely in CSS, and since a
1002 // web author may be counting on that threshold to define a precise float
1003 // area from an image, it is least confusing to have the rendered pixels
1004 // have unfiltered alpha. We use SamplingFilter::POINT to ensure that each
1005 // rendered pixel has an alpha that precisely matches the alpha of the
1006 // closest pixel in the image.
1007 return nsLayoutUtils::DrawSingleImage(
1008 aRenderingContext
, aPresContext
, mImageContainer
, SamplingFilter::POINT
,
1009 dest
, dest
, SVGImageContext(), drawFlags
);
1012 if (mImage
->IsGradient()) {
1013 nsCSSGradientRenderer renderer
= nsCSSGradientRenderer::Create(
1014 aPresContext
, mForFrame
->Style(), *mGradientData
, mSize
);
1015 nsRect
dest(nsPoint(0, 0), mSize
);
1016 renderer
.Paint(aRenderingContext
, dest
, dest
, mSize
,
1017 CSSIntRect::FromAppUnitsRounded(dest
), dest
, 1.0);
1018 return ImgDrawResult::SUCCESS
;
1021 // Unsupported image type.
1022 return ImgDrawResult::BAD_IMAGE
;
1025 bool nsImageRenderer::IsRasterImage() {
1026 return mImageContainer
&&
1027 mImageContainer
->GetType() == imgIContainer::TYPE_RASTER
;
1030 already_AddRefed
<imgIContainer
> nsImageRenderer::GetImage() {
1031 return do_AddRef(mImageContainer
);