Bug 1274236 - Part 2. Prototype. draft
authorcku <cku@mozilla.com>
Tue, 04 Oct 2016 16:26:43 +0800
changeset 420529 eacced6b1baa45559cf06f7e3b85fe1e219838cc
parent 420528 213738b1f3983089befaae79f83c5d5f3701d887
child 532834 ac157dcfc8fa55913c946217aa3ad90153052e79
push id31224
push userbmo:cku@mozilla.com
push dateTue, 04 Oct 2016 09:01:22 +0000
bugs1274236
milestone52.0a1
Bug 1274236 - Part 2. Prototype. MozReview-Commit-ID: LWP0umyfOcs
layout/reftests/w3c-css/submitted/masking/mask-image-3a.html
layout/svg/nsSVGIntegrationUtils.cpp
layout/svg/nsSVGMaskFrame.cpp
layout/svg/nsSVGMaskFrame.h
--- a/layout/reftests/w3c-css/submitted/masking/mask-image-3a.html
+++ b/layout/reftests/w3c-css/submitted/masking/mask-image-3a.html
@@ -4,20 +4,20 @@
     <meta charset="utf-8">
     <title>CSS Masking: mask-image: multiple SVG masks</title>
     <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
     <link rel="author" title="Mozilla" href="https://www.mozilla.org">
     <link rel="help" href="https://www.w3.org/TR/css-masking-1/#the-mask-image">
     <link rel="match" href="mask-image-3-ref.html">
     <meta name="assert" content="Test checks whether SVG mask as mask layer works correctly or not.">
     <svg height="0">
-      <mask id="mask1" x="0" y="0" width="100" height="100" >
+      <mask id="mask1" x="0" y="0" width="100" height="100">
         <rect x="0" y="0" width="50" height="50" style="stroke:none; fill: #ffffff"/>
       </mask>
-      <mask id="mask2" x="0" y="0" width="100" height="100" >
+      <mask id="mask2" x="0" y="0" width="100" height="100">
         <circle cx="50" cy="50" r="25" style="stroke:none; fill: #ffffff"/>
       </mask>
     </svg>
     <style type="text/css">
       div {
         background-color: purple;
         mask-image: url(#mask1), url(#mask2);
         width: 100px;
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -573,31 +573,23 @@ GenerateMaskSurface(const PaintFramesPar
       ? CompositionOp::OP_OVER
       : nsCSSRendering::GetGFXCompositeMode(svgReset->mMask.mLayers[i].mComposite);
 
     // maskFrame != nullptr means we get a SVG mask.
     // maskFrame == nullptr means we get an image mask.
     if (maskFrame) {
       Matrix svgMaskMatrix;
       nsSVGMaskFrame::MaskFrameParams params(maskContext, aParams.frame,
-                                           cssPxToDevPxMatrix,
-                                           aOpacityApplied ? aOpacity : 1.0,
-                                           &svgMaskMatrix,
-                                           svgReset->mMask.mLayers[i].mMaskMode);
-      RefPtr<SourceSurface> svgMask =
-        maskFrame->GetMaskForMaskedFrame(params);
-      if (svgMask) {
-        gfxContextMatrixAutoSaveRestore matRestore(maskContext);
-
-        maskContext->Multiply(ThebesMatrix(svgMaskMatrix));
-        Rect drawRect = IntRectToRect(IntRect(IntPoint(0, 0), svgMask->GetSize()));
-        maskDT->MaskSurface(ColorPattern(Color(0.0, 0.0, 0.0, 1.0)), svgMask,
-                            drawRect.TopLeft(),
-                            DrawOptions(1.0, compositionOp));
-      }
+                                             cssPxToDevPxMatrix,
+                                             aOpacityApplied ? aOpacity : 1.0,
+                                             &svgMaskMatrix,
+                                             svgReset->mMask.mLayers[i].mMaskMode,
+                                             maskDT,
+                                             compositionOp);
+      RefPtr<SourceSurface> svgMask = maskFrame->GetMaskForMaskedFrame(params);
     } else {
       gfxContextMatrixAutoSaveRestore matRestore(maskContext);
 
       maskContext->Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace));
       nsRenderingContext rc(maskContext);
       nsCSSRendering::PaintBGParams  params =
         nsCSSRendering::PaintBGParams::ForSingleLayer(*presContext,
                                                       rc, aParams.dirtyRect,
--- a/layout/svg/nsSVGMaskFrame.cpp
+++ b/layout/svg/nsSVGMaskFrame.cpp
@@ -54,16 +54,63 @@ 147, 149, 151, 152, 154, 156, 157, 159,
 161, 163, 164, 166, 168, 170, 171, 173,
 175, 177, 179, 181, 183, 184, 186, 188,
 190, 192, 194, 196, 198, 200, 202, 204,
 206, 208, 210, 212, 214, 216, 218, 220,
 222, 224, 226, 229, 231, 233, 235, 237,
 239, 242, 244, 246, 248, 250, 253, 255
 };
 
+struct CompositionNone {
+  static uint8_t Compose(uint8_t aSourceAlpha, uint8_t aDestAlpah,
+                         float aOpacity)
+  {
+    // No blend.
+    return aSourceAlpha * aOpacity;
+  }
+};
+
+struct CompositionOver {
+  static uint8_t Compose(uint8_t aSourceAlpha, uint8_t aDestAlpah,
+                         float aOpacity)
+  {
+    // αo = αs + αb x (1 – αs)
+    return aSourceAlpha + aDestAlpah * ((255 - aSourceAlpha) / 255.0) * aOpacity;
+  }
+};
+
+struct CompositionSubtract {
+  static uint8_t Compose(uint8_t aSourceAlpha, uint8_t aDestAlpah,
+                         float aOpacity)
+  {
+    // αo = αs x (1 – αb)
+    return aSourceAlpha * ((255 - aDestAlpah) / 255.0) * aOpacity;
+  }
+};
+
+struct CompositionIntersect {
+  static uint8_t Compose(uint8_t aSourceAlpha, uint8_t aDestAlpah,
+                         float aOpacity)
+  {
+    // αo = αs x αb
+    return aSourceAlpha * (aSourceAlpha / 255.0) * aOpacity;
+  }
+};
+
+struct CompositionExclude {
+  static uint8_t Compose(uint8_t aSourceAlpha, uint8_t aDestAlpah,
+                         float aOpacity)
+  {
+    // αo = αs x (1 - αb) + αb x (1 – αs)
+    return (aSourceAlpha * ((255 - aDestAlpah) / 255.0) +
+            aDestAlpah * ((255 - aSourceAlpha) / 255.0))* aOpacity;
+  }
+};
+
+template<typename CompositionT>
 static void
 ComputesRGBLuminanceMask(const uint8_t *aSourceData,
                          int32_t aSourceStride,
                          uint8_t *aDestData,
                          int32_t aDestStride,
                          const IntSize &aSize,
                          float aOpacity)
 {
@@ -84,31 +131,98 @@ ComputesRGBLuminanceMask(const uint8_t *
   int32_t destOffset = aDestStride - aSize.width;
   uint8_t *destPixel = aDestData;
 
   for (int32_t y = 0; y < aSize.height; y++) {
     for (int32_t x = 0; x < aSize.width; x++) {
       uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A];
 
       if (a) {
-        *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R] +
-                      greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] +
-                      blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> 8;
+        uint8_t sourceAlpha =
+          (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R] +
+           greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] +
+           blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> 8;
+        *destPixel = CompositionT::Compose(sourceAlpha, *destPixel, aOpacity);
       } else {
-        *destPixel = 0;
+        *destPixel = CompositionT::Compose(0, *destPixel, aOpacity);
       }
       sourcePixel += 4;
       destPixel++;
     }
     sourcePixel += sourceOffset;
     destPixel += destOffset;
   }
 }
 
 static void
+ComputesRGBLuminanceMask(const uint8_t *aSourceData,
+                         int32_t aSourceStride,
+                         uint8_t *aDestData,
+                         int32_t aDestStride,
+                         const IntSize &aSize,
+                         float aOpacity,
+                         CompositionOp compositionOp,
+                         bool aBlendTarget)
+{
+  if (aBlendTarget) {
+    switch (compositionOp) {
+      case CompositionOp::OP_OVER:
+        ComputesRGBLuminanceMask<CompositionOver>(aSourceData,
+                                                  aSourceStride,
+                                                  aDestData,
+                                                  aDestStride,
+                                                  aSize,
+                                                  aOpacity);
+        break;
+      case CompositionOp::OP_OUT:
+        ComputesRGBLuminanceMask<CompositionSubtract>(aSourceData,
+                                                      aSourceStride,
+                                                      aDestData,
+                                                      aDestStride,
+                                                      aSize,
+                                                      aOpacity);
+        break;
+      case CompositionOp::OP_IN:
+        ComputesRGBLuminanceMask<CompositionIntersect>(aSourceData,
+                                                       aSourceStride,
+                                                       aDestData,
+                                                       aDestStride,
+                                                       aSize,
+                                                       aOpacity);
+        break;
+      case CompositionOp::OP_XOR:
+        ComputesRGBLuminanceMask<CompositionExclude>(aSourceData,
+                                                     aSourceStride,
+                                                     aDestData,
+                                                     aDestStride,
+                                                     aSize,
+                                                     aOpacity);
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE("unexpected composition type!");
+        ComputesRGBLuminanceMask<CompositionOver>(aSourceData,
+                                                  aSourceStride,
+                                                  aDestData,
+                                                  aDestStride,
+                                                  aSize,
+                                                  aOpacity);
+        break;
+    }
+  } else {
+    ComputesRGBLuminanceMask<CompositionNone>(aSourceData,
+                                                       aSourceStride,
+                                                       aDestData,
+                                                       aDestStride,
+                                                       aSize,
+                                                       aOpacity);
+  }
+}
+
+template<typename CompositionT>
+static void
 ComputeLinearRGBLuminanceMask(const uint8_t *aSourceData,
                               int32_t aSourceStride,
                               uint8_t *aDestData,
                               int32_t aDestStride,
                               const IntSize &aSize,
                               float aOpacity)
 {
   int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
@@ -122,78 +236,207 @@ ComputeLinearRGBLuminanceMask(const uint
   for (int32_t y = 0; y < aSize.height; y++) {
     for (int32_t x = 0; x < aSize.width; x++) {
       uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A];
 
       // unpremultiply
       if (a) {
         if (a == 255) {
           /* sRGB -> linearRGB -> intensity */
-          *destPixel =
+          uint8_t sourceAlpha =
             static_cast<uint8_t>
                        ((gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_R]] *
                          redFactor +
                          gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_G]] *
                          greenFactor +
                          gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_B]] *
                          blueFactor) >> 8);
+          *destPixel = CompositionT::Compose(sourceAlpha, *destPixel, aOpacity);
         } else {
           uint8_t tempPixel[4];
           tempPixel[GFX_ARGB32_OFFSET_B] =
             (255 * sourcePixel[GFX_ARGB32_OFFSET_B]) / a;
           tempPixel[GFX_ARGB32_OFFSET_G] =
             (255 * sourcePixel[GFX_ARGB32_OFFSET_G]) / a;
           tempPixel[GFX_ARGB32_OFFSET_R] =
             (255 * sourcePixel[GFX_ARGB32_OFFSET_R]) / a;
 
           /* sRGB -> linearRGB -> intensity */
-          *destPixel =
+          uint8_t sourceAlpha =
             static_cast<uint8_t>
                        (((gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_R]] *
                           redFactor +
                           gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_G]] *
                           greenFactor +
                           gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_B]] *
                           blueFactor) >> 8) * (a / 255.0f));
+          *destPixel = CompositionT::Compose(sourceAlpha, *destPixel, aOpacity);
         }
       } else {
-        *destPixel = 0;
+        *destPixel = CompositionT::Compose(0, *destPixel, aOpacity);
       }
       sourcePixel += 4;
       destPixel++;
     }
     sourcePixel += sourceOffset;
     destPixel += destOffset;
   }
 }
 
 static void
+ComputeLinearRGBLuminanceMask(const uint8_t *aSourceData,
+                         int32_t aSourceStride,
+                         uint8_t *aDestData,
+                         int32_t aDestStride,
+                         const IntSize &aSize,
+                         float aOpacity,
+                         CompositionOp compositionOp,
+                         bool aBlendTarget)
+{
+  if (aBlendTarget) {
+    switch (compositionOp) {
+      case CompositionOp::OP_OVER:
+        ComputeLinearRGBLuminanceMask<CompositionOver>(aSourceData,
+                                                       aSourceStride,
+                                                       aDestData,
+                                                       aDestStride,
+                                                       aSize,
+                                                       aOpacity);
+        break;
+      case CompositionOp::OP_OUT:
+        ComputeLinearRGBLuminanceMask<CompositionSubtract>(aSourceData,
+                                                           aSourceStride,
+                                                           aDestData,
+                                                           aDestStride,
+                                                           aSize,
+                                                           aOpacity);
+        break;
+      case CompositionOp::OP_IN:
+        ComputeLinearRGBLuminanceMask<CompositionIntersect>(aSourceData,
+                                                            aSourceStride,
+                                                            aDestData,
+                                                            aDestStride,
+                                                            aSize,
+                                                            aOpacity);
+        break;
+      case CompositionOp::OP_XOR:
+        ComputeLinearRGBLuminanceMask<CompositionExclude>(aSourceData,
+                                                          aSourceStride,
+                                                          aDestData,
+                                                          aDestStride,
+                                                          aSize,
+                                                          aOpacity);
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE("unexpected composition type!");
+        ComputeLinearRGBLuminanceMask<CompositionOver>(aSourceData,
+                                                       aSourceStride,
+                                                       aDestData,
+                                                       aDestStride,
+                                                       aSize,
+                                                       aOpacity);
+        break;
+    }
+  } else {
+    ComputeLinearRGBLuminanceMask<CompositionNone>(aSourceData,
+                                                   aSourceStride,
+                                                   aDestData,
+                                                   aDestStride,
+                                                   aSize,
+                                                   aOpacity);
+  }
+}
+
+template<typename CompositionT>
+static void
 ComputeAlphaMask(const uint8_t *aSourceData,
                  int32_t aSourceStride,
                  uint8_t *aDestData,
                  int32_t aDestStride,
                  const IntSize &aSize,
                  float aOpacity)
 {
   int32_t sourceOffset = aSourceStride - 4 * aSize.width;
   const uint8_t *sourcePixel = aSourceData;
   int32_t destOffset = aDestStride - aSize.width;
   uint8_t *destPixel = aDestData;
 
   for (int32_t y = 0; y < aSize.height; y++) {
     for (int32_t x = 0; x < aSize.width; x++) {
-      *destPixel = sourcePixel[GFX_ARGB32_OFFSET_A] * aOpacity;
+      *destPixel = CompositionT::Compose(sourcePixel[GFX_ARGB32_OFFSET_A],
+                                         *destPixel,
+                                         aOpacity);
       sourcePixel += 4;
       destPixel++;
     }
     sourcePixel += sourceOffset;
     destPixel += destOffset;
   }
 }
 
+static void
+ComputeAlphaMask(const uint8_t *aSourceData,
+                 int32_t aSourceStride,
+                 uint8_t *aDestData,
+                 int32_t aDestStride,
+                 const IntSize &aSize,
+                 float aOpacity,
+                 CompositionOp compositionOp,
+                 bool aBlendTarget)
+{
+  if (aBlendTarget) {
+    switch (compositionOp) {
+      case CompositionOp::OP_OVER:
+        ComputeAlphaMask<CompositionOver>(aSourceData,
+                                          aSourceStride,
+                                          aDestData,
+                                          aDestStride,
+                                          aSize,
+                                          aOpacity);
+        break;
+      case CompositionOp::OP_OUT:
+        ComputeAlphaMask<CompositionSubtract>(aSourceData,
+                                              aSourceStride,
+                                              aDestData,
+                                              aDestStride,
+                                              aSize,
+                                              aOpacity);
+        break;
+      case CompositionOp::OP_IN:
+        ComputeAlphaMask<CompositionIntersect>(aSourceData,
+                                               aSourceStride,
+                                               aDestData,
+                                               aDestStride,
+                                               aSize,
+                                               aOpacity);
+        break;
+      case CompositionOp::OP_XOR:
+        ComputeAlphaMask<CompositionExclude>(aSourceData,
+                                             aSourceStride,
+                                             aDestData,
+                                             aDestStride,
+                                             aSize,
+                                             aOpacity);
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE("unexpected composition type!");
+        ComputeAlphaMask<CompositionOver>(aSourceData,
+                                          aSourceStride,
+                                          aDestData,
+                                          aDestStride,
+                                          aSize,
+                                          aOpacity);
+        break;
+    }
+  } else {
+    ComputeAlphaMask<CompositionNone>(aSourceData, aSourceStride, aDestData,
+                                      aDestStride, aSize, aOpacity);
+  }
+}
+
 //----------------------------------------------------------------------
 // Implementation
 
 nsIFrame*
 NS_NewSVGMaskFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsSVGMaskFrame(aContext);
 }
@@ -277,19 +520,20 @@ nsSVGMaskFrame::GetMaskForMaskedFrame(co
   }
   RefPtr<DataSourceSurface> maskSurface = maskSnapshot->GetDataSurface();
   DataSourceSurface::MappedSurface map;
   if (!maskSurface->Map(DataSourceSurface::MapType::READ, &map)) {
     return nullptr;
   }
 
   // Create alpha channel mask for output
-  RefPtr<DrawTarget> destMaskDT =
-    Factory::CreateDrawTarget(BackendType::CAIRO, maskSurfaceSize,
-                              SurfaceFormat::A8);
+  RefPtr<DrawTarget> destMaskDT = aParams.maskDT
+    ? RefPtr<DrawTarget>(aParams.maskDT).forget()
+    : Factory::CreateDrawTarget(BackendType::CAIRO, maskSurfaceSize,
+                                SurfaceFormat::A8);
   if (!destMaskDT) {
     return nullptr;
   }
   RefPtr<SourceSurface> destMaskSnapshot = destMaskDT->Snapshot();
   if (!destMaskSnapshot) {
     return nullptr;
   }
   RefPtr<DataSourceSurface> destMaskSurface = destMaskSnapshot->GetDataSurface();
@@ -304,28 +548,30 @@ nsSVGMaskFrame::GetMaskForMaskedFrame(co
   } else {
     maskType = aParams.maskOp == NS_STYLE_MASK_MODE_LUMINANCE ?
                  NS_STYLE_MASK_TYPE_LUMINANCE : NS_STYLE_MASK_TYPE_ALPHA;
   }
 
   if (maskType == NS_STYLE_MASK_TYPE_LUMINANCE) {
     if (StyleSVG()->mColorInterpolation ==
         NS_STYLE_COLOR_INTERPOLATION_LINEARRGB) {
-      ComputeLinearRGBLuminanceMask(map.mData, map.mStride,
-                                    destMap.mData, destMap.mStride,
-                                    maskSurfaceSize, aParams.opacity);
+      ComputeLinearRGBLuminanceMask(map.mData, map.mStride, destMap.mData,
+                                    destMap.mStride, maskSurfaceSize,
+                                    aParams.opacity, aParams.compositionOp,
+                                    aParams.maskDT);
     } else {
-      ComputesRGBLuminanceMask(map.mData, map.mStride,
-                               destMap.mData, destMap.mStride,
-                               maskSurfaceSize, aParams.opacity);
+      ComputesRGBLuminanceMask(map.mData, map.mStride, destMap.mData,
+                               destMap.mStride, maskSurfaceSize,
+                               aParams.opacity, aParams.compositionOp,
+                               aParams.maskDT);
     }
   } else {
-      ComputeAlphaMask(map.mData, map.mStride,
-                       destMap.mData, destMap.mStride,
-                       maskSurfaceSize, aParams.opacity);
+    ComputeAlphaMask(map.mData, map.mStride, destMap.mData, destMap.mStride,
+                     maskSurfaceSize, aParams.opacity, aParams.compositionOp,
+                     aParams.maskDT);
   }
 
   maskSurface->Unmap();
   destMaskSurface->Unmap();
 
   // Moz2D transforms in the opposite direction to Thebes
   if (!maskSurfaceMatrix.Invert()) {
     return nullptr;
--- a/layout/svg/nsSVGMaskFrame.h
+++ b/layout/svg/nsSVGMaskFrame.h
@@ -52,23 +52,29 @@ public:
 
   struct MaskFrameParams {
     gfxContext* context;
     nsIFrame* maskedFrame;
     const gfxMatrix& cssPxToDevPxMatrix;
     float opacity;
     Matrix* maskTransform;
     uint8_t maskOp;
+    DrawTarget* maskDT;
+    CompositionOp compositionOp;
+
     MaskFrameParams(gfxContext* aContext, nsIFrame* aMaskedFrame,
                     const gfxMatrix& aCssPxToDevPxMatrix, float aOpacity,
                     Matrix* aMaskTransform,
-                    uint8_t aMaskOp= NS_STYLE_MASK_MODE_MATCH_SOURCE)
+                    uint8_t aMaskOp= NS_STYLE_MASK_MODE_MATCH_SOURCE,
+                    DrawTarget* aMaskDT = nullptr,
+                    CompositionOp aCompositionOp = CompositionOp::OP_OVER)
       : context(aContext), maskedFrame(aMaskedFrame),
         cssPxToDevPxMatrix(aCssPxToDevPxMatrix),
-        opacity(opacity), maskTransform(aMaskTransform), maskOp(aMaskOp)
+        opacity(aOpacity), maskTransform(aMaskTransform), maskOp(aMaskOp),
+        maskDT(aMaskDT), compositionOp(aCompositionOp)
     {}
   };
 
   // nsSVGMaskFrame method:
   already_AddRefed<SourceSurface>
   GetMaskForMaskedFrame(const MaskFrameParams& aParams);
 
   gfxRect