Allow sampling GIF images during decode.

SkScaledBitmapSampler:
Add a mode for sampling rows out of order, used by GIF decoder for
interlaced images.
Add a getter for the X sampling rate.

SkImageDecoder_libgif:
Respect the sampleSize set on SkImageDecoder.

skimage_main:
Provide an option to set a sample size.

BUG=https://b.corp.google.com/issue?id=8999690
R=reed@google.com, djsollen@google.com, halcanary@google.com

Author: scroggo@google.com

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

git-svn-id: http://skia.googlecode.com/svn/trunk/src@11659 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/images/SkImageDecoder_libgif.cpp b/images/SkImageDecoder_libgif.cpp
index 1d25120..08f37ef 100644
--- a/images/SkImageDecoder_libgif.cpp
+++ b/images/SkImageDecoder_libgif.cpp
@@ -1,4 +1,3 @@
-
 /*
  * Copyright 2006 The Android Open Source Project
  *
@@ -7,12 +6,13 @@
  */
 
 
-#include "SkImageDecoder.h"
 #include "SkColor.h"
 #include "SkColorPriv.h"
+#include "SkColorTable.h"
+#include "SkImageDecoder.h"
+#include "SkScaledBitmapSampler.h"
 #include "SkStream.h"
 #include "SkTemplates.h"
-#include "SkPackBits.h"
 
 #include "gif_lib.h"
 
@@ -154,6 +154,23 @@
     return false;
 }
 
+/**
+ *  Skip rows in the source gif image.
+ *  @param gif Source image.
+ *  @param dst Scratch output needed by gif library call. Must be >= width bytes.
+ *  @param width Bytes per row in the source image.
+ *  @param rowsToSkip Number of rows to skip.
+ *  @return True on success, false on GIF_ERROR.
+ */
+static bool skip_src_rows(GifFileType* gif, uint8_t* dst, int width, int rowsToSkip) {
+    for (int i = 0; i < rowsToSkip; i++) {
+        if (DGifGetLine(gif, dst, width) == GIF_ERROR) {
+            return false;
+        }
+    }
+    return true;
+}
+
 bool SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, Mode mode) {
 #if GIFLIB_MAJOR < 5
     GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc);
@@ -196,13 +213,20 @@
 
             width = gif->SWidth;
             height = gif->SHeight;
-            if (width <= 0 || height <= 0 ||
-                !this->chooseFromOneChoice(SkBitmap::kIndex8_Config,
-                                           width, height)) {
+            if (width <= 0 || height <= 0) {
+                return error_return(gif, *bm, "invalid dimensions");
+            }
+
+            // FIXME: We could give the caller a choice of images or configs.
+            if (!this->chooseFromOneChoice(SkBitmap::kIndex8_Config, width, height)) {
                 return error_return(gif, *bm, "chooseFromOneChoice");
             }
 
-            bm->setConfig(SkBitmap::kIndex8_Config, width, height);
+            SkScaledBitmapSampler sampler(width, height, this->getSampleSize());
+
+            bm->setConfig(SkBitmap::kIndex8_Config, sampler.scaledWidth(),
+                          sampler.scaledHeight());
+
             if (SkImageDecoder::kDecodeBounds_Mode == mode) {
                 return true;
             }
@@ -226,33 +250,28 @@
                 }
 
                 colorCount = cmap->ColorCount;
-                SkColorTable* ctable = SkNEW_ARGS(SkColorTable, (colorCount));
-                SkPMColor* colorPtr = ctable->lockColors();
-                for (int index = 0; index < colorCount; index++)
+                SkAutoTMalloc<SkPMColor> colorStorage(colorCount);
+                SkPMColor* colorPtr = colorStorage.get();
+                for (int index = 0; index < colorCount; index++) {
                     colorPtr[index] = SkPackARGB32(0xFF,
                                                    cmap->Colors[index].Red,
                                                    cmap->Colors[index].Green,
                                                    cmap->Colors[index].Blue);
+                }
 
                 transpIndex = find_transpIndex(temp_save, colorCount);
-                if (transpIndex < 0)
-                    ctable->setFlags(ctable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
-                else
-                    colorPtr[transpIndex] = 0; // ram in a transparent SkPMColor
-                ctable->unlockColors(true);
+                bool reallyHasAlpha = transpIndex >= 0;
+                if (reallyHasAlpha) {
+                    colorPtr[transpIndex] = SK_ColorTRANSPARENT; // ram in a transparent SkPMColor
+                }
 
-                SkAutoUnref aurts(ctable);
+                SkAutoTUnref<SkColorTable> ctable(SkNEW_ARGS(SkColorTable, (colorPtr, colorCount)));
+                ctable->setIsOpaque(!reallyHasAlpha);
                 if (!this->allocPixelRef(bm, ctable)) {
                     return error_return(gif, *bm, "allocPixelRef");
                 }
             }
 
-            SkAutoLockPixels alp(*bm);
-
-            // time to decode the scanlines
-            //
-            uint8_t*  scanline = bm->getAddr8(0, 0);
-            const int rowBytes = bm->rowBytes();
             const int innerWidth = desc.Width;
             const int innerHeight = desc.Height;
 
@@ -261,10 +280,19 @@
                 return error_return(gif, *bm, "non-pos inner width/height");
             }
 
+            SkAutoLockPixels alp(*bm);
+
+            SkAutoMalloc storage(innerWidth);
+            uint8_t* scanline = (uint8_t*) storage.get();
+
+            // GIF has an option to store the scanlines of an image, plus a larger background,
+            // filled by a fill color. In this case, we will use a subset of the larger bitmap
+            // for sampling.
+            SkBitmap subset;
+            SkBitmap* workingBitmap;
             // are we only a subset of the total bounds?
             if ((desc.Top | desc.Left) > 0 ||
-                 innerWidth < width || innerHeight < height)
-            {
+                 innerWidth < width || innerHeight < height) {
                 int fill;
                 if (transpIndex >= 0) {
                     fill = transpIndex;
@@ -276,33 +304,62 @@
                         static_cast<unsigned>(colorCount)) {
                     fill = 0;
                 }
-                memset(scanline, fill, bm->getSize());
-                // bump our starting address
-                scanline += desc.Top * rowBytes + desc.Left;
+                // Fill the background.
+                memset(bm->getPixels(), fill, bm->getSize());
+
+                // Create a subset of the bitmap.
+                SkIRect subsetRect(SkIRect::MakeXYWH(desc.Left / sampler.srcDX(),
+                                                     desc.Top / sampler.srcDY(),
+                                                     innerWidth / sampler.srcDX(),
+                                                     innerHeight / sampler.srcDY()));
+                if (!bm->extractSubset(&subset, subsetRect)) {
+                    return error_return(gif, *bm, "Extract failed.");
+                }
+                // Update the sampler. We'll now be only sampling into the subset.
+                sampler = SkScaledBitmapSampler(innerWidth, innerHeight, this->getSampleSize());
+                workingBitmap = &subset;
+            } else {
+                workingBitmap = bm;
+            }
+
+            // bm is already locked, but if we had to take a subset, it must be locked also,
+            // so that getPixels() will point to its pixels.
+            SkAutoLockPixels alpWorking(*workingBitmap);
+
+            if (!sampler.begin(workingBitmap, SkScaledBitmapSampler::kIndex, *this)) {
+                return error_return(gif, *bm, "Sampler failed to begin.");
             }
 
             // now decode each scanline
-            if (gif->Image.Interlace)
-            {
+            if (gif->Image.Interlace) {
+                // Iterate over the height of the source data. The sampler will
+                // take care of skipping unneeded rows.
                 GifInterlaceIter iter(innerHeight);
-                for (int y = 0; y < innerHeight; y++)
-                {
-                    uint8_t* row = scanline + iter.currY() * rowBytes;
-                    if (DGifGetLine(gif, row, innerWidth) == GIF_ERROR) {
+                for (int y = 0; y < innerHeight; y++){
+                    if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
                         return error_return(gif, *bm, "interlace DGifGetLine");
                     }
+                    sampler.sampleInterlaced(scanline, iter.currY());
                     iter.next();
                 }
-            }
-            else
-            {
+            } else {
                 // easy, non-interlace case
-                for (int y = 0; y < innerHeight; y++) {
+                const int outHeight = workingBitmap->height();
+                skip_src_rows(gif, scanline, innerWidth, sampler.srcY0());
+                for (int y = 0; y < outHeight; y++) {
                     if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
                         return error_return(gif, *bm, "DGifGetLine");
                     }
-                    scanline += rowBytes;
+                    // scanline now contains the raw data. Sample it.
+                    sampler.next(scanline);
+                    if (y < outHeight - 1) {
+                        skip_src_rows(gif, scanline, innerWidth, sampler.srcDY() - 1);
+                    }
                 }
+                // skip the rest of the rows (if any)
+                int read = (outHeight - 1) * sampler.srcDY() + sampler.srcY0() + 1;
+                SkASSERT(read <= innerHeight);
+                skip_src_rows(gif, scanline, innerWidth, innerHeight - read);
             }
             goto DONE;
             } break;
diff --git a/images/SkScaledBitmapSampler.cpp b/images/SkScaledBitmapSampler.cpp
index cad97ea..825f9d5 100644
--- a/images/SkScaledBitmapSampler.cpp
+++ b/images/SkScaledBitmapSampler.cpp
@@ -576,6 +576,8 @@
         sk_throw();
     }
 
+    SkDEBUGCODE(fSampleMode = kUninitialized_SampleMode);
+
     if (sampleSize <= 1) {
         fScaledWidth = width;
         fScaledHeight = height;
@@ -711,6 +713,8 @@
 }
 
 bool SkScaledBitmapSampler::next(const uint8_t* SK_RESTRICT src) {
+    SkASSERT(kInterlaced_SampleMode != fSampleMode);
+    SkDEBUGCODE(fSampleMode = kConsecutive_SampleMode);
     SkASSERT((unsigned)fCurrY < (unsigned)fScaledHeight);
 
     bool hadAlpha = fRowProc(fDstRow, src + fX0 * fSrcPixelSize, fScaledWidth,
@@ -720,6 +724,29 @@
     return hadAlpha;
 }
 
+bool SkScaledBitmapSampler::sampleInterlaced(const uint8_t* SK_RESTRICT src, int srcY) {
+    SkASSERT(kConsecutive_SampleMode != fSampleMode);
+    SkDEBUGCODE(fSampleMode = kInterlaced_SampleMode);
+    // Any line that should be a part of the destination can be created by the formula:
+    // fY0 + (some multiplier) * fDY
+    // so if srcY - fY0 is not an integer multiple of fDY that srcY will be skipped.
+    const int srcYMinusY0 = srcY - fY0;
+    if (srcYMinusY0 % fDY != 0) {
+        // This line is not part of the output, so return false for alpha, since we have
+        // not added an alpha to the output.
+        return false;
+    }
+    // Unlike in next(), where the data is used sequentially, this function skips around,
+    // so fDstRow and fCurrY are never updated. fDstRow must always be the starting point
+    // of the destination bitmap's pixels, which is used to calculate the destination row
+    // each time this function is called.
+    const int dstY = srcYMinusY0 / fDY;
+    SkASSERT(dstY < fScaledHeight);
+    char* dstRow = fDstRow + dstY * fDstRowBytes;
+    return fRowProc(dstRow, src + fX0 * fSrcPixelSize, fScaledWidth,
+                    fDX * fSrcPixelSize, dstY, fCTable);
+}
+
 #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
diff --git a/images/SkScaledBitmapSampler.h b/images/SkScaledBitmapSampler.h
index a293fe7..e6c4577 100644
--- a/images/SkScaledBitmapSampler.h
+++ b/images/SkScaledBitmapSampler.h
@@ -22,6 +22,7 @@
     int scaledHeight() const { return fScaledHeight; }
 
     int srcY0() const { return fY0; }
+    int srcDX() const { return fDX; }
     int srcDY() const { return fDY; }
 
     enum SrcConfig {
@@ -42,6 +43,12 @@
     // returns true if the row had non-opaque alpha in it
     bool next(const uint8_t* SK_RESTRICT src);
 
+    // Like next(), but specifies the y value of the source row, so the
+    // rows can come in any order. If the row is not part of the output
+    // sample, it will be skipped. Only sampleInterlaced OR next should
+    // be called for one SkScaledBitmapSampler.
+    bool sampleInterlaced(const uint8_t* SK_RESTRICT src, int srcY);
+
     typedef bool (*RowProc)(void* SK_RESTRICT dstRow,
                             const uint8_t* SK_RESTRICT src,
                             int width, int deltaSrc, int y,
@@ -56,6 +63,18 @@
     int fDX;    // step between X samples
     int fDY;    // step between Y samples
 
+#ifdef SK_DEBUG
+    // Keep track of whether the caller is using next or sampleInterlaced.
+    // Only one can be used per sampler.
+    enum SampleMode {
+        kUninitialized_SampleMode,
+        kConsecutive_SampleMode,
+        kInterlaced_SampleMode,
+    };
+
+    SampleMode fSampleMode;
+#endif
+
     // setup state
     char*   fDstRow; // points into bitmap's pixels
     size_t  fDstRowBytes;