Commit 97fa2e45 authored by Joseph Wen's avatar Joseph Wen
Browse files

Do JPEG tile-based decoding.

Change-Id: I795129d55a0a7da90b4d604a902066868933d2a9
parent 6394a2c7
......@@ -78,6 +78,7 @@ LOCAL_SRC_FILES:= \
src/images/SkImageRef_GlobalPool.cpp \
src/images/SkImageRefPool.cpp \
src/images/SkJpegUtility.cpp \
src/images/SkLargeBitmap.cpp \
src/images/SkMovie.cpp \
src/images/SkMovie_gif.cpp \
src/images/SkPageFlipper.cpp \
......
......@@ -18,10 +18,17 @@
#define SkImageDecoder_DEFINED
#include "SkBitmap.h"
#include "SkRect.h"
#include "SkRefCnt.h"
class SkStream;
class SkVMMemoryReporter : public SkRefCnt {
public:
virtual ~SkVMMemoryReporter() {}
virtual bool reportMemory(size_t memorySize);
};
/** \class SkImageDecoder
Base class for decoding compressed images into a SkBitmap
......@@ -30,6 +37,7 @@ class SkImageDecoder {
public:
virtual ~SkImageDecoder();
// Should be consistent with kFormatName
enum Format {
kUnknown_Format,
kBMP_Format,
......@@ -42,10 +50,21 @@ public:
kLastKnownFormat = kWBMP_Format
};
/** Contains the image format name.
* This should be consistent with Format.
*
* The format name gives a more meaningful error message than enum.
*/
static const char *kFormatName[7];
/** Return the compressed data's format (see Format enum)
*/
virtual Format getFormat() const;
/** Return the compressed data's format name.
*/
const char* getFormatName() const { return kFormatName[getFormat()]; }
/** Returns true if the decoder should try to dither the resulting image.
The default setting is true.
*/
......@@ -118,6 +137,7 @@ public:
SkBitmap::Allocator* getAllocator() const { return fAllocator; }
SkBitmap::Allocator* setAllocator(SkBitmap::Allocator*);
SkVMMemoryReporter* setReporter(SkVMMemoryReporter*);
// sample-size, if set to > 1, tells the decoder to return a smaller than
// original bitmap, sampling 1 pixel for every size pixels. e.g. if sample
......@@ -176,6 +196,29 @@ public:
return this->decode(stream, bitmap, SkBitmap::kNo_Config, mode);
}
/**
* Given a stream, build an index for doing tile-based decode.
* The built index will be saved in the decoder, and the image size will
* be returned in width and height.
*
* Return true for success or false on failure.
*/
virtual bool buildTileIndex(SkStream*,
int *width, int *height, bool isShareable) {
return false;
}
/**
* Decode a rectangle region in the image specified by rect.
* The method can only be called after buildTileIndex().
*
* Return true for success.
* Return false if the index is never built or failing in decoding.
*/
virtual bool decodeRegion(SkBitmap* bitmap, SkIRect rect,
SkBitmap::Config pref);
/** Given a stream, this will try to find an appropriate decoder object.
If none is found, the method returns NULL.
*/
......@@ -264,6 +307,11 @@ protected:
// must be overridden in subclasses. This guy is called by decode(...)
virtual bool onDecode(SkStream*, SkBitmap* bitmap, Mode) = 0;
// must be overridden in subclasses. This guy is called by decodeRegion(...)
virtual bool onDecodeRegion(SkBitmap* bitmap, SkIRect rect) {
return false;
}
/** Can be queried from within onDecode, to see if the user (possibly in
a different thread) has requested the decode to cancel. If this returns
true, your onDecode() should stop and return false.
......@@ -304,6 +352,8 @@ protected:
*/
SkBitmap::Config getPrefConfig(SrcDepth, bool hasAlpha) const;
SkVMMemoryReporter* fReporter;
private:
Peeker* fPeeker;
Chooser* fChooser;
......
......@@ -41,11 +41,13 @@ 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);
skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder, bool copyStream, bool ownStream);
~skjpeg_source_mgr();
SkStream* fStream;
const void* fMemoryBase;
void* fMemoryBase;
size_t fMemoryBaseSize;
bool fUnrefStream;
SkImageDecoder* fDecoder;
enum {
kBufferSize = 1024
......
#ifndef SkLargeBitmap_DEFINED
#define SkLargeBitmap_DEFINED
#include "SkBitmap.h"
#include "SkRect.h"
#include "SkImageDecoder.h"
class SkLargeBitmap {
public:
SkLargeBitmap(SkImageDecoder *decoder, int width, int height) {
fDecoder = decoder;
fWidth = width;
fHeight = height;
}
virtual ~SkLargeBitmap() {
delete fDecoder;
}
virtual bool decodeRegion(SkBitmap* bitmap, SkIRect rect,
SkBitmap::Config pref, int sampleSize);
virtual int getWidth() { return fWidth; }
virtual int getHeight() { return fHeight; }
virtual SkImageDecoder* getDecoder() { return fDecoder; }
private:
SkImageDecoder *fDecoder;
int fWidth;
int fHeight;
};
#endif
......@@ -20,6 +20,17 @@
#include "SkPixelRef.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkCanvas.h"
const char *SkImageDecoder::kFormatName[] = {
"Unknown Format",
"BMP",
"GIF",
"ICO",
"JPEG",
"PNG",
"WBMP",
};
static SkBitmap::Config gDeviceConfig = SkBitmap::kNo_Config;
......@@ -36,8 +47,8 @@ void SkImageDecoder::SetDeviceConfig(SkBitmap::Config config)
///////////////////////////////////////////////////////////////////////////////
SkImageDecoder::SkImageDecoder()
: fPeeker(NULL), fChooser(NULL), fAllocator(NULL), fSampleSize(1),
fDefaultPref(SkBitmap::kNo_Config), fDitherImage(true),
: fReporter(NULL), fPeeker(NULL), fChooser(NULL), fAllocator(NULL),
fSampleSize(1), fDefaultPref(SkBitmap::kNo_Config), fDitherImage(true),
fUsePrefTable(false) {
}
......@@ -45,6 +56,7 @@ SkImageDecoder::~SkImageDecoder() {
fPeeker->safeUnref();
fChooser->safeUnref();
fAllocator->safeUnref();
fReporter->safeUnref();
}
SkImageDecoder::Format SkImageDecoder::getFormat() const {
......@@ -66,6 +78,11 @@ SkBitmap::Allocator* SkImageDecoder::setAllocator(SkBitmap::Allocator* alloc) {
return alloc;
}
SkVMMemoryReporter* SkImageDecoder::setReporter(SkVMMemoryReporter* reporter) {
SkRefCnt_SafeAssign(fReporter, reporter);
return reporter;
}
void SkImageDecoder::setSampleSize(int size) {
if (size < 1) {
size = 1;
......@@ -150,6 +167,24 @@ bool SkImageDecoder::decode(SkStream* stream, SkBitmap* bm,
return true;
}
bool SkImageDecoder::decodeRegion(SkBitmap* bm, SkIRect rect,
SkBitmap::Config pref) {
// pass a temporary bitmap, so that if we return false, we are assured of
// leaving the caller's bitmap untouched.
SkBitmap tmp;
// we reset this to false before calling onDecodeRegion
fShouldCancelDecode = false;
// assign this, for use by getPrefConfig(), in case fUsePrefTable is false
fDefaultPref = pref;
if (!this->onDecodeRegion(&tmp, rect)) {
return false;
}
bm->swap(tmp);
return true;
}
///////////////////////////////////////////////////////////////////////////////
bool SkImageDecoder::DecodeFile(const char file[], SkBitmap* bm,
......
......@@ -23,6 +23,8 @@
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkUtils.h"
#include "SkRect.h"
#include "SkCanvas.h"
#include <stdio.h>
extern "C" {
......@@ -48,14 +50,44 @@ static const char KEY_MEM_CAP[] = "ro.media.dec.jpeg.memcap";
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
class SkJPEGImageIndex {
public:
SkJPEGImageIndex() {}
virtual ~SkJPEGImageIndex() {
jpeg_destroy_huffman_index(index);
delete cinfo->src;
jpeg_finish_decompress(cinfo);
jpeg_destroy_decompress(cinfo);
free(cinfo);
}
jpeg_decompress_struct *cinfo;
huffman_index *index;
};
class SkJPEGImageDecoder : public SkImageDecoder {
public:
SkJPEGImageDecoder() {
index = NULL;
}
~SkJPEGImageDecoder() {
if (index)
delete index;
}
virtual Format getFormat() const {
return kJPEG_Format;
}
virtual bool buildTileIndex(SkStream *stream,
int *width, int *height, bool isShareable);
protected:
virtual bool onDecodeRegion(SkBitmap* bitmap, SkIRect rect);
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
virtual void cropBitmap(SkBitmap *dest, SkBitmap *src, int sampleSize,
int srcX, int srcY, int width, int height,
int destX, int destY);
private:
SkJPEGImageIndex *index;
};
//////////////////////////////////////////////////////////////////////////
......@@ -141,6 +173,21 @@ static bool skip_src_rows(jpeg_decompress_struct* cinfo, void* buffer,
return true;
}
static bool skip_src_rows_tile(jpeg_decompress_struct* cinfo,
huffman_index *index, void* buffer,
int startX, int startY, int width, int height,
int count) {
for (int i = 0; i < count; i++) {
JSAMPLE* rowptr = (JSAMPLE*)buffer;
int row_count = jpeg_read_tile_scanline(cinfo, index, &rowptr,
startX, startY, width, height);
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,
......@@ -163,7 +210,7 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
jpeg_decompress_struct cinfo;
skjpeg_error_mgr sk_err;
skjpeg_source_mgr sk_stream(stream, this);
skjpeg_source_mgr sk_stream(stream, this, false, false);
cinfo.err = jpeg_std_error(&sk_err);
sk_err.error_exit = skjpeg_error_exit;
......@@ -388,6 +435,278 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
return true;
}
bool SkJPEGImageDecoder::buildTileIndex(SkStream* stream,
int *width, int *height,
bool isShareable) {
SkAutoMalloc srcStorage;
SkJPEGImageIndex *index = new SkJPEGImageIndex;
jpeg_decompress_struct *cinfo = (jpeg_decompress_struct*)
malloc(sizeof(jpeg_decompress_struct));
skjpeg_error_mgr sk_err;
skjpeg_source_mgr *sk_stream =
new skjpeg_source_mgr(stream, this, !isShareable, true);
if (cinfo == NULL || sk_stream == NULL) {
return false;
}
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;
}
jpeg_create_decompress(cinfo);
cinfo->do_fancy_upsampling = 0;
cinfo->do_block_smoothing = 0;
#ifdef ANDROID
overwrite_mem_buffer_size(cinfo);
#endif
cinfo->src = sk_stream;
int status = jpeg_read_header(cinfo, true);
if (status != JPEG_HEADER_OK) {
return false;
}
index->index = (huffman_index*)malloc(sizeof(huffman_index));
jpeg_create_huffman_index(cinfo, index->index);
cinfo->dct_method = JDCT_IFAST;
cinfo->scale_num = 1;
cinfo->scale_denom = 1;
if (!jpeg_build_huffman_index(cinfo, index->index)) {
return false;
}
if (fReporter)
fReporter->reportMemory(index->index->mem_used);
jpeg_destroy_decompress(cinfo);
// Init decoder to image decode mode
jpeg_create_decompress(cinfo);
#ifdef ANDROID
overwrite_mem_buffer_size(cinfo);
#endif
cinfo->src = sk_stream;
status = jpeg_read_header(cinfo,true);
if (status != JPEG_HEADER_OK) {
return false;
}
cinfo->out_color_space = JCS_RGBA_8888;
cinfo->do_fancy_upsampling = 0;
cinfo->do_block_smoothing = 0;
//jpeg_start_decompress(cinfo);
jpeg_start_tile_decompress(cinfo);
cinfo->dct_method = JDCT_IFAST;
cinfo->scale_num = 1;
index->cinfo = cinfo;
*height = cinfo->output_height;
*width = cinfo->output_width;
this->index = index;
return true;
}
/*
* Crop a rectangle from the src Bitmap to the dest Bitmap. src and dest are
* both sampled by sampleSize from an original Bitmap.
*
* @param dest the destination Bitmap.
* @param src the source Bitmap that is sampled by sampleSize from the original
* Bitmap.
* @param sampleSize the sample size that src is sampled from the original Bitmap.
* @param (srcX, srcY) the upper-left point of the src Btimap in terms of
* the coordinate in the original Bitmap.
* @param (width, height) the width and height of the unsampled dest.
* @param (destX, destY) the upper-left point of the dest Bitmap in terms of
* the coordinate in the original Bitmap.
*/
void SkJPEGImageDecoder::cropBitmap(SkBitmap *dest, SkBitmap *src,
int sampleSize, int srcX, int srcY,
int width, int height, int destX, int destY)
{
int w = width / sampleSize;
int h = height / sampleSize;
if (w == src->width() && h == src->height() &&
(destX - srcX) / sampleSize == 0 && (destY - srcY) / sampleSize == 0) {
// The output rect is the same as the decode result
dest->swap( *src );
return;
}
dest->setConfig(src->getConfig(), w, h);
dest->setIsOpaque(true);
this->allocPixelRef(dest, NULL);
SkCanvas canvas(*dest);
canvas.drawBitmap(*src, (destX - srcX) / sampleSize,
(destY - srcY) / sampleSize);
}
bool SkJPEGImageDecoder::onDecodeRegion(SkBitmap* bm, SkIRect region) {
if (index == NULL) {
return false;
}
int startX = region.fLeft;
int startY = region.fTop;
int width = region.width();
int height = region.height();
jpeg_decompress_struct *cinfo = index->cinfo;
SkAutoMalloc srcStorage;
skjpeg_error_mgr sk_err;
cinfo->err = jpeg_std_error(&sk_err);
sk_err.error_exit = skjpeg_error_exit;
if (setjmp(sk_err.fJmpBuf)) {
return false;
}
int requestedSampleSize = this->getSampleSize();
cinfo->scale_denom = requestedSampleSize;
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;
}
#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 (requestedSampleSize == 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
int oriStartX = startX;
int oriStartY = startY;
int oriWidth = width;
int oriHeight = height;
jpeg_init_read_tile_scanline(cinfo, index->index,
&startX, &startY, &width, &height);
int skiaSampleSize = recompute_sampleSize(requestedSampleSize, *cinfo);
SkBitmap *bitmap = new SkBitmap;
SkAutoTDelete<SkBitmap> adb(bitmap);
int actualSampleSize = skiaSampleSize * cinfo->image_width / cinfo->output_width;
#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)))
{
bitmap->setConfig(config, cinfo->output_width, height);
bitmap->setIsOpaque(true);
if (!this->allocPixelRef(bitmap, NULL)) {
return return_false(*cinfo, *bitmap, "allocPixelRef");
}
SkAutoLockPixels alp(*bitmap);
JSAMPLE* rowptr = (JSAMPLE*)bitmap->getPixels();
INT32 const bpr = bitmap->rowBytes();
int row_total_count = 0;
while (row_total_count < height) {
int row_count = jpeg_read_tile_scanline(cinfo,
index->index, &rowptr, startX, startY, width, height);
// 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, *bitmap, "read_scanlines");
}
if (this->shouldCancelDecode()) {
return return_false(*cinfo, *bitmap, "shouldCancelDecode");
}
row_total_count += row_count;
rowptr += bpr;
}
cropBitmap(bm, bitmap, actualSampleSize, oriStartX, oriStartY,
oriWidth, oriHeight, startX, startY);
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;
#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(width, height, skiaSampleSize);
bitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
bitmap->setIsOpaque(true);
if (!this->allocPixelRef(bitmap, NULL)) {
return return_false(*cinfo, *bitmap, "allocPixelRef");
}
SkAutoLockPixels alp(*bitmap);
if (!sampler.begin(bitmap, sc, this->getDitherImage())) {
return return_false(*cinfo, *bitmap, "sampler.begin");
}
uint8_t* srcRow = (uint8_t*)srcStorage.alloc(width * 4);
// Possibly skip initial rows [sampler.srcY0]
if (!skip_src_rows_tile(cinfo, index->index, srcRow,
startX, startY, width, height, 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, index->index, &rowptr,
startX, startY, width, height);
if (0 == row_count) {
return return_false(*cinfo, *bitmap, "read_scanlines");
}
if (this->shouldCancelDecode()) {
return return_false(*cinfo, *bitmap, "shouldCancelDecode");
}
sampler.next(srcRow);
if (bitmap->height() - 1 == y) {
// we're done
break;
}
if (!skip_src_rows_tile(cinfo, index->index, srcRow,
startX, startY, width, height,
sampler.srcDY() - 1)) {
return return_false(*cinfo, *bitmap, "skip rows");
}
}
cropBitmap(bm, bitmap, actualSampleSize, oriStartX, oriStartY,
oriWidth, oriHeight, startX, startY);
return true;
}
///////////////////////////////////////////////////////////////////////////////
#include "SkColorPriv.h"
......
......@@ -21,6 +21,24 @@ 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;
src->current_offset = 0;
src->fStream->rewind();
}
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;
}
static boolean sk_fill_input_buffer(j_decompress_ptr cinfo) {
......@@ -35,6 +53,7 @@ static boolean sk_fill_input_buffer(j_decompress_ptr cinfo) {
return FALSE;
}
src->current_offset += bytes;
src->next_input_byte = (const JOCTET*)src->fBuffer;
src->bytes_in_buffer = bytes;
return TRUE;
......@@ -52,6 +71,7 @@ static void sk_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
cinfo->err->error_exit((j_common_ptr)cinfo);
return;
}
src->current_offset += bytes;
bytesToSkip -= bytes;
}
src->next_input_byte = (const JOCTET*)src->fBuffer;
......@@ -83,7 +103,9 @@ static void sk_term_source(j_decompress_ptr /*cinfo*/) {}
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->start_input_byte = (const JOCTET*)src->fMemoryBase;
src->bytes_in_buffer = src->fMemoryBaseSize;
src->current_offset = src->fMemoryBaseSize;
}
static boolean skmem_fill_input_buffer(j_decompress_ptr cinfo) {
......@@ -108,18 +130,23 @@ static void skmem_term_source(j_decompress_ptr /*cinfo*/) {}
///////////////////////////////////////////////////////////////////////////////
skjpeg_source_mgr::skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder) : fStream(stream) {
skjpeg_source_mgr::skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder,
bool copyStream, bool ownStream) : fStream(stream) {
fDecoder = decoder;
const void* baseAddr = stream->getMemoryBase();
if (baseAddr && false) {
fMemoryBase = baseAddr;
fMemoryBase = NULL;
fUnrefStream = ownStream;
if (copyStream) {
fMemoryBaseSize = stream->getLength();
fMemoryBase = sk_malloc_throw(fMemoryBaseSize);
stream->read(fMemoryBase, fMemoryBaseSize);
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;
seek_input_data = NULL;
} else {
fMemoryBase = NULL;
fMemoryBaseSize = 0;
......@@ -129,10 +156,20 @@ 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;
seek_input_data = sk_seek_input_data;
}
// SkDebugf("**************** use memorybase %p %d\n", fMemoryBase, fMemoryBaseSize);
}
skjpeg_source_mgr::~skjpeg_source_mgr() {
if (fMemoryBase) {
sk_free(fMemoryBase);
}
if (fUnrefStream) {
fStream->unref();
}
}
///////////////////////////////////////////////////////////////////////////////
static void sk_init_destination(j_compress_ptr cinfo) {
......
#include "SkLargeBitmap.h"
bool SkLargeBitmap::decodeRegion(SkBitmap* bitmap, SkIRect rect,
SkBitmap::Config pref, int sampleSize) {
fDecoder->setSampleSize(sampleSize);
return fDecoder->decodeRegion(bitmap, rect, pref);
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment