Do JPEG tile-based decoding.

Change-Id: I795129d55a0a7da90b4d604a902066868933d2a9
diff --git a/Android.mk b/Android.mk
index a63a0f1..6f8162c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -78,6 +78,7 @@
 	src/images/SkImageRef_GlobalPool.cpp \
 	src/images/SkImageRefPool.cpp \
 	src/images/SkJpegUtility.cpp \
+	src/images/SkLargeBitmap.cpp \
 	src/images/SkMovie.cpp \
 	src/images/SkMovie_gif.cpp \
 	src/images/SkPageFlipper.cpp \
diff --git a/include/images/SkImageDecoder.h b/include/images/SkImageDecoder.h
index f8a941f..be1046b 100644
--- a/include/images/SkImageDecoder.h
+++ b/include/images/SkImageDecoder.h
@@ -18,10 +18,17 @@
 #define SkImageDecoder_DEFINED
 
 #include "SkBitmap.h"
+#include "SkRect.h"
 #include "SkRefCnt.h"
 
 class SkStream;
 
+class SkVMMemoryReporter : public SkRefCnt {
+public:
+    virtual ~SkVMMemoryReporter() {}
+    virtual bool reportMemory(size_t memorySize);
+};
+
 /** \class SkImageDecoder
 
     Base class for decoding compressed images into a SkBitmap
@@ -30,6 +37,7 @@
 public:
     virtual ~SkImageDecoder();
 
+    // Should be consistent with kFormatName
     enum Format {
         kUnknown_Format,
         kBMP_Format,
@@ -42,10 +50,21 @@
         kLastKnownFormat = kWBMP_Format
     };
 
+    /** Contains the image format name.
+     *  This should be consistent with Format.
+     *
+     *  The format name gives a more meaningful error message than enum.
+     */
+    static const char *kFormatName[7];
+
     /** Return the compressed data's format (see Format enum)
     */
     virtual Format getFormat() const;
 
+    /** Return the compressed data's format name.
+    */
+    const char* getFormatName() const { return kFormatName[getFormat()]; }
+
     /** Returns true if the decoder should try to dither the resulting image.
         The default setting is true.
     */
@@ -118,6 +137,7 @@
 
     SkBitmap::Allocator* getAllocator() const { return fAllocator; }
     SkBitmap::Allocator* setAllocator(SkBitmap::Allocator*);
+    SkVMMemoryReporter* setReporter(SkVMMemoryReporter*);
 
     // sample-size, if set to > 1, tells the decoder to return a smaller than
     // original bitmap, sampling 1 pixel for every size pixels. e.g. if sample
@@ -176,6 +196,29 @@
         return this->decode(stream, bitmap, SkBitmap::kNo_Config, mode);
     }
 
+    /**
+     * Given a stream, build an index for doing tile-based decode.
+     * The built index will be saved in the decoder, and the image size will
+     * be returned in width and height.
+     *
+     * Return true for success or false on failure.
+     */
+    virtual bool buildTileIndex(SkStream*,
+                                int *width, int *height, bool isShareable) {
+        return false;
+    }
+
+    /**
+     * Decode a rectangle region in the image specified by rect.
+     * The method can only be called after buildTileIndex().
+     *
+     * Return true for success.
+     * Return false if the index is never built or failing in decoding.
+     */
+    virtual bool decodeRegion(SkBitmap* bitmap, SkIRect rect,
+                              SkBitmap::Config pref);
+
+
     /** Given a stream, this will try to find an appropriate decoder object.
         If none is found, the method returns NULL.
     */
@@ -264,6 +307,11 @@
     // must be overridden in subclasses. This guy is called by decode(...)
     virtual bool onDecode(SkStream*, SkBitmap* bitmap, Mode) = 0;
 
+    // must be overridden in subclasses. This guy is called by decodeRegion(...)
+    virtual bool onDecodeRegion(SkBitmap* bitmap, SkIRect rect) {
+        return false;
+    }
+
     /** Can be queried from within onDecode, to see if the user (possibly in
         a different thread) has requested the decode to cancel. If this returns
         true, your onDecode() should stop and return false.
@@ -304,6 +352,8 @@
      */
     SkBitmap::Config getPrefConfig(SrcDepth, bool hasAlpha) const;
 
+    SkVMMemoryReporter*      fReporter;
+
 private:
     Peeker*                 fPeeker;
     Chooser*                fChooser;
diff --git a/include/images/SkJpegUtility.h b/include/images/SkJpegUtility.h
index cc9d246..5dd789c9 100644
--- a/include/images/SkJpegUtility.h
+++ b/include/images/SkJpegUtility.h
@@ -41,11 +41,13 @@
 /* Our source struct for directing jpeg to our stream object.
 */
 struct skjpeg_source_mgr : jpeg_source_mgr {
-    skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder);
+    skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder, bool copyStream, bool ownStream);
+    ~skjpeg_source_mgr();
 
     SkStream*   fStream;
-    const void* fMemoryBase;
+    void*       fMemoryBase;
     size_t      fMemoryBaseSize;
+    bool        fUnrefStream;
     SkImageDecoder* fDecoder;
     enum {
         kBufferSize = 1024
diff --git a/include/images/SkLargeBitmap.h b/include/images/SkLargeBitmap.h
new file mode 100644
index 0000000..0d19f32
--- /dev/null
+++ b/include/images/SkLargeBitmap.h
@@ -0,0 +1,33 @@
+#ifndef SkLargeBitmap_DEFINED
+#define SkLargeBitmap_DEFINED
+
+#include "SkBitmap.h"
+#include "SkRect.h"
+#include "SkImageDecoder.h"
+
+class SkLargeBitmap {
+public:
+    SkLargeBitmap(SkImageDecoder *decoder, int width, int height) {
+        fDecoder = decoder;
+        fWidth = width;
+        fHeight = height;
+    }
+    virtual ~SkLargeBitmap() {
+        delete fDecoder;
+    }
+
+    virtual bool decodeRegion(SkBitmap* bitmap, SkIRect rect,
+                              SkBitmap::Config pref, int sampleSize);
+
+    virtual int getWidth() { return fWidth; }
+    virtual int getHeight() { return fHeight; }
+
+    virtual SkImageDecoder* getDecoder() { return fDecoder; }
+
+private:
+    SkImageDecoder *fDecoder;
+    int fWidth;
+    int fHeight;
+};
+
+#endif
diff --git a/src/images/SkImageDecoder.cpp b/src/images/SkImageDecoder.cpp
index 768d671..5191117 100644
--- a/src/images/SkImageDecoder.cpp
+++ b/src/images/SkImageDecoder.cpp
@@ -20,6 +20,17 @@
 #include "SkPixelRef.h"
 #include "SkStream.h"
 #include "SkTemplates.h"
+#include "SkCanvas.h"
+
+const char *SkImageDecoder::kFormatName[] = {
+    "Unknown Format",
+    "BMP",
+    "GIF",
+    "ICO",
+    "JPEG",
+    "PNG",
+    "WBMP",
+};
 
 static SkBitmap::Config gDeviceConfig = SkBitmap::kNo_Config;
 
@@ -36,8 +47,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 SkImageDecoder::SkImageDecoder()
-    : fPeeker(NULL), fChooser(NULL), fAllocator(NULL), fSampleSize(1),
-      fDefaultPref(SkBitmap::kNo_Config), fDitherImage(true),
+    : fReporter(NULL), fPeeker(NULL), fChooser(NULL), fAllocator(NULL),
+      fSampleSize(1), fDefaultPref(SkBitmap::kNo_Config), fDitherImage(true),
       fUsePrefTable(false) {
 }
 
@@ -45,6 +56,7 @@
     fPeeker->safeUnref();
     fChooser->safeUnref();
     fAllocator->safeUnref();
+    fReporter->safeUnref();
 }
 
 SkImageDecoder::Format SkImageDecoder::getFormat() const {
@@ -66,6 +78,11 @@
     return alloc;
 }
 
+SkVMMemoryReporter* SkImageDecoder::setReporter(SkVMMemoryReporter* reporter) {
+    SkRefCnt_SafeAssign(fReporter, reporter);
+    return reporter;
+}
+
 void SkImageDecoder::setSampleSize(int size) {
     if (size < 1) {
         size = 1;
@@ -150,6 +167,24 @@
     return true;
 }
 
+bool SkImageDecoder::decodeRegion(SkBitmap* bm, SkIRect rect,
+                                  SkBitmap::Config pref) {
+    // pass a temporary bitmap, so that if we return false, we are assured of
+    // leaving the caller's bitmap untouched.
+    SkBitmap    tmp;
+
+    // we reset this to false before calling onDecodeRegion
+    fShouldCancelDecode = false;
+    // assign this, for use by getPrefConfig(), in case fUsePrefTable is false
+    fDefaultPref = pref;
+
+    if (!this->onDecodeRegion(&tmp, rect)) {
+        return false;
+    }
+    bm->swap(tmp);
+    return true;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 bool SkImageDecoder::DecodeFile(const char file[], SkBitmap* bm,
diff --git a/src/images/SkImageDecoder_libjpeg.cpp b/src/images/SkImageDecoder_libjpeg.cpp
index ed523bb..2318a37 100644
--- a/src/images/SkImageDecoder_libjpeg.cpp
+++ b/src/images/SkImageDecoder_libjpeg.cpp
@@ -23,6 +23,8 @@
 #include "SkStream.h"
 #include "SkTemplates.h"
 #include "SkUtils.h"
+#include "SkRect.h"
+#include "SkCanvas.h"
 
 #include <stdio.h>
 extern "C" {
@@ -48,14 +50,44 @@
 //////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////
 
+class SkJPEGImageIndex {
+public:
+    SkJPEGImageIndex() {}
+    virtual ~SkJPEGImageIndex() {
+        jpeg_destroy_huffman_index(index);
+        delete cinfo->src;
+        jpeg_finish_decompress(cinfo);
+        jpeg_destroy_decompress(cinfo);
+        free(cinfo);
+    }
+    jpeg_decompress_struct *cinfo;
+    huffman_index *index;
+};
+
+
 class SkJPEGImageDecoder : public SkImageDecoder {
 public:
+    SkJPEGImageDecoder() {
+        index = NULL;
+    }
+    ~SkJPEGImageDecoder() {
+        if (index)
+            delete index;
+    }
     virtual Format getFormat() const {
         return kJPEG_Format;
     }
+    virtual bool buildTileIndex(SkStream *stream,
+                                int *width, int *height, bool isShareable);
 
 protected:
+    virtual bool onDecodeRegion(SkBitmap* bitmap, SkIRect rect);
     virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
+    virtual void cropBitmap(SkBitmap *dest, SkBitmap *src, int sampleSize,
+                            int srcX, int srcY, int width, int height,
+                            int destX, int destY);
+private:
+    SkJPEGImageIndex *index;
 };
 
 //////////////////////////////////////////////////////////////////////////
@@ -141,6 +173,21 @@
     return true;
 }
 
+static bool skip_src_rows_tile(jpeg_decompress_struct* cinfo,
+                          huffman_index *index, void* buffer,
+                          int startX, int startY, int width, int height,
+                          int count) {
+    for (int i = 0; i < count; i++) {
+        JSAMPLE* rowptr = (JSAMPLE*)buffer;
+        int row_count = jpeg_read_tile_scanline(cinfo, index, &rowptr,
+                                              startX, startY, width, height);
+        if (row_count != 1) {
+            return false;
+        }
+    }
+    return true;
+}
+
 // 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,
@@ -163,7 +210,7 @@
 
     jpeg_decompress_struct  cinfo;
     skjpeg_error_mgr        sk_err;
-    skjpeg_source_mgr       sk_stream(stream, this);
+    skjpeg_source_mgr       sk_stream(stream, this, false, false);
 
     cinfo.err = jpeg_std_error(&sk_err);
     sk_err.error_exit = skjpeg_error_exit;
@@ -388,6 +435,278 @@
     return true;
 }
 
+bool SkJPEGImageDecoder::buildTileIndex(SkStream* stream,
+                                        int *width, int *height,
+                                        bool isShareable) {
+    SkAutoMalloc  srcStorage;
+    SkJPEGImageIndex *index = new SkJPEGImageIndex;
+
+    jpeg_decompress_struct  *cinfo = (jpeg_decompress_struct*)
+                                        malloc(sizeof(jpeg_decompress_struct));
+    skjpeg_error_mgr        sk_err;
+    skjpeg_source_mgr       *sk_stream =
+        new skjpeg_source_mgr(stream, this, !isShareable, true);
+    if (cinfo == NULL || sk_stream == NULL) {
+        return false;
+    }
+
+    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;
+    }
+
+    jpeg_create_decompress(cinfo);
+    cinfo->do_fancy_upsampling = 0;
+    cinfo->do_block_smoothing = 0;
+
+#ifdef ANDROID
+    overwrite_mem_buffer_size(cinfo);
+#endif
+
+    cinfo->src = sk_stream;
+    int status = jpeg_read_header(cinfo, true);
+    if (status != JPEG_HEADER_OK) {
+        return false;
+    }
+    index->index = (huffman_index*)malloc(sizeof(huffman_index));
+    jpeg_create_huffman_index(cinfo, index->index);
+
+    cinfo->dct_method = JDCT_IFAST;
+    cinfo->scale_num = 1;
+    cinfo->scale_denom = 1;
+    if (!jpeg_build_huffman_index(cinfo, index->index)) {
+        return false;
+    }
+    if (fReporter)
+        fReporter->reportMemory(index->index->mem_used);
+    jpeg_destroy_decompress(cinfo);
+
+
+    // Init decoder to image decode mode
+    jpeg_create_decompress(cinfo);
+
+#ifdef ANDROID
+    overwrite_mem_buffer_size(cinfo);
+#endif
+
+    cinfo->src = sk_stream;
+    status = jpeg_read_header(cinfo,true);
+    if (status != JPEG_HEADER_OK) {
+        return false;
+    }
+    cinfo->out_color_space = JCS_RGBA_8888;
+    cinfo->do_fancy_upsampling = 0;
+    cinfo->do_block_smoothing = 0;
+    //jpeg_start_decompress(cinfo);
+    jpeg_start_tile_decompress(cinfo);
+
+    cinfo->dct_method = JDCT_IFAST;
+    cinfo->scale_num = 1;
+    index->cinfo = cinfo;
+    *height = cinfo->output_height;
+    *width = cinfo->output_width;
+
+    this->index = index;
+    return true;
+}
+
+/*
+ * Crop a rectangle from the src Bitmap to the dest Bitmap. src and dest are
+ * both sampled by sampleSize from an original Bitmap.
+ *
+ * @param dest the destination Bitmap.
+ * @param src the source Bitmap that is sampled by sampleSize from the original
+ *            Bitmap.
+ * @param sampleSize the sample size that src is sampled from the original Bitmap.
+ * @param (srcX, srcY) the upper-left point of the src Btimap in terms of
+ *                     the coordinate in the original Bitmap.
+ * @param (width, height) the width and height of the unsampled dest.
+ * @param (destX, destY) the upper-left point of the dest Bitmap in terms of
+ *                       the coordinate in the original Bitmap.
+ */
+void SkJPEGImageDecoder::cropBitmap(SkBitmap *dest, SkBitmap *src,
+                                    int sampleSize, int srcX, int srcY,
+                                    int width, int height, int destX, int destY)
+{
+    int w = width / sampleSize;
+    int h = height / sampleSize;
+    if (w == src->width() && h == src->height() &&
+          (destX - srcX) / sampleSize == 0 && (destY - srcY) / sampleSize == 0) {
+        // The output rect is the same as the decode result
+        dest->swap( *src );
+        return;
+    }
+    dest->setConfig(src->getConfig(), w, h);
+    dest->setIsOpaque(true);
+    this->allocPixelRef(dest, NULL);
+
+    SkCanvas canvas(*dest);
+    canvas.drawBitmap(*src, (destX - srcX) / sampleSize,
+                             (destY - srcY) / sampleSize);
+}
+
+bool SkJPEGImageDecoder::onDecodeRegion(SkBitmap* bm, SkIRect region) {
+    if (index == NULL) {
+        return false;
+    }
+    int startX = region.fLeft;
+    int startY = region.fTop;
+    int width = region.width();
+    int height = region.height();
+    jpeg_decompress_struct *cinfo = index->cinfo;
+    SkAutoMalloc  srcStorage;
+    skjpeg_error_mgr        sk_err;
+    cinfo->err = jpeg_std_error(&sk_err);
+    sk_err.error_exit = skjpeg_error_exit;
+    if (setjmp(sk_err.fJmpBuf)) {
+        return false;
+    }
+    int requestedSampleSize = this->getSampleSize();
+    cinfo->scale_denom = requestedSampleSize;
+
+    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;
+    }
+#ifdef ANDROID_RGB
+    cinfo->dither_mode = JDITHER_NONE;
+    if (config == SkBitmap::kARGB_8888_Config) {
+        cinfo->out_color_space = JCS_RGBA_8888;
+    } else if (config == SkBitmap::kRGB_565_Config) {
+        if (requestedSampleSize == 1) {
+            // SkScaledBitmapSampler can't handle RGB_565 yet,
+            // so don't even try.
+            cinfo->out_color_space = JCS_RGB_565;
+            if (this->getDitherImage()) {
+                cinfo->dither_mode = JDITHER_ORDERED;
+            }
+        }
+    }
+#endif
+
+    int oriStartX = startX;
+    int oriStartY = startY;
+    int oriWidth = width;
+    int oriHeight = height;
+    jpeg_init_read_tile_scanline(cinfo, index->index,
+                                 &startX, &startY, &width, &height);
+    int skiaSampleSize = recompute_sampleSize(requestedSampleSize, *cinfo);
+    SkBitmap *bitmap = new SkBitmap;
+    SkAutoTDelete<SkBitmap> adb(bitmap);
+
+    int actualSampleSize = skiaSampleSize * cinfo->image_width / cinfo->output_width;
+
+#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)))
+    {
+        bitmap->setConfig(config, cinfo->output_width, height);
+        bitmap->setIsOpaque(true);
+        if (!this->allocPixelRef(bitmap, NULL)) {
+            return return_false(*cinfo, *bitmap, "allocPixelRef");
+        }
+        SkAutoLockPixels alp(*bitmap);
+        JSAMPLE* rowptr = (JSAMPLE*)bitmap->getPixels();
+        INT32 const bpr = bitmap->rowBytes();
+        int row_total_count = 0;
+
+        while (row_total_count < height) {
+            int row_count = jpeg_read_tile_scanline(cinfo,
+                    index->index, &rowptr, startX, startY, width, height);
+            // 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 == row_count) {
+                return return_false(*cinfo, *bitmap, "read_scanlines");
+            }
+            if (this->shouldCancelDecode()) {
+                return return_false(*cinfo, *bitmap, "shouldCancelDecode");
+            }
+            row_total_count += row_count;
+            rowptr += bpr;
+        }
+        cropBitmap(bm, bitmap, actualSampleSize, oriStartX, oriStartY,
+                   oriWidth, oriHeight, startX, startY);
+        return true;
+    }
+#endif
+    // check for supported formats
+    SkScaledBitmapSampler::SrcConfig sc;
+    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;
+#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");
+    }
+
+    SkScaledBitmapSampler sampler(width, height, skiaSampleSize);
+
+    bitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
+    bitmap->setIsOpaque(true);
+
+    if (!this->allocPixelRef(bitmap, NULL)) {
+        return return_false(*cinfo, *bitmap, "allocPixelRef");
+    }
+
+    SkAutoLockPixels alp(*bitmap);
+    if (!sampler.begin(bitmap, sc, this->getDitherImage())) {
+        return return_false(*cinfo, *bitmap, "sampler.begin");
+    }
+
+    uint8_t* srcRow = (uint8_t*)srcStorage.alloc(width * 4);
+
+    //  Possibly skip initial rows [sampler.srcY0]
+    if (!skip_src_rows_tile(cinfo, index->index, srcRow,
+                            startX, startY, width, height, 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, index->index, &rowptr,
+                                                startX, startY, width, height);
+        if (0 == row_count) {
+            return return_false(*cinfo, *bitmap, "read_scanlines");
+        }
+        if (this->shouldCancelDecode()) {
+            return return_false(*cinfo, *bitmap, "shouldCancelDecode");
+        }
+
+        sampler.next(srcRow);
+        if (bitmap->height() - 1 == y) {
+            // we're done
+            break;
+        }
+
+        if (!skip_src_rows_tile(cinfo, index->index, srcRow,
+                                startX, startY, width, height,
+                                sampler.srcDY() - 1)) {
+            return return_false(*cinfo, *bitmap, "skip rows");
+        }
+    }
+    cropBitmap(bm, bitmap, actualSampleSize, oriStartX, oriStartY,
+               oriWidth, oriHeight, startX, startY);
+    return true;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #include "SkColorPriv.h"
diff --git a/src/images/SkJpegUtility.cpp b/src/images/SkJpegUtility.cpp
index e207592..aaf87db 100644
--- a/src/images/SkJpegUtility.cpp
+++ b/src/images/SkJpegUtility.cpp
@@ -21,6 +21,24 @@
     skjpeg_source_mgr*  src = (skjpeg_source_mgr*)cinfo->src;
     src->next_input_byte = (const JOCTET*)src->fBuffer;
     src->bytes_in_buffer = 0;
+    src->current_offset = 0;
+    src->fStream->rewind();
+}
+
+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;
 }
 
 static boolean sk_fill_input_buffer(j_decompress_ptr cinfo) {
@@ -35,6 +53,7 @@
         return FALSE;
     }
 
+    src->current_offset += bytes;
     src->next_input_byte = (const JOCTET*)src->fBuffer;
     src->bytes_in_buffer = bytes;
     return TRUE;
@@ -52,6 +71,7 @@
                 cinfo->err->error_exit((j_common_ptr)cinfo);
                 return;
             }
+            src->current_offset += bytes;
             bytesToSkip -= bytes;
         }
         src->next_input_byte = (const JOCTET*)src->fBuffer;
@@ -83,7 +103,9 @@
 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->start_input_byte = (const JOCTET*)src->fMemoryBase;
     src->bytes_in_buffer = src->fMemoryBaseSize;
+    src->current_offset = src->fMemoryBaseSize;
 }
 
 static boolean skmem_fill_input_buffer(j_decompress_ptr cinfo) {
@@ -108,18 +130,23 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-skjpeg_source_mgr::skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder) : fStream(stream) {
+skjpeg_source_mgr::skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder,
+                                     bool copyStream, bool ownStream) : fStream(stream) {
     fDecoder = decoder;
     const void* baseAddr = stream->getMemoryBase();
-    if (baseAddr && false) {
-        fMemoryBase = baseAddr;
+    fMemoryBase = NULL;
+    fUnrefStream = ownStream;
+    if (copyStream) {
         fMemoryBaseSize = stream->getLength();
+        fMemoryBase = sk_malloc_throw(fMemoryBaseSize);
+        stream->read(fMemoryBase, fMemoryBaseSize);
 
         init_source = skmem_init_source;
         fill_input_buffer = skmem_fill_input_buffer;
         skip_input_data = skmem_skip_input_data;
         resync_to_restart = skmem_resync_to_restart;
         term_source = skmem_term_source;
+        seek_input_data = NULL;
     } else {
         fMemoryBase = NULL;
         fMemoryBaseSize = 0;
@@ -129,10 +156,20 @@
         skip_input_data = sk_skip_input_data;
         resync_to_restart = sk_resync_to_restart;
         term_source = sk_term_source;
+        seek_input_data = sk_seek_input_data;
     }
 //    SkDebugf("**************** use memorybase %p %d\n", fMemoryBase, fMemoryBaseSize);
 }
 
+skjpeg_source_mgr::~skjpeg_source_mgr() {
+    if (fMemoryBase) {
+        sk_free(fMemoryBase);
+    }
+    if (fUnrefStream) {
+        fStream->unref();
+    }
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static void sk_init_destination(j_compress_ptr cinfo) {
diff --git a/src/images/SkLargeBitmap.cpp b/src/images/SkLargeBitmap.cpp
new file mode 100644
index 0000000..18fb36f
--- /dev/null
+++ b/src/images/SkLargeBitmap.cpp
@@ -0,0 +1,7 @@
+#include "SkLargeBitmap.h"
+
+bool SkLargeBitmap::decodeRegion(SkBitmap* bitmap, SkIRect rect,
+                                 SkBitmap::Config pref, int sampleSize) {
+    fDecoder->setSampleSize(sampleSize);
+    return fDecoder->decodeRegion(bitmap, rect, pref);
+}