Bug 772726. Part 6: Add NudgeToIntegers operation to Azure Matrix, add MultiplyAndNudgeToIntegers to gfxContext, and call it from SVG when we render path geometry, to ensure that small internal rounding errors in transforms don't cause unnecessary differences in rasterization of paths. r=bas
authorRobert O'Callahan <robert@ocallahan.org>
Wed, 05 Sep 2012 12:15:52 +1200
changeset 107682 23ee0b1ce2507092498c3c32c5367f279d1f5cae
parent 107681 7c820213a4d1fa755eb8f3f0f98fc9cccfe15040
child 107683 6ffdab09eff621f3bc33521e65c36be4af213dcc
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersbas
bugs772726
milestone18.0a1
Bug 772726. Part 6: Add NudgeToIntegers operation to Azure Matrix, add MultiplyAndNudgeToIntegers to gfxContext, and call it from SVG when we render path geometry, to ensure that small internal rounding errors in transforms don't cause unnecessary differences in rasterization of paths. r=bas
gfx/2d/Matrix.cpp
gfx/2d/Matrix.h
gfx/thebes/gfx3DMatrix.cpp
gfx/thebes/gfxContext.cpp
gfx/thebes/gfxContext.h
layout/svg/base/src/nsSVGPathGeometryFrame.cpp
--- a/gfx/2d/Matrix.cpp
+++ b/gfx/2d/Matrix.cpp
@@ -51,10 +51,29 @@ Matrix::TransformBounds(const Rect &aRec
       min_y = quad[i].y;
     if (quad[i].y > max_y)
       max_y = quad[i].y;
   }
 
   return Rect(min_x, min_y, max_x - min_x, max_y - min_y);
 }
 
+static void NudgeToInteger(float *aVal)
+{
+  float r = floorf(*aVal + 0.5f);
+  if (fabsf(*aVal - r) < 1e-4f) {
+    *aVal = r;
+  }
+}
+
+void
+Matrix::NudgeToIntegers()
+{
+  NudgeToInteger(&_11);
+  NudgeToInteger(&_12);
+  NudgeToInteger(&_21);
+  NudgeToInteger(&_22);
+  NudgeToInteger(&_31);
+  NudgeToInteger(&_32);
+}
+
 }
 }
--- a/gfx/2d/Matrix.h
+++ b/gfx/2d/Matrix.h
@@ -154,16 +154,18 @@ public:
    */
   bool IsIdentity() const
   {
     return _11 == 1.0f && _12 == 0.0f &&
            _21 == 0.0f && _22 == 1.0f &&
            _31 == 0.0f && _32 == 0.0f;
   }
 
+  GFX2D_API void NudgeToIntegers();
+
 private:
   static bool FuzzyEqual(Float aV1, Float aV2) {
     // XXX - Check if fabs does the smart thing and just negates the sign bit.
     return fabs(aV2 - aV1) < 1e-6;
   }
 };
 
 }
--- a/gfx/thebes/gfx3DMatrix.cpp
+++ b/gfx/thebes/gfx3DMatrix.cpp
@@ -807,18 +807,18 @@ bool gfx3DMatrix::IsBackfaceVisible() co
   float _33 = _12*_24*_41 - _14*_22*_41 +
               _14*_21*_42 - _11*_24*_42 -
               _12*_21*_44 + _11*_22*_44;
   return (_33 * det) < 0;
 }
 
 static void NudgeToInteger(float *aVal)
 {
-  float r = NS_roundf(*aVal);
-  if (fabsf(*aVal - r) < 1e-4) {
+  float r = floorf(*aVal + 0.5f);
+  if (fabsf(*aVal - r) < 1e-4f) {
     *aVal = r;
   }
 }
 
 void gfx3DMatrix::NudgeToIntegers(void)
 {
   NudgeToInteger(&_11);
   NudgeToInteger(&_12);
--- a/gfx/thebes/gfxContext.cpp
+++ b/gfx/thebes/gfxContext.cpp
@@ -588,16 +588,30 @@ gfxContext::Multiply(const gfxMatrix& ma
     const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix);
     cairo_transform(mCairo, &mat);
   } else {
     ChangeTransform(ToMatrix(matrix) * mDT->GetTransform());
   }
 }
 
 void
+gfxContext::MultiplyAndNudgeToIntegers(const gfxMatrix& matrix)
+{
+  if (mCairo) {
+    const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix);
+    cairo_transform(mCairo, &mat);
+    // XXX nudging to integers not currently supported for Thebes
+  } else {
+    Matrix transform = ToMatrix(matrix) * mDT->GetTransform();
+    transform.NudgeToIntegers();
+    ChangeTransform(transform);
+  }
+}
+
+void
 gfxContext::SetMatrix(const gfxMatrix& matrix)
 {
   if (mCairo) {
     const cairo_matrix_t& mat = reinterpret_cast<const cairo_matrix_t&>(matrix);
     cairo_set_matrix(mCairo, &mat);
   } else {
     ChangeTransform(ToMatrix(matrix));
   }
@@ -2092,9 +2106,9 @@ gfxContext::ChangeTransform(const Matrix
       // Create path in device space.
       mPathBuilder = path->TransformedCopyToBuilder(toNewUS);
     }
     // No need to consider the transform changed now!
     mTransformChanged = false;
   }
 
   mDT->SetTransform(aNewMatrix);
-}
\ No newline at end of file
+}
--- a/gfx/thebes/gfxContext.h
+++ b/gfx/thebes/gfxContext.h
@@ -255,16 +255,22 @@ public:
     void Rotate(gfxFloat angle);
 
     /**
      * Post-multiplies 'other' onto the current CTM, i.e. this
      * matrix's transformation will take place before the previously set
      * transformations.
      */
     void Multiply(const gfxMatrix& other);
+    /**
+     * As "Multiply", but also nudges any entries in the resulting matrix that
+     * are close to an integer to that integer, to correct for
+     * compounded rounding errors.
+     */
+    void MultiplyAndNudgeToIntegers(const gfxMatrix& other);
 
     /**
      * Replaces the current transformation matrix with matrix.
      */
     void SetMatrix(const gfxMatrix& matrix);
 
     /**
      * Sets the transformation matrix to the identity matrix.
--- a/layout/svg/base/src/nsSVGPathGeometryFrame.cpp
+++ b/layout/svg/base/src/nsSVGPathGeometryFrame.cpp
@@ -596,17 +596,17 @@ nsSVGPathGeometryFrame::GeneratePath(gfx
                                      const gfxMatrix &aTransform)
 {
   if (aTransform.IsSingular()) {
     aContext->IdentityMatrix();
     aContext->NewPath();
     return;
   }
 
-  aContext->Multiply(aTransform);
+  aContext->MultiplyAndNudgeToIntegers(aTransform);
 
   // Hack to let SVGPathData::ConstructPath know if we have square caps:
   const nsStyleSVG* style = GetStyleSVG();
   if (style->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_SQUARE) {
     aContext->SetLineCap(gfxContext::LINE_CAP_SQUARE);
   }
 
   aContext->NewPath();