Bug 764125; drawing changes to DrawTargetCairo; r=jrmuizel
authorNicholas Cameron <ncameron@mozilla.com>
Tue, 24 Jul 2012 22:18:38 +1200
changeset 100535 436db785018e693e5bf1054f0cd600f22d6ffa05
parent 100534 0aaf4ca5e3b1d4984c50b4963f9d9135e88b7a50
child 100536 b6d09d53d37c2c3ee65ba00f3545eececc07bef0
push idunknown
push userunknown
push dateunknown
reviewersjrmuizel
bugs764125
milestone17.0a1
Bug 764125; drawing changes to DrawTargetCairo; r=jrmuizel
gfx/2d/DrawTargetCairo.cpp
gfx/2d/DrawTargetCairo.h
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -9,16 +9,17 @@
 #include "PathCairo.h"
 #include "HelpersCairo.h"
 #include "ScaledFontBase.h"
 
 #include "cairo.h"
 
 #include "Blur.h"
 #include "Logging.h"
+#include "Tools.h"
 
 #ifdef CAIRO_HAS_QUARTZ_SURFACE
 #include "cairo-quartz.h"
 #include <ApplicationServices/ApplicationServices.h>
 #endif
 
 #ifdef CAIRO_HAS_XLIB_SURFACE
 #include "cairo-xlib.h"
@@ -214,16 +215,31 @@ GfxPatternToCairoPattern(const Pattern& 
       // We should support all pattern types!
       MOZ_ASSERT(false);
     }
   }
 
   return pat;
 }
 
+/**
+ * Returns true iff the the given operator should affect areas of the
+ * destination where the source is transparent. Among other things, this
+ * implies that a fully transparent source would still affect the canvas.
+ */
+static bool
+OperatorAffectsUncoveredAreas(CompositionOp op)
+{
+  return op == OP_IN ||
+         op == OP_OUT ||
+         op == OP_DEST_IN ||
+         op == OP_DEST_ATOP ||
+         op == OP_DEST_OUT;
+}
+
 static bool
 NeedIntermediateSurface(const Pattern& aPattern, const DrawOptions& aOptions)
 {
   // We pre-multiply colours' alpha by the global alpha, so we don't need to
   // use an intermediate surface for them.
   if (aPattern.GetType() == PATTERN_COLOR)
     return false;
 
@@ -254,28 +270,24 @@ IntSize
 DrawTargetCairo::GetSize()
 {
   return mSize;
 }
 
 TemporaryRef<SourceSurface>
 DrawTargetCairo::Snapshot()
 {
-  cairo_surface_t* csurf = cairo_get_target(mContext);
-  IntSize size;
-  if (GetCairoSurfaceSize(csurf, size)) {
-    cairo_content_t content = cairo_surface_get_content(csurf);
-    RefPtr<SourceSurfaceCairo> surf = new SourceSurfaceCairo(csurf, size,
-                                                             CairoContentToGfxFormat(content),
-                                                             this);
-    AppendSnapshot(surf);
-    return surf;
-  }
+  IntSize size = GetSize();
 
-  return NULL;
+  cairo_content_t content = cairo_surface_get_content(mSurface);
+  RefPtr<SourceSurfaceCairo> surf = new SourceSurfaceCairo(mSurface, size,
+                                                           CairoContentToGfxFormat(content),
+                                                           this);
+  AppendSnapshot(surf);
+  return surf;
 }
 
 void
 DrawTargetCairo::Flush()
 {
   cairo_surface_t* surf = cairo_get_target(mContext);
   cairo_surface_flush(surf);
 }
@@ -294,82 +306,94 @@ DrawTargetCairo::DrawSurface(SourceSurfa
                              const DrawOptions &aOptions)
 {
   AutoPrepareForDrawing prep(this, mContext);
 
   float sx = aSource.Width() / aDest.Width();
   float sy = aSource.Height() / aDest.Height();
 
   cairo_matrix_t src_mat;
-  cairo_matrix_init_scale(&src_mat, sx, sy);
-  cairo_matrix_translate(&src_mat, aSource.X(), aSource.Y());
+  cairo_matrix_init_translate(&src_mat, aSource.X(), aSource.Y());
+  cairo_matrix_scale(&src_mat, sx, sy);
 
   cairo_surface_t* surf = NULL;
   if (aSurface->GetType() == SURFACE_CAIRO) {
     surf = static_cast<SourceSurfaceCairo*>(aSurface)->GetSurface();
   }
 
   cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf);
   cairo_pattern_set_matrix(pat, &src_mat);
   cairo_pattern_set_filter(pat, GfxFilterToCairoFilter(aSurfOptions.mFilter));
 
   cairo_save(mContext);
+  cairo_translate(mContext, aDest.X(), aDest.Y());
+
+  if (OperatorAffectsUncoveredAreas(aOptions.mCompositionOp) ||
+      aOptions.mCompositionOp == OP_SOURCE) {
+    cairo_push_group(mContext);
+      cairo_new_path(mContext);
+      cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height());
+      //TODO[nrc] remove comments if test ok
+      //cairo_clip(mContext);
+      cairo_set_source(mContext, pat);
+      //cairo_paint(mContext);
+      cairo_fill(mContext);
+    cairo_pop_group_to_source(mContext);
+  } else {
+    cairo_new_path(mContext);
+    cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height());
+    cairo_clip(mContext);
+    cairo_set_source(mContext, pat);
+  }
 
   cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
 
-  cairo_translate(mContext, aDest.X(), aDest.Y());
-
-  cairo_set_source(mContext, pat);
-
-  cairo_new_path(mContext);
-  cairo_rectangle(mContext, 0, 0, aDest.Width(), aDest.Height());
-  cairo_clip(mContext);
   cairo_paint_with_alpha(mContext, aOptions.mAlpha);
 
   cairo_restore(mContext);
 
   cairo_pattern_destroy(pat);
 }
 
 void
 DrawTargetCairo::DrawSurfaceWithShadow(SourceSurface *aSurface,
                                        const Point &aDest,
                                        const Color &aColor,
                                        const Point &aOffset,
                                        Float aSigma,
                                        CompositionOp aOperator)
 {
-  WillChange();
-
   if (aSurface->GetType() != SURFACE_CAIRO) {
     return;
   }
 
-  SourceSurfaceCairo* source = static_cast<SourceSurfaceCairo*>(aSurface);
+  WillChange();
 
   Float width = aSurface->GetSize().width;
   Float height = aSurface->GetSize().height;
   Rect extents(0, 0, width, height);
 
   AlphaBoxBlur blur(extents, IntSize(0, 0),
                     AlphaBoxBlur::CalculateBlurRadius(Point(aSigma, aSigma)),
                     NULL, NULL);
   if (!blur.GetData()) {
     return;
   }
 
   IntSize blursize = blur.GetSize();
-
   cairo_surface_t* blursurf = cairo_image_surface_create_for_data(blur.GetData(),
                                                                   CAIRO_FORMAT_A8,
                                                                   blursize.width,
                                                                   blursize.height,
                                                                   blur.GetStride());
 
+  ClearSurfaceForUnboundedSource(aOperator);
+  
   // Draw the source surface into the surface we're going to blur.
+  SourceSurfaceCairo* source = static_cast<SourceSurfaceCairo*>(aSurface);
   cairo_surface_t* surf = source->GetSurface();
   cairo_pattern_t* pat = cairo_pattern_create_for_surface(surf);
 
   cairo_t* ctx = cairo_create(blursurf);
 
   cairo_set_source(ctx, pat);
 
   IntRect blurrect = blur.GetRect();
@@ -378,36 +402,49 @@ DrawTargetCairo::DrawSurfaceWithShadow(S
   cairo_clip(ctx);
   cairo_paint(ctx);
 
   cairo_destroy(ctx);
 
   // Blur the result, then use that blurred result as a mask to draw the shadow
   // colour to the surface.
   blur.Blur();
-
   cairo_save(mContext);
-
-  cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
-  cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a);
-
+  cairo_set_operator(mContext, GfxOpToCairoOp(aOperator));
   cairo_identity_matrix(mContext);
   cairo_translate(mContext, aDest.x, aDest.y);
 
-  cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y);
-
-  // Now that the shadow has been drawn, we can draw the surface on top.
-
-  cairo_set_operator(mContext, GfxOpToCairoOp(aOperator));
+  if (OperatorAffectsUncoveredAreas(aOperator) ||
+      aOperator == OP_SOURCE){
+    cairo_push_group(mContext);
+      cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a);
+      cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y);
+    cairo_pop_group_to_source(mContext);
+    cairo_paint(mContext);
 
-  cairo_set_source(mContext, pat);
+    // Now that the shadow has been drawn, we can draw the surface on top.
+    cairo_push_group(mContext);
+      cairo_new_path(mContext);
+      cairo_rectangle(mContext, 0, 0, width, height);
+      //TODO[nrc] remove comments if test ok
+      //cairo_clip(mContext);
+      cairo_set_source(mContext, pat);
+      //cairo_paint(mContext);
+      cairo_fill(mContext);
+    cairo_pop_group_to_source(mContext);
+  } else {
+    cairo_set_source_rgba(mContext, aColor.r, aColor.g, aColor.b, aColor.a);
+    cairo_mask_surface(mContext, blursurf, aOffset.x, aOffset.y);
 
-  cairo_new_path(mContext);
-  cairo_rectangle(mContext, 0, 0, width, height);
-  cairo_clip(mContext);
+    // Now that the shadow has been drawn, we can draw the surface on top.
+    cairo_set_source(mContext, pat);
+    cairo_new_path(mContext);
+    cairo_rectangle(mContext, 0, 0, width, height);
+    cairo_clip(mContext);
+  }
 
   cairo_paint(mContext);
 
   cairo_restore(mContext);
 
   cairo_pattern_destroy(pat);
 }
 
@@ -419,35 +456,39 @@ DrawTargetCairo::DrawPattern(const Patte
 {
   if (!PatternIsCompatible(aPattern)) {
     return;
   }
 
   cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha);
   cairo_set_source(mContext, pat);
 
-  if (NeedIntermediateSurface(aPattern, aOptions)) {
+  if (NeedIntermediateSurface(aPattern, aOptions) ||
+      OperatorAffectsUncoveredAreas(aOptions.mCompositionOp)) {
     cairo_push_group_with_content(mContext, CAIRO_CONTENT_COLOR_ALPHA);
 
+    ClearSurfaceForUnboundedSource(aOptions.mCompositionOp);
+
     // Don't want operators to be applied twice
     cairo_set_operator(mContext, CAIRO_OPERATOR_OVER);
 
     if (aDrawType == DRAW_STROKE) {
       SetCairoStrokeOptions(mContext, aStrokeOptions);
       cairo_stroke_preserve(mContext);
     } else {
       cairo_fill_preserve(mContext);
     }
 
     cairo_pop_group_to_source(mContext);
 
     // Now draw the content using the desired operator
     cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
     cairo_paint_with_alpha(mContext, aOptions.mAlpha);
   } else {
+    ClearSurfaceForUnboundedSource(aOptions.mCompositionOp);
     cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
 
     if (aDrawType == DRAW_STROKE) {
       SetCairoStrokeOptions(mContext, aStrokeOptions);
       cairo_stroke_preserve(mContext);
     } else {
       cairo_fill_preserve(mContext);
     }
@@ -656,36 +697,70 @@ DrawTargetCairo::CreatePathBuilder(FillR
   // Creating a PathBuilder implicitly resets our mPathObserver, as it calls
   // SetPathObserver() on us. Since this guarantees our old path is saved off,
   // it's safe to reset the path here.
   cairo_new_path(mContext);
 
   return builder;
 }
 
+void
+DrawTargetCairo::ClearSurfaceForUnboundedSource(const CompositionOp &aOperator)
+{
+  if (aOperator != OP_SOURCE)
+    return;
+  cairo_set_operator(mContext, CAIRO_OPERATOR_CLEAR);
+  // It doesn't really matter what the source is here, since Paint
+  // isn't bounded by the source and the mask covers the entire clip
+  // region.
+  cairo_paint(mContext);
+}
+
+
 TemporaryRef<GradientStops>
 DrawTargetCairo::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops, ExtendMode aExtendMode) const
 {
   RefPtr<GradientStopsCairo> stops = new GradientStopsCairo(aStops, aNumStops);
   return stops;
 }
 
+/**
+ * Copies pixel data from aData into aSurface; aData must have the dimensions
+ * given in aSize, with a stride of aStride bytes and aPixelWidth bytes per pixel
+ */
+static void
+CopyDataToCairoSurface(cairo_surface_t* aSurface,
+                       unsigned char *aData,
+                       const IntSize &aSize,
+                       int32_t aStride,
+                       int32_t aPixelWidth)
+{
+  unsigned char* surfData = cairo_image_surface_get_data(aSurface);
+  for (int32_t y = 0; y < aSize.height; ++y) {
+    memcpy(surfData + y * aSize.width * aPixelWidth,
+           aData + y * aStride,
+           aSize.width * aPixelWidth);
+  }
+  cairo_surface_mark_dirty(aSurface);
+}
+
 TemporaryRef<SourceSurface>
 DrawTargetCairo::CreateSourceSurfaceFromData(unsigned char *aData,
                                              const IntSize &aSize,
                                              int32_t aStride,
                                              SurfaceFormat aFormat) const
 {
-  cairo_surface_t* surf = cairo_image_surface_create_for_data(aData,
-                                                              GfxFormatToCairoFormat(aFormat),
-                                                              aSize.width,
-                                                              aSize.height,
-                                                              aStride);
+  cairo_surface_t* surf = cairo_image_surface_create(GfxFormatToCairoFormat(aFormat),
+                                                     aSize.width,
+                                                     aSize.height);
+  CopyDataToCairoSurface(surf, aData, aSize, aStride, BytesPerPixel(aFormat));
+    
   RefPtr<SourceSurfaceCairo> source_surf = new SourceSurfaceCairo(surf, aSize, aFormat);
   cairo_surface_destroy(surf);
+
   return source_surf;
 }
 
 TemporaryRef<SourceSurface>
 DrawTargetCairo::OptimizeSourceSurface(SourceSurface *aSurface) const
 {
   return aSurface;
 }
--- a/gfx/2d/DrawTargetCairo.h
+++ b/gfx/2d/DrawTargetCairo.h
@@ -150,16 +150,19 @@ private: // methods
   // context is associated. Pass the path you're going to be using if you have
   // one.
   void WillChange(const Path* aPath = NULL);
 
   // Call if there is any reason to disassociate all snapshots from this draw
   // target; for example, because we're going to be destroyed.
   void MarkSnapshotsIndependent();
 
+  // If the current operator is "source" then clear the destination before we
+  // draw into it, to simulate the effect of an unbounded source operator.
+  void ClearSurfaceForUnboundedSource(const CompositionOp &aOperator);
 private: // data
   cairo_t* mContext;
   cairo_surface_t* mSurface;
   IntSize mSize;
   std::vector<SourceSurfaceCairo*> mSnapshots;
   mutable RefPtr<CairoPathContext> mPathObserver;
 };