blob: e503a8b45f5debba62ffc9eee8d40210549360e3 [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 "third_party/blink/renderer/core/paint/box_painter_base.h"
#include "base/optional.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/paint/background_image_geometry.h"
#include "third_party/blink/renderer/core/paint/box_border_painter.h"
#include "third_party/blink/renderer/core/paint/nine_piece_image_painter.h"
#include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/rounded_inner_rect_clipper.h"
#include "third_party/blink/renderer/core/style/border_edge.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/style/shadow_list.h"
#include "third_party/blink/renderer/platform/geometry/layout_rect.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
#include "third_party/blink/renderer/platform/graphics/scoped_interpolation_quality.h"
namespace blink {
void BoxPainterBase::PaintFillLayers(const PaintInfo& paint_info,
const Color& c,
const FillLayer& fill_layer,
const LayoutRect& rect,
BackgroundImageGeometry& geometry,
BackgroundBleedAvoidance bleed) {
FillLayerOcclusionOutputList reversed_paint_list;
bool should_draw_background_in_separate_buffer =
CalculateFillLayerOcclusionCulling(reversed_paint_list, fill_layer);
// 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 = paint_info.context;
if (should_draw_background_in_separate_buffer)
for (auto it = reversed_paint_list.rbegin(); it != reversed_paint_list.rend();
++it) {
PaintFillLayer(paint_info, c, **it, rect, bleed, geometry);
if (should_draw_background_in_separate_buffer)
void BoxPainterBase::PaintNormalBoxShadow(const PaintInfo& info,
const LayoutRect& paint_rect,
const ComputedStyle& style,
bool include_logical_left_edge,
bool include_logical_right_edge,
bool background_is_skipped) {
if (!style.BoxShadow())
GraphicsContext& context = info.context;
FloatRoundedRect border = style.GetRoundedBorderFor(
paint_rect, include_logical_left_edge, include_logical_right_edge);
bool has_border_radius = style.HasBorderRadius();
bool has_opaque_background =
!background_is_skipped &&
style.VisitedDependentColor(GetCSSPropertyBackgroundColor()).Alpha() ==
GraphicsContextStateSaver state_saver(context, false);
const ShadowList* shadow_list = style.BoxShadow();
for (wtf_size_t i = shadow_list->Shadows().size(); i--;) {
const ShadowData& shadow = shadow_list->Shadows()[i];
if (shadow.Style() != kNormal)
FloatSize shadow_offset(shadow.X(), shadow.Y());
float shadow_blur = shadow.Blur();
float shadow_spread = shadow.Spread();
if (shadow_offset.IsZero() && !shadow_blur && !shadow_spread)
const Color& shadow_color = shadow.GetColor().Resolve(
FloatRect fill_rect = border.Rect();
if (fill_rect.IsEmpty())
// Save the state and clip, if not already done.
// The clip does not depend on any shadow-specific properties.
if (!state_saver.Saved()) {
if (has_border_radius) {
FloatRoundedRect rect_to_clip_out = 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 (has_opaque_background)
if (!rect_to_clip_out.IsEmpty())
} 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 rect_to_clip_out = 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 (has_opaque_background)
if (!rect_to_clip_out.IsEmpty())
// Draw only the shadow.
context.SetShadow(shadow_offset, shadow_blur, shadow_color,
DrawLooperBuilder::kShadowIgnoresAlpha, kDrawShadowOnly);
if (has_border_radius) {
FloatRoundedRect rounded_fill_rect = border;
if (shadow_spread >= 0)
if (!rounded_fill_rect.IsRenderable())
context.FillRoundedRect(rounded_fill_rect, Color::kBlack);
} else {
context.FillRect(fill_rect, Color::kBlack);
void BoxPainterBase::PaintInsetBoxShadowWithBorderRect(
const PaintInfo& info,
const LayoutRect& border_rect,
const ComputedStyle& style,
bool include_logical_left_edge,
bool include_logical_right_edge) {
if (!style.BoxShadow())
auto bounds = style.GetRoundedInnerBorderFor(
border_rect, include_logical_left_edge, include_logical_right_edge);
PaintInsetBoxShadow(info, bounds, style, include_logical_left_edge,
void BoxPainterBase::PaintInsetBoxShadowWithInnerRect(
const PaintInfo& info,
const LayoutRect& inner_rect,
const ComputedStyle& style) {
if (!style.BoxShadow())
auto bounds = style.GetRoundedInnerBorderFor(inner_rect, LayoutRectOutsets());
PaintInsetBoxShadow(info, bounds, style);
void BoxPainterBase::PaintInsetBoxShadow(const PaintInfo& info,
const FloatRoundedRect& bounds,
const ComputedStyle& style,
bool include_logical_left_edge,
bool include_logical_right_edge) {
GraphicsContext& context = info.context;
bool is_horizontal = style.IsHorizontalWritingMode();
GraphicsContextStateSaver state_saver(context, false);
const ShadowList* shadow_list = style.BoxShadow();
for (wtf_size_t i = shadow_list->Shadows().size(); i--;) {
const ShadowData& shadow = shadow_list->Shadows()[i];
if (shadow.Style() != kInset)
FloatSize shadow_offset(shadow.X(), shadow.Y());
float shadow_blur = shadow.Blur();
float shadow_spread = shadow.Spread();
if (shadow_offset.IsZero() && !shadow_blur && !shadow_spread)
const Color& shadow_color = shadow.GetColor().Resolve(
// The inset shadow case.
GraphicsContext::Edges clipped_edges = GraphicsContext::kNoEdge;
if (!include_logical_left_edge) {
if (is_horizontal)
clipped_edges |= GraphicsContext::kLeftEdge;
clipped_edges |= GraphicsContext::kTopEdge;
if (!include_logical_right_edge) {
if (is_horizontal)
clipped_edges |= GraphicsContext::kRightEdge;
clipped_edges |= GraphicsContext::kBottomEdge;
context.DrawInnerShadow(bounds, shadow_color, shadow_offset, shadow_blur,
shadow_spread, clipped_edges);
bool BoxPainterBase::ShouldForceWhiteBackgroundForPrintEconomy(
const Document& document,
const ComputedStyle& style) {
return document.Printing() &&
style.PrintColorAdjust() == EPrintColorAdjust::kEconomy &&
(!document.GetSettings() ||
bool BoxPainterBase::CalculateFillLayerOcclusionCulling(
FillLayerOcclusionOutputList& reversed_paint_list,
const FillLayer& fill_layer) {
bool is_non_associative = false;
for (auto* current_layer = &fill_layer; current_layer;
current_layer = current_layer->Next()) {
// 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 (current_layer->GetBlendMode() != BlendMode::kNormal)
is_non_associative = true;
// TODO(trchen): A fill layer cannot paint if the calculated tile size is
// empty. This occlusion check can be wrong.
if (current_layer->ClipOccludesNextLayers() &&
current_layer->ImageOccludesNextLayers(*document_, style_)) {
if (current_layer->Clip() == EFillBox::kBorder)
is_non_associative = false;
return is_non_associative;
const Document& doc,
const ComputedStyle& style,
bool has_overflow_clip,
Color bg_color,
const FillLayer& layer,
BackgroundBleedAvoidance bleed_avoidance,
bool include_left,
bool include_right)
: image(layer.GetImage()),
is_border_fill(layer.Clip() == EFillBox::kBorder),
has_overflow_clip && layer.Attachment() == EFillAttachment::kLocal) {
// 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 (BoxPainterBase::ShouldForceWhiteBackgroundForPrintEconomy(doc, style)) {
// Note that we can't reuse this variable below because the bgColor might
// be changed.
bool should_paint_background_color = is_bottom_layer && color.Alpha();
if (image || should_paint_background_color) {
color = Color::kWhite;
image = nullptr;
const bool has_rounded_border =
style.HasBorderRadius() && (include_left_edge || include_right_edge);
// BorderFillBox radius clipping is taken care of by
// BackgroundBleedClip{Only,Layer}
is_rounded_fill =
has_rounded_border &&
!(is_border_fill && BleedAvoidanceIsClipping(bleed_avoidance));
should_paint_image = image && image->CanRender();
should_paint_color =
is_bottom_layer && color.Alpha() &&
(!should_paint_image || !layer.ImageOccludesNextLayers(doc, style));
namespace {
// Given the |size| that the whole image should draw at, and the input phase
// requested by the content, and the space between repeated tiles, return a
// rectangle with |size| and a location that respects the phase but is no more
// than one size + space in magnitude. In practice, this means that if there is
// no repeating the returned rect would contain the destination_offset
// location. The destination_offset passed here must exactly match the location
// of the subset in a following call to ComputeSubsetForBackground.
FloatRect ComputePhaseForBackground(const FloatPoint& destination_offset,
const FloatSize& size,
const FloatPoint& phase,
const FloatSize& spacing) {
const FloatSize step_per_tile(size + spacing);
return FloatRect(
destination_offset.X() + fmodf(-phase.X(), step_per_tile.Width()),
destination_offset.Y() + fmodf(-phase.Y(), step_per_tile.Height())),
// Compute the image subset, in intrinsic image coordinates, that gets mapped
// onto the |subset|, when the whole image would be drawn with phase and size
// given by |phase_and_size|. Assumes |phase_and_size| contains |subset|. The
// location of the requested subset should be the painting snapped location, or
// whatever was used as a destination_offset in ComputePhaseForBackground.
// It is used to undo the offset added in ComputePhaseForBackground. The size
// of requested subset should be the unsnapped size so that the computed
// scale and location in the source image can be correctly determined.
FloatRect ComputeSubsetForBackground(const FloatRect& phase_and_size,
const FloatRect& subset,
const FloatSize& intrinsic_size) {
// TODO(schenney): Re-enable this after determining why it fails for
// CAP, and maybe other cases.
// DCHECK(phase_and_size.Contains(subset));
const FloatSize scale(phase_and_size.Width() / intrinsic_size.Width(),
phase_and_size.Height() / intrinsic_size.Height());
return FloatRect((subset.X() - phase_and_size.X()) / scale.Width(),
(subset.Y() - phase_and_size.Y()) / scale.Height(),
subset.Width() / scale.Width(),
subset.Height() / scale.Height());
// The unsnapped_subset_size should be the target painting area implied by the
// content, without any snapping applied. It is necessary to correctly
// compute the subset of the source image to paint into the destination.
// The snapped_paint_rect should be the target destination for painting into.
// The phase is never snapped.
// The tile_size is the total image size. The mapping from this size
// to the unsnapped_dest_rect size defines the scaling of the image for
// sprite computation.
void DrawTiledBackground(GraphicsContext& context,
Image* image,
const FloatSize& unsnapped_subset_size,
const FloatRect& snapped_paint_rect,
const FloatPoint& phase,
const FloatSize& tile_size,
SkBlendMode op,
const FloatSize& repeat_spacing) {
// Use the intrinsic size of the image if it has one, otherwise force the
// generated image to be the tile size.
FloatSize intrinsic_tile_size(image->Size());
FloatSize scale(1, 1);
if (image->HasRelativeSize()) {
intrinsic_tile_size = tile_size;
} else {
scale = FloatSize(tile_size.Width() / intrinsic_tile_size.Width(),
tile_size.Height() / intrinsic_tile_size.Height());
const FloatRect one_tile_rect = ComputePhaseForBackground(
snapped_paint_rect.Location(), tile_size, phase, repeat_spacing);
// Check and see if a single draw of the image can cover the entire area we
// are supposed to tile. The dest_rect_for_subset must use the same
// location that was used in ComputePhaseForBackground and the unsnapped
// destination rect in order to correctly evaluate the subset size and
// location in the presence of border snapping and zoom.
FloatRect dest_rect_for_subset(snapped_paint_rect.Location(),
if (one_tile_rect.Contains(dest_rect_for_subset)) {
FloatRect visible_src_rect = ComputeSubsetForBackground(
one_tile_rect, dest_rect_for_subset, intrinsic_tile_size);
// Round to avoid filtering pulling in neighboring pixels, for the
// common case of sprite maps.
// TODO(schenney): Snapping at this level is a problem for cases where we
// might be animating background-position to pan over an image. Ideally we
// would either snap only if close to integral, or move snapping
// calculations up the stack.
visible_src_rect = FloatRect(RoundedIntRect(visible_src_rect));
context.DrawImage(image, Image::kSyncDecode, snapped_paint_rect,
&visible_src_rect, op, kDoNotRespectImageOrientation);
// Note that this tile rect the image's pre-scaled size.
FloatRect tile_rect(FloatPoint(), intrinsic_tile_size);
// This call takes the unscaled image, applies the given scale, and paints
// it into the snapped_dest_rect using phase from one_tile_rect and the
// given repeat spacing. Note the phase is already scaled.
context.DrawImageTiled(image, snapped_paint_rect, tile_rect, scale,
one_tile_rect.Location(), repeat_spacing, op);
inline bool PaintFastBottomLayer(Node* node,
const PaintInfo& paint_info,
const BoxPainterBase::FillLayerInfo& info,
const LayoutRect& rect,
const FloatRoundedRect& border_rect,
BackgroundImageGeometry& geometry,
Image* image,
SkBlendMode composite_op) {
// Painting a background image from an ancestor onto a cell is a complex case.
if (geometry.CellUsingContainerBackground())
return false;
// Complex cases not handled on the fast path.
if (!info.is_bottom_layer || !info.is_border_fill)
return false;
// Transparent layer, nothing to paint.
if (!info.should_paint_color && !info.should_paint_image)
return true;
// Compute the destination rect for painting the color here because we may
// need it for computing the image painting rect for optimization.
GraphicsContext& context = paint_info.context;
FloatRoundedRect color_border =
info.is_rounded_fill ? border_rect
: FloatRoundedRect(PixelSnappedIntRect(rect));
// When the layer has an image, figure out whether it is covered by a single
// tile. The border for painting images may not be the same as the color due
// to optimizations for the image painting destination that avoid painting
// under the border.
FloatRect image_tile;
FloatRoundedRect image_border;
if (info.should_paint_image) {
// Avoid image shaders when printing (poorly supported in PDF).
if (info.is_rounded_fill && paint_info.IsPrinting())
return false;
// Compute the dest rect we will be using for images.
image_border =
? color_border
: FloatRoundedRect(FloatRect(geometry.SnappedDestRect()));
if (!image_border.Rect().IsEmpty()) {
// We cannot optimize if the tile is too small.
if (geometry.TileSize().Width() < image_border.Rect().Width() ||
geometry.TileSize().Height() < image_border.Rect().Height())
return false;
// Phase calculation uses the actual painted location, given by the
// border-snapped destination rect.
image_tile = ComputePhaseForBackground(
FloatSize(geometry.TileSize()), geometry.Phase(),
// Force the image tile to LayoutUnit precision, which is the precision
// it was calcuated in. This avoids bleeding due to values very close to
// integers.
// The test images/sprite-no-bleed.html fails on two of the sub-cases
// due to this rounding still not being enough to make the Contains check
// pass. The best way to fix this would be to remove the paint rect offset
// from the tile computation, because we effectively add it in
// ComputePhaseForBackground then remove it in ComputeSubsetForBackground.
image_tile = FloatRect(LayoutRect(image_tile));
// We cannot optimize if the tile is misaligned.
if (!image_tile.Contains(image_border.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.
base::Optional<RoundedInnerRectClipper> clipper;
if (info.is_rounded_fill && !color_border.IsRenderable()) {
// When the rrect is not renderable, we resort to clipping.
// RoundedInnerRectClipper handles this case via discrete, corner-wise
// clipping.
clipper.emplace(context, rect, color_border);
// Paint the color if needed.
if (info.should_paint_color)
context.FillRoundedRect(color_border, info.color);
// Paint the image if needed.
if (!info.should_paint_image || !image || image_tile.IsEmpty())
return true;
// Generated images will be created at the desired tile size, so assume their
// intrinsic size is the requested tile size.
bool is_generated_image = image->HasRelativeSize();
const FloatSize intrinsic_tile_size =
is_generated_image ? image_tile.Size() : FloatSize(image->Size());
// Subset computation needs the same location as was used with
// ComputePhaseForBackground above, but needs the unsnapped destination
// size to correctly calculate sprite subsets in the presence of zoom. But if
// this is a generated image sized according to the tile size (which is a
// snapped value), use the snapped dest rect instead.
FloatRect dest_rect_for_subset(
is_generated_image ? FloatSize(geometry.SnappedDestRect().Size())
: FloatSize(geometry.UnsnappedDestRect().Size()));
// Content providers almost always choose source pixels at integer locations,
// so snap to integers. This is particuarly important for sprite maps.
// Calculation up to this point, in LayoutUnits, can lead to small variations
// from integer size, so it is safe to round without introducing major issues.
const FloatRect unrounded_subset = ComputeSubsetForBackground(
image_tile, dest_rect_for_subset, intrinsic_tile_size);
FloatRect src_rect = FloatRect(RoundedIntRect(unrounded_subset));
// If we have rounded the image size to 0, revert the rounding.
if (src_rect.IsEmpty())
src_rect = unrounded_subset;
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage",
node, *info.image, FloatRect(image->Rect()),
// Since there is no way for the developer to specify decode behavior, use
// kSync by default.
context.DrawImageRRect(image, Image::kSyncDecode, image_border, src_rect,
return true;
// Inset the background rect by a "safe" amount: 1/2 border-width for opaque
// border styles, 1/6 border-width for double borders.
FloatRoundedRect BackgroundRoundedRectAdjustedForBleedAvoidance(
const ComputedStyle& style,
const LayoutRect& border_rect,
bool object_has_multiple_boxes,
bool include_logical_left_edge,
bool include_logical_right_edge,
FloatRoundedRect background_rounded_rect) {
// TODO(fmalita): we should be able to fold these parameters into
// BoxBorderInfo or BoxDecorationData and avoid calling getBorderEdgeInfo
// redundantly here.
BorderEdge edges[4];
style.GetBorderEdgeInfo(edges, include_logical_left_edge,
// Use the most conservative inset to avoid mixed-style corner issues.
float fractional_inset = 1.0f / 2;
for (auto& edge : edges) {
if (edge.BorderStyle() == EBorderStyle::kDouble) {
fractional_inset = 1.0f / 6;
FloatRectOutsets insets(
-fractional_inset * edges[static_cast<unsigned>(BoxSide::kTop)].Width(),
-fractional_inset * edges[static_cast<unsigned>(BoxSide::kRight)].Width(),
-fractional_inset *
-fractional_inset * edges[static_cast<unsigned>(BoxSide::kLeft)].Width());
FloatRect inset_rect(background_rounded_rect.Rect());
FloatRoundedRect::Radii inset_radii(background_rounded_rect.GetRadii());
inset_radii.Shrink(-insets.Top(), -insets.Bottom(), -insets.Left(),
return FloatRoundedRect(inset_rect, inset_radii);
FloatRoundedRect RoundedBorderRectForClip(
const ComputedStyle& style,
const BoxPainterBase::FillLayerInfo& info,
const FillLayer& bg_layer,
const LayoutRect& rect,
bool object_has_multiple_boxes,
const LayoutSize& flow_box_size,
BackgroundBleedAvoidance bleed_avoidance,
LayoutRectOutsets border_padding_insets) {
if (!info.is_rounded_fill)
return FloatRoundedRect();
FloatRoundedRect border = style.GetRoundedBorderFor(
rect, info.include_left_edge, info.include_right_edge);
if (object_has_multiple_boxes) {
FloatRoundedRect segment_border = style.GetRoundedBorderFor(
LayoutRect(LayoutPoint(), LayoutSize(FlooredIntSize(flow_box_size))),
info.include_left_edge, info.include_right_edge);
if (info.is_border_fill &&
bleed_avoidance == kBackgroundBleedShrinkBackground) {
border = BackgroundRoundedRectAdjustedForBleedAvoidance(
style, rect, object_has_multiple_boxes, info.include_left_edge,
info.include_right_edge, border);
// Clip to the padding or content boxes as necessary.
if (bg_layer.Clip() == EFillBox::kContent) {
border = style.GetRoundedInnerBorderFor(
LayoutRect(border.Rect()), border_padding_insets,
info.include_left_edge, info.include_right_edge);
} else if (bg_layer.Clip() == EFillBox::kPadding) {
border = style.GetRoundedInnerBorderFor(LayoutRect(border.Rect()),
return border;
void PaintFillLayerBackground(GraphicsContext& context,
const BoxPainterBase::FillLayerInfo& info,
Node* node,
Image* image,
SkBlendMode composite_op,
const BackgroundImageGeometry& geometry,
LayoutRect scrolled_paint_rect) {
// 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.is_bottom_layer && info.color.Alpha() && info.should_paint_color) {
IntRect background_rect(PixelSnappedIntRect(scrolled_paint_rect));
context.FillRect(background_rect, info.color);
// No progressive loading of the background image.
// NOTE: This method can be called with no image in situations when a bad
// resource locator is given such as "//:0", so still check for image.
if (info.should_paint_image && !geometry.SnappedDestRect().IsEmpty() &&
!geometry.TileSize().IsEmpty() && image) {
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "PaintImage",
node, *info.image, FloatRect(image->Rect()),
DrawTiledBackground(context, image,
FloatRect(geometry.SnappedDestRect()), geometry.Phase(),
FloatSize(geometry.TileSize()), composite_op,
LayoutRectOutsets AdjustOutsetsForEdgeInclusion(
const LayoutRectOutsets outsets,
const BoxPainterBase::FillLayerInfo& info) {
LayoutRectOutsets adjusted = outsets;
if (!info.include_right_edge)
if (!info.include_left_edge)
return adjusted;
bool ShouldApplyBlendOperation(const BoxPainterBase::FillLayerInfo& info,
const FillLayer& layer) {
// For a mask layer, don't use the operator if this is the bottom layer.
return !info.is_bottom_layer || layer.GetType() != EFillLayerType::kMask;
} // anonymous namespace
LayoutRectOutsets BoxPainterBase::AdjustedBorderOutsets(
const FillLayerInfo& info) const {
return AdjustOutsetsForEdgeInclusion(ComputeBorders(), info);
void BoxPainterBase::PaintFillLayer(const PaintInfo& paint_info,
const Color& color,
const FillLayer& bg_layer,
const LayoutRect& rect,
BackgroundBleedAvoidance bleed_avoidance,
BackgroundImageGeometry& geometry,
bool object_has_multiple_boxes,
const LayoutSize flow_box_size) {
GraphicsContext& context = paint_info.context;
if (rect.IsEmpty())
const FillLayerInfo info = GetFillLayerInfo(color, bg_layer, bleed_avoidance);
// If we're not actually going to paint anything, abort early.
if (!info.should_paint_image && !info.should_paint_color)
GraphicsContextStateSaver clip_with_scrolling_state_saver(
context, info.is_clipped_with_local_scrolling);
auto scrolled_paint_rect =
AdjustRectForScrolledContent(paint_info, info, rect);
const auto did_adjust_paint_rect = scrolled_paint_rect != rect;
scoped_refptr<Image> image;
SkBlendMode composite_op = SkBlendMode::kSrcOver;
base::Optional<ScopedInterpolationQuality> interpolation_quality_context;
if (info.should_paint_image) {
geometry.Calculate(paint_info.PaintContainer(), paint_info.phase,
paint_info.GetGlobalPaintFlags(), bg_layer,
image = info.image->GetImage(
geometry.ImageClient(), geometry.ImageDocument(), geometry.ImageStyle(),
if (bg_layer.MaskSourceType() == EMaskSourceType::kLuminance)
if (ShouldApplyBlendOperation(info, bg_layer)) {
composite_op = WebCoreCompositeToSkiaComposite(bg_layer.Composite(),
LayoutRectOutsets border = ComputeBorders();
LayoutRectOutsets padding = ComputePadding();
LayoutRectOutsets border_padding_insets = -(border + padding);
FloatRoundedRect border_rect = RoundedBorderRectForClip(
style_, info, bg_layer, rect, object_has_multiple_boxes, flow_box_size,
bleed_avoidance, border_padding_insets);
// Fast path for drawing simple color backgrounds. Do not use the fast
// path with images if the dest rect has been adjusted for scrolling
// backgrounds because correcting the dest rect for scrolling reduces the
// accuracy of the destination rects. Also disable the fast path for images
// if we are shrinking the background for bleed avoidance, because this
// adjusts the border rects in a way that breaks the optimization.
bool disable_fast_path =
info.should_paint_image &&
(bleed_avoidance == kBackgroundBleedShrinkBackground ||
if (!disable_fast_path &&
PaintFastBottomLayer(node_, paint_info, info, rect, border_rect, geometry,
image.get(), composite_op)) {
base::Optional<RoundedInnerRectClipper> clip_to_border;
if (info.is_rounded_fill)
clip_to_border.emplace(context, rect, border_rect);
if (bg_layer.Clip() == EFillBox::kText) {
PaintFillLayerTextFillBox(context, info, image.get(), composite_op,
geometry, rect, scrolled_paint_rect,
GraphicsContextStateSaver background_clip_state_saver(context, false);
switch (bg_layer.Clip()) {
case EFillBox::kPadding:
case EFillBox::kContent: {
if (info.is_rounded_fill)
// Clip to the padding or content boxes as necessary.
LayoutRect clip_rect = scrolled_paint_rect;
clip_rect.Contract(AdjustOutsetsForEdgeInclusion(border, info));
if (bg_layer.Clip() == EFillBox::kContent)
clip_rect.Contract(AdjustOutsetsForEdgeInclusion(padding, info));
// TODO(chrishtr): this should be pixel-snapped.
case EFillBox::kBorder:
case EFillBox::kText: // fall through
PaintFillLayerBackground(context, info, node_, image.get(), composite_op,
geometry, scrolled_paint_rect);
void BoxPainterBase::PaintFillLayerTextFillBox(
GraphicsContext& context,
const BoxPainterBase::FillLayerInfo& info,
Image* image,
SkBlendMode composite_op,
const BackgroundImageGeometry& geometry,
const LayoutRect& rect,
const LayoutRect& scrolled_paint_rect,
bool object_has_multiple_boxes) {
// 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.
IntRect mask_rect = PixelSnappedIntRect(rect);
// We draw the background into a separate layer, to be later masked with
// yet another layer holding the text content.
GraphicsContextStateSaver background_clip_state_saver(context, false);
context.BeginLayer(1, composite_op);
PaintFillLayerBackground(context, info, node_, image, SkBlendMode::kSrcOver,
geometry, scrolled_paint_rect);
// Create the text mask layer and 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.
context.BeginLayer(1, SkBlendMode::kDstIn);
PaintTextClipMask(context, mask_rect, scrolled_paint_rect.Location(),
context.EndLayer(); // Text mask layer.
context.EndLayer(); // Background layer.
void BoxPainterBase::PaintBorder(const ImageResourceObserver& obj,
const Document& document,
Node* node,
const PaintInfo& info,
const LayoutRect& rect,
const ComputedStyle& style,
BackgroundBleedAvoidance bleed_avoidance,
bool include_logical_left_edge,
bool include_logical_right_edge) {
// border-image is not affected by border-radius.
if (NinePieceImagePainter::Paint(info.context, obj, document, node, rect,
style, style.BorderImage())) {
const BoxBorderPainter border_painter(rect, style, bleed_avoidance,
border_painter.PaintBorder(info, rect);
void BoxPainterBase::PaintMaskImages(const PaintInfo& paint_info,
const LayoutRect& paint_rect,
const ImageResourceObserver& obj,
BackgroundImageGeometry& geometry,
bool include_logical_left_edge,
bool include_logical_right_edge) {
if (!style_.HasMask() || style_.Visibility() != EVisibility::kVisible)
PaintFillLayers(paint_info, Color::kTransparent, style_.MaskLayers(),
paint_rect, geometry);
NinePieceImagePainter::Paint(paint_info.context, obj, *document_, node_,
paint_rect, style_, style_.MaskBoxImage(),
} // namespace blink