diff options
Diffstat (limited to 'src/images/SkImageDecoder_libjpeg.cpp')
-rw-r--r-- | src/images/SkImageDecoder_libjpeg.cpp | 286 |
1 files changed, 182 insertions, 104 deletions
diff --git a/src/images/SkImageDecoder_libjpeg.cpp b/src/images/SkImageDecoder_libjpeg.cpp index 914ceb7e8a..788e3526d2 100644 --- a/src/images/SkImageDecoder_libjpeg.cpp +++ b/src/images/SkImageDecoder_libjpeg.cpp @@ -30,7 +30,6 @@ extern "C" { //#define TIME_DECODE // this enables our rgb->yuv code, which is faster than libjpeg on ARM -// 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 @@ -39,7 +38,7 @@ extern "C" { ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// -static void overwrite_mem_buffer_size(j_decompress_ptr cinfo) { +static void overwrite_mem_buffer_size(jpeg_decompress_struct* 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. @@ -55,6 +54,14 @@ static void overwrite_mem_buffer_size(j_decompress_ptr cinfo) { ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// +static void initialize_info(jpeg_decompress_struct* cinfo, skjpeg_source_mgr* src_mgr) { + SkASSERT(cinfo != NULL); + SkASSERT(src_mgr != NULL); + jpeg_create_decompress(cinfo); + overwrite_mem_buffer_size(cinfo); + cinfo->src = src_mgr; +} + #ifdef SK_BUILD_FOR_ANDROID class SkJPEGImageIndex { public: @@ -69,10 +76,17 @@ public: ~SkJPEGImageIndex() { if (fHuffmanCreated) { + // Set to false before calling the libjpeg function, in case + // the libjpeg function calls longjmp. Our setjmp handler may + // attempt to delete this SkJPEGImageIndex, thus entering this + // destructor again. Setting fHuffmanCreated to false first + // prevents an infinite loop. fHuffmanCreated = false; jpeg_destroy_huffman_index(&fHuffmanIndex); } if (fDecompressStarted) { + // Like fHuffmanCreated, set to false before calling libjpeg + // function to prevent potential infinite loop. fDecompressStarted = false; jpeg_finish_decompress(&fCInfo); } @@ -91,6 +105,8 @@ public: void destroyInfo() { SkASSERT(fInfoInitialized); SkASSERT(!fDecompressStarted); + // Like fHuffmanCreated, set to false before calling libjpeg + // function to prevent potential infinite loop. fInfoInitialized = false; jpeg_destroy_decompress(&fCInfo); SkDEBUGCODE(fReadHeaderSucceeded = false;) @@ -106,9 +122,7 @@ public: */ bool initializeInfoAndReadHeader() { SkASSERT(!fInfoInitialized && !fDecompressStarted); - jpeg_create_decompress(&fCInfo); - overwrite_mem_buffer_size(&fCInfo); - fCInfo.src = &fSrcMgr; + initialize_info(&fCInfo, &fSrcMgr); fInfoInitialized = true; const bool success = (JPEG_HEADER_OK == jpeg_read_header(&fCInfo, true)); SkDEBUGCODE(fReadHeaderSucceeded = success;) @@ -193,6 +207,14 @@ private: int fImageHeight; #endif + /** + * Determine the appropriate bitmap config and out_color_space based on + * both the preference of the caller and the jpeg_color_space on the + * jpeg_decompress_struct passed in. + * Must be called after jpeg_read_header. + */ + SkBitmap::Config getBitmapConfig(jpeg_decompress_struct*); + typedef SkImageDecoder INHERITED; }; @@ -295,6 +317,131 @@ static void convert_CMYK_to_RGB(uint8_t* scanline, unsigned int width) { } } +/** + * Common code for setting the error manager. + */ +static void set_error_mgr(jpeg_decompress_struct* cinfo, skjpeg_error_mgr* errorManager) { + SkASSERT(cinfo != NULL); + SkASSERT(errorManager != NULL); + cinfo->err = jpeg_std_error(errorManager); + errorManager->error_exit = skjpeg_error_exit; +} + +/** + * Common code for turning off upsampling and smoothing. Turning these + * off helps performance without showing noticable differences in the + * resulting bitmap. + */ +static void turn_off_visual_optimizations(jpeg_decompress_struct* cinfo) { + SkASSERT(cinfo != NULL); + /* this gives about 30% performance improvement. In theory it may + reduce the visual quality, in practice I'm not seeing a difference + */ + cinfo->do_fancy_upsampling = 0; + + /* this gives another few percents */ + cinfo->do_block_smoothing = 0; +} + +/** + * Common code for setting the dct method. + */ +static void set_dct_method(const SkImageDecoder& decoder, jpeg_decompress_struct* cinfo) { + SkASSERT(cinfo != NULL); +#ifdef DCT_IFAST_SUPPORTED + if (decoder.getPreferQualityOverSpeed()) { + cinfo->dct_method = JDCT_ISLOW; + } else { + cinfo->dct_method = JDCT_IFAST; + } +#else + cinfo->dct_method = JDCT_ISLOW; +#endif +} + +SkBitmap::Config SkJPEGImageDecoder::getBitmapConfig(jpeg_decompress_struct* cinfo) { + SkASSERT(cinfo != NULL); + + SrcDepth srcDepth = k32Bit_SrcDepth; + if (JCS_GRAYSCALE == cinfo->jpeg_color_space) { + srcDepth = k8BitGray_SrcDepth; + } + + SkBitmap::Config config = this->getPrefConfig(srcDepth, /*hasAlpha*/ false); + switch (config) { + case SkBitmap::kA8_Config: + // Only respect A8 config if the original is grayscale, + // in which case we will treat the grayscale as alpha + // values. + if (cinfo->jpeg_color_space != JCS_GRAYSCALE) { + config = SkBitmap::kARGB_8888_Config; + } + break; + case SkBitmap::kARGB_8888_Config: + // Fall through. + case SkBitmap::kARGB_4444_Config: + // Fall through. + case SkBitmap::kRGB_565_Config: + // These are acceptable destination configs. + break; + default: + // Force all other configs to 8888. + config = SkBitmap::kARGB_8888_Config; + break; + } + + switch (cinfo->jpeg_color_space) { + case JCS_CMYK: + // Fall through. + case JCS_YCCK: + // libjpeg cannot convert from CMYK or YCCK to RGB - here we set up + // so libjpeg will give us CMYK samples back and we will later + // manually convert them to RGB + cinfo->out_color_space = JCS_CMYK; + break; + case JCS_GRAYSCALE: + if (SkBitmap::kA8_Config == config) { + cinfo->out_color_space = JCS_GRAYSCALE; + break; + } + // The data is JCS_GRAYSCALE, but the caller wants some sort of RGB + // config. Fall through to set to the default. + default: + cinfo->out_color_space = JCS_RGB; + break; + } + return config; +} + +#ifdef ANDROID_RGB +/** + * Based on the config and dither mode, adjust out_color_space and + * dither_mode of cinfo. + */ +static void adjust_out_color_space_and_dither(jpeg_decompress_struct* cinfo, + SkBitmap::Config config, + const SkImageDecoder& decoder) { + SkASSERT(cinfo != NULL); + cinfo->dither_mode = JDITHER_NONE; + if (JCS_CMYK == cinfo->out_color_space) { + return; + } + switch(config) { + case SkBitmap::kARGB_8888_Config: + cinfo->out_color_space = JCS_RGBA_8888; + break; + case SkBitmap::kRGB_565_Config: + cinfo->out_color_space = JCS_RGB_565; + if (decoder.getDitherImage()) { + cinfo->dither_mode = JDITHER_ORDERED; + } + break; + default: + break; + } +} +#endif + bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { #ifdef TIME_DECODE SkAutoTime atm("JPEG Decode"); @@ -303,11 +450,10 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { JPEGAutoClean autoClean; jpeg_decompress_struct cinfo; - skjpeg_error_mgr errorManager; skjpeg_source_mgr srcManager(stream, this); - cinfo.err = jpeg_std_error(&errorManager); - errorManager.error_exit = skjpeg_error_exit; + skjpeg_error_mgr errorManager; + set_error_mgr(&cinfo, &errorManager); // All objects need to be instantiated before this setjmp call so that // they will be cleaned up properly if an error occurs. @@ -315,14 +461,9 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { return return_false(cinfo, *bm, "setjmp"); } - jpeg_create_decompress(&cinfo); + initialize_info(&cinfo, &srcManager); autoClean.set(&cinfo); - overwrite_mem_buffer_size(&cinfo); - - //jpeg_stdio_src(&cinfo, file); - cinfo.src = &srcManager; - int status = jpeg_read_header(&cinfo, true); if (status != JPEG_HEADER_OK) { return return_false(cinfo, *bm, "read_header"); @@ -334,67 +475,17 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { */ int sampleSize = this->getSampleSize(); -#ifdef DCT_IFAST_SUPPORTED - if (this->getPreferQualityOverSpeed()) { - cinfo.dct_method = JDCT_ISLOW; - } else { - cinfo.dct_method = JDCT_IFAST; - } -#else - cinfo.dct_method = JDCT_ISLOW; -#endif + set_dct_method(*this, &cinfo); - cinfo.scale_num = 1; + SkASSERT(1 == cinfo.scale_num); cinfo.scale_denom = sampleSize; - /* this gives about 30% performance improvement. In theory it may - reduce the visual quality, in practice I'm not seeing a difference - */ - cinfo.do_fancy_upsampling = 0; - - /* this gives another few percents */ - cinfo.do_block_smoothing = 0; - - SrcDepth srcDepth = k32Bit_SrcDepth; - /* default format is RGB */ - if (cinfo.jpeg_color_space == JCS_CMYK) { - // libjpeg cannot convert from CMYK to RGB - here we set up - // so libjpeg will give us CMYK samples back and we will - // later manually convert them to RGB - cinfo.out_color_space = JCS_CMYK; - } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { - cinfo.out_color_space = JCS_GRAYSCALE; - srcDepth = k8BitGray_SrcDepth; - } else { - cinfo.out_color_space = JCS_RGB; - } + turn_off_visual_optimizations(&cinfo); - SkBitmap::Config config = this->getPrefConfig(srcDepth, false); - // only these make sense for jpegs - if (SkBitmap::kA8_Config == config) { - if (cinfo.jpeg_color_space != JCS_GRAYSCALE) { - // Converting from a non grayscale image to A8 is - // not currently supported. - config = SkBitmap::kARGB_8888_Config; - // Change the output from jpeg back to RGB. - cinfo.out_color_space = JCS_RGB; - } - } else if (config != SkBitmap::kARGB_8888_Config && - config != SkBitmap::kARGB_4444_Config && - config != SkBitmap::kRGB_565_Config) { - config = SkBitmap::kARGB_8888_Config; - } + const SkBitmap::Config config = this->getBitmapConfig(&cinfo); #ifdef ANDROID_RGB - cinfo.dither_mode = JDITHER_NONE; - if (SkBitmap::kARGB_8888_Config == config && JCS_CMYK != cinfo.out_color_space) { - cinfo.out_color_space = JCS_RGBA_8888; - } else if (SkBitmap::kRGB_565_Config == config && JCS_CMYK != cinfo.out_color_space) { - cinfo.out_color_space = JCS_RGB_565; - if (this->getDitherImage()) { - cinfo.dither_mode = JDITHER_ORDERED; - } - } + adjust_out_color_space_and_dither(&cinfo, config, *this); #endif if (1 == sampleSize && SkImageDecoder::kDecodeBounds_Mode == mode) { @@ -552,8 +643,7 @@ bool SkJPEGImageDecoder::onBuildTileIndex(SkStream* stream, int *width, int *hei jpeg_decompress_struct* cinfo = imageIndex->cinfo(); skjpeg_error_mgr sk_err; - cinfo->err = jpeg_std_error(&sk_err); - sk_err.error_exit = skjpeg_error_exit; + set_error_mgr(cinfo, &sk_err); // All objects need to be instantiated before this setjmp call so that // they will be cleaned up properly if an error occurs. @@ -578,9 +668,13 @@ bool SkJPEGImageDecoder::onBuildTileIndex(SkStream* stream, int *width, int *hei return false; } - cinfo->out_color_space = JCS_RGBA_8888; - cinfo->do_fancy_upsampling = 0; - cinfo->do_block_smoothing = 0; + // FIXME: This sets cinfo->out_color_space, which we may change later + // based on the config in onDecodeSubset. This should be fine, since + // jpeg_init_read_tile_scanline will check out_color_space again after + // that change (when it calls jinit_color_deconverter). + (void) this->getBitmapConfig(cinfo); + + turn_off_visual_optimizations(cinfo); // instead of jpeg_start_decompress() we start a tiled decompress if (!imageIndex->startTileDecompress()) { @@ -588,10 +682,15 @@ bool SkJPEGImageDecoder::onBuildTileIndex(SkStream* stream, int *width, int *hei } SkASSERT(1 == cinfo->scale_num); - *height = cinfo->output_height; - *width = cinfo->output_width; - fImageWidth = *width; - fImageHeight = *height; + fImageWidth = cinfo->output_width; + fImageHeight = cinfo->output_height; + + if (width) { + *width = fImageWidth; + } + if (height) { + *height = fImageHeight; + } SkDELETE(fImageIndex); fImageIndex = imageIndex.detach(); @@ -613,8 +712,8 @@ bool SkJPEGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) { skjpeg_error_mgr errorManager; - cinfo->err = jpeg_std_error(&errorManager); - errorManager.error_exit = skjpeg_error_exit; + set_error_mgr(cinfo, &errorManager); + if (setjmp(errorManager.fJmpBuf)) { return false; } @@ -622,32 +721,11 @@ bool SkJPEGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) { 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; + set_dct_method(*this, cinfo); + const SkBitmap::Config config = this->getBitmapConfig(cinfo); #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; - } - } + adjust_out_color_space_and_dither(cinfo, config, *this); #endif int startX = rect.fLeft; |