blob: a7991e4061040b8d9a756475c56ce402ad0470e8 [file] [log] [blame]
// Copyright 2015 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/SVGClipPainter.h"
#include "core/dom/ElementTraversal.h"
#include "core/layout/svg/LayoutSVGResourceClipper.h"
#include "core/layout/svg/SVGLayoutSupport.h"
#include "core/layout/svg/SVGResources.h"
#include "core/layout/svg/SVGResourcesCache.h"
#include "core/paint/ClipPathClipper.h"
#include "core/paint/LayoutObjectDrawingRecorder.h"
#include "core/paint/PaintInfo.h"
#include "core/paint/TransformRecorder.h"
#include "platform/graphics/paint/ClipPathDisplayItem.h"
#include "platform/graphics/paint/ClipPathRecorder.h"
#include "platform/graphics/paint/CompositingRecorder.h"
#include "platform/graphics/paint/DrawingDisplayItem.h"
#include "platform/graphics/paint/PaintController.h"
#include "platform/graphics/paint/SkPictureBuilder.h"
namespace blink {
namespace {
class SVGClipExpansionCycleHelper {
public:
SVGClipExpansionCycleHelper(LayoutSVGResourceClipper& clip) : m_clip(clip) {
clip.beginClipExpansion();
}
~SVGClipExpansionCycleHelper() { m_clip.endClipExpansion(); }
private:
LayoutSVGResourceClipper& m_clip;
};
} // namespace
bool SVGClipPainter::prepareEffect(const LayoutObject& target,
const FloatRect& targetBoundingBox,
const FloatRect& paintInvalidationRect,
const FloatPoint& layerPositionOffset,
GraphicsContext& context,
ClipperState& clipperState) {
DCHECK_EQ(clipperState, ClipperState::NotApplied);
SECURITY_DCHECK(!m_clip.needsLayout());
m_clip.clearInvalidationMask();
if (paintInvalidationRect.isEmpty() || m_clip.hasCycle())
return false;
SVGClipExpansionCycleHelper inClipExpansionChange(m_clip);
AffineTransform animatedLocalTransform =
toSVGClipPathElement(m_clip.element())->calculateAnimatedLocalTransform();
// When drawing a clip for non-SVG elements, the CTM does not include the zoom
// factor. In this case, we need to apply the zoom scale explicitly - but
// only for clips with userSpaceOnUse units (the zoom is accounted for
// objectBoundingBox-resolved lengths).
if (!target.isSVG() &&
m_clip.clipPathUnits() == SVGUnitTypes::kSvgUnitTypeUserspaceonuse) {
DCHECK(m_clip.style());
animatedLocalTransform.scale(m_clip.style()->effectiveZoom());
}
// First, try to apply the clip as a clipPath.
Path clipPath;
if (m_clip.asPath(animatedLocalTransform, targetBoundingBox, clipPath)) {
AffineTransform positionTransform;
positionTransform.translate(layerPositionOffset.x(),
layerPositionOffset.y());
clipPath.transform(positionTransform);
clipperState = ClipperState::AppliedPath;
context.getPaintController().createAndAppend<BeginClipPathDisplayItem>(
target, clipPath);
return true;
}
// Fall back to masking.
clipperState = ClipperState::AppliedMask;
// Begin compositing the clip mask.
CompositingRecorder::beginCompositing(
context, target, SkXfermode::kSrcOver_Mode, 1, &paintInvalidationRect);
{
if (!drawClipAsMask(context, target, targetBoundingBox,
paintInvalidationRect, animatedLocalTransform,
layerPositionOffset)) {
// End the clip mask's compositor.
CompositingRecorder::endCompositing(context, target);
return false;
}
}
// Masked content layer start.
CompositingRecorder::beginCompositing(
context, target, SkXfermode::kSrcIn_Mode, 1, &paintInvalidationRect);
return true;
}
void SVGClipPainter::finishEffect(const LayoutObject& target,
GraphicsContext& context,
ClipperState& clipperState) {
switch (clipperState) {
case ClipperState::AppliedPath:
// Path-only clipping, no layers to restore but we need to emit an end to
// the clip path display item.
context.getPaintController().endItem<EndClipPathDisplayItem>(target);
break;
case ClipperState::AppliedMask:
// Transfer content -> clip mask (SrcIn)
CompositingRecorder::endCompositing(context, target);
// Transfer clip mask -> bg (SrcOver)
CompositingRecorder::endCompositing(context, target);
break;
default:
NOTREACHED();
}
}
bool SVGClipPainter::drawClipAsMask(
GraphicsContext& context,
const LayoutObject& layoutObject,
const FloatRect& targetBoundingBox,
const FloatRect& targetPaintInvalidationRect,
const AffineTransform& localTransform,
const FloatPoint& layerPositionOffset) {
if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(
context, layoutObject, DisplayItem::kSVGClip))
return true;
SkPictureBuilder maskPictureBuilder(targetPaintInvalidationRect, nullptr,
&context);
GraphicsContext& maskContext = maskPictureBuilder.context();
{
TransformRecorder recorder(maskContext, layoutObject, localTransform);
// Apply any clip-path clipping this clipPath (nested shape/clipPath.)
Optional<ClipPathClipper> nestedClipPathClipper;
if (ClipPathOperation* clipPathOperation = m_clip.styleRef().clipPath())
nestedClipPathClipper.emplace(maskContext, *clipPathOperation, m_clip,
targetBoundingBox, layerPositionOffset);
{
AffineTransform contentTransform;
if (m_clip.clipPathUnits() ==
SVGUnitTypes::kSvgUnitTypeObjectboundingbox) {
contentTransform.translate(targetBoundingBox.x(),
targetBoundingBox.y());
contentTransform.scaleNonUniform(targetBoundingBox.width(),
targetBoundingBox.height());
}
SubtreeContentTransformScope contentTransformScope(contentTransform);
TransformRecorder contentTransformRecorder(maskContext, layoutObject,
contentTransform);
maskContext.getPaintController().createAndAppend<DrawingDisplayItem>(
layoutObject, DisplayItem::kSVGClip, m_clip.createContentPicture());
}
}
LayoutObjectDrawingRecorder drawingRecorder(context, layoutObject,
DisplayItem::kSVGClip,
targetPaintInvalidationRect);
sk_sp<SkPicture> maskPicture = maskPictureBuilder.endRecording();
context.drawPicture(maskPicture.get());
return true;
}
} // namespace blink