Backed out changeset 496886cb30a5 (bug 1867152) for bc failures on browser_user_input...
[gecko.git] / layout / painting / nsImageRenderer.cpp
blob845b0b8719068055ce25c5696049252c863519c7
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"
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"
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);
44 if (mHasWidth) {
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,
53 uint32_t aFlags)
54 : mForFrame(aForFrame),
55 mImage(&aImage->FinalImage()),
56 mImageResolution(aImage->GetResolution(*aForFrame->Style())),
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) {}
67 bool nsImageRenderer::PrepareImage() {
68 if (mImage->IsNone()) {
69 mPrepareResult = ImgDrawResult::BAD_IMAGE;
70 return false;
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;
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;
106 return false;
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
121 // nothing.
122 if (!syncDecodeWillComplete && canDrawPartial) {
123 nsCOMPtr<imgIContainer> image;
124 canDrawPartial =
125 canDrawPartial &&
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
132 // available.
133 if (!(syncDecodeWillComplete || canDrawPartial)) {
134 mPrepareResult = ImgDrawResult::NOT_READY;
135 return false;
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)) {
147 srcImage = nullptr;
150 if (srcImage) {
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;
180 return false;
182 mPaintServerFrame = paintServerFrame;
185 mPrepareResult = ImgDrawResult::SUCCESS;
186 } else if (mImage->IsCrossFade()) {
187 // See bug 546052 - cross-fade implementation still being worked
188 // on.
189 mPrepareResult = ImgDrawResult::BAD_IMAGE;
190 return false;
191 } else {
192 MOZ_ASSERT(mImage->IsNone(), "Unknown image type?");
195 return IsReady();
198 CSSSizeOrRatio nsImageRenderer::ComputeIntrinsicSize() {
199 NS_ASSERTION(IsReady(),
200 "Ensure PrepareImage() has returned true "
201 "before calling me");
203 CSSSizeOrRatio result;
204 switch (mType) {
205 case StyleImage::Tag::Url: {
206 bool haveWidth, haveHeight;
207 CSSIntSize imageIntSize;
208 nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, mImageResolution,
209 imageIntSize, result.mRatio,
210 haveWidth, haveHeight);
211 if (haveWidth) {
212 result.SetWidth(CSSPixel::ToAppUnits(imageIntSize.width));
214 if (haveHeight) {
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));
229 break;
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
237 // when fixing this!
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));
251 } else {
252 NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
253 "Surface should be ready.");
254 IntSize surfaceSize = mImageElementSurface.mSize;
255 result.SetSize(
256 nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
257 nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
259 break;
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:
269 break;
272 return result;
275 /* static */
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) {
307 nscoord height;
308 if (aIntrinsicSize.HasRatio()) {
309 height = aIntrinsicSize.mRatio.Inverted().ApplyTo(aSpecifiedSize.mWidth);
310 } else if (aIntrinsicSize.mHasHeight) {
311 height = aIntrinsicSize.mHeight;
312 } else {
313 height = aDefaultSize.height;
315 return nsSize(aSpecifiedSize.mWidth, height);
318 MOZ_ASSERT(aSpecifiedSize.mHasHeight);
319 nscoord width;
320 if (aIntrinsicSize.HasRatio()) {
321 width = aIntrinsicSize.mRatio.ApplyTo(aSpecifiedSize.mHeight);
322 } else if (aIntrinsicSize.mHasWidth) {
323 width = aIntrinsicSize.mWidth;
324 } else {
325 width = aDefaultSize.width;
327 return nsSize(width, aSpecifiedSize.mHeight);
330 /* static */
331 nsSize nsImageRenderer::ComputeConstrainedSize(
332 const nsSize& aConstrainingSize, const AspectRatio& aIntrinsicRatio,
333 FitType aFitType) {
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
351 // constraint area.
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);
374 nsSize size;
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;
384 } else {
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;
392 return size;
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) {
416 mSize.width =
417 aIntrinsicSize.mHasWidth ? aIntrinsicSize.mWidth : aDefaultSize.width;
418 mSize.height =
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;
428 } else {
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;
435 return drawFlags;
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) {
445 if (!IsReady()) {
446 MOZ_ASSERT_UNREACHABLE(
447 "Ensure PrepareImage() has returned true before "
448 "calling me");
449 return ImgDrawResult::TEMPORARY_ERROR;
452 if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 ||
453 mSize.height <= 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;
462 IntRect tmpDTRect;
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();
482 if (!ctx) {
483 gfxDevCrash(LogReason::InvalidContext)
484 << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
485 return ImgDrawResult::TEMPORARY_ERROR;
489 switch (mType) {
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);
495 break;
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,
502 aOpacity);
503 break;
505 case StyleImage::Tag::Element: {
506 RefPtr<gfxDrawable> drawable = DrawableForElement(aDest, *ctx);
507 if (!drawable) {
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),
516 aOpacity);
517 break;
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
522 // on.
523 case StyleImage::Tag::CrossFade:
524 case StyleImage::Tag::None:
525 break;
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,
536 tmpDTRect.TopLeft(),
537 DrawOptions(1.0f, aRenderingContext.CurrentOp()));
538 } else {
539 RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
540 dt->DrawSurface(
541 surf,
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;
555 return result;
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,
565 float aOpacity) {
566 if (!IsReady()) {
567 MOZ_ASSERT_UNREACHABLE(
568 "Ensure PrepareImage() has returned true before "
569 "calling me");
570 return ImgDrawResult::NOT_READY;
573 if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 ||
574 mSize.height <= 0) {
575 return ImgDrawResult::SUCCESS;
578 ImgDrawResult drawResult = ImgDrawResult::SUCCESS;
579 switch (mType) {
580 case StyleImage::Tag::Gradient: {
581 nsCSSGradientRenderer renderer = nsCSSGradientRenderer::Create(
582 aPresContext, mForFrame->Style(), *mGradientData, mSize);
584 renderer.BuildWebRenderDisplayItems(aBuilder, aSc, aDest, aFill,
585 aRepeatSize, aSrc,
586 !aItem->BackfaceIsHidden(), aOpacity);
587 break;
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()) {
631 break;
634 auto rendering =
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});
647 } else {
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();
662 break;
663 case ExtendMode::REPEAT_X:
664 fill.min.y = dest.min.y;
665 fill.max.y = dest.max.y;
666 stretchSize.height = dest.height();
667 break;
668 default:
669 break;
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});
680 break;
682 default:
683 break;
686 if (!mImage->IsComplete() && drawResult == ImgDrawResult::SUCCESS) {
687 return ImgDrawResult::SUCCESS_NOT_COMPLETE;
689 return drawResult;
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,
714 format)) {
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) {
735 if (!IsReady()) {
736 MOZ_ASSERT_UNREACHABLE(
737 "Ensure PrepareImage() has returned true before "
738 "calling me");
739 return ImgDrawResult::TEMPORARY_ERROR;
742 if (aDest.IsEmpty() || aFill.IsEmpty() || mSize.width <= 0 ||
743 mSize.height <= 0) {
744 return ImgDrawResult::SUCCESS;
747 return Draw(
748 aPresContext, aRenderingContext, aDirty, aDest, aFill, aAnchor,
749 aRepeatSize,
750 CSSIntRect(0, 0, nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
751 nsPresContext::AppUnitsToIntCSSPixels(mSize.height)),
752 aOpacity);
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) {
762 if (!IsReady()) {
763 MOZ_ASSERT_UNREACHABLE(
764 "Ensure PrepareImage() has returned true before "
765 "calling me");
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) {
791 nsRect tile;
792 switch (aHFill) {
793 case StyleBorderImageRepeat::Stretch:
794 tile.x = aFill.x;
795 tile.width = aFill.width;
796 aRepeatSize.width = tile.width;
797 break;
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;
802 break;
803 case StyleBorderImageRepeat::Round:
804 tile.x = aFill.x;
805 tile.width =
806 nsCSSRendering::ComputeRoundedSize(aUnitSize.width, aFill.width);
807 aRepeatSize.width = tile.width;
808 break;
809 case StyleBorderImageRepeat::Space: {
810 nscoord space;
811 aRepeatSize.width = nsCSSRendering::ComputeBorderSpacedRepeatSize(
812 aUnitSize.width, aFill.width, space);
813 tile.x = aFill.x + space;
814 tile.width = aUnitSize.width;
815 aFill.x = tile.x;
816 aFill.width = aFill.width - space * 2;
817 } break;
818 default:
819 MOZ_ASSERT_UNREACHABLE("unrecognized border-image fill style");
822 switch (aVFill) {
823 case StyleBorderImageRepeat::Stretch:
824 tile.y = aFill.y;
825 tile.height = aFill.height;
826 aRepeatSize.height = tile.height;
827 break;
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;
832 break;
833 case StyleBorderImageRepeat::Round:
834 tile.y = aFill.y;
835 tile.height =
836 nsCSSRendering::ComputeRoundedSize(aUnitSize.height, aFill.height);
837 aRepeatSize.height = tile.height;
838 break;
839 case StyleBorderImageRepeat::Space: {
840 nscoord space;
841 aRepeatSize.height = nsCSSRendering::ComputeBorderSpacedRepeatSize(
842 aUnitSize.height, aFill.height, space);
843 tile.y = aFill.y + space;
844 tile.height = aUnitSize.height;
845 aFill.y = tile.y;
846 aFill.height = aFill.height - space * 2;
847 } break;
848 default:
849 MOZ_ASSERT_UNREACHABLE("unrecognized border-image fill style");
852 return tile;
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
864 // scaling step.
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) {
876 if (!IsReady()) {
877 MOZ_ASSERT_UNREACHABLE(
878 "Ensure PrepareImage() has returned true before "
879 "calling me");
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);
911 } else {
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);
922 if (!drawable) {
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;
946 return result;
949 nsSize repeatSize;
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;
962 return result;
965 nsSize repeatSize(aFill.Size());
966 nsRect fillRect(aFill);
967 nsRect destTile =
968 RequiresScaling(fillRect, aHFill, aVFill, aUnitSize)
969 ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize)
970 : fillRect;
972 return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile, fillRect,
973 destTile.TopLeft(), repeatSize, aSrc);
976 ImgDrawResult nsImageRenderer::DrawShapeImage(nsPresContext* aPresContext,
977 gfxContext& aRenderingContext) {
978 if (!IsReady()) {
979 MOZ_ASSERT_UNREACHABLE(
980 "Ensure PrepareImage() has returned true before "
981 "calling me");
982 return ImgDrawResult::NOT_READY;
985 if (mSize.width <= 0 || mSize.height <= 0) {
986 return ImgDrawResult::SUCCESS;
989 if (mImage->IsImageRequestType()) {
990 uint32_t drawFlags =
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);