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
This commit is contained in:
djsollen@google.com 2013-03-20 17:45:27 +00:00
parent 4d9853288b
commit 113994051b
4 changed files with 578 additions and 216 deletions

View File

@ -13,110 +13,127 @@
'use_system_libjpeg%': 0,
},
'conditions': [
['use_system_libjpeg==0', {
'targets': [
{
'target_name': 'libjpeg',
'type': 'static_library',
'sources': [
'../third_party/externals/libjpeg/jcapimin.c',
'../third_party/externals/libjpeg/jcapistd.c',
'../third_party/externals/libjpeg/jccoefct.c',
'../third_party/externals/libjpeg/jccolor.c',
'../third_party/externals/libjpeg/jcdctmgr.c',
'../third_party/externals/libjpeg/jchuff.c',
'../third_party/externals/libjpeg/jchuff.h',
'../third_party/externals/libjpeg/jcinit.c',
'../third_party/externals/libjpeg/jcmainct.c',
'../third_party/externals/libjpeg/jcmarker.c',
'../third_party/externals/libjpeg/jcmaster.c',
'../third_party/externals/libjpeg/jcomapi.c',
'../third_party/externals/libjpeg/jconfig.h',
'../third_party/externals/libjpeg/jcparam.c',
'../third_party/externals/libjpeg/jcphuff.c',
'../third_party/externals/libjpeg/jcprepct.c',
'../third_party/externals/libjpeg/jcsample.c',
'../third_party/externals/libjpeg/jdapimin.c',
'../third_party/externals/libjpeg/jdapistd.c',
'../third_party/externals/libjpeg/jdatadst.c',
'../third_party/externals/libjpeg/jdatasrc.c',
'../third_party/externals/libjpeg/jdcoefct.c',
'../third_party/externals/libjpeg/jdcolor.c',
'../third_party/externals/libjpeg/jdct.h',
'../third_party/externals/libjpeg/jddctmgr.c',
'../third_party/externals/libjpeg/jdhuff.c',
'../third_party/externals/libjpeg/jdhuff.h',
'../third_party/externals/libjpeg/jdinput.c',
'../third_party/externals/libjpeg/jdmainct.c',
'../third_party/externals/libjpeg/jdmarker.c',
'../third_party/externals/libjpeg/jdmaster.c',
'../third_party/externals/libjpeg/jdmerge.c',
'../third_party/externals/libjpeg/jdphuff.c',
'../third_party/externals/libjpeg/jdpostct.c',
'../third_party/externals/libjpeg/jdsample.c',
'../third_party/externals/libjpeg/jerror.c',
'../third_party/externals/libjpeg/jerror.h',
'../third_party/externals/libjpeg/jfdctflt.c',
'../third_party/externals/libjpeg/jfdctfst.c',
'../third_party/externals/libjpeg/jfdctint.c',
'../third_party/externals/libjpeg/jidctflt.c',
'../third_party/externals/libjpeg/jidctfst.c',
'../third_party/externals/libjpeg/jidctint.c',
'../third_party/externals/libjpeg/jinclude.h',
'../third_party/externals/libjpeg/jmemmgr.c',
'../third_party/externals/libjpeg/jmemnobs.c',
'../third_party/externals/libjpeg/jmemsys.h',
'../third_party/externals/libjpeg/jmorecfg.h',
'../third_party/externals/libjpeg/jpegint.h',
'../third_party/externals/libjpeg/jpeglib.h',
'../third_party/externals/libjpeg/jquant1.c',
'../third_party/externals/libjpeg/jquant2.c',
'../third_party/externals/libjpeg/jutils.c',
'../third_party/externals/libjpeg/jversion.h',
],
'direct_dependent_settings': {
'include_dirs': [
'../third_party/externals/libjpeg',
],
},
'conditions': [
['OS!="win"', {
'product_name': 'jpeg',
'cflags': [
'-Wno-main', # supresses warnings about naming things "main"
],
}],
['OS=="android"', {
'cflags!': [
'-fno-rtti', # supresses warnings about invalid option of non-C++ code
'-Wall',
'-Werror',
],
}],
['OS in ["linux", "freebsd", "openbsd", "solaris", "nacl"]', {
'cflags!': [
'-Werror',
],
}],
],
},
],
}, {
['skia_os == "android"', {
'targets': [
{
'target_name': 'libjpeg',
'type': 'none',
'direct_dependent_settings': {
'defines': [
'USE_SYSTEM_LIBJPEG',
],
},
'link_settings': {
'libraries': [
'-ljpeg',
],
},
}
'dependencies': [
'android_deps.gyp:jpeg',
],
'export_dependent_settings': [
'android_deps.gyp:jpeg',
],
},
],
}, { # skia_os != android
'conditions': [
['use_system_libjpeg==0', {
'targets': [
{
'target_name': 'libjpeg',
'type': 'static_library',
'sources': [
'../third_party/externals/libjpeg/jcapimin.c',
'../third_party/externals/libjpeg/jcapistd.c',
'../third_party/externals/libjpeg/jccoefct.c',
'../third_party/externals/libjpeg/jccolor.c',
'../third_party/externals/libjpeg/jcdctmgr.c',
'../third_party/externals/libjpeg/jchuff.c',
'../third_party/externals/libjpeg/jchuff.h',
'../third_party/externals/libjpeg/jcinit.c',
'../third_party/externals/libjpeg/jcmainct.c',
'../third_party/externals/libjpeg/jcmarker.c',
'../third_party/externals/libjpeg/jcmaster.c',
'../third_party/externals/libjpeg/jcomapi.c',
'../third_party/externals/libjpeg/jconfig.h',
'../third_party/externals/libjpeg/jcparam.c',
'../third_party/externals/libjpeg/jcphuff.c',
'../third_party/externals/libjpeg/jcprepct.c',
'../third_party/externals/libjpeg/jcsample.c',
'../third_party/externals/libjpeg/jdapimin.c',
'../third_party/externals/libjpeg/jdapistd.c',
'../third_party/externals/libjpeg/jdatadst.c',
'../third_party/externals/libjpeg/jdatasrc.c',
'../third_party/externals/libjpeg/jdcoefct.c',
'../third_party/externals/libjpeg/jdcolor.c',
'../third_party/externals/libjpeg/jdct.h',
'../third_party/externals/libjpeg/jddctmgr.c',
'../third_party/externals/libjpeg/jdhuff.c',
'../third_party/externals/libjpeg/jdhuff.h',
'../third_party/externals/libjpeg/jdinput.c',
'../third_party/externals/libjpeg/jdmainct.c',
'../third_party/externals/libjpeg/jdmarker.c',
'../third_party/externals/libjpeg/jdmaster.c',
'../third_party/externals/libjpeg/jdmerge.c',
'../third_party/externals/libjpeg/jdphuff.c',
'../third_party/externals/libjpeg/jdpostct.c',
'../third_party/externals/libjpeg/jdsample.c',
'../third_party/externals/libjpeg/jerror.c',
'../third_party/externals/libjpeg/jerror.h',
'../third_party/externals/libjpeg/jfdctflt.c',
'../third_party/externals/libjpeg/jfdctfst.c',
'../third_party/externals/libjpeg/jfdctint.c',
'../third_party/externals/libjpeg/jidctflt.c',
'../third_party/externals/libjpeg/jidctfst.c',
'../third_party/externals/libjpeg/jidctint.c',
'../third_party/externals/libjpeg/jinclude.h',
'../third_party/externals/libjpeg/jmemmgr.c',
'../third_party/externals/libjpeg/jmemnobs.c',
'../third_party/externals/libjpeg/jmemsys.h',
'../third_party/externals/libjpeg/jmorecfg.h',
'../third_party/externals/libjpeg/jpegint.h',
'../third_party/externals/libjpeg/jpeglib.h',
'../third_party/externals/libjpeg/jquant1.c',
'../third_party/externals/libjpeg/jquant2.c',
'../third_party/externals/libjpeg/jutils.c',
'../third_party/externals/libjpeg/jversion.h',
],
'direct_dependent_settings': {
'include_dirs': [
'../third_party/externals/libjpeg',
],
},
'conditions': [
['OS!="win"', {
'product_name': 'jpeg',
'cflags': [
'-Wno-main', # supresses warnings about naming things "main"
],
}],
['OS=="android"', {
'cflags!': [
'-fno-rtti', # supresses warnings about invalid option of non-C++ code
'-Wall',
'-Werror',
],
}],
['OS in ["linux", "freebsd", "openbsd", "solaris", "nacl"]', {
'cflags!': [
'-Werror',
],
}],
],
},
],
}, {
'targets': [
{
'target_name': 'libjpeg',
'type': 'none',
'direct_dependent_settings': {
'defines': [
'USE_SYSTEM_LIBJPEG',
],
},
'link_settings': {
'libraries': [
'-ljpeg',
],
},
}
],
}],
],
}],
],

View File

@ -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.
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
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:
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);
}
/**
* 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; }
#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 SkJPEGImageDecoder : public SkImageDecoder {
public:
SkJPEGImageDecoder() {
fImageIndex = NULL;
fImageWidth = 0;
fImageHeight = 0;
}
virtual ~SkJPEGImageDecoder() {
SkDELETE(fImageIndex);
}
virtual Format getFormat() const {
return kJPEG_Format;
}
protected:
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
#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:
SkJPEGImageIndex* fImageIndex;
int fImageWidth;
int fImageHeight;
typedef SkImageDecoder INHERITED;
};
//////////////////////////////////////////////////////////////////////////
#include "SkTime.h"
class AutoTimeMillis {
public:
AutoTimeMillis(const char label[]) : fLabel(label) {
if (!fLabel) {
fLabel = "";
}
fNow = SkTime::GetMSecs();
}
~AutoTimeMillis() {
SkDebugf("---- Time (ms): %s %d\n", fLabel, SkTime::GetMSecs() - fNow);
}
private:
const char* fLabel;
SkMSec fNow;
};
/* 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);

View File

@ -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);
}