Add an option on SkImageDecoder to skip writing 0s.

Only implemented for PNG.

Add a getter and setter, and sets the default to false in the
constructor. Also copies the setting in copyFieldsToOther.

Fix an indpendent bug where fDitherImage was not being copied in
copyFieldsToOther.

In SkScaledBitmapSampler::begin, consolidate the settings passed in
by passing a const reference to the decoder. The decoder can be
referenced for its settings of dither, unpremultiplied, and now
skipping writing zeroes. Update callers to use the new API. In png
decoder, rather than passing around a pointer to an initial
read of getDitherImage, and potentially changing it, look at the
field on the decoder itself, and modify it directly. This is a
change in behavior - now if that same decoder is used to decode
a different image, the dither setting has changed. I think this is
okay because A) the typical use case is to use a new decoder for
each decode, B) we do not make any promises that a decode does not
change the decoder and C) it makes the code in SkScaledBitmapSampler
much cleaner.

In SkScaledBitmapScampler, add new row procs for skipping zeroes. Now
that choosing the row proc has five dimensions (src config, dst config,
dither, skip writing zeroes, unpremultiplied), use a new method: each
src/dst combination has a function for choosing the right proc depending
on the decoder.

SkScaledBitmapScampler::RowProc is now public for convenience.

Remove Sample_Gray_D8888_Unpremul, which is effectively no different
from Sample_Gray_D8888.

In cases where unpremultiplied was trivial, such as 565 and when
sampling from gray, decoding may now succeed.

Add a benchmark (currently disabled) for comparing the speed of skipping
writing zeroes versus not skipping. For this particular image, which is
mostly transparent pixels, normal decoding took about 3.6 milliseconds,
while skipping zeroes in the decode took only about 2.5 milliseconds
(this is on a Nexus 4). Presumably it would be slower on an image
with a small amount of transparency, but there will be no slowdown
for an image which reports that it has no transparency.

In SkImageRef_ashmem, always skip writing zeroes, since ashmem
memory is guaranteed to be initialized to 0.

Add a flag to skip writing zeroes in skimage.

Add a regression test for choosing the rowproc to ensure I did not
change any behavior accidentally.

BUG=skia:1661
R=reed@google.com

Review URL: https://codereview.chromium.org/24269006

git-svn-id: http://skia.googlecode.com/svn/trunk@11558 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/bench/SkipZeroesBench.cpp b/bench/SkipZeroesBench.cpp
index b56838c..fc2f060 100644
--- a/bench/SkipZeroesBench.cpp
+++ b/bench/SkipZeroesBench.cpp
@@ -61,15 +61,7 @@
                 fStream.setData(skdata.get());
                 fDecoder.reset(SkImageDecoder::Factory(&fStream));
                 if (fDecoder.get()) {
-                    // Disabling until the feature is checked in.
-                    // See https://codereview.chromium.org/24269006/
-                    //fDecoder->setSkipWritingZeroes(fSkipZeroes);
-                    if (fSkipZeroes) {
-                        // This printf will never happen since this bench is
-                        // only currently created with false. Remove once
-                        // the above code is uncommented.
-                        SkDebugf("Should be skipping zeroes...\n");
-                    }
+                    fDecoder->setSkipWritingZeroes(fSkipZeroes);
                 } else {
                     fValid = false;
                 }
@@ -117,5 +109,5 @@
 };
 
 // Enable the true version once the feature is checked in.
-//DEF_BENCH( return SkNEW_ARGS(SkipZeroesBench, ("arrow.png", true)));
+DEF_BENCH( return SkNEW_ARGS(SkipZeroesBench, ("arrow.png", true)));
 DEF_BENCH( return SkNEW_ARGS(SkipZeroesBench, ("arrow.png", false)));
diff --git a/include/core/SkImageDecoder.h b/include/core/SkImageDecoder.h
index bff7495..eac2157 100644
--- a/include/core/SkImageDecoder.h
+++ b/include/core/SkImageDecoder.h
@@ -60,6 +60,23 @@
     */
     const char* getFormatName() const;
 
+    /** Whether the decoder should skip writing zeroes to output if possible.
+    */
+    bool getSkipWritingZeroes() const { return fSkipWritingZeroes; }
+
+    /** Set to true if the decoder should skip writing any zeroes when
+        creating the output image.
+        This is a hint that may not be respected by the decoder.
+        It should only be used if it is known that the memory to write
+        to has already been set to 0; otherwise the resulting image will
+        have garbage.
+        This is ideal for images that contain a lot of completely transparent
+        pixels, but may be a performance hit for an image that has only a
+        few transparent pixels.
+        The default is false.
+    */
+    void setSkipWritingZeroes(bool skip) { fSkipWritingZeroes = skip; }
+
     /** Returns true if the decoder should try to dither the resulting image.
         The default setting is true.
     */
@@ -508,6 +525,7 @@
     PrefConfigTable         fPrefTable;     // use if fUsePrefTable is true
     bool                    fDitherImage;
     bool                    fUsePrefTable;
+    bool                    fSkipWritingZeroes;
     mutable bool            fShouldCancelDecode;
     bool                    fPreferQualityOverSpeed;
     bool                    fRequireUnpremultipliedColors;
diff --git a/src/images/SkImageDecoder.cpp b/src/images/SkImageDecoder.cpp
index 1544edf..15cd1a6 100644
--- a/src/images/SkImageDecoder.cpp
+++ b/src/images/SkImageDecoder.cpp
@@ -40,6 +40,7 @@
     , fDefaultPref(SkBitmap::kNo_Config)
     , fDitherImage(true)
     , fUsePrefTable(false)
+    , fSkipWritingZeroes(false)
     , fPreferQualityOverSpeed(false)
     , fRequireUnpremultipliedColors(false) {
 }
@@ -63,6 +64,8 @@
     } else {
         other->fDefaultPref = fDefaultPref;
     }
+    other->setDitherImage(fDitherImage);
+    other->setSkipWritingZeroes(fSkipWritingZeroes);
     other->setPreferQualityOverSpeed(fPreferQualityOverSpeed);
     other->setRequireUnpremultipliedColors(fRequireUnpremultipliedColors);
 }
diff --git a/src/images/SkImageDecoder_libbmp.cpp b/src/images/SkImageDecoder_libbmp.cpp
index 3eb181f..2283dbf 100644
--- a/src/images/SkImageDecoder_libbmp.cpp
+++ b/src/images/SkImageDecoder_libbmp.cpp
@@ -146,7 +146,7 @@
 
     SkAutoLockPixels alp(*bm);
 
-    if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, getDitherImage())) {
+    if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) {
         return false;
     }
 
diff --git a/src/images/SkImageDecoder_libjpeg.cpp b/src/images/SkImageDecoder_libjpeg.cpp
index 37be2a7..3b3ea88 100644
--- a/src/images/SkImageDecoder_libjpeg.cpp
+++ b/src/images/SkImageDecoder_libjpeg.cpp
@@ -588,7 +588,7 @@
         return return_false(cinfo, *bm, "jpeg colorspace");
     }
 
-    if (!sampler.begin(bm, sc, this->getDitherImage())) {
+    if (!sampler.begin(bm, sc, *this)) {
         return return_false(cinfo, *bm, "sampler.begin");
     }
 
@@ -827,7 +827,7 @@
         return return_false(*cinfo, *bm, "jpeg colorspace");
     }
 
-    if (!sampler.begin(&bitmap, sc, this->getDitherImage())) {
+    if (!sampler.begin(&bitmap, sc, *this)) {
         return return_false(*cinfo, bitmap, "sampler.begin");
     }
 
diff --git a/src/images/SkImageDecoder_libpng.cpp b/src/images/SkImageDecoder_libpng.cpp
index b6aa329..e942f21 100644
--- a/src/images/SkImageDecoder_libpng.cpp
+++ b/src/images/SkImageDecoder_libpng.cpp
@@ -91,7 +91,7 @@
                        SkColorTable **colorTablep);
     bool getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
                          SkBitmap::Config *config, bool *hasAlpha,
-                         bool *doDither, SkPMColor *theTranspColor);
+                         SkPMColor *theTranspColor);
 
     typedef SkImageDecoder INHERITED;
 };
@@ -294,10 +294,9 @@
 
     SkBitmap::Config    config;
     bool                hasAlpha = false;
-    bool                doDither = this->getDitherImage();
     SkPMColor           theTranspColor = 0; // 0 tells us not to try to match
 
-    if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
+    if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
         return false;
     }
 
@@ -377,8 +376,7 @@
             upscale png's palette to a direct model
          */
         SkAutoLockColors ctLock(colorTable);
-        if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors(),
-                           this->getRequireUnpremultipliedColors())) {
+        if (!sampler.begin(decodedBitmap, sc, *this, ctLock.colors())) {
             return false;
         }
         const int height = decodedBitmap->height();
@@ -446,7 +444,6 @@
 bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
                                         SkBitmap::Config* SK_RESTRICT configp,
                                         bool* SK_RESTRICT hasAlphap,
-                                        bool* SK_RESTRICT doDitherp,
                                         SkPMColor* SK_RESTRICT theTranspColorp) {
     png_uint_32 origWidth, origHeight;
     int bitDepth, colorType;
@@ -456,7 +453,7 @@
     // check for sBIT chunk data, in case we should disable dithering because
     // our data is not truely 8bits per component
     png_color_8p sig_bit;
-    if (*doDitherp && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
+    if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
 #if 0
         SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
                  sig_bit->blue, sig_bit->alpha);
@@ -465,7 +462,7 @@
         if (pos_le(sig_bit->red, SK_R16_BITS) &&
             pos_le(sig_bit->green, SK_G16_BITS) &&
             pos_le(sig_bit->blue, SK_B16_BITS)) {
-            *doDitherp = false;
+            this->setDitherImage(false);
         }
     }
 
@@ -724,10 +721,9 @@
 
     SkBitmap::Config    config;
     bool                hasAlpha = false;
-    bool                doDither = this->getDitherImage();
     SkPMColor           theTranspColor = 0; // 0 tells us not to try to match
 
-    if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
+    if (!this->getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &theTranspColor)) {
         return false;
     }
 
@@ -834,8 +830,7 @@
             upscale png's palette to a direct model
          */
         SkAutoLockColors ctLock(colorTable);
-        if (!sampler.begin(&decodedBitmap, sc, doDither, ctLock.colors(),
-                           this->getRequireUnpremultipliedColors())) {
+        if (!sampler.begin(&decodedBitmap, sc, *this, ctLock.colors())) {
             return false;
         }
         const int height = decodedBitmap.height();
diff --git a/src/images/SkImageRef_ashmem.cpp b/src/images/SkImageRef_ashmem.cpp
index 0186bf6..0dba1d1 100644
--- a/src/images/SkImageRef_ashmem.cpp
+++ b/src/images/SkImageRef_ashmem.cpp
@@ -134,6 +134,9 @@
         return this->INHERITED::onDecode(codec, stream, bitmap, config, mode);
     }
 
+    // Ashmem memory is guaranteed to be initialized to 0.
+    codec->setSkipWritingZeroes(true);
+
     AshmemAllocator alloc(&fRec, this->getURI());
 
     codec->setAllocator(&alloc);
diff --git a/src/images/SkScaledBitmapSampler.cpp b/src/images/SkScaledBitmapSampler.cpp
index 021f86b..3bab8de 100644
--- a/src/images/SkScaledBitmapSampler.cpp
+++ b/src/images/SkScaledBitmapSampler.cpp
@@ -25,6 +25,11 @@
     return false;
 }
 
+static SkScaledBitmapSampler::RowProc get_gray_to_8888_proc(const SkImageDecoder& decoder) {
+    // Dither, unpremul, and skipZeroes have no effect
+    return Sample_Gray_D8888;
+}
+
 static bool Sample_RGBx_D8888(void* SK_RESTRICT dstRow,
                               const uint8_t* SK_RESTRICT src,
                               int width, int deltaSrc, int, const SkPMColor[]) {
@@ -36,6 +41,11 @@
     return false;
 }
 
+static SkScaledBitmapSampler::RowProc get_RGBx_to_8888_proc(const SkImageDecoder& decoder) {
+    // Dither, unpremul, and skipZeroes have no effect
+    return Sample_RGBx_D8888;
+}
+
 static bool Sample_RGBA_D8888(void* SK_RESTRICT dstRow,
                               const uint8_t* SK_RESTRICT src,
                               int width, int deltaSrc, int, const SkPMColor[]) {
@@ -50,6 +60,52 @@
     return alphaMask != 0xFF;
 }
 
+static bool Sample_RGBA_D8888_Unpremul(void* SK_RESTRICT dstRow,
+                                       const uint8_t* SK_RESTRICT src,
+                                       int width, int deltaSrc, int,
+                                       const SkPMColor[]) {
+    uint32_t* SK_RESTRICT dst = reinterpret_cast<uint32_t*>(dstRow);
+    unsigned alphaMask = 0xFF;
+    for (int x = 0; x < width; x++) {
+        unsigned alpha = src[3];
+        dst[x] = SkPackARGB32NoCheck(alpha, src[0], src[1], src[2]);
+        src += deltaSrc;
+        alphaMask &= alpha;
+    }
+    return alphaMask != 0xFF;
+}
+
+static bool Sample_RGBA_D8888_SkipZ(void* SK_RESTRICT dstRow,
+                                    const uint8_t* SK_RESTRICT src,
+                                    int width, int deltaSrc, int,
+                                    const SkPMColor[]) {
+    SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+    unsigned alphaMask = 0xFF;
+    for (int x = 0; x < width; x++) {
+        unsigned alpha = src[3];
+        if (0 != alpha) {
+            dst[x] = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+        }
+        src += deltaSrc;
+        alphaMask &= alpha;
+    }
+    return alphaMask != 0xFF;
+}
+
+static SkScaledBitmapSampler::RowProc get_RGBA_to_8888_proc(const SkImageDecoder& decoder) {
+    // Dither has no effect.
+    if (decoder.getRequireUnpremultipliedColors()) {
+        // We could check each component for a zero, at the expense of extra checks.
+        // For now, just return unpremul.
+        return Sample_RGBA_D8888_Unpremul;
+    }
+    // Supply the versions that premultiply the colors
+    if (decoder.getSkipWritingZeroes()) {
+        return Sample_RGBA_D8888_SkipZ;
+    }
+    return Sample_RGBA_D8888;
+}
+
 // 565
 
 static bool Sample_Gray_D565(void* SK_RESTRICT dstRow,
@@ -75,6 +131,14 @@
     return false;
 }
 
+static SkScaledBitmapSampler::RowProc get_gray_to_565_proc(const SkImageDecoder& decoder) {
+    // Unpremul and skip zeroes make no difference
+    if (decoder.getDitherImage()) {
+        return Sample_Gray_D565_D;
+    }
+    return Sample_Gray_D565;
+}
+
 static bool Sample_RGBx_D565(void* SK_RESTRICT dstRow,
                              const uint8_t* SK_RESTRICT src,
                              int width, int deltaSrc, int, const SkPMColor[]) {
@@ -86,6 +150,28 @@
     return false;
 }
 
+static bool Sample_RGBx_D565_D(void* SK_RESTRICT dstRow,
+                               const uint8_t* SK_RESTRICT src,
+                               int width, int deltaSrc, int y,
+                               const SkPMColor[]) {
+    uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+    DITHER_565_SCAN(y);
+    for (int x = 0; x < width; x++) {
+        dst[x] = SkDitherRGBTo565(src[0], src[1], src[2], DITHER_VALUE(x));
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static SkScaledBitmapSampler::RowProc get_RGBx_to_565_proc(const SkImageDecoder& decoder) {
+    // Unpremul and skip zeroes make no difference
+    if (decoder.getDitherImage()) {
+        return Sample_RGBx_D565_D;
+    }
+    return Sample_RGBx_D565;
+}
+
+
 static bool Sample_D565_D565(void* SK_RESTRICT dstRow,
                              const uint8_t* SK_RESTRICT src,
                              int width, int deltaSrc, int, const SkPMColor[]) {
@@ -98,16 +184,9 @@
     return false;
 }
 
-static bool Sample_RGBx_D565_D(void* SK_RESTRICT dstRow,
-                               const uint8_t* SK_RESTRICT src,
-                           int width, int deltaSrc, int y, const SkPMColor[]) {
-    uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
-    DITHER_565_SCAN(y);
-    for (int x = 0; x < width; x++) {
-        dst[x] = SkDitherRGBTo565(src[0], src[1], src[2], DITHER_VALUE(x));
-        src += deltaSrc;
-    }
-    return false;
+static SkScaledBitmapSampler::RowProc get_565_to_565_proc(const SkImageDecoder& decoder) {
+    // Unpremul, dither, and skip zeroes have no effect
+    return Sample_D565_D565;
 }
 
 // 4444
@@ -137,6 +216,14 @@
     return false;
 }
 
+static SkScaledBitmapSampler::RowProc get_gray_to_4444_proc(const SkImageDecoder& decoder) {
+    // Skip zeroes and unpremul make no difference
+    if (decoder.getDitherImage()) {
+        return Sample_Gray_D4444_D;
+    }
+    return Sample_Gray_D4444;
+}
+
 static bool Sample_RGBx_D4444(void* SK_RESTRICT dstRow,
                               const uint8_t* SK_RESTRICT src,
                               int width, int deltaSrc, int, const SkPMColor[]) {
@@ -162,6 +249,14 @@
     return false;
 }
 
+static SkScaledBitmapSampler::RowProc get_RGBx_to_4444_proc(const SkImageDecoder& decoder) {
+    // Skip zeroes and unpremul make no difference
+    if (decoder.getDitherImage()) {
+        return Sample_RGBx_D4444_D;
+    }
+    return Sample_RGBx_D4444;
+}
+
 static bool Sample_RGBA_D4444(void* SK_RESTRICT dstRow,
                               const uint8_t* SK_RESTRICT src,
                               int width, int deltaSrc, int, const SkPMColor[]) {
@@ -178,9 +273,30 @@
     return alphaMask != 0xFF;
 }
 
+static bool Sample_RGBA_D4444_SkipZ(void* SK_RESTRICT dstRow,
+                                    const uint8_t* SK_RESTRICT src,
+                                    int width, int deltaSrc, int,
+                                    const SkPMColor[]) {
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    unsigned alphaMask = 0xFF;
+
+    for (int x = 0; x < width; x++) {
+        unsigned alpha = src[3];
+        if (alpha != 0) {
+            SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+            dst[x] = SkPixel32ToPixel4444(c);
+        }
+        src += deltaSrc;
+        alphaMask &= alpha;
+    }
+    return alphaMask != 0xFF;
+}
+
+
 static bool Sample_RGBA_D4444_D(void* SK_RESTRICT dstRow,
                                 const uint8_t* SK_RESTRICT src,
-                            int width, int deltaSrc, int y, const SkPMColor[]) {
+                                int width, int deltaSrc, int y,
+                                const SkPMColor[]) {
     SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
     unsigned alphaMask = 0xFF;
     DITHER_4444_SCAN(y);
@@ -195,6 +311,44 @@
     return alphaMask != 0xFF;
 }
 
+static bool Sample_RGBA_D4444_D_SkipZ(void* SK_RESTRICT dstRow,
+                                      const uint8_t* SK_RESTRICT src,
+                                      int width, int deltaSrc, int y,
+                                      const SkPMColor[]) {
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    unsigned alphaMask = 0xFF;
+    DITHER_4444_SCAN(y);
+
+    for (int x = 0; x < width; x++) {
+        unsigned alpha = src[3];
+        if (alpha != 0) {
+            SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+            dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x));
+        }
+        src += deltaSrc;
+        alphaMask &= alpha;
+    }
+    return alphaMask != 0xFF;
+}
+
+static SkScaledBitmapSampler::RowProc get_RGBA_to_4444_proc(const SkImageDecoder& decoder) {
+    if (decoder.getRequireUnpremultipliedColors()) {
+        // Unpremultiplied is not supported for 4444
+        return NULL;
+    }
+    const bool dither = decoder.getDitherImage();
+    if (decoder.getSkipWritingZeroes()) {
+        if (dither) {
+            return Sample_RGBA_D4444_D_SkipZ;
+        }
+        return Sample_RGBA_D4444_SkipZ;
+    }
+    if (dither) {
+        return Sample_RGBA_D4444_D;
+    }
+    return Sample_RGBA_D4444;
+}
+
 // Index
 
 #define A32_MASK_IN_PLACE   (SkPMColor)(SK_A32_MASK << SK_A32_SHIFT)
@@ -214,6 +368,36 @@
     return cc != A32_MASK_IN_PLACE;
 }
 
+static bool Sample_Index_D8888_SkipZ(void* SK_RESTRICT dstRow,
+                                     const uint8_t* SK_RESTRICT src,
+                                     int width, int deltaSrc, int,
+                                     const SkPMColor ctable[]) {
+
+    SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+    SkPMColor cc = A32_MASK_IN_PLACE;
+    for (int x = 0; x < width; x++) {
+        SkPMColor c = ctable[*src];
+        cc &= c;
+        if (c != 0) {
+            dst[x] = c;
+        }
+        src += deltaSrc;
+    }
+    return cc != A32_MASK_IN_PLACE;
+}
+
+static SkScaledBitmapSampler::RowProc get_index_to_8888_proc(const SkImageDecoder& decoder) {
+    if (decoder.getRequireUnpremultipliedColors()) {
+        // Unpremultiplied is not supported for an index source.
+        return NULL;
+    }
+    // Dither makes no difference
+    if (decoder.getSkipWritingZeroes()) {
+        return Sample_Index_D8888_SkipZ;
+    }
+    return Sample_Index_D8888;
+}
+
 static bool Sample_Index_D565(void* SK_RESTRICT dstRow,
                                const uint8_t* SK_RESTRICT src,
                        int width, int deltaSrc, int, const SkPMColor ctable[]) {
@@ -242,6 +426,14 @@
     return false;
 }
 
+static SkScaledBitmapSampler::RowProc get_index_to_565_proc(const SkImageDecoder& decoder) {
+    // Unpremultiplied and skip zeroes make no difference
+    if (decoder.getDitherImage()) {
+        return Sample_Index_D565_D;
+    }
+    return Sample_Index_D565;
+}
+
 static bool Sample_Index_D4444(void* SK_RESTRICT dstRow,
                                const uint8_t* SK_RESTRICT src, int width,
                                int deltaSrc, int y, const SkPMColor ctable[]) {
@@ -274,6 +466,60 @@
     return cc != A32_MASK_IN_PLACE;
 }
 
+static bool Sample_Index_D4444_SkipZ(void* SK_RESTRICT dstRow,
+                                     const uint8_t* SK_RESTRICT src, int width,
+                                     int deltaSrc, int y, const SkPMColor ctable[]) {
+
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    SkPMColor cc = A32_MASK_IN_PLACE;
+    for (int x = 0; x < width; x++) {
+        SkPMColor c = ctable[*src];
+        cc &= c;
+        if (c != 0) {
+            dst[x] = SkPixel32ToPixel4444(c);
+        }
+        src += deltaSrc;
+    }
+    return cc != A32_MASK_IN_PLACE;
+}
+
+static bool Sample_Index_D4444_D_SkipZ(void* SK_RESTRICT dstRow,
+                                       const uint8_t* SK_RESTRICT src, int width,
+                                       int deltaSrc, int y, const SkPMColor ctable[]) {
+
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    SkPMColor cc = A32_MASK_IN_PLACE;
+    DITHER_4444_SCAN(y);
+
+    for (int x = 0; x < width; x++) {
+        SkPMColor c = ctable[*src];
+        cc &= c;
+        if (c != 0) {
+            dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x));
+        }
+        src += deltaSrc;
+    }
+    return cc != A32_MASK_IN_PLACE;
+}
+
+static SkScaledBitmapSampler::RowProc get_index_to_4444_proc(const SkImageDecoder& decoder) {
+    // Unpremul not allowed
+    if (decoder.getRequireUnpremultipliedColors()) {
+        return NULL;
+    }
+    const bool dither = decoder.getDitherImage();
+    if (decoder.getSkipWritingZeroes()) {
+        if (dither) {
+            return Sample_Index_D4444_D_SkipZ;
+        }
+        return Sample_Index_D4444_SkipZ;
+    }
+    if (dither) {
+        return Sample_Index_D4444_D;
+    }
+    return Sample_Index_D4444;
+}
+
 static bool Sample_Index_DI(void* SK_RESTRICT dstRow,
                             const uint8_t* SK_RESTRICT src,
                             int width, int deltaSrc, int, const SkPMColor[]) {
@@ -289,6 +535,15 @@
     return false;
 }
 
+static SkScaledBitmapSampler::RowProc get_index_to_index_proc(const SkImageDecoder& decoder) {
+    // Unpremul not allowed
+    if (decoder.getRequireUnpremultipliedColors()) {
+        return NULL;
+    }
+    // Ignore dither and skip zeroes
+    return Sample_Index_DI;
+}
+
 // A8
 static bool Sample_Gray_DA8(void* SK_RESTRICT dstRow,
                             const uint8_t* SK_RESTRICT src,
@@ -298,41 +553,15 @@
     return true;
 }
 
-// 8888 Unpremul
-
-static bool Sample_Gray_D8888_Unpremul(void* SK_RESTRICT dstRow,
-                                       const uint8_t* SK_RESTRICT src,
-                                       int width, int deltaSrc, int,
-                                       const SkPMColor[]) {
-    uint32_t* SK_RESTRICT dst = reinterpret_cast<uint32_t*>(dstRow);
-    for (int x = 0; x < width; x++) {
-        dst[x] = SkPackARGB32NoCheck(0xFF, src[0], src[0], src[0]);
-        src += deltaSrc;
+static SkScaledBitmapSampler::RowProc get_gray_to_A8_proc(const SkImageDecoder& decoder) {
+    if (decoder.getRequireUnpremultipliedColors()) {
+        return NULL;
     }
-    return false;
+    // Ignore skip and dither.
+    return Sample_Gray_DA8;
 }
 
-// Sample_RGBx_D8888_Unpremul is no different from Sample_RGBx_D8888, since alpha
-// is 0xFF
-
-static bool Sample_RGBA_D8888_Unpremul(void* SK_RESTRICT dstRow,
-                                       const uint8_t* SK_RESTRICT src,
-                                       int width, int deltaSrc, int,
-                                       const SkPMColor[]) {
-    uint32_t* SK_RESTRICT dst = reinterpret_cast<uint32_t*>(dstRow);
-    unsigned alphaMask = 0xFF;
-    for (int x = 0; x < width; x++) {
-        unsigned alpha = src[3];
-        dst[x] = SkPackARGB32NoCheck(alpha, src[0], src[1], src[2]);
-        src += deltaSrc;
-        alphaMask &= alpha;
-    }
-    return alphaMask != 0xFF;
-}
-
-// Sample_Index_D8888_Unpremul is the same as Sample_Index_D8888, since the
-// color table has its colors inserted unpremultiplied.
-
+typedef SkScaledBitmapSampler::RowProc (*RowProcChooser)(const SkImageDecoder& decoder);
 ///////////////////////////////////////////////////////////////////////////////
 
 #include "SkScaledBitmapSampler.h"
@@ -377,58 +606,49 @@
     SkASSERT(fDY > 0 && (fY0 + fDY * (fScaledHeight - 1)) < height);
 }
 
-bool SkScaledBitmapSampler::begin(SkBitmap* dst, SrcConfig sc, bool dither,
-                                  const SkPMColor ctable[],
-                                  bool requireUnpremul) {
-    static const RowProc gProcs[] = {
-        // 8888 (no dither distinction)
-        Sample_Gray_D8888,              Sample_Gray_D8888,
-        Sample_RGBx_D8888,              Sample_RGBx_D8888,
-        Sample_RGBA_D8888,              Sample_RGBA_D8888,
-        Sample_Index_D8888,             Sample_Index_D8888,
-        NULL,                           NULL,
-        // 565 (no alpha distinction)
-        Sample_Gray_D565,               Sample_Gray_D565_D,
-        Sample_RGBx_D565,               Sample_RGBx_D565_D,
-        Sample_RGBx_D565,               Sample_RGBx_D565_D,
-        Sample_Index_D565,              Sample_Index_D565_D,
-        Sample_D565_D565,               Sample_D565_D565,
-        // 4444
-        Sample_Gray_D4444,              Sample_Gray_D4444_D,
-        Sample_RGBx_D4444,              Sample_RGBx_D4444_D,
-        Sample_RGBA_D4444,              Sample_RGBA_D4444_D,
-        Sample_Index_D4444,             Sample_Index_D4444_D,
-        NULL,                           NULL,
-        // Index8
-        NULL,                           NULL,
-        NULL,                           NULL,
-        NULL,                           NULL,
-        Sample_Index_DI,                Sample_Index_DI,
-        NULL,                           NULL,
-        // A8
-        Sample_Gray_DA8,                Sample_Gray_DA8,
-        NULL,                           NULL,
-        NULL,                           NULL,
-        NULL,                           NULL,
-        NULL,                           NULL,
-        // 8888 Unpremul (no dither distinction)
-        Sample_Gray_D8888_Unpremul,     Sample_Gray_D8888_Unpremul,
-        Sample_RGBx_D8888,              Sample_RGBx_D8888,
-        Sample_RGBA_D8888_Unpremul,     Sample_RGBA_D8888_Unpremul,
-        Sample_Index_D8888,             Sample_Index_D8888,
-        NULL,                           NULL,
+bool SkScaledBitmapSampler::begin(SkBitmap* dst, SrcConfig sc,
+                                  const SkImageDecoder& decoder,
+                                  const SkPMColor ctable[]) {
+    static const RowProcChooser gProcChoosers[] = {
+        get_gray_to_8888_proc,
+        get_RGBx_to_8888_proc,
+        get_RGBA_to_8888_proc,
+        get_index_to_8888_proc,
+        NULL, // 565 to 8888
+
+        get_gray_to_565_proc,
+        get_RGBx_to_565_proc,
+        get_RGBx_to_565_proc, // The source alpha will be ignored.
+        get_index_to_565_proc,
+        get_565_to_565_proc,
+
+        get_gray_to_4444_proc,
+        get_RGBx_to_4444_proc,
+        get_RGBA_to_4444_proc,
+        get_index_to_4444_proc,
+        NULL, // 565 to 4444
+
+        NULL, // gray to index
+        NULL, // rgbx to index
+        NULL, // rgba to index
+        get_index_to_index_proc,
+        NULL, // 565 to index
+
+        get_gray_to_A8_proc,
+        NULL, // rgbx to a8
+        NULL, // rgba to a8
+        NULL, // index to a8
+        NULL, // 565 to a8
     };
+
     // The jump between dst configs in the table
-    static const int gProcDstConfigSpan = 10;
-    SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gProcs) == 6 * gProcDstConfigSpan,
+    static const int gProcDstConfigSpan = 5;
+    SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gProcChoosers) == 5 * gProcDstConfigSpan,
                       gProcs_has_the_wrong_number_of_entries);
 
     fCTable = ctable;
 
     int index = 0;
-    if (dither) {
-        index += 1;
-    }
     switch (sc) {
         case SkScaledBitmapSampler::kGray:
             fSrcPixelSize = 1;
@@ -436,23 +656,23 @@
             break;
         case SkScaledBitmapSampler::kRGB:
             fSrcPixelSize = 3;
-            index += 2;
+            index += 1;
             break;
         case SkScaledBitmapSampler::kRGBX:
             fSrcPixelSize = 4;
-            index += 2;
+            index += 1;
             break;
         case SkScaledBitmapSampler::kRGBA:
             fSrcPixelSize = 4;
-            index += 4;
+            index += 2;
             break;
         case SkScaledBitmapSampler::kIndex:
             fSrcPixelSize = 1;
-            index += 6;
+            index += 3;
             break;
         case SkScaledBitmapSampler::kRGB_565:
             fSrcPixelSize = 2;
-            index += 8;
+            index += 4;
             break;
         default:
             return false;
@@ -478,14 +698,12 @@
             return false;
     }
 
-    if (requireUnpremul) {
-        if (dst->config() != SkBitmap::kARGB_8888_Config) {
-            return false;
-        }
-        index += 5 * gProcDstConfigSpan;
+    RowProcChooser chooser = gProcChoosers[index];
+    if (NULL == chooser) {
+        fRowProc = NULL;
+    } else {
+        fRowProc = chooser(decoder);
     }
-
-    fRowProc = gProcs[index];
     fDstRow = (char*)dst->getPixels();
     fDstRowBytes = dst->rowBytes();
     fCurrY = 0;
@@ -501,3 +719,102 @@
     fCurrY += 1;
     return hadAlpha;
 }
+
+#ifdef SK_DEBUG
+// The following code is for a test to ensure that changing the method to get the right row proc
+// did not change the row proc unintentionally. Tested by ImageDecodingTest.cpp
+
+// friend of SkScaledBitmapSampler solely for the purpose of accessing fRowProc.
+class RowProcTester {
+public:
+    static SkScaledBitmapSampler::RowProc getRowProc(const SkScaledBitmapSampler& sampler) {
+        return sampler.fRowProc;
+    }
+};
+
+
+// Table showing the expected RowProc for each combination of inputs.
+// Table formated as follows:
+// Each group of 5 consecutive rows represents sampling from a single
+// SkScaledBitmapSampler::SrcConfig.
+// Within each set, each row represents a different destination SkBitmap::Config
+// Each column represents a different combination of dither and unpremul.
+// D = dither   ~D = no dither
+// U = unpremul ~U = no unpremul
+//  ~D~U                D~U                     ~DU                         DU
+SkScaledBitmapSampler::RowProc gTestProcs[] = {
+    // Gray
+    Sample_Gray_DA8,    Sample_Gray_DA8,        NULL,                       NULL,                       // to A8
+    NULL,               NULL,                   NULL,                       NULL,                       // to Index8
+    Sample_Gray_D565,   Sample_Gray_D565_D,     Sample_Gray_D565,           Sample_Gray_D565_D,         // to 565
+    Sample_Gray_D4444,  Sample_Gray_D4444_D,    Sample_Gray_D4444,          Sample_Gray_D4444_D,        // to 4444
+    Sample_Gray_D8888,  Sample_Gray_D8888,      Sample_Gray_D8888,          Sample_Gray_D8888,          // to 8888
+    // Index
+    NULL,               NULL,                   NULL,                       NULL,                       // to A8
+    Sample_Index_DI,    Sample_Index_DI,        NULL,                       NULL,                       // to Index8
+    Sample_Index_D565,  Sample_Index_D565_D,    Sample_Index_D565,          Sample_Index_D565_D,        // to 565
+    Sample_Index_D4444, Sample_Index_D4444_D,   NULL,                       NULL,                       // to 4444
+    Sample_Index_D8888, Sample_Index_D8888,     NULL,                       NULL,                       // to 8888
+    // RGB
+    NULL,               NULL,                   NULL,                       NULL,                       // to A8
+    NULL,               NULL,                   NULL,                       NULL,                       // to Index8
+    Sample_RGBx_D565,   Sample_RGBx_D565_D,     Sample_RGBx_D565,           Sample_RGBx_D565_D,         // to 565
+    Sample_RGBx_D4444,  Sample_RGBx_D4444_D,    Sample_RGBx_D4444,          Sample_RGBx_D4444_D,        // to 4444
+    Sample_RGBx_D8888,  Sample_RGBx_D8888,      Sample_RGBx_D8888,          Sample_RGBx_D8888,          // to 8888
+    // RGBx is the same as RGB
+    NULL,               NULL,                   NULL,                       NULL,                       // to A8
+    NULL,               NULL,                   NULL,                       NULL,                       // to Index8
+    Sample_RGBx_D565,   Sample_RGBx_D565_D,     Sample_RGBx_D565,           Sample_RGBx_D565_D,         // to 565
+    Sample_RGBx_D4444,  Sample_RGBx_D4444_D,    Sample_RGBx_D4444,          Sample_RGBx_D4444_D,        // to 4444
+    Sample_RGBx_D8888,  Sample_RGBx_D8888,      Sample_RGBx_D8888,          Sample_RGBx_D8888,          // to 8888
+    // RGBA
+    NULL,               NULL,                   NULL,                       NULL,                       // to A8
+    NULL,               NULL,                   NULL,                       NULL,                       // to Index8
+    Sample_RGBx_D565,   Sample_RGBx_D565_D,     Sample_RGBx_D565,           Sample_RGBx_D565_D,         // to 565
+    Sample_RGBA_D4444,  Sample_RGBA_D4444_D,    NULL,                       NULL,                       // to 4444
+    Sample_RGBA_D8888,  Sample_RGBA_D8888,      Sample_RGBA_D8888_Unpremul, Sample_RGBA_D8888_Unpremul, // to 8888
+    // RGB_565
+    NULL,               NULL,                   NULL,                       NULL,                       // to A8
+    NULL,               NULL,                   NULL,                       NULL,                       // to Index8
+    Sample_D565_D565,   Sample_D565_D565,       Sample_D565_D565,           Sample_D565_D565,           // to 565
+    NULL,               NULL,                   NULL,                       NULL,                       // to 4444
+    NULL,               NULL,                   NULL,                       NULL,                       // to 8888
+};
+
+// Dummy class that allows instantiation of an ImageDecoder, so begin can query its fields.
+class DummyDecoder : public SkImageDecoder {
+public:
+    DummyDecoder() {}
+protected:
+    virtual bool onDecode(SkStream*, SkBitmap*, SkImageDecoder::Mode) SK_OVERRIDE {
+        return false;
+    }
+};
+
+void test_row_proc_choice() {
+    SkBitmap dummyBitmap;
+    DummyDecoder dummyDecoder;
+    size_t procCounter = 0;
+    for (int sc = SkScaledBitmapSampler::kGray; sc <= SkScaledBitmapSampler::kRGB_565; ++sc) {
+        for (int c = SkBitmap::kA8_Config; c <= SkBitmap::kARGB_8888_Config; ++c) {
+            for (int unpremul = 0; unpremul <= 1; ++unpremul) {
+                for (int dither = 0; dither <= 1; ++dither) {
+                    // Arbitrary width/height/sampleSize to allow SkScaledBitmapSampler to
+                    // be considered valid.
+                    SkScaledBitmapSampler sampler(10, 10, 1);
+                    dummyBitmap.setConfig((SkBitmap::Config) c, 10, 10);
+                    dummyDecoder.setDitherImage(SkToBool(dither));
+                    dummyDecoder.setRequireUnpremultipliedColors(SkToBool(unpremul));
+                    sampler.begin(&dummyBitmap, (SkScaledBitmapSampler::SrcConfig) sc,
+                                  dummyDecoder);
+                    SkScaledBitmapSampler::RowProc expected = gTestProcs[procCounter];
+                    SkScaledBitmapSampler::RowProc actual = RowProcTester::getRowProc(sampler);
+                    SkASSERT(expected == actual);
+                    procCounter++;
+                }
+            }
+        }
+    }
+    SkASSERT(SK_ARRAY_COUNT(gTestProcs) == procCounter);
+}
+#endif // SK_DEBUG
diff --git a/src/images/SkScaledBitmapSampler.h b/src/images/SkScaledBitmapSampler.h
index 6477db2..a293fe7 100644
--- a/src/images/SkScaledBitmapSampler.h
+++ b/src/images/SkScaledBitmapSampler.h
@@ -10,6 +10,7 @@
 
 #include "SkTypes.h"
 #include "SkColor.h"
+#include "SkImageDecoder.h"
 
 class SkBitmap;
 
@@ -35,12 +36,17 @@
     // Given a dst bitmap (with pixels already allocated) and a src-config,
     // prepares iterator to process the src colors and write them into dst.
     // Returns false if the request cannot be fulfulled.
-    bool begin(SkBitmap* dst, SrcConfig sc, bool doDither,
-               const SkPMColor* = NULL, bool requireUnPremul = false);
+    bool begin(SkBitmap* dst, SrcConfig sc, const SkImageDecoder& decoder,
+               const SkPMColor* = NULL);
     // call with row of src pixels, for y = 0...scaledHeight-1.
     // returns true if the row had non-opaque alpha in it
     bool next(const uint8_t* SK_RESTRICT src);
 
+    typedef bool (*RowProc)(void* SK_RESTRICT dstRow,
+                            const uint8_t* SK_RESTRICT src,
+                            int width, int deltaSrc, int y,
+                            const SkPMColor[]);
+
 private:
     int fScaledWidth;
     int fScaledHeight;
@@ -50,11 +56,6 @@
     int fDX;    // step between X samples
     int fDY;    // step between Y samples
 
-    typedef bool (*RowProc)(void* SK_RESTRICT dstRow,
-                            const uint8_t* SK_RESTRICT src,
-                            int width, int deltaSrc, int y,
-                            const SkPMColor[]);
-
     // setup state
     char*   fDstRow; // points into bitmap's pixels
     size_t  fDstRowBytes;
@@ -64,6 +65,11 @@
 
     // optional reference to the src colors if the src is a palette model
     const SkPMColor* fCTable;
+
+#ifdef SK_DEBUG
+    // Helper class allowing a test to have access to fRowProc.
+    friend class RowProcTester;
+#endif
 };
 
 #endif
diff --git a/tests/ImageDecodingTest.cpp b/tests/ImageDecodingTest.cpp
index 89d18ab..5146b08 100644
--- a/tests/ImageDecodingTest.cpp
+++ b/tests/ImageDecodingTest.cpp
@@ -255,13 +255,18 @@
                               SkBitmap::kARGB_8888_Config);
     }
 }
-#endif
+
+// Test inside SkScaledBitmapSampler.cpp
+extern void test_row_proc_choice();
+
+#endif // SK_DEBUG
 
 static void test_imageDecodingTests(skiatest::Reporter* reporter) {
     test_unpremul(reporter);
     test_pref_config_table(reporter);
 #ifdef SK_DEBUG
     test_stream_life();
+    test_row_proc_choice();
 #endif
 }
 
diff --git a/tools/skimage_main.cpp b/tools/skimage_main.cpp
index 7cd15e8..d98a619 100644
--- a/tools/skimage_main.cpp
+++ b/tools/skimage_main.cpp
@@ -30,6 +30,7 @@
 DEFINE_bool(reencode, true, "Reencode the images to test encoding.");
 DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images.");
 DEFINE_string2(writePath, w, "",  "Write rendered images into this directory.");
+DEFINE_bool(skip, false, "Skip writing zeroes.");
 
 struct Format {
     SkImageEncoder::Type    fType;
@@ -383,6 +384,7 @@
 
     SkAutoTDelete<SkImageDecoder> ad(codec);
 
+    codec->setSkipWritingZeroes(FLAGS_skip);
     stream.rewind();
 
     // Create a string representing just the filename itself, for use in json expectations.