diff options
author | brettw@google.com <brettw@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-12-12 21:01:41 +0000 |
---|---|---|
committer | brettw@google.com <brettw@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-12-12 21:01:41 +0000 |
commit | 52e935d04c59135739c3a68fb6e19d313dc6d5ad (patch) | |
tree | 95f7ab178b045bef4456cbf92c6aa7e476becd99 /skia/images/SkImageDecoder_libjpeg.cpp | |
parent | 30fab79877b4bb067944b74d98346ac9bb6bfc7e (diff) | |
download | chromium_src-52e935d04c59135739c3a68fb6e19d313dc6d5ad.zip chromium_src-52e935d04c59135739c3a68fb6e19d313dc6d5ad.tar.gz chromium_src-52e935d04c59135739c3a68fb6e19d313dc6d5ad.tar.bz2 |
New drop of Skia. This is up to CL 121320.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@6925 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'skia/images/SkImageDecoder_libjpeg.cpp')
-rw-r--r-- | skia/images/SkImageDecoder_libjpeg.cpp | 367 |
1 files changed, 262 insertions, 105 deletions
diff --git a/skia/images/SkImageDecoder_libjpeg.cpp b/skia/images/SkImageDecoder_libjpeg.cpp index 0d8da7ef..492de23 100644 --- a/skia/images/SkImageDecoder_libjpeg.cpp +++ b/skia/images/SkImageDecoder_libjpeg.cpp @@ -1,16 +1,16 @@ /* - * Copyright 2007, Google Inc. + * Copyright 2007, The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and * limitations under the License. */ @@ -25,6 +25,7 @@ #include <stdio.h> extern "C" { #include "jpeglib.h" + #include "jerror.h" } // this enables timing code to report milliseconds for an encode @@ -49,8 +50,21 @@ protected: SkBitmap::Config pref, Mode); }; -SkImageDecoder* SkImageDecoder_JPEG_Factory(SkStream* stream) { - // !!! unimplemented; rely on PNG test first for now +SkImageDecoder* SkImageDecoder_JPEG_Factory(SkStream* stream) { + static const char gHeader[] = { 0xFF, 0xD8, 0xFF }; + static const size_t HEADER_SIZE = sizeof(gHeader); + + char buffer[HEADER_SIZE]; + size_t len = stream->read(buffer, HEADER_SIZE); + + if (len != HEADER_SIZE) { + return NULL; // can't read enough + } + + if (memcmp(buffer, gHeader, HEADER_SIZE)) { + return NULL; + } + return SkNEW(SkJPEGImageDecoder); } @@ -77,10 +91,12 @@ private: /* our source struct for directing jpeg to our stream object */ struct sk_source_mgr : jpeg_source_mgr { - sk_source_mgr(SkStream* stream); + sk_source_mgr(SkStream* stream, SkImageDecoder* decoder); SkStream* fStream; - + const void* fMemoryBase; + size_t fMemoryBaseSize; + SkImageDecoder* fDecoder; enum { kBufferSize = 1024 }; @@ -111,11 +127,13 @@ static void sk_init_source(j_decompress_ptr cinfo) { static boolean sk_fill_input_buffer(j_decompress_ptr cinfo) { sk_source_mgr* src = (sk_source_mgr*)cinfo->src; + if (src->fDecoder != NULL && src->fDecoder->shouldCancelDecode()) { + return FALSE; + } size_t bytes = src->fStream->read(src->fBuffer, sk_source_mgr::kBufferSize); // note that JPEG is happy with less than the full read, // as long as the result is non-zero if (bytes == 0) { - cinfo->err->error_exit((j_common_ptr)cinfo); return FALSE; } @@ -129,11 +147,13 @@ static void sk_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { sk_source_mgr* src = (sk_source_mgr*)cinfo->src; - long skip = num_bytes - src->bytes_in_buffer; + long bytesToSkip = num_bytes - src->bytes_in_buffer; - if (skip > 0) { - size_t bytes = src->fStream->read(NULL, skip); - if (bytes != (size_t)skip) { + // check if the skip amount exceeds the current buffer + if (bytesToSkip > 0) { + size_t bytes = src->fStream->skip(bytesToSkip); + if (bytes != (size_t)bytesToSkip) { +// SkDebugf("xxxxxxxxxxxxxx failure to skip request %d actual %d\n", bytesToSkip, bytes); cinfo->err->error_exit((j_common_ptr)cinfo); } src->next_input_byte = (const JOCTET*)src->fBuffer; @@ -150,22 +170,69 @@ static boolean sk_resync_to_restart(j_decompress_ptr cinfo, int desired) { // what is the desired param for??? if (!src->fStream->rewind()) { - printf("------------- sk_resync_to_restart: stream->rewind() failed\n"); + SkDebugf("xxxxxxxxxxxxxx failure to rewind\n"); cinfo->err->error_exit((j_common_ptr)cinfo); return FALSE; } + src->next_input_byte = (const JOCTET*)src->fBuffer; + src->bytes_in_buffer = 0; return TRUE; } static void sk_term_source(j_decompress_ptr /*cinfo*/) {} -sk_source_mgr::sk_source_mgr(SkStream* stream) - : fStream(stream) { - init_source = sk_init_source; - fill_input_buffer = sk_fill_input_buffer; - skip_input_data = sk_skip_input_data; - resync_to_restart = sk_resync_to_restart; - term_source = sk_term_source; +/////////////////////////////////////////////////////////////////////////////// + +static void skmem_init_source(j_decompress_ptr cinfo) { + sk_source_mgr* src = (sk_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) { + sk_source_mgr* src = (sk_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*/) {} + +/////////////////////////////////////////////////////////////////////////////// + +sk_source_mgr::sk_source_mgr(SkStream* stream, SkImageDecoder* decoder) : fStream(stream) { + fDecoder = decoder; + const void* baseAddr = stream->getMemoryBase(); + if (baseAddr && false) { + fMemoryBase = baseAddr; + fMemoryBaseSize = stream->getLength(); + + 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; + } else { + fMemoryBase = NULL; + fMemoryBaseSize = 0; + + init_source = sk_init_source; + fill_input_buffer = sk_fill_input_buffer; + skip_input_data = sk_skip_input_data; + resync_to_restart = sk_resync_to_restart; + term_source = sk_term_source; + } +// SkDebugf("**************** use memorybase %p %d\n", fMemoryBase, fMemoryBaseSize); } #include <setjmp.h> @@ -187,14 +254,29 @@ static void sk_error_exit(j_common_ptr cinfo) { /////////////////////////////////////////////////////////////////////////////// -static void skip_src_rows(jpeg_decompress_struct* cinfo, void* buffer, +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); - SkASSERT(row_count == 1); + 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, + const SkBitmap& bm, const char msg[]) { +#if 0 + 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()); +#endif + return false; // must always return false +} bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, SkBitmap::Config prefConfig, Mode mode) { @@ -207,7 +289,7 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, jpeg_decompress_struct cinfo; sk_error_mgr sk_err; - sk_source_mgr sk_stream(stream); + sk_source_mgr sk_stream(stream, this); cinfo.err = jpeg_std_error(&sk_err); sk_err.error_exit = sk_error_exit; @@ -215,7 +297,7 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, // 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; + return return_false(cinfo, *bm, "setjmp"); } jpeg_create_decompress(&cinfo); @@ -224,7 +306,10 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, //jpeg_stdio_src(&cinfo, file); cinfo.src = &sk_stream; - jpeg_read_header(&cinfo, true); + int status = jpeg_read_header(&cinfo, true); + if (status != JPEG_HEADER_OK) { + return return_false(cinfo, *bm, "read_header"); + } /* Try to fulfill the requested sampleSize. Since jpeg can do it (when it can) much faster that we, just use their num/denom api to approximate @@ -236,55 +321,128 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, cinfo.scale_num = 1; 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; + + /* default format is RGB */ + cinfo.out_color_space = JCS_RGB; + + SkBitmap::Config config = prefConfig; + // if no user preference, see what the device recommends + if (config == SkBitmap::kNo_Config) + config = SkImageDecoder::GetDeviceConfig(); + + // only these make sense for jpegs + 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 (sampleSize == 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 + /* image_width and image_height are the original dimensions, available after jpeg_read_header(). To see the scaled dimensions, we have to call jpeg_start_decompress(), and then read output_width and output_height. */ - jpeg_start_decompress(&cinfo); + if (!jpeg_start_decompress(&cinfo)) { + return return_false(cinfo, *bm, "start_decompress"); + } /* If we need to better match the request, we might examine the image and output dimensions, and determine if the downsampling jpeg provided is not sufficient. If so, we can recompute a modified sampleSize value to make up the difference. - + To skip this additional scaling, just set sampleSize = 1; below. */ sampleSize = sampleSize * cinfo.output_width / cinfo.image_width; - // check for supported formats - bool isRGB; // as opposed to gray8 - if (3 == cinfo.num_components && JCS_RGB == cinfo.out_color_space) { - isRGB = true; - } else if (1 == cinfo.num_components && - JCS_GRAYSCALE == cinfo.out_color_space) { - isRGB = false; // could use Index8 config if we want... - } else { - SkDEBUGF(("SkJPEGImageDecoder: unsupported jpeg colorspace %d with %d components\n", - cinfo.jpeg_color_space, cinfo.num_components)); - return false; - } - - SkBitmap::Config config = prefConfig; - // if no user preference, see what the device recommends - if (config == SkBitmap::kNo_Config) - config = SkImageDecoder::GetDeviceConfig(); - - // only these make sense for jpegs - if (config != SkBitmap::kARGB_8888_Config && - config != SkBitmap::kARGB_4444_Config && - config != SkBitmap::kRGB_565_Config) { - config = SkBitmap::kARGB_8888_Config; - } // should we allow the Chooser (if present) to pick a config for us??? if (!this->chooseFromOneChoice(config, cinfo.output_width, cinfo.output_height)) { - return false; + return return_false(cinfo, *bm, "chooseFromOneChoice"); + } + +#ifdef ANDROID_RGB + /* short-circuit the SkScaledBitmapSampler when possible, as this gives + a significant performance boost. + */ + if (sampleSize == 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))) + { + 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(); + INT32 const bpr = bm->rowBytes(); + + while (cinfo.output_scanline < cinfo.output_height) { + int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1); + // 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, *bm, "read_scanlines"); + } + if (this->shouldCancelDecode()) { + return return_false(cinfo, *bm, "shouldCancelDecode"); + } + rowptr += bpr; + } + jpeg_finish_decompress(&cinfo); + 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; + //} 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"); } SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height, sampleSize); - + bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight()); // jpegs are always opauqe (i.e. have no per-pixel alpha) bm->setIsOpaque(true); @@ -293,38 +451,51 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, return true; } if (!this->allocPixelRef(bm, NULL)) { - return false; + return return_false(cinfo, *bm, "allocPixelRef"); } - SkAutoLockPixels alp(*bm); - - if (!sampler.begin(bm, - isRGB ? SkScaledBitmapSampler::kRGB : - SkScaledBitmapSampler::kGray, - this->getDitherImage())) { - return false; + SkAutoLockPixels alp(*bm); + if (!sampler.begin(bm, sc, this->getDitherImage())) { + return return_false(cinfo, *bm, "sampler.begin"); } - uint8_t* srcRow = (uint8_t*)srcStorage.alloc(cinfo.output_width * 3); + uint8_t* srcRow = (uint8_t*)srcStorage.alloc(cinfo.output_width * 4); + + // Possibly skip initial rows [sampler.srcY0] + if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) { + return return_false(cinfo, *bm, "skip rows"); + } - skip_src_rows(&cinfo, srcRow, sampler.srcY0()); + // now loop through scanlines until y == bm->height() - 1 for (int y = 0;; y++) { JSAMPLE* rowptr = (JSAMPLE*)srcRow; int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1); - SkASSERT(row_count == 1); + if (0 == row_count) { + return return_false(cinfo, *bm, "read_scanlines"); + } + if (this->shouldCancelDecode()) { + return return_false(cinfo, *bm, "shouldCancelDecode"); + } sampler.next(srcRow); if (bm->height() - 1 == y) { + // we're done break; } - skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1); + + if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) { + return return_false(cinfo, *bm, "skip rows"); + } } - // ??? If I don't do this, I get an error from finish_decompress - skip_src_rows(&cinfo, srcRow, cinfo.output_height - cinfo.output_scanline); - + // we formally skip the rest, so we don't get a complaint from libjpeg + if (!skip_src_rows(&cinfo, srcRow, + cinfo.output_height - cinfo.output_scanline)) { + return return_false(cinfo, *bm, "skip rows"); + } jpeg_finish_decompress(&cinfo); +// SkDebugf("------------------- bm2 size %d [%d %d] %d\n", bm->getSize(), bm->width(), bm->height(), bm->config()); return true; } @@ -383,11 +554,11 @@ static void rgb2yuv_4444(uint8_t dst[], U16CPU c) { int r = SkGetPackedR4444(c); int g = SkGetPackedG4444(c); int b = SkGetPackedB4444(c); - + int y = ( CYR*r + CYG*g + CYB*b ) >> (CSHIFT - 4); int u = ( CUR*r + CUG*g + CUB*b ) >> (CSHIFT - 4); int v = ( CVR*r + CVG*g + CVB*b ) >> (CSHIFT - 4); - + dst[0] = SkToU8(y); dst[1] = SkToU8(u + 128); dst[2] = SkToU8(v + 128); @@ -397,11 +568,11 @@ static void rgb2yuv_16(uint8_t dst[], U16CPU c) { int r = SkGetPackedR16(c); int g = SkGetPackedG16(c); int b = SkGetPackedB16(c); - + int y = ( 2*CYR*r + CYG*g + 2*CYB*b ) >> (CSHIFT - 2); int u = ( 2*CUR*r + CUG*g + 2*CUB*b ) >> (CSHIFT - 2); int v = ( 2*CVR*r + CVG*g + 2*CVB*b ) >> (CSHIFT - 2); - + dst[0] = SkToU8(y); dst[1] = SkToU8(u + 128); dst[2] = SkToU8(v + 128); @@ -519,9 +690,9 @@ static boolean sk_empty_output_buffer(j_compress_ptr cinfo) { // if (!dest->fStream->write(dest->fBuffer, sk_destination_mgr::kBufferSize - dest->free_in_buffer)) if (!dest->fStream->write(dest->fBuffer, sk_destination_mgr::kBufferSize)) { - sk_throw(); + ERREXIT(cinfo, JERR_FILE_WRITE); + return false; } - // ERREXIT(cinfo, JERR_FILE_WRITE); dest->next_output_byte = dest->fBuffer; dest->free_in_buffer = sk_destination_mgr::kBufferSize; @@ -534,7 +705,8 @@ static void sk_term_destination (j_compress_ptr cinfo) { size_t size = sk_destination_mgr::kBufferSize - dest->free_in_buffer; if (size > 0) { if (!dest->fStream->write(dest->fBuffer, size)) { - sk_throw(); + ERREXIT(cinfo, JERR_FILE_WRITE); + return; } } dest->fStream->flush(); @@ -547,23 +719,6 @@ sk_destination_mgr::sk_destination_mgr(SkWStream* stream) this->term_destination = sk_term_destination; } -class SkAutoLockColors : public SkNoncopyable { -public: - SkAutoLockColors(const SkBitmap& bm) { - fCTable = bm.getColorTable(); - fColors = fCTable ? fCTable->lockColors() : NULL; - } - ~SkAutoLockColors() { - if (fCTable) { - fCTable->unlockColors(false); - } - } - const SkPMColor* colors() const { return fColors; } -private: - SkColorTable* fCTable; - const SkPMColor* fColors; -}; - class SkJPEGImageEncoder : public SkImageEncoder { protected: virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) { @@ -585,6 +740,10 @@ protected: sk_error_mgr sk_err; sk_destination_mgr sk_wstream(stream); + // allocate these before set call setjmp + SkAutoMalloc oneRow; + SkAutoLockColors ctLocker; + cinfo.err = jpeg_std_error(&sk_err); sk_err.error_exit = sk_error_exit; if (setjmp(sk_err.fJmpBuf)) { @@ -606,17 +765,15 @@ protected: jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); cinfo.dct_method = JDCT_IFAST; - + jpeg_start_compress(&cinfo, TRUE); const int width = bm.width(); - SkAutoMalloc oneRow(width * 3); - uint8_t* oneRowP = (uint8_t*)oneRow.get(); + uint8_t* oneRowP = (uint8_t*)oneRow.alloc(width * 3); - SkAutoLockColors alc(bm); - const SkPMColor* colors = alc.colors(); + const SkPMColor* colors = ctLocker.lockColors(bm); const void* srcRow = bm.getPixels(); - + while (cinfo.next_scanline < cinfo.image_height) { JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ |