Bug 1317636 - Part 1. Implement nsSVGClipPathFrame::CreateClipMask and PaintClipMask. draft
authorcku <cku@mozilla.com>
Wed, 16 Nov 2016 16:45:03 +0800
changeset 440355 4f0740934bb2c9277616178b8cfb3ed4bc4cfa58
parent 440319 a3971641002bfe516ce095a170c09c169c011e20
child 440356 c80517b0bdb249d75b81ab1192886d52f052606c
push id36211
push userbmo:cku@mozilla.com
push dateThu, 17 Nov 2016 12:48:44 +0000
bugs1317636
milestone53.0a1
Bug 1317636 - Part 1. Implement nsSVGClipPathFrame::CreateClipMask and PaintClipMask. Simply split the code in nsSVGClipPathFrame::GetClipMask into two different functions. MozReview-Commit-ID: KMdVL3Wg8OC
layout/svg/nsSVGClipPathFrame.cpp
layout/svg/nsSVGClipPathFrame.h
--- 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