Bug 841601 - Add background-blend-mode implementation. r=roc
authorHoria Iosif Olaru <olaru@adobe.com>
Fri, 08 Nov 2013 10:08:03 -0500
changeset 168729 fd52ed539580d5a6480b4260d069a3900df99b2e
parent 168728 ba2614a068842563e289b8c904829b9a17c50a62
child 168730 cea821ee6e174e939e752b31e80a01738ed54774
push id3224
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:06:49 +0000
treeherdermozilla-beta@60c04d0987f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs841601
milestone28.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 841601 - Add background-blend-mode implementation. r=roc
layout/base/nsCSSRendering.cpp
layout/base/nsCSSRendering.h
layout/base/nsDisplayList.cpp
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -315,16 +315,40 @@ static void DrawBorderImageComponent(nsR
                                      const nsSize& aUnitSize,
                                      const nsStyleBorder& aStyleBorder,
                                      uint8_t aIndex);
 
 static nscolor MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
                               nscolor aBackgroundColor,
                               nscolor aBorderColor);
 
+static gfxContext::GraphicsOperator GetGFXBlendMode(uint8_t mBlendMode)
+{
+  switch (mBlendMode) {
+     case NS_STYLE_BLEND_NORMAL:      return gfxContext::OPERATOR_OVER;
+     case NS_STYLE_BLEND_MULTIPLY:    return gfxContext::OPERATOR_MULTIPLY;
+     case NS_STYLE_BLEND_SCREEN:      return gfxContext::OPERATOR_SCREEN;
+     case NS_STYLE_BLEND_OVERLAY:     return gfxContext::OPERATOR_OVERLAY;
+     case NS_STYLE_BLEND_DARKEN:      return gfxContext::OPERATOR_DARKEN;
+     case NS_STYLE_BLEND_LIGHTEN:     return gfxContext::OPERATOR_LIGHTEN;
+     case NS_STYLE_BLEND_COLOR_DODGE: return gfxContext::OPERATOR_COLOR_DODGE;
+     case NS_STYLE_BLEND_COLOR_BURN:  return gfxContext::OPERATOR_COLOR_BURN;
+     case NS_STYLE_BLEND_HARD_LIGHT:  return gfxContext::OPERATOR_HARD_LIGHT;
+     case NS_STYLE_BLEND_SOFT_LIGHT:  return gfxContext::OPERATOR_SOFT_LIGHT;
+     case NS_STYLE_BLEND_DIFFERENCE:  return gfxContext::OPERATOR_DIFFERENCE;
+     case NS_STYLE_BLEND_EXCLUSION:   return gfxContext::OPERATOR_EXCLUSION;
+     case NS_STYLE_BLEND_HUE:         return gfxContext::OPERATOR_HUE;
+     case NS_STYLE_BLEND_SATURATION:  return gfxContext::OPERATOR_SATURATION;
+     case NS_STYLE_BLEND_COLOR:       return gfxContext::OPERATOR_COLOR;
+     case NS_STYLE_BLEND_LUMINOSITY:  return gfxContext::OPERATOR_LUMINOSITY;
+  }
+
+  return gfxContext::OPERATOR_OVER;
+}
+
 static InlineBackgroundData* gInlineBGData = nullptr;
 
 // Initialize any static variables used by nsCSSRendering.
 void nsCSSRendering::Init()
 {
   NS_ASSERTION(!gInlineBGData, "Init called twice");
   gInlineBGData = new InlineBackgroundData();
 }
@@ -2556,20 +2580,28 @@ nsCSSRendering::PaintBackgroundWithSC(ns
           clipSet = true;
         }
       }
       if ((aLayer < 0 || i == (uint32_t)startLayer) &&
           !clipState.mDirtyRectGfx.IsEmpty()) {
         nsBackgroundLayerState state = PrepareBackgroundLayer(aPresContext, aForFrame,
             aFlags, aBorderArea, clipState.mBGClipArea, *bg, layer);
         if (!state.mFillArea.IsEmpty()) {
+          if (state.mCompositingOp != gfxContext::OPERATOR_OVER) {
+            NS_ASSERTION(ctx->CurrentOperator() == gfxContext::OPERATOR_OVER,
+                         "It is assumed the initial operator is OPERATOR_OVER, when it is restored later");
+            ctx->SetOperator(state.mCompositingOp);
+          }
           state.mImageRenderer.DrawBackground(aPresContext, aRenderingContext,
                                               state.mDestArea, state.mFillArea,
                                               state.mAnchor + aBorderArea.TopLeft(),
                                               clipState.mDirtyRect);
+          if (state.mCompositingOp != gfxContext::OPERATOR_OVER) {
+            ctx->SetOperator(gfxContext::OPERATOR_OVER);
+          }
         }
       }
     }
   }
 }
 
 void
 nsCSSRendering::PaintBackgroundColorWithSC(nsPresContext* aPresContext,
@@ -2851,16 +2883,17 @@ nsCSSRendering::PrepareBackgroundLayer(n
    *   background-image
    *   background-repeat
    *   background-attachment
    *   background-position
    *   background-clip
    *   background-origin
    *   background-size
    *   background-break (-moz-background-inline-policy)
+   *   background-blend-mode
    *
    * (background-color applies to the entire element and not to individual
    * layers, so it is irrelevant to this method.)
    *
    * These properties have the following dependencies upon each other when
    * determining rendering:
    *
    *   background-image
@@ -2973,16 +3006,19 @@ nsCSSRendering::PrepareBackgroundLayer(n
     state.mFillArea.x = bgClipRect.x;
     state.mFillArea.width = bgClipRect.width;
   }
   if (repeatY == NS_STYLE_BG_REPEAT_REPEAT) {
     state.mFillArea.y = bgClipRect.y;
     state.mFillArea.height = bgClipRect.height;
   }
   state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
+
+  state.mCompositingOp = GetGFXBlendMode(aLayer.mBlendMode);
+
   return state;
 }
 
 nsRect
 nsCSSRendering::GetBackgroundLayerRect(nsPresContext* aPresContext,
                                        nsIFrame* aForFrame,
                                        const nsRect& aBorderArea,
                                        const nsRect& aClipRect,
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -208,17 +208,17 @@ private:
  * image to some target, taking into account all CSS background-* properties.
  * See PrepareBackgroundLayer.
  */
 struct nsBackgroundLayerState {
   /**
    * @param aFlags some combination of nsCSSRendering::PAINTBG_* flags
    */
   nsBackgroundLayerState(nsIFrame* aForFrame, const nsStyleImage* aImage, uint32_t aFlags)
-    : mImageRenderer(aForFrame, aImage, aFlags) {}
+    : mImageRenderer(aForFrame, aImage, aFlags), mCompositingOp(gfxContext::OPERATOR_OVER) {}
 
   /**
    * The nsImageRenderer that will be used to draw the background.
    */
   nsImageRenderer mImageRenderer;
   /**
    * A rectangle that one copy of the image tile is mapped onto. Same
    * coordinate system as aBorderArea/aBGClipRect passed into
@@ -232,16 +232,20 @@ struct nsBackgroundLayerState {
    */
   nsRect mFillArea;
   /**
    * The anchor point that should be snapped to a pixel corner. Same
    * coordinate system as aBorderArea/aBGClipRect passed into
    * PrepareBackgroundLayer.
    */
   nsPoint mAnchor;
+  /**
+   * The compositing operation that the image should use
+   */
+  gfxContext::GraphicsOperator mCompositingOp;
 };
 
 struct nsCSSRendering {
   /**
    * Initialize any static variables used by nsCSSRendering.
    */
   static void Init();
   
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1734,47 +1734,66 @@ nsDisplayBackgroundImage::AppendBackgrou
   nscolor color;
   if (!nsCSSRendering::IsCanvasFrame(aFrame) && bg) {
     bool drawBackgroundImage;
     color =
       nsCSSRendering::DetermineBackgroundColor(presContext, bgSC, aFrame,
                                                drawBackgroundImage, drawBackgroundColor);
   }
 
+  // An auxiliary list is necessary in case we have background blending; if that
+  // is the case, background items need to be wrapped by a blend container to
+  // isolate blending to the background
+  nsDisplayList bgItemList;
   // Even if we don't actually have a background color to paint, we may still need
   // to create an item for hit testing.
   if ((drawBackgroundColor && color != NS_RGBA(0,0,0,0)) ||
       aBuilder->IsForEventDelivery()) {
-    aList->AppendNewToTop(
+    bgItemList.AppendNewToTop(
         new (aBuilder) nsDisplayBackgroundColor(aBuilder, aFrame, bg,
                                                 drawBackgroundColor ? color : NS_RGBA(0, 0, 0, 0)));
   }
 
   if (isThemed) {
     nsDisplayThemedBackground* bgItem =
       new (aBuilder) nsDisplayThemedBackground(aBuilder, aFrame);
-    aList->AppendNewToTop(bgItem);
+    bgItemList.AppendNewToTop(bgItem);
+    aList->AppendToTop(&bgItemList);
     return true;
   }
 
   if (!bg) {
+    aList->AppendToTop(&bgItemList);
     return false;
   }
  
+  bool needBlendContainer = false;
+
   // Passing bg == nullptr in this macro will result in one iteration with
   // i = 0.
   NS_FOR_VISIBLE_BACKGROUND_LAYERS_BACK_TO_FRONT(i, bg) {
     if (bg->mLayers[i].mImage.IsEmpty()) {
       continue;
     }
+
+    if (bg->mLayers[i].mBlendMode != NS_STYLE_BLEND_NORMAL) {
+      needBlendContainer = true;
+    }
+
     nsDisplayBackgroundImage* bgItem =
       new (aBuilder) nsDisplayBackgroundImage(aBuilder, aFrame, i, bg);
-    aList->AppendNewToTop(bgItem);
-  }
-
+    bgItemList.AppendNewToTop(bgItem);
+  }
+
+  if (needBlendContainer) {
+    bgItemList.AppendNewToTop(
+      new (aBuilder) nsDisplayBlendContainer(aBuilder, aFrame, &bgItemList));
+  }
+
+  aList->AppendToTop(&bgItemList);
   return false;
 }
 
 // Check that the rounded border of aFrame, added to aToReferenceFrame,
 // intersects aRect.  Assumes that the unrounded border has already
 // been checked for intersection.
 static bool
 RoundedBorderIntersectsRect(nsIFrame* aFrame,
@@ -2086,17 +2105,17 @@ nsDisplayBackgroundImage::GetOpaqueRegio
   // For policies other than EACH_BOX, don't try to optimize here, since
   // this could easily lead to O(N^2) behavior inside InlineBackgroundData,
   // which expects frames to be sent to it in content order, not reverse
   // content order which we'll produce here.
   // Of course, if there's only one frame in the flow, it doesn't matter.
   if (mBackgroundStyle->mBackgroundInlinePolicy == NS_STYLE_BG_INLINE_POLICY_EACH_BOX ||
       (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) {
     const nsStyleBackground::Layer& layer = mBackgroundStyle->mLayers[mLayer];
-    if (layer.mImage.IsOpaque()) {
+    if (layer.mImage.IsOpaque() && layer.mBlendMode == NS_STYLE_BLEND_NORMAL) {
       nsPresContext* presContext = mFrame->PresContext();
       result = GetInsideClipRegion(this, presContext, layer.mClip, mBounds, aSnap);
     }
   }
 
   return result;
 }