diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index 96613f9f5698..8074849bd238 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -2046,9 +2046,13 @@ nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext, } // We can skip painting the background color if a background image is opaque. + nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat; + bool xFullRepeat = repeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT || + repeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND; + bool yFullRepeat = repeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT || + repeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND; if (aDrawBackgroundColor && - bg->BottomLayer().mRepeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT && - bg->BottomLayer().mRepeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT && + xFullRepeat && yFullRepeat && bg->BottomLayer().mImage.IsOpaque() && bg->BottomLayer().mBlendMode == NS_STYLE_BLEND_NORMAL) { aDrawBackgroundColor = false; @@ -3102,7 +3106,8 @@ nsCSSRendering::PaintBackgroundWithSC(const PaintBGParams& aParams, aParams.renderingCtx, state.mDestArea, state.mFillArea, state.mAnchor + paintBorderArea.TopLeft(), - clipState.mDirtyRect); + clipState.mDirtyRect, + state.mRepeatSize); if (co != CompositionOp::OP_OVER) { ctx->SetOp(CompositionOp::OP_OVER); @@ -3225,6 +3230,19 @@ nsCSSRendering::ComputeImageLayerPositioningArea(nsPresContext* aPresContext, return bgPositioningArea; } +// Implementation of the formula for computation of background-repeat round +// See http://dev.w3.org/csswg/css3-background/#the-background-size +// This function returns the adjusted size of the background image. +static nscoord +ComputeRoundedSize(nscoord aCurrentSize, nscoord aPositioningSize) +{ + float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize)); + if (repeatCount < 1.0f) { + return aPositioningSize; + } + return nscoord(NS_lround(float(aPositioningSize) / repeatCount)); +} + // Apply the CSS image sizing algorithm as it applies to background images. // See http://www.w3.org/TR/css3-background/#the-background-size . // aIntrinsicSize is the size that the background image 'would like to be'. @@ -3232,8 +3250,11 @@ nsCSSRendering::ComputeImageLayerPositioningArea(nsPresContext* aPresContext, static nsSize ComputeDrawnSizeForBackground(const CSSSizeOrRatio& aIntrinsicSize, const nsSize& aBgPositioningArea, - const nsStyleImageLayers::Size& aLayerSize) + const nsStyleImageLayers::Size& aLayerSize, + uint8_t aXRepeat, uint8_t aYRepeat) { + nsSize imageSize; + // Size is dictated by cover or contain rules. if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eContain || aLayerSize.mWidthType == nsStyleImageLayers::Size::eCover) { @@ -3241,25 +3262,82 @@ ComputeDrawnSizeForBackground(const CSSSizeOrRatio& aIntrinsicSize, aLayerSize.mWidthType == nsStyleImageLayers::Size::eCover ? nsImageRenderer::COVER : nsImageRenderer::CONTAIN; - return nsImageRenderer::ComputeConstrainedSize(aBgPositioningArea, - aIntrinsicSize.mRatio, - fitType); + imageSize = nsImageRenderer::ComputeConstrainedSize(aBgPositioningArea, + aIntrinsicSize.mRatio, + fitType); + } else { + // No cover/contain constraint, use default algorithm. + CSSSizeOrRatio specifiedSize; + if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eLengthPercentage) { + specifiedSize.SetWidth( + aLayerSize.ResolveWidthLengthPercentage(aBgPositioningArea)); + } + if (aLayerSize.mHeightType == nsStyleImageLayers::Size::eLengthPercentage) { + specifiedSize.SetHeight( + aLayerSize.ResolveHeightLengthPercentage(aBgPositioningArea)); + } + + imageSize = nsImageRenderer::ComputeConcreteSize(specifiedSize, + aIntrinsicSize, + aBgPositioningArea); } - // No cover/contain constraint, use default algorithm. - CSSSizeOrRatio specifiedSize; - if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eLengthPercentage) { - specifiedSize.SetWidth( - aLayerSize.ResolveWidthLengthPercentage(aBgPositioningArea)); - } - if (aLayerSize.mHeightType == nsStyleImageLayers::Size::eLengthPercentage) { - specifiedSize.SetHeight( - aLayerSize.ResolveHeightLengthPercentage(aBgPositioningArea)); + // See https://www.w3.org/TR/css3-background/#background-size . + // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a second + // step. The UA must scale the image in that dimension (or both dimensions) so that + // it fits a whole number of times in the background positioning area." + // "If 'background-repeat' is 'round' for one dimension only and if 'background-size' + // is 'auto' for the other dimension, then there is a third step: that other dimension + // is scaled so that the original aspect ratio is restored." + bool isRepeatRoundInBothDimensions = aXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND && + aYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND; + + // Calculate the rounded size only if the background-size computation + // returned a correct size for the image. + if (imageSize.width && aXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND) { + imageSize.width = ComputeRoundedSize(imageSize.width, aBgPositioningArea.width); + if (!isRepeatRoundInBothDimensions && + aLayerSize.mHeightType == nsStyleImageLayers::Size::DimensionType::eAuto) { + // Restore intrinsic rato + if (aIntrinsicSize.mRatio.width) { + float scale = float(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width; + imageSize.height = NSCoordSaturatingNonnegativeMultiply(imageSize.width, scale); + } + } } - return nsImageRenderer::ComputeConcreteSize(specifiedSize, - aIntrinsicSize, - aBgPositioningArea); + // Calculate the rounded size only if the background-size computation + // returned a correct size for the image. + if (imageSize.height && aYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND) { + imageSize.height = ComputeRoundedSize(imageSize.height, aBgPositioningArea.height); + if (!isRepeatRoundInBothDimensions && + aLayerSize.mWidthType == nsStyleImageLayers::Size::DimensionType::eAuto) { + // Restore intrinsic rato + if (aIntrinsicSize.mRatio.height) { + float scale = float(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height; + imageSize.width = NSCoordSaturatingNonnegativeMultiply(imageSize.height, scale); + } + } + } + + return imageSize; +} + +/* ComputeSpacedRepeatSize + * aImageDimension: the image width/height + * aAvailableSpace: the background positioning area width/height + * aRepeatSize: the image size plus gap size of app units for use as spacing + */ +static nscoord +ComputeSpacedRepeatSize(nscoord aImageDimension, + nscoord aAvailableSpace) { + float ratio = aAvailableSpace / aImageDimension; + + if (ratio < 2.0f) { // If you can't repeat at least twice, then don't repeat. + return aImageDimension; + } else { + return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1); + } } nsBackgroundLayerState @@ -3384,14 +3462,20 @@ nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext, } } - // Scale the image as specified for background-size and as required for - // proper background positioning when background-position is defined with - // percentages. + int repeatX = aLayer.mRepeat.mXRepeat; + int repeatY = aLayer.mRepeat.mYRepeat; + + // Scale the image as specified for background-size and background-repeat. + // Also as required for proper background positioning when background-position + // is defined with percentages. CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize(); nsSize bgPositionSize = bgPositioningArea.Size(); nsSize imageSize = ComputeDrawnSizeForBackground(intrinsicSize, bgPositionSize, - aLayer.mSize); + aLayer.mSize, + repeatX, + repeatY); + if (imageSize.width <= 0 || imageSize.height <= 0) return state; @@ -3403,21 +3487,45 @@ nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext, nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition, bgPositionSize, imageSize, &imageTopLeft, &state.mAnchor); + state.mRepeatSize = imageSize; + if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_SPACE) { + state.mRepeatSize.width = ComputeSpacedRepeatSize(imageSize.width, + bgPositionSize.width); + if (state.mRepeatSize.width > imageSize.width) { + imageTopLeft.x = 0; + state.mAnchor.x = 0; + } else { + repeatX = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT; + } + } + + if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_SPACE) { + state.mRepeatSize.height = ComputeSpacedRepeatSize(imageSize.height, + bgPositionSize.height); + if (state.mRepeatSize.height > imageSize.height) { + imageTopLeft.y = 0; + state.mAnchor.y = 0; + } else { + repeatY = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT; + } + } + imageTopLeft += bgPositioningArea.TopLeft(); state.mAnchor += bgPositioningArea.TopLeft(); - state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize); state.mFillArea = state.mDestArea; - int repeatX = aLayer.mRepeat.mXRepeat; - int repeatY = aLayer.mRepeat.mYRepeat; ExtendMode repeatMode = ExtendMode::CLAMP; - if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_REPEAT) { + if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_REPEAT || + repeatX == NS_STYLE_IMAGELAYER_REPEAT_ROUND || + repeatX == NS_STYLE_IMAGELAYER_REPEAT_SPACE) { state.mFillArea.x = bgClipRect.x; state.mFillArea.width = bgClipRect.width; repeatMode = ExtendMode::REPEAT_X; } - if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_REPEAT) { + if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_REPEAT || + repeatY == NS_STYLE_IMAGELAYER_REPEAT_ROUND || + repeatY == NS_STYLE_IMAGELAYER_REPEAT_SPACE) { state.mFillArea.y = bgClipRect.y; state.mFillArea.height = bgClipRect.height; @@ -5122,6 +5230,7 @@ nsImageRenderer::Draw(nsPresContext* aPresContext, const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor, + const nsSize& aRepeatSize, const CSSIntRect& aSrc) { if (!IsReady()) { @@ -5158,7 +5267,8 @@ nsImageRenderer::Draw(nsPresContext* aPresContext, nsLayoutUtils::DrawBackgroundImage(*ctx, aPresContext, mImageContainer, imageSize, filter, - aDest, aFill, aAnchor, aDirtyRect, + aDest, aFill, aRepeatSize, + aAnchor, aDirtyRect, ConvertImageRendererToDrawFlags(mFlags), mExtendMode); break; @@ -5255,7 +5365,8 @@ nsImageRenderer::DrawBackground(nsPresContext* aPresContext, const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor, - const nsRect& aDirty) + const nsRect& aDirty, + const nsSize& aRepeatSize) { if (!IsReady()) { NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me"); @@ -5267,7 +5378,7 @@ nsImageRenderer::DrawBackground(nsPresContext* aPresContext, } return Draw(aPresContext, aRenderingContext, - aDirty, aDest, aFill, aAnchor, + aDirty, aDest, aFill, aAnchor, aRepeatSize, CSSIntRect(0, 0, nsPresContext::AppUnitsToIntCSSPixels(mSize.width), nsPresContext::AppUnitsToIntCSSPixels(mSize.height))); @@ -5431,7 +5542,7 @@ nsImageRenderer::DrawBorderImageComponent(nsPresContext* aPresContext, : aFill; return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile, - aFill, destTile.TopLeft(), aSrc); + aFill, destTile.TopLeft(), nsSize(0, 0), aSrc); } bool diff --git a/layout/base/nsCSSRendering.h b/layout/base/nsCSSRendering.h index bfe062eec725..e9d0504438f6 100644 --- a/layout/base/nsCSSRendering.h +++ b/layout/base/nsCSSRendering.h @@ -217,7 +217,8 @@ public: const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor, - const nsRect& aDirty); + const nsRect& aDirty, + const nsSize& aRepeatSize); /** * Draw the image to a single component of a border-image style rendering. @@ -273,10 +274,11 @@ private: const nsRect& aDest, const nsRect& aFill, const nsPoint& aAnchor, + const nsSize& aRepeatSize, const mozilla::CSSIntRect& aSrc); /** - * Helper method for creating a gfxDrawable from mPaintServerFrame or + * Helper method for creating a gfxDrawable from mPaintServerFrame or * mImageElementSurface. * Requires mType is eStyleImageType_Element. * Returns null if we cannot create the drawable. @@ -336,6 +338,11 @@ struct nsBackgroundLayerState { * PrepareImageLayer. */ nsPoint mAnchor; + /** + * The background-repeat property space keyword computes the + * repeat size which is image size plus spacing. + */ + nsSize mRepeatSize; }; struct nsCSSRendering { diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index ebb09563de76..0414307e2807 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -2912,6 +2912,8 @@ nsDisplayBackgroundImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) { const nsStyleImageLayers::Layer& layer = mBackgroundStyle->mImage.mLayers[mLayer]; if (layer.mImage.IsOpaque() && layer.mBlendMode == NS_STYLE_BLEND_NORMAL && + layer.mRepeat.mXRepeat != NS_STYLE_IMAGELAYER_REPEAT_SPACE && + layer.mRepeat.mYRepeat != NS_STYLE_IMAGELAYER_REPEAT_SPACE && layer.mClip != NS_STYLE_IMAGELAYER_CLIP_TEXT) { result = GetInsideClipRegion(this, layer.mClip, mBounds, mBackgroundRect); } diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 42a3d756029a..702c23e500a9 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -6678,7 +6678,6 @@ ComputeSnappedImageDrawingParameters(gfxContext* aCtx, region, svgViewportSize); } - static DrawResult DrawImageInternal(gfxContext& aContext, nsPresContext* aPresContext, @@ -6920,6 +6919,7 @@ nsLayoutUtils::DrawBackgroundImage(gfxContext& aContext, Filter aGraphicsFilter, const nsRect& aDest, const nsRect& aFill, + const nsSize& aRepeatSize, const nsPoint& aAnchor, const nsRect& aDirty, uint32_t aImageFlags, @@ -6934,9 +6934,29 @@ nsLayoutUtils::DrawBackgroundImage(gfxContext& aContext, SVGImageContext svgContext(aImageSize, Nothing()); - return DrawImageInternal(aContext, aPresContext, aImage, - aGraphicsFilter, aDest, aFill, aAnchor, - aDirty, &svgContext, aImageFlags, aExtendMode); + /* Fast path when there is no need for image spacing */ + if (aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height) { + return DrawImageInternal(aContext, aPresContext, aImage, + aGraphicsFilter, aDest, aFill, aAnchor, + aDirty, &svgContext, aImageFlags, aExtendMode); + } + + nsPoint firstTilePos = aDest.TopLeft() + + nsPoint(NSToIntFloor(float(aFill.x - aDest.x) / aRepeatSize.width) * aRepeatSize.width, + NSToIntFloor(float(aFill.y - aDest.y) / aRepeatSize.height) * aRepeatSize.height); + for (int32_t i = firstTilePos.x; i < aFill.XMost(); i += aRepeatSize.width) { + for (int32_t j = firstTilePos.y; j < aFill.YMost(); j += aRepeatSize.height) { + nsRect dest(i, j, aDest.width, aDest.height); + DrawResult result = DrawImageInternal(aContext, aPresContext, aImage, aGraphicsFilter, + dest, dest, aAnchor, aDirty, &svgContext, + aImageFlags, ExtendMode::CLAMP); + if (result != DrawResult::SUCCESS) { + return result; + } + } + } + + return DrawResult::SUCCESS; } /* static */ DrawResult diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 3db939d72508..45f003976d06 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -1735,8 +1735,13 @@ public: * the image is a vector image being rendered at * that size.) * @param aDest The position and scaled area where one copy of - * the image should be drawn. + * the image should be drawn. This area represents + * the image itself in its correct position as defined + * with the background-position css property. * @param aFill The area to be filled with copies of the image. + * @param aRepeatSize The distance between the positions of two subsequent + * repeats of the image. Sizes larger than aDest.Size() + * create gaps between the images. * @param aAnchor A point in aFill which we will ensure is * pixel-aligned in the output. * @param aDirty Pixels outside this area may be skipped. @@ -1750,6 +1755,7 @@ public: Filter aGraphicsFilter, const nsRect& aDest, const nsRect& aFill, + const nsSize& aRepeatSize, const nsPoint& aAnchor, const nsRect& aDirty, uint32_t aImageFlags,