Index: src/images/SkImageDecoder_libjpeg.cpp |
diff --git a/src/images/SkImageDecoder_libjpeg.cpp b/src/images/SkImageDecoder_libjpeg.cpp |
index 914ceb7e8aa458a3ea4e011be2db27f73d0ead8a..788e3526d2d9880e62fcb9a8a612e6262765425e 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; |