Upstream changes from Android for decoding jpeg images.

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

git-svn-id: http://skia.googlecode.com/svn/trunk@8267 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gyp/libjpeg.gyp b/gyp/libjpeg.gyp
index 394f2ee..f668310 100644
--- a/gyp/libjpeg.gyp
+++ b/gyp/libjpeg.gyp
@@ -13,110 +13,127 @@
     'use_system_libjpeg%': 0,
   },
   'conditions': [
-    ['use_system_libjpeg==0', {
-      'targets': [
-        {
-          'target_name': 'libjpeg',
-          'type': 'static_library',
-          'sources': [
-            '../third_party/externals/libjpeg/jcapimin.c',
-            '../third_party/externals/libjpeg/jcapistd.c',
-            '../third_party/externals/libjpeg/jccoefct.c',
-            '../third_party/externals/libjpeg/jccolor.c',
-            '../third_party/externals/libjpeg/jcdctmgr.c',
-            '../third_party/externals/libjpeg/jchuff.c',
-            '../third_party/externals/libjpeg/jchuff.h',
-            '../third_party/externals/libjpeg/jcinit.c',
-            '../third_party/externals/libjpeg/jcmainct.c',
-            '../third_party/externals/libjpeg/jcmarker.c',
-            '../third_party/externals/libjpeg/jcmaster.c',
-            '../third_party/externals/libjpeg/jcomapi.c',
-            '../third_party/externals/libjpeg/jconfig.h',
-            '../third_party/externals/libjpeg/jcparam.c',
-            '../third_party/externals/libjpeg/jcphuff.c',
-            '../third_party/externals/libjpeg/jcprepct.c',
-            '../third_party/externals/libjpeg/jcsample.c',
-            '../third_party/externals/libjpeg/jdapimin.c',
-            '../third_party/externals/libjpeg/jdapistd.c',
-            '../third_party/externals/libjpeg/jdatadst.c',
-            '../third_party/externals/libjpeg/jdatasrc.c',
-            '../third_party/externals/libjpeg/jdcoefct.c',
-            '../third_party/externals/libjpeg/jdcolor.c',
-            '../third_party/externals/libjpeg/jdct.h',
-            '../third_party/externals/libjpeg/jddctmgr.c',
-            '../third_party/externals/libjpeg/jdhuff.c',
-            '../third_party/externals/libjpeg/jdhuff.h',
-            '../third_party/externals/libjpeg/jdinput.c',
-            '../third_party/externals/libjpeg/jdmainct.c',
-            '../third_party/externals/libjpeg/jdmarker.c',
-            '../third_party/externals/libjpeg/jdmaster.c',
-            '../third_party/externals/libjpeg/jdmerge.c',
-            '../third_party/externals/libjpeg/jdphuff.c',
-            '../third_party/externals/libjpeg/jdpostct.c',
-            '../third_party/externals/libjpeg/jdsample.c',
-            '../third_party/externals/libjpeg/jerror.c',
-            '../third_party/externals/libjpeg/jerror.h',
-            '../third_party/externals/libjpeg/jfdctflt.c',
-            '../third_party/externals/libjpeg/jfdctfst.c',
-            '../third_party/externals/libjpeg/jfdctint.c',
-            '../third_party/externals/libjpeg/jidctflt.c',
-            '../third_party/externals/libjpeg/jidctfst.c',
-            '../third_party/externals/libjpeg/jidctint.c',
-            '../third_party/externals/libjpeg/jinclude.h',
-            '../third_party/externals/libjpeg/jmemmgr.c',
-            '../third_party/externals/libjpeg/jmemnobs.c',
-            '../third_party/externals/libjpeg/jmemsys.h',
-            '../third_party/externals/libjpeg/jmorecfg.h',
-            '../third_party/externals/libjpeg/jpegint.h',
-            '../third_party/externals/libjpeg/jpeglib.h',
-            '../third_party/externals/libjpeg/jquant1.c',
-            '../third_party/externals/libjpeg/jquant2.c',
-            '../third_party/externals/libjpeg/jutils.c',
-            '../third_party/externals/libjpeg/jversion.h',
-          ],
-          'direct_dependent_settings': {
-            'include_dirs': [
-              '../third_party/externals/libjpeg',
-            ],
-          },
-          'conditions': [
-            ['OS!="win"', {
-              'product_name': 'jpeg',
-              'cflags': [
-               '-Wno-main', # supresses warnings about naming things "main"
-              ],
-            }],
-            ['OS=="android"', {
-              'cflags!': [
-               '-fno-rtti', # supresses warnings about invalid option of non-C++ code
-               '-Wall',
-               '-Werror',
-              ],
-            }],
-            ['OS in ["linux", "freebsd", "openbsd", "solaris", "nacl"]', {
-              'cflags!': [
-               '-Werror',
-              ],
-            }],
-          ],
-        },
-      ],
-    }, {
+    ['skia_os == "android"', {
       'targets': [
         {
           'target_name': 'libjpeg',
           'type': 'none',
-          'direct_dependent_settings': {
-            'defines': [
-              'USE_SYSTEM_LIBJPEG',
-            ],
-          },
-          'link_settings': {
-            'libraries': [
-              '-ljpeg',
-            ],
-          },
-        }
+          'dependencies': [
+            'android_deps.gyp:jpeg',
+          ],
+          'export_dependent_settings': [
+            'android_deps.gyp:jpeg',
+          ],
+        },
+      ],
+    }, { # skia_os != android
+      'conditions': [
+          ['use_system_libjpeg==0', {
+          'targets': [
+            {
+              'target_name': 'libjpeg',
+              'type': 'static_library',
+              'sources': [
+                '../third_party/externals/libjpeg/jcapimin.c',
+                '../third_party/externals/libjpeg/jcapistd.c',
+                '../third_party/externals/libjpeg/jccoefct.c',
+                '../third_party/externals/libjpeg/jccolor.c',
+                '../third_party/externals/libjpeg/jcdctmgr.c',
+                '../third_party/externals/libjpeg/jchuff.c',
+                '../third_party/externals/libjpeg/jchuff.h',
+                '../third_party/externals/libjpeg/jcinit.c',
+                '../third_party/externals/libjpeg/jcmainct.c',
+                '../third_party/externals/libjpeg/jcmarker.c',
+                '../third_party/externals/libjpeg/jcmaster.c',
+                '../third_party/externals/libjpeg/jcomapi.c',
+                '../third_party/externals/libjpeg/jconfig.h',
+                '../third_party/externals/libjpeg/jcparam.c',
+                '../third_party/externals/libjpeg/jcphuff.c',
+                '../third_party/externals/libjpeg/jcprepct.c',
+                '../third_party/externals/libjpeg/jcsample.c',
+                '../third_party/externals/libjpeg/jdapimin.c',
+                '../third_party/externals/libjpeg/jdapistd.c',
+                '../third_party/externals/libjpeg/jdatadst.c',
+                '../third_party/externals/libjpeg/jdatasrc.c',
+                '../third_party/externals/libjpeg/jdcoefct.c',
+                '../third_party/externals/libjpeg/jdcolor.c',
+                '../third_party/externals/libjpeg/jdct.h',
+                '../third_party/externals/libjpeg/jddctmgr.c',
+                '../third_party/externals/libjpeg/jdhuff.c',
+                '../third_party/externals/libjpeg/jdhuff.h',
+                '../third_party/externals/libjpeg/jdinput.c',
+                '../third_party/externals/libjpeg/jdmainct.c',
+                '../third_party/externals/libjpeg/jdmarker.c',
+                '../third_party/externals/libjpeg/jdmaster.c',
+                '../third_party/externals/libjpeg/jdmerge.c',
+                '../third_party/externals/libjpeg/jdphuff.c',
+                '../third_party/externals/libjpeg/jdpostct.c',
+                '../third_party/externals/libjpeg/jdsample.c',
+                '../third_party/externals/libjpeg/jerror.c',
+                '../third_party/externals/libjpeg/jerror.h',
+                '../third_party/externals/libjpeg/jfdctflt.c',
+                '../third_party/externals/libjpeg/jfdctfst.c',
+                '../third_party/externals/libjpeg/jfdctint.c',
+                '../third_party/externals/libjpeg/jidctflt.c',
+                '../third_party/externals/libjpeg/jidctfst.c',
+                '../third_party/externals/libjpeg/jidctint.c',
+                '../third_party/externals/libjpeg/jinclude.h',
+                '../third_party/externals/libjpeg/jmemmgr.c',
+                '../third_party/externals/libjpeg/jmemnobs.c',
+                '../third_party/externals/libjpeg/jmemsys.h',
+                '../third_party/externals/libjpeg/jmorecfg.h',
+                '../third_party/externals/libjpeg/jpegint.h',
+                '../third_party/externals/libjpeg/jpeglib.h',
+                '../third_party/externals/libjpeg/jquant1.c',
+                '../third_party/externals/libjpeg/jquant2.c',
+                '../third_party/externals/libjpeg/jutils.c',
+                '../third_party/externals/libjpeg/jversion.h',
+              ],
+              'direct_dependent_settings': {
+                'include_dirs': [
+                  '../third_party/externals/libjpeg',
+                ],
+              },
+              'conditions': [
+                ['OS!="win"', {
+                  'product_name': 'jpeg',
+                  'cflags': [
+                   '-Wno-main', # supresses warnings about naming things "main"
+                  ],
+                }],
+                ['OS=="android"', {
+                  'cflags!': [
+                   '-fno-rtti', # supresses warnings about invalid option of non-C++ code
+                   '-Wall',
+                   '-Werror',
+                  ],
+                }],
+                ['OS in ["linux", "freebsd", "openbsd", "solaris", "nacl"]', {
+                  'cflags!': [
+                   '-Werror',
+                  ],
+                }],
+              ],
+            },
+          ],
+        }, {
+          'targets': [
+            {
+              'target_name': 'libjpeg',
+              'type': 'none',
+              'direct_dependent_settings': {
+                'defines': [
+                  'USE_SYSTEM_LIBJPEG',
+                ],
+              },
+              'link_settings': {
+                'libraries': [
+                  '-ljpeg',
+                ],
+              },
+            }
+          ],
+        }],
       ],
     }],
   ],
diff --git a/src/images/SkImageDecoder_libjpeg.cpp b/src/images/SkImageDecoder_libjpeg.cpp
index 867c41c..4f32aa9 100644
--- a/src/images/SkImageDecoder_libjpeg.cpp
+++ b/src/images/SkImageDecoder_libjpeg.cpp
@@ -15,7 +15,10 @@
 #include "SkScaledBitmapSampler.h"
 #include "SkStream.h"
 #include "SkTemplates.h"
+#include "SkTime.h"
 #include "SkUtils.h"
+#include "SkRect.h"
+#include "SkCanvas.h"
 
 #include <stdio.h>
 extern "C" {
@@ -23,7 +26,13 @@
     #include "jerror.h"
 }
 
-// this enables timing code to report milliseconds for an encode
+// Uncomment to enable the code path used by the Android framework with their
+// custom image decoders.
+//#if defined(SK_BUILD_FOR_ANDROID) && defined(SK_DEBUG)
+//  #define SK_BUILD_FOR_ANDROID_FRAMEWORK
+//#endif
+
+// These enable timing code that report milliseconds for an encoding/decoding
 //#define TIME_ENCODE
 //#define TIME_DECODE
 
@@ -31,39 +40,98 @@
 // disable for the moment, as we have some glitches when width != multiple of 4
 #define WE_CONVERT_TO_YUV
 
+// If ANDROID_RGB is defined by in the jpeg headers it indicates that jpeg offers
+// support for two additional formats (1) JCS_RGBA_8888 and (2) JCS_RGB_565.
+
 //////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////
 
+static void overwrite_mem_buffer_size(j_decompress_ptr cinfo) {
+#ifdef SK_BUILD_FOR_ANDROID
+    /* Check if the device indicates that it has a large amount of system memory
+     * if so, increase the memory allocation to 30MB instead of the default 5MB.
+     */
+#ifdef ANDROID_LARGE_MEMORY_DEVICE
+    cinfo->mem->max_memory_to_use = 30 * 1024 * 1024;
+#else
+    cinfo->mem->max_memory_to_use = 5 * 1024 * 1024;
+#endif
+#endif // SK_BUILD_FOR_ANDROID
+}
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+class SkJPEGImageIndex {
+public:
+    SkJPEGImageIndex(SkStream* stream, SkImageDecoder* decoder)
+        : fSrcMgr(stream, decoder, true) {}
+
+    ~SkJPEGImageIndex() {
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+        jpeg_destroy_huffman_index(&fHuffmanIndex);
+#endif
+        jpeg_finish_decompress(&fCInfo);
+        jpeg_destroy_decompress(&fCInfo);
+    }
+
+    /**
+     * Init the cinfo struct using libjpeg and apply any necessary
+     * customizations.
+     */
+    void initializeInfo() {
+        jpeg_create_decompress(&fCInfo);
+        overwrite_mem_buffer_size(&fCInfo);
+        fCInfo.src = &fSrcMgr;
+    }
+
+    jpeg_decompress_struct* cinfo() { return &fCInfo; }
+
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+    huffman_index* huffmanIndex() { return &fHuffmanIndex; }
+#endif
+
+private:
+    skjpeg_source_mgr  fSrcMgr;
+    jpeg_decompress_struct fCInfo;
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+    huffman_index fHuffmanIndex;
+#endif
+};
+
 class SkJPEGImageDecoder : public SkImageDecoder {
 public:
+    SkJPEGImageDecoder() {
+        fImageIndex = NULL;
+        fImageWidth = 0;
+        fImageHeight = 0;
+    }
+
+    virtual ~SkJPEGImageDecoder() {
+        SkDELETE(fImageIndex);
+    }
+
     virtual Format getFormat() const {
         return kJPEG_Format;
     }
 
 protected:
-    virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+    virtual bool onBuildTileIndex(SkStream *stream, int *width, int *height) SK_OVERRIDE;
+    virtual bool onDecodeRegion(SkBitmap* bitmap, const SkIRect& rect) SK_OVERRIDE;
+#endif
+    virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
+
+private:
+    SkJPEGImageIndex* fImageIndex;
+    int fImageWidth;
+    int fImageHeight;
+
+    typedef SkImageDecoder INHERITED;
 };
 
 //////////////////////////////////////////////////////////////////////////
 
-#include "SkTime.h"
-
-class AutoTimeMillis {
-public:
-    AutoTimeMillis(const char label[]) : fLabel(label) {
-        if (!fLabel) {
-            fLabel = "";
-        }
-        fNow = SkTime::GetMSecs();
-    }
-    ~AutoTimeMillis() {
-        SkDebugf("---- Time (ms): %s %d\n", fLabel, SkTime::GetMSecs() - fNow);
-    }
-private:
-    const char* fLabel;
-    SkMSec      fNow;
-};
-
 /* Automatically clean up after throwing an exception */
 class JPEGAutoClean {
 public:
@@ -80,24 +148,6 @@
     jpeg_decompress_struct* cinfo_ptr;
 };
 
-#ifdef SK_BUILD_FOR_ANDROID
-
-/* For non-ndk builds we could look at the system's jpeg memory cap and use it
- * if it is set. However, for now we will use the NDK compliant hardcoded values
- */
-//#include <cutils/properties.h>
-//static const char KEY_MEM_CAP[] = "ro.media.dec.jpeg.memcap";
-
-static void overwrite_mem_buffer_size(j_decompress_ptr cinfo) {
-#ifdef ANDROID_LARGE_MEMORY_DEVICE
-    cinfo->mem->max_memory_to_use = 30 * 1024 * 1024;
-#else
-    cinfo->mem->max_memory_to_use = 5 * 1024 * 1024;
-#endif
-}
-#endif
-
-
 ///////////////////////////////////////////////////////////////////////////////
 
 /*  If we need to better match the request, we might examine the image and
@@ -116,26 +166,39 @@
     /* These are initialized to 0, so if they have non-zero values, we assume
        they are "valid" (i.e. have been computed by libjpeg)
      */
-    return cinfo.output_width != 0 && cinfo.output_height != 0;
+    return 0 != cinfo.output_width && 0 != cinfo.output_height;
 }
 
-static bool skip_src_rows(jpeg_decompress_struct* cinfo, void* buffer,
-                          int count) {
+static bool skip_src_rows(jpeg_decompress_struct* cinfo, void* buffer, int count) {
     for (int i = 0; i < count; i++) {
         JSAMPLE* rowptr = (JSAMPLE*)buffer;
         int row_count = jpeg_read_scanlines(cinfo, &rowptr, 1);
-        if (row_count != 1) {
+        if (1 != row_count) {
             return false;
         }
     }
     return true;
 }
 
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+static bool skip_src_rows_tile(jpeg_decompress_struct* cinfo,
+                               huffman_index *index, void* buffer, int count) {
+    for (int i = 0; i < count; i++) {
+        JSAMPLE* rowptr = (JSAMPLE*)buffer;
+        int row_count = jpeg_read_tile_scanline(cinfo, index, &rowptr);
+        if (1 != row_count) {
+            return false;
+        }
+    }
+    return true;
+}
+#endif
+
 // This guy exists just to aid in debugging, as it allows debuggers to just
 // set a break-point in one place to see all error exists.
 static bool return_false(const jpeg_decompress_struct& cinfo,
                          const SkBitmap& bm, const char msg[]) {
-#if 0
+#ifdef SK_DEBUG
     SkDebugf("libjpeg error %d <%s> from %s [%d %d]", cinfo.err->msg_code,
              cinfo.err->jpeg_message_table[cinfo.err->msg_code], msg,
              bm.width(), bm.height());
@@ -168,34 +231,31 @@
 
 bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
 #ifdef TIME_DECODE
-    AutoTimeMillis atm("JPEG Decode");
+    SkAutoTime atm("JPEG Decode");
 #endif
 
-    SkAutoMalloc  srcStorage;
     JPEGAutoClean autoClean;
 
     jpeg_decompress_struct  cinfo;
-    skjpeg_error_mgr        sk_err;
-    skjpeg_source_mgr       sk_stream(stream, this, false);
+    skjpeg_error_mgr        errorManager;
+    skjpeg_source_mgr       srcManager(stream, this, false);
 
-    cinfo.err = jpeg_std_error(&sk_err);
-    sk_err.error_exit = skjpeg_error_exit;
+    cinfo.err = jpeg_std_error(&errorManager);
+    errorManager.error_exit = skjpeg_error_exit;
 
     // All objects need to be instantiated before this setjmp call so that
     // they will be cleaned up properly if an error occurs.
-    if (setjmp(sk_err.fJmpBuf)) {
+    if (setjmp(errorManager.fJmpBuf)) {
         return return_false(cinfo, *bm, "setjmp");
     }
 
     jpeg_create_decompress(&cinfo);
     autoClean.set(&cinfo);
 
-#ifdef SK_BUILD_FOR_ANDROID
     overwrite_mem_buffer_size(&cinfo);
-#endif
 
     //jpeg_stdio_src(&cinfo, file);
-    cinfo.src = &sk_stream;
+    cinfo.src = &srcManager;
 
     int status = jpeg_read_header(&cinfo, true);
     if (status != JPEG_HEADER_OK) {
@@ -208,7 +268,12 @@
     */
     int sampleSize = this->getSampleSize();
 
-    cinfo.dct_method = JDCT_IFAST;
+    if (this->getPreferQualityOverSpeed()) {
+        cinfo.dct_method = JDCT_ISLOW;
+    } else {
+        cinfo.dct_method = JDCT_IFAST;
+    }
+
     cinfo.scale_num = 1;
     cinfo.scale_denom = sampleSize;
 
@@ -250,7 +315,7 @@
     }
 #endif
 
-    if (sampleSize == 1 && mode == SkImageDecoder::kDecodeBounds_Mode) {
+    if (1 == sampleSize && SkImageDecoder::kDecodeBounds_Mode == mode) {
         bm->setConfig(config, cinfo.image_width, cinfo.image_height);
         bm->setIsOpaque(true);
         return true;
@@ -270,8 +335,7 @@
             to complete the setup. However, output dimensions seem to get
             computed very early, which is why this special check can pay off.
          */
-        if (SkImageDecoder::kDecodeBounds_Mode == mode &&
-                valid_output_dimensions(cinfo)) {
+        if (SkImageDecoder::kDecodeBounds_Mode == mode && valid_output_dimensions(cinfo)) {
             SkScaledBitmapSampler smpl(cinfo.output_width, cinfo.output_height,
                                        recompute_sampleSize(sampleSize, cinfo));
             bm->setConfig(config, smpl.scaledWidth(), smpl.scaledHeight());
@@ -284,11 +348,38 @@
     sampleSize = recompute_sampleSize(sampleSize, cinfo);
 
     // should we allow the Chooser (if present) to pick a config for us???
-    if (!this->chooseFromOneChoice(config, cinfo.output_width,
-                                   cinfo.output_height)) {
+    if (!this->chooseFromOneChoice(config, cinfo.output_width, cinfo.output_height)) {
         return return_false(cinfo, *bm, "chooseFromOneChoice");
     }
 
+    SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height, sampleSize);
+
+    bm->lockPixels();
+    JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels();
+    bm->unlockPixels();
+    bool reuseBitmap = (rowptr != NULL);
+
+    if (reuseBitmap) {
+        if (sampler.scaledWidth() != bm->width() ||
+            sampler.scaledHeight() != bm->height()) {
+            // Dimensions must match
+            return false;
+        } else if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+            return true;
+        }
+    } else {
+        bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
+        bm->setIsOpaque(true);
+        if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+            return true;
+        }
+        if (!this->allocPixelRef(bm, NULL)) {
+            return return_false(cinfo, *bm, "allocPixelRef");
+        }
+    }
+
+    SkAutoLockPixels alp(*bm);
+
 #ifdef ANDROID_RGB
     /* short-circuit the SkScaledBitmapSampler when possible, as this gives
        a significant performance boost.
@@ -299,16 +390,7 @@
         (config == SkBitmap::kRGB_565_Config &&
                 cinfo.out_color_space == JCS_RGB_565)))
     {
-        bm->setConfig(config, cinfo.output_width, cinfo.output_height);
-        bm->setIsOpaque(true);
-        if (SkImageDecoder::kDecodeBounds_Mode == mode) {
-            return true;
-        }
-        if (!this->allocPixelRef(bm, NULL)) {
-            return return_false(cinfo, *bm, "allocPixelRef");
-        }
-        SkAutoLockPixels alp(*bm);
-        JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels();
+        rowptr = (JSAMPLE*)bm->getPixels();
         INT32 const bpr =  bm->rowBytes();
 
         while (cinfo.output_scanline < cinfo.output_height) {
@@ -323,6 +405,9 @@
             }
             rowptr += bpr;
         }
+        if (reuseBitmap) {
+            bm->notifyPixelsChanged();
+        }
         jpeg_finish_decompress(&cinfo);
         return true;
     }
@@ -348,27 +433,13 @@
         return return_false(cinfo, *bm, "jpeg colorspace");
     }
 
-    SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height,
-                                  sampleSize);
-
-    bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
-    // jpegs are always opaque (i.e. have no per-pixel alpha)
-    bm->setIsOpaque(true);
-
-    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
-        return true;
-    }
-    if (!this->allocPixelRef(bm, NULL)) {
-        return return_false(cinfo, *bm, "allocPixelRef");
-    }
-
-    SkAutoLockPixels alp(*bm);
     if (!sampler.begin(bm, sc, this->getDitherImage())) {
         return return_false(cinfo, *bm, "sampler.begin");
     }
 
     // The CMYK work-around relies on 4 components per pixel here
-    uint8_t* srcRow = (uint8_t*)srcStorage.reset(cinfo.output_width * 4);
+    SkAutoMalloc srcStorage(cinfo.output_width * 4);
+    uint8_t* srcRow = (uint8_t*)srcStorage.get();
 
     //  Possibly skip initial rows [sampler.srcY0]
     if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) {
@@ -406,12 +477,279 @@
                        cinfo.output_height - cinfo.output_scanline)) {
         return return_false(cinfo, *bm, "skip rows");
     }
+    if (reuseBitmap) {
+        bm->notifyPixelsChanged();
+    }
     jpeg_finish_decompress(&cinfo);
 
-//    SkDebugf("------------------- bm2 size %d [%d %d] %d\n", bm->getSize(), bm->width(), bm->height(), bm->config());
     return true;
 }
 
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+bool SkJPEGImageDecoder::onBuildTileIndex(SkStream* stream, int *width, int *height) {
+
+    SkJPEGImageIndex* imageIndex = SkNEW_ARGS(SkJPEGImageIndex, (stream, this));
+    jpeg_decompress_struct* cinfo = imageIndex->cinfo();
+    huffman_index* huffmanIndex = imageIndex->huffmanIndex();
+
+    skjpeg_error_mgr sk_err;
+    cinfo->err = jpeg_std_error(&sk_err);
+    sk_err.error_exit = skjpeg_error_exit;
+
+    // All objects need to be instantiated before this setjmp call so that
+    // they will be cleaned up properly if an error occurs.
+    if (setjmp(sk_err.fJmpBuf)) {
+        return false;
+    }
+
+    // create the cinfo used to create/build the huffmanIndex
+    imageIndex->initializeInfo();
+    cinfo->do_fancy_upsampling = 0;
+    cinfo->do_block_smoothing = 0;
+
+    int status = jpeg_read_header(cinfo, true);
+    if (JPEG_HEADER_OK != status) {
+        SkDELETE(imageIndex);
+        return false;
+    }
+
+    jpeg_create_huffman_index(cinfo, huffmanIndex);
+    cinfo->scale_num = 1;
+    cinfo->scale_denom = 1;
+    if (!jpeg_build_huffman_index(cinfo, huffmanIndex)) {
+        SkDELETE(imageIndex);
+        return false;
+    }
+
+    // destroy the cinfo used to create/build the huffman index
+    jpeg_destroy_decompress(cinfo);
+
+    // Init decoder to image decode mode
+    imageIndex->initializeInfo();
+
+    status = jpeg_read_header(cinfo, true);
+    if (JPEG_HEADER_OK != status) {
+        SkDELETE(imageIndex);
+        return false;
+    }
+
+    cinfo->out_color_space = JCS_RGBA_8888;
+    cinfo->do_fancy_upsampling = 0;
+    cinfo->do_block_smoothing = 0;
+
+    // instead of jpeg_start_decompress() we start a tiled decompress
+    jpeg_start_tile_decompress(cinfo);
+
+    cinfo->scale_num = 1;
+    *height = cinfo->output_height;
+    *width = cinfo->output_width;
+    fImageWidth = *width;
+    fImageHeight = *height;
+
+    SkDELETE(fImageIndex);
+    fImageIndex = imageIndex;
+
+    return true;
+}
+
+bool SkJPEGImageDecoder::onDecodeRegion(SkBitmap* bm, const SkIRect& region) {
+    if (NULL == fImageIndex) {
+        return false;
+    }
+    jpeg_decompress_struct* cinfo = fImageIndex->cinfo();
+
+    SkIRect rect = SkIRect::MakeWH(fImageWidth, fImageHeight);
+    if (!rect.intersect(region)) {
+        // If the requested region is entirely outside the image return false
+        return false;
+    }
+
+
+    skjpeg_error_mgr errorManager;
+    cinfo->err = jpeg_std_error(&errorManager);
+    errorManager.error_exit = skjpeg_error_exit;
+    if (setjmp(errorManager.fJmpBuf)) {
+        return false;
+    }
+
+    int requestedSampleSize = this->getSampleSize();
+    cinfo->scale_denom = requestedSampleSize;
+
+    if (this->getPreferQualityOverSpeed()) {
+        cinfo->dct_method = JDCT_ISLOW;
+    } else {
+        cinfo->dct_method = JDCT_IFAST;
+    }
+
+    SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, false);
+    if (config != SkBitmap::kARGB_8888_Config &&
+        config != SkBitmap::kARGB_4444_Config &&
+        config != SkBitmap::kRGB_565_Config) {
+        config = SkBitmap::kARGB_8888_Config;
+    }
+
+    /* default format is RGB */
+    cinfo->out_color_space = JCS_RGB;
+
+#ifdef ANDROID_RGB
+    cinfo->dither_mode = JDITHER_NONE;
+    if (SkBitmap::kARGB_8888_Config == config) {
+        cinfo->out_color_space = JCS_RGBA_8888;
+    } else if (SkBitmap::kRGB_565_Config == config) {
+        cinfo->out_color_space = JCS_RGB_565;
+        if (this->getDitherImage()) {
+            cinfo->dither_mode = JDITHER_ORDERED;
+        }
+    }
+#endif
+
+    int startX = rect.fLeft;
+    int startY = rect.fTop;
+    int width = rect.width();
+    int height = rect.height();
+
+    jpeg_init_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(),
+                                 &startX, &startY, &width, &height);
+    int skiaSampleSize = recompute_sampleSize(requestedSampleSize, *cinfo);
+    int actualSampleSize = skiaSampleSize * (DCTSIZE / cinfo->min_DCT_scaled_size);
+
+    SkScaledBitmapSampler sampler(width, height, skiaSampleSize);
+
+    SkBitmap bitmap;
+    bitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
+    bitmap.setIsOpaque(true);
+
+    // Check ahead of time if the swap(dest, src) is possible or not.
+    // If yes, then we will stick to AllocPixelRef since it's cheaper with the
+    // swap happening. If no, then we will use alloc to allocate pixels to
+    // prevent garbage collection.
+    int w = rect.width() / actualSampleSize;
+    int h = rect.height() / actualSampleSize;
+    bool swapOnly = (rect == region) && bm->isNull() &&
+                    (w == bitmap.width()) && (h == bitmap.height()) &&
+                    ((startX - rect.x()) / actualSampleSize == 0) &&
+                    ((startY - rect.y()) / actualSampleSize == 0);
+    if (swapOnly) {
+        if (!this->allocPixelRef(&bitmap, NULL)) {
+            return return_false(*cinfo, bitmap, "allocPixelRef");
+        }
+    } else {
+        if (!bitmap.allocPixels()) {
+            return return_false(*cinfo, bitmap, "allocPixels");
+        }
+    }
+
+    SkAutoLockPixels alp(bitmap);
+
+#ifdef ANDROID_RGB
+    /* short-circuit the SkScaledBitmapSampler when possible, as this gives
+       a significant performance boost.
+    */
+    if (skiaSampleSize == 1 &&
+        ((config == SkBitmap::kARGB_8888_Config &&
+                cinfo->out_color_space == JCS_RGBA_8888) ||
+        (config == SkBitmap::kRGB_565_Config &&
+                cinfo->out_color_space == JCS_RGB_565)))
+    {
+        JSAMPLE* rowptr = (JSAMPLE*)bitmap.getPixels();
+        INT32 const bpr = bitmap.rowBytes();
+        int rowTotalCount = 0;
+
+        while (rowTotalCount < height) {
+            int rowCount = jpeg_read_tile_scanline(cinfo,
+                                                   fImageIndex->huffmanIndex(),
+                                                   &rowptr);
+            // if row_count == 0, then we didn't get a scanline, so abort.
+            // if we supported partial images, we might return true in this case
+            if (0 == rowCount) {
+                return return_false(*cinfo, bitmap, "read_scanlines");
+            }
+            if (this->shouldCancelDecode()) {
+                return return_false(*cinfo, bitmap, "shouldCancelDecode");
+            }
+            rowTotalCount += rowCount;
+            rowptr += bpr;
+        }
+
+        if (swapOnly) {
+            bm->swap(bitmap);
+        } else {
+            cropBitmap(bm, &bitmap, actualSampleSize, region.x(), region.y(),
+                       region.width(), region.height(), startX, startY);
+        }
+        return true;
+    }
+#endif
+
+    // check for supported formats
+    SkScaledBitmapSampler::SrcConfig sc;
+    if (JCS_CMYK == cinfo->out_color_space) {
+        // In this case we will manually convert the CMYK values to RGB
+        sc = SkScaledBitmapSampler::kRGBX;
+    } else if (3 == cinfo->out_color_components && JCS_RGB == cinfo->out_color_space) {
+        sc = SkScaledBitmapSampler::kRGB;
+#ifdef ANDROID_RGB
+    } else if (JCS_RGBA_8888 == cinfo->out_color_space) {
+        sc = SkScaledBitmapSampler::kRGBX;
+    } else if (JCS_RGB_565 == cinfo->out_color_space) {
+        sc = SkScaledBitmapSampler::kRGB_565;
+#endif
+    } else if (1 == cinfo->out_color_components &&
+               JCS_GRAYSCALE == cinfo->out_color_space) {
+        sc = SkScaledBitmapSampler::kGray;
+    } else {
+        return return_false(*cinfo, *bm, "jpeg colorspace");
+    }
+
+    if (!sampler.begin(&bitmap, sc, this->getDitherImage())) {
+        return return_false(*cinfo, bitmap, "sampler.begin");
+    }
+
+    // The CMYK work-around relies on 4 components per pixel here
+    SkAutoMalloc  srcStorage(width * 4);
+    uint8_t* srcRow = (uint8_t*)srcStorage.get();
+
+    //  Possibly skip initial rows [sampler.srcY0]
+    if (!skip_src_rows_tile(cinfo, fImageIndex->huffmanIndex(), srcRow, sampler.srcY0())) {
+        return return_false(*cinfo, bitmap, "skip rows");
+    }
+
+    // now loop through scanlines until y == bitmap->height() - 1
+    for (int y = 0;; y++) {
+        JSAMPLE* rowptr = (JSAMPLE*)srcRow;
+        int row_count = jpeg_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(), &rowptr);
+        if (0 == row_count) {
+            return return_false(*cinfo, bitmap, "read_scanlines");
+        }
+        if (this->shouldCancelDecode()) {
+            return return_false(*cinfo, bitmap, "shouldCancelDecode");
+        }
+
+        if (JCS_CMYK == cinfo->out_color_space) {
+            convert_CMYK_to_RGB(srcRow, width);
+        }
+
+        sampler.next(srcRow);
+        if (bitmap.height() - 1 == y) {
+            // we're done
+            break;
+        }
+
+        if (!skip_src_rows_tile(cinfo, fImageIndex->huffmanIndex(), srcRow,
+                                sampler.srcDY() - 1)) {
+            return return_false(*cinfo, bitmap, "skip rows");
+        }
+    }
+    if (swapOnly) {
+        bm->swap(bitmap);
+    } else {
+        cropBitmap(bm, &bitmap, actualSampleSize, region.x(), region.y(),
+                   region.width(), region.height(), startX, startY);
+    }
+    return true;
+}
+#endif
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #include "SkColorPriv.h"
@@ -582,7 +920,7 @@
 protected:
     virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) {
 #ifdef TIME_ENCODE
-        AutoTimeMillis atm("JPEG Encode");
+        SkAutoTime atm("JPEG Encode");
 #endif
 
         const WriteScanline writer = ChooseWriter(bm);
diff --git a/src/images/SkJpegUtility.cpp b/src/images/SkJpegUtility.cpp
index 19db018..89a0472 100644
--- a/src/images/SkJpegUtility.cpp
+++ b/src/images/SkJpegUtility.cpp
@@ -9,14 +9,41 @@
 
 #include "SkJpegUtility.h"
 
+// Uncomment to enable the code path used by the Android framework with their
+// custom image decoders.
+//#if defined(SK_BUILD_FOR_ANDROID) && defined(SK_DEBUG)
+//  #define SK_BUILD_FOR_ANDROID_FRAMEWORK
+//#endif
+
 /////////////////////////////////////////////////////////////////////
 static void sk_init_source(j_decompress_ptr cinfo) {
     skjpeg_source_mgr*  src = (skjpeg_source_mgr*)cinfo->src;
     src->next_input_byte = (const JOCTET*)src->fBuffer;
     src->bytes_in_buffer = 0;
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+    src->current_offset = 0;
+#endif
     src->fStream->rewind();
 }
 
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+static boolean sk_seek_input_data(j_decompress_ptr cinfo, long byte_offset) {
+    skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
+
+    if (byte_offset > src->current_offset) {
+        (void)src->fStream->skip(byte_offset - src->current_offset);
+    } else {
+        src->fStream->rewind();
+        (void)src->fStream->skip(byte_offset);
+    }
+
+    src->current_offset = byte_offset;
+    src->next_input_byte = (const JOCTET*)src->fBuffer;
+    src->bytes_in_buffer = 0;
+    return true;
+}
+#endif
+
 static boolean sk_fill_input_buffer(j_decompress_ptr cinfo) {
     skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
     if (src->fDecoder != NULL && src->fDecoder->shouldCancelDecode()) {
@@ -29,6 +56,9 @@
         return FALSE;
     }
 
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+    src->current_offset += bytes;
+#endif
     src->next_input_byte = (const JOCTET*)src->fBuffer;
     src->bytes_in_buffer = bytes;
     return TRUE;
@@ -46,6 +76,9 @@
                 cinfo->err->error_exit((j_common_ptr)cinfo);
                 return;
             }
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+            src->current_offset += bytes;
+#endif
             bytesToSkip -= bytes;
         }
         src->next_input_byte = (const JOCTET*)src->fBuffer;
@@ -74,40 +107,11 @@
 static void sk_term_source(j_decompress_ptr /*cinfo*/) {}
 
 
-#if 0 // UNUSED
-static void skmem_init_source(j_decompress_ptr cinfo) {
-    skjpeg_source_mgr*  src = (skjpeg_source_mgr*)cinfo->src;
-    src->next_input_byte = (const JOCTET*)src->fMemoryBase;
-    src->bytes_in_buffer = src->fMemoryBaseSize;
-}
-
-static boolean skmem_fill_input_buffer(j_decompress_ptr cinfo) {
-    SkDebugf("xxxxxxxxxxxxxx skmem_fill_input_buffer called\n");
-    return FALSE;
-}
-
-static void skmem_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
-    skjpeg_source_mgr*  src = (skjpeg_source_mgr*)cinfo->src;
-//    SkDebugf("xxxxxxxxxxxxxx skmem_skip_input_data called %d\n", num_bytes);
-    src->next_input_byte = (const JOCTET*)((const char*)src->next_input_byte + num_bytes);
-    src->bytes_in_buffer -= num_bytes;
-}
-
-static boolean skmem_resync_to_restart(j_decompress_ptr cinfo, int desired) {
-    SkDebugf("xxxxxxxxxxxxxx skmem_resync_to_restart called\n");
-    return TRUE;
-}
-
-static void skmem_term_source(j_decompress_ptr /*cinfo*/) {}
-#endif
-
-
 ///////////////////////////////////////////////////////////////////////////////
 
 skjpeg_source_mgr::skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder,
                                      bool ownStream) : fStream(stream) {
     fDecoder = decoder;
-    // const void* baseAddr = stream->getMemoryBase();
     fMemoryBase = NULL;
     fUnrefStream = ownStream;
     fMemoryBaseSize = 0;
@@ -117,6 +121,9 @@
     skip_input_data = sk_skip_input_data;
     resync_to_restart = sk_resync_to_restart;
     term_source = sk_term_source;
+#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
+    seek_input_data = sk_seek_input_data;
+#endif
 //    SkDebugf("**************** use memorybase %p %d\n", fMemoryBase, fMemoryBaseSize);
 }
 
diff --git a/include/images/SkJpegUtility.h b/src/images/SkJpegUtility.h
similarity index 100%
rename from include/images/SkJpegUtility.h
rename to src/images/SkJpegUtility.h