Bug 548372 - Part2. background-repeat round/space rendering. r=mstange

This commit is contained in:
Ethan Lin 2016-05-25 02:29:00 +02:00
Родитель a045541027
Коммит 6710f05dd7
5 изменённых файлов: 185 добавлений и 39 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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,