blob: 20513104feab509ad995bedeba75979ec0995fee [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/paint/BoxPainter.h"
#include "core/HTMLNames.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLFrameOwnerElement.h"
#include "core/layout/ImageQualityController.h"
#include "core/layout/LayoutBox.h"
#include "core/layout/LayoutBoxModelObject.h"
#include "core/layout/LayoutObject.h"
#include "core/layout/LayoutTable.h"
#include "core/layout/LayoutTheme.h"
#include "core/layout/compositing/CompositedLayerMapping.h"
#include "core/layout/line/RootInlineBox.h"
#include "core/style/BorderEdge.h"
#include "core/style/ShadowList.h"
#include "core/paint/BackgroundImageGeometry.h"
#include "core/paint/BoxBorderPainter.h"
#include "core/paint/BoxDecorationData.h"
#include "core/paint/LayoutObjectDrawingRecorder.h"
#include "core/paint/NinePieceImagePainter.h"
#include "core/paint/ObjectPainter.h"
#include "core/paint/PaintInfo.h"
#include "core/paint/PaintLayer.h"
#include "core/paint/RoundedInnerRectClipper.h"
#include "core/paint/ScrollRecorder.h"
#include "core/paint/ThemePainter.h"
#include "platform/LengthFunctions.h"
#include "platform/geometry/LayoutPoint.h"
#include "platform/geometry/LayoutRectOutsets.h"
#include "platform/graphics/GraphicsContextStateSaver.h"
#include "platform/graphics/paint/CompositingDisplayItem.h"
#include "wtf/Optional.h"
namespace blink {
namespace {
bool isPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer(
const LayoutBoxModelObject* obj,
const PaintInfo& paintInfo) {
return paintInfo.paintFlags() & PaintLayerPaintingOverflowContents &&
!(paintInfo.paintFlags() &
PaintLayerPaintingCompositingBackgroundPhase) &&
obj == paintInfo.paintContainer();
}
} // namespace
void BoxPainter::paint(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) {
ObjectPainter(m_layoutBox).checkPaintOffset(paintInfo, paintOffset);
// Default implementation. Just pass paint through to the children.
paintChildren(paintInfo, paintOffset + m_layoutBox.location());
}
void BoxPainter::paintChildren(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) {
PaintInfo childInfo(paintInfo);
for (LayoutObject* child = m_layoutBox.slowFirstChild(); child;
child = child->nextSibling())
child->paint(childInfo, paintOffset);
}
void BoxPainter::paintBoxDecorationBackground(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) {
LayoutRect paintRect;
Optional<ScrollRecorder> scrollRecorder;
if (isPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer(
&m_layoutBox, paintInfo)) {
// For the case where we are painting the background into the scrolling
// contents layer of a composited scroller we need to include the entire
// overflow rect.
paintRect = m_layoutBox.layoutOverflowRect();
scrollRecorder.emplace(paintInfo.context, m_layoutBox, paintInfo.phase,
m_layoutBox.scrolledContentOffset());
// The background painting code assumes that the borders are part of the
// paintRect so we expand the paintRect by the border size when painting the
// background into the scrolling contents layer.
paintRect.expandEdges(LayoutUnit(m_layoutBox.borderTop()),
LayoutUnit(m_layoutBox.borderRight()),
LayoutUnit(m_layoutBox.borderBottom()),
LayoutUnit(m_layoutBox.borderLeft()));
} else {
paintRect = m_layoutBox.borderBoxRect();
}
paintRect.moveBy(paintOffset);
paintBoxDecorationBackgroundWithRect(paintInfo, paintOffset, paintRect);
}
LayoutRect BoxPainter::boundsForDrawingRecorder(
const PaintInfo& paintInfo,
const LayoutPoint& adjustedPaintOffset) {
LayoutRect bounds =
isPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer(
&m_layoutBox, paintInfo)
? m_layoutBox.layoutOverflowRect()
: m_layoutBox.selfVisualOverflowRect();
bounds.moveBy(adjustedPaintOffset);
return bounds;
}
namespace {
bool bleedAvoidanceIsClipping(BackgroundBleedAvoidance bleedAvoidance) {
return bleedAvoidance == BackgroundBleedClipOnly ||
bleedAvoidance == BackgroundBleedClipLayer;
}
} // anonymous namespace
// Sets a preferred composited raster scale for box with a background image,
// if possible.
// |srcRect| is the rect, in the space of the source image, to raster.
// |destRect| is the rect, in the local layout space of |obj|, to raster.
inline void updatePreferredRasterBoundsFromImage(
const FloatRect srcRect,
const FloatRect& destRect,
const LayoutBoxModelObject& obj) {
if (!RuntimeEnabledFeatures::preferredImageRasterBoundsEnabled())
return;
// Not yet implemented for SPv2.
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
return;
if (destRect.width() == 0.0f || destRect.height() == 0.0f)
return;
if (PaintLayer* paintLayer = obj.layer()) {
if (paintLayer->compositingState() != PaintsIntoOwnBacking)
return;
// TODO(chrishtr): ensure that this rounding does not ever lose any
// precision.
paintLayer->graphicsLayerBacking()->setPreferredRasterBounds(
roundedIntSize(srcRect.size()));
}
}
inline void clearPreferredRasterBounds(const LayoutBox& obj) {
if (!RuntimeEnabledFeatures::preferredImageRasterBoundsEnabled())
return;
if (PaintLayer* paintLayer = obj.layer()) {
if (paintLayer->compositingState() != PaintsIntoOwnBacking)
return;
paintLayer->graphicsLayerBacking()->clearPreferredRasterBounds();
}
}
void BoxPainter::paintBoxDecorationBackgroundWithRect(
const PaintInfo& paintInfo,
const LayoutPoint& paintOffset,
const LayoutRect& paintRect) {
bool paintingOverflowContents =
isPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer(
&m_layoutBox, paintInfo);
const ComputedStyle& style = m_layoutBox.styleRef();
Optional<DisplayItemCacheSkipper> cacheSkipper;
// Disable cache in under-invalidation checking mode for MediaSliderPart
// because we always paint using the latest data (buffered ranges, current
// time and duration) which may be different from the cached data.
if ((RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled() &&
style.appearance() == MediaSliderPart)
// We may paint a delayed-invalidation object before it's actually
// invalidated. Note this would be handled for us by
// LayoutObjectDrawingRecorder but we have to use DrawingRecorder as we
// may use the scrolling contents layer as DisplayItemClient below.
||
m_layoutBox.fullPaintInvalidationReason() ==
PaintInvalidationDelayedFull) {
cacheSkipper.emplace(paintInfo.context);
}
const DisplayItemClient& displayItemClient =
paintingOverflowContents ? static_cast<const DisplayItemClient&>(
*m_layoutBox.layer()
->compositedLayerMapping()
->scrollingContentsLayer())
: m_layoutBox;
if (DrawingRecorder::useCachedDrawingIfPossible(
paintInfo.context, displayItemClient,
DisplayItem::kBoxDecorationBackground))
return;
clearPreferredRasterBounds(m_layoutBox);
DrawingRecorder recorder(
paintInfo.context, displayItemClient,
DisplayItem::kBoxDecorationBackground,
FloatRect(boundsForDrawingRecorder(paintInfo, paintOffset)));
BoxDecorationData boxDecorationData(m_layoutBox);
GraphicsContextStateSaver stateSaver(paintInfo.context, false);
if (!paintingOverflowContents) {
// FIXME: Should eventually give the theme control over whether the box
// shadow should paint, since controls could have custom shadows of their
// own.
paintNormalBoxShadow(paintInfo, paintRect, style);
if (bleedAvoidanceIsClipping(boxDecorationData.bleedAvoidance)) {
stateSaver.save();
FloatRoundedRect border = style.getRoundedBorderFor(paintRect);
paintInfo.context.clipRoundedRect(border);
if (boxDecorationData.bleedAvoidance == BackgroundBleedClipLayer)
paintInfo.context.beginLayer();
}
}
// If we have a native theme appearance, paint that before painting our
// background. The theme will tell us whether or not we should also paint the
// CSS background.
IntRect snappedPaintRect(pixelSnappedIntRect(paintRect));
ThemePainter& themePainter = LayoutTheme::theme().painter();
bool themePainted =
boxDecorationData.hasAppearance &&
!themePainter.paint(m_layoutBox, paintInfo, snappedPaintRect);
bool shouldPaintBackground =
!themePainted && (!paintInfo.skipRootBackground() ||
paintInfo.paintContainer() != &m_layoutBox);
if (shouldPaintBackground) {
paintBackground(paintInfo, paintRect, boxDecorationData.backgroundColor,
boxDecorationData.bleedAvoidance);
if (boxDecorationData.hasAppearance)
themePainter.paintDecorations(m_layoutBox, paintInfo, snappedPaintRect);
}
if (!paintingOverflowContents) {
paintInsetBoxShadow(paintInfo, paintRect, style);
// The theme will tell us whether or not we should also paint the CSS
// border.
if (boxDecorationData.hasBorderDecoration &&
(!boxDecorationData.hasAppearance ||
(!themePainted &&
LayoutTheme::theme().painter().paintBorderOnly(m_layoutBox, paintInfo,
snappedPaintRect))) &&
!(m_layoutBox.isTable() &&
toLayoutTable(&m_layoutBox)->collapseBorders())) {
paintBorder(m_layoutBox, paintInfo, paintRect, style,
boxDecorationData.bleedAvoidance);
}
}
if (boxDecorationData.bleedAvoidance == BackgroundBleedClipLayer)
paintInfo.context.endLayer();
}
void BoxPainter::paintBackground(const PaintInfo& paintInfo,
const LayoutRect& paintRect,
const Color& backgroundColor,
BackgroundBleedAvoidance bleedAvoidance) {
if (m_layoutBox.isDocumentElement())
return;
if (m_layoutBox.backgroundStolenForBeingBody())
return;
if (m_layoutBox.backgroundIsKnownToBeObscured())
return;
paintFillLayers(paintInfo, backgroundColor,
m_layoutBox.style()->backgroundLayers(), paintRect,
bleedAvoidance);
}
bool BoxPainter::calculateFillLayerOcclusionCulling(
FillLayerOcclusionOutputList& reversedPaintList,
const FillLayer& fillLayer) {
bool isNonAssociative = false;
for (auto currentLayer = &fillLayer; currentLayer;
currentLayer = currentLayer->next()) {
reversedPaintList.append(currentLayer);
// 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
// LayoutBoxModelObject::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.
// TODO(trchen): Need to check compositing mode as well.
if (currentLayer->blendMode() != WebBlendModeNormal)
isNonAssociative = true;
// TODO(trchen): A fill layer cannot paint if the calculated tile size is
// empty. This occlusion check can be wrong.
if (currentLayer->clipOccludesNextLayers() &&
currentLayer->imageOccludesNextLayers(m_layoutBox)) {
if (currentLayer->clip() == BorderFillBox)
isNonAssociative = false;
break;
}
}
return isNonAssociative;
}
void BoxPainter::paintFillLayers(const PaintInfo& paintInfo,
const Color& c,
const FillLayer& fillLayer,
const LayoutRect& rect,
BackgroundBleedAvoidance bleedAvoidance,
SkBlendMode op,
const LayoutObject* backgroundObject) {
FillLayerOcclusionOutputList reversedPaintList;
bool shouldDrawBackgroundInSeparateBuffer =
calculateFillLayerOcclusionCulling(reversedPaintList, fillLayer);
// TODO(trchen): We can optimize out isolation group if we have a
// non-transparent background color and the bottom layer encloses all other
// layers.
GraphicsContext& context = paintInfo.context;
if (shouldDrawBackgroundInSeparateBuffer)
context.beginLayer();
for (auto it = reversedPaintList.rbegin(); it != reversedPaintList.rend();
++it)
paintFillLayer(m_layoutBox, paintInfo, c, **it, rect, bleedAvoidance, 0,
LayoutSize(), op, backgroundObject);
if (shouldDrawBackgroundInSeparateBuffer)
context.endLayer();
}
namespace {
FloatRoundedRect getBackgroundRoundedRect(const LayoutObject& obj,
const LayoutRect& borderRect,
const InlineFlowBox* box,
LayoutUnit inlineBoxWidth,
LayoutUnit inlineBoxHeight,
bool includeLogicalLeftEdge,
bool includeLogicalRightEdge) {
FloatRoundedRect border = obj.style()->getRoundedBorderFor(
borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
if (box && (box->nextLineBox() || box->prevLineBox())) {
FloatRoundedRect segmentBorder = obj.style()->getRoundedBorderFor(
LayoutRect(0, 0, inlineBoxWidth.toInt(), inlineBoxHeight.toInt()),
includeLogicalLeftEdge, includeLogicalRightEdge);
border.setRadii(segmentBorder.getRadii());
}
return border;
}
FloatRoundedRect backgroundRoundedRectAdjustedForBleedAvoidance(
const LayoutObject& obj,
const LayoutRect& borderRect,
BackgroundBleedAvoidance bleedAvoidance,
const InlineFlowBox* box,
const LayoutSize& boxSize,
bool includeLogicalLeftEdge,
bool includeLogicalRightEdge) {
if (bleedAvoidance == BackgroundBleedShrinkBackground) {
// Inset the background rect by a "safe" amount: 1/2 border-width for opaque
// border styles, 1/6 border-width for double borders.
// TODO(fmalita): we should be able to fold these parameters into
// BoxBorderInfo or BoxDecorationData and avoid calling getBorderEdgeInfo
// redundantly here.
BorderEdge edges[4];
obj.style()->getBorderEdgeInfo(edges, includeLogicalLeftEdge,
includeLogicalRightEdge);
// Use the most conservative inset to avoid mixed-style corner issues.
float fractionalInset = 1.0f / 2;
for (auto& edge : edges) {
if (edge.borderStyle() == BorderStyleDouble) {
fractionalInset = 1.0f / 6;
break;
}
}
FloatRectOutsets insets(-fractionalInset * edges[BSTop].width,
-fractionalInset * edges[BSRight].width,
-fractionalInset * edges[BSBottom].width,
-fractionalInset * edges[BSLeft].width);
FloatRoundedRect backgroundRoundedRect = getBackgroundRoundedRect(
obj, borderRect, box, boxSize.width(), boxSize.height(),
includeLogicalLeftEdge, includeLogicalRightEdge);
FloatRect insetRect(backgroundRoundedRect.rect());
insetRect.expand(insets);
FloatRoundedRect::Radii insetRadii(backgroundRoundedRect.getRadii());
insetRadii.shrink(-insets.top(), -insets.bottom(), -insets.left(),
-insets.right());
return FloatRoundedRect(insetRect, insetRadii);
}
return getBackgroundRoundedRect(obj, borderRect, box, boxSize.width(),
boxSize.height(), includeLogicalLeftEdge,
includeLogicalRightEdge);
}
struct FillLayerInfo {
STACK_ALLOCATED();
FillLayerInfo(const LayoutBoxModelObject& obj,
Color bgColor,
const FillLayer& layer,
BackgroundBleedAvoidance bleedAvoidance,
const InlineFlowBox* box)
: image(layer.image()),
color(bgColor),
includeLeftEdge(box ? box->includeLogicalLeftEdge() : true),
includeRightEdge(box ? box->includeLogicalRightEdge() : true),
isBottomLayer(!layer.next()),
isBorderFill(layer.clip() == BorderFillBox),
isClippedWithLocalScrolling(obj.hasOverflowClip() &&
layer.attachment() ==
LocalBackgroundAttachment) {
// 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 layout.)
if (BoxPainter::shouldForceWhiteBackgroundForPrintEconomy(obj.styleRef(),
obj.document())) {
// Note that we can't reuse this variable below because the bgColor might
// be changed.
bool shouldPaintBackgroundColor = isBottomLayer && color.alpha();
if (image || shouldPaintBackgroundColor) {
color = Color::white;
image = nullptr;
}
}
const bool hasRoundedBorder =
obj.style()->hasBorderRadius() && (includeLeftEdge || includeRightEdge);
// BorderFillBox radius clipping is taken care of by
// BackgroundBleedClip{Only,Layer}
isRoundedFill = hasRoundedBorder &&
!(isBorderFill && bleedAvoidanceIsClipping(bleedAvoidance));
shouldPaintImage = image && image->canRender();
shouldPaintColor =
isBottomLayer && color.alpha() &&
(!shouldPaintImage || !layer.imageOccludesNextLayers(obj));
}
// FillLayerInfo is a temporary, stack-allocated container which cannot
// outlive the StyleImage. This would normally be a raw pointer, if not for
// the Oilpan tooling complaints.
Member<StyleImage> image;
Color color;
bool includeLeftEdge;
bool includeRightEdge;
bool isBottomLayer;
bool isBorderFill;
bool isClippedWithLocalScrolling;
bool isRoundedFill;
bool shouldPaintImage;
bool shouldPaintColor;
};
// RAII image paint helper.
class ImagePaintContext {
STACK_ALLOCATED();
public:
ImagePaintContext(const LayoutBoxModelObject& obj,
GraphicsContext& context,
const FillLayer& layer,
const StyleImage& styleImage,
SkBlendMode op,
const LayoutObject* backgroundObject,
const LayoutSize& containerSize)
: m_context(context),
m_previousInterpolationQuality(context.imageInterpolationQuality()) {
SkBlendMode bgOp =
WebCoreCompositeToSkiaComposite(layer.composite(), layer.blendMode());
// if op != SkBlendMode::kSrcOver, a mask is being painted.
m_compositeOp = (op == SkBlendMode::kSrcOver) ? bgOp : op;
const LayoutObject& imageClient =
backgroundObject ? *backgroundObject : obj;
m_image = styleImage.image(imageClient, flooredIntSize(containerSize),
obj.style()->effectiveZoom());
m_interpolationQuality = BoxPainter::chooseInterpolationQuality(
imageClient, m_image.get(), &layer, containerSize);
if (m_interpolationQuality != m_previousInterpolationQuality)
context.setImageInterpolationQuality(m_interpolationQuality);
if (layer.maskSourceType() == MaskLuminance)
context.setColorFilter(ColorFilterLuminanceToAlpha);
}
~ImagePaintContext() {
if (m_interpolationQuality != m_previousInterpolationQuality)
m_context.setImageInterpolationQuality(m_previousInterpolationQuality);
}
Image* image() const { return m_image.get(); }
SkBlendMode compositeOp() const { return m_compositeOp; }
private:
RefPtr<Image> m_image;
GraphicsContext& m_context;
SkBlendMode m_compositeOp;
InterpolationQuality m_interpolationQuality;
InterpolationQuality m_previousInterpolationQuality;
};
inline bool paintFastBottomLayer(const LayoutBoxModelObject& obj,
const PaintInfo& paintInfo,
const FillLayerInfo& info,
const FillLayer& layer,
const LayoutRect& rect,
BackgroundBleedAvoidance bleedAvoidance,
const InlineFlowBox* box,
const LayoutSize& boxSize,
SkBlendMode op,
const LayoutObject* backgroundObject,
Optional<BackgroundImageGeometry>& geometry) {
// Complex cases not handled on the fast path.
if (!info.isBottomLayer || !info.isBorderFill ||
info.isClippedWithLocalScrolling)
return false;
// Transparent layer, nothing to paint.
if (!info.shouldPaintColor && !info.shouldPaintImage)
return true;
// When the layer has an image, figure out whether it is covered by a single
// tile.
FloatRect imageTile;
if (info.shouldPaintImage) {
DCHECK(!geometry);
geometry.emplace();
geometry->calculate(obj, paintInfo.paintContainer(),
paintInfo.getGlobalPaintFlags(), layer, rect);
if (!geometry->destRect().isEmpty()) {
// The tile is too small.
if (geometry->tileSize().width() < rect.width() ||
geometry->tileSize().height() < rect.height())
return false;
imageTile = Image::computeTileContaining(
FloatPoint(geometry->destRect().location()),
FloatSize(geometry->tileSize()), FloatPoint(geometry->phase()),
FloatSize(geometry->spaceSize()));
// The tile is misaligned.
if (!imageTile.contains(FloatRect(rect)))
return false;
}
}
// At this point we're committed to the fast path: the destination (r)rect
// fits within a single tile, and we can paint it using direct draw(R)Rect()
// calls.
GraphicsContext& context = paintInfo.context;
FloatRoundedRect border =
info.isRoundedFill ? backgroundRoundedRectAdjustedForBleedAvoidance(
obj, rect, bleedAvoidance, box, boxSize,
info.includeLeftEdge, info.includeRightEdge)
: FloatRoundedRect(pixelSnappedIntRect(rect));
Optional<RoundedInnerRectClipper> clipper;
if (info.isRoundedFill && !border.isRenderable()) {
// When the rrect is not renderable, we resort to clipping.
// RoundedInnerRectClipper handles this case via discrete, corner-wise
// clipping.
clipper.emplace(obj, paintInfo, rect, border, ApplyToContext);
border.setRadii(FloatRoundedRect::Radii());
}
// Paint the color if needed.
if (info.shouldPaintColor)
context.fillRoundedRect(border, info.color);
// Paint the image if needed.
if (!info.shouldPaintImage || imageTile.isEmpty())
return true;
const ImagePaintContext imageContext(obj, context, layer, *info.image, op,
backgroundObject, geometry->tileSize());
if (!imageContext.image())
return true;
const FloatSize intrinsicTileSize =
imageContext.image()->hasRelativeSize()
? imageTile.size()
: FloatSize(imageContext.image()->size());
const FloatRect srcRect =
Image::computeSubsetForTile(imageTile, border.rect(), intrinsicTileSize);
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage",
"data", InspectorPaintImageEvent::data(obj, *info.image));
context.drawImageRRect(imageContext.image(), border, srcRect,
imageContext.compositeOp());
updatePreferredRasterBoundsFromImage(srcRect, border.rect(), obj);
return true;
}
} // anonymous namespace
void BoxPainter::paintFillLayer(const LayoutBoxModelObject& obj,
const PaintInfo& paintInfo,
const Color& color,
const FillLayer& bgLayer,
const LayoutRect& rect,
BackgroundBleedAvoidance bleedAvoidance,
const InlineFlowBox* box,
const LayoutSize& boxSize,
SkBlendMode op,
const LayoutObject* backgroundObject) {
GraphicsContext& context = paintInfo.context;
if (rect.isEmpty())
return;
const FillLayerInfo info(obj, color, bgLayer, bleedAvoidance, box);
Optional<BackgroundImageGeometry> geometry;
// Fast path for drawing simple color backgrounds.
if (paintFastBottomLayer(obj, paintInfo, info, bgLayer, rect, bleedAvoidance,
box, boxSize, op, backgroundObject, geometry)) {
return;
}
Optional<RoundedInnerRectClipper> clipToBorder;
if (info.isRoundedFill) {
FloatRoundedRect border =
info.isBorderFill
? backgroundRoundedRectAdjustedForBleedAvoidance(
obj, rect, bleedAvoidance, box, boxSize, info.includeLeftEdge,
info.includeRightEdge)
: getBackgroundRoundedRect(obj, rect, box, boxSize.width(),
boxSize.height(), info.includeLeftEdge,
info.includeRightEdge);
// Clip to the padding or content boxes as necessary.
if (bgLayer.clip() == ContentFillBox) {
border = obj.style()->getRoundedInnerBorderFor(
LayoutRect(border.rect()),
LayoutRectOutsets(-(obj.paddingTop() + obj.borderTop()),
-(obj.paddingRight() + obj.borderRight()),
-(obj.paddingBottom() + obj.borderBottom()),
-(obj.paddingLeft() + obj.borderLeft())),
info.includeLeftEdge, info.includeRightEdge);
} else if (bgLayer.clip() == PaddingFillBox) {
border = obj.style()->getRoundedInnerBorderFor(LayoutRect(border.rect()),
info.includeLeftEdge,
info.includeRightEdge);
}
clipToBorder.emplace(obj, paintInfo, rect, border, ApplyToContext);
}
int bLeft = info.includeLeftEdge ? obj.borderLeft() : 0;
int bRight = info.includeRightEdge ? obj.borderRight() : 0;
LayoutUnit pLeft = info.includeLeftEdge ? obj.paddingLeft() : LayoutUnit();
LayoutUnit pRight = info.includeRightEdge ? obj.paddingRight() : LayoutUnit();
GraphicsContextStateSaver clipWithScrollingStateSaver(
context, info.isClippedWithLocalScrolling);
LayoutRect scrolledPaintRect = rect;
if (info.isClippedWithLocalScrolling &&
!isPaintingBackgroundOfPaintContainerIntoScrollingContentsLayer(
&obj, paintInfo)) {
// Clip to the overflow area.
const LayoutBox& thisBox = toLayoutBox(obj);
// TODO(chrishtr): this should be pixel-snapped.
context.clip(FloatRect(thisBox.overflowClipRect(rect.location())));
// Adjust the paint rect to reflect a scrolled content box with borders at
// the ends.
IntSize offset = thisBox.scrolledContentOffset();
scrolledPaintRect.move(-offset);
scrolledPaintRect.setWidth(bLeft + thisBox.scrollWidth() + bRight);
scrolledPaintRect.setHeight(thisBox.borderTop() + thisBox.scrollHeight() +
thisBox.borderBottom());
}
GraphicsContextStateSaver backgroundClipStateSaver(context, false);
IntRect maskRect;
switch (bgLayer.clip()) {
case PaddingFillBox:
case ContentFillBox: {
if (info.isRoundedFill)
break;
// Clip to the padding or content boxes as necessary.
bool includePadding = bgLayer.clip() == ContentFillBox;
LayoutRect clipRect(
scrolledPaintRect.x() + bLeft +
(includePadding ? pLeft : LayoutUnit()),
scrolledPaintRect.y() + obj.borderTop() +
(includePadding ? obj.paddingTop() : LayoutUnit()),
scrolledPaintRect.width() - bLeft - bRight -
(includePadding ? pLeft + pRight : LayoutUnit()),
scrolledPaintRect.height() - obj.borderTop() - obj.borderBottom() -
(includePadding ? obj.paddingTop() + obj.paddingBottom()
: LayoutUnit()));
backgroundClipStateSaver.save();
// TODO(chrishtr): this should be pixel-snapped.
context.clip(FloatRect(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);
// 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.beginLayer();
break;
}
case BorderFillBox:
break;
default:
ASSERT_NOT_REACHED();
break;
}
// Paint the color first underneath all images, culled if background image
// occludes it.
// TODO(trchen): In the !bgLayer.hasRepeatXY() case, we could improve the
// culling test by verifying whether the background image covers the entire
// painting area.
if (info.isBottomLayer && info.color.alpha() && info.shouldPaintColor) {
IntRect backgroundRect(pixelSnappedIntRect(scrolledPaintRect));
context.fillRect(backgroundRect, info.color);
}
// no progressive loading of the background image
if (info.shouldPaintImage) {
if (!geometry) {
geometry.emplace();
geometry->calculate(obj, paintInfo.paintContainer(),
paintInfo.getGlobalPaintFlags(), bgLayer,
scrolledPaintRect);
} else {
// The geometry was calculated in paintFastBottomLayer().
DCHECK(info.isBottomLayer && info.isBorderFill &&
!info.isClippedWithLocalScrolling);
}
if (!geometry->destRect().isEmpty()) {
const ImagePaintContext imageContext(obj, context, bgLayer, *info.image,
op, backgroundObject,
geometry->tileSize());
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage",
"data", InspectorPaintImageEvent::data(obj, *info.image));
context.drawTiledImage(
imageContext.image(), FloatRect(geometry->destRect()),
FloatPoint(geometry->phase()), FloatSize(geometry->tileSize()),
imageContext.compositeOp(), FloatSize(geometry->spaceSize()));
}
}
if (bgLayer.clip() == TextFillBox) {
// Create the text mask layer.
context.beginLayer(1, SkBlendMode::kDstIn);
// 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,
GlobalPaintNormalPhase, 0);
if (box) {
const RootInlineBox& root = box->root();
box->paint(info, LayoutPoint(scrolledPaintRect.x() - box->x(),
scrolledPaintRect.y() - box->y()),
root.lineTop(), root.lineBottom());
} else {
// FIXME: this should only have an effect for the line box list within
// |obj|. Change this to create a LineBoxListPainter directly.
LayoutSize localOffset =
obj.isBox() ? toLayoutBox(&obj)->locationOffset() : LayoutSize();
obj.paint(info, scrolledPaintRect.location() - localOffset);
}
context.endLayer();
context.endLayer();
}
}
void BoxPainter::paintMask(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) {
if (m_layoutBox.style()->visibility() != EVisibility::Visible ||
paintInfo.phase != PaintPhaseMask)
return;
if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(
paintInfo.context, m_layoutBox, paintInfo.phase))
return;
LayoutRect visualOverflowRect(m_layoutBox.visualOverflowRect());
visualOverflowRect.moveBy(paintOffset);
LayoutObjectDrawingRecorder recorder(paintInfo.context, m_layoutBox,
paintInfo.phase, visualOverflowRect);
LayoutRect paintRect = LayoutRect(paintOffset, m_layoutBox.size());
paintMaskImages(paintInfo, paintRect);
}
void BoxPainter::paintMaskImages(const PaintInfo& paintInfo,
const LayoutRect& paintRect) {
// Figure out if we need to push a transparency layer to render our mask.
bool pushTransparencyLayer = false;
bool compositedMask =
m_layoutBox.hasLayer() && m_layoutBox.layer()->hasCompositedMask();
bool flattenCompositingLayers =
paintInfo.getGlobalPaintFlags() & GlobalPaintFlattenCompositingLayers;
bool allMaskImagesLoaded = true;
if (!compositedMask || flattenCompositingLayers) {
pushTransparencyLayer = true;
StyleImage* maskBoxImage = m_layoutBox.style()->maskBoxImage().image();
const FillLayer& maskLayers = m_layoutBox.style()->maskLayers();
// Don't render a masked element until all the mask images have loaded, to
// prevent a flash of unmasked content.
if (maskBoxImage)
allMaskImagesLoaded &= maskBoxImage->isLoaded();
allMaskImagesLoaded &= maskLayers.imagesAreLoaded();
paintInfo.context.beginLayer(1, SkBlendMode::kDstIn);
}
if (allMaskImagesLoaded) {
paintFillLayers(paintInfo, Color::transparent,
m_layoutBox.style()->maskLayers(), paintRect);
paintNinePieceImage(m_layoutBox, paintInfo.context, paintRect,
m_layoutBox.styleRef(),
m_layoutBox.style()->maskBoxImage());
}
if (pushTransparencyLayer)
paintInfo.context.endLayer();
}
void BoxPainter::paintClippingMask(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) {
DCHECK(paintInfo.phase == PaintPhaseClippingMask);
if (m_layoutBox.style()->visibility() != EVisibility::Visible)
return;
if (!m_layoutBox.layer() ||
m_layoutBox.layer()->compositingState() != PaintsIntoOwnBacking)
return;
if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(
paintInfo.context, m_layoutBox, paintInfo.phase))
return;
IntRect paintRect =
pixelSnappedIntRect(LayoutRect(paintOffset, m_layoutBox.size()));
LayoutObjectDrawingRecorder drawingRecorder(paintInfo.context, m_layoutBox,
paintInfo.phase, paintRect);
paintInfo.context.fillRect(paintRect, Color::black);
}
InterpolationQuality BoxPainter::chooseInterpolationQuality(
const LayoutObject& obj,
Image* image,
const void* layer,
const LayoutSize& size) {
return ImageQualityController::imageQualityController()
->chooseInterpolationQuality(obj, image, layer, size);
}
bool BoxPainter::paintNinePieceImage(const LayoutBoxModelObject& obj,
GraphicsContext& graphicsContext,
const LayoutRect& rect,
const ComputedStyle& style,
const NinePieceImage& ninePieceImage,
SkBlendMode op) {
NinePieceImagePainter ninePieceImagePainter(obj);
return ninePieceImagePainter.paint(graphicsContext, rect, style,
ninePieceImage, op);
}
void BoxPainter::paintBorder(const LayoutBoxModelObject& obj,
const PaintInfo& info,
const LayoutRect& rect,
const ComputedStyle& style,
BackgroundBleedAvoidance bleedAvoidance,
bool includeLogicalLeftEdge,
bool includeLogicalRightEdge) {
// border-image is not affected by border-radius.
if (paintNinePieceImage(obj, info.context, rect, style, style.borderImage()))
return;
const BoxBorderPainter borderPainter(rect, style, bleedAvoidance,
includeLogicalLeftEdge,
includeLogicalRightEdge);
borderPainter.paintBorder(info, rect);
}
void BoxPainter::paintNormalBoxShadow(const PaintInfo& info,
const LayoutRect& paintRect,
const ComputedStyle& style,
bool includeLogicalLeftEdge,
bool includeLogicalRightEdge) {
if (!style.boxShadow())
return;
GraphicsContext& context = info.context;
FloatRoundedRect border = style.getRoundedBorderFor(
paintRect, includeLogicalLeftEdge, includeLogicalRightEdge);
bool hasBorderRadius = style.hasBorderRadius();
bool hasOpaqueBackground =
style.visitedDependentColor(CSSPropertyBackgroundColor).alpha() == 255;
GraphicsContextStateSaver stateSaver(context, false);
const ShadowList* shadowList = style.boxShadow();
for (size_t i = shadowList->shadows().size(); i--;) {
const ShadowData& shadow = shadowList->shadows()[i];
if (shadow.style() != Normal)
continue;
FloatSize shadowOffset(shadow.x(), shadow.y());
float shadowBlur = shadow.blur();
float shadowSpread = shadow.spread();
if (shadowOffset.isZero() && !shadowBlur && !shadowSpread)
continue;
const Color& shadowColor =
shadow.color().resolve(style.visitedDependentColor(CSSPropertyColor));
FloatRect fillRect = border.rect();
fillRect.inflate(shadowSpread);
if (fillRect.isEmpty())
continue;
FloatRect shadowRect(border.rect());
shadowRect.inflate(shadowBlur + shadowSpread);
shadowRect.move(shadowOffset);
// Save the state and clip, if not already done.
// The clip does not depend on any shadow-specific properties.
if (!stateSaver.saved()) {
stateSaver.save();
if (hasBorderRadius) {
FloatRoundedRect rectToClipOut = border;
// If the box is opaque, it is unnecessary to clip it out. However,
// doing so saves time when painting the shadow. On the other hand, it
// introduces subpixel gaps along the corners. Those are avoided by
// insetting the clipping path by one CSS pixel.
if (hasOpaqueBackground)
rectToClipOut.inflateWithRadii(-1);
if (!rectToClipOut.isEmpty())
context.clipOutRoundedRect(rectToClipOut);
} else {
// This IntRect is correct even with fractional shadows, because it is
// used for the rectangle of the box itself, which is always
// pixel-aligned.
FloatRect rectToClipOut = border.rect();
// If the box is opaque, it is unnecessary to clip it out. However,
// doing so saves time when painting the shadow. On the other hand, it
// introduces subpixel gaps along the edges if they are not
// pixel-aligned. Those are avoided by insetting the clipping path by
// one CSS pixel.
if (hasOpaqueBackground)
rectToClipOut.inflate(-1);
if (!rectToClipOut.isEmpty())
context.clipOut(rectToClipOut);
}
}
// Draw only the shadow.
context.setShadow(shadowOffset, shadowBlur, shadowColor,
DrawLooperBuilder::ShadowRespectsTransforms,
DrawLooperBuilder::ShadowIgnoresAlpha, DrawShadowOnly);
if (hasBorderRadius) {
FloatRoundedRect influenceRect(
pixelSnappedIntRect(LayoutRect(shadowRect)), border.getRadii());
float changeAmount = 2 * shadowBlur + shadowSpread;
if (changeAmount >= 0)
influenceRect.expandRadii(changeAmount);
else
influenceRect.shrinkRadii(-changeAmount);
FloatRoundedRect roundedFillRect = border;
roundedFillRect.inflate(shadowSpread);
if (shadowSpread >= 0)
roundedFillRect.expandRadii(shadowSpread);
else
roundedFillRect.shrinkRadii(-shadowSpread);
if (!roundedFillRect.isRenderable())
roundedFillRect.adjustRadii();
roundedFillRect.constrainRadii();
context.fillRoundedRect(roundedFillRect, Color::black);
} else {
context.fillRect(fillRect, Color::black);
}
}
}
void BoxPainter::paintInsetBoxShadow(const PaintInfo& info,
const LayoutRect& paintRect,
const ComputedStyle& style,
bool includeLogicalLeftEdge,
bool includeLogicalRightEdge) {
if (!style.boxShadow())
return;
FloatRoundedRect bounds = style.getRoundedInnerBorderFor(
paintRect, includeLogicalLeftEdge, includeLogicalRightEdge);
paintInsetBoxShadowInBounds(info, bounds, style, includeLogicalLeftEdge,
includeLogicalRightEdge);
}
void BoxPainter::paintInsetBoxShadowInBounds(const PaintInfo& info,
const FloatRoundedRect& bounds,
const ComputedStyle& style,
bool includeLogicalLeftEdge,
bool includeLogicalRightEdge) {
// The caller should have checked style.boxShadow() when computing bounds.
DCHECK(style.boxShadow());
GraphicsContext& context = info.context;
bool isHorizontal = style.isHorizontalWritingMode();
GraphicsContextStateSaver stateSaver(context, false);
const ShadowList* shadowList = style.boxShadow();
for (size_t i = shadowList->shadows().size(); i--;) {
const ShadowData& shadow = shadowList->shadows()[i];
if (shadow.style() != Inset)
continue;
FloatSize shadowOffset(shadow.x(), shadow.y());
float shadowBlur = shadow.blur();
float shadowSpread = shadow.spread();
if (shadowOffset.isZero() && !shadowBlur && !shadowSpread)
continue;
const Color& shadowColor =
shadow.color().resolve(style.visitedDependentColor(CSSPropertyColor));
// The inset shadow case.
GraphicsContext::Edges clippedEdges = GraphicsContext::NoEdge;
if (!includeLogicalLeftEdge) {
if (isHorizontal)
clippedEdges |= GraphicsContext::LeftEdge;
else
clippedEdges |= GraphicsContext::TopEdge;
}
if (!includeLogicalRightEdge) {
if (isHorizontal)
clippedEdges |= GraphicsContext::RightEdge;
else
clippedEdges |= GraphicsContext::BottomEdge;
}
context.drawInnerShadow(bounds, shadowColor, shadowOffset, shadowBlur,
shadowSpread, clippedEdges);
}
}
bool BoxPainter::shouldForceWhiteBackgroundForPrintEconomy(
const ComputedStyle& style,
const Document& document) {
return document.printing() &&
style.printColorAdjust() == EPrintColorAdjust::Economy &&
(!document.settings() ||
!document.settings()->shouldPrintBackgrounds());
}
} // namespace blink