Bug 1097437. Work around Quartz bug where corners of stroked rects don't get a solid color when they should. r=jrmuizel
authorRobert O'Callahan <robert@ocallahan.org>
Wed, 12 Nov 2014 20:52:57 +1300
changeset 241770 2999f246a4e293455cc8fa32f7f1ee279a2c51c1
parent 241769 9906eabbfd3a86c9cfb6882d770ff28a0a8b55b9
child 241771 31fb338f26bf8fc5edbf7a1c85e2f8c37164ea0b
push idunknown
push userunknown
push dateunknown
reviewersjrmuizel
bugs1097437
milestone36.0a1
Bug 1097437. Work around Quartz bug where corners of stroked rects don't get a solid color when they should. r=jrmuizel
gfx/2d/DrawTargetCG.cpp
gfx/2d/Matrix.h
gfx/thebes/gfxMatrix.h
layout/reftests/bugs/1097437-1-ref.html
layout/reftests/bugs/1097437-1.html
layout/reftests/bugs/reftest.list
--- a/gfx/2d/DrawTargetCG.cpp
+++ b/gfx/2d/DrawTargetCG.cpp
@@ -990,16 +990,31 @@ DrawTargetCG::StrokeLine(const Point &p1
     SetStrokeFromPattern(cg, mColorSpace, aPattern);
     CGContextStrokePath(cg);
   }
 
   fixer.Fix(mCg);
   CGContextRestoreGState(mCg);
 }
 
+static bool
+IsInteger(Float aValue)
+{
+  return floorf(aValue) == aValue;
+}
+
+static bool
+IsPixelAlignedStroke(const Rect& aRect, Float aLineWidth)
+{
+  Float halfWidth = aLineWidth/2;
+  return IsInteger(aLineWidth) &&
+         IsInteger(aRect.x - halfWidth) && IsInteger(aRect.y - halfWidth) &&
+         IsInteger(aRect.XMost() - halfWidth) && IsInteger(aRect.YMost() - halfWidth);
+}
+
 void
 DrawTargetCG::StrokeRect(const Rect &aRect,
                          const Pattern &aPattern,
                          const StrokeOptions &aStrokeOptions,
                          const DrawOptions &aDrawOptions)
 {
   if (!aRect.IsFinite()) {
     return;
@@ -1007,19 +1022,30 @@ DrawTargetCG::StrokeRect(const Rect &aRe
 
   MarkChanged();
 
   CGContextSaveGState(mCg);
 
   UnboundnessFixer fixer;
   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
   CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
-  CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
 
+  // Work around Quartz bug where antialiasing causes corner pixels to be off by
+  // 1 channel value (e.g. rgb(1,1,1) values appear at the corner of solid
+  // black stroke), by turning off antialiasing when the edges of the stroke
+  // are pixel-aligned. Note that when a transform's components are all
+  // integers, it maps integers coordinates to integer coordinates.
+  bool pixelAlignedStroke = mTransform.IsAllIntegers() &&
+    mTransform.PreservesAxisAlignedRectangles() &&
+    aPattern.GetType() == PatternType::COLOR &&
+    IsPixelAlignedStroke(aRect, aStrokeOptions.mLineWidth);
+  CGContextSetShouldAntialias(cg,
+    aDrawOptions.mAntialiasMode != AntialiasMode::NONE && !pixelAlignedStroke);
+
   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
 
   SetStrokeOptions(cg, aStrokeOptions);
 
   if (isGradient(aPattern)) {
     // There's no CGContextClipStrokeRect so we do it by hand
     CGContextBeginPath(cg);
     CGContextAddRect(cg, RectToCGRect(aRect));
--- a/gfx/2d/Matrix.h
+++ b/gfx/2d/Matrix.h
@@ -312,21 +312,31 @@ public:
   GFX2D_API Matrix &NudgeToIntegers();
 
   bool IsTranslation() const
   {
     return FuzzyEqual(_11, 1.0f) && FuzzyEqual(_12, 0.0f) &&
            FuzzyEqual(_21, 0.0f) && FuzzyEqual(_22, 1.0f);
   }
 
+  static bool FuzzyIsInteger(Float aValue)
+  {
+    return FuzzyEqual(aValue, floorf(aValue + 0.5f));
+  }
+
   bool IsIntegerTranslation() const
   {
-    return IsTranslation() &&
-           FuzzyEqual(_31, floorf(_31 + 0.5f)) &&
-           FuzzyEqual(_32, floorf(_32 + 0.5f));
+    return IsTranslation() && FuzzyIsInteger(_31) && FuzzyIsInteger(_32);
+  }
+
+  bool IsAllIntegers() const
+  {
+    return FuzzyIsInteger(_11) && FuzzyIsInteger(_12) &&
+           FuzzyIsInteger(_21) && FuzzyIsInteger(_22) &&
+           FuzzyIsInteger(_31) && FuzzyIsInteger(_32);
   }
 
   Point GetTranslation() const {
     return Point(_31, _32);
   }
 
   /**
    * Returns true if matrix is multiple of 90 degrees rotation with flipping,
@@ -340,24 +350,16 @@ public:
   /**
    * Returns true if the matrix has any transform other
    * than a translation or scale; this is, if there is
    * no rotation.
    */
   bool HasNonAxisAlignedTransform() const {
       return !FuzzyEqual(_21, 0.0) || !FuzzyEqual(_12, 0.0);
   }
-
-  /**
-   * Returns true if the matrix has non-integer scale
-   */
-  bool HasNonIntegerScale() const {
-      return !FuzzyEqual(_11, floor(_11 + 0.5)) ||
-             !FuzzyEqual(_22, floor(_22 + 0.5));
-  }
 };
 
 class Matrix4x4
 {
 public:
   Matrix4x4()
     : _11(1.0f), _12(0.0f), _13(0.0f), _14(0.0f)
     , _21(0.0f), _22(1.0f), _23(0.0f), _24(0.0f)
--- a/gfx/thebes/gfxMatrix.h
+++ b/gfx/thebes/gfxMatrix.h
@@ -290,23 +290,15 @@ public:
      * Returns true if matrix is multiple of 90 degrees rotation with flipping,
      * scaling and translation.
      */
     bool PreservesAxisAlignedRectangles() const {
         return ((FuzzyEqual(_11, 0.0) && FuzzyEqual(_22, 0.0))
             || (FuzzyEqual(_21, 0.0) && FuzzyEqual(_12, 0.0)));
     }
 
-    /**
-     * Returns true if the matrix has non-integer scale
-     */
-    bool HasNonIntegerScale() const {
-        return !FuzzyEqual(_11, floor(_11 + 0.5)) ||
-               !FuzzyEqual(_22, floor(_22 + 0.5));
-    }
-
 private:
     static bool FuzzyEqual(gfxFloat aV1, gfxFloat aV2) {
         return fabs(aV2 - aV1) < 1e-6;
     }
 };
 
 #endif /* GFX_MATRIX_H */
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1097437-1-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<style>
+body { background-color: white; }
+#outer
+{
+  width: 100px; height: 100px;
+  padding: 1px;
+  background-color: black;
+}
+#inner
+{
+  width: 100px; height:100px;
+  background-color: white;
+}
+</style>
+</head>
+<body>
+<div id="outer"><div id="inner"></div></div>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1097437-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<style>
+body { background-color: white; }
+#outer
+{
+  width: 100px; height: 100px;
+  border: 1px solid black;
+}
+</style>
+</head>
+<body>
+<div id="outer"></div>
+</body>
+</html>
\ No newline at end of file
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1839,8 +1839,9 @@ test-pref(layout.css.grid.enabled,true) 
 == 1059498-3.html 1059498-1-ref.html
 fails-if(Android) == 1062792-1.html 1062792-1-ref.html
 == 1062963-floatmanager-reflow.html 1062963-floatmanager-reflow-ref.html
 test-pref(dom.webcomponents.enabled,true) == 1066554-1.html 1066554-1-ref.html
 == 1069716-1.html 1069716-1-ref.html
 == 1078262-1.html about:blank
 test-pref(layout.testing.overlay-scrollbars.always-visible,false) == 1081072-1.html 1081072-1-ref.html
 == 1081185-1.html 1081185-1-ref.html
+== 1097437-1.html 1097437-1-ref.html
\ No newline at end of file