Bug 1317636 - Part 1. Implement nsSVGClipPathFrame::CreateClipMask and PaintClipMask.
Simply split the code in nsSVGClipPathFrame::GetClipMask into two different
functions.
MozReview-Commit-ID: KMdVL3Wg8OC
--- a/layout/svg/nsSVGClipPathFrame.cpp
+++ b/layout/svg/nsSVGClipPathFrame.cpp
@@ -76,16 +76,190 @@ nsSVGClipPathFrame::ApplyClipPath(gfxCon
aContext.Clip(clipPath);
} else {
// The spec says clip away everything if we have no children or the
// clipping path otherwise can't be resolved:
aContext.Clip(Rect());
}
}
+already_AddRefed<DrawTarget>
+nsSVGClipPathFrame::CreateClipMask(gfxContext& aReferenceContext,
+ IntPoint& aOffset)
+{
+ gfxContextMatrixAutoSaveRestore autoRestoreMatrix(&aReferenceContext);
+
+ IntRect bonuds;
+ aReferenceContext.SetMatrix(gfxMatrix());
+ gfxRect rect = aReferenceContext.GetClipExtents();
+ bonuds = RoundedOut(ToRect(rect));
+ if (bonuds.IsEmpty()) {
+ // We don't need to create a mask surface, all drawing is clipped anyway.
+ return nullptr;
+ }
+
+ DrawTarget* sourceDT = aReferenceContext.GetDrawTarget();
+ RefPtr<DrawTarget> maskDT =
+ sourceDT->CreateSimilarDrawTarget(bonuds.Size(), SurfaceFormat::A8);
+
+ aOffset = bonuds.TopLeft();
+
+ return maskDT.forget();
+}
+
+DrawResult
+nsSVGClipPathFrame::PaintClipMask(gfxContext& aMaskContext,
+ nsIFrame* aClippedFrame,
+ const gfxMatrix& aMatrix,
+ Matrix* aMaskTransform,
+ SourceSurface* aExtraMask,
+ const Matrix& aExtraMasksTransform)
+{
+ // A clipPath can reference another clipPath. We re-enter this method for
+ // each clipPath in a reference chain, so here we limit chain length:
+ static int16_t sRefChainLengthCounter = AutoReferenceLimiter::notReferencing;
+ AutoReferenceLimiter
+ refChainLengthLimiter(&sRefChainLengthCounter,
+ MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH);
+ if (!refChainLengthLimiter.Reference()) {
+ return DrawResult::SUCCESS; // Reference chain is too long!
+ }
+
+ // And to prevent reference loops we check that this clipPath only appears
+ // once in the reference chain (if any) that we're currently processing:
+ AutoReferenceLimiter refLoopDetector(&mReferencing, 1);
+ if (!refLoopDetector.Reference()) {
+ return DrawResult::SUCCESS; // Reference loop!
+ }
+
+ DrawResult result = DrawResult::SUCCESS;
+ DrawTarget* maskDT = aMaskContext.GetDrawTarget();
+ MOZ_ASSERT(maskDT->GetFormat() == SurfaceFormat::A8);
+
+ // Paint this clipPath's contents into aMaskDT:
+ {
+ // We need to set mMatrixForChildren here so that under the PaintSVG calls
+ // on our children (below) our GetCanvasTM() method will return the correct
+ // transform.
+ mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix;
+
+ // Check if this clipPath is itself clipped by another clipPath:
+ nsSVGClipPathFrame* clipPathThatClipsClipPath =
+ nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr);
+ bool clippingOfClipPathRequiredMasking;
+ if (clipPathThatClipsClipPath) {
+ aMaskContext.Save();
+ clippingOfClipPathRequiredMasking = !clipPathThatClipsClipPath->IsTrivial();
+ if (!clippingOfClipPathRequiredMasking) {
+ clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame,
+ aMatrix);
+ } else {
+ Matrix maskTransform;
+ RefPtr<SourceSurface> mask =
+ clipPathThatClipsClipPath->GetClipMask(aMaskContext, aClippedFrame,
+ aMatrix, &maskTransform);
+ aMaskContext.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0,
+ mask, maskTransform);
+ // The corresponding PopGroupAndBlend call below will mask the
+ // blend using |mask|.
+ }
+ }
+
+ // Paint our children into the mask:
+ for (nsIFrame* kid = mFrames.FirstChild(); kid;
+ kid = kid->GetNextSibling()) {
+ nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ // The CTM of each frame referencing us can be different.
+ SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED);
+
+ bool isOK = true;
+ // Children of this clipPath may themselves be clipped.
+ nsSVGClipPathFrame *clipPathThatClipsChild =
+ nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(&isOK);
+ if (!isOK) {
+ continue;
+ }
+
+ bool childsClipPathRequiresMasking;
+
+ if (clipPathThatClipsChild) {
+ childsClipPathRequiresMasking = !clipPathThatClipsChild->IsTrivial();
+ aMaskContext.Save();
+ if (!childsClipPathRequiresMasking) {
+ clipPathThatClipsChild->ApplyClipPath(aMaskContext, aClippedFrame,
+ aMatrix);
+ } else {
+ Matrix maskTransform;
+ RefPtr<SourceSurface> mask =
+ clipPathThatClipsChild->GetClipMask(aMaskContext, aClippedFrame,
+ aMatrix, &maskTransform);
+ aMaskContext.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0,
+ mask, maskTransform);
+ // The corresponding PopGroupAndBlend call below will mask the
+ // blend using |mask|.
+ }
+ }
+
+ gfxMatrix toChildsUserSpace = mMatrixForChildren;
+ nsIFrame* child = do_QueryFrame(SVGFrame);
+ nsIContent* childContent = child->GetContent();
+ if (childContent->IsSVGElement()) {
+ toChildsUserSpace =
+ static_cast<const nsSVGElement*>(childContent)->
+ PrependLocalTransformsTo(mMatrixForChildren, eUserSpaceToParent);
+ }
+
+ // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and
+ // nsSVGPathGeometryFrame::Render checks for that state bit and paints
+ // only the geometry (opaque black) if set.
+ result &= SVGFrame->PaintSVG(aMaskContext, toChildsUserSpace);
+
+ if (clipPathThatClipsChild) {
+ if (childsClipPathRequiresMasking) {
+ aMaskContext.PopGroupAndBlend();
+ }
+ aMaskContext.Restore();
+ }
+ }
+ }
+
+
+ if (clipPathThatClipsClipPath) {
+ if (clippingOfClipPathRequiredMasking) {
+ aMaskContext.PopGroupAndBlend();
+ }
+ aMaskContext.Restore();
+ }
+ }
+
+ // Moz2D transforms in the opposite direction to Thebes
+ gfxMatrix maskTransfrom = aMaskContext.CurrentMatrix();
+ maskTransfrom.Invert();
+
+ if (aExtraMask) {
+ // We could potentially due this more efficiently with OPERATOR_IN
+ // but that operator does not work well on CG or D2D
+ RefPtr<SourceSurface> currentMask = maskDT->Snapshot();
+ IntSize targetSize = maskDT->GetSize();
+ Matrix transform = maskDT->GetTransform();
+ maskDT->SetTransform(Matrix());
+ maskDT->ClearRect(Rect(0, 0, targetSize.width, targetSize.height));
+ maskDT->SetTransform(aExtraMasksTransform * transform);
+ // draw currentMask with the inverse of the transform that we just so that
+ // it ends up in the same spot with aExtraMask transformed by aExtraMasksTransform
+ maskDT->MaskSurface(SurfacePattern(currentMask, ExtendMode::CLAMP, aExtraMasksTransform.Inverse() * ToMatrix(maskTransfrom)),
+ aExtraMask,
+ Point(0, 0));
+ }
+
+ *aMaskTransform = ToMatrix(maskTransfrom);
+ return result;
+}
+
already_AddRefed<SourceSurface>
nsSVGClipPathFrame::GetClipMask(gfxContext& aReferenceContext,
nsIFrame* aClippedFrame,
const gfxMatrix& aMatrix,
Matrix* aMaskTransform,
SourceSurface* aExtraMask,
const Matrix& aExtraMasksTransform,
DrawResult* aResult)
--- a/layout/svg/nsSVGClipPathFrame.h
+++ b/layout/svg/nsSVGClipPathFrame.h
@@ -82,16 +82,36 @@ public:
already_AddRefed<SourceSurface>
GetClipMask(gfxContext& aReferenceContext, nsIFrame* aClippedFrame,
const gfxMatrix& aMatrix, Matrix* aMaskTransform,
SourceSurface* aExtraMask = nullptr,
const Matrix& aExtraMasksTransform = Matrix(),
DrawResult* aResult = nullptr);
/**
+ * Paint mask directly onto a given context(aMaskContext).
+ *
+ * @param aMaskContext The target of mask been painting on.
+ * @param aClippedFrame The/an nsIFrame of the element that references this
+ * clipPath that is currently being processed.
+ * @param aMatrix The transform from aClippedFrame's user space to
+ * current transform.
+ * @param [out] aMaskTransform The transform to use with the returned
+ * surface.
+ * @param [in, optional] aExtraMask An extra surface that the returned
+ * surface should be masked with.
+ * @param [in, optional] aExtraMasksTransform The transform to use with
+ * aExtraMask. Should be passed when aExtraMask is passed.
+ */
+ DrawResult
+ PaintClipMask(gfxContext& aMaskContext, nsIFrame* aClippedFrame,
+ const gfxMatrix& aMatrix, Matrix* aMaskTransform,
+ SourceSurface* aExtraMask, const Matrix& aExtraMasksTransform);
+
+ /**
* aPoint is expected to be in aClippedFrame's SVG user space.
*/
bool PointIsInsideClipPath(nsIFrame* aClippedFrame, const gfxPoint &aPoint);
// Check if this clipPath is made up of more than one geometry object.
// If so, the clipping API in cairo isn't enough and we need to use
// mask based clipping.
bool IsTrivial(nsISVGChildFrame **aSingleChild = nullptr);
@@ -132,16 +152,19 @@ public:
*/
gfxMatrix GetClipPathTransform(nsIFrame* aClippedFrame);
private:
// nsSVGContainerFrame methods:
virtual gfxMatrix GetCanvasTM() override;
+ already_AddRefed<DrawTarget>
+ CreateClipMask(gfxContext& aReferenceContext, IntPoint& aOffset);
+
// Set, during a GetClipMask() call, to the transform that still needs to be
// concatenated to the transform of the DrawTarget that was passed to
// GetClipMask in order to establish the coordinate space that the clipPath
// establishes for its contents (i.e. including applying 'clipPathUnits' and
// any 'transform' attribute set on the clipPath) specifically for clipping
// the frame that was passed to GetClipMask at that moment in time. This is
// set so that if our GetCanvasTM method is called while GetClipMask is
// painting its children, the returned matrix will include the transforms