Snapshot from http://skia.googlecode.com/svn/trunk@7527

Change-Id: I83c7c2152f5d2c303b4655a5a604f513a54f350a
diff --git a/tools/PdfRenderer.cpp b/tools/PdfRenderer.cpp
index 8819266..9a4bd38 100644
--- a/tools/PdfRenderer.cpp
+++ b/tools/PdfRenderer.cpp
@@ -48,15 +48,10 @@
     }
 }
 
-bool PdfRenderer::write(const SkString& path) const {
+void PdfRenderer::write(SkWStream* stream) const {
     SkPDFDocument doc;
     doc.appendPage(fPDFDevice);
-    SkFILEWStream stream(path.c_str());
-    if (stream.isValid()) {
-        doc.emitPDF(&stream);
-        return true;
-    }
-    return false;
+    doc.emitPDF(stream);
 }
 
 void SimplePdfRenderer::render() {
diff --git a/tools/PdfRenderer.h b/tools/PdfRenderer.h
index bce6197..3524a9d 100644
--- a/tools/PdfRenderer.h
+++ b/tools/PdfRenderer.h
@@ -39,7 +39,7 @@
         , fPDFDevice(NULL)
         {}
 
-    bool write(const SkString& path) const;
+    void write(SkWStream* stream) const;
 
 protected:
     SkCanvas* setupCanvas();
diff --git a/tools/PictureBenchmark.cpp b/tools/PictureBenchmark.cpp
index bdf1306..f1be2aa 100644
--- a/tools/PictureBenchmark.cpp
+++ b/tools/PictureBenchmark.cpp
@@ -70,9 +70,8 @@
     // We throw this away to remove first time effects (such as paging in this program)
     fRenderer->setup();
     fRenderer->render(NULL);
-    fRenderer->resetState();
+    fRenderer->resetState(true);
 
-    BenchTimer* timer = this->setupTimer();
     bool usingGpu = false;
 #if SK_SUPPORT_GPU
     usingGpu = fRenderer->isUsingGpuDevice();
@@ -95,26 +94,55 @@
 
         int x, y;
         while (tiledRenderer->nextTile(x, y)) {
-            TimerData timerData(tiledRenderer->getPerIterTimeFormat(),
-                                tiledRenderer->getNormalTimeFormat());
+            // There are two timers, which will behave slightly differently:
+            // 1) longRunningTimer, along with perTileTimerData, will time how long it takes to draw
+            // one tile fRepeats times, and take the average. As such, it will not respect the
+            // logPerIter or printMin options, since it does not know the time per iteration. It
+            // will also be unable to call flush() for each tile.
+            // The goal of this timer is to make up for a system timer that is not precise enough to
+            // measure the small amount of time it takes to draw one tile once.
+            //
+            // 2) perTileTimer, along with perTileTimerData, will record each run separately, and
+            // then take the average. As such, it supports logPerIter and printMin options.
+            SkAutoTDelete<BenchTimer> longRunningTimer(this->setupTimer());
+            TimerData longRunningTimerData(tiledRenderer->getPerIterTimeFormat(),
+                                           tiledRenderer->getNormalTimeFormat());
+            SkAutoTDelete<BenchTimer> perTileTimer(this->setupTimer());
+            TimerData perTileTimerData(tiledRenderer->getPerIterTimeFormat(),
+                                       tiledRenderer->getNormalTimeFormat());
+            longRunningTimer->start();
             for (int i = 0; i < fRepeats; ++i) {
-                timer->start();
+                perTileTimer->start();
                 tiledRenderer->drawCurrentTile();
-                timer->truncatedEnd();
-                tiledRenderer->resetState();
-                timer->end();
-                timerData.appendTimes(timer, fRepeats - 1 == i);
+                perTileTimer->truncatedEnd();
+                tiledRenderer->resetState(false);
+                perTileTimer->end();
+                perTileTimerData.appendTimes(perTileTimer.get(), fRepeats - 1 == i);
             }
+            longRunningTimer->truncatedEnd();
+            tiledRenderer->resetState(true);
+            longRunningTimer->end();
+            longRunningTimerData.appendTimes(longRunningTimer.get(), true);
+
             SkString configName = tiledRenderer->getConfigName();
             configName.appendf(": tile [%i,%i] out of [%i,%i]", x, y, xTiles, yTiles);
-            SkString result = timerData.getResult(fLogPerIter, fPrintMin, fRepeats,
-                                                  configName.c_str(), fShowWallTime,
-                                                  fShowTruncatedWallTime, fShowCpuTime,
-                                                  fShowTruncatedCpuTime, usingGpu && fShowGpuTime);
+            SkString result = perTileTimerData.getResult(fLogPerIter, fPrintMin, fRepeats,
+                                                         configName.c_str(), fShowWallTime,
+                                                         fShowTruncatedWallTime, fShowCpuTime,
+                                                         fShowTruncatedCpuTime,
+                                                         usingGpu && fShowGpuTime);
             result.append("\n");
             this->logProgress(result.c_str());
+
+            configName.append(" <averaged>");
+            SkString longRunningResult = longRunningTimerData.getResult(false, false, fRepeats,
+                    configName.c_str(), fShowWallTime, fShowTruncatedWallTime,
+                    fShowCpuTime, fShowTruncatedCpuTime, usingGpu && fShowGpuTime);
+            longRunningResult.append("\n");
+            this->logProgress(longRunningResult.c_str());
         }
     } else {
+        SkAutoTDelete<BenchTimer> timer(this->setupTimer());
         TimerData timerData(fRenderer->getPerIterTimeFormat(), fRenderer->getNormalTimeFormat());
         for (int i = 0; i < fRepeats; ++i) {
             fRenderer->setup();
@@ -124,10 +152,10 @@
             timer->truncatedEnd();
 
             // Finishes gl context
-            fRenderer->resetState();
+            fRenderer->resetState(true);
             timer->end();
 
-            timerData.appendTimes(timer, fRepeats - 1 == i);
+            timerData.appendTimes(timer.get(), fRepeats - 1 == i);
         }
 
         SkString configName = fRenderer->getConfigName();
@@ -140,7 +168,6 @@
     }
 
     fRenderer->end();
-    SkDELETE(timer);
 }
 
 }
diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp
index 61de8c6..cb2c3a4 100644
--- a/tools/PictureRenderer.cpp
+++ b/tools/PictureRenderer.cpp
@@ -131,7 +131,7 @@
 }
 
 void PictureRenderer::end() {
-    this->resetState();
+    this->resetState(true);
     SkSafeUnref(fPicture);
     fPicture = NULL;
     fCanvas.reset(NULL);
@@ -172,7 +172,7 @@
     }
 }
 
-void PictureRenderer::resetState() {
+void PictureRenderer::resetState(bool callFinish) {
 #if SK_SUPPORT_GPU
     if (this->isUsingGpuDevice()) {
         SkGLContext* glContext = fGrContextFactory.getGLContext(
@@ -184,14 +184,17 @@
         }
 
         fGrContext->flush();
-        SK_GL(*glContext, Finish());
+        if (callFinish) {
+            SK_GL(*glContext, Finish());
+        }
     }
 #endif
 }
 
 uint32_t PictureRenderer::recordFlags() {
-    return kNone_BBoxHierarchyType == fBBoxHierarchyType ? 0 :
-        SkPicture::kOptimizeForClippedPlayback_RecordingFlag;
+    return ((kNone_BBoxHierarchyType == fBBoxHierarchyType) ? 0 :
+        SkPicture::kOptimizeForClippedPlayback_RecordingFlag) |
+        SkPicture::kUsePathBoundsForClip_RecordingFlag;
 }
 
 /**
@@ -288,7 +291,7 @@
         *out = SkNEW(SkBitmap);
         setup_bitmap(*out, fPicture->width(), fPicture->height());
         fCanvas->readPixels(*out, 0, 0);
-    }    
+    }
     return true;
 }
 
@@ -315,7 +318,7 @@
     if (NULL != path) {
         return write(fCanvas, *path);
     }
-    
+
     if (NULL != out) {
         *out = SkNEW(SkBitmap);
         setup_bitmap(*out, fPicture->width(), fPicture->height());
@@ -522,8 +525,8 @@
         }
         if (NULL != out) {
             if (fCanvas->readPixels(&bitmap, 0, 0)) {
-                bitmapCopySubset(bitmap, *out, fTileRects[i].left(),
-                                 fTileRects[i].top());
+                bitmapCopySubset(bitmap, *out, SkScalarFloorToInt(fTileRects[i].left()),
+                                 SkScalarFloorToInt(fTileRects[i].top()));
             } else {
                 success = false;
             }
@@ -594,9 +597,9 @@
         SkBitmap bitmap;
         if (fBitmap != NULL) {
             // All tiles are the same size.
-            setup_bitmap(&bitmap, fRects[0].width(), fRects[0].height());
+            setup_bitmap(&bitmap, SkScalarFloorToInt(fRects[0].width()), SkScalarFloorToInt(fRects[0].height()));
         }
-        
+
         for (int i = fStart; i < fEnd; i++) {
             DrawTileToCanvas(fCanvas, fRects[i], fClone);
             if (fPath != NULL && !writeAppendNumber(fCanvas, fPath, i)
@@ -608,8 +611,8 @@
             if (fBitmap != NULL) {
                 if (fCanvas->readPixels(&bitmap, 0, 0)) {
                     SkAutoLockPixels alp(*fBitmap);
-                    bitmapCopySubset(bitmap, fBitmap, fRects[i].left(),
-                                     fRects[i].top());
+                    bitmapCopySubset(bitmap, fBitmap, SkScalarFloorToInt(fRects[i].left()),
+                                     SkScalarFloorToInt(fRects[i].top()));
                 } else {
                     *fSuccess = false;
                     // If one tile fails to read pixels, do not continue drawing the rest.
diff --git a/tools/PictureRenderer.h b/tools/PictureRenderer.h
index 5d6c516..df38faa 100644
--- a/tools/PictureRenderer.h
+++ b/tools/PictureRenderer.h
@@ -110,7 +110,12 @@
      */
     virtual TiledPictureRenderer* getTiledRenderer() { return NULL; }
 
-    void resetState();
+    /**
+     * Resets the GPU's state. Does nothing if the backing is raster. For a GPU renderer, calls
+     * flush, and calls finish if callFinish is true.
+     * @param callFinish Whether to call finish.
+     */
+    void resetState(bool callFinish);
 
     void setDeviceType(SkDeviceTypes deviceType) {
         fDeviceType = deviceType;
@@ -184,10 +189,10 @@
         , fBBoxHierarchyType(kNone_BBoxHierarchyType)
         , fGridWidth(0)
         , fGridHeight(0)
-        , fScaleFactor(SK_Scalar1)
 #if SK_SUPPORT_GPU
         , fGrContext(fGrContextFactory.get(GrContextFactory::kNative_GLContextType))
 #endif
+        , fScaleFactor(SK_Scalar1)
         {
             sk_bzero(fDrawFilters, sizeof(fDrawFilters));
             fViewport.set(0, 0);
diff --git a/tools/bench_pictures.cfg b/tools/bench_pictures.cfg
index c64a686..661af52 100644
--- a/tools/bench_pictures.cfg
+++ b/tools/bench_pictures.cfg
@@ -55,11 +55,11 @@
   TiledBitmapConfig(1024, 64),
 
   # Different bounding box heirarchies, for different modes.
-  RecordRTreeConfig(          DEFAULT_TILE_X, DEFAULT_TILE_Y),
-  RecordGridConfig(           DEFAULT_TILE_X, DEFAULT_TILE_Y),
-  PlaybackCreationRTreeConfig(DEFAULT_TILE_X, DEFAULT_TILE_Y),
-  PlaybackCreationGridConfig( DEFAULT_TILE_X, DEFAULT_TILE_Y),
+  RecordRTreeConfig(),
+  PlaybackCreationRTreeConfig(),
   TileRTreeConfig(            DEFAULT_TILE_X, DEFAULT_TILE_Y),
+  RecordGridConfig(           DEFAULT_TILE_X, DEFAULT_TILE_Y),
+  PlaybackCreationGridConfig( DEFAULT_TILE_X, DEFAULT_TILE_Y),
   TileGridConfig(             DEFAULT_TILE_X, DEFAULT_TILE_Y),
 ]
 
@@ -73,8 +73,8 @@
 
   configs = [
     # Record
-    RecordConfig(scale=str(scale)),
-    RecordRTreeConfig(tile_x, tile_y, scale=str(scale)),
+    RecordConfig(     scale=str(scale)),
+    RecordRTreeConfig(scale=str(scale)),
     RecordGridConfig( tile_x, tile_y, scale=str(scale)),
 
     # Tiled playback
@@ -85,7 +85,6 @@
     # Viewport playback
     ViewportBitmapConfig(viewport_x, viewport_y, scale=str(scale)),
     ViewportRTreeConfig( viewport_x, viewport_y, scale=str(scale)),
-    ViewportGridConfig(  viewport_x, viewport_y, scale=str(scale)),
   ]
 
   if do_gpu:
@@ -107,11 +106,11 @@
   'debug': [TiledBitmapConfig(DEFAULT_TILE_X, DEFAULT_TILE_Y)],
   'default': default_configs,
   'no_gpu': [cfg for cfg in default_configs if cfg['device'] != 'gpu'],
-  'nexus_s':      AndroidConfigList((256, 256), 0.4897, [],  (800,  480),
+  'nexus_s':      AndroidConfigList((256, 256), 0.4897, [],  (480,  800),
                                     do_gpu=False),
-  'nexus_4':      AndroidConfigList((256, 256), 0.8163, [],  (1280, 768)),
-  'nexus_7':      AndroidConfigList((256, 256), 0.8163, [2], (1280, 800)),
-  'nexus_10':     AndroidConfigList((512, 512), 1.6326, [],  (2560, 1600)),
-  'galaxy_nexus': AndroidConfigList((256, 256), 0.8163, [],  (1280, 800)),
-  'xoom':         AndroidConfigList((256, 256), 0.8163, [],  (1200, 800)),
+  'xoom':         AndroidConfigList((256, 256), 1.2244, [],  (1200, 800)),
+  'galaxy_nexus': AndroidConfigList((256, 256), 0.8163, [],  (800,  1280)),
+  'nexus_4':      AndroidConfigList((256, 256), 0.7836, [],  (768,  1280)),
+  'nexus_7':      AndroidConfigList((256, 256), 1.3061, [2], (1280, 800)),
+  'nexus_10':     AndroidConfigList((512, 512), 2.6122, [],  (2560, 1600)),
 }
\ No newline at end of file
diff --git a/tools/bench_pictures_cfg_helper.py b/tools/bench_pictures_cfg_helper.py
index 654bad6..65d63a4 100644
--- a/tools/bench_pictures_cfg_helper.py
+++ b/tools/bench_pictures_cfg_helper.py
@@ -13,6 +13,13 @@
   return config
 
 
+def TileArgs(tile_x, tile_y):
+  return {'mode': ['tile', str(tile_x), str(tile_y)],
+          # TODO(borenet): Turn this back on once the parser is working.
+          #'timeIndividualTiles': True
+          }
+
+
 def BitmapConfig(**kwargs):
   return Config(device='bitmap', **kwargs)
 
@@ -22,11 +29,11 @@
 
 
 def TiledBitmapConfig(tile_x, tile_y, **kwargs):
-  return BitmapConfig(mode=['tile', str(tile_x), str(tile_y)], **kwargs)
+  return BitmapConfig(**dict(TileArgs(tile_x, tile_y).items() + kwargs.items()))
 
 
 def TiledGPUConfig(tile_x, tile_y, **kwargs):
-  return GPUConfig(mode=['tile', str(tile_x), str(tile_y)], **kwargs)
+  return GPUConfig(**dict(TileArgs(tile_x, tile_y).items() + kwargs.items()))
 
 
 def ViewportBitmapConfig(viewport_x, viewport_y, **kwargs):
@@ -38,8 +45,8 @@
 
 
 def ViewportRTreeConfig(viewport_x, viewport_y, **kwargs):
-  return RTreeConfig(viewport_x, viewport_y, mode='simple',
-                     viewport=[str(viewport_x), str(viewport_y)], **kwargs)
+  return RTreeConfig(mode='simple', viewport=[str(viewport_x), str(viewport_y)],
+                     **kwargs)
 
 
 def ViewportGridConfig(viewport_x, viewport_y, **kwargs):
@@ -64,9 +71,8 @@
                            **kwargs)
 
 
-def RTreeConfig(tile_x, tile_y, mode, **kwargs):
-  return BitmapConfig(mode=mode, bbh=['rtree', str(tile_x), str(tile_y)],
-                      **kwargs)
+def RTreeConfig(**kwargs):
+  return BitmapConfig(bbh='rtree', **kwargs)
 
 
 def GridConfig(tile_x, tile_y, mode, **kwargs):
@@ -74,18 +80,16 @@
                       **kwargs)
 
 
-def RecordRTreeConfig(tile_x, tile_y, **kwargs):
-  return RTreeConfig(tile_x=tile_x, tile_y=tile_y, mode='record', **kwargs)
+def RecordRTreeConfig(**kwargs):
+  return RTreeConfig(mode='record', **kwargs)
 
 
-def PlaybackCreationRTreeConfig(tile_x, tile_y, **kwargs):
-  return RTreeConfig(tile_x=tile_x, tile_y=tile_y, mode='playbackCreation',
-                     **kwargs)
+def PlaybackCreationRTreeConfig(**kwargs):
+  return RTreeConfig(mode='playbackCreation', **kwargs)
 
 
 def TileRTreeConfig(tile_x, tile_y, **kwargs):
-  return RTreeConfig(tile_x=tile_x, tile_y=tile_y,
-                     mode=['tile', str(tile_x), str(tile_y)], **kwargs)
+  return RTreeConfig(**dict(TileArgs(tile_x, tile_y).items() + kwargs.items()))
 
 
 def RecordGridConfig(tile_x, tile_y, **kwargs):
@@ -97,5 +101,5 @@
 
 
 def TileGridConfig(tile_x, tile_y, **kwargs):
-  return GridConfig(tile_x, tile_y, mode=['tile', str(tile_x), str(tile_y)],
-                    **kwargs)
\ No newline at end of file
+  return GridConfig(tile_x, tile_y,
+                    **dict(TileArgs(tile_x, tile_y).items() + kwargs.items()))
\ No newline at end of file
diff --git a/tools/bench_pictures_main.cpp b/tools/bench_pictures_main.cpp
index 65fc42a..b485aff 100644
--- a/tools/bench_pictures_main.cpp
+++ b/tools/bench_pictures_main.cpp
@@ -27,6 +27,7 @@
     "line",
     "bitmap",
     "rect",
+    "oval",
     "path",
     "text",
     "all",
@@ -450,7 +451,7 @@
                 gLogger.logError("Missing scaleFactor for --scale\n");
                 PRINT_USAGE_AND_EXIT;
             }
-            scaleFactor = atof(*argv);
+            scaleFactor = SkDoubleToScalar(atof(*argv));
         } else if (0 == strcmp(*argv, "--tiles")) {
             ++argv;
             if (argv >= stop) {
@@ -774,7 +775,7 @@
 
 int tool_main(int argc, char** argv);
 int tool_main(int argc, char** argv) {
-#ifdef SK_ENABLE_INST_COUNT
+#if SK_ENABLE_INST_COUNT
     gPrintInstCount = true;
 #endif
     SkAutoGraphics ag;
diff --git a/tools/build-tot-chromium.sh b/tools/build-tot-chromium.sh
new file mode 100755
index 0000000..9592462
--- /dev/null
+++ b/tools/build-tot-chromium.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+#
+# Author: Ravi Mistry
+#
+# Script to checkout and build a fresh copy of Chromium from head that uses a
+# writable, tip-of-tree Skia rather than the read-only, revision-locked Skia
+# specified in http://src.chromium.org/viewvc/chrome/trunk/src/DEPS
+#
+# Sample Usage:
+#   tools/build-tot-chromium.sh ~/chromiumtrunk
+
+if [[ $# -ne 1 ]] ; then
+  echo "usage: $0 chromium_location"
+  exit 1
+fi
+CHROMIUM_LOCATION=$1
+
+echo -e "\n\n========Deleting $CHROMIUM_LOCATION========\n\n"
+rm -rf $CHROMIUM_LOCATION
+
+mkdir $CHROMIUM_LOCATION
+cd $CHROMIUM_LOCATION
+gclient config https://src.chromium.org/chrome/trunk/src
+echo '
+solutions = [
+  { "name"        : "src",
+    "url"         : "https://src.chromium.org/chrome/trunk/src",
+    "deps_file"   : "DEPS",
+    "managed"     : True,
+    "custom_deps" : {
+      "src/third_party/skia": "https://skia.googlecode.com/svn/trunk",
+      "src/third_party/skia/gyp": None,
+      "src/third_party/skia/src": None,
+      "src/third_party/skia/include": None,
+    },
+    "safesync_url": "",
+  },
+]
+' > .gclient
+
+echo -e "\n\n========Starting gclient sync========\n\n"
+START_TIME=$SECONDS
+gclient sync
+END_TIME=$SECONDS
+echo -e "\n\n========gclient sync took $((END_TIME - START_TIME)) seconds========\n\n"
+
+cd src
+rm -rf out/Debug out/Release
+GYP_GENERATORS='ninja' ./build/gyp_chromium
+
+echo -e "\n\n========Starting ninja build========\n\n"
+START_TIME=$SECONDS
+ninja -C out/Release chrome
+END_TIME=$SECONDS
+echo -e "\n\n========ninja build took $((END_TIME - START_TIME)) seconds========\n\n"
+
+SVN_VERSION=`svnversion .`
+echo -e "\n\n========The Chromium & Skia versions are $SVN_VERSION========\n\n"
+
diff --git a/tools/filtermain.cpp b/tools/filtermain.cpp
index c1b2040..3715b3e 100644
--- a/tools/filtermain.cpp
+++ b/tools/filtermain.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkDebugCanvas.h"
 #include "SkDevice.h"
 #include "SkGraphics.h"
 #include "SkImageDecoder.h"
@@ -19,129 +20,27 @@
 
 static void usage() {
     SkDebugf("Usage: filter -i inFile [-o outFile] [--input-dir path] [--output-dir path]\n");
-    SkDebugf("                        [-p pathFile] [-t textureDir] [-h|--help]\n\n");
+    SkDebugf("                        [-h|--help]\n\n");
     SkDebugf("    -i inFile  : file to file.\n");
     SkDebugf("    -o outFile : result of filtering.\n");
     SkDebugf("    --input-dir : process all files in dir with .skp extension.\n");
     SkDebugf("    --output-dir : results of filtering the input dir.\n");
-    SkDebugf("    -p pathFile : file in which to place compileable path data.\n");
-    SkDebugf("    -t textureDir : directory in which to place textures. (only available w/ single file)\n");
     SkDebugf("    -h|--help  : Show this help message.\n");
 }
 
-// SkFilterRecord allows the filter to manipulate the read in SkPicture
-class SkFilterRecord : public SkPictureRecord {
-public:
-    SkFilterRecord(uint32_t recordFlags, SkDevice* device, SkFILEWStream* pathStream)
-        : INHERITED(recordFlags, device)
-        , fTransSkipped(0)
-        , fTransTot(0)
-        , fScalesSkipped(0)
-        , fScalesTot(0)
-        , fPathStream(pathStream) {
-    }
-
-    virtual ~SkFilterRecord() {
-    }
-
-    virtual bool clipPath(const SkPath& path, SkRegion::Op op, bool doAntiAlias) SK_OVERRIDE {
-        if (!path.isRect(NULL) && 4 < path.countPoints()) {
-            sk_tools::dump_path(fPathStream, path);
-        }
-        return INHERITED::clipPath(path, op, doAntiAlias);
-    }
-
-    virtual void drawPath(const SkPath& path, const SkPaint& p) SK_OVERRIDE {
-        if (!path.isRect(NULL) && 4 < path.countPoints()) {
-            sk_tools::dump_path(fPathStream, path);
-        }
-        INHERITED::drawPath(path, p);
-    }
-
-    virtual bool translate(SkScalar dx, SkScalar dy) SK_OVERRIDE {
-        ++fTransTot;
-
-#if 0
-        if (0 == dx && 0 == dy) {
-            ++fTransSkipped;
-            return true;
-        }
-#endif
-
-        return INHERITED::translate(dx, dy);
-    }
-
-    virtual bool scale(SkScalar sx, SkScalar sy) SK_OVERRIDE {
-        ++fScalesTot;
-
-#if 0
-        if (SK_Scalar1 == sx && SK_Scalar1 == sy) {
-            ++fScalesSkipped;
-            return true;
-        }
-#endif
-
-        return INHERITED::scale(sx, sy);
-    }
-
-    void saveImages(const SkString& path) {
-        SkTRefArray<SkBitmap>* bitmaps = fBitmapHeap->extractBitmaps();
-
-        if (NULL != bitmaps) {
-            for (int i = 0; i < bitmaps->count(); ++i) {
-                SkString filename(path);
-                if (!path.endsWith("\\")) {
-                    filename.append("\\");
-                }
-                filename.append("image");
-                filename.appendS32(i);
-                filename.append(".png");
-
-                SkImageEncoder::EncodeFile(filename.c_str(), (*bitmaps)[i],
-                                           SkImageEncoder::kPNG_Type, 0);
-            }
-        }
-
-        bitmaps->unref();
-    }
-
-    void report() {
-        SkDebugf("%d Trans skipped (out of %d)\n", fTransSkipped, fTransTot);
-        SkDebugf("%d Scales skipped (out of %d)\n", fScalesSkipped, fScalesTot);
-    }
-
-protected:
-    int fTransSkipped;
-    int fTransTot;
-
-    int fScalesSkipped;
-    int fScalesTot;
-
-    SkFILEWStream* fPathStream;
-private:
-    typedef SkPictureRecord INHERITED;
-};
-
-// Wrap SkPicture to allow installation of a SkFilterRecord object
-class SkFilterPicture : public SkPicture {
-public:
-    SkFilterPicture(int width, int height, SkPictureRecord* record) {
-        fWidth = width;
-        fHeight = height;
-        fRecord = record;
-        SkSafeRef(fRecord);
-    }
-
-private:
-    typedef SkPicture INHERITED;
-};
-
-static bool PNGEncodeBitmapToStream(SkWStream* stream, const SkBitmap& bitmap) {
-    return SkImageEncoder::EncodeStream(stream, bitmap, SkImageEncoder::kPNG_Type, 100);
+// Is the supplied paint simply a color?
+static bool is_simple(const SkPaint& p) {
+    return NULL == p.getPathEffect() &&
+           NULL == p.getShader() &&
+           NULL == p.getXfermode() &&
+           NULL == p.getMaskFilter() &&
+           NULL == p.getColorFilter() &&
+           NULL == p.getRasterizer() &&
+           NULL == p.getLooper() &&
+           NULL == p.getImageFilter();
 }
 
-int filter_picture(const SkString& inFile, const SkString& outFile,
-                   const SkString& textureDir, SkFILEWStream *pathStream) {
+static int filter_picture(const SkString& inFile, const SkString& outFile) {
     SkPicture* inPicture = NULL;
 
     SkFILEStream inStream(inFile.c_str());
@@ -154,35 +53,67 @@
         return -1;
     }
 
-    SkBitmap bm;
-    bm.setConfig(SkBitmap::kNo_Config, inPicture->width(), inPicture->height());
-    SkAutoTUnref<SkDevice> dev(SkNEW_ARGS(SkDevice, (bm)));
+    SkDebugCanvas debugCanvas(inPicture->width(), inPicture->height());
+    debugCanvas.setBounds(inPicture->width(), inPicture->height());
+    inPicture->draw(&debugCanvas);
 
-    SkAutoTUnref<SkFilterRecord> filterRecord(SkNEW_ARGS(SkFilterRecord, (0, dev, pathStream)));
+    const SkTDArray<SkDrawCommand*>& commands = debugCanvas.getDrawCommands();
 
-    // Playback the read in picture to the SkFilterRecorder to allow filtering
-    filterRecord->beginRecording();
-    inPicture->draw(filterRecord);
-    filterRecord->endRecording();
+    for (int i = 0; i < commands.count(); ++i) {
+        // Check for:
+        //    SAVE_LAYER
+        //      DRAW_BITMAP_RECT_TO_RECT
+        //    RESTORE
+        // where the saveLayer's color can be moved into the drawBitmapRect
+        if (SAVE_LAYER == commands[i]->getType() && commands.count() > i+2) {
+            if (DRAW_BITMAP_RECT_TO_RECT == commands[i+1]->getType() &&
+                RESTORE == commands[i+2]->getType()) {
+                SaveLayer* sl = (SaveLayer*) commands[i];
+                DrawBitmapRect* dbmr = (DrawBitmapRect*) commands[i+1];
 
-    filterRecord->report();
+                const SkPaint* p0 = sl->paint();
+                SkPaint* p1 = dbmr->paint();
+
+                if (NULL == p0) {
+                    commands[i]->setVisible(false);
+                    commands[i+2]->setVisible(false);
+                } else if (NULL == p1) {
+                    commands[i]->setVisible(false);
+                    dbmr->setPaint(*p0);
+                    commands[i+2]->setVisible(false);
+                } else if (is_simple(*p0) &&
+                           (SkColorGetR(p0->getColor()) == SkColorGetR(p1->getColor())) &&
+                           (SkColorGetG(p0->getColor()) == SkColorGetG(p1->getColor())) &&
+                           (SkColorGetB(p0->getColor()) == SkColorGetB(p1->getColor()))) {
+                    commands[i]->setVisible(false);
+                    SkColor newColor = SkColorSetA(p1->getColor(),
+                                                   SkColorGetA(p0->getColor()));
+                    p1->setColor(newColor);
+                    commands[i+2]->setVisible(false);
+                }
+            }
+        }
+    }
 
     if (!outFile.isEmpty()) {
-        SkFilterPicture outPicture(inPicture->width(), inPicture->height(), filterRecord);
+        SkPicture outPicture;
+
+        SkCanvas* canvas = outPicture.beginRecording(inPicture->width(), inPicture->height());
+        debugCanvas.draw(canvas);
+        outPicture.endRecording();
+
         SkFILEWStream outStream(outFile.c_str());
 
         outPicture.serialize(&outStream);
     }
 
-    if (!textureDir.isEmpty()) {
-        filterRecord->saveImages(textureDir);
-    }
-
     return 0;
 }
 
 // This function is not marked as 'static' so it can be referenced externally
 // in the iOS build.
+int tool_main(int argc, char** argv); // suppress a warning on mac
+
 int tool_main(int argc, char** argv) {
     SkGraphics::Init();
 
@@ -191,7 +122,7 @@
         return -1;
     }
 
-    SkString inFile, outFile, inDir, outDir, textureDir, pathFile;
+    SkString inFile, outFile, inDir, outDir;
 
     char* const* stop = argv + argc;
     for (++argv; argv < stop; ++argv) {
@@ -231,24 +162,6 @@
                 usage();
                 return -1;
             }
-        } else if (strcmp(*argv, "-p") == 0) {
-            argv++;
-            if (argv < stop && **argv) {
-                pathFile.set(*argv);
-            } else {
-                SkDebugf("missing arg for -p\n");
-                usage();
-                return -1;
-            }
-        } else if (strcmp(*argv, "-t") == 0) {
-            argv++;
-            if (argv < stop && **argv) {
-                textureDir.set(*argv);
-            } else {
-                SkDebugf("missing arg for -t\n");
-                usage();
-                return -1;
-            }
         } else if (strcmp(*argv, "--help") == 0 || strcmp(*argv, "-h") == 0) {
             usage();
             return 0;
@@ -259,27 +172,8 @@
         }
     }
 
-    if(!inDir.isEmpty() && !textureDir.isEmpty()) {
-        SkDebugf("ERROR: The textureDir option is not permitted when passing an input directory.\n");
-        usage();
-        return -1;
-    }
-
-    SkFILEWStream *pathStream = NULL;
-
-    if (!pathFile.isEmpty()) {
-        pathStream = new SkFILEWStream(pathFile.c_str());
-        if (!pathStream->isValid()) {
-            SkDebugf("Could open path file %s\n", pathFile.c_str());
-            delete pathStream;
-            return -1;
-        }
-
-        sk_tools::dump_path_prefix(pathStream);
-    }
-
     SkOSFile::Iter iter(inDir.c_str(), "skp");
-    int failures = 0;
+
     SkString inputFilename, outputFilename;
     if (iter.next(&inputFilename)) {
 
@@ -289,26 +183,16 @@
                 sk_tools::make_filepath(&outFile, outDir, inputFilename);
             }
             SkDebugf("Executing %s\n", inputFilename.c_str());
-            filter_picture(inFile, outFile, textureDir, pathStream);
+            filter_picture(inFile, outFile);
         } while(iter.next(&inputFilename));
 
     } else if (!inFile.isEmpty()) {
-        filter_picture(inFile, outFile, textureDir, pathStream);
+        filter_picture(inFile, outFile);
     } else {
         usage();
-        if (NULL != pathStream) {
-            delete pathStream;
-            pathStream = NULL;
-        }
         return -1;
     }
 
-    if (NULL != pathStream) {
-        sk_tools::dump_path_suffix(pathStream);
-        delete pathStream;
-        pathStream = NULL;
-    }
-
     SkGraphics::Term();
     return 0;
 }
diff --git a/tools/pinspect.cpp b/tools/pinspect.cpp
index b82bd33..b5a6727 100644
--- a/tools/pinspect.cpp
+++ b/tools/pinspect.cpp
@@ -39,9 +39,13 @@
 }
 
 static void dumpOps(SkPicture* pic) {
+#ifdef SK_DEVELOPER
     SkDebugfDumper dumper;
     SkDumpCanvas canvas(&dumper);
     canvas.drawPicture(*pic);
+#else
+    printf("SK_DEVELOPER mode not enabled\n");
+#endif
 }
 
 int tool_main(int argc, char** argv);
diff --git a/tools/render_pdfs_main.cpp b/tools/render_pdfs_main.cpp
index 231f52e..3aac682 100644
--- a/tools/render_pdfs_main.cpp
+++ b/tools/render_pdfs_main.cpp
@@ -33,7 +33,7 @@
     SkDebugf("SKP to PDF rendering tool\n");
     SkDebugf("\n"
 "Usage: \n"
-"     %s <input>... <outputDir> \n"
+"     %s <input>... -w <outputDir> \n"
 , argv0);
     SkDebugf("\n\n");
     SkDebugf(
@@ -89,15 +89,25 @@
 static bool write_output(const SkString& outputDir,
                          const SkString& inputFilename,
                          const sk_tools::PdfRenderer& renderer) {
+    if (outputDir.isEmpty()) {
+        SkDynamicMemoryWStream stream;
+        renderer.write(&stream);
+        return true;
+    }
+
     SkString outputPath;
     if (!make_output_filepath(&outputPath, outputDir, inputFilename)) {
         return false;
     }
-    bool isWritten = renderer.write(outputPath);
-    if (!isWritten) {
+
+    SkFILEWStream stream(outputPath.c_str());
+    if (!stream.isValid()) {
         SkDebugf("Could not write to file %s\n", outputPath.c_str());
+        return false;
     }
-    return isWritten;
+    renderer.write(&stream);
+
+    return true;
 }
 
 /** Reads an skp file, renders it to pdf and writes the output to a pdf file
@@ -168,7 +178,8 @@
 }
 
 static void parse_commandline(int argc, char* const argv[],
-                              SkTArray<SkString>* inputs) {
+                              SkTArray<SkString>* inputs,
+                              SkString* outputDir) {
     const char* argv0 = argv[0];
     char* const* stop = argv + argc;
 
@@ -176,12 +187,20 @@
         if ((0 == strcmp(*argv, "-h")) || (0 == strcmp(*argv, "--help"))) {
             usage(argv0);
             exit(-1);
+        } else if (0 == strcmp(*argv, "-w")) {
+            ++argv;
+            if (argv >= stop) {
+                SkDebugf("Missing outputDir for -w\n");
+                usage(argv0);
+                exit(-1);
+            }
+            *outputDir = SkString(*argv);
         } else {
             inputs->push_back(SkString(*argv));
         }
     }
 
-    if (inputs->count() < 2) {
+    if (inputs->count() < 1) {
         usage(argv0);
         exit(-1);
     }
@@ -197,11 +216,11 @@
         renderer(SkNEW(sk_tools::SimplePdfRenderer));
     SkASSERT(renderer.get());
 
-    parse_commandline(argc, argv, &inputs);
-    SkString outputDir = inputs[inputs.count() - 1];
+    SkString outputDir;
+    parse_commandline(argc, argv, &inputs, &outputDir);
 
     int failures = 0;
-    for (int i = 0; i < inputs.count() - 1; i ++) {
+    for (int i = 0; i < inputs.count(); i ++) {
         failures += process_input(inputs[i], outputDir, *renderer);
     }
 
@@ -209,6 +228,7 @@
         SkDebugf("Failed to render %i PDFs.\n", failures);
         return 1;
     }
+    return 0;
 }
 
 #if !defined SK_BUILD_FOR_IOS
@@ -216,4 +236,3 @@
     return tool_main(argc, (char**) argv);
 }
 #endif
-
diff --git a/tools/render_pictures_main.cpp b/tools/render_pictures_main.cpp
index fce4055..a459bf2 100644
--- a/tools/render_pictures_main.cpp
+++ b/tools/render_pictures_main.cpp
@@ -32,7 +32,7 @@
 "     [--pipe]\n"
 "     [--bbh bbhType]\n"
 "     [--multi count]\n"
-"     [--validate]\n"
+"     [--validate [--maxComponentDiff n]]\n"
 "     [--writeWholeImage]\n"
 "     [--clone n]\n"
 "     [--viewport width height][--scale sf]\n"
@@ -86,6 +86,8 @@
     SkDebugf(
 "     --validate: Verify that the rendered image contains the same pixels as "
 "the picture rendered in simple mode.\n"
+"     --maxComponentDiff: maximum diff on a component. Default is 256, "
+"which means we report but we do not generate an error.\n"
 "     --writeWholeImage: In tile mode, write the entire rendered image to a "
 "file, instead of an image for each tile.\n");
     SkDebugf(
@@ -166,19 +168,28 @@
         SkDELETE(outputPath);
     }
 
-    renderer.resetState();
-
     renderer.end();
 
     SkDELETE(picture);
     return success;
 }
 
+static inline int getByte(uint32_t value, int index) {
+    SkASSERT(0 <= index && index < 4);
+    return (value >> (index * 8)) & 0xFF;
+}
+
+static int MaxByteDiff(uint32_t v1, uint32_t v2) {
+    return SkMax32(SkMax32(abs(getByte(v1, 0) - getByte(v2, 0)), abs(getByte(v1, 1) - getByte(v2, 1))),
+                   SkMax32(abs(getByte(v1, 2) - getByte(v2, 2)), abs(getByte(v1, 3) - getByte(v2, 3))));
+}
+
 static bool render_picture(const SkString& inputPath, const SkString* outputDir,
                            sk_tools::PictureRenderer& renderer,
-                           bool validate,
+                           bool validate, int maxComponentDiff,
                            bool writeWholeImage,
                            int clones) {
+    int diffs[256] = {0};
     SkBitmap* bitmap = NULL;
     bool success = render_picture(inputPath,
         writeWholeImage ? NULL : outputDir,
@@ -218,13 +229,19 @@
             SkDELETE(referenceBitmap);
             return false;
         }
-        
+
         for (int y = 0; success && y < bitmap->height(); y++) {
             for (int x = 0; success && x < bitmap->width(); x++) {
-                if (*referenceBitmap->getAddr32(x, y) != *bitmap->getAddr32(x, y)) {
-                    SkDebugf("Expected pixel at (%i %i): 0x%x, actual 0x%x\n",
-                             x, y,
-                             referenceBitmap->getAddr32(x, y),
+                int diff = MaxByteDiff(*referenceBitmap->getAddr32(x, y),
+                                       *bitmap->getAddr32(x, y));
+                SkASSERT(diff >= 0 && diff <= 255);
+                diffs[diff]++;
+
+                if (diff > maxComponentDiff) {
+                    SkDebugf("Expected pixel at (%i %i) exceedds maximum "
+                                 "component diff of %i: 0x%x, actual 0x%x\n",
+                             x, y, maxComponentDiff,
+                             *referenceBitmap->getAddr32(x, y),
                              *bitmap->getAddr32(x, y));
                     SkDELETE(bitmap);
                     SkDELETE(referenceBitmap);
@@ -233,6 +250,12 @@
             }
         }
         SkDELETE(referenceBitmap);
+
+        for (int i = 1; i <= 255; ++i) {
+            if(diffs[i] > 0) {
+                SkDebugf("Number of pixels with max diff of %i is %i\n", i, diffs[i]);
+            }
+        }
     }
 
     if (writeWholeImage) {
@@ -258,7 +281,8 @@
 
 static int process_input(const SkString& input, const SkString* outputDir,
                          sk_tools::PictureRenderer& renderer,
-                         bool validate, bool writeWholeImage, int clones) {
+                         bool validate, int maxComponentDiff,
+                         bool writeWholeImage, int clones) {
     SkOSFile::Iter iter(input.c_str(), "skp");
     SkString inputFilename;
     int failures = 0;
@@ -268,14 +292,16 @@
             SkString inputPath;
             sk_tools::make_filepath(&inputPath, input, inputFilename);
             if (!render_picture(inputPath, outputDir, renderer,
-                                validate, writeWholeImage, clones)) {
+                                validate, maxComponentDiff,
+                                writeWholeImage, clones)) {
                 ++failures;
             }
         } while(iter.next(&inputFilename));
     } else if (SkStrEndsWith(input.c_str(), ".skp")) {
         SkString inputPath(input);
         if (!render_picture(inputPath, outputDir, renderer,
-                            validate, writeWholeImage, clones)) {
+                            validate, maxComponentDiff,
+                            writeWholeImage, clones)) {
             ++failures;
         }
     } else {
@@ -288,7 +314,8 @@
 
 static void parse_commandline(int argc, char* const argv[], SkTArray<SkString>* inputs,
                               sk_tools::PictureRenderer*& renderer, SkString*& outputDir,
-                              bool* validate, bool* writeWholeImage,
+                              bool* validate, int* maxComponentDiff,
+                              bool* writeWholeImage,
                               int* clones){
     const char* argv0 = argv[0];
     char* const* stop = argv + argc;
@@ -312,6 +339,7 @@
     sk_tools::PictureRenderer::BBoxHierarchyType bbhType =
         sk_tools::PictureRenderer::kNone_BBoxHierarchyType;
     *validate = false;
+    *maxComponentDiff = 256;
     *writeWholeImage = false;
     *clones = 0;
     SkISize viewport;
@@ -425,7 +453,7 @@
                 usage(argv0);
                 exit(-1);
             }
-            scaleFactor = atof(*argv);
+            scaleFactor = SkDoubleToScalar(atof(*argv));
         } else if (0 == strcmp(*argv, "--tiles")) {
             ++argv;
             if (argv >= stop) {
@@ -511,6 +539,25 @@
             outputDir = SkNEW_ARGS(SkString, (*argv));
         } else if (0 == strcmp(*argv, "--validate")) {
             *validate = true;
+        } else if (0 == strcmp(*argv, "--maxComponentDiff")) {
+            if (!*validate) {
+                SkDebugf("--maxComponentDiff must be used only with --validate\n");
+                usage(argv0);
+                exit(-1);
+            }
+            ++argv;
+            if (argv >= stop) {
+                SkDebugf("Missing arg for --maxComponentDiff\n");
+                usage(argv0);
+                exit(-1);
+            }
+            *maxComponentDiff = atoi(*argv);
+            if (*maxComponentDiff < 0 || *maxComponentDiff > 256) {
+                SkSafeUnref(renderer);
+                SkDebugf("maxComponentDiff: 0 - 256.\n");
+                usage(argv0);
+                exit(-1);
+            }
         } else if (0 == strcmp(*argv, "--writeWholeImage")) {
             *writeWholeImage = true;
         } else {
@@ -676,16 +723,18 @@
     sk_tools::PictureRenderer* renderer = NULL;
     SkString* outputDir = NULL;
     bool validate = false;
+    int maxComponentDiff = 256;
     bool writeWholeImage = false;
     int clones = 0;
     parse_commandline(argc, argv, &inputs, renderer, outputDir,
-                      &validate, &writeWholeImage, &clones);
+                      &validate, &maxComponentDiff, &writeWholeImage, &clones);
     SkASSERT(renderer);
 
     int failures = 0;
     for (int i = 0; i < inputs.count(); i ++) {
         failures += process_input(inputs[i], outputDir, *renderer,
-                                  validate, writeWholeImage, clones);
+                                  validate, maxComponentDiff,
+                                  writeWholeImage, clones);
     }
     if (failures != 0) {
         SkDebugf("Failed to render %i pictures.\n", failures);
diff --git a/tools/sanitize_source_files.py b/tools/sanitize_source_files.py
index d6e3ec6..c7edaa0 100755
--- a/tools/sanitize_source_files.py
+++ b/tools/sanitize_source_files.py
@@ -23,7 +23,7 @@
     directory: string - The directory which will be recursively traversed to
         find source files to apply modifiers to.
     file_modifiers: list - file-modification methods which should be applied to
-        the complete file content (Eg: EOFNewlineAdder).
+        the complete file content (Eg: EOFOneAndOnlyOneNewlineAdder).
     line_modifiers: list - line-modification methods which should be applied to
         lines in a file (Eg: TabReplacer).
   """
@@ -114,11 +114,12 @@
   return file_content
 
 
-def EOFNewlineAdder(file_content, file_path):
-  """Adds a LF at the end of the file if it does not have one."""
-  if file_content and file_content[-1] != '\n':
+def EOFOneAndOnlyOneNewlineAdder(file_content, file_path):
+  """Adds one and only one LF at the end of the file."""
+  if file_content and (file_content[-1] != '\n' or file_content[-2:-1] == '\n'):
+    file_content = file_content.rstrip()
     file_content += '\n'
-    print 'Added newline to %s' % file_path
+    print 'Added exactly one newline to %s' % file_path
   return file_content
 
 
@@ -140,7 +141,7 @@
       os.getcwd(),
       file_modifiers=[
           CopywriteChecker,
-          EOFNewlineAdder,
+          EOFOneAndOnlyOneNewlineAdder,
           SvnEOLChecker,
       ],
       line_modifiers=[
@@ -149,4 +150,3 @@
           TrailingWhitespaceRemover,
       ],
   ))
-
diff --git a/tools/skdiff.cpp b/tools/skdiff.cpp
index a1783a4..ae6d72c 100644
--- a/tools/skdiff.cpp
+++ b/tools/skdiff.cpp
@@ -166,6 +166,7 @@
     SkAutoLockPixels alpDiff(dr->fDifference.fBitmap);
     SkAutoLockPixels alpWhite(dr->fWhite.fBitmap);
     int mismatchedPixels = 0;
+    int totalMismatchA = 0;
     int totalMismatchR = 0;
     int totalMismatchG = 0;
     int totalMismatchB = 0;
@@ -177,17 +178,21 @@
         for (int x = 0; x < w; x++) {
             SkPMColor c0 = *dr->fBase.fBitmap.getAddr32(x, y);
             SkPMColor c1 = *dr->fComparison.fBitmap.getAddr32(x, y);
-            SkPMColor trueDifference = compute_diff_pmcolor(c0, c1);
             SkPMColor outputDifference = diffFunction(c0, c1);
-            uint32_t thisR = SkGetPackedR32(trueDifference);
-            uint32_t thisG = SkGetPackedG32(trueDifference);
-            uint32_t thisB = SkGetPackedB32(trueDifference);
+            uint32_t thisA = SkAbs32(SkGetPackedA32(c0) - SkGetPackedA32(c1));
+            uint32_t thisR = SkAbs32(SkGetPackedR32(c0) - SkGetPackedR32(c1));
+            uint32_t thisG = SkAbs32(SkGetPackedG32(c0) - SkGetPackedG32(c1));
+            uint32_t thisB = SkAbs32(SkGetPackedB32(c0) - SkGetPackedB32(c1));
+            totalMismatchA += thisA;
             totalMismatchR += thisR;
             totalMismatchG += thisG;
             totalMismatchB += thisB;
             // In HSV, value is defined as max RGB component.
             int value = MAX3(thisR, thisG, thisB);
             dr->fWeightedFraction += ((float) value) / 255;
+            if (thisA > dr->fMaxMismatchA) {
+                dr->fMaxMismatchA = thisA;
+            }
             if (thisR > dr->fMaxMismatchR) {
                 dr->fMaxMismatchR = thisR;
             }
@@ -215,6 +220,8 @@
     int pixelCount = w * h;
     dr->fFractionDifference = ((float) mismatchedPixels) / pixelCount;
     dr->fWeightedFraction /= pixelCount;
+    dr->fTotalMismatchA = totalMismatchA;
+    dr->fAverageMismatchA = ((float) totalMismatchA) / pixelCount;
     dr->fAverageMismatchR = ((float) totalMismatchR) / pixelCount;
     dr->fAverageMismatchG = ((float) totalMismatchG) / pixelCount;
     dr->fAverageMismatchB = ((float) totalMismatchB) / pixelCount;
diff --git a/tools/skdiff.h b/tools/skdiff.h
index b9e69ce..6abaf6c 100644
--- a/tools/skdiff.h
+++ b/tools/skdiff.h
@@ -115,9 +115,12 @@
         , fWhite()
         , fFractionDifference(0)
         , fWeightedFraction(0)
+        , fAverageMismatchA(0)
         , fAverageMismatchR(0)
         , fAverageMismatchG(0)
         , fAverageMismatchB(0)
+        , fTotalMismatchA(0)
+        , fMaxMismatchA(0)
         , fMaxMismatchR(0)
         , fMaxMismatchG(0)
         , fMaxMismatchB(0)
@@ -135,10 +138,14 @@
     float fFractionDifference;
     float fWeightedFraction;
 
+    float fAverageMismatchA;
     float fAverageMismatchR;
     float fAverageMismatchG;
     float fAverageMismatchB;
 
+    uint32_t fTotalMismatchA;
+
+    uint32_t fMaxMismatchA;
     uint32_t fMaxMismatchR;
     uint32_t fMaxMismatchG;
     uint32_t fMaxMismatchB;
diff --git a/tools/skdiff_html.cpp b/tools/skdiff_html.cpp
index 85d8777..6f3c3b0 100644
--- a/tools/skdiff_html.cpp
+++ b/tools/skdiff_html.cpp
@@ -124,10 +124,23 @@
         if (diff.fFractionDifference < 0.01) {
             print_pixel_count(stream, diff);
         }
+        stream->writeText("<br>");
+        if (SkScalarRoundToInt(diff.fAverageMismatchA) > 0) {
+          stream->writeText("<br>Average alpha channel mismatch ");
+          stream->writeDecAsText(SkScalarRoundToInt(diff.fAverageMismatchA));
+        }
+
+        stream->writeText("<br>Max alpha channel mismatch ");
+        stream->writeDecAsText(SkScalarRoundToInt(diff.fMaxMismatchA));
+
+        stream->writeText("<br>Total alpha channel mismatch ");
+        stream->writeDecAsText(static_cast<int>(diff.fTotalMismatchA));
+
+        stream->writeText("<br>");
         stream->writeText("<br>Average color mismatch ");
-        stream->writeDecAsText(static_cast<int>(MAX3(diff.fAverageMismatchR,
-                                                     diff.fAverageMismatchG,
-                                                     diff.fAverageMismatchB)));
+        stream->writeDecAsText(SkScalarRoundToInt(MAX3(diff.fAverageMismatchR,
+                                                       diff.fAverageMismatchG,
+                                                       diff.fAverageMismatchB)));
         stream->writeText("<br>Max color mismatch ");
         stream->writeDecAsText(MAX3(diff.fMaxMismatchR,
                                     diff.fMaxMismatchG,
diff --git a/tools/skimage_main.cpp b/tools/skimage_main.cpp
index 83f115c..53cc6b3 100644
--- a/tools/skimage_main.cpp
+++ b/tools/skimage_main.cpp
@@ -108,4 +108,3 @@
     return tool_main(argc, (char**) argv);
 }
 #endif
-
diff --git a/tools/submit_try b/tools/submit_try
new file mode 100755
index 0000000..0a4cf2d
--- /dev/null
+++ b/tools/submit_try
@@ -0,0 +1,266 @@
+#!/usr/bin/python
+
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+"""
+submit_try: Submit a try request.
+
+This is a thin wrapper around the try request utilities in depot_tools which
+adds some validation and supports both git and svn.
+"""
+
+
+import httplib
+import json
+import os
+import subprocess
+import sys
+
+
+# Alias which can be used to run a try on every builder.
+ALL_BUILDERS = 'all'
+
+# Contact information for the build master.
+# TODO(borenet): Share this information from a single location. Filed bug:
+# http://code.google.com/p/skia/issues/detail?id=1081
+SKIA_BUILD_MASTER_HOST = '70.32.156.51'
+SKIA_BUILD_MASTER_PORT = '10117'
+
+# All try builders have this suffix.
+TRYBOT_SUFFIX = '_Trybot'
+
+# Location of the codereview.settings file in the Skia repo.
+SKIA_URL = 'skia.googlecode.com'
+CODEREVIEW_SETTINGS = '/svn/codereview.settings'
+
+# String for matching the svn url of the try server inside codereview.settings.
+TRYSERVER_SVN_URL = 'TRYSERVER_SVN_URL: '
+
+# Strings used for matching svn config properties.
+URL_STR = 'URL: '
+REPO_ROOT_STR = 'Repository Root: '
+
+
+def FindDepotTools():
+  """ Find depot_tools on the local machine and return its location. """
+  which_cmd = 'where' if os.name == 'nt' else 'which'
+  cmd = [which_cmd, 'gcl']
+  proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+  if proc.wait() != 0:
+    raise Exception('Couldn\'t find depot_tools in PATH!')
+  gcl = proc.communicate()[0].split('\n')[0].rstrip()
+  depot_tools_dir = os.path.dirname(gcl)
+  return depot_tools_dir
+
+
+def GetCheckoutRoot(is_svn=True):
+  """ Determine where the local checkout is rooted.
+
+  is_svn: boolean; whether we're in an SVN checkout. If False, assume we're in
+      a git checkout.
+  """
+  if is_svn:
+    svn_cmd = 'svn.bat' if os.name == 'nt' else 'svn'
+    cmd = [svn_cmd, 'info']
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                            stderr=subprocess.STDOUT)
+    if proc.wait() != 0:
+      raise Exception('Couldn\'t find checkout root!')
+    output = proc.communicate()[0].split('\n')
+    url = None
+    repo_root = None
+    for line in output:
+      if line.startswith(REPO_ROOT_STR):
+        repo_root = line[len(REPO_ROOT_STR):].rstrip()
+      elif line.startswith(URL_STR):
+        url = line[len(URL_STR):].rstrip()
+    if not url or not repo_root:
+      raise Exception('Couldn\'t find checkout root!')
+    if url == repo_root:
+      return 'svn'
+    return url[len(repo_root)+1:]
+  else:
+    cmd = ['git', 'rev-parse', '--show-toplevel']
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                            stderr=subprocess.STDOUT)
+    if proc.wait() != 0:
+      raise Exception('Couldn\'t find checkout root!')
+    return os.path.basename(proc.communicate()[0])
+
+
+def GetTryRepo():
+  """ Determine the TRYSERVER_SVN_URL from the codereview.settings file in the
+  Skia repo. """
+  connection = httplib.HTTPConnection(SKIA_URL)
+  connection.request('GET', CODEREVIEW_SETTINGS)
+  content = connection.getresponse().read()
+  for line in content.split('\n'):
+    if line.startswith(TRYSERVER_SVN_URL):
+      return line[len(TRYSERVER_SVN_URL):].rstrip()
+  raise Exception('Couldn\'t determine the TRYSERVER_SVN_URL. Make sure it is '
+                  'defined in the %s file.' % CODEREVIEW_SETTINGS)
+
+
+def RetrieveTrybotList():
+  """ Retrieve the list of known trybots from the build master, stripping
+  TRYBOT_SUFFIX from the name. """
+  trybots = []
+  connection = httplib.HTTPConnection(SKIA_BUILD_MASTER_HOST,  
+                                      SKIA_BUILD_MASTER_PORT)  
+  connection.request('GET', '/json/builders')  
+  response = connection.getresponse()  
+  builders = json.load(response)
+
+  for builder in builders:
+    if builder.endswith(TRYBOT_SUFFIX):
+      trybots.append(builder[:-len(TRYBOT_SUFFIX)])
+  return trybots
+
+
+def ValidateArgs(argv, trybots, is_svn=True):
+  """ Parse and validate command-line arguments. If the arguments are valid,
+  returns a tuple of (<changelist name>, <list of trybots>).
+
+  trybots: A list of the known try builders.
+  """
+
+  class CollectedArgs(object):
+    def __init__(self, bots, changelist, revision):
+      self._bots = bots
+      self._changelist = changelist
+      self._revision = revision
+
+    @property
+    def bots(self):
+      for bot in self._bots:
+        yield bot
+
+    @property
+    def changelist(self):
+      return self._changelist
+
+    @property
+    def revision(self):
+      return self._revision
+
+  usage = (
+"""submit_try: Submit a try request.
+submit_try %s--bot <buildername> [<buildername> ...]
+
+--bot               Builder on which to run the try. Required.
+-h, --help          Show this message.
+-r <revision#>      Revision from which to run the try.
+-l, --list_bots     List the available try builders and exit.
+""" % ('<changelist> ' if is_svn else ''))
+
+  def Error(msg=None):
+    if msg:
+      print msg
+    print usage
+    sys.exit(1)
+
+  using_bots = None
+  changelist = None
+  revision = None
+
+  while argv:
+    arg = argv.pop(0)
+    if arg == '-h' or arg == '--help':
+      Error()
+    elif arg == '-l' or arg == '--list_bots':
+      print 'submit_try: Available builders:\n  %s' % '\n  '.join(trybots)
+      sys.exit(0)
+    elif arg == '--bot':
+      if using_bots:
+        Error('--bot specified multiple times.')
+      if len(argv) < 1:
+        Error('You must specify a builder with "--bot".')
+      using_bots = []
+      while argv and not argv[0].startswith('-'):
+        bot = argv.pop(0)
+        if bot == ALL_BUILDERS:
+          if using_bots:
+            Error('Cannot specify "all" with additional builder names.')
+          using_bots = trybots
+          break
+        else:
+          if not bot in trybots:
+            Error('Unrecognized builder: %s' % bot)
+          using_bots.append(bot)
+    elif arg == '-r':
+      if len(argv) < 1:
+        Error('You must specify a revision with "-r".')
+      revision = argv.pop(0)
+    else:
+      if changelist or not is_svn:
+        Error('Unknown argument: %s' % arg)
+      changelist = arg
+  if is_svn and not changelist:
+    Error('You must specify a changelist name.')
+  if not using_bots:
+    Error('You must specify one or more builders using --bot.')
+  return CollectedArgs(bots=using_bots, changelist=changelist,
+                       revision=revision)
+
+
+def SubmitTryRequest(args, is_svn=True):
+  """ Submits a try request for the given changelist on the given list of
+  trybots.
+
+  args: Object whose properties are derived from command-line arguments. If
+      is_svn is True, it should contain:
+      - changelist: string; the name of the changelist to try.
+      - bot: list of strings; the names of the try builders to run.
+      - revision: optional, int; the revision number from which to run the try.
+      If is_svn is False, it should contain:
+      - bot: list of strings; the names of the try builders to run.
+      - revision: optional, int; the revision number from which to run the try. 
+  is_svn: boolean; are we in an SVN repo?
+  """
+  botlist = ','.join(['%s%s' % (bot, TRYBOT_SUFFIX) for bot in args.bots])
+  if is_svn:
+    gcl_cmd = 'gcl.bat' if os.name == 'nt' else 'gcl'
+    try_args = [gcl_cmd, 'try', args.changelist,
+                '--root', GetCheckoutRoot(is_svn),
+                '--bot', botlist]
+    if args.revision:
+      try_args.extend(['-r', args.revision])
+    print ' '.join(try_args)
+    proc = subprocess.Popen(try_args, stdout=subprocess.PIPE,
+                            stderr=subprocess.STDOUT)
+    if proc.wait() != 0:
+      raise Exception('Failed to submit try request: %s' % (
+          proc.communicate()[0]))
+    print proc.communicate()[0]
+  else:
+    # First, find depot_tools. This is needed to import trychange.
+    sys.path.append(FindDepotTools())
+    import trychange
+    try_args = ['--use_svn',
+                '--svn_repo', GetTryRepo(),
+                '--root', GetCheckoutRoot(is_svn),
+                '--bot', botlist]
+    if args.revision:
+      try_args.extend(['-r', args.revision])
+    trychange.TryChange(try_args, None, False)
+
+
+def main():
+  # Retrieve the list of active try builders from the build master.
+  trybots = RetrieveTrybotList()
+
+  # Determine if we're in an SVN checkout.
+  is_svn = os.path.isdir('.svn')
+
+  # Parse and validate the command-line arguments.
+  args = ValidateArgs(sys.argv[1:], trybots=trybots, is_svn=is_svn)
+
+  # Submit the try request.
+  SubmitTryRequest(args, is_svn=is_svn)
+
+
+if __name__ == '__main__':
+  sys.exit(main())
\ No newline at end of file
diff --git a/tools/submit_try.bat b/tools/submit_try.bat
new file mode 100644
index 0000000..d1d5392
--- /dev/null
+++ b/tools/submit_try.bat
@@ -0,0 +1 @@
+python tools\submit_try %*
\ No newline at end of file
diff --git a/tools/tests/bench_pictures_cfg_test.py b/tools/tests/bench_pictures_cfg_test.py
index 77ad553..9b0e0ac 100644
--- a/tools/tests/bench_pictures_cfg_test.py
+++ b/tools/tests/bench_pictures_cfg_test.py
@@ -39,7 +39,7 @@
         if type(value).__name__ == 'list':
           for item in value:
             ThrowIfNotAString(item)
-        else:
+        elif not value is True:
           ThrowIfNotAString(value)
 
 if __name__ == '__main__':
diff --git a/tools/tests/rebaseline.sh b/tools/tests/rebaseline.sh
new file mode 100755
index 0000000..eee8db8
--- /dev/null
+++ b/tools/tests/rebaseline.sh
@@ -0,0 +1,73 @@
+#!/bin/bash
+
+# Rebaseline the skdiff/*/output-expected/ subdirectories used by the skdiff
+# self-tests.
+# Use with caution: are you sure the new results are actually correct?
+#
+# YOU MUST RE-RUN THIS UNTIL THE SELF-TESTS SUCCEED!
+#
+# TODO: currently, this must be run on Linux to generate baselines that match
+# the ones on the housekeeper bot (which runs on Linux... see
+# http://70.32.156.51:10117/builders/Skia_PerCommit_House_Keeping/builds/1417/steps/RunGmSelfTests/logs/stdio )
+# See https://code.google.com/p/skia/issues/detail?id=677
+# ('make tools/tests/run.sh work cross-platform')
+
+function replace_expected_with_actual {
+  # Delete all the expected output files
+  EXPECTED_FILES=$(find skdiff/*/output-expected -type f | grep -v /\.svn/)
+  for EXPECTED_FILE in $EXPECTED_FILES; do
+    rm $EXPECTED_FILE
+  done
+
+  # Copy all the actual output files into the "expected" directories,
+  # creating new subdirs as we go.
+  ACTUAL_FILES=$(find skdiff/*/output-actual -type f | grep -v /\.svn/)
+  for ACTUAL_FILE in $ACTUAL_FILES; do
+    EXPECTED_FILE=${ACTUAL_FILE//actual/expected}
+    mkdir -p $(dirname $EXPECTED_FILE)
+    cp $ACTUAL_FILE $EXPECTED_FILE
+  done
+}
+
+function svn_add_new_files {
+  # Delete all the "actual" directories, so we can svn-add any new "expected"
+  # directories without adding the "actual" ones.
+  rm -rf skdiff/*/output-actual
+  FILES=$(svn stat skdiff/* | grep ^\? | awk '{print $2}')
+  for FILE in $FILES; do
+    svn add $FILE
+  done
+  FILES=$(svn stat skdiff/*/output-expected | grep ^\? | awk '{print $2}')
+  for FILE in $FILES; do
+    svn add $FILE
+  done
+}
+
+function svn_delete_old_files {
+  FILES=$(svn stat skdiff/*/output-expected | grep ^\! | awk '{print $2}')
+  for FILE in $FILES; do
+    svn rm $FILE
+  done
+  FILES=$(svn stat skdiff/* | grep ^\! | awk '{print $2}')
+  for FILE in $FILES; do
+    svn rm $FILE
+  done
+}
+
+
+# cd into the gm self-test dir
+cd $(dirname $0)
+
+./run.sh
+SELFTEST_RESULT=$?
+echo
+if [ "$SELFTEST_RESULT" != "0" ]; then
+  replace_expected_with_actual
+  echo "Self-tests still failing, you should probably run this again..."
+else
+  svn_add_new_files
+  svn_delete_old_files
+  echo "Self-tests succeeded this time, you should be done!"
+fi
+exit $SELFTEST_RESULT
+
diff --git a/tools/tests/run.sh b/tools/tests/run.sh
index 1acb124..7731805 100755
--- a/tools/tests/run.sh
+++ b/tools/tests/run.sh
@@ -1,7 +1,17 @@
 #!/bin/bash
 
 # Tests for our tools.
+#
 # TODO: for now, it only tests skdiff
+#
+# TODO: currently, this only passes on Linux (which is the platform that
+# the housekeeper bot runs on, e.g.
+# http://70.32.156.51:10117/builders/Skia_PerCommit_House_Keeping/builds/1415/steps/RunToolSelfTests/logs/stdio )
+# See https://code.google.com/p/skia/issues/detail?id=677
+# ('make tools/tests/run.sh work cross-platform')
+# Ideally, these tests should pass on all development platforms...
+# otherwise, how can developers be expected to test them before committing a
+# change?
 
 # cd into .../trunk so all the paths will work
 cd $(dirname $0)/../..
diff --git a/tools/tests/skdiff/test1/output-expected/index.html b/tools/tests/skdiff/test1/output-expected/index.html
index a3e192e..4bf8eee 100644
--- a/tools/tests/skdiff/test1/output-expected/index.html
+++ b/tools/tests/skdiff/test1/output-expected/index.html
@@ -39,10 +39,10 @@
 <td><input type="checkbox" name="different-bits/very-different-sizes.png" checked="yes"></td><td><b>different-bits/very-different-sizes.png</b><br>Image sizes differ</td><td>N/A</td><td>N/A</td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-sizes.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-sizes.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-sizes.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-sizes.png" height="128px"></a></td></tr>
 <tr>
 <td><input type="checkbox" name="different-bits/very-different-pixels-same-size.png" checked="yes"></td><td><b>different-bits/very-different-pixels-same-size.png</b><br>97.9926% of pixels differ
-  (42.8911% weighted)<br>Average color mismatch 89<br>Max color mismatch 239</td><td><a href="different-bits_very-different-pixels-same-size-white.png"><img src="different-bits_very-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_very-different-pixels-same-size-diff.png"><img src="different-bits_very-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td></tr>
+  (42.8911% weighted)<br><br>Max alpha channel mismatch 0<br>Total alpha channel mismatch 0<br><br>Average color mismatch 89<br>Max color mismatch 239</td><td><a href="different-bits_very-different-pixels-same-size-white.png"><img src="different-bits_very-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_very-different-pixels-same-size-diff.png"><img src="different-bits_very-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/very-different-pixels-same-size.png" height="240px"></a></td></tr>
 <tr>
 <td><input type="checkbox" name="different-bits/slightly-different-pixels-same-size.png" checked="yes"></td><td><b>different-bits/slightly-different-pixels-same-size.png</b><br>0.6630% of pixels differ
-  (0.1904% weighted)<br>(2164 pixels)<br>Average color mismatch 0<br>Max color mismatch 213</td><td><a href="different-bits_slightly-different-pixels-same-size-white.png"><img src="different-bits_slightly-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_slightly-different-pixels-same-size-diff.png"><img src="different-bits_slightly-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td></tr>
+  (0.1904% weighted)<br>(2164 pixels)<br><br>Max alpha channel mismatch 0<br>Total alpha channel mismatch 0<br><br>Average color mismatch 0<br>Max color mismatch 213</td><td><a href="different-bits_slightly-different-pixels-same-size-white.png"><img src="different-bits_slightly-different-pixels-same-size-white.png" height="240px"></a></td><td><a href="different-bits_slightly-different-pixels-same-size-diff.png"><img src="different-bits_slightly-different-pixels-same-size-diff.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/baseDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td><td><a href="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png"><img src="../../../../../tools/tests/skdiff/comparisonDir/different-bits/slightly-different-pixels-same-size.png" height="240px"></a></td></tr>
 </table>
 <input type="button" onclick="generateCheckedList()" value="Create Rebaseline List">
 <div id="checkedList"></div>