aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/images
diff options
context:
space:
mode:
authorGravatar djsollen@google.com <djsollen@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2013-03-20 17:45:27 +0000
committerGravatar djsollen@google.com <djsollen@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2013-03-20 17:45:27 +0000
commit113994051b41366a7b25851d05cd56e89866a33b (patch)
tree373fe1104f36909ce35ad6b4d9afeca26c01e7b4 /src/images
parent4d9853288bd726f8def70dc13b15ca2ce36326dc (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.cpp510
-rw-r--r--src/images/SkJpegUtility.cpp65
-rw-r--r--src/images/SkJpegUtility.h66
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