Bug 686281 - Implement CSS mask rendering; r?mstange draft
authorCJKu <cku@mozilla.com>
Fri, 13 Nov 2015 22:51:42 +0800
changeset 308750 7583cdd32f6b48fcad02f32731f1facbfca4754b
parent 308749 86ab4c938c4d92d0945cbc94b303ecdf863fd859
child 308751 2cbf37a375dbf59d5a04f9049f015194b01e32fb
push id7516
push usercku@mozilla.com
push dateFri, 13 Nov 2015 14:52:29 +0000
reviewersmstange
bugs686281
milestone45.0a1
Bug 686281 - Implement CSS mask rendering; r?mstange
layout/base/nsCSSRendering.cpp
layout/base/nsCSSRendering.h
layout/base/nsDisplayList.cpp
layout/svg/nsSVGIntegrationUtils.cpp
layout/svg/nsSVGIntegrationUtils.h
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -2841,25 +2841,26 @@ nsCSSRendering::PaintGradient(nsPresCont
       ctx->Fill();
       ctx->SetMatrix(ctm);
     }
   }
 }
 
 DrawResult
 nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext,
-                                      nsRenderingContext& aRenderingContext,
-                                      nsIFrame* aForFrame,
-                                      const nsRect& aDirtyRect,
-                                      const nsRect& aBorderArea,
-                                      nsStyleContext* aBackgroundSC,
-                                      const nsStyleBorder& aBorder,
-                                      uint32_t aFlags,
-                                      nsRect* aBGClipRect,
-                                      int32_t aLayer)
+                                  nsRenderingContext& aRenderingContext,
+                                  nsIFrame* aForFrame,
+                                  const nsRect& aDirtyRect,
+                                  const nsRect& aBorderArea,
+                                  nsStyleContext* aBackgroundSC,
+                                  const nsStyleBorder& aBorder,
+                                  uint32_t aFlags,
+                                  nsRect* aBGClipRect,
+                                  int32_t aLayer,
+                                  bool aMask)
 {
   NS_PRECONDITION(aForFrame,
                   "Frame is expected to be provided to PaintBackground");
 
   DrawResult result = DrawResult::SUCCESS;
 
   // Check to see if we have an appearance defined.  If so, we let the theme
   // renderer draw the background and bail out.
@@ -2895,20 +2896,22 @@ nsCSSRendering::PaintBackgroundWithSC(ns
   bool drawBackgroundColor;
 
   nscolor bgColor = DetermineBackgroundColor(aPresContext,
                                              aBackgroundSC,
                                              aForFrame,
                                              drawBackgroundImage,
                                              drawBackgroundColor);
 
-  const nsStyleImageLayers& layers = aBackgroundSC->StyleBackground()->mLayers;
+  const nsStyleImageLayers& layers = aMask ?
+    aBackgroundSC->StyleSVGReset()->mLayers :
+    aBackgroundSC->StyleBackground()->mLayers;
   // If we're drawing a specific layer, we don't want to draw the
   // background color.
-  if (drawBackgroundColor && aLayer >= 0) {
+  if ((drawBackgroundColor && aLayer >= 0) || aMask) {
     drawBackgroundColor = false;
   }
 
   // At this point, drawBackgroundImage and drawBackgroundColor are
   // true if and only if we are actually supposed to paint an image or
   // color into aDirtyRect, respectively.
   if (!drawBackgroundImage && !drawBackgroundColor)
     return DrawResult::SUCCESS;
@@ -3022,17 +3025,17 @@ nsCSSRendering::PaintBackgroundWithSC(ns
             ctx->SnappedRectangle(clip);
             ctx->Clip();
           }
         }
       }
       if ((aLayer < 0 || i == (uint32_t)startLayer) &&
           !clipState.mDirtyRectGfx.IsEmpty()) {
         nsBackgroundLayerState state = PrepareImageLayer(aPresContext, aForFrame,
-            aFlags, paintBorderArea, clipState.mBGClipArea, layer);
+            aFlags, paintBorderArea, clipState.mBGClipArea, layer, aMask);
         result &= state.mImageRenderer.PrepareResult();
         if (!state.mFillArea.IsEmpty()) {
           if (state.mCompositionOp != CompositionOp::OP_OVER) {
             NS_ASSERTION(ctx->CurrentOp() == CompositionOp::OP_OVER,
                          "It is assumed the initial op is OP_OVER, when it is restored later");
             ctx->SetOp(state.mCompositionOp);
           }
 
@@ -3204,17 +3207,18 @@ ComputeDrawnSizeForBackground(const CSSS
 }
 
 nsBackgroundLayerState
 nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext,
                                   nsIFrame* aForFrame,
                                   uint32_t aFlags,
                                   const nsRect& aBorderArea,
                                   const nsRect& aBGClipRect,
-                                  const nsStyleImageLayers::Layer& aLayer)
+                                  const nsStyleImageLayers::Layer& aLayer,
+                                  bool aMask)
 {
   /*
    * The properties we need to keep in mind when drawing style
    * layers are:
    *
    *   background-image/ mask-image
    *   background-repeat/ mask-repeat
    *   background-attachment
@@ -3350,17 +3354,18 @@ nsCSSRendering::PrepareImageLayer(nsPres
     state.mFillArea.width = bgClipRect.width;
   }
   if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_REPEAT) {
     state.mFillArea.y = bgClipRect.y;
     state.mFillArea.height = bgClipRect.height;
   }
   state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
 
-  state.mCompositionOp = GetGFXBlendMode(aLayer.mBlendMode);
+  state.mCompositionOp = aMask ? GetGFXCompositeMode(aLayer.mComposite) :
+                                 GetGFXBlendMode(aLayer.mBlendMode);
 
   return state;
 }
 
 nsRect
 nsCSSRendering::GetBackgroundLayerRect(nsPresContext* aPresContext,
                                        nsIFrame* aForFrame,
                                        const nsRect& aBorderArea,
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -538,17 +538,18 @@ struct nsCSSRendering {
                                    nsIFrame** aAttachedToFrame);
 
   static nsBackgroundLayerState
   PrepareImageLayer(nsPresContext* aPresContext,
                     nsIFrame* aForFrame,
                     uint32_t aFlags,
                     const nsRect& aBorderArea,
                     const nsRect& aBGClipRect,
-                    const nsStyleImageLayers::Layer& aLayer);
+                    const nsStyleImageLayers::Layer& aLayer,
+                    bool aMask = false);
 
   struct ImageLayerClipState {
     nsRect mBGClipArea;  // Affected by mClippedRadii
     nsRect mAdditionalBGClipArea;  // Not affected by mClippedRadii
     nsRect mDirtyRect;
     gfxRect mDirtyRectGfx;
 
     nscoord mRadii[8];
@@ -610,17 +611,18 @@ struct nsCSSRendering {
                                           nsRenderingContext& aRenderingContext,
                                           nsIFrame* aForFrame,
                                           const nsRect& aDirtyRect,
                                           const nsRect& aBorderArea,
                                           nsStyleContext *aStyleContext,
                                           const nsStyleBorder& aBorder,
                                           uint32_t aFlags,
                                           nsRect* aBGClipRect = nullptr,
-                                          int32_t aLayer = -1);
+                                          int32_t aLayer = -1,
+                                          bool aMask = false);
 
   /**
    * Returns the rectangle covered by the given background layer image, taking
    * into account background positioning, sizing, and repetition, but not
    * clipping.
    */
   static nsRect GetBackgroundLayerRect(nsPresContext* aPresContext,
                                        nsIFrame* aForFrame,
@@ -789,16 +791,28 @@ struct nsCSSRendering {
       case NS_STYLE_BLEND_HUE:         return CompositionOp::OP_HUE;
       case NS_STYLE_BLEND_SATURATION:  return CompositionOp::OP_SATURATION;
       case NS_STYLE_BLEND_COLOR:       return CompositionOp::OP_COLOR;
       case NS_STYLE_BLEND_LUMINOSITY:  return CompositionOp::OP_LUMINOSITY;
       default:      MOZ_ASSERT(false); return CompositionOp::OP_OVER;
     }
   }
 
+  static CompositionOp GetGFXCompositeMode(uint8_t mBlendMode) {
+    switch (mBlendMode) {
+      case NS_STYLE_COMPOSITE_MODE_ADD:       return CompositionOp::OP_OVER;
+      case NS_STYLE_COMPOSITE_MODE_SUBTRACT:  return CompositionOp::OP_OUT;
+      case NS_STYLE_COMPOSITE_MODE_INTERSECT: return CompositionOp::OP_IN;
+      case NS_STYLE_COMPOSITE_MODE_EXCLUDE:   return CompositionOp::OP_XOR;
+      default:
+        MOZ_ASSERT(false); return CompositionOp::OP_OVER;
+    }
+
+    return CompositionOp::OP_OVER;
+  }
 protected:
   static gfxRect GetTextDecorationRectInternal(const Point& aPt,
                                                const Size& aLineSize,
                                                const gfxFloat aAscent,
                                                const gfxFloat aOffset,
                                                const uint8_t aDecoration,
                                                const uint8_t aStyle,
                                                bool aVertical,
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -6023,18 +6023,20 @@ nsDisplaySVGEffects::HitTest(nsDisplayLi
   }
 }
 
 void
 nsDisplaySVGEffects::PaintAsLayer(nsDisplayListBuilder* aBuilder,
                                   nsRenderingContext* aCtx,
                                   LayerManager* aManager)
 {
+  nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
   nsSVGIntegrationUtils::PaintFramesWithEffects(*aCtx->ThebesContext(), mFrame,
                                                 mVisibleRect,
+                                                borderArea,
                                                 aBuilder, aManager);
 }
 
 LayerState
 nsDisplaySVGEffects::GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters)
 {
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -18,16 +18,17 @@
 #include "nsSVGElement.h"
 #include "nsSVGFilterPaintCallback.h"
 #include "nsSVGMaskFrame.h"
 #include "nsSVGPaintServerFrame.h"
 #include "nsSVGUtils.h"
 #include "FrameLayerBuilder.h"
 #include "BasicLayers.h"
 #include "mozilla/gfx/Point.h"
+#include "nsCSSRendering.h"
 
 using namespace mozilla;
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 
 // ----------------------------------------------------------------------
 
 /**
@@ -147,18 +148,19 @@ GetPreEffectsVisualOverflowUnion(nsIFram
 bool
 nsSVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame)
 {
   // Even when SVG display lists are disabled, returning true for SVG frames
   // does not adversely affect any of our callers. Therefore we don't bother
   // checking the SDL prefs here, since we don't know if we're being called for
   // painting or hit-testing anyway.
   const nsStyleSVGReset *style = aFrame->StyleSVGReset();
-  return (style->HasFilters() ||
-          style->mClipPath.GetType() != NS_STYLE_CLIP_PATH_NONE || style->mMask);
+  return (style->HasFilters() || style->mMask ||
+          (style->mClipPath.GetType() != NS_STYLE_CLIP_PATH_NONE) ||
+          (style->mLayers.mImageCount > 1));
 }
 
 // For non-SVG frames, this gives the offset to the frame's "user space".
 // For SVG frames, this returns a zero offset.
 static nsPoint
 GetOffsetToBoundingBox(nsIFrame* aFrame)
 {
   if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
@@ -404,16 +406,17 @@ private:
   LayerManager* mLayerManager;
   nsPoint mOffset;
 };
 
 void
 nsSVGIntegrationUtils::PaintFramesWithEffects(gfxContext& aContext,
                                               nsIFrame* aFrame,
                                               const nsRect& aDirtyRect,
+                                              const nsRect& aBorderArea,
                                               nsDisplayListBuilder* aBuilder,
                                               LayerManager *aLayerManager)
 {
 #ifdef DEBUG
   NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
                (NS_SVGDisplayListPaintingEnabled() &&
                 !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)),
                "Should not use nsSVGIntegrationUtils on this SVG frame");
@@ -459,20 +462,16 @@ nsSVGIntegrationUtils::PaintFramesWithEf
      so make sure all applicable ones are set again. */
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
   nsSVGEffects::EffectProperties effectProperties =
     nsSVGEffects::GetEffectProperties(firstFrame);
 
   bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
   nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK);
-  nsSVGMaskFrame *maskFrame = effectProperties.GetMaskFrame(&isOK);
-  if (!isOK) {
-    return; // Some resource is missing. We shouldn't paint anything.
-  }
 
   bool isTrivialClip = clipPathFrame ? clipPathFrame->IsTrivial() : true;
 
   DrawTarget* drawTarget = aContext.GetDrawTarget();
   gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&aContext);
 
   nsPoint firstFrameOffset = GetOffsetToBoundingBox(firstFrame);
   nsPoint offsetToBoundingBox = aBuilder->ToReferenceFrame(firstFrame) - firstFrameOffset;
@@ -505,21 +504,27 @@ nsSVGIntegrationUtils::PaintFramesWithEf
 
   gfxPoint devPixelOffsetToUserSpace =
     nsLayoutUtils::PointToGfxPoint(offsetToUserSpace,
                                    aFrame->PresContext()->AppUnitsPerDevPixel());
   aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
 
   gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(aFrame);
 
+  const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset();
+  nsSVGMaskFrame *maskFrame = effectProperties.GetMaskFrame(&isOK);
+  if (!isOK) {
+    return; // Some resource is missing. We shouldn't paint anything.
+  }
   bool complexEffects = false;
   /* Check if we need to do additional operations on this child's
    * rendering, which necessitates rendering into another surface. */
-  if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)
-      || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
+  if (opacity != 1.0f ||  (clipPathFrame && !isTrivialClip)
+      || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL
+      || maskFrame || svgReset->mLayers.mImageCount > 1) {
     complexEffects = true;
     aContext.Save();
     nsRect clipRect =
       aFrame->GetVisualOverflowRectRelativeToSelf() + toUserSpace;
     aContext.Clip(NSRectToSnappedRect(clipRect,
                                   aFrame->PresContext()->AppUnitsPerDevPixel(),
                                   *drawTarget));
     aContext.PushGroup(gfxContentType::COLOR_ALPHA);
@@ -553,44 +558,75 @@ nsSVGIntegrationUtils::PaintFramesWithEf
 
   /* No more effects, we're done. */
   if (!complexEffects) {
     return;
   }
 
   aContext.PopGroupToSource();
 
+  // svg mask
+  /*nsSVGMaskFrame *maskFrame = effectProperties.GetMaskFrame(&isOK);
+  if (!isOK) {
+    return; // Some resource is missing. We shouldn't paint anything.
+  }*/
   Matrix maskTransform;
   RefPtr<SourceSurface> maskSurface =
     maskFrame ? maskFrame->GetMaskForMaskedFrame(&aContext,
                                                  aFrame, cssPxToDevPxMatrix,
                                                  opacity, &maskTransform)
               : nullptr;
 
+  // clip-path
   if (clipPathFrame && !isTrivialClip) {
     aContext.PushGroup(gfxContentType::COLOR_ALPHA);
 
     nsresult rv = clipPathFrame->ApplyClipOrPaintClipMask(aContext, aFrame, cssPxToDevPxMatrix);
     Matrix clippedMaskTransform;
     RefPtr<SourceSurface> clipMaskSurface = aContext.PopGroupToSurface(&clippedMaskTransform);
-
     if (NS_SUCCEEDED(rv) && clipMaskSurface) {
       // Still more set after clipping, so clip to another surface
-      if (maskSurface || opacity != 1.0f) {
+      if (svgReset->mLayers.mImageCount > 1 || maskSurface ||
+          opacity != 1.0f) {
         aContext.PushGroup(gfxContentType::COLOR_ALPHA);
         aContext.Mask(clipMaskSurface, clippedMaskTransform);
         aContext.PopGroupToSource();
       } else {
         aContext.Mask(clipMaskSurface, clippedMaskTransform);
       }
     }
   }
 
+  // We don't support all mask properties if mask-image property is of
+  // type <mask>
   if (maskSurface) {
     aContext.Mask(maskSurface, maskTransform);
+  // If mask-image property is of type <image>, we fully support all mask
+  // properties, except mask-mode.
+  } else if  (svgReset->mLayers.mImageCount > 1) {
+    aContext.SetMatrix(matrixAutoSaveRestore.Matrix());
+    aContext.PushGroup(gfxContentType::COLOR_ALPHA);
+
+    uint32_t flags = aBuilder->GetBackgroundPaintFlags();
+    nsRenderingContext rc(&aContext);
+    nsCSSRendering::PaintBackgroundWithSC(aFrame->PresContext(),
+                                          rc,
+                                          aFrame,
+                                          aDirtyRect,
+                                          aBorderArea,
+                                          firstFrame->StyleContext(),
+                                          *aFrame->StyleBorder(),
+                                          flags,
+                                          nullptr,
+                                          -1,
+                                          true);
+    Matrix maskMatrix;
+    RefPtr<SourceSurface> maskSurface = aContext.PopGroupToSurface(&maskMatrix);
+
+    aContext.Mask(maskSurface, maskMatrix);
   } else if (opacity != 1.0f ||
              aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
     aContext.Paint(opacity);
   }
 
   aContext.Restore();
 }
 
--- a/layout/svg/nsSVGIntegrationUtils.h
+++ b/layout/svg/nsSVGIntegrationUtils.h
@@ -124,16 +124,17 @@ public:
   HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt);
 
   /**
    * Paint non-SVG frame with SVG effects.
    */
   static void
   PaintFramesWithEffects(gfxContext& aCtx,
                          nsIFrame* aFrame, const nsRect& aDirtyRect,
+                         const nsRect& aBorderArea,
                          nsDisplayListBuilder* aBuilder,
                          mozilla::layers::LayerManager* aManager);
 
   /**
    * SVG frames expect to paint in SVG user units, which are equal to CSS px
    * units. This method provides a transform matrix to multiply onto a
    * gfxContext's current transform to convert the context's current units from
    * its usual dev pixels to SVG user units/CSS px to keep the SVG code happy.