API change: SkPath computeBounds -> getBounds



git-svn-id: http://skia.googlecode.com/svn/trunk@140 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkPaint.h b/include/core/SkPaint.h
index fc390ab..fd35292 100644
--- a/include/core/SkPaint.h
+++ b/include/core/SkPaint.h
@@ -371,8 +371,14 @@
         bounds (i.e. there is nothing complex like a patheffect that would make
         the bounds computation expensive.
     */
-    bool canComputeFastBounds() const;
-    
+    bool canComputeFastBounds() const {
+        // use bit-or since no need for early exit
+        return (reinterpret_cast<uintptr_t>(this->getMaskFilter()) |
+                reinterpret_cast<uintptr_t>(this->getLooper()) |
+                reinterpret_cast<uintptr_t>(this->getRasterizer()) |
+                reinterpret_cast<uintptr_t>(this->getPathEffect())) == 0;
+    }
+
     /** Only call this if canComputeFastBounds() returned true. This takes a
         raw rectangle (the raw bounds of a shape), and adjusts it for stylistic
         effects in the paint (e.g. stroking). If needed, it uses the storage
@@ -394,7 +400,10 @@
             }
         }
     */
-    const SkRect& computeFastBounds(const SkRect& orig, SkRect* storage) const;
+    const SkRect& computeFastBounds(const SkRect& orig, SkRect* storage) const {
+        return this->getStyle() == kFill_Style ? orig :
+                    this->computeStrokeFastBounds(orig, storage);
+    }
 
     /** Get the paint's shader object.
         <p />
@@ -759,7 +768,10 @@
     void descriptorProc(const SkMatrix* deviceMatrix,
                         void (*proc)(const SkDescriptor*, void*),
                         void* context) const;
-        
+
+    const SkRect& computeStrokeFastBounds(const SkRect& orig,
+                                          SkRect* storage) const;
+
     enum {
         kCanonicalTextSizeForPaths = 64
     };
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index 4cedcde..be49ebd 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -122,32 +122,27 @@
     //! Swap contents of this and other. Guaranteed not to throw
     void swap(SkPath& other);
 
-    enum BoundsType {
-        /** compute the bounds of the path's control points, may be larger than
-            with kExact_BoundsType, but may be faster to compute
-        */
-        kFast_BoundsType,
-        /** compute the exact bounds of the path, may be smaller than with
-            kFast_BoundsType, but may be slower to compute
-        */
-        kExact_BoundsType
-    };
-
-    /** Compute the bounds of the path, and write the answer into bounds. If the
-        path contains 0 or 1 points, the bounds is set to (0,0,0,0)
-     
-        @param bounds   Returns the computed bounds of the path
-        @param btype    Specifies if the computed bounds should be exact
-                        (slower) or approximate (faster)
+    /** Returns the bounds of the path's points. If the path contains 0 or 1
+        points, the bounds is set to (0,0,0,0), and isEmpty() will return true.
+        Note: this bounds may be larger than the actual shape, since curves
+        do not extend as far as their control points.
     */
-    void computeBounds(SkRect* bounds, BoundsType btype) const;
+    const SkRect& getBounds() const {
+        if (fBoundsIsDirty) {
+            this->computeBounds();
+        }
+        return fBounds;
+    }
 
     /** Calling this will, if the internal cache of the bounds is out of date,
-        update it so that subsequent calls to computeBounds will be instanteous.
+        update it so that subsequent calls to getBounds will be instanteous.
         This also means that any copies or simple transformations of the path
         will inherit the cached bounds.
-    */
-    void updateBoundsCache() const;
+     */
+    void updateBoundsCache() const {
+        // for now, just calling getBounds() is sufficient
+        this->getBounds();
+    }
 
     //  Construction methods
 
@@ -564,10 +559,13 @@
 private:
     SkTDArray<SkPoint>  fPts;
     SkTDArray<uint8_t>  fVerbs;
-    mutable SkRect      fFastBounds;
-    mutable uint8_t     fFastBoundsIsDirty;
+    mutable SkRect      fBounds;
+    mutable uint8_t     fBoundsIsDirty;
     uint8_t             fFillType;
 
+    // called, if dirty, by getBounds()
+    void computeBounds() const;
+
     friend class Iter;
     void cons_moveto();
 
diff --git a/include/core/SkXfermode.h b/include/core/SkXfermode.h
index f7e6510..6a7edec 100644
--- a/include/core/SkXfermode.h
+++ b/include/core/SkXfermode.h
@@ -41,20 +41,38 @@
     virtual void xferA8(SkAlpha dst[], const SkPMColor src[], int count,
                         const SkAlpha aa[]);
     
+    /** Enum of possible coefficients to describe some xfermodes
+     */
     enum Coeff {
-        kZero_Coeff,
-        kOne_Coeff,
-        kSC_Coeff,
-        kISC_Coeff,
-        kDC_Coeff,
-        kIDC_Coeff,
-        kSA_Coeff,
-        kISA_Coeff,
-        kDA_Coeff,
-        kIDA_Coeff,
+        kZero_Coeff,    /** 0 */
+        kOne_Coeff,     /** 1 */
+        kSC_Coeff,      /** src color */
+        kISC_Coeff,     /** inverse src color (i.e. 1 - sc) */
+        kDC_Coeff,      /** dst color */
+        kIDC_Coeff,     /** inverse dst color (i.e. 1 - dc) */
+        kSA_Coeff,      /** src alpha */
+        kISA_Coeff,     /** inverse src alpha (i.e. 1 - sa) */
+        kDA_Coeff,      /** dst alpha */
+        kIDA_Coeff,     /** inverse dst alpha (i.e. 1 - da) */
         
         kCoeffCount
     };
+    
+    /** If the xfermode can be expressed as an equation using the coefficients
+        in Coeff, then asCoeff() returns true, and sets (if not null) src and
+        dst accordingly.
+     
+            result = src_coeff * src_color + dst_coeff * dst_color;
+     
+        As examples, here are some of the porterduff coefficients
+     
+        MODE        SRC_COEFF       DST_COEFF
+        clear       zero            zero
+        src         one             zero
+        dst         zero            one
+        srcover     one             isa
+        dstover     ida             one
+     */
     virtual bool asCoeff(Coeff* src, Coeff* dst);
 
 protected:
diff --git a/samplecode/SampleCircle.cpp b/samplecode/SampleCircle.cpp
index bfb92d4..3f39dbd 100644
--- a/samplecode/SampleCircle.cpp
+++ b/samplecode/SampleCircle.cpp
@@ -13,9 +13,7 @@
     SkRect r = { 1.39999998, 1, 21.3999996, 21 };
     SkPath p;
     p.addOval(r);
-    SkRect r2;
-    p.computeBounds(&r2, SkPath::kFast_BoundsType);
-    SkASSERT(r == r2);
+    SkASSERT(r == p.getBounds());
 #endif
 }
 
diff --git a/samplecode/SampleStrokeText.cpp b/samplecode/SampleStrokeText.cpp
index 0627d51..2743897 100644
--- a/samplecode/SampleStrokeText.cpp
+++ b/samplecode/SampleStrokeText.cpp
@@ -29,8 +29,7 @@
         original.getTextWidths(&chars[i], 1, &width);
         x += width;
     }
-    SkRect bounds;
-    path.computeBounds(&bounds, SkPath::kExact_BoundsType);
+    SkRect bounds = path.getBounds();
     SkScalar sw = -original.getStrokeWidth();
     bounds.inset(sw, sw);
     path.offset(-bounds.fLeft, -bounds.fTop);
@@ -76,8 +75,7 @@
         original.getTextWidths(&chars[i], 1, &width);
         x += width;
     }
-    SkRect bounds;
-    path.computeBounds(&bounds, SkPath::kExact_BoundsType);
+    SkRect bounds = path.getBounds();
     SkScalar sw = -original.getStrokeWidth();
     bounds.inset(sw, sw);
     path.offset(-bounds.fLeft, -bounds.fTop);
diff --git a/src/core/SkBlitter.cpp b/src/core/SkBlitter.cpp
index 9208429..25303ac 100644
--- a/src/core/SkBlitter.cpp
+++ b/src/core/SkBlitter.cpp
@@ -784,6 +784,64 @@
     SkDELETE((SkBlitter*)blitter);
 }
 
+static bool just_solid_color(const SkPaint& paint) {
+    if (paint.getAlpha() == 0xFF && paint.getColorFilter() == NULL) {
+        SkShader* shader = paint.getShader();
+        if (NULL == shader ||
+            (shader->getFlags() & SkShader::kOpaqueAlpha_Flag)) {
+            return true;
+        }
+    }
+    return false;
+}
+    
+/** By analyzing the paint (with an xfermode), we may decide we can take
+    special action. This enum lists our possible actions
+ */
+enum XferInterp {
+    kNormal_XferInterp,         // no special interpretation, draw normally
+    kSrcOver_XferInterp,        // draw as if in srcover mode
+    kSkipDrawing_XferInterp     // draw nothing
+};
+
+static XferInterp interpret_xfermode(const SkPaint& paint, SkXfermode* xfer,
+                                     SkBitmap::Config deviceConfig) {
+    SkPorterDuff::Mode  mode;
+    
+    if (SkPorterDuff::IsMode(xfer, &mode)) {
+        switch (mode) {
+            case SkPorterDuff::kSrc_Mode:
+                if (just_solid_color(paint)) {
+                    return kSrcOver_XferInterp;
+                }
+                break;
+            case SkPorterDuff::kDst_Mode:
+                return kSkipDrawing_XferInterp;
+            case SkPorterDuff::kSrcOver_Mode:
+                return kSrcOver_XferInterp;
+            case SkPorterDuff::kDstOver_Mode:
+                if (SkBitmap::kRGB_565_Config == deviceConfig) {
+                    return kSkipDrawing_XferInterp;
+                }
+                break;
+            case SkPorterDuff::kSrcIn_Mode:
+                if (SkBitmap::kRGB_565_Config == deviceConfig &&
+                    just_solid_color(paint)) {
+                    return kSrcOver_XferInterp;
+                }
+                break;
+            case SkPorterDuff::kDstIn_Mode:
+                if (just_solid_color(paint)) {
+                    return kSkipDrawing_XferInterp;
+                }
+                break;
+            default:
+                break;
+        }
+    }
+    return kNormal_XferInterp;
+}
+
 SkBlitter* SkBlitter::Choose(const SkBitmap& device,
                              const SkMatrix& matrix,
                              const SkPaint& paint,
@@ -813,6 +871,19 @@
     }
 
     SkXfermode* mode = paint.getXfermode();
+    if (NULL != mode) {
+        switch (interpret_xfermode(paint, mode, device.config())) {
+            case kSrcOver_XferInterp:
+                mode = NULL;
+                break;
+            case kSkipDrawing_XferInterp:
+                SK_PLACEMENT_NEW(blitter, SkNullBlitter, storage, storageSize);
+                return blitter;
+            default:
+                break;
+        }
+    }
+
     if (NULL == shader && (NULL != mode || paint.getColorFilter() != NULL))
     {
         // xfermodes require shaders for our current set of blitters
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 16c94c2..199c1b0 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -879,58 +879,33 @@
     }
 }
 
+/*  current impl ignores edgetype, and relies on
+    getLocalClipBoundsCompareType(), which always returns a value assuming
+    antialiasing (worst case)
+ */
 bool SkCanvas::quickReject(const SkRect& rect, EdgeType) const {
-    /*  current impl ignores edgetype, and relies on
-        getLocalClipBoundsCompareType(), which always returns a value assuming
-        antialiasing (worst case)
-     */
-
     if (fMCRec->fRegion->isEmpty()) {
         return true;
     }
-    
-    // check for empty user rect (horizontal)
-    SkScalarCompareType userL = SkScalarToCompareType(rect.fLeft);
-    SkScalarCompareType userR = SkScalarToCompareType(rect.fRight);
-    if (userL >= userR) {
-        return true;
-    }
 
-    // check for empty user rect (vertical)
+    const SkRectCompareType& clipR = this->getLocalClipBoundsCompareType();
+
+    // for speed, do the most likely reject compares first
     SkScalarCompareType userT = SkScalarToCompareType(rect.fTop);
     SkScalarCompareType userB = SkScalarToCompareType(rect.fBottom);
-    if (userT >= userB) {
+    if (userT >= clipR.fBottom || userB <= clipR.fTop) {
         return true;
     }
-    
-    // check if we are completely outside of the local clip bounds
-    const SkRectCompareType& clipR = this->getLocalClipBoundsCompareType();
-    return  userL >= clipR.fRight || userT >= clipR.fBottom ||
-            userR <= clipR.fLeft  || userB <= clipR.fTop;
+    SkScalarCompareType userL = SkScalarToCompareType(rect.fLeft);
+    SkScalarCompareType userR = SkScalarToCompareType(rect.fRight);
+    if (userL >= clipR.fRight || userR <= clipR.fLeft) {
+        return true;
+    }    
+    return false;
 }
 
 bool SkCanvas::quickReject(const SkPath& path, EdgeType et) const {
-    if (fMCRec->fRegion->isEmpty() || path.isEmpty()) {
-        return true;
-    }
-
-    if (fMCRec->fMatrix->rectStaysRect()) {
-        SkRect  r;
-        path.computeBounds(&r, SkPath::kFast_BoundsType);
-        return this->quickReject(r, et);
-    }
-
-    SkPath      dstPath;
-    SkRect      r;
-    SkIRect     ir;
-
-    path.transform(*fMCRec->fMatrix, &dstPath);
-    dstPath.computeBounds(&r, SkPath::kFast_BoundsType);
-    r.round(&ir);
-    if (kAA_EdgeType == et) {
-        ir.inset(-1, -1);
-    }
-    return fMCRec->fRegion->quickReject(ir);
+    return path.isEmpty() || this->quickReject(path.getBounds(), et);
 }
 
 bool SkCanvas::quickRejectY(SkScalar top, SkScalar bottom, EdgeType et) const {
@@ -947,6 +922,7 @@
     SkScalarCompareType userB = SkScalarToCompareType(bottom);
     
     // check for invalid user Y coordinates (i.e. empty)
+    // reed: why do we need to do this check, since it slows us down?
     if (userT >= userB) {
         return true;
     }
@@ -1063,9 +1039,9 @@
 
 void SkCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
     if (paint.canComputeFastBounds()) {
-        SkRect r;
-        path.computeBounds(&r, SkPath::kFast_BoundsType);
-        if (this->quickReject(paint.computeFastBounds(r, &r),
+        SkRect storage;
+        const SkRect& bounds = path.getBounds();
+        if (this->quickReject(paint.computeFastBounds(bounds, &storage),
                               paint2EdgeType(&paint))) {
             return;
         }
diff --git a/src/core/SkDraw.cpp b/src/core/SkDraw.cpp
index 60c474a..93b5d4e 100644
--- a/src/core/SkDraw.cpp
+++ b/src/core/SkDraw.cpp
@@ -2236,10 +2236,8 @@
 }
 
 bool SkBounder::doPath(const SkPath& path, const SkPaint& paint, bool doFill) {
-    SkRect      bounds;
-    SkIRect     r;
-
-    path.computeBounds(&bounds, SkPath::kFast_BoundsType);
+    SkIRect       r;
+    const SkRect& bounds = path.getBounds();
 
     if (doFill) {
         bounds.round(&r);
@@ -2276,8 +2274,7 @@
 
     //  init our bounds from the path
     {
-        SkRect      pathBounds;
-        devPath.computeBounds(&pathBounds, SkPath::kExact_BoundsType);
+        SkRect pathBounds = devPath.getBounds();
         pathBounds.inset(-SK_ScalarHalf, -SK_ScalarHalf);
         pathBounds.roundOut(bounds);
     }
diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp
index da70c74..1441385 100644
--- a/src/core/SkPaint.cpp
+++ b/src/core/SkPaint.cpp
@@ -1517,36 +1517,24 @@
     return width != 0;  // return true if we're filled, or false if we're hairline (width == 0)
 }
 
-bool SkPaint::canComputeFastBounds() const {
-    // use bit-or since no need for early exit
-    return (reinterpret_cast<uintptr_t>(this->getMaskFilter()) |
-            reinterpret_cast<uintptr_t>(this->getLooper()) |
-            reinterpret_cast<uintptr_t>(this->getRasterizer()) |
-            reinterpret_cast<uintptr_t>(this->getPathEffect())) == 0;
-}
-
-const SkRect& SkPaint::computeFastBounds(const SkRect& src,
-                                         SkRect* storage) const {
+const SkRect& SkPaint::computeStrokeFastBounds(const SkRect& src,
+                                               SkRect* storage) const {
     SkASSERT(storage);
-    
-    if (this->getStyle() != SkPaint::kFill_Style) {
-        // if we're stroked, outset the rect by the radius (and join type)
-        SkScalar radius = SkScalarHalf(this->getStrokeWidth());
-        
-        if (0 == radius) {  // hairline
-            radius = SK_Scalar1;
-        } else if (this->getStrokeJoin() == SkPaint::kMiter_Join) {
-            SkScalar scale = this->getStrokeMiter();
-            if (scale > SK_Scalar1) {
-                radius = SkScalarMul(radius, scale);
-            }
+    SkASSERT(this->getStyle() != SkPaint::kFill_Style);
+
+    // since we're stroked, outset the rect by the radius (and join type)
+    SkScalar radius = SkScalarHalf(this->getStrokeWidth());
+    if (0 == radius) {  // hairline
+        radius = SK_Scalar1;
+    } else if (this->getStrokeJoin() == SkPaint::kMiter_Join) {
+        SkScalar scale = this->getStrokeMiter();
+        if (scale > SK_Scalar1) {
+            radius = SkScalarMul(radius, scale);
         }
-        storage->set(src.fLeft - radius, src.fTop - radius,
-                     src.fRight + radius, src.fBottom + radius);
-        return *storage;
     }
-    // no adjustments needed, just return the original rect
-    return src;
+    storage->set(src.fLeft - radius, src.fTop - radius,
+                 src.fRight + radius, src.fBottom + radius);
+    return *storage;
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index da90227..0cb50fb 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -27,7 +27,7 @@
  
     It captures some state about the path up front (i.e. if it already has a
     cached bounds), and the if it can, it updates the cache bounds explicitly,
-    avoiding the need to revisit all of the points in computeBounds().
+    avoiding the need to revisit all of the points in getBounds().
  */
 class SkAutoPathBoundsUpdate {
 public:
@@ -43,11 +43,11 @@
     
     ~SkAutoPathBoundsUpdate() {
         if (fEmpty) {
-            fPath->fFastBounds = fRect;
-            fPath->fFastBoundsIsDirty = false;
+            fPath->fBounds = fRect;
+            fPath->fBoundsIsDirty = false;
         } else if (!fDirty) {
-            fPath->fFastBounds.join(fRect);
-            fPath->fFastBoundsIsDirty = false;
+            fPath->fBounds.join(fRect);
+            fPath->fBoundsIsDirty = false;
         }
     }
     
@@ -60,14 +60,14 @@
     // returns true if we should proceed
     void init(const SkPath* path) {
         fPath = path;
-        fDirty = path->fFastBoundsIsDirty;
+        fDirty = path->fBoundsIsDirty;
         fEmpty = path->isEmpty();
         // Cannot use fRect for our bounds unless we know it is sorted
         fRect.sort();
     }
 };
 
-static void compute_fast_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
+static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
     if (pts.count() <= 1) {  // we ignore just 1 point (moveto)
         bounds->set(0, 0, 0, 0);
     } else {
@@ -91,7 +91,7 @@
 
 ////////////////////////////////////////////////////////////////////////////
 
-SkPath::SkPath() : fFastBoundsIsDirty(true), fFillType(kWinding_FillType) {}
+SkPath::SkPath() : fBoundsIsDirty(true), fFillType(kWinding_FillType) {}
 
 SkPath::SkPath(const SkPath& src) {
     SkDEBUGCODE(src.validate();)
@@ -106,11 +106,11 @@
     SkDEBUGCODE(src.validate();)
 
     if (this != &src) {
-        fFastBounds         = src.fFastBounds;
-        fPts                = src.fPts;
-        fVerbs              = src.fVerbs;
-        fFillType           = src.fFillType;
-        fFastBoundsIsDirty  = src.fFastBoundsIsDirty;
+        fBounds         = src.fBounds;
+        fPts            = src.fPts;
+        fVerbs          = src.fVerbs;
+        fFillType       = src.fFillType;
+        fBoundsIsDirty  = src.fBoundsIsDirty;
     }
     SkDEBUGCODE(this->validate();)
     return *this;
@@ -125,11 +125,11 @@
     SkASSERT(&other != NULL);
 
     if (this != &other) {
-        SkTSwap<SkRect>(fFastBounds, other.fFastBounds);
+        SkTSwap<SkRect>(fBounds, other.fBounds);
         fPts.swap(other.fPts);
         fVerbs.swap(other.fVerbs);
         SkTSwap<uint8_t>(fFillType, other.fFillType);
-        SkTSwap<uint8_t>(fFastBoundsIsDirty, other.fFastBoundsIsDirty);
+        SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
     }
 }
 
@@ -138,7 +138,7 @@
 
     fPts.reset();
     fVerbs.reset();
-    fFastBoundsIsDirty = true;
+    fBoundsIsDirty = true;
 }
 
 void SkPath::rewind() {
@@ -146,7 +146,7 @@
 
     fPts.rewind();
     fVerbs.rewind();
-    fFastBoundsIsDirty = true;
+    fBoundsIsDirty = true;
 }
 
 bool SkPath::isEmpty() const {
@@ -198,20 +198,12 @@
     }
 }
 
-#define ALWAYS_FAST_BOUNDS_FOR_NOW  true
-
-void SkPath::computeBounds(SkRect* bounds, BoundsType bt) const {
+void SkPath::computeBounds() const {
     SkDEBUGCODE(this->validate();)
+    SkASSERT(fBoundsIsDirty);
 
-    SkASSERT(bounds);
-    
-    // we BoundsType for now
-
-    if (fFastBoundsIsDirty) {
-        fFastBoundsIsDirty = false;
-        compute_fast_bounds(&fFastBounds, fPts);
-    }
-    *bounds = fFastBounds;
+    fBoundsIsDirty = false;
+    compute_pt_bounds(&fBounds, fPts);
 }
 
 //////////////////////////////////////////////////////////////////////////////
@@ -240,7 +232,7 @@
     }
     pt->set(x, y);
 
-    fFastBoundsIsDirty = true;
+    fBoundsIsDirty = true;
 }
 
 void SkPath::rMoveTo(SkScalar x, SkScalar y) {
@@ -259,7 +251,7 @@
     fPts.append()->set(x, y);
     *fVerbs.append() = kLine_Verb;
 
-    fFastBoundsIsDirty = true;
+    fBoundsIsDirty = true;
 }
 
 void SkPath::rLineTo(SkScalar x, SkScalar y) {
@@ -281,7 +273,7 @@
     pts[1].set(x2, y2);
     *fVerbs.append() = kQuad_Verb;
 
-    fFastBoundsIsDirty = true;
+    fBoundsIsDirty = true;
 }
 
 void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
@@ -304,7 +296,7 @@
     pts[2].set(x3, y3);
     *fVerbs.append() = kCubic_Verb;
 
-    fFastBoundsIsDirty = true;
+    fBoundsIsDirty = true;
 }
 
 void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
@@ -899,13 +891,13 @@
         matrix.mapPoints(dst->fPts.begin(), dst->fPts.count());
     } else {
         // remember that dst might == this, so be sure to check
-        // fFastBoundsIsDirty before we set it
-        if (!fFastBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
+        // fBoundsIsDirty before we set it
+        if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.count() > 1) {
             // if we're empty, fastbounds should not be mapped
-            matrix.mapRect(&dst->fFastBounds, fFastBounds);
-            dst->fFastBoundsIsDirty = false;
+            matrix.mapRect(&dst->fBounds, fBounds);
+            dst->fBoundsIsDirty = false;
         } else {
-            dst->fFastBoundsIsDirty = true;
+            dst->fBoundsIsDirty = true;
         }
 
         if (this != dst) {
@@ -918,14 +910,6 @@
     }
 }
 
-void SkPath::updateBoundsCache() const {
-    if (fFastBoundsIsDirty) {
-        SkRect  r;
-        this->computeBounds(&r, kFast_BoundsType);
-        SkASSERT(!fFastBoundsIsDirty);
-    }
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -1220,7 +1204,7 @@
     buffer.read(fPts.begin(), sizeof(SkPoint) * fPts.count());
     buffer.read(fVerbs.begin(), fVerbs.count());
     
-    fFastBoundsIsDirty = true;
+    fBoundsIsDirty = true;
 
     SkDEBUGCODE(this->validate();)
 }
@@ -1291,14 +1275,14 @@
     fPts.validate();
     fVerbs.validate();
 
-    if (!fFastBoundsIsDirty) {
+    if (!fBoundsIsDirty) {
         SkRect bounds;
-        compute_fast_bounds(&bounds, fPts);
+        compute_pt_bounds(&bounds, fPts);
         // can't call contains(), since it returns false if the rect is empty
-        SkASSERT(fFastBounds.fLeft <= bounds.fLeft);
-        SkASSERT(fFastBounds.fTop <= bounds.fTop);
-        SkASSERT(fFastBounds.fRight >= bounds.fRight);
-        SkASSERT(fFastBounds.fBottom >= bounds.fBottom);
+        SkASSERT(fBounds.fLeft <= bounds.fLeft);
+        SkASSERT(fBounds.fTop <= bounds.fTop);
+        SkASSERT(fBounds.fRight >= bounds.fRight);
+        SkASSERT(fBounds.fBottom >= bounds.fBottom);
     }
 }
 
diff --git a/src/core/SkPictureRecord.cpp b/src/core/SkPictureRecord.cpp
index ed47d64..77756a9 100644
--- a/src/core/SkPictureRecord.cpp
+++ b/src/core/SkPictureRecord.cpp
@@ -138,9 +138,7 @@
     validate();
     
     if (fRecordFlags & SkPicture::kUsePathBoundsForClip_RecordingFlag) {
-        SkRect bounds;
-        path.computeBounds(&bounds, SkPath::kFast_BoundsType);
-        return this->INHERITED::clipRect(bounds, op);
+        return this->INHERITED::clipRect(path.getBounds(), op);
     } else {
         return this->INHERITED::clipPath(path, op);
     }
diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp
index b271970..cc3eb1a 100644
--- a/src/core/SkScalerContext.cpp
+++ b/src/core/SkScalerContext.cpp
@@ -271,11 +271,8 @@
             }
         } else {
             // just use devPath
-            SkRect  r;
             SkIRect ir;
-
-            devPath.computeBounds(&r, SkPath::kExact_BoundsType);
-            r.roundOut(&ir);
+            devPath.getBounds().roundOut(&ir);
             
             glyph->fLeft    = ir.fLeft;
             glyph->fTop     = ir.fTop;
diff --git a/src/core/SkScan_AntiPath.cpp b/src/core/SkScan_AntiPath.cpp
index 422d060..f2f117a 100644
--- a/src/core/SkScan_AntiPath.cpp
+++ b/src/core/SkScan_AntiPath.cpp
@@ -350,11 +350,8 @@
         return;
     }
 
-    SkRect      r;
-    SkIRect     ir;
-
-    path.computeBounds(&r, SkPath::kFast_BoundsType);
-    r.roundOut(&ir);
+    SkIRect ir;
+    path.getBounds().roundOut(&ir);
     if (ir.isEmpty()) {
         return;
     }
diff --git a/src/core/SkScan_Hairline.cpp b/src/core/SkScan_Hairline.cpp
index 6a66754..b959968 100644
--- a/src/core/SkScan_Hairline.cpp
+++ b/src/core/SkScan_Hairline.cpp
@@ -248,11 +248,8 @@
 
     if (clip)
     {
-        SkRect      bounds;
-        SkIRect     ibounds;
-
-        path.computeBounds(&bounds, SkPath::kFast_BoundsType);
-        bounds.roundOut(&ibounds);
+        SkIRect ibounds;
+        path.getBounds().roundOut(&ibounds);
         ibounds.inset(-1, -1);
 
         if (clip->quickReject(ibounds))
diff --git a/src/core/SkScan_Path.cpp b/src/core/SkScan_Path.cpp
index fcf1530..ead1b85 100644
--- a/src/core/SkScan_Path.cpp
+++ b/src/core/SkScan_Path.cpp
@@ -582,11 +582,8 @@
         return;
     }
 
-    SkRect  r;
     SkIRect ir;
-
-    path.computeBounds(&r, SkPath::kFast_BoundsType);
-    r.round(&ir);
+    path.getBounds().round(&ir);
     if (ir.isEmpty()) {
         if (path.isInverseFillType()) {
             blitter->blitRegion(clip);
diff --git a/src/core/SkStroke.cpp b/src/core/SkStroke.cpp
index 86dff48..30d524e 100644
--- a/src/core/SkStroke.cpp
+++ b/src/core/SkStroke.cpp
@@ -538,8 +538,7 @@
         routine
     */
     static int needs_to_shrink(const SkPath& path) {
-        SkRect r;
-        path.computeBounds(&r, SkPath::kFast_BoundsType);
+        const SkRect& r = path.getBounds();
         SkFixed mask = SkAbs32(r.fLeft);
         mask |= SkAbs32(r.fTop);
         mask |= SkAbs32(r.fRight);
diff --git a/src/effects/Sk2DPathEffect.cpp b/src/effects/Sk2DPathEffect.cpp
index 405b194..603deb7 100644
--- a/src/effects/Sk2DPathEffect.cpp
+++ b/src/effects/Sk2DPathEffect.cpp
@@ -45,12 +45,10 @@
 {
     Sk2DPathEffectBlitter   blitter(this, dst);
     SkPath                  tmp;
-    SkRect                  bounds;
     SkIRect                 ir;
 
     src.transform(fInverse, &tmp);
-    tmp.computeBounds(&bounds, SkPath::kExact_BoundsType);
-    bounds.round(&ir);
+    tmp.getBounds().round(&ir);
     if (!ir.isEmpty()) {
         // need to pass a clip to fillpath, required for inverse filltypes,
         // even though those do not make sense for this patheffect
diff --git a/src/gl/SkGL.cpp b/src/gl/SkGL.cpp
index 0634709..89bcdf4 100644
--- a/src/gl/SkGL.cpp
+++ b/src/gl/SkGL.cpp
@@ -443,9 +443,7 @@
 }
 
 void SkGL::DrawPath(const SkPath& path, bool useTex, SkGLClipIter* clipIter) {
-    SkRect  bounds;
-    
-    path.computeBounds(&bounds, SkPath::kFast_BoundsType);
+    const SkRect& bounds = path.getBounds();
     if (bounds.isEmpty()) {
         return;
     }
diff --git a/src/utils/SkDumpCanvas.cpp b/src/utils/SkDumpCanvas.cpp
index a0c85fe..1ef444c 100644
--- a/src/utils/SkDumpCanvas.cpp
+++ b/src/utils/SkDumpCanvas.cpp
@@ -53,9 +53,7 @@
     if (path.isEmpty()) {
         str->set("path:empty");
     } else {
-        SkRect bounds;
-        path.computeBounds(&bounds, SkPath::kFast_BoundsType);
-        toString(bounds, str);
+        toString(path.getBounds(), str);
 #if 1
         SkString s;
         dumpVerbs(path, &s);
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index 383ad03..0e44719 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -11,16 +11,11 @@
     REPORTER_ASSERT(reporter, p == p2);
     REPORTER_ASSERT(reporter, !(p != p2));
 
-    // initialize bounds to not-empty
-    bounds.set(0, 0, SK_Scalar1, SK_Scalar1);
-    p.computeBounds(&bounds, SkPath::kFast_BoundsType);
-    REPORTER_ASSERT(reporter, bounds.isEmpty());
+    REPORTER_ASSERT(reporter, p.getBounds().isEmpty());
     
     bounds.set(0, 0, SK_Scalar1, SK_Scalar1);
     p.addRect(bounds);
-    bounds2.setEmpty();
-    p.computeBounds(&bounds2, SkPath::kFast_BoundsType);
-    REPORTER_ASSERT(reporter, bounds == bounds2);
+    REPORTER_ASSERT(reporter, bounds == p.getBounds());
 
     REPORTER_ASSERT(reporter, p != p2);
     REPORTER_ASSERT(reporter, !(p == p2));
@@ -35,8 +30,7 @@
     
     bounds.offset(SK_Scalar1*3, SK_Scalar1*4);
     p.offset(SK_Scalar1*3, SK_Scalar1*4);
-    p.computeBounds(&bounds2, SkPath::kFast_BoundsType);
-    REPORTER_ASSERT(reporter, bounds == bounds2);
+    REPORTER_ASSERT(reporter, bounds == p.getBounds());
 
 #if 0 // isRect needs to be implemented
     REPORTER_ASSERT(reporter, p.isRect(NULL));
diff --git a/tests/main.cpp b/tests/main.cpp
index 7975de0..13f8817 100644
--- a/tests/main.cpp
+++ b/tests/main.cpp
@@ -1,9 +1,11 @@
-#include <iostream>
 #include "SkGraphics.h"
 #include "Test.h"
 
 using namespace skiatest;
 
+// need to explicitly declare this, or we get some weird infinite loop llist
+template TestRegistry* TestRegistry::gHead;
+
 class Iter {
 public:
     Iter(Reporter* r) : fReporter(r) {
@@ -35,13 +37,13 @@
     return result == Reporter::kPassed ? "passed" : "FAILED";
 }
 
-class PrintfReporter : public Reporter {
+class DebugfReporter : public Reporter {
 protected:
     virtual void onStart(Test* test) {
-        printf("Running %s...\n", test->getName());
+        SkDebugf("Running %s...\n", test->getName());
     }
     virtual void onReport(const char desc[], Reporter::Result result) {
-        printf("\t%s: %s\n", result2string(result), desc);
+        SkDebugf("\t%s: %s\n", result2string(result), desc);
     }
     virtual void onEnd(Test* test) {}
 };
@@ -59,7 +61,7 @@
 int main (int argc, char * const argv[]) {
     SkAutoGraphics ag;
 
-    PrintfReporter reporter;
+    DebugfReporter reporter;
     Iter iter(&reporter);
     Test* test;
     
@@ -71,7 +73,7 @@
     int total = reporter.countTests();
     int passed = reporter.countResults(Reporter::kPassed);
     int failed = reporter.countResults(Reporter::kFailed);
-    printf("Tests=%d Passed=%d (%g%%) Failed=%d (%g%%)\n", total,
+    SkDebugf("Tests=%d Passed=%d (%g%%) Failed=%d (%g%%)\n", total,
            passed, passed * 100.f / total,
            failed, failed * 100.f / total);
 
diff --git a/xcode/sampleapp/SampleApp.xcodeproj/project.pbxproj b/xcode/sampleapp/SampleApp.xcodeproj/project.pbxproj
index 813856b..24777c7 100644
--- a/xcode/sampleapp/SampleApp.xcodeproj/project.pbxproj
+++ b/xcode/sampleapp/SampleApp.xcodeproj/project.pbxproj
@@ -68,8 +68,6 @@
 		008C4D980F77DAEE0056981C /* SampleHairline.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 008C4D970F77DAEE0056981C /* SampleHairline.cpp */; };
 		009CC9190F65918A002185BE /* SampleFontScalerTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 009CC9180F65918A002185BE /* SampleFontScalerTest.cpp */; };
 		00A41E4B0EFC312F00C9CBEB /* SampleArc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 00A41E4A0EFC312F00C9CBEB /* SampleArc.cpp */; };
-		00FACE3C0F7D0E6600F8A7FF /* SkLargeBlock_malloc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 00FACE3B0F7D0E6600F8A7FF /* SkLargeBlock_malloc.cpp */; };
-		00FACE680F7D23DD00F8A7FF /* SkWriter32_largeBlock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 00FACE5C0F7D209700F8A7FF /* SkWriter32_largeBlock.cpp */; };
 		0156F80407C56A3000C6122B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0156F80307C56A3000C6122B /* Foundation.framework */; };
 		01FC44D507BD3BB800D228F4 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01FC44D407BD3BB800D228F4 /* Quartz.framework */; };
 		8D0C4E8D0486CD37000505A6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0867D6AAFE840B52C02AAC07 /* InfoPlist.strings */; };
@@ -186,10 +184,6 @@
 		009CC9180F65918A002185BE /* SampleFontScalerTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SampleFontScalerTest.cpp; path = ../../samplecode/SampleFontScalerTest.cpp; sourceTree = SOURCE_ROOT; };
 		00A41E4A0EFC312F00C9CBEB /* SampleArc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SampleArc.cpp; path = ../../samplecode/SampleArc.cpp; sourceTree = SOURCE_ROOT; };
 		00D6B5CB0F72DC4300C466B9 /* SampleFuzz.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SampleFuzz.cpp; path = ../../samplecode/SampleFuzz.cpp; sourceTree = SOURCE_ROOT; };
-		00FACE3B0F7D0E6600F8A7FF /* SkLargeBlock_malloc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SkLargeBlock_malloc.cpp; path = ../../src/utils/SkLargeBlock_malloc.cpp; sourceTree = SOURCE_ROOT; };
-		00FACE3F0F7D167400F8A7FF /* SkLargeBlock_remap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SkLargeBlock_remap.cpp; path = ../../src/utils/SkLargeBlock_remap.cpp; sourceTree = SOURCE_ROOT; };
-		00FACE5C0F7D209700F8A7FF /* SkWriter32_largeBlock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SkWriter32_largeBlock.cpp; path = ../../src/utils/SkWriter32_largeBlock.cpp; sourceTree = SOURCE_ROOT; };
-		00FACE600F7D22CF00F8A7FF /* SkWriter32_noLargeBlock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SkWriter32_noLargeBlock.cpp; path = ../../src/utils/SkWriter32_noLargeBlock.cpp; sourceTree = SOURCE_ROOT; };
 		0156F80307C56A3000C6122B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
 		01FC44D407BD3BB800D228F4 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = /System/Library/Frameworks/Quartz.framework; sourceTree = "<absolute>"; };
 		0867D6ABFE840B52C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -337,10 +331,6 @@
 				002884490EFAA35C0083E387 /* core.xcodeproj */,
 				002884B40EFAB69F0083E387 /* maccore.xcodeproj */,
 				00003C8C0EFC230E000FF73A /* effects.xcodeproj */,
-				00FACE3B0F7D0E6600F8A7FF /* SkLargeBlock_malloc.cpp */,
-				00FACE3F0F7D167400F8A7FF /* SkLargeBlock_remap.cpp */,
-				00FACE5C0F7D209700F8A7FF /* SkWriter32_largeBlock.cpp */,
-				00FACE600F7D22CF00F8A7FF /* SkWriter32_noLargeBlock.cpp */,
 			);
 			name = CICarbonSample;
 			sourceTree = "<group>";
@@ -529,8 +519,6 @@
 				007A7CB30F01658C00A2D6EE /* SamplePicture.cpp in Sources */,
 				0041CE440F00A12400695E8C /* SampleLines.cpp in Sources */,
 				008C4D980F77DAEE0056981C /* SampleHairline.cpp in Sources */,
-				00FACE3C0F7D0E6600F8A7FF /* SkLargeBlock_malloc.cpp in Sources */,
-				00FACE680F7D23DD00F8A7FF /* SkWriter32_largeBlock.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};