From da7f1abe0ba98769541491ccf549e553b9cd1669 Mon Sep 17 00:00:00 2001 From: Antti Koivisto Date: Fri, 9 Sep 2022 08:56:47 -0700 Subject: [PATCH] Move more painting code to BackgroundPainter https://bugs.webkit.org/show_bug.cgi?id=244927 . Reviewed by Alan Bujtas. Move more background painting code out from RenderObjects to a dedicated painter class. * Source/WebCore/rendering/BackgroundPainter.cpp: (WebCore::BackgroundImageGeometry::BackgroundImageGeometry): (WebCore::BackgroundPainter::paintFillLayers): (WebCore::BackgroundPainter::paintFillLayer): (WebCore::BackgroundPainter::backgroundRoundedRectAdjustedForBleedAvoidance const): (WebCore::BackgroundPainter::backgroundRoundedRect const): (WebCore::getSpace): (WebCore::resolveEdgeRelativeLength): (WebCore::pixelSnapBackgroundImageGeometryForPainting): (WebCore::BackgroundPainter::calculateBackgroundImageGeometry): (WebCore::BackgroundPainter::calculateFillTileSize): (WebCore::BackgroundPainter::getBackgroundRoundedRect const): Deleted. * Source/WebCore/rendering/BackgroundPainter.h: (WebCore::BackgroundImageGeometry::relativePhase const): (WebCore::BackgroundImageGeometry::clip): Make BackgroundImageGeometry a struct and add tileSizeWithoutPixelSnapping so calculateBackgroundImageGeometry can return it and does not need to deal with updating SVG container context itself. * Source/WebCore/rendering/InlineBoxPainter.cpp: (WebCore::InlineBoxPainter::paintFillLayers): * Source/WebCore/rendering/RenderBox.cpp: (WebCore::RenderBox::paintRootBoxFillLayers): (WebCore::RenderBox::paintBackground): (WebCore::RenderBox::getBackgroundPaintedExtent const): (WebCore::RenderBox::paintMaskImages): (WebCore::RenderBox::maskClipRect): (WebCore::RenderBox::repaintLayerRectsForImage): (WebCore::RenderBox::paintFillLayers): Deleted. * Source/WebCore/rendering/RenderBox.h: * Source/WebCore/rendering/RenderBoxModelObject.cpp: (WebCore::RenderBoxModelObject::calculateFillTileSize const): Deleted. (WebCore::pixelSnapBackgroundImageGeometryForPainting): Deleted. (WebCore::getSpace): Deleted. (WebCore::resolveEdgeRelativeLength): Deleted. (WebCore::RenderBoxModelObject::calculateBackgroundImageGeometry const): Deleted. (WebCore::RenderBoxModelObject::getGeometryForBackgroundImage const): Deleted. * Source/WebCore/rendering/RenderBoxModelObject.h: (WebCore::BackgroundImageGeometry::BackgroundImageGeometry): Deleted. (WebCore::BackgroundImageGeometry::destRect const): Deleted. (WebCore::BackgroundImageGeometry::phase const): Deleted. (WebCore::BackgroundImageGeometry::tileSize const): Deleted. (WebCore::BackgroundImageGeometry::spaceSize const): Deleted. (WebCore::BackgroundImageGeometry::hasNonLocalGeometry const): Deleted. (WebCore::BackgroundImageGeometry::relativePhase const): Deleted. (WebCore::BackgroundImageGeometry::clip): Deleted. * Source/WebCore/rendering/RenderLayerBacking.cpp: (WebCore::RenderLayerBacking::updateDirectlyCompositedBackgroundImage): * Source/WebCore/rendering/RenderTableCell.cpp: (WebCore::RenderTableCell::paintBackgroundsBehindCell): Canonical link: https://commits.webkit.org/254301@main --- .../WebCore/rendering/BackgroundPainter.cpp | 362 +++++++++++++++++- Source/WebCore/rendering/BackgroundPainter.h | 27 +- Source/WebCore/rendering/InlineBoxPainter.cpp | 4 +- Source/WebCore/rendering/RenderBox.cpp | 65 +--- Source/WebCore/rendering/RenderBox.h | 3 - .../rendering/RenderBoxModelObject.cpp | 298 -------------- .../WebCore/rendering/RenderBoxModelObject.h | 46 +-- .../WebCore/rendering/RenderLayerBacking.cpp | 15 +- Source/WebCore/rendering/RenderTableCell.cpp | 3 +- 9 files changed, 403 insertions(+), 420 deletions(-) diff --git a/Source/WebCore/rendering/BackgroundPainter.cpp b/Source/WebCore/rendering/BackgroundPainter.cpp index 22c1c13539bf..6cd29ad7ee6b 100644 --- a/Source/WebCore/rendering/BackgroundPainter.cpp +++ b/Source/WebCore/rendering/BackgroundPainter.cpp @@ -43,11 +43,63 @@ namespace WebCore { +BackgroundImageGeometry::BackgroundImageGeometry(const LayoutRect& destinationRect, const LayoutSize& tileSizeWithoutPixelSnapping, const LayoutSize& tileSize, const LayoutSize& phase, const LayoutSize& spaceSize, bool fixedAttachment) + : destinationRect(destinationRect) + , destinationOrigin(destinationRect.location()) + , tileSizeWithoutPixelSnapping(tileSizeWithoutPixelSnapping) + , tileSize(tileSize) + , phase(phase) + , spaceSize(spaceSize) + , hasNonLocalGeometry(fixedAttachment) +{ +} + BackgroundPainter::BackgroundPainter(RenderBoxModelObject& renderer, const PaintInfo& paintInfo) : m_renderer(renderer) , m_paintInfo(paintInfo) { } + +void BackgroundPainter::paintFillLayers(const Color& color, const FillLayer& fillLayer, const LayoutRect& rect, BackgroundBleedAvoidance bleedAvoidance, CompositeOperator op, RenderElement* backgroundObject) +{ + Vector layers; + bool shouldDrawBackgroundInSeparateBuffer = false; + + for (auto* layer = &fillLayer; layer; layer = layer->next()) { + layers.append(layer); + + if (layer->blendMode() != BlendMode::Normal) + shouldDrawBackgroundInSeparateBuffer = true; + + // Stop traversal when an opaque layer is encountered. + // FIXME: It would be possible for the following occlusion culling test to be more aggressive + // on layers with no repeat by testing whether the image covers the layout rect. + // Testing that here would imply duplicating a lot of calculations that are currently done in + // BackgroundPainter::paintFillLayer. A more efficient solution might be to move + // the layer recursion into paintFillLayer, or to compute the layer geometry here + // and pass it down. + + // The clipOccludesNextLayers condition must be evaluated first to avoid short-circuiting. + if (layer->clipOccludesNextLayers(layer == &fillLayer) && layer->hasOpaqueImage(m_renderer) && layer->image()->canRender(&m_renderer, m_renderer.style().effectiveZoom()) && layer->hasRepeatXY() && layer->blendMode() == BlendMode::Normal) + break; + } + + auto& context = m_paintInfo.context(); + auto baseBgColorUsage = BaseBackgroundColorUse; + + if (shouldDrawBackgroundInSeparateBuffer) { + paintFillLayer(color, *layers.last(), rect, bleedAvoidance, { }, { }, op, backgroundObject, BaseBackgroundColorOnly); + baseBgColorUsage = BaseBackgroundColorSkip; + context.beginTransparencyLayer(1); + } + + for (auto& layer : makeReversedRange(layers)) + paintFillLayer(color, *layer, rect, bleedAvoidance, { }, { }, op, backgroundObject, baseBgColorUsage); + + if (shouldDrawBackgroundInSeparateBuffer) + context.endTransparencyLayer(); +} + static void applyBoxShadowForBackground(GraphicsContext& context, const RenderStyle& style) { const ShadowData* boxShadow = style.boxShadow(); @@ -164,7 +216,7 @@ void BackgroundPainter::paintFillLayer(const Color& color, const FillLayer& bgLa bool clipToBorderRadius = hasRoundedBorder && !(isBorderFill && bleedAvoidance == BackgroundBleedUseTransparencyLayer); GraphicsContextStateSaver clipToBorderStateSaver(context, clipToBorderRadius); if (clipToBorderRadius) { - RoundedRect border = isBorderFill ? backgroundRoundedRectAdjustedForBleedAvoidance(rect, bleedAvoidance, box, includeLeftEdge, includeRightEdge) : getBackgroundRoundedRect(rect, box, includeLeftEdge, includeRightEdge); + RoundedRect border = isBorderFill ? backgroundRoundedRectAdjustedForBleedAvoidance(rect, bleedAvoidance, box, includeLeftEdge, includeRightEdge) : backgroundRoundedRect(rect, box, includeLeftEdge, includeRightEdge); // Clip to the padding or content boxes as necessary. if (bgLayer.clip() == FillBox::Content) { @@ -284,10 +336,14 @@ void BackgroundPainter::paintFillLayer(const Color& color, const FillLayer& bgLa // Multiline inline boxes paint like the image was one long strip spanning lines. The backgroundImageStrip is this fictional rectangle. auto imageRect = backgroundImageStrip.isEmpty() ? scrolledPaintRect : backgroundImageStrip; auto paintOffset = backgroundImageStrip.isEmpty() ? rect.location() : backgroundImageStrip.location(); - auto geometry = m_renderer.calculateBackgroundImageGeometry(m_paintInfo.paintContainer, bgLayer, paintOffset, imageRect, backgroundObject); + auto geometry = calculateBackgroundImageGeometry(m_renderer, m_paintInfo.paintContainer, bgLayer, paintOffset, imageRect); + + auto& clientForBackgroundImage = backgroundObject ? *backgroundObject : m_renderer; + bgImage->setContainerContextForRenderer(clientForBackgroundImage, geometry.tileSizeWithoutPixelSnapping, m_renderer.style().effectiveZoom()); + geometry.clip(LayoutRect(pixelSnappedRect)); RefPtr image; - if (!geometry.destRect().isEmpty() && (image = bgImage->image(backgroundObject ? backgroundObject : &m_renderer, geometry.tileSize()))) { + if (!geometry.destinationRect.isEmpty() && (image = bgImage->image(backgroundObject ? backgroundObject : &m_renderer, geometry.tileSize))) { context.setDrawLuminanceMask(bgLayer.maskMode() == MaskMode::Luminance); if (is(image)) @@ -298,10 +354,10 @@ void BackgroundPainter::paintFillLayer(const Color& color, const FillLayer& bgLa bgLayer.blendMode(), m_renderer.decodingModeForImageDraw(*image, m_paintInfo), ImageOrientation::FromImage, - m_renderer.chooseInterpolationQuality(context, *image, &bgLayer, geometry.tileSize()) + m_renderer.chooseInterpolationQuality(context, *image, &bgLayer, geometry.tileSize) }; - auto drawResult = context.drawTiledImage(*image, geometry.destRect(), toLayoutPoint(geometry.relativePhase()), geometry.tileSize(), geometry.spaceSize(), options); + auto drawResult = context.drawTiledImage(*image, geometry.destinationRect, toLayoutPoint(geometry.relativePhase()), geometry.tileSize, geometry.spaceSize, options); if (drawResult == ImageDrawResult::DidRequestDecoding) { ASSERT(bgImage->hasCachedImage()); bgImage->cachedImage()->addClientWaitingForAsyncDecoding(m_renderer); @@ -352,16 +408,16 @@ RoundedRect BackgroundPainter::backgroundRoundedRectAdjustedForBleedAvoidance(co { if (bleedAvoidance == BackgroundBleedShrinkBackground) { // We shrink the rectangle by one device pixel on each side because the bleed is one pixel maximum. - return getBackgroundRoundedRect(shrinkRectByOneDevicePixel(m_paintInfo.context(), borderRect, document().deviceScaleFactor()), box, + return backgroundRoundedRect(shrinkRectByOneDevicePixel(m_paintInfo.context(), borderRect, document().deviceScaleFactor()), box, includeLogicalLeftEdge, includeLogicalRightEdge); } if (bleedAvoidance == BackgroundBleedBackgroundOverBorder) return m_renderer.style().getRoundedInnerBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge); - return getBackgroundRoundedRect(borderRect, box, includeLogicalLeftEdge, includeLogicalRightEdge); + return backgroundRoundedRect(borderRect, box, includeLogicalLeftEdge, includeLogicalRightEdge); } -RoundedRect BackgroundPainter::getBackgroundRoundedRect(const LayoutRect& borderRect, const InlineIterator::InlineBoxIterator& box, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const +RoundedRect BackgroundPainter::backgroundRoundedRect(const LayoutRect& borderRect, const InlineIterator::InlineBoxIterator& box, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const { RoundedRect border = m_renderer.style().getRoundedBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge); if (box && (box->nextInlineBox() || box->previousInlineBox())) { @@ -371,6 +427,296 @@ RoundedRect BackgroundPainter::getBackgroundRoundedRect(const LayoutRect& border return border; } +static inline std::optional getSpace(LayoutUnit areaSize, LayoutUnit tileSize) +{ + if (int numberOfTiles = areaSize / tileSize; numberOfTiles > 1) + return (areaSize - numberOfTiles * tileSize) / (numberOfTiles - 1); + return std::nullopt; +} + +static LayoutUnit resolveEdgeRelativeLength(const Length& length, Edge edge, LayoutUnit availableSpace, const LayoutSize& areaSize, const LayoutSize& tileSize) +{ + LayoutUnit result = minimumValueForLength(length, availableSpace); + + if (edge == Edge::Right) + return areaSize.width() - tileSize.width() - result; + + if (edge == Edge::Bottom) + return areaSize.height() - tileSize.height() - result; + + return result; +} + +static void pixelSnapBackgroundImageGeometryForPainting(LayoutRect& destinationRect, LayoutSize& tileSize, LayoutSize& phase, LayoutSize& space, float scaleFactor) +{ + tileSize = LayoutSize(snapRectToDevicePixels(LayoutRect(destinationRect.location(), tileSize), scaleFactor).size()); + phase = LayoutSize(snapRectToDevicePixels(LayoutRect(destinationRect.location(), phase), scaleFactor).size()); + space = LayoutSize(snapRectToDevicePixels(LayoutRect(LayoutPoint(), space), scaleFactor).size()); + destinationRect = LayoutRect(snapRectToDevicePixels(destinationRect, scaleFactor)); +} + +BackgroundImageGeometry BackgroundPainter::calculateBackgroundImageGeometry(const RenderBoxModelObject& renderer, const RenderLayerModelObject* paintContainer, const FillLayer& fillLayer, const LayoutPoint& paintOffset, const LayoutRect& borderBoxRect) +{ + auto& view = renderer.view(); + + LayoutUnit left; + LayoutUnit top; + LayoutSize positioningAreaSize; + // Determine the background positioning area and set destination rect to the background painting area. + // Destination rect will be adjusted later if the background is non-repeating. + // FIXME: transforms spec says that fixed backgrounds behave like scroll inside transforms. https://bugs.webkit.org/show_bug.cgi?id=15679 + LayoutRect destinationRect(borderBoxRect); + bool fixedAttachment = fillLayer.attachment() == FillAttachment::FixedBackground; + float deviceScaleFactor = renderer.document().deviceScaleFactor(); + if (!fixedAttachment) { + LayoutUnit right; + LayoutUnit bottom; + // Scroll and Local. + if (fillLayer.origin() != FillBox::Border) { + left = renderer.borderLeft(); + right = renderer.borderRight(); + top = renderer.borderTop(); + bottom = renderer.borderBottom(); + if (fillLayer.origin() == FillBox::Content) { + left += renderer.paddingLeft(); + right += renderer.paddingRight(); + top += renderer.paddingTop(); + bottom += renderer.paddingBottom(); + } + } + + // The background of the box generated by the root element covers the entire canvas including + // its margins. Since those were added in already, we have to factor them out when computing + // the background positioning area. + if (renderer.isDocumentElementRenderer()) { + positioningAreaSize = downcast(renderer).size() - LayoutSize(left + right, top + bottom); + positioningAreaSize = LayoutSize(snapSizeToDevicePixel(positioningAreaSize, LayoutPoint(), deviceScaleFactor)); + if (view.frameView().hasExtendedBackgroundRectForPainting()) { + LayoutRect extendedBackgroundRect = view.frameView().extendedBackgroundRectForPainting(); + left += (renderer.marginLeft() - extendedBackgroundRect.x()); + top += (renderer.marginTop() - extendedBackgroundRect.y()); + } + } else { + positioningAreaSize = borderBoxRect.size() - LayoutSize(left + right, top + bottom); + positioningAreaSize = LayoutSize(snapRectToDevicePixels(LayoutRect(paintOffset, positioningAreaSize), deviceScaleFactor).size()); + } + } else { + LayoutRect viewportRect; + float topContentInset = 0; + if (renderer.settings().fixedBackgroundsPaintRelativeToDocument()) + viewportRect = view.unscaledDocumentRect(); + else { + FrameView& frameView = view.frameView(); + bool useFixedLayout = frameView.useFixedLayout() && !frameView.fixedLayoutSize().isEmpty(); + + if (useFixedLayout) { + // Use the fixedLayoutSize() when useFixedLayout() because the rendering will scale + // down the frameView to to fit in the current viewport. + viewportRect.setSize(frameView.fixedLayoutSize()); + } else + viewportRect.setSize(frameView.sizeForVisibleContent()); + + if (renderer.fixedBackgroundPaintsInLocalCoordinates()) { + if (!useFixedLayout) { + // Shifting location up by topContentInset is needed for layout tests which expect + // layout to be shifted down when calling window.internals.setTopContentInset(). + topContentInset = frameView.topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset); + viewportRect.setLocation(LayoutPoint(0, -topContentInset)); + } + } else if (useFixedLayout || frameView.frameScaleFactor() != 1) { + // scrollPositionForFixedPosition() is adjusted for page scale and it does not include + // topContentInset so do not add it to the calculation below. + viewportRect.setLocation(frameView.scrollPositionForFixedPosition()); + } else { + // documentScrollPositionRelativeToViewOrigin() includes -topContentInset in its height + // so we need to account for that in calculating the phase size + topContentInset = frameView.topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset); + viewportRect.setLocation(frameView.documentScrollPositionRelativeToViewOrigin()); + } + + top += topContentInset; + } + + if (paintContainer) + viewportRect.moveBy(LayoutPoint(-paintContainer->localToAbsolute(FloatPoint()))); + + destinationRect = viewportRect; + positioningAreaSize = destinationRect.size(); + positioningAreaSize.setHeight(positioningAreaSize.height() - topContentInset); + positioningAreaSize = LayoutSize(snapRectToDevicePixels(LayoutRect(destinationRect.location(), positioningAreaSize), deviceScaleFactor).size()); + } + + LayoutSize tileSize = calculateFillTileSize(renderer, fillLayer, positioningAreaSize); + + FillRepeat backgroundRepeatX = fillLayer.repeat().x; + FillRepeat backgroundRepeatY = fillLayer.repeat().y; + LayoutUnit availableWidth = positioningAreaSize.width() - tileSize.width(); + LayoutUnit availableHeight = positioningAreaSize.height() - tileSize.height(); + + LayoutSize spaceSize; + LayoutSize phase; + LayoutSize noRepeat; + LayoutUnit computedXPosition = resolveEdgeRelativeLength(fillLayer.xPosition(), fillLayer.backgroundXOrigin(), availableWidth, positioningAreaSize, tileSize); + if (backgroundRepeatX == FillRepeat::Round && positioningAreaSize.width() > 0 && tileSize.width() > 0) { + int numTiles = std::max(1, roundToInt(positioningAreaSize.width() / tileSize.width())); + if (fillLayer.size().size.height.isAuto() && backgroundRepeatY != FillRepeat::Round) + tileSize.setHeight(tileSize.height() * positioningAreaSize.width() / (numTiles * tileSize.width())); + + tileSize.setWidth(positioningAreaSize.width() / numTiles); + phase.setWidth(tileSize.width() ? tileSize.width() - fmodf((computedXPosition + left), tileSize.width()) : 0); + } + + LayoutUnit computedYPosition = resolveEdgeRelativeLength(fillLayer.yPosition(), fillLayer.backgroundYOrigin(), availableHeight, positioningAreaSize, tileSize); + if (backgroundRepeatY == FillRepeat::Round && positioningAreaSize.height() > 0 && tileSize.height() > 0) { + int numTiles = std::max(1, roundToInt(positioningAreaSize.height() / tileSize.height())); + if (fillLayer.size().size.width.isAuto() && backgroundRepeatX != FillRepeat::Round) + tileSize.setWidth(tileSize.width() * positioningAreaSize.height() / (numTiles * tileSize.height())); + + tileSize.setHeight(positioningAreaSize.height() / numTiles); + phase.setHeight(tileSize.height() ? tileSize.height() - fmodf((computedYPosition + top), tileSize.height()) : 0); + } + + if (backgroundRepeatX == FillRepeat::Repeat) { + phase.setWidth(tileSize.width() ? tileSize.width() - fmodf(computedXPosition + left, tileSize.width()) : 0); + spaceSize.setWidth(0); + } else if (backgroundRepeatX == FillRepeat::Space && tileSize.width() > 0) { + if (auto space = getSpace(positioningAreaSize.width(), tileSize.width())) { + LayoutUnit actualWidth = tileSize.width() + *space; + computedXPosition = minimumValueForLength(Length(), availableWidth); + spaceSize.setWidth(*space); + spaceSize.setHeight(0); + phase.setWidth(actualWidth ? actualWidth - fmodf((computedXPosition + left), actualWidth) : 0); + } else + backgroundRepeatX = FillRepeat::NoRepeat; + } + + if (backgroundRepeatX == FillRepeat::NoRepeat) { + LayoutUnit xOffset = left + computedXPosition; + if (xOffset > 0) + destinationRect.move(xOffset, 0_lu); + xOffset = std::min(xOffset, 0); + phase.setWidth(-xOffset); + destinationRect.setWidth(tileSize.width() + xOffset); + spaceSize.setWidth(0); + } + + if (backgroundRepeatY == FillRepeat::Repeat) { + phase.setHeight(tileSize.height() ? tileSize.height() - fmodf(computedYPosition + top, tileSize.height()) : 0); + spaceSize.setHeight(0); + } else if (backgroundRepeatY == FillRepeat::Space && tileSize.height() > 0) { + if (auto space = getSpace(positioningAreaSize.height(), tileSize.height())) { + LayoutUnit actualHeight = tileSize.height() + *space; + computedYPosition = minimumValueForLength(Length(), availableHeight); + spaceSize.setHeight(*space); + phase.setHeight(actualHeight ? actualHeight - fmodf((computedYPosition + top), actualHeight) : 0); + } else + backgroundRepeatY = FillRepeat::NoRepeat; + } + if (backgroundRepeatY == FillRepeat::NoRepeat) { + LayoutUnit yOffset = top + computedYPosition; + if (yOffset > 0) + destinationRect.move(0_lu, yOffset); + yOffset = std::min(yOffset, 0); + phase.setHeight(-yOffset); + destinationRect.setHeight(tileSize.height() + yOffset); + spaceSize.setHeight(0); + } + + if (fixedAttachment) { + LayoutPoint attachmentPoint = borderBoxRect.location(); + phase.expand(std::max(attachmentPoint.x() - destinationRect.x(), 0), std::max(attachmentPoint.y() - destinationRect.y(), 0)); + } + + destinationRect.intersect(borderBoxRect); + + auto tileSizeWithoutPixelSnapping = tileSize; + pixelSnapBackgroundImageGeometryForPainting(destinationRect, tileSize, phase, spaceSize, deviceScaleFactor); + + return BackgroundImageGeometry(destinationRect, tileSizeWithoutPixelSnapping, tileSize, phase, spaceSize, fixedAttachment); +} + +LayoutSize BackgroundPainter::calculateFillTileSize(const RenderBoxModelObject& renderer, const FillLayer& fillLayer, const LayoutSize& positioningAreaSize) +{ + StyleImage* image = fillLayer.image(); + FillSizeType type = fillLayer.size().type; + auto devicePixelSize = LayoutUnit { 1.0 / renderer.document().deviceScaleFactor() }; + + LayoutSize imageIntrinsicSize; + if (image) { + imageIntrinsicSize = renderer.calculateImageIntrinsicDimensions(image, positioningAreaSize, RenderBoxModelObject::ScaleByEffectiveZoom); + imageIntrinsicSize.scale(1 / image->imageScaleFactor(), 1 / image->imageScaleFactor()); + } else + imageIntrinsicSize = positioningAreaSize; + + switch (type) { + case FillSizeType::Size: { + LayoutSize tileSize = positioningAreaSize; + + Length layerWidth = fillLayer.size().size.width; + Length layerHeight = fillLayer.size().size.height; + + if (layerWidth.isFixed()) + tileSize.setWidth(layerWidth.value()); + else if (layerWidth.isPercentOrCalculated()) { + auto resolvedWidth = valueForLength(layerWidth, positioningAreaSize.width()); + // Non-zero resolved value should always produce some content. + tileSize.setWidth(!resolvedWidth ? resolvedWidth : std::max(devicePixelSize, resolvedWidth)); + } + + if (layerHeight.isFixed()) + tileSize.setHeight(layerHeight.value()); + else if (layerHeight.isPercentOrCalculated()) { + auto resolvedHeight = valueForLength(layerHeight, positioningAreaSize.height()); + // Non-zero resolved value should always produce some content. + tileSize.setHeight(!resolvedHeight ? resolvedHeight : std::max(devicePixelSize, resolvedHeight)); + } + + // If one of the values is auto we have to use the appropriate + // scale to maintain our aspect ratio. + if (layerWidth.isAuto() && !layerHeight.isAuto()) { + if (imageIntrinsicSize.height()) + tileSize.setWidth(imageIntrinsicSize.width() * tileSize.height() / imageIntrinsicSize.height()); + } else if (!layerWidth.isAuto() && layerHeight.isAuto()) { + if (imageIntrinsicSize.width()) + tileSize.setHeight(imageIntrinsicSize.height() * tileSize.width() / imageIntrinsicSize.width()); + } else if (layerWidth.isAuto() && layerHeight.isAuto()) { + // If both width and height are auto, use the image's intrinsic size. + tileSize = imageIntrinsicSize; + } + + tileSize.clampNegativeToZero(); + return tileSize; + } + case FillSizeType::None: { + // If both values are ‘auto’ then the intrinsic width and/or height of the image should be used, if any. + if (!imageIntrinsicSize.isEmpty()) + return imageIntrinsicSize; + + // If the image has neither an intrinsic width nor an intrinsic height, its size is determined as for ‘contain’. + type = FillSizeType::Contain; + } + FALLTHROUGH; + case FillSizeType::Contain: + case FillSizeType::Cover: { + // Scale computation needs higher precision than what LayoutUnit can offer. + FloatSize localImageIntrinsicSize = imageIntrinsicSize; + FloatSize localPositioningAreaSize = positioningAreaSize; + + float horizontalScaleFactor = localImageIntrinsicSize.width() ? (localPositioningAreaSize.width() / localImageIntrinsicSize.width()) : 1; + float verticalScaleFactor = localImageIntrinsicSize.height() ? (localPositioningAreaSize.height() / localImageIntrinsicSize.height()) : 1; + float scaleFactor = type == FillSizeType::Contain ? std::min(horizontalScaleFactor, verticalScaleFactor) : std::max(horizontalScaleFactor, verticalScaleFactor); + + if (localImageIntrinsicSize.isEmpty()) + return { }; + + return LayoutSize(localImageIntrinsicSize.scaled(scaleFactor).expandedTo({ devicePixelSize, devicePixelSize })); + } + } + + ASSERT_NOT_REACHED(); + return { }; +} + const Document& BackgroundPainter::document() const { return m_renderer.document(); diff --git a/Source/WebCore/rendering/BackgroundPainter.h b/Source/WebCore/rendering/BackgroundPainter.h index cb3efac3eb1e..28e578c4ec54 100644 --- a/Source/WebCore/rendering/BackgroundPainter.h +++ b/Source/WebCore/rendering/BackgroundPainter.h @@ -27,17 +27,42 @@ namespace WebCore { +struct BackgroundImageGeometry { + BackgroundImageGeometry(const LayoutRect& destinationRect, const LayoutSize& tileSizeWithoutPixelSnapping, const LayoutSize& tileSize, const LayoutSize& phase, const LayoutSize& spaceSize, bool fixedAttachment); + + LayoutSize relativePhase() const + { + LayoutSize relativePhase = phase; + relativePhase += destinationRect.location() - destinationOrigin; + return relativePhase; + } + + void clip(const LayoutRect& clipRect) { destinationRect.intersect(clipRect); } + + LayoutRect destinationRect; + LayoutPoint destinationOrigin; + LayoutSize tileSizeWithoutPixelSnapping; + LayoutSize tileSize; + LayoutSize phase; + LayoutSize spaceSize; + bool hasNonLocalGeometry; // Has background-attachment: fixed. Implies that we can't always cheaply compute destRect. +}; + class BackgroundPainter { public: BackgroundPainter(RenderBoxModelObject&, const PaintInfo&); + void paintFillLayers(const Color&, const FillLayer&, const LayoutRect&, BackgroundBleedAvoidance, CompositeOperator, RenderElement* backgroundObject = nullptr); void paintFillLayer(const Color&, const FillLayer&, const LayoutRect&, BackgroundBleedAvoidance, const InlineIterator::InlineBoxIterator&, const LayoutRect& backgroundImageStrip = { }, CompositeOperator = CompositeOperator::SourceOver, RenderElement* backgroundObject = nullptr, BaseBackgroundColorUsage = BaseBackgroundColorUse); + static BackgroundImageGeometry calculateBackgroundImageGeometry(const RenderBoxModelObject&, const RenderLayerModelObject* paintContainer, const FillLayer&, const LayoutPoint& paintOffset, const LayoutRect& borderBoxRect); static void clipRoundedInnerRect(GraphicsContext&, const FloatRect&, const FloatRoundedRect& clipRect); private: RoundedRect backgroundRoundedRectAdjustedForBleedAvoidance(const LayoutRect& borderRect, BackgroundBleedAvoidance, const InlineIterator::InlineBoxIterator&, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const; - RoundedRect getBackgroundRoundedRect(const LayoutRect& borderRect, const InlineIterator::InlineBoxIterator&, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const; + RoundedRect backgroundRoundedRect(const LayoutRect& borderRect, const InlineIterator::InlineBoxIterator&, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const; + + static LayoutSize calculateFillTileSize(const RenderBoxModelObject&, const FillLayer&, const LayoutSize& positioningAreaSize); const Document& document() const; const RenderView& view() const; diff --git a/Source/WebCore/rendering/InlineBoxPainter.cpp b/Source/WebCore/rendering/InlineBoxPainter.cpp index 9c9ee3415e81..3bdb6a106ced 100644 --- a/Source/WebCore/rendering/InlineBoxPainter.cpp +++ b/Source/WebCore/rendering/InlineBoxPainter.cpp @@ -287,8 +287,8 @@ void InlineBoxPainter::paintFillLayers(const Color& color, const FillLayer& fill Vector layers; for (auto* layer = &fillLayer; layer; layer = layer->next()) layers.append(layer); - layers.reverse(); - for (auto* layer : layers) + + for (auto* layer : makeReversedRange(layers)) paintFillLayer(color, *layer, rect, op); } diff --git a/Source/WebCore/rendering/RenderBox.cpp b/Source/WebCore/rendering/RenderBox.cpp index eea772510524..483b09ac5f13 100644 --- a/Source/WebCore/rendering/RenderBox.cpp +++ b/Source/WebCore/rendering/RenderBox.cpp @@ -1610,7 +1610,7 @@ void RenderBox::paintRootBoxFillLayers(const PaintInfo& paintInfo) auto color = style.visitedDependentColor(CSSPropertyBackgroundColor); auto compositeOp = document().compositeOperatorForBackgroundColor(color, *this); - paintFillLayers(paintInfo, style.colorByApplyingColorFilter(color), style.backgroundLayers(), view().backgroundRect(), BackgroundBleedNone, compositeOp, rootBackgroundRenderer); + BackgroundPainter { *this, paintInfo }.paintFillLayers(style.colorByApplyingColorFilter(color), style.backgroundLayers(), view().backgroundRect(), BackgroundBleedNone, compositeOp, rootBackgroundRenderer); } BackgroundBleedAvoidance RenderBox::determineBackgroundBleedAvoidance(GraphicsContext& context) const @@ -1736,7 +1736,7 @@ void RenderBox::paintBackground(const PaintInfo& paintInfo, const LayoutRect& pa auto backgroundColor = style().visitedDependentColor(CSSPropertyBackgroundColor); auto compositeOp = document().compositeOperatorForBackgroundColor(backgroundColor, *this); - paintFillLayers(paintInfo, style().colorByApplyingColorFilter(backgroundColor), style().backgroundLayers(), paintRect, bleedAvoidance, compositeOp); + BackgroundPainter { *this, paintInfo }.paintFillLayers(style().colorByApplyingColorFilter(backgroundColor), style().backgroundLayers(), paintRect, bleedAvoidance, compositeOp); } bool RenderBox::getBackgroundPaintedExtent(const LayoutPoint& paintOffset, LayoutRect& paintedExtent) const @@ -1756,9 +1756,9 @@ bool RenderBox::getBackgroundPaintedExtent(const LayoutPoint& paintOffset, Layou return true; } - auto geometry = calculateBackgroundImageGeometry(nullptr, layers, paintOffset, backgroundRect); - paintedExtent = geometry.destRect(); - return !geometry.hasNonLocalGeometry(); + auto geometry = BackgroundPainter::calculateBackgroundImageGeometry(*this, nullptr, layers, paintOffset, backgroundRect); + paintedExtent = geometry.destinationRect; + return !geometry.hasNonLocalGeometry; } bool RenderBox::backgroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect) const @@ -1947,7 +1947,7 @@ void RenderBox::paintMaskImages(const PaintInfo& paintInfo, const LayoutRect& pa } if (allMaskImagesLoaded) { - paintFillLayers(paintInfo, Color(), style().maskLayers(), paintRect, BackgroundBleedNone, compositeOp); + BackgroundPainter { *this, paintInfo }.paintFillLayers(Color(), style().maskLayers(), paintRect, BackgroundBleedNone, compositeOp); BorderPainter { *this, paintInfo }.paintNinePieceImage(paintRect, style(), style().maskBoxImage(), compositeOp); } @@ -1971,55 +1971,12 @@ LayoutRect RenderBox::maskClipRect(const LayoutPoint& paintOffset) for (auto* maskLayer = &style().maskLayers(); maskLayer; maskLayer = maskLayer->next()) { if (maskLayer->image()) { // Masks should never have fixed attachment, so it's OK for paintContainer to be null. - result.unite(calculateBackgroundImageGeometry(nullptr, *maskLayer, paintOffset, borderBox).destRect()); + result.unite(BackgroundPainter::calculateBackgroundImageGeometry(*this, nullptr, *maskLayer, paintOffset, borderBox).destinationRect); } } return result; } -void RenderBox::paintFillLayers(const PaintInfo& paintInfo, const Color& color, const FillLayer& fillLayer, const LayoutRect& rect, - BackgroundBleedAvoidance bleedAvoidance, CompositeOperator op, RenderElement* backgroundObject) -{ - Vector layers; - bool shouldDrawBackgroundInSeparateBuffer = false; - - for (auto* layer = &fillLayer; layer; layer = layer->next()) { - layers.append(layer); - - if (layer->blendMode() != BlendMode::Normal) - shouldDrawBackgroundInSeparateBuffer = true; - - // Stop traversal when an opaque layer is encountered. - // FIXME: It would be possible for the following occlusion culling test to be more aggressive - // on layers with no repeat by testing whether the image covers the layout rect. - // Testing that here would imply duplicating a lot of calculations that are currently done in - // BackgroundPainter::paintFillLayer. A more efficient solution might be to move - // the layer recursion into paintFillLayer, or to compute the layer geometry here - // and pass it down. - - // The clipOccludesNextLayers condition must be evaluated first to avoid short-circuiting. - if (layer->clipOccludesNextLayers(layer == &fillLayer) && layer->hasOpaqueImage(*this) && layer->image()->canRender(this, style().effectiveZoom()) && layer->hasRepeatXY() && layer->blendMode() == BlendMode::Normal) - break; - } - - auto& context = paintInfo.context(); - auto baseBgColorUsage = BaseBackgroundColorUse; - - BackgroundPainter backgroundPainter { *this, paintInfo }; - - if (shouldDrawBackgroundInSeparateBuffer) { - backgroundPainter.paintFillLayer(color, *layers.last(), rect, bleedAvoidance, { }, { }, op, backgroundObject, BaseBackgroundColorOnly); - baseBgColorUsage = BaseBackgroundColorSkip; - context.beginTransparencyLayer(1); - } - - for (auto& layer : makeReversedRange(layers)) - backgroundPainter.paintFillLayer(color, *layer, rect, bleedAvoidance, { }, { }, op, backgroundObject, baseBgColorUsage); - - if (shouldDrawBackgroundInSeparateBuffer) - context.endTransparencyLayer(); -} - static StyleImage* findLayerUsedImage(WrappedImagePtr image, const FillLayer& layers) { for (auto* layer = &layers; layer; layer = layer->next()) { @@ -2107,15 +2064,15 @@ bool RenderBox::repaintLayerRectsForImage(WrappedImagePtr image, const FillLayer } } // FIXME: Figure out how to pass absolute position to calculateBackgroundImageGeometry (for pixel snapping) - BackgroundImageGeometry geometry = layerRenderer->calculateBackgroundImageGeometry(nullptr, *layer, LayoutPoint(), rendererRect); - if (geometry.hasNonLocalGeometry()) { + auto geometry = BackgroundPainter::calculateBackgroundImageGeometry(*layerRenderer, nullptr, *layer, LayoutPoint(), rendererRect); + if (geometry.hasNonLocalGeometry) { // Rather than incur the costs of computing the paintContainer for renderers with fixed backgrounds // in order to get the right destRect, just repaint the entire renderer. layerRenderer->repaint(); return true; } - LayoutRect rectToRepaint = geometry.destRect(); + LayoutRect rectToRepaint = geometry.destinationRect; bool shouldClipToLayer = true; // If this is the root background layer, we may need to extend the repaintRect if the FrameView has an @@ -2135,7 +2092,7 @@ bool RenderBox::repaintLayerRectsForImage(WrappedImagePtr image, const FillLayer } layerRenderer->repaintRectangle(rectToRepaint, shouldClipToLayer); - if (geometry.destRect() == rendererRect) + if (geometry.destinationRect == rendererRect) return true; } } diff --git a/Source/WebCore/rendering/RenderBox.h b/Source/WebCore/rendering/RenderBox.h index 09f78d77950c..d314d0ce1483 100644 --- a/Source/WebCore/rendering/RenderBox.h +++ b/Source/WebCore/rendering/RenderBox.h @@ -691,9 +691,6 @@ override; bool computeBackgroundIsKnownToBeObscured(const LayoutPoint& paintOffset) override; void paintBackground(const PaintInfo&, const LayoutRect&, BackgroundBleedAvoidance = BackgroundBleedNone); - - void paintFillLayers(const PaintInfo&, const Color&, const FillLayer&, const LayoutRect&, BackgroundBleedAvoidance = BackgroundBleedNone, CompositeOperator = CompositeOperator::SourceOver, RenderElement* backgroundObject = nullptr); - void paintMaskImages(const PaintInfo&, const LayoutRect&); BackgroundBleedAvoidance determineBackgroundBleedAvoidance(GraphicsContext&) const; diff --git a/Source/WebCore/rendering/RenderBoxModelObject.cpp b/Source/WebCore/rendering/RenderBoxModelObject.cpp index dd04cd29bfa4..7d58b0589b8d 100644 --- a/Source/WebCore/rendering/RenderBoxModelObject.cpp +++ b/Source/WebCore/rendering/RenderBoxModelObject.cpp @@ -750,96 +750,6 @@ LayoutSize RenderBoxModelObject::calculateImageIntrinsicDimensions(StyleImage* i return positioningAreaSize; } -LayoutSize RenderBoxModelObject::calculateFillTileSize(const FillLayer& fillLayer, const LayoutSize& positioningAreaSize) const -{ - StyleImage* image = fillLayer.image(); - FillSizeType type = fillLayer.size().type; - auto devicePixelSize = LayoutUnit { 1.0 / document().deviceScaleFactor() }; - - LayoutSize imageIntrinsicSize; - if (image) { - imageIntrinsicSize = calculateImageIntrinsicDimensions(image, positioningAreaSize, ScaleByEffectiveZoom); - imageIntrinsicSize.scale(1 / image->imageScaleFactor(), 1 / image->imageScaleFactor()); - } else - imageIntrinsicSize = positioningAreaSize; - - switch (type) { - case FillSizeType::Size: { - LayoutSize tileSize = positioningAreaSize; - - Length layerWidth = fillLayer.size().size.width; - Length layerHeight = fillLayer.size().size.height; - - if (layerWidth.isFixed()) - tileSize.setWidth(layerWidth.value()); - else if (layerWidth.isPercentOrCalculated()) { - auto resolvedWidth = valueForLength(layerWidth, positioningAreaSize.width()); - // Non-zero resolved value should always produce some content. - tileSize.setWidth(!resolvedWidth ? resolvedWidth : std::max(devicePixelSize, resolvedWidth)); - } - - if (layerHeight.isFixed()) - tileSize.setHeight(layerHeight.value()); - else if (layerHeight.isPercentOrCalculated()) { - auto resolvedHeight = valueForLength(layerHeight, positioningAreaSize.height()); - // Non-zero resolved value should always produce some content. - tileSize.setHeight(!resolvedHeight ? resolvedHeight : std::max(devicePixelSize, resolvedHeight)); - } - - // If one of the values is auto we have to use the appropriate - // scale to maintain our aspect ratio. - if (layerWidth.isAuto() && !layerHeight.isAuto()) { - if (imageIntrinsicSize.height()) - tileSize.setWidth(imageIntrinsicSize.width() * tileSize.height() / imageIntrinsicSize.height()); - } else if (!layerWidth.isAuto() && layerHeight.isAuto()) { - if (imageIntrinsicSize.width()) - tileSize.setHeight(imageIntrinsicSize.height() * tileSize.width() / imageIntrinsicSize.width()); - } else if (layerWidth.isAuto() && layerHeight.isAuto()) { - // If both width and height are auto, use the image's intrinsic size. - tileSize = imageIntrinsicSize; - } - - tileSize.clampNegativeToZero(); - return tileSize; - } - case FillSizeType::None: { - // If both values are ‘auto’ then the intrinsic width and/or height of the image should be used, if any. - if (!imageIntrinsicSize.isEmpty()) - return imageIntrinsicSize; - - // If the image has neither an intrinsic width nor an intrinsic height, its size is determined as for ‘contain’. - type = FillSizeType::Contain; - } - FALLTHROUGH; - case FillSizeType::Contain: - case FillSizeType::Cover: { - // Scale computation needs higher precision than what LayoutUnit can offer. - FloatSize localImageIntrinsicSize = imageIntrinsicSize; - FloatSize localPositioningAreaSize = positioningAreaSize; - - float horizontalScaleFactor = localImageIntrinsicSize.width() ? (localPositioningAreaSize.width() / localImageIntrinsicSize.width()) : 1; - float verticalScaleFactor = localImageIntrinsicSize.height() ? (localPositioningAreaSize.height() / localImageIntrinsicSize.height()) : 1; - float scaleFactor = type == FillSizeType::Contain ? std::min(horizontalScaleFactor, verticalScaleFactor) : std::max(horizontalScaleFactor, verticalScaleFactor); - - if (localImageIntrinsicSize.isEmpty()) - return { }; - - return LayoutSize(localImageIntrinsicSize.scaled(scaleFactor).expandedTo({ devicePixelSize, devicePixelSize })); - } - } - - ASSERT_NOT_REACHED(); - return { }; -} - -static void pixelSnapBackgroundImageGeometryForPainting(LayoutRect& destinationRect, LayoutSize& tileSize, LayoutSize& phase, LayoutSize& space, float scaleFactor) -{ - tileSize = LayoutSize(snapRectToDevicePixels(LayoutRect(destinationRect.location(), tileSize), scaleFactor).size()); - phase = LayoutSize(snapRectToDevicePixels(LayoutRect(destinationRect.location(), phase), scaleFactor).size()); - space = LayoutSize(snapRectToDevicePixels(LayoutRect(LayoutPoint(), space), scaleFactor).size()); - destinationRect = LayoutRect(snapRectToDevicePixels(destinationRect, scaleFactor)); -} - bool RenderBoxModelObject::fixedBackgroundPaintsInLocalCoordinates() const { if (!isDocumentElementRenderer()) @@ -855,214 +765,6 @@ bool RenderBoxModelObject::fixedBackgroundPaintsInLocalCoordinates() const return rootLayer->backing()->backgroundLayerPaintsFixedRootBackground(); } -static inline std::optional getSpace(LayoutUnit areaSize, LayoutUnit tileSize) -{ - if (int numberOfTiles = areaSize / tileSize; numberOfTiles > 1) - return (areaSize - numberOfTiles * tileSize) / (numberOfTiles - 1); - return std::nullopt; -} - -static LayoutUnit resolveEdgeRelativeLength(const Length& length, Edge edge, LayoutUnit availableSpace, const LayoutSize& areaSize, const LayoutSize& tileSize) -{ - LayoutUnit result = minimumValueForLength(length, availableSpace); - - if (edge == Edge::Right) - return areaSize.width() - tileSize.width() - result; - - if (edge == Edge::Bottom) - return areaSize.height() - tileSize.height() - result; - - return result; -} - -BackgroundImageGeometry RenderBoxModelObject::calculateBackgroundImageGeometry(const RenderLayerModelObject* paintContainer, const FillLayer& fillLayer, const LayoutPoint& paintOffset, - const LayoutRect& borderBoxRect, RenderElement* backgroundObject) const -{ - LayoutUnit left; - LayoutUnit top; - LayoutSize positioningAreaSize; - // Determine the background positioning area and set destination rect to the background painting area. - // Destination rect will be adjusted later if the background is non-repeating. - // FIXME: transforms spec says that fixed backgrounds behave like scroll inside transforms. https://bugs.webkit.org/show_bug.cgi?id=15679 - LayoutRect destinationRect(borderBoxRect); - bool fixedAttachment = fillLayer.attachment() == FillAttachment::FixedBackground; - float deviceScaleFactor = document().deviceScaleFactor(); - if (!fixedAttachment) { - LayoutUnit right; - LayoutUnit bottom; - // Scroll and Local. - if (fillLayer.origin() != FillBox::Border) { - left = borderLeft(); - right = borderRight(); - top = borderTop(); - bottom = borderBottom(); - if (fillLayer.origin() == FillBox::Content) { - left += paddingLeft(); - right += paddingRight(); - top += paddingTop(); - bottom += paddingBottom(); - } - } - - // The background of the box generated by the root element covers the entire canvas including - // its margins. Since those were added in already, we have to factor them out when computing - // the background positioning area. - if (isDocumentElementRenderer()) { - positioningAreaSize = downcast(*this).size() - LayoutSize(left + right, top + bottom); - positioningAreaSize = LayoutSize(snapSizeToDevicePixel(positioningAreaSize, LayoutPoint(), deviceScaleFactor)); - if (view().frameView().hasExtendedBackgroundRectForPainting()) { - LayoutRect extendedBackgroundRect = view().frameView().extendedBackgroundRectForPainting(); - left += (marginLeft() - extendedBackgroundRect.x()); - top += (marginTop() - extendedBackgroundRect.y()); - } - } else { - positioningAreaSize = borderBoxRect.size() - LayoutSize(left + right, top + bottom); - positioningAreaSize = LayoutSize(snapRectToDevicePixels(LayoutRect(paintOffset, positioningAreaSize), deviceScaleFactor).size()); - } - } else { - LayoutRect viewportRect; - float topContentInset = 0; - if (settings().fixedBackgroundsPaintRelativeToDocument()) - viewportRect = view().unscaledDocumentRect(); - else { - FrameView& frameView = view().frameView(); - bool useFixedLayout = frameView.useFixedLayout() && !frameView.fixedLayoutSize().isEmpty(); - - if (useFixedLayout) { - // Use the fixedLayoutSize() when useFixedLayout() because the rendering will scale - // down the frameView to to fit in the current viewport. - viewportRect.setSize(frameView.fixedLayoutSize()); - } else - viewportRect.setSize(frameView.sizeForVisibleContent()); - - if (fixedBackgroundPaintsInLocalCoordinates()) { - if (!useFixedLayout) { - // Shifting location up by topContentInset is needed for layout tests which expect - // layout to be shifted down when calling window.internals.setTopContentInset(). - topContentInset = frameView.topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset); - viewportRect.setLocation(LayoutPoint(0, -topContentInset)); - } - } else if (useFixedLayout || frameView.frameScaleFactor() != 1) { - // scrollPositionForFixedPosition() is adjusted for page scale and it does not include - // topContentInset so do not add it to the calculation below. - viewportRect.setLocation(frameView.scrollPositionForFixedPosition()); - } else { - // documentScrollPositionRelativeToViewOrigin() includes -topContentInset in its height - // so we need to account for that in calculating the phase size - topContentInset = frameView.topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset); - viewportRect.setLocation(frameView.documentScrollPositionRelativeToViewOrigin()); - } - - top += topContentInset; - } - - if (paintContainer) - viewportRect.moveBy(LayoutPoint(-paintContainer->localToAbsolute(FloatPoint()))); - - destinationRect = viewportRect; - positioningAreaSize = destinationRect.size(); - positioningAreaSize.setHeight(positioningAreaSize.height() - topContentInset); - positioningAreaSize = LayoutSize(snapRectToDevicePixels(LayoutRect(destinationRect.location(), positioningAreaSize), deviceScaleFactor).size()); - } - - auto clientForBackgroundImage = backgroundObject ? backgroundObject : this; - LayoutSize tileSize = calculateFillTileSize(fillLayer, positioningAreaSize); - if (StyleImage* layerImage = fillLayer.image()) - layerImage->setContainerContextForRenderer(*clientForBackgroundImage, tileSize, style().effectiveZoom()); - - FillRepeat backgroundRepeatX = fillLayer.repeat().x; - FillRepeat backgroundRepeatY = fillLayer.repeat().y; - LayoutUnit availableWidth = positioningAreaSize.width() - tileSize.width(); - LayoutUnit availableHeight = positioningAreaSize.height() - tileSize.height(); - - LayoutSize spaceSize; - LayoutSize phase; - LayoutSize noRepeat; - LayoutUnit computedXPosition = resolveEdgeRelativeLength(fillLayer.xPosition(), fillLayer.backgroundXOrigin(), availableWidth, positioningAreaSize, tileSize); - if (backgroundRepeatX == FillRepeat::Round && positioningAreaSize.width() > 0 && tileSize.width() > 0) { - int numTiles = std::max(1, roundToInt(positioningAreaSize.width() / tileSize.width())); - if (fillLayer.size().size.height.isAuto() && backgroundRepeatY != FillRepeat::Round) - tileSize.setHeight(tileSize.height() * positioningAreaSize.width() / (numTiles * tileSize.width())); - - tileSize.setWidth(positioningAreaSize.width() / numTiles); - phase.setWidth(tileSize.width() ? tileSize.width() - fmodf((computedXPosition + left), tileSize.width()) : 0); - } - - LayoutUnit computedYPosition = resolveEdgeRelativeLength(fillLayer.yPosition(), fillLayer.backgroundYOrigin(), availableHeight, positioningAreaSize, tileSize); - if (backgroundRepeatY == FillRepeat::Round && positioningAreaSize.height() > 0 && tileSize.height() > 0) { - int numTiles = std::max(1, roundToInt(positioningAreaSize.height() / tileSize.height())); - if (fillLayer.size().size.width.isAuto() && backgroundRepeatX != FillRepeat::Round) - tileSize.setWidth(tileSize.width() * positioningAreaSize.height() / (numTiles * tileSize.height())); - - tileSize.setHeight(positioningAreaSize.height() / numTiles); - phase.setHeight(tileSize.height() ? tileSize.height() - fmodf((computedYPosition + top), tileSize.height()) : 0); - } - - if (backgroundRepeatX == FillRepeat::Repeat) { - phase.setWidth(tileSize.width() ? tileSize.width() - fmodf(computedXPosition + left, tileSize.width()) : 0); - spaceSize.setWidth(0); - } else if (backgroundRepeatX == FillRepeat::Space && tileSize.width() > 0) { - if (auto space = getSpace(positioningAreaSize.width(), tileSize.width())) { - LayoutUnit actualWidth = tileSize.width() + *space; - computedXPosition = minimumValueForLength(Length(), availableWidth); - spaceSize.setWidth(*space); - spaceSize.setHeight(0); - phase.setWidth(actualWidth ? actualWidth - fmodf((computedXPosition + left), actualWidth) : 0); - } else - backgroundRepeatX = FillRepeat::NoRepeat; - } - - if (backgroundRepeatX == FillRepeat::NoRepeat) { - LayoutUnit xOffset = left + computedXPosition; - if (xOffset > 0) - destinationRect.move(xOffset, 0_lu); - xOffset = std::min(xOffset, 0); - phase.setWidth(-xOffset); - destinationRect.setWidth(tileSize.width() + xOffset); - spaceSize.setWidth(0); - } - - if (backgroundRepeatY == FillRepeat::Repeat) { - phase.setHeight(tileSize.height() ? tileSize.height() - fmodf(computedYPosition + top, tileSize.height()) : 0); - spaceSize.setHeight(0); - } else if (backgroundRepeatY == FillRepeat::Space && tileSize.height() > 0) { - if (auto space = getSpace(positioningAreaSize.height(), tileSize.height())) { - LayoutUnit actualHeight = tileSize.height() + *space; - computedYPosition = minimumValueForLength(Length(), availableHeight); - spaceSize.setHeight(*space); - phase.setHeight(actualHeight ? actualHeight - fmodf((computedYPosition + top), actualHeight) : 0); - } else - backgroundRepeatY = FillRepeat::NoRepeat; - } - if (backgroundRepeatY == FillRepeat::NoRepeat) { - LayoutUnit yOffset = top + computedYPosition; - if (yOffset > 0) - destinationRect.move(0_lu, yOffset); - yOffset = std::min(yOffset, 0); - phase.setHeight(-yOffset); - destinationRect.setHeight(tileSize.height() + yOffset); - spaceSize.setHeight(0); - } - - if (fixedAttachment) { - LayoutPoint attachmentPoint = borderBoxRect.location(); - phase.expand(std::max(attachmentPoint.x() - destinationRect.x(), 0), std::max(attachmentPoint.y() - destinationRect.y(), 0)); - } - - destinationRect.intersect(borderBoxRect); - pixelSnapBackgroundImageGeometryForPainting(destinationRect, tileSize, phase, spaceSize, deviceScaleFactor); - return BackgroundImageGeometry(destinationRect, tileSize, phase, spaceSize, fixedAttachment); -} - -void RenderBoxModelObject::getGeometryForBackgroundImage(const RenderLayerModelObject* paintContainer, const LayoutPoint& paintOffset, FloatRect& destRect, FloatSize& phase, FloatSize& tileSize) const -{ - LayoutRect paintRect(destRect); - auto geometry = calculateBackgroundImageGeometry(paintContainer, style().backgroundLayers(), paintOffset, paintRect); - phase = geometry.phase(); - tileSize = geometry.tileSize(); - destRect = geometry.destRect(); -} - bool RenderBoxModelObject::borderObscuresBackgroundEdge(const FloatSize& contextScale) const { auto edges = borderEdges(style(), document().deviceScaleFactor()); diff --git a/Source/WebCore/rendering/RenderBoxModelObject.h b/Source/WebCore/rendering/RenderBoxModelObject.h index dfd72a27a33a..c8a992687197 100644 --- a/Source/WebCore/rendering/RenderBoxModelObject.h +++ b/Source/WebCore/rendering/RenderBoxModelObject.h @@ -72,42 +72,6 @@ enum class BoxSideFlag : uint8_t; using BoxSideSet = OptionSet; using BorderEdges = RectEdges; -class BackgroundImageGeometry { -public: - BackgroundImageGeometry(const LayoutRect& destinationRect, const LayoutSize& tile, const LayoutSize& phase, const LayoutSize& space, bool fixedAttachment) - : m_destRect(destinationRect) - , m_destOrigin(m_destRect.location()) - , m_tileSize(tile) - , m_phase(phase) - , m_space(space) - , m_hasNonLocalGeometry(fixedAttachment) - { - } - - LayoutRect destRect() const { return m_destRect; } - LayoutSize phase() const { return m_phase; } - LayoutSize tileSize() const { return m_tileSize; } - LayoutSize spaceSize() const { return m_space; } - bool hasNonLocalGeometry() const { return m_hasNonLocalGeometry; } - - LayoutSize relativePhase() const - { - LayoutSize phase = m_phase; - phase += m_destRect.location() - m_destOrigin; - return phase; - } - - void clip(const LayoutRect& clipRect) { m_destRect.intersect(clipRect); } - -private: - LayoutRect m_destRect; - LayoutPoint m_destOrigin; - LayoutSize m_tileSize; - LayoutSize m_phase; - LayoutSize m_space; - bool m_hasNonLocalGeometry; // Has background-attachment: fixed. Implies that we can't always cheaply compute destRect. -}; - // This class is the base for all objects that adhere to the CSS box model as described // at http://www.w3.org/TR/CSS21/box.html @@ -230,7 +194,6 @@ class RenderBoxModelObject : public RenderLayerModelObject { bool canHaveBoxInfoInFragment() const { return !isFloating() && !isReplacedOrInlineBlock() && !isInline() && !isTableCell() && isRenderBlock() && !isRenderSVGBlock(); } - void getGeometryForBackgroundImage(const RenderLayerModelObject* paintContainer, const LayoutPoint& paintOffset, FloatRect& destRect, FloatSize& phase, FloatSize& tileSize) const; void contentChanged(ContentChangeType); bool hasAcceleratedCompositing() const; @@ -264,14 +227,11 @@ class RenderBoxModelObject : public RenderLayerModelObject { bool hasVisibleBoxDecorationStyle() const; bool borderObscuresBackgroundEdge(const FloatSize& contextScale) const; bool borderObscuresBackground() const; - RoundedRect backgroundRoundedRectAdjustedForBleedAvoidance(const GraphicsContext&, const LayoutRect&, BackgroundBleedAvoidance, const InlineIterator::InlineBoxIterator&, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const; bool hasAutoHeightOrContainingBlockWithAutoHeight() const; public: - BackgroundImageGeometry calculateBackgroundImageGeometry(const RenderLayerModelObject* paintContainer, const FillLayer&, const LayoutPoint& paintOffset, - const LayoutRect& paintRect, RenderElement* = nullptr) const; - + bool fixedBackgroundPaintsInLocalCoordinates() const; InterpolationQuality chooseInterpolationQuality(GraphicsContext&, Image&, const void*, const LayoutSize&) const; DecodingMode decodingModeForImageDraw(const Image&, const PaintInfo&) const; @@ -311,10 +271,6 @@ class RenderBoxModelObject : public RenderLayerModelObject { ContinuationChainNode& ensureContinuationChainNode(); virtual LayoutRect frameRectForStickyPositioning() const = 0; - - LayoutSize calculateFillTileSize(const FillLayer&, const LayoutSize& scaledPositioningAreaSize) const; - - bool fixedBackgroundPaintsInLocalCoordinates() const; }; } // namespace WebCore diff --git a/Source/WebCore/rendering/RenderLayerBacking.cpp b/Source/WebCore/rendering/RenderLayerBacking.cpp index aa7993e13bb3..f009e550eded 100644 --- a/Source/WebCore/rendering/RenderLayerBacking.cpp +++ b/Source/WebCore/rendering/RenderLayerBacking.cpp @@ -27,6 +27,7 @@ #include "RenderLayerBacking.h" +#include "BackgroundPainter.h" #include "BitmapImage.h" #include "CanvasRenderingContext.h" #include "CSSPropertyNames.h" @@ -2649,16 +2650,14 @@ void RenderLayerBacking::updateDirectlyCompositedBackgroundImage(PaintedContents return; } - auto destRect = backgroundBoxForSimpleContainerPainting(); - FloatSize phase; - FloatSize tileSize; + auto backgroundBox = LayoutRect { backgroundBoxForSimpleContainerPainting() }; // FIXME: Absolute paint location is required here. - downcast(renderer()).getGeometryForBackgroundImage(&renderer(), LayoutPoint(), destRect, phase, tileSize); + auto geometry = BackgroundPainter::calculateBackgroundImageGeometry(*renderBox(), renderBox(), style.backgroundLayers(), { }, backgroundBox); - m_graphicsLayer->setContentsTileSize(tileSize); - m_graphicsLayer->setContentsTilePhase(phase); - m_graphicsLayer->setContentsRect(destRect); - m_graphicsLayer->setContentsClippingRect(FloatRoundedRect(destRect)); + m_graphicsLayer->setContentsTileSize(geometry.tileSize); + m_graphicsLayer->setContentsTilePhase(geometry.phase); + m_graphicsLayer->setContentsRect(geometry.destinationRect); + m_graphicsLayer->setContentsClippingRect(FloatRoundedRect(geometry.destinationRect)); m_graphicsLayer->setContentsToImage(style.backgroundLayers().image()->cachedImage()->image()); didUpdateContentsRect = true; diff --git a/Source/WebCore/rendering/RenderTableCell.cpp b/Source/WebCore/rendering/RenderTableCell.cpp index 0a78e8e17896..7c48c3967ab7 100644 --- a/Source/WebCore/rendering/RenderTableCell.cpp +++ b/Source/WebCore/rendering/RenderTableCell.cpp @@ -25,6 +25,7 @@ #include "config.h" #include "RenderTableCell.h" +#include "BackgroundPainter.h" #include "BorderPainter.h" #include "CollapsedBorderValue.h" #include "ElementInlines.h" @@ -1317,7 +1318,7 @@ void RenderTableCell::paintBackgroundsBehindCell(PaintInfo& paintInfo, const Lay paintInfo.context().clip(clipRect); } auto compositeOp = document().compositeOperatorForBackgroundColor(color, *this); - paintFillLayers(paintInfo, color, bgLayer, LayoutRect(adjustedPaintOffset, frameRect().size()), BackgroundBleedNone, compositeOp, backgroundObject); + BackgroundPainter { *this, paintInfo }.paintFillLayers(color, bgLayer, LayoutRect(adjustedPaintOffset, frameRect().size()), BackgroundBleedNone, compositeOp, backgroundObject); } void RenderTableCell::paintBoxDecorations(PaintInfo& paintInfo, const LayoutPoint& paintOffset)