Bug 1220629 - Part 3: Implement PushLayer/PopLayer API in cairo. r=jrmuizel draft
authorBas Schouten <bschouten@mozilla.com>
Wed, 06 Jan 2016 00:04:38 +0100
changeset 319144 058ed10659456f59761535f7538ced6df5425aec
parent 319143 97685e7ee1e9249dd465bfcee40bd7401f2a6e4e
child 319145 78f439fd806576f9c4cd55ea49f12a2994e46fc3
push id8986
push userbschouten@mozilla.com
push dateTue, 05 Jan 2016 23:23:40 +0000
reviewersjrmuizel
bugs1220629
milestone46.0a1
Bug 1220629 - Part 3: Implement PushLayer/PopLayer API in cairo. r=jrmuizel
gfx/2d/DrawTargetCairo.cpp
gfx/2d/DrawTargetCairo.h
gfx/thebes/gfxContext.cpp
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -1229,16 +1229,28 @@ DrawTargetCairo::Fill(const Path *aPath,
     return;
 
   PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
   path->SetPathOnContext(mContext);
 
   DrawPattern(aPattern, StrokeOptions(), aOptions, DRAW_FILL);
 }
 
+bool
+DrawTargetCairo::IsCurrentGroupOpaque()
+{
+  cairo_surface_t* surf = cairo_get_group_target(mContext);
+
+  if (!surf) {
+    return false;
+  }
+
+  return cairo_surface_get_content(surf) == CAIRO_CONTENT_COLOR;
+}
+
 void
 DrawTargetCairo::SetPermitSubpixelAA(bool aPermitSubpixelAA)
 {
   DrawTarget::SetPermitSubpixelAA(aPermitSubpixelAA);
 #ifdef MOZ_TREE_CAIRO
   cairo_surface_set_subpixel_antialiasing(mSurface,
     aPermitSubpixelAA ? CAIRO_SUBPIXEL_ANTIALIASING_ENABLED : CAIRO_SUBPIXEL_ANTIALIASING_DISABLED);
 #endif
@@ -1450,16 +1462,97 @@ DrawTargetCairo::PopClip()
   // so we'll save it now and restore it after the cairo_restore
   cairo_matrix_t mat;
   cairo_get_matrix(mContext, &mat);
 
   cairo_restore(mContext);
 
   cairo_set_matrix(mContext, &mat);
 }
+ 
+void
+DrawTargetCairo::PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask,
+                          const Matrix& aMaskTransform, const IntRect& aBounds,
+                          bool aCopyBackground)
+{
+  cairo_content_t content = CAIRO_CONTENT_COLOR_ALPHA;
+
+  if (mFormat == SurfaceFormat::A8) {
+    content = CAIRO_CONTENT_ALPHA;
+  } else if (aOpaque) {
+    content = CAIRO_CONTENT_COLOR;
+  }
+
+  if (aCopyBackground) {
+    cairo_surface_t* source = cairo_get_group_target(mContext);
+    cairo_push_group_with_content(mContext, content);
+    cairo_surface_t* dest = cairo_get_group_target(mContext);
+    cairo_t* ctx = cairo_create(dest);
+    cairo_set_source_surface(ctx, source, 0, 0);
+    cairo_set_operator(ctx, CAIRO_OPERATOR_SOURCE);
+    cairo_paint(ctx);
+    cairo_destroy(ctx);
+  } else {
+    cairo_push_group_with_content(mContext, content);
+  }
+
+  PushedLayer layer(aOpacity, mPermitSubpixelAA);
+
+  if (aMask) {
+    cairo_surface_t* surf = GetCairoSurfaceForSourceSurface(aMask);
+    if (surf) {
+      layer.mMaskPattern = cairo_pattern_create_for_surface(surf);
+      cairo_matrix_t mat;
+      GfxMatrixToCairoMatrix(aMaskTransform, mat);
+      cairo_matrix_invert(&mat);
+      cairo_pattern_set_matrix(layer.mMaskPattern, &mat);
+      cairo_surface_destroy(surf);
+    } else {
+      gfxCriticalError() << "Failed to get cairo surface for mask surface!";
+    }
+  }
+
+  mPushedLayers.push_back(layer);
+
+  SetPermitSubpixelAA(aOpaque);
+}
+
+void
+DrawTargetCairo::PopLayer()
+{
+  MOZ_ASSERT(mPushedLayers.size());
+
+  cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
+
+  cairo_pop_group_to_source(mContext);
+
+  PushedLayer layer = mPushedLayers.back();
+  mPushedLayers.pop_back();
+
+  if (!layer.mMaskPattern) {
+    cairo_paint_with_alpha(mContext, layer.mOpacity);
+  } else {
+    if (layer.mOpacity != Float(1.0)) {
+      cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA);
+
+      // Now draw the content using the desired operator
+      cairo_paint_with_alpha(mContext, layer.mOpacity);
+
+      cairo_pop_group_to_source(mContext);
+    }
+    cairo_mask(mContext, layer.mMaskPattern);
+  }
+
+  cairo_matrix_t mat;
+  GfxMatrixToCairoMatrix(mTransform, mat);
+  cairo_set_matrix(mContext, &mat);
+
+  cairo_pattern_destroy(layer.mMaskPattern);
+  SetPermitSubpixelAA(layer.mWasPermittingSubpixelAA);
+}
 
 already_AddRefed<PathBuilder>
 DrawTargetCairo::CreatePathBuilder(FillRule aFillRule /* = FillRule::FILL_WINDING */) const
 {
   return MakeAndAddRef<PathBuilderCairo>(aFillRule);
 }
 
 void
@@ -1767,17 +1860,17 @@ DrawTargetCairo::Init(unsigned char* aDa
                                         aStride);
   return InitAlreadyReferenced(surf, aSize);
 }
 
 void *
 DrawTargetCairo::GetNativeSurface(NativeSurfaceType aType)
 {
   if (aType == NativeSurfaceType::CAIRO_SURFACE) {
-    return cairo_get_target(mContext);
+    return cairo_get_group_target(mContext);
   }
   if (aType == NativeSurfaceType::CAIRO_CONTEXT) {
     return mContext;
   }
 
   return nullptr;
 }
 
--- a/gfx/2d/DrawTargetCairo.h
+++ b/gfx/2d/DrawTargetCairo.h
@@ -60,16 +60,18 @@ public:
   virtual ~DrawTargetCairo();
 
   virtual bool IsValid() const override;
   virtual DrawTargetType GetType() const override;
   virtual BackendType GetBackendType() const override { return BackendType::CAIRO; }
   virtual already_AddRefed<SourceSurface> Snapshot() override;
   virtual IntSize GetSize() override;
 
+  virtual bool IsCurrentGroupOpaque() override;
+
   virtual void SetPermitSubpixelAA(bool aPermitSubpixelAA) override;
 
   virtual bool LockBits(uint8_t** aData, IntSize* aSize,
                         int32_t* aStride, SurfaceFormat* aFormat) override;
   virtual void ReleaseBits(uint8_t* aData) override;
 
   virtual void Flush() override;
   virtual void DrawSurface(SourceSurface *aSurface,
@@ -129,16 +131,22 @@ public:
   virtual void MaskSurface(const Pattern &aSource,
                            SourceSurface *aMask,
                            Point aOffset,
                            const DrawOptions &aOptions = DrawOptions()) override;
 
   virtual void PushClip(const Path *aPath) override;
   virtual void PushClipRect(const Rect &aRect) override;
   virtual void PopClip() override;
+  virtual void PushLayer(bool aOpaque, Float aOpacity,
+                         SourceSurface* aMask,
+                         const Matrix& aMaskTransform,
+                         const IntRect& aBounds = IntRect(),
+                         bool aCopyBackground = false) override;
+  virtual void PopLayer() override;
 
   virtual already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule = FillRule::FILL_WINDING) const override;
 
   virtual already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
                                                             const IntSize &aSize,
                                                             int32_t aStride,
                                                             SurfaceFormat aFormat) const override;
   virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override;
@@ -209,16 +217,29 @@ private: // methods
 private: // data
   cairo_t* mContext;
   cairo_surface_t* mSurface;
   IntSize mSize;
   bool mTransformSingular;
 
   uint8_t* mLockedBits;
 
+  struct PushedLayer
+  {
+    PushedLayer(Float aOpacity, bool aWasPermittingSubpixelAA)
+      : mOpacity(aOpacity)
+      , mMaskPattern(nullptr)
+      , mWasPermittingSubpixelAA(aWasPermittingSubpixelAA)
+    {}
+    Float mOpacity;
+    cairo_pattern_t* mMaskPattern;
+    bool mWasPermittingSubpixelAA;
+  };
+  std::vector<PushedLayer> mPushedLayers;
+
   // The latest snapshot of this surface. This needs to be told when this
   // target is modified. We keep it alive as a cache.
   RefPtr<SourceSurfaceCairo> mSnapshot;
   static cairo_surface_t *mDummySurface;
 };
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/thebes/gfxContext.cpp
+++ b/gfx/thebes/gfxContext.cpp
@@ -110,18 +110,20 @@ gfxContext::~gfxContext()
 already_AddRefed<gfxASurface>
 gfxContext::CurrentSurface(gfxFloat *dx, gfxFloat *dy)
 {
   if (mDT->GetBackendType() == BackendType::CAIRO) {
     cairo_surface_t *s =
     (cairo_surface_t*)mDT->GetNativeSurface(NativeSurfaceType::CAIRO_SURFACE);
     if (s) {
       if (dx && dy) {
-        *dx = -CurrentState().deviceOffset.x;
-        *dy = -CurrentState().deviceOffset.y;
+        double sdx, sdy;
+        cairo_surface_get_device_offset(s, &sdx, &sdy);
+        *dx = -CurrentState().deviceOffset.x + sdx;
+        *dy = -CurrentState().deviceOffset.y + sdy;
       }
       return gfxASurface::Wrap(s);
     }
   }
 
   if (dx && dy) {
     *dx = *dy = 0;
   }