Index: Source/core/paint/BoxPainter.cpp |
diff --git a/Source/core/paint/BoxPainter.cpp b/Source/core/paint/BoxPainter.cpp |
index 684e70fba3c259d815c22e3b588e37f092ba9a78..e393f24ce02dc610f1303fbfc00604d642ce6ad8 100644 |
--- a/Source/core/paint/BoxPainter.cpp |
+++ b/Source/core/paint/BoxPainter.cpp |
@@ -5,13 +5,22 @@ |
#include "config.h" |
#include "core/paint/BoxPainter.h" |
+#include "core/HTMLNames.h" |
+#include "core/frame/Settings.h" |
+#include "core/html/HTMLFrameOwnerElement.h" |
+#include "core/paint/BackgroundImageGeometry.h" |
#include "core/paint/BoxDecorationData.h" |
+#include "core/rendering/ImageQualityController.h" |
#include "core/rendering/PaintInfo.h" |
#include "core/rendering/RenderBox.h" |
+#include "core/rendering/RenderBoxModelObject.h" |
#include "core/rendering/RenderLayer.h" |
#include "core/rendering/RenderTable.h" |
#include "core/rendering/RenderTheme.h" |
#include "core/rendering/RenderView.h" |
+#include "core/rendering/compositing/CompositedLayerMapping.h" |
+#include "core/rendering/style/ShadowList.h" |
+#include "platform/LengthFunctions.h" |
#include "platform/geometry/LayoutPoint.h" |
#include "platform/graphics/GraphicsContextStateSaver.h" |
@@ -157,8 +166,8 @@ void BoxPainter::paintFillLayers(const PaintInfo& paintInfo, const Color& c, con |
// Paint the document's base background color outside the transparency layer, |
// so that the background images don't blend with this color: http://crbug.com/389039. |
- if (isBaseColorVisible && m_renderBox.isDocumentElementWithOpaqueBackground()) { |
- m_renderBox.paintRootBackgroundColor(paintInfo, rect, Color()); |
+ if (isBaseColorVisible && isDocumentElementWithOpaqueBackground()) { |
+ paintRootBackgroundColor(paintInfo, rect, Color()); |
skipBaseColor = true; |
} |
context->beginTransparencyLayer(1); |
@@ -175,7 +184,307 @@ void BoxPainter::paintFillLayers(const PaintInfo& paintInfo, const Color& c, con |
void BoxPainter::paintFillLayer(const PaintInfo& paintInfo, const Color& c, const FillLayer& fillLayer, const LayoutRect& rect, |
BackgroundBleedAvoidance bleedAvoidance, CompositeOperator op, RenderObject* backgroundObject, bool skipBaseColor) |
{ |
- m_renderBox.paintFillLayerExtended(paintInfo, c, fillLayer, rect, bleedAvoidance, 0, LayoutSize(), op, backgroundObject, skipBaseColor); |
+ paintFillLayerExtended(paintInfo, c, fillLayer, rect, bleedAvoidance, 0, LayoutSize(), op, backgroundObject, skipBaseColor); |
+} |
+ |
+void BoxPainter::applyBoxShadowForBackground(GraphicsContext* context) |
+{ |
+ const ShadowList* shadowList = m_renderBox.style()->boxShadow(); |
+ ASSERT(shadowList); |
+ for (size_t i = shadowList->shadows().size(); i--; ) { |
+ const ShadowData& boxShadow = shadowList->shadows()[i]; |
+ if (boxShadow.style() != Normal) |
+ continue; |
+ FloatSize shadowOffset(boxShadow.x(), boxShadow.y()); |
+ context->setShadow(shadowOffset, boxShadow.blur(), boxShadow.color(), |
+ DrawLooperBuilder::ShadowRespectsTransforms, DrawLooperBuilder::ShadowIgnoresAlpha); |
+ return; |
+ } |
+} |
+ |
+// FIXME: See crbug.com/382491. The use of getCTM in this context is incorrect because the matrix returned does not |
+// include scales applied at raster time, such as the device zoom. |
+static LayoutRect shrinkRectByOnePixel(GraphicsContext* context, const LayoutRect& rect) |
+{ |
+ LayoutRect shrunkRect = rect; |
+ AffineTransform transform = context->getCTM(); |
+ shrunkRect.inflateX(-static_cast<LayoutUnit>(ceil(1 / transform.xScale()))); |
+ shrunkRect.inflateY(-static_cast<LayoutUnit>(ceil(1 / transform.yScale()))); |
+ return shrunkRect; |
+} |
+ |
+RoundedRect BoxPainter::getBackgroundRoundedRect(const LayoutRect& borderRect, InlineFlowBox* box, LayoutUnit inlineBoxWidth, LayoutUnit inlineBoxHeight, |
+ bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const |
+{ |
+ RoundedRect border = m_renderBox.style()->getRoundedBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge); |
+ if (box && (box->nextLineBox() || box->prevLineBox())) { |
+ RoundedRect segmentBorder = m_renderBox.style()->getRoundedBorderFor(LayoutRect(0, 0, inlineBoxWidth, inlineBoxHeight), includeLogicalLeftEdge, includeLogicalRightEdge); |
+ border.setRadii(segmentBorder.radii()); |
+ } |
+ |
+ return border; |
+} |
+ |
+RoundedRect BoxPainter::backgroundRoundedRectAdjustedForBleedAvoidance(GraphicsContext* context, const LayoutRect& borderRect, BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, const LayoutSize& boxSize, bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const |
+{ |
+ if (bleedAvoidance == BackgroundBleedShrinkBackground) { |
+ // We shrink the rectangle by one pixel on each side because the bleed is one pixel maximum. |
+ return getBackgroundRoundedRect(shrinkRectByOnePixel(context, borderRect), box, boxSize.width(), boxSize.height(), includeLogicalLeftEdge, includeLogicalRightEdge); |
+ } |
+ if (bleedAvoidance == BackgroundBleedBackgroundOverBorder) |
+ return m_renderBox.style()->getRoundedInnerBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge); |
+ |
+ return getBackgroundRoundedRect(borderRect, box, boxSize.width(), boxSize.height(), includeLogicalLeftEdge, includeLogicalRightEdge); |
+} |
+ |
+void BoxPainter::clipRoundedInnerRect(GraphicsContext * context, const LayoutRect& rect, const RoundedRect& clipRect) |
+{ |
+ if (clipRect.isRenderable()) { |
+ context->clipRoundedRect(clipRect); |
+ } else { |
+ // We create a rounded rect for each of the corners and clip it, while making sure we clip opposing corners together. |
+ if (!clipRect.radii().topLeft().isEmpty() || !clipRect.radii().bottomRight().isEmpty()) { |
+ IntRect topCorner(clipRect.rect().x(), clipRect.rect().y(), rect.maxX() - clipRect.rect().x(), rect.maxY() - clipRect.rect().y()); |
+ RoundedRect::Radii topCornerRadii; |
+ topCornerRadii.setTopLeft(clipRect.radii().topLeft()); |
+ context->clipRoundedRect(RoundedRect(topCorner, topCornerRadii)); |
+ |
+ IntRect bottomCorner(rect.x(), rect.y(), clipRect.rect().maxX() - rect.x(), clipRect.rect().maxY() - rect.y()); |
+ RoundedRect::Radii bottomCornerRadii; |
+ bottomCornerRadii.setBottomRight(clipRect.radii().bottomRight()); |
+ context->clipRoundedRect(RoundedRect(bottomCorner, bottomCornerRadii)); |
+ } |
+ |
+ if (!clipRect.radii().topRight().isEmpty() || !clipRect.radii().bottomLeft().isEmpty()) { |
+ IntRect topCorner(rect.x(), clipRect.rect().y(), clipRect.rect().maxX() - rect.x(), rect.maxY() - clipRect.rect().y()); |
+ RoundedRect::Radii topCornerRadii; |
+ topCornerRadii.setTopRight(clipRect.radii().topRight()); |
+ context->clipRoundedRect(RoundedRect(topCorner, topCornerRadii)); |
+ |
+ IntRect bottomCorner(clipRect.rect().x(), rect.y(), rect.maxX() - clipRect.rect().x(), clipRect.rect().maxY() - rect.y()); |
+ RoundedRect::Radii bottomCornerRadii; |
+ bottomCornerRadii.setBottomLeft(clipRect.radii().bottomLeft()); |
+ context->clipRoundedRect(RoundedRect(bottomCorner, bottomCornerRadii)); |
+ } |
+ } |
+} |
+ |
+void BoxPainter::paintFillLayerExtended(const PaintInfo& paintInfo, const Color& color, const FillLayer& bgLayer, const LayoutRect& rect, |
+ BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, const LayoutSize& boxSize, CompositeOperator op, RenderObject* backgroundObject, bool skipBaseColor) |
+{ |
+ GraphicsContext* context = paintInfo.context; |
+ if (rect.isEmpty()) |
+ return; |
+ |
+ bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true; |
+ bool includeRightEdge = box ? box->includeLogicalRightEdge() : true; |
+ |
+ bool hasRoundedBorder = m_renderBox.style()->hasBorderRadius() && (includeLeftEdge || includeRightEdge); |
+ bool clippedWithLocalScrolling = m_renderBox.hasOverflowClip() && bgLayer.attachment() == LocalBackgroundAttachment; |
+ bool isBorderFill = bgLayer.clip() == BorderFillBox; |
+ bool isDocumentElementRenderer = m_renderBox.isDocumentElement(); |
+ bool isBottomLayer = !bgLayer.next(); |
+ |
+ Color bgColor = color; |
+ StyleImage* bgImage = bgLayer.image(); |
+ bool shouldPaintBackgroundImage = bgImage && bgImage->canRender(m_renderBox, m_renderBox.style()->effectiveZoom()); |
+ |
+ bool forceBackgroundToWhite = false; |
+ if (m_renderBox.document().printing()) { |
+ if (m_renderBox.style()->printColorAdjust() == PrintColorAdjustEconomy) |
+ forceBackgroundToWhite = true; |
+ if (m_renderBox.document().settings() && m_renderBox.document().settings()->shouldPrintBackgrounds()) |
+ forceBackgroundToWhite = false; |
+ } |
+ |
+ // When printing backgrounds is disabled or using economy mode, |
+ // change existing background colors and images to a solid white background. |
+ // If there's no bg color or image, leave it untouched to avoid affecting transparency. |
+ // We don't try to avoid loading the background images, because this style flag is only set |
+ // when printing, and at that point we've already loaded the background images anyway. (To avoid |
+ // loading the background images we'd have to do this check when applying styles rather than |
+ // while rendering.) |
+ if (forceBackgroundToWhite) { |
+ // Note that we can't reuse this variable below because the bgColor might be changed |
+ bool shouldPaintBackgroundColor = isBottomLayer && bgColor.alpha(); |
+ if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) { |
+ bgColor = Color::white; |
+ shouldPaintBackgroundImage = false; |
+ } |
+ } |
+ |
+ bool colorVisible = bgColor.alpha(); |
+ |
+ // Fast path for drawing simple color backgrounds. |
+ if (!isDocumentElementRenderer && !clippedWithLocalScrolling && !shouldPaintBackgroundImage && isBorderFill && isBottomLayer) { |
+ if (!colorVisible) |
+ return; |
+ |
+ bool boxShadowShouldBeAppliedToBackground = m_renderBox.boxShadowShouldBeAppliedToBackground(bleedAvoidance, box); |
+ GraphicsContextStateSaver shadowStateSaver(*context, boxShadowShouldBeAppliedToBackground); |
+ if (boxShadowShouldBeAppliedToBackground) |
+ applyBoxShadowForBackground(context); |
+ |
+ if (hasRoundedBorder && bleedAvoidance != BackgroundBleedClipBackground) { |
+ RoundedRect border = backgroundRoundedRectAdjustedForBleedAvoidance(context, rect, bleedAvoidance, box, boxSize, includeLeftEdge, includeRightEdge); |
+ if (border.isRenderable()) { |
+ context->fillRoundedRect(border, bgColor); |
+ } else { |
+ context->save(); |
+ clipRoundedInnerRect(context, rect, border); |
+ context->fillRect(border.rect(), bgColor); |
+ context->restore(); |
+ } |
+ } else { |
+ context->fillRect(pixelSnappedIntRect(rect), bgColor); |
+ } |
+ |
+ return; |
+ } |
+ |
+ // BorderFillBox radius clipping is taken care of by BackgroundBleedClipBackground |
+ bool clipToBorderRadius = hasRoundedBorder && !(isBorderFill && bleedAvoidance == BackgroundBleedClipBackground); |
+ GraphicsContextStateSaver clipToBorderStateSaver(*context, clipToBorderRadius); |
+ if (clipToBorderRadius) { |
+ RoundedRect border = isBorderFill ? backgroundRoundedRectAdjustedForBleedAvoidance(context, rect, bleedAvoidance, box, boxSize, includeLeftEdge, includeRightEdge) : getBackgroundRoundedRect(rect, box, boxSize.width(), boxSize.height(), includeLeftEdge, includeRightEdge); |
+ |
+ // Clip to the padding or content boxes as necessary. |
+ if (bgLayer.clip() == ContentFillBox) { |
+ border = m_renderBox.style()->getRoundedInnerBorderFor(border.rect(), |
+ m_renderBox.paddingTop() + m_renderBox.borderTop(), m_renderBox.paddingBottom() + m_renderBox.borderBottom(), |
+ m_renderBox.paddingLeft() + m_renderBox.borderLeft(), m_renderBox.paddingRight() + m_renderBox.borderRight(), includeLeftEdge, includeRightEdge); |
+ } else if (bgLayer.clip() == PaddingFillBox) { |
+ border = m_renderBox.style()->getRoundedInnerBorderFor(border.rect(), includeLeftEdge, includeRightEdge); |
+ } |
+ |
+ clipRoundedInnerRect(context, rect, border); |
+ } |
+ |
+ int bLeft = includeLeftEdge ? m_renderBox.borderLeft() : 0; |
+ int bRight = includeRightEdge ? m_renderBox.borderRight() : 0; |
+ LayoutUnit pLeft = includeLeftEdge ? m_renderBox.paddingLeft() : LayoutUnit(); |
+ LayoutUnit pRight = includeRightEdge ? m_renderBox.paddingRight() : LayoutUnit(); |
+ |
+ GraphicsContextStateSaver clipWithScrollingStateSaver(*context, clippedWithLocalScrolling); |
+ LayoutRect scrolledPaintRect = rect; |
+ if (clippedWithLocalScrolling) { |
+ // Clip to the overflow area. |
+ context->clip(m_renderBox.overflowClipRect(rect.location())); |
+ |
+ // Adjust the paint rect to reflect a scrolled content box with borders at the ends. |
+ IntSize offset = m_renderBox.scrolledContentOffset(); |
+ scrolledPaintRect.move(-offset); |
+ scrolledPaintRect.setWidth(bLeft + m_renderBox.scrollWidth() + bRight); |
+ scrolledPaintRect.setHeight(m_renderBox.borderTop() + m_renderBox.scrollHeight() + m_renderBox.borderBottom()); |
+ } |
+ |
+ GraphicsContextStateSaver backgroundClipStateSaver(*context, false); |
+ IntRect maskRect; |
+ |
+ switch (bgLayer.clip()) { |
+ case PaddingFillBox: |
+ case ContentFillBox: { |
+ if (clipToBorderRadius) |
+ break; |
+ |
+ // Clip to the padding or content boxes as necessary. |
+ bool includePadding = bgLayer.clip() == ContentFillBox; |
+ LayoutRect clipRect = LayoutRect(scrolledPaintRect.x() + bLeft + (includePadding ? pLeft : LayoutUnit()), |
+ scrolledPaintRect.y() + m_renderBox.borderTop() + (includePadding ? m_renderBox.paddingTop() : LayoutUnit()), |
+ scrolledPaintRect.width() - bLeft - bRight - (includePadding ? pLeft + pRight : LayoutUnit()), |
+ scrolledPaintRect.height() - m_renderBox.borderTop() - m_renderBox.borderBottom() - (includePadding ? m_renderBox.paddingTop() + m_renderBox.paddingBottom() : LayoutUnit())); |
+ backgroundClipStateSaver.save(); |
+ context->clip(clipRect); |
+ |
+ break; |
+ } |
+ case TextFillBox: { |
+ // First figure out how big the mask has to be. It should be no bigger than what we need |
+ // to actually render, so we should intersect the dirty rect with the border box of the background. |
+ maskRect = pixelSnappedIntRect(rect); |
+ maskRect.intersect(paintInfo.rect); |
+ |
+ // We draw the background into a separate layer, to be later masked with yet another layer |
+ // holding the text content. |
+ backgroundClipStateSaver.save(); |
+ context->clip(maskRect); |
+ context->beginTransparencyLayer(1); |
+ |
+ break; |
+ } |
+ case BorderFillBox: |
+ break; |
+ default: |
+ ASSERT_NOT_REACHED(); |
+ break; |
+ } |
+ |
+ // Paint the color first underneath all images, culled if background image occludes it. |
+ // FIXME: In the bgLayer->hasFiniteBounds() case, we could improve the culling test |
+ // by verifying whether the background image covers the entire layout rect. |
+ if (isBottomLayer) { |
+ IntRect backgroundRect(pixelSnappedIntRect(scrolledPaintRect)); |
+ bool boxShadowShouldBeAppliedToBackground = m_renderBox.boxShadowShouldBeAppliedToBackground(bleedAvoidance, box); |
+ bool isOpaqueRoot = (isDocumentElementRenderer && !bgColor.hasAlpha()) || isDocumentElementWithOpaqueBackground(); |
+ if (boxShadowShouldBeAppliedToBackground || !shouldPaintBackgroundImage || !bgLayer.hasOpaqueImage(&m_renderBox) || !bgLayer.hasRepeatXY() || (isOpaqueRoot && m_renderBox.height())) { |
+ if (!boxShadowShouldBeAppliedToBackground) |
+ backgroundRect.intersect(paintInfo.rect); |
+ |
+ GraphicsContextStateSaver shadowStateSaver(*context, boxShadowShouldBeAppliedToBackground); |
+ if (boxShadowShouldBeAppliedToBackground) |
+ applyBoxShadowForBackground(context); |
+ |
+ if (isOpaqueRoot && !skipBaseColor) { |
+ paintRootBackgroundColor(paintInfo, rect, bgColor); |
+ } else if (bgColor.alpha()) { |
+ context->fillRect(backgroundRect, bgColor, context->compositeOperation()); |
+ } |
+ } |
+ } |
+ |
+ // no progressive loading of the background image |
+ if (shouldPaintBackgroundImage) { |
+ BackgroundImageGeometry geometry; |
+ calculateBackgroundImageGeometry(paintInfo.paintContainer(), bgLayer, scrolledPaintRect, geometry, backgroundObject); |
+ geometry.clip(paintInfo.rect); |
+ if (!geometry.destRect().isEmpty()) { |
+ CompositeOperator compositeOp = op == CompositeSourceOver ? bgLayer.composite() : op; |
+ RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : &m_renderBox; |
+ RefPtr<Image> image = bgImage->image(clientForBackgroundImage, geometry.tileSize()); |
+ InterpolationQuality interpolationQuality = chooseInterpolationQuality(context, image.get(), &bgLayer, geometry.tileSize()); |
+ if (bgLayer.maskSourceType() == MaskLuminance) |
+ context->setColorFilter(ColorFilterLuminanceToAlpha); |
+ InterpolationQuality previousInterpolationQuality = context->imageInterpolationQuality(); |
+ context->setImageInterpolationQuality(interpolationQuality); |
+ context->drawTiledImage(image.get(), geometry.destRect(), geometry.relativePhase(), geometry.tileSize(), |
+ compositeOp, bgLayer.blendMode(), geometry.spaceSize()); |
+ context->setImageInterpolationQuality(previousInterpolationQuality); |
+ } |
+ } |
+ |
+ if (bgLayer.clip() == TextFillBox) { |
+ // Create the text mask layer. |
+ context->setCompositeOperation(CompositeDestinationIn); |
+ context->beginTransparencyLayer(1); |
+ |
+ // FIXME: Workaround for https://code.google.com/p/skia/issues/detail?id=1291. |
+ context->clearRect(maskRect); |
+ |
+ // Now draw the text into the mask. We do this by painting using a special paint phase that signals to |
+ // InlineTextBoxes that they should just add their contents to the clip. |
+ PaintInfo info(context, maskRect, PaintPhaseTextClip, PaintBehaviorForceBlackText, 0); |
+ context->setCompositeOperation(CompositeSourceOver); |
+ if (box) { |
+ RootInlineBox& root = box->root(); |
+ box->paint(info, LayoutPoint(scrolledPaintRect.x() - box->x(), scrolledPaintRect.y() - box->y()), root.lineTop(), root.lineBottom()); |
+ } else { |
+ LayoutSize localOffset = m_renderBox.locationOffset(); |
+ paint(info, scrolledPaintRect.location() - localOffset); |
+ } |
+ |
+ context->endLayer(); |
+ context->endLayer(); |
+ } |
} |
void BoxPainter::paintMask(PaintInfo& paintInfo, const LayoutPoint& paintOffset) |
@@ -238,4 +547,321 @@ void BoxPainter::paintClippingMask(PaintInfo& paintInfo, const LayoutPoint& pain |
paintInfo.context->fillRect(pixelSnappedIntRect(paintRect), Color::black); |
} |
+void BoxPainter::paintRootBackgroundColor(const PaintInfo& paintInfo, const LayoutRect& rect, const Color& bgColor) |
+{ |
+ GraphicsContext* context = paintInfo.context; |
+ if (rect.isEmpty()) |
+ return; |
+ |
+ ASSERT(isDocumentElement()); |
+ |
+ IntRect backgroundRect(pixelSnappedIntRect(rect)); |
+ backgroundRect.intersect(paintInfo.rect); |
+ |
+ Color baseColor = m_renderBox.view()->frameView()->baseBackgroundColor(); |
+ bool shouldClearDocumentBackground = m_renderBox.document().settings() && m_renderBox.document().settings()->shouldClearDocumentBackground(); |
+ CompositeOperator operation = shouldClearDocumentBackground ? CompositeCopy : context->compositeOperation(); |
+ |
+ // If we have an alpha go ahead and blend with the base background color. |
+ if (baseColor.alpha()) { |
+ if (bgColor.alpha()) |
+ baseColor = baseColor.blend(bgColor); |
+ context->fillRect(backgroundRect, baseColor, operation); |
+ } else if (bgColor.alpha()) { |
+ context->fillRect(backgroundRect, bgColor, operation); |
+ } else if (shouldClearDocumentBackground) { |
+ context->clearRect(backgroundRect); |
+ } |
+} |
+ |
+bool BoxPainter::isDocumentElementWithOpaqueBackground() const |
+{ |
+ if (!m_renderBox.isDocumentElement()) |
+ return false; |
+ |
+ // The background is opaque only if we're the root document, since iframes with |
+ // no background in the child document should show the parent's background. |
+ bool isOpaque = true; |
+ Element* ownerElement = m_renderBox.document().ownerElement(); |
+ if (ownerElement) { |
+ if (!isHTMLFrameElement(*ownerElement)) { |
+ // Locate the <body> element using the DOM. This is easier than trying |
+ // to crawl around a render tree with potential :before/:after content and |
+ // anonymous blocks created by inline <body> tags etc. We can locate the <body> |
+ // render object very easily via the DOM. |
+ HTMLElement* body = m_renderBox.document().body(); |
+ if (body) { |
+ // Can't scroll a frameset document anyway. |
+ isOpaque = body->hasTagName(HTMLNames::framesetTag); |
+ } else { |
+ // FIXME: SVG specific behavior should be in the SVG code. |
+ // SVG documents and XML documents with SVG root nodes are transparent. |
+ isOpaque = !m_renderBox.document().hasSVGRootNode(); |
+ } |
+ } |
+ } else if (m_renderBox.view()->frameView()) { |
+ isOpaque = !m_renderBox.view()->frameView()->isTransparent(); |
+ } |
+ |
+ return isOpaque; |
+} |
+ |
+static inline int getSpace(int areaSize, int tileSize) |
+{ |
+ int numberOfTiles = areaSize / tileSize; |
+ int space = -1; |
+ |
+ if (numberOfTiles > 1) |
+ space = lroundf((float)(areaSize - numberOfTiles * tileSize) / (numberOfTiles - 1)); |
+ |
+ return space; |
+} |
+ |
+void BoxPainter::calculateBackgroundImageGeometry(const RenderLayerModelObject* paintContainer, const FillLayer& fillLayer, const LayoutRect& paintRect, |
+ BackgroundImageGeometry& geometry, RenderObject* backgroundObject) const |
+{ |
+ LayoutUnit left = 0; |
+ LayoutUnit top = 0; |
+ IntSize positioningAreaSize; |
+ IntRect snappedPaintRect = pixelSnappedIntRect(paintRect); |
+ |
+ // Determine the background positioning area and set destRect to the background painting area. |
+ // destRect will be adjusted later if the background is non-repeating. |
+ // FIXME: transforms spec says that fixed backgrounds behave like scroll inside transforms. |
+ bool fixedAttachment = fillLayer.attachment() == FixedBackgroundAttachment; |
+ |
+ if (RuntimeEnabledFeatures::fastMobileScrollingEnabled()) { |
+ // As a side effect of an optimization to blit on scroll, we do not honor the CSS |
+ // property "background-attachment: fixed" because it may result in rendering |
+ // artifacts. Note, these artifacts only appear if we are blitting on scroll of |
+ // a page that has fixed background images. |
+ fixedAttachment = false; |
+ } |
+ |
+ if (!fixedAttachment) { |
+ geometry.setDestRect(snappedPaintRect); |
+ |
+ LayoutUnit right = 0; |
+ LayoutUnit bottom = 0; |
+ // Scroll and Local. |
+ if (fillLayer.origin() != BorderFillBox) { |
+ left = m_renderBox.borderLeft(); |
+ right = m_renderBox.borderRight(); |
+ top = m_renderBox.borderTop(); |
+ bottom = m_renderBox.borderBottom(); |
+ if (fillLayer.origin() == ContentFillBox) { |
+ left += m_renderBox.paddingLeft(); |
+ right += m_renderBox.paddingRight(); |
+ top += m_renderBox.paddingTop(); |
+ bottom += m_renderBox.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 (m_renderBox.isDocumentElement()) { |
+ positioningAreaSize = pixelSnappedIntSize(m_renderBox.size() - LayoutSize(left + right, top + bottom), m_renderBox.location()); |
+ left += m_renderBox.marginLeft(); |
+ top += m_renderBox.marginTop(); |
+ } else { |
+ positioningAreaSize = pixelSnappedIntSize(paintRect.size() - LayoutSize(left + right, top + bottom), paintRect.location()); |
+ } |
+ } else { |
+ geometry.setHasNonLocalGeometry(); |
+ |
+ IntRect viewportRect = pixelSnappedIntRect(m_renderBox.viewRect()); |
+ if (fixedBackgroundPaintsInLocalCoordinates()) |
+ viewportRect.setLocation(IntPoint()); |
+ else if (FrameView* frameView = m_renderBox.view()->frameView()) |
+ viewportRect.setLocation(IntPoint(frameView->scrollOffsetForFixedPosition())); |
+ |
+ if (paintContainer) { |
+ IntPoint absoluteContainerOffset = roundedIntPoint(paintContainer->localToAbsolute(FloatPoint())); |
+ viewportRect.moveBy(-absoluteContainerOffset); |
+ } |
+ |
+ geometry.setDestRect(pixelSnappedIntRect(viewportRect)); |
+ positioningAreaSize = geometry.destRect().size(); |
+ } |
+ |
+ const RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : &m_renderBox; |
+ IntSize fillTileSize = calculateFillTileSize(fillLayer, positioningAreaSize); |
+ fillLayer.image()->setContainerSizeForRenderer(clientForBackgroundImage, fillTileSize, m_renderBox.style()->effectiveZoom()); |
+ geometry.setTileSize(fillTileSize); |
+ |
+ EFillRepeat backgroundRepeatX = fillLayer.repeatX(); |
+ EFillRepeat backgroundRepeatY = fillLayer.repeatY(); |
+ int availableWidth = positioningAreaSize.width() - geometry.tileSize().width(); |
+ int availableHeight = positioningAreaSize.height() - geometry.tileSize().height(); |
+ |
+ LayoutUnit computedXPosition = roundedMinimumValueForLength(fillLayer.xPosition(), availableWidth); |
+ if (backgroundRepeatX == RoundFill && positioningAreaSize.width() > 0 && fillTileSize.width() > 0) { |
+ long nrTiles = std::max(1l, lroundf((float)positioningAreaSize.width() / fillTileSize.width())); |
+ |
+ if (fillLayer.size().size.height().isAuto() && backgroundRepeatY != RoundFill) { |
+ fillTileSize.setHeight(fillTileSize.height() * positioningAreaSize.width() / (nrTiles * fillTileSize.width())); |
+ } |
+ |
+ fillTileSize.setWidth(positioningAreaSize.width() / nrTiles); |
+ geometry.setTileSize(fillTileSize); |
+ geometry.setPhaseX(geometry.tileSize().width() ? geometry.tileSize().width() - roundToInt(computedXPosition + left) % geometry.tileSize().width() : 0); |
+ geometry.setSpaceSize(IntSize()); |
+ } |
+ |
+ LayoutUnit computedYPosition = roundedMinimumValueForLength(fillLayer.yPosition(), availableHeight); |
+ if (backgroundRepeatY == RoundFill && positioningAreaSize.height() > 0 && fillTileSize.height() > 0) { |
+ long nrTiles = std::max(1l, lroundf((float)positioningAreaSize.height() / fillTileSize.height())); |
+ |
+ if (fillLayer.size().size.width().isAuto() && backgroundRepeatX != RoundFill) { |
+ fillTileSize.setWidth(fillTileSize.width() * positioningAreaSize.height() / (nrTiles * fillTileSize.height())); |
+ } |
+ |
+ fillTileSize.setHeight(positioningAreaSize.height() / nrTiles); |
+ geometry.setTileSize(fillTileSize); |
+ geometry.setPhaseY(geometry.tileSize().height() ? geometry.tileSize().height() - roundToInt(computedYPosition + top) % geometry.tileSize().height() : 0); |
+ geometry.setSpaceSize(IntSize()); |
+ } |
+ |
+ if (backgroundRepeatX == RepeatFill) { |
+ geometry.setPhaseX(geometry.tileSize().width() ? geometry.tileSize().width() - roundToInt(computedXPosition + left) % geometry.tileSize().width() : 0); |
+ geometry.setSpaceSize(IntSize()); |
+ } else if (backgroundRepeatX == SpaceFill && fillTileSize.width() > 0) { |
+ int space = getSpace(positioningAreaSize.width(), geometry.tileSize().width()); |
+ int actualWidth = geometry.tileSize().width() + space; |
+ |
+ if (space >= 0) { |
+ computedXPosition = roundedMinimumValueForLength(Length(), availableWidth); |
+ geometry.setSpaceSize(IntSize(space, 0)); |
+ geometry.setPhaseX(actualWidth ? actualWidth - roundToInt(computedXPosition + left) % actualWidth : 0); |
+ } else { |
+ backgroundRepeatX = NoRepeatFill; |
+ } |
+ } |
+ if (backgroundRepeatX == NoRepeatFill) { |
+ int xOffset = fillLayer.backgroundXOrigin() == RightEdge ? availableWidth - computedXPosition : computedXPosition; |
+ geometry.setNoRepeatX(left + xOffset); |
+ geometry.setSpaceSize(IntSize(0, geometry.spaceSize().height())); |
+ } |
+ |
+ if (backgroundRepeatY == RepeatFill) { |
+ geometry.setPhaseY(geometry.tileSize().height() ? geometry.tileSize().height() - roundToInt(computedYPosition + top) % geometry.tileSize().height() : 0); |
+ geometry.setSpaceSize(IntSize(geometry.spaceSize().width(), 0)); |
+ } else if (backgroundRepeatY == SpaceFill && fillTileSize.height() > 0) { |
+ int space = getSpace(positioningAreaSize.height(), geometry.tileSize().height()); |
+ int actualHeight = geometry.tileSize().height() + space; |
+ |
+ if (space >= 0) { |
+ computedYPosition = roundedMinimumValueForLength(Length(), availableHeight); |
+ geometry.setSpaceSize(IntSize(geometry.spaceSize().width(), space)); |
+ geometry.setPhaseY(actualHeight ? actualHeight - roundToInt(computedYPosition + top) % actualHeight : 0); |
+ } else { |
+ backgroundRepeatY = NoRepeatFill; |
+ } |
+ } |
+ if (backgroundRepeatY == NoRepeatFill) { |
+ int yOffset = fillLayer.backgroundYOrigin() == BottomEdge ? availableHeight - computedYPosition : computedYPosition; |
+ geometry.setNoRepeatY(top + yOffset); |
+ geometry.setSpaceSize(IntSize(geometry.spaceSize().width(), 0)); |
+ } |
+ |
+ if (fixedAttachment) |
+ geometry.useFixedAttachment(snappedPaintRect.location()); |
+ |
+ geometry.clip(snappedPaintRect); |
+ geometry.setDestOrigin(geometry.destRect().location()); |
+} |
+ |
+ |
+InterpolationQuality BoxPainter::chooseInterpolationQuality(GraphicsContext* context, Image* image, const void* layer, const LayoutSize& size) |
+{ |
+ return ImageQualityController::imageQualityController()->chooseInterpolationQuality(context, &m_renderBox, image, layer, size); |
+} |
+ |
+bool BoxPainter::fixedBackgroundPaintsInLocalCoordinates() const |
+{ |
+ if (!m_renderBox.isDocumentElement()) |
+ return false; |
+ |
+ if (m_renderBox.view()->frameView() && m_renderBox.view()->frameView()->paintBehavior() & PaintBehaviorFlattenCompositingLayers) |
+ return false; |
+ |
+ RenderLayer* rootLayer = m_renderBox.view()->layer(); |
+ if (!rootLayer || rootLayer->compositingState() == NotComposited) |
+ return false; |
+ |
+ return rootLayer->compositedLayerMapping()->backgroundLayerPaintsFixedRootBackground(); |
+} |
+ |
+static inline void applySubPixelHeuristicForTileSize(LayoutSize& tileSize, const IntSize& positioningAreaSize) |
+{ |
+ tileSize.setWidth(positioningAreaSize.width() - tileSize.width() <= 1 ? tileSize.width().ceil() : tileSize.width().floor()); |
+ tileSize.setHeight(positioningAreaSize.height() - tileSize.height() <= 1 ? tileSize.height().ceil() : tileSize.height().floor()); |
+} |
+ |
+IntSize BoxPainter::calculateFillTileSize(const FillLayer& fillLayer, const IntSize& positioningAreaSize) const |
+{ |
+ StyleImage* image = fillLayer.image(); |
+ EFillSizeType type = fillLayer.size().type; |
+ |
+ IntSize imageIntrinsicSize = m_renderBox.calculateImageIntrinsicDimensions(image, positioningAreaSize, RenderBoxModelObject::ScaleByEffectiveZoom); |
+ imageIntrinsicSize.scale(1 / image->imageScaleFactor(), 1 / image->imageScaleFactor()); |
+ switch (type) { |
+ case SizeLength: { |
+ 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.isPercent()) |
+ tileSize.setWidth(valueForLength(layerWidth, positioningAreaSize.width())); |
+ |
+ if (layerHeight.isFixed()) |
+ tileSize.setHeight(layerHeight.value()); |
+ else if (layerHeight.isPercent()) |
+ tileSize.setHeight(valueForLength(layerHeight, positioningAreaSize.height())); |
+ |
+ applySubPixelHeuristicForTileSize(tileSize, positioningAreaSize); |
+ |
+ // 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 flooredIntSize(tileSize); |
+ } |
+ case SizeNone: { |
+ // 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 = Contain; |
+ } |
+ case Contain: |
+ case Cover: { |
+ float horizontalScaleFactor = imageIntrinsicSize.width() |
+ ? static_cast<float>(positioningAreaSize.width()) / imageIntrinsicSize.width() : 1; |
+ float verticalScaleFactor = imageIntrinsicSize.height() |
+ ? static_cast<float>(positioningAreaSize.height()) / imageIntrinsicSize.height() : 1; |
+ float scaleFactor = type == Contain ? std::min(horizontalScaleFactor, verticalScaleFactor) : std::max(horizontalScaleFactor, verticalScaleFactor); |
+ return IntSize(std::max(1l, lround(imageIntrinsicSize.width() * scaleFactor)), std::max(1l, lround(imageIntrinsicSize.height() * scaleFactor))); |
+ } |
+ } |
+ |
+ ASSERT_NOT_REACHED(); |
+ return IntSize(); |
+} |
+ |
} // namespace blink |