Bug 1033375 - Nudge simple linear gradients with hard stops to half-pixel gradient. r=nical
authorLee Salzman <lsalzman@mozilla.com>
Tue, 23 Jun 2015 20:50:36 -0400
changeset 250103 9e8d2b0dd0ce2a40b31d5344248a97015dbc7e39
parent 250102 80005aa6f8e193faa05e5d9442dcba89c4695ca6
child 250104 a8fc665d7fab6603fa05cdbcddb32b2f8e00c63a
push id61439
push userryanvm@gmail.com
push dateWed, 24 Jun 2015 16:25:31 +0000
treeherdermozilla-inbound@9e8d2b0dd0ce [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnical
bugs1033375
milestone41.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 1033375 - Nudge simple linear gradients with hard stops to half-pixel gradient. r=nical
gfx/2d/DrawTargetCairo.cpp
layout/base/nsCSSRenderingBorders.cpp
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -452,25 +452,38 @@ private:
     cairo_surface_set_device_offset(mSurface, 0, 0);
   }
 
   cairo_surface_t* mSurface;
   double mX;
   double mY;
 };
 
+static inline void
+CairoPatternAddGradientStop(cairo_pattern_t* aPattern,
+                            const GradientStop &aStop,
+                            Float aNudge = 0)
+{
+  cairo_pattern_add_color_stop_rgba(aPattern, aStop.offset + aNudge,
+                                    aStop.color.r, aStop.color.g, aStop.color.b,
+                                    aStop.color.a);
+
+}
+
 // Never returns nullptr. As such, you must always pass in Cairo-compatible
 // patterns, most notably gradients with a GradientStopCairo.
 // The pattern returned must have cairo_pattern_destroy() called on it by the
 // caller.
 // As the cairo_pattern_t returned may depend on the Pattern passed in, the
 // lifetime of the cairo_pattern_t returned must not exceed the lifetime of the
 // Pattern passed in.
 static cairo_pattern_t*
-GfxPatternToCairoPattern(const Pattern& aPattern, Float aAlpha)
+GfxPatternToCairoPattern(const Pattern& aPattern,
+                         Float aAlpha,
+                         const Matrix& aTransform)
 {
   cairo_pattern_t* pat;
   const Matrix* matrix = nullptr;
 
   switch (aPattern.GetType())
   {
     case PatternType::COLOR:
     {
@@ -507,21 +520,35 @@ GfxPatternToCairoPattern(const Pattern& 
 
       MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO);
       GradientStopsCairo* cairoStops = static_cast<GradientStopsCairo*>(pattern.mStops.get());
       cairo_pattern_set_extend(pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode()));
 
       matrix = &pattern.mMatrix;
 
       const std::vector<GradientStop>& stops = cairoStops->GetStops();
+      if (stops.size() >= 2 && stops.front().offset == stops.back().offset) {
+        // Certain Cairo backends that use pixman to implement gradients can have jagged
+        // edges occur with hard stops. Such hard stops are used for implementing certain
+        // types of CSS borders. Work around this by turning these hard-stops into half-pixel
+        // gradients to anti-alias them. See bug 1033375
+        Matrix patternToDevice = aTransform * pattern.mMatrix;
+        Float gradLength = (patternToDevice * pattern.mEnd - patternToDevice * pattern.mBegin).Length();
+        if (gradLength > 0) {
+          Float aaOffset = 0.25 / gradLength;
+          CairoPatternAddGradientStop(pat, stops.front(), -aaOffset);
+          for (size_t i = 1; i < stops.size()-1; ++i) {
+            CairoPatternAddGradientStop(pat, stops[i]);
+          }
+          CairoPatternAddGradientStop(pat, stops.back(), aaOffset);
+          break;
+        }
+      }
       for (size_t i = 0; i < stops.size(); ++i) {
-        const GradientStop& stop = stops[i];
-        cairo_pattern_add_color_stop_rgba(pat, stop.offset, stop.color.r,
-                                          stop.color.g, stop.color.b,
-                                          stop.color.a);
+        CairoPatternAddGradientStop(pat, stops[i]);
       }
 
       break;
     }
     case PatternType::RADIAL_GRADIENT:
     {
       const RadialGradientPattern& pattern = static_cast<const RadialGradientPattern&>(aPattern);
 
@@ -531,20 +558,17 @@ GfxPatternToCairoPattern(const Pattern& 
       MOZ_ASSERT(pattern.mStops->GetBackendType() == BackendType::CAIRO);
       GradientStopsCairo* cairoStops = static_cast<GradientStopsCairo*>(pattern.mStops.get());
       cairo_pattern_set_extend(pat, GfxExtendToCairoExtend(cairoStops->GetExtendMode()));
 
       matrix = &pattern.mMatrix;
 
       const std::vector<GradientStop>& stops = cairoStops->GetStops();
       for (size_t i = 0; i < stops.size(); ++i) {
-        const GradientStop& stop = stops[i];
-        cairo_pattern_add_color_stop_rgba(pat, stop.offset, stop.color.r,
-                                          stop.color.g, stop.color.b,
-                                          stop.color.a);
+        CairoPatternAddGradientStop(pat, stops[i]);
       }
 
       break;
     }
     default:
     {
       // We should support all pattern types!
       MOZ_ASSERT(false);
@@ -887,17 +911,17 @@ DrawTargetCairo::DrawPattern(const Patte
                              bool aPathBoundsClip)
 {
   if (!PatternIsCompatible(aPattern)) {
     return;
   }
 
   AutoClearDeviceOffset clear(aPattern);
 
-  cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha);
+  cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
   if (!pat) {
     return;
   }
   if (cairo_pattern_status(pat)) {
     cairo_pattern_destroy(pat);
     gfxWarning() << "Invalid pattern";
     return;
   }
@@ -1169,17 +1193,17 @@ DrawTargetCairo::FillGlyphs(ScaledFont *
                             const GlyphRenderingOptions*)
 {
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clear(aPattern);
 
   ScaledFontBase* scaledFont = static_cast<ScaledFontBase*>(aFont);
   cairo_set_scaled_font(mContext, scaledFont->GetCairoScaledFont());
 
-  cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha);
+  cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
   if (!pat)
     return;
 
   cairo_set_source(mContext, pat);
   cairo_pattern_destroy(pat);
 
   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 
@@ -1208,22 +1232,22 @@ DrawTargetCairo::Mask(const Pattern &aSo
                       const DrawOptions &aOptions /* = DrawOptions() */)
 {
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clearSource(aSource);
   AutoClearDeviceOffset clearMask(aMask);
 
   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 
-  cairo_pattern_t* source = GfxPatternToCairoPattern(aSource, aOptions.mAlpha);
+  cairo_pattern_t* source = GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform());
   if (!source) {
     return;
   }
 
-  cairo_pattern_t* mask = GfxPatternToCairoPattern(aMask, aOptions.mAlpha);
+  cairo_pattern_t* mask = GfxPatternToCairoPattern(aMask, aOptions.mAlpha, GetTransform());
   if (!mask) {
     cairo_pattern_destroy(source);
     return;
   }
 
   if (cairo_pattern_status(source) || cairo_pattern_status(mask)) {
     cairo_pattern_destroy(source);
     cairo_pattern_destroy(mask);
@@ -1249,17 +1273,17 @@ DrawTargetCairo::MaskSurface(const Patte
   AutoClearDeviceOffset clearMask(aMask);
 
   if (!PatternIsCompatible(aSource)) {
     return;
   }
 
   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 
-  cairo_pattern_t* pat = GfxPatternToCairoPattern(aSource, aOptions.mAlpha);
+  cairo_pattern_t* pat = GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform());
   if (!pat) {
     return;
   }
 
   if (cairo_pattern_status(pat)) {
     cairo_pattern_destroy(pat);
     gfxWarning() << "Invalid pattern";
     return;
--- a/layout/base/nsCSSRenderingBorders.cpp
+++ b/layout/base/nsCSSRenderingBorders.cpp
@@ -1067,20 +1067,16 @@ nsCSSBorderRenderer::CreateCornerGradien
   aPoint1 = Point(pat1.x, pat1.y);
   aPoint2 = Point(pat2.x, pat2.y);
 
   Color firstColor = Color::FromABGR(aFirstColor);
   Color secondColor = Color::FromABGR(aSecondColor);
 
   nsTArray<gfx::GradientStop> rawStops(2);
   rawStops.SetLength(2);
-  // This is only guaranteed to give correct (and in some cases more correct)
-  // rendering with the Direct2D Azure and Quartz Cairo backends. For other
-  // cairo backends it could create un-antialiased border corner transitions
-  // since that at least used to be pixman's behaviour for hard stops.
   rawStops[0].color = firstColor;
   rawStops[0].offset = 0.5;
   rawStops[1].color = secondColor;
   rawStops[1].offset = 0.5;
   RefPtr<GradientStops> gs =
     gfxGradientCache::GetGradientStops(aDT, rawStops, ExtendMode::CLAMP);
   if (!gs) {
     // Having two corners, both with reversed color stops is pretty common