Bug 1317636 - Part 1. Implement nsSVGClipPathFrame::CreateClipMask and PaintClipMask. draft
authorcku <cku@mozilla.com>
Thu, 17 Nov 2016 14:50:35 +0800
changeset 440748 802a481192db28de8dd9e4ff16e4c40f173ed99b
parent 440747 076f822986b9849d5bf3b82e7726ee43496d7809
child 440749 4493cb470092e912c6ecf8caf08990ac86d3501c
push id36315
push userbmo:cku@mozilla.com
push dateFri, 18 Nov 2016 02:41:02 +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,98 +76,91 @@ 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<SourceSurface>
-nsSVGClipPathFrame::GetClipMask(gfxContext& aReferenceContext,
-                                nsIFrame* aClippedFrame,
-                                const gfxMatrix& aMatrix,
-                                Matrix* aMaskTransform,
-                                SourceSurface* aExtraMask,
-                                const Matrix& aExtraMasksTransform,
-                                DrawResult* aResult)
+already_AddRefed<DrawTarget>
+nsSVGClipPathFrame::CreateClipMask(gfxContext& aReferenceContext,
+                                   IntPoint& aOffset)
 {
-  MOZ_ASSERT(!IsTrivial(), "Caller needs to use ApplyClipPath");
+  gfxContextMatrixAutoSaveRestore autoRestoreMatrix(&aReferenceContext);
+
+  aReferenceContext.SetMatrix(gfxMatrix());
+  gfxRect rect = aReferenceContext.GetClipExtents();
+  IntRect bounds = RoundedOut(ToRect(rect));
+  if (bounds.IsEmpty()) {
+    // We don't need to create a mask surface, all drawing is clipped anyway.
+    return nullptr;
+  }
+
+  DrawTarget* referenceDT = aReferenceContext.GetDrawTarget();
+  RefPtr<DrawTarget> maskDT =
+    referenceDT->CreateSimilarDrawTarget(bounds.Size(), SurfaceFormat::A8);
 
-  if (aResult) {
-    *aResult = DrawResult::SUCCESS;
-  }
-  DrawTarget& aReferenceDT = *aReferenceContext.GetDrawTarget();
+  aOffset = bounds.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 nullptr; // Reference chain is too long!
+    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 nullptr; // Reference loop!
-  }
-
-  IntRect devSpaceClipExtents;
-  {
-    gfxContextMatrixAutoSaveRestore autoRestoreMatrix(&aReferenceContext);
-
-    aReferenceContext.SetMatrix(gfxMatrix());
-    gfxRect rect = aReferenceContext.GetClipExtents();
-    devSpaceClipExtents = RoundedOut(ToRect(rect));
-    if (devSpaceClipExtents.IsEmpty()) {
-      // We don't need to create a mask surface, all drawing is clipped anyway.
-      return nullptr;
-    }
+    return DrawResult::SUCCESS; // Reference loop!
   }
 
-  RefPtr<DrawTarget> maskDT =
-    aReferenceDT.CreateSimilarDrawTarget(devSpaceClipExtents.Size(),
-                                         SurfaceFormat::A8);
-
-  gfxMatrix mat = aReferenceContext.CurrentMatrix() *
-                    gfxMatrix::Translation(-devSpaceClipExtents.TopLeft());
+  DrawResult result = DrawResult::SUCCESS;
+  DrawTarget* maskDT = aMaskContext.GetDrawTarget();
+  MOZ_ASSERT(maskDT->GetFormat() == SurfaceFormat::A8);
 
-  // Paint this clipPath's contents into maskDT:
+  // Paint this clipPath's contents into aMaskDT:
   {
-    RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(maskDT);
-    if (!ctx) {
-      gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT);
-      return nullptr;
-    }
-    ctx->SetMatrix(mat);
-
     // 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) {
-      ctx->Save();
+      aMaskContext.Save();
       clippingOfClipPathRequiredMasking = !clipPathThatClipsClipPath->IsTrivial();
       if (!clippingOfClipPathRequiredMasking) {
-        clipPathThatClipsClipPath->ApplyClipPath(*ctx, aClippedFrame, aMatrix);
+        clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame,
+                                                 aMatrix);
       } else {
         Matrix maskTransform;
         RefPtr<SourceSurface> mask =
-          clipPathThatClipsClipPath->GetClipMask(*ctx, aClippedFrame,
+          clipPathThatClipsClipPath->GetClipMask(aMaskContext, aClippedFrame,
                                                  aMatrix, &maskTransform);
-        ctx->PushGroupForBlendBack(gfxContentType::ALPHA, 1.0,
+        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;
@@ -184,25 +177,26 @@ nsSVGClipPathFrame::GetClipMask(gfxConte
         if (!isOK) {
           continue;
         }
 
         bool childsClipPathRequiresMasking;
 
         if (clipPathThatClipsChild) {
           childsClipPathRequiresMasking = !clipPathThatClipsChild->IsTrivial();
-          ctx->Save();
+          aMaskContext.Save();
           if (!childsClipPathRequiresMasking) {
-            clipPathThatClipsChild->ApplyClipPath(*ctx, aClippedFrame, aMatrix);
+            clipPathThatClipsChild->ApplyClipPath(aMaskContext, aClippedFrame,
+                                                  aMatrix);
           } else {
             Matrix maskTransform;
             RefPtr<SourceSurface> mask =
-              clipPathThatClipsChild->GetClipMask(*ctx, aClippedFrame,
+              clipPathThatClipsChild->GetClipMask(aMaskContext, aClippedFrame,
                                                   aMatrix, &maskTransform);
-            ctx->PushGroupForBlendBack(gfxContentType::ALPHA, 1.0,
+            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);
@@ -211,60 +205,98 @@ nsSVGClipPathFrame::GetClipMask(gfxConte
           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.
-        DrawResult result = SVGFrame->PaintSVG(*ctx, toChildsUserSpace);
-        if (aResult) {
-          *aResult &= result;
-        }
+        result &= SVGFrame->PaintSVG(aMaskContext, toChildsUserSpace);
 
         if (clipPathThatClipsChild) {
           if (childsClipPathRequiresMasking) {
-            ctx->PopGroupAndBlend();
+            aMaskContext.PopGroupAndBlend();
           }
-          ctx->Restore();
+          aMaskContext.Restore();
         }
       }
     }
 
 
     if (clipPathThatClipsClipPath) {
       if (clippingOfClipPathRequiredMasking) {
-        ctx->PopGroupAndBlend();
+        aMaskContext.PopGroupAndBlend();
       }
-      ctx->Restore();
+      aMaskContext.Restore();
     }
   }
 
   // Moz2D transforms in the opposite direction to Thebes
-  mat.Invert();
+  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,
-                           devSpaceClipExtents.width,
-                           devSpaceClipExtents.height));
+    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(mat)),
+    maskDT->MaskSurface(SurfacePattern(currentMask, ExtendMode::CLAMP, aExtraMasksTransform.Inverse() * ToMatrix(maskTransfrom)),
                         aExtraMask,
                         Point(0, 0));
   }
 
-  *aMaskTransform = ToMatrix(mat);
+  *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)
+{
+  MOZ_ASSERT(!IsTrivial(), "Caller needs to use ApplyClipPath");
+
+  IntPoint offset;
+  RefPtr<DrawTarget> maskDT = CreateClipMask(aReferenceContext, offset);
+  if (!maskDT) {
+    if (aResult) {
+      *aResult = DrawResult::SUCCESS;
+    }
+    return nullptr;
+  }
+
+  RefPtr<gfxContext> maskContext = gfxContext::CreateOrNull(maskDT);
+  if (!maskContext) {
+    gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT);
+    if (aResult) {
+      *aResult = DrawResult::TEMPORARY_ERROR;
+    }
+    return nullptr;
+  }
+  maskContext->SetMatrix(aReferenceContext.CurrentMatrix() *
+                         gfxMatrix::Translation(-offset));
+
+  DrawResult result = PaintClipMask(*maskContext, aClippedFrame, aMatrix,
+                                    aMaskTransform, aExtraMask,
+                                    aExtraMasksTransform);
+  if (aResult) {
+    *aResult = result;
+  }
+
   return maskDT->Snapshot();
 }
 
 bool
 nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame,
                                           const gfxPoint &aPoint)
 {
   // A clipPath can reference another clipPath.  We re-enter this method for
@@ -315,16 +347,17 @@ nsSVGClipPathFrame::PointIsInsideClipPat
         }
         pointForChild = m.Transform(point);
       }
       if (SVGFrame->GetFrameForPoint(pointForChild)) {
         return true;
       }
     }
   }
+
   return false;
 }
 
 bool
 nsSVGClipPathFrame::IsTrivial(nsISVGChildFrame **aSingleChild)
 {
   // If the clip path is clipped then it's non-trivial
   if (nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr))
--- 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