diff options
author | djsollen@google.com <djsollen@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-03-20 17:45:27 +0000 |
---|---|---|
committer | djsollen@google.com <djsollen@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-03-20 17:45:27 +0000 |
commit | 113994051b41366a7b25851d05cd56e89866a33b (patch) | |
tree | 373fe1104f36909ce35ad6b4d9afeca26c01e7b4 /src/images | |
parent | 4d9853288bd726f8def70dc13b15ca2ce36326dc (diff) |
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
Diffstat (limited to 'src/images')
-rw-r--r-- | src/images/SkImageDecoder_libjpeg.cpp | 510 | ||||
-rw-r--r-- | src/images/SkJpegUtility.cpp | 65 | ||||
-rw-r--r-- | src/images/SkJpegUtility.h | 66 |
3 files changed, 526 insertions, 115 deletions
diff --git a/src/images/SkImageDecoder_libjpeg.cpp b/src/images/SkImageDecoder_libjpeg.cpp index 867c41cb62..4f32aa9186 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 @@ extern "C" { #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 @@ extern "C" { // 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. + ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -class SkJPEGImageDecoder : public SkImageDecoder { +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: - virtual Format getFormat() const { - return kJPEG_Format; + 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); } -protected: - virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode); -}; + /** + * 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; } -#include "SkTime.h" +#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 AutoTimeMillis { +class SkJPEGImageDecoder : public SkImageDecoder { public: - AutoTimeMillis(const char label[]) : fLabel(label) { - if (!fLabel) { - fLabel = ""; - } - fNow = SkTime::GetMSecs(); + SkJPEGImageDecoder() { + fImageIndex = NULL; + fImageWidth = 0; + fImageHeight = 0; } - ~AutoTimeMillis() { - SkDebugf("---- Time (ms): %s %d\n", fLabel, SkTime::GetMSecs() - fNow); + + virtual ~SkJPEGImageDecoder() { + SkDELETE(fImageIndex); } + + virtual Format getFormat() const { + return kJPEG_Format; + } + +protected: +#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: - const char* fLabel; - SkMSec fNow; + SkJPEGImageIndex* fImageIndex; + int fImageWidth; + int fImageHeight; + + typedef SkImageDecoder INHERITED; }; +////////////////////////////////////////////////////////////////////////// + /* Automatically clean up after throwing an exception */ class JPEGAutoClean { public: @@ -80,24 +148,6 @@ private: 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 @@ static bool valid_output_dimensions(const jpeg_decompress_struct& cinfo) { /* 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 @@ static void convert_CMYK_to_RGB(uint8_t* scanline, unsigned int width) { 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 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { */ 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 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { } #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 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { 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 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { 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 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { (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 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { } rowptr += bpr; } + if (reuseBitmap) { + bm->notifyPixelsChanged(); + } jpeg_finish_decompress(&cinfo); return true; } @@ -348,27 +433,13 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { 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 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { 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 @@ class SkJPEGImageEncoder : public SkImageEncoder { 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 19db0186d2..89a0472380 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 @@ static boolean sk_fill_input_buffer(j_decompress_ptr cinfo) { 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 @@ static void sk_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { 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 boolean sk_resync_to_restart(j_decompress_ptr cinfo, int desired) { 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 @@ skjpeg_source_mgr::skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder, 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/src/images/SkJpegUtility.h b/src/images/SkJpegUtility.h new file mode 100644 index 0000000000..74f1a21042 --- /dev/null +++ b/src/images/SkJpegUtility.h @@ -0,0 +1,66 @@ + +/* + * Copyright 2010 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkJpegUtility_DEFINED +#define SkJpegUtility_DEFINED + +#include "SkImageDecoder.h" +#include "SkStream.h" + +extern "C" { + #include "jpeglib.h" + #include "jerror.h" +} + +#include <setjmp.h> + +/* Our error-handling struct. + * +*/ +struct skjpeg_error_mgr : jpeg_error_mgr { + jmp_buf fJmpBuf; +}; + + +void skjpeg_error_exit(j_common_ptr cinfo); + +/////////////////////////////////////////////////////////////////////////// +/* Our source struct for directing jpeg to our stream object. +*/ +struct skjpeg_source_mgr : jpeg_source_mgr { + skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder, bool ownStream); + ~skjpeg_source_mgr(); + + SkStream* fStream; + void* fMemoryBase; + size_t fMemoryBaseSize; + bool fUnrefStream; + SkImageDecoder* fDecoder; + enum { + kBufferSize = 1024 + }; + char fBuffer[kBufferSize]; +}; + +///////////////////////////////////////////////////////////////////////////// +/* Our destination struct for directing decompressed pixels to our stream + * object. + */ +struct skjpeg_destination_mgr : jpeg_destination_mgr { + skjpeg_destination_mgr(SkWStream* stream); + + SkWStream* fStream; + + enum { + kBufferSize = 1024 + }; + uint8_t fBuffer[kBufferSize]; +}; + +#endif |