Bug 586683 - Part 1 - Add resolution handling to BasicLayers. r=roc a=blocking2.0
authorMatt Woodrow <mwoodrow@mozilla.com>
Wed, 09 Feb 2011 09:35:54 +1300
changeset 62161 5ed6c4935486d1aee52f9edcd96043b0fab86fb8
parent 62160 ffe65159d361f313c2f7dbf83e498acf112b0edd
child 62162 b0512b9a8c11ae974391a714753aca994857a391
push idunknown
push userunknown
push dateunknown
reviewersroc, blocking2.0
bugs586683
milestone2.0b12pre
Bug 586683 - Part 1 - Add resolution handling to BasicLayers. r=roc a=blocking2.0
gfx/layers/basic/BasicLayers.cpp
gfx/src/nsRect.cpp
gfx/src/nsRect.h
gfx/src/nsRegion.cpp
gfx/src/nsRegion.h
gfx/thebes/gfxUtils.cpp
gfx/thebes/gfxUtils.h
--- a/gfx/layers/basic/BasicLayers.cpp
+++ b/gfx/layers/basic/BasicLayers.cpp
@@ -549,28 +549,30 @@ BasicThebesLayer::Paint(gfxContext* aCon
       }
 
       aContext->Restore();
     }
     return;
   }
 
   {
-    float paintXRes = BasicManager()->XResolution();
-    float paintYRes = BasicManager()->YResolution();
+    gfxSize scale = aContext->CurrentMatrix().ScaleFactors(PR_TRUE);
+    float paintXRes = gfxUtils::ClampToScaleFactor(BasicManager()->XResolution() * scale.width);
+    float paintYRes = gfxUtils::ClampToScaleFactor(BasicManager()->YResolution() * scale.height);
     Buffer::PaintState state =
       mBuffer.BeginPaint(this, contentType, paintXRes, paintYRes);
     mValidRegion.Sub(mValidRegion, state.mRegionToInvalidate);
 
     if (state.mContext) {
       // The area that became invalid and is visible needs to be repainted
       // (this could be the whole visible area if our buffer switched
       // from RGB to RGBA, because we might need to repaint with
       // subpixel AA)
       state.mRegionToInvalidate.And(state.mRegionToInvalidate, mVisibleRegion);
+      state.mRegionToDraw.ExtendForScaling(paintXRes, paintYRes);
       mXResolution = paintXRes;
       mYResolution = paintYRes;
       SetAntialiasingFlags(this, state.mContext);
       PaintBuffer(state.mContext,
                   state.mRegionToDraw, state.mRegionToInvalidate,
                   aCallback, aCallbackData);
       Mutated();
     } else {
--- a/gfx/src/nsRect.cpp
+++ b/gfx/src/nsRect.cpp
@@ -200,16 +200,42 @@ nsRect& nsRect::ScaleRoundOut(float aXSc
   nscoord bottom = NSToCoordCeil(float(YMost()) * aYScale);
   x = NSToCoordFloor(float(x) * aXScale);
   y = NSToCoordFloor(float(y) * aYScale);
   width = (right - x);
   height = (bottom - y);
   return *this;
 }
 
+static bool IsFloatInteger(float aFloat)
+{
+  return fabs(aFloat - NS_round(aFloat)) < 1e-6;
+}
+
+nsRect& nsRect::ExtendForScaling(float aXMult, float aYMult)
+{
+  NS_ASSERTION((IsFloatInteger(aXMult) || IsFloatInteger(1/aXMult)) &&
+               (IsFloatInteger(aYMult) || IsFloatInteger(1/aYMult)),
+               "Multiplication factors must be integers or 1/integer");
+               
+  // Scale rect by multiplier, snap outwards to integers and then unscale.
+  // We round the results to the nearest integer to prevent floating point errors.
+  if (aXMult < 1) {
+    nscoord right = NSToCoordRound(ceil(float(XMost()) * aXMult) / aXMult);
+    x = NSToCoordRound(floor(float(x) * aXMult) / aXMult);
+    width = right - x;
+  }
+  if (aYMult < 1) {
+    nscoord bottom = NSToCoordRound(ceil(float(YMost()) * aYMult) / aYMult);
+    y = NSToCoordRound(floor(float(y) * aYMult) / aYMult);
+    height = bottom - y;
+  }
+  return *this;
+}
+
 #ifdef DEBUG
 // Diagnostics
 
 FILE* operator<<(FILE* out, const nsRect& rect)
 {
   nsAutoString tmp;
 
   // Output the coordinates in fractional pixels so they're easier to read
--- a/gfx/src/nsRect.h
+++ b/gfx/src/nsRect.h
@@ -178,16 +178,21 @@ struct NS_GFX nsRect {
   nsRect  operator+(const nsMargin& aMargin) const { return nsRect(*this) += aMargin; }
   nsRect  operator-(const nsMargin& aMargin) const { return nsRect(*this) -= aMargin; }
 
   // Scale by aScale, converting coordinates to integers so that the result is
   // the smallest integer-coordinate rectangle containing the unrounded result.
   nsRect& ScaleRoundOut(float aScale) { return ScaleRoundOut(aScale, aScale); }
   nsRect& ScaleRoundOut(float aXScale, float aYScale);
 
+  // Extend the rect outwards such that the edges are on integer boundaries
+  // and the edges scaled by aXMult/aYMult are also on integer boundaries.
+  // aXMult/aYMult must be N or 1/N for integer N.
+  nsRect& ExtendForScaling(float aXMult, float aYMult);
+
   // Converts this rect from aFromAPP, an appunits per pixel ratio, to aToAPP.
   // In the RoundOut version we make the rect the smallest rect containing the
   // unrounded result. In the RoundIn version we make the rect the largest rect
   // contained in the unrounded result.
   inline nsRect ConvertAppUnitsRoundOut(PRInt32 aFromAPP, PRInt32 aToAPP) const;
   inline nsRect ConvertAppUnitsRoundIn(PRInt32 aFromAPP, PRInt32 aToAPP) const;
 
   // Helpers for accessing the vertices
--- a/gfx/src/nsRegion.cpp
+++ b/gfx/src/nsRegion.cpp
@@ -1296,16 +1296,32 @@ void nsRegion::MoveBy (nsPoint aPt)
       pRect->MoveBy (aPt.x, aPt.y);
       pRect = pRect->next;
     }
 
     mBoundRect.MoveBy (aPt.x, aPt.y);
   }
 }
 
+nsRegion& nsRegion::ExtendForScaling (float aXMult, float aYMult)
+{
+  nsRegion region;
+  nsRegionRectIterator iter(*this);
+  for (;;) {
+    const nsRect* r = iter.Next();
+    if (!r)
+      break;
+    nsRect rect = *r;
+    rect.ExtendForScaling(aXMult, aYMult);
+    region.Or(region, rect);
+  }
+  *this = region;
+  return *this;
+}
+
 nsRegion nsRegion::ConvertAppUnitsRoundOut (PRInt32 aFromAPP, PRInt32 aToAPP) const
 {
   if (aFromAPP == aToAPP) {
     return *this;
   }
   // Do it in a simplistic and slow way to avoid any weird behaviour with
   // rounding causing rects to overlap. Should be fast enough for what we need.
   nsRegion region;
--- a/gfx/src/nsRegion.h
+++ b/gfx/src/nsRegion.h
@@ -180,16 +180,18 @@ public:
   PRUint32 GetNumRects () const { return mRectCount; }
   const nsRect& GetBounds () const { return mBoundRect; }
   // Converts this region from aFromAPP, an appunits per pixel ratio, to
   // aToAPP. This applies nsRect::ConvertAppUnitsRoundOut/In to each rect of
   // the region.
   nsRegion ConvertAppUnitsRoundOut (PRInt32 aFromAPP, PRInt32 aToAPP) const;
   nsRegion ConvertAppUnitsRoundIn (PRInt32 aFromAPP, PRInt32 aToAPP) const;
   nsIntRegion ToOutsidePixels (nscoord aAppUnitsPerPixel) const;
+  nsRegion& ExtendForScaling (float aXMult, float aYMult);
+
   /**
    * Gets the largest rectangle contained in the region.
    * @param aContainingRect if non-empty, we choose a rectangle that
    * maximizes the area intersecting with aContainingRect (and break ties by
    * then choosing the largest rectangle overall)
    */
   nsRect GetLargestRectangle (const nsRect& aContainingRect = nsRect()) const;
 
@@ -432,16 +434,22 @@ public:
   PRUint32 GetNumRects () const { return mImpl.GetNumRects (); }
   nsIntRect GetBounds () const { return FromRect (mImpl.GetBounds ()); }
   nsRegion ToAppUnits (nscoord aAppUnitsPerPixel) const;
   nsIntRect GetLargestRectangle (const nsIntRect& aContainingRect = nsIntRect()) const
   {
     return FromRect (mImpl.GetLargestRectangle( ToRect(aContainingRect) ));
   }
 
+  nsIntRegion& ExtendForScaling (float aXMult, float aYMult)
+  {
+    mImpl.ExtendForScaling(aXMult, aYMult);
+    return *this;
+  }
+
   /**
    * Make sure the region has at most aMaxRects by adding area to it
    * if necessary. The simplified region will be a superset of the
    * original region. The simplified region's bounding box will be
    * the same as for the current region.
    */
   void SimplifyOutward (PRUint32 aMaxRects)
   {
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -462,16 +462,45 @@ gfxUtils::ClipToRegion(gfxContext* aCont
 }
 
 /*static*/ void
 gfxUtils::ClipToRegionSnapped(gfxContext* aContext, const nsIntRegion& aRegion)
 {
   ClipToRegionInternal(aContext, aRegion, PR_TRUE);
 }
 
+/*static*/ gfxFloat
+gfxUtils::ClampToScaleFactor(gfxFloat aVal)
+{
+  // Arbitary scale factor limitation. We can increase this
+  // for better scaling performance at the cost of worse
+  // quality.
+  static const gfxFloat kScaleResolution = 2;
+
+  // Negative scaling is just a flip and irrelevant to
+  // our resolution calculation.
+  if (aVal < 0.0) {
+    aVal = -aVal;
+  }
+
+  gfxFloat power = log(aVal)/log(kScaleResolution);
+
+  // If power is within 1e-6 of an integer, round to nearest to
+  // prevent floating point errors, otherwise round up to the
+  // next integer value.
+  if (fabs(power - NS_round(power)) < 1e-6) {
+    power = NS_round(power);
+  } else {
+    power = NS_ceil(power);
+  }
+
+  return pow(kScaleResolution, power);
+}
+
+
 /*static*/ void
 gfxUtils::PathFromRegion(gfxContext* aContext, const nsIntRegion& aRegion)
 {
   PathFromRegionInternal(aContext, aRegion, PR_FALSE);
 }
 
 /*static*/ void
 gfxUtils::PathFromRegionSnapped(gfxContext* aContext, const nsIntRegion& aRegion)
--- a/gfx/thebes/gfxUtils.h
+++ b/gfx/thebes/gfxUtils.h
@@ -112,11 +112,16 @@ public:
     static int ImageFormatToDepth(gfxASurface::gfxImageFormat aFormat);
 
     /**
      * If aIn can be represented exactly using an nsIntRect (i.e.
      * integer-aligned edges and coordinates in the PRInt32 range) then we
      * set aOut to that rectangle, otherwise return failure.
     */
     static PRBool GfxRectToIntRect(const gfxRect& aIn, nsIntRect* aOut);
+
+    /**
+     * Clamp aVal to a power of kScaleResolution.
+     */
+    static gfxFloat ClampToScaleFactor(gfxFloat aVal);
 };
 
 #endif