Upstream Android modifications to the image encoders/decoders.

This CL does not update the libjpeg as that change is large enough
to warrant its own CL.


Author: djsollen@google.com

Reviewed By: reed@google.com,robertphillips@google.com,scroggo@google.com

Review URL: https://chromiumcodereview.appspot.com/12604006

git-svn-id: http://skia.googlecode.com/svn/trunk@8155 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
commit-bot@chromium.org 2013-03-14 14:42:18 +00:00
parent 5439ea2e19
commit a936e37cc7
14 changed files with 1586 additions and 238 deletions

1
DEPS
View File

@ -15,6 +15,7 @@ deps = {
"third_party/externals/libjpeg" : "http://src.chromium.org/svn/trunk/src/third_party/libjpeg@125399",
"third_party/externals/jsoncpp" : "http://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk/jsoncpp@248",
"third_party/externals/jsoncpp-chromium" : "http://src.chromium.org/svn/trunk/src/third_party/jsoncpp@125399",
"third_party/externals/libwebp" : "http://src.chromium.org/svn/trunk/src/third_party/libwebp@186718",
}
#hooks = [

View File

@ -7,6 +7,7 @@
'standalone_static_library': 1,
'dependencies': [
'libjpeg.gyp:*',
'libwebp.gyp:libwebp',
'utils.gyp:utils',
],
'export_dependent_settings': [
@ -31,13 +32,17 @@
'../src/images/bmpdecoderhelper.cpp',
'../src/images/bmpdecoderhelper.h',
'../src/images/SkBitmapRegionDecoder.cpp',
'../src/images/SkImageDecoder.cpp',
'../src/images/SkImageDecoder_Factory.cpp',
'../src/images/SkImageDecoder_libjpeg.cpp',
'../src/images/SkImageDecoder_libbmp.cpp',
'../src/images/SkImageDecoder_libgif.cpp',
'../src/images/SkImageDecoder_libico.cpp',
'../src/images/SkImageDecoder_libjpeg.cpp',
'../src/images/SkImageDecoder_libpng.cpp',
'../src/images/SkImageDecoder_libwebp.cpp',
'../src/images/SkImageDecoder_wbmp.cpp',
'../src/images/SkImageEncoder.cpp',
'../src/images/SkImageEncoder_Factory.cpp',

174
gyp/libwebp.gyp Normal file
View File

@ -0,0 +1,174 @@
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
{
'variables': {
'use_system_libwebp%': 0,
},
'conditions': [
['use_system_libwebp==0', {
'targets': [
{
'target_name': 'libwebp_dec',
'type': 'static_library',
'include_dirs': [
'../third_party/externals/libwebp',
],
'sources': [
'../third_party/externals/libwebp/dec/alpha.c',
'../third_party/externals/libwebp/dec/buffer.c',
'../third_party/externals/libwebp/dec/frame.c',
'../third_party/externals/libwebp/dec/idec.c',
'../third_party/externals/libwebp/dec/io.c',
'../third_party/externals/libwebp/dec/layer.c',
'../third_party/externals/libwebp/dec/quant.c',
'../third_party/externals/libwebp/dec/tree.c',
'../third_party/externals/libwebp/dec/vp8.c',
'../third_party/externals/libwebp/dec/vp8l.c',
'../third_party/externals/libwebp/dec/webp.c',
],
'cflags!': [
'-fno-rtti', # supresses warnings about invalid option of non-C++ code
],
},
{
'target_name': 'libwebp_dsp',
'type': 'static_library',
'include_dirs': [
'../third_party/externals/libwebp',
],
'sources': [
'../third_party/externals/libwebp/dsp/cpu.c',
'../third_party/externals/libwebp/dsp/dec.c',
'../third_party/externals/libwebp/dsp/dec_sse2.c',
'../third_party/externals/libwebp/dsp/enc.c',
'../third_party/externals/libwebp/dsp/enc_sse2.c',
'../third_party/externals/libwebp/dsp/lossless.c',
'../third_party/externals/libwebp/dsp/upsampling.c',
'../third_party/externals/libwebp/dsp/upsampling_sse2.c',
'../third_party/externals/libwebp/dsp/yuv.c',
],
'cflags!': [
'-fno-rtti', # supresses warnings about invalid option of non-C++ code
],
'conditions': [
['skia_os == "android"', {
'dependencies' : [
'android_deps.gyp:cpu_features',
],
}],
],
},
{
'target_name': 'libwebp_dsp_neon',
'conditions': [
['armv7 == 1', {
'type': 'static_library',
'include_dirs': [
'../third_party/externals/libwebp',
],
'sources': [
'../third_party/externals/libwebp/dsp/dec_neon.c',
],
# behavior similar dsp_neon.c.neon in an Android.mk
'cflags!': [
'-mfpu=vfpv3-d16',
'-fno-rtti', # supresses warnings about invalid option of non-C++ code
],
'cflags': [ '-mfpu=neon' ],
},{ # "armv7 != 1"
'type': 'none',
}],
],
},
{
'target_name': 'libwebp_enc',
'type': 'static_library',
'include_dirs': [
'../third_party/externals/libwebp',
],
'sources': [
'../third_party/externals/libwebp/enc/alpha.c',
'../third_party/externals/libwebp/enc/analysis.c',
'../third_party/externals/libwebp/enc/backward_references.c',
'../third_party/externals/libwebp/enc/config.c',
'../third_party/externals/libwebp/enc/cost.c',
'../third_party/externals/libwebp/enc/filter.c',
'../third_party/externals/libwebp/enc/frame.c',
'../third_party/externals/libwebp/enc/histogram.c',
'../third_party/externals/libwebp/enc/iterator.c',
'../third_party/externals/libwebp/enc/layer.c',
'../third_party/externals/libwebp/enc/picture.c',
'../third_party/externals/libwebp/enc/quant.c',
'../third_party/externals/libwebp/enc/syntax.c',
'../third_party/externals/libwebp/enc/tree.c',
'../third_party/externals/libwebp/enc/vp8l.c',
'../third_party/externals/libwebp/enc/webpenc.c',
],
'cflags!': [
'-fno-rtti', # supresses warnings about invalid option of non-C++ code
],
},
{
'target_name': 'libwebp_utils',
'type': 'static_library',
'include_dirs': [
'../third_party/externals/libwebp',
],
'sources': [
'../third_party/externals/libwebp/utils/bit_reader.c',
'../third_party/externals/libwebp/utils/bit_writer.c',
'../third_party/externals/libwebp/utils/color_cache.c',
'../third_party/externals/libwebp/utils/filters.c',
'../third_party/externals/libwebp/utils/huffman.c',
'../third_party/externals/libwebp/utils/huffman_encode.c',
'../third_party/externals/libwebp/utils/quant_levels.c',
'../third_party/externals/libwebp/utils/rescaler.c',
'../third_party/externals/libwebp/utils/thread.c',
'../third_party/externals/libwebp/utils/utils.c',
],
'cflags!': [
'-fno-rtti', # supresses warnings about invalid option of non-C++ code
],
},
{
'target_name': 'libwebp',
'type': 'none',
'dependencies' : [
'libwebp_dec',
'libwebp_dsp',
'libwebp_dsp_neon',
'libwebp_enc',
'libwebp_utils',
],
'direct_dependent_settings': {
'include_dirs': [
'../third_party/externals/libwebp',
],
},
'conditions': [
['OS!="win"', {'product_name': 'webp'}],
],
},
],
}, {
'targets': [
{
'target_name': 'libwebp',
'type': 'none',
'direct_dependent_settings': {
'defines': [
'ENABLE_WEBP',
],
},
'link_settings': {
'libraries': [
'-lwebp',
],
},
}
],
}],
],
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2011 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 SkBitmapRegionDecoder_DEFINED
#define SkBitmapRegionDecoder_DEFINED
#include "SkBitmap.h"
#include "SkImageDecoder.h"
#include "SkStream.h"
class SkIRect;
/**
* SkBitmapRegionDecoder can be used to decode a specified rect from an image.
* This is particularly useful when the original image is large and you only
* need parts of the image.
*
* However, not all image codecs on all platforms support this feature so be
* prepared to fallback to standard decoding if decodeRegion(...) returns false.
*/
class SkBitmapRegionDecoder {
public:
SkBitmapRegionDecoder(SkImageDecoder* decoder, SkStream* stream,
int width, int height) {
fDecoder = decoder;
fStream = stream;
fWidth = width;
fHeight = height;
}
~SkBitmapRegionDecoder() {
SkDELETE(fDecoder);
SkSafeUnref(fStream);
}
bool decodeRegion(SkBitmap* bitmap, const SkIRect& rect,
SkBitmap::Config pref, int sampleSize);
SkImageDecoder* getDecoder() const { return fDecoder; }
int getWidth() const { return fWidth; }
int getHeight() const { return fHeight; }
private:
SkImageDecoder* fDecoder;
SkStream* fStream;
int fWidth;
int fHeight;
};
#endif

View File

@ -13,6 +13,7 @@
#include "SkBitmap.h"
#include "SkBitmapFactory.h"
#include "SkImage.h"
#include "SkRect.h"
#include "SkRefCnt.h"
class SkStream;
@ -25,6 +26,7 @@ class SkImageDecoder {
public:
virtual ~SkImageDecoder();
// Should be consistent with kFormatName
enum Format {
kUnknown_Format,
kBMP_Format,
@ -33,14 +35,19 @@ public:
kJPEG_Format,
kPNG_Format,
kWBMP_Format,
kWEBP_Format,
kLastKnownFormat = kWBMP_Format
kLastKnownFormat = kWEBP_Format
};
/** Return the compressed data's format (see Format enum)
*/
virtual Format getFormat() const;
/** Return the compressed data's format name.
*/
const char* getFormatName() const;
/** Returns true if the decoder should try to dither the resulting image.
The default setting is true.
*/
@ -51,6 +58,20 @@ public:
*/
void setDitherImage(bool dither) { fDitherImage = dither; }
/** Returns true if the decoder should try to decode the
resulting image to a higher quality even at the expense of
the decoding speed.
*/
bool getPreferQualityOverSpeed() const { return fPreferQualityOverSpeed; }
/** Set to true if the the decoder should try to decode the
resulting image to a higher quality even at the expense of
the decoding speed.
*/
void setPreferQualityOverSpeed(bool qualityOverSpeed) {
fPreferQualityOverSpeed = qualityOverSpeed;
}
/** \class Peeker
Base class for optional callbacks to retrieve meta/chunk data out of
@ -175,11 +196,29 @@ public:
note: document use of Allocator, Peeker and Chooser
*/
bool decode(SkStream*, SkBitmap* bitmap, SkBitmap::Config pref, Mode);
bool decode(SkStream* stream, SkBitmap* bitmap, Mode mode) {
return this->decode(stream, bitmap, SkBitmap::kNo_Config, mode);
bool decode(SkStream*, SkBitmap* bitmap, SkBitmap::Config pref, Mode, bool reuseBitmap = false);
bool decode(SkStream* stream, SkBitmap* bitmap, Mode mode, bool reuseBitmap = false) {
return this->decode(stream, bitmap, SkBitmap::kNo_Config, mode, reuseBitmap);
}
/**
* 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.
*/
bool buildTileIndex(SkStream*, int *width, int *height);
/**
* 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.
*/
bool decodeRegion(SkBitmap* bitmap, const 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.
*/
@ -296,6 +335,38 @@ protected:
// must be overridden in subclasses. This guy is called by decode(...)
virtual bool onDecode(SkStream*, SkBitmap* bitmap, Mode) = 0;
// If the decoder wants to support tiled based decoding,
// this method must be overridden. This guy is called by buildTileIndex(...)
virtual bool onBuildTileIndex(SkStream*, int *width, int *height) {
return false;
}
// If the decoder wants to support tiled based decoding,
// this method must be overridden. This guy is called by decodeRegion(...)
virtual bool onDecodeRegion(SkBitmap* bitmap, const SkIRect& rect) {
return false;
}
/*
* Crop a rectangle from the src Bitmap to the dest Bitmap. src and dst are
* both sampled by sampleSize from an original Bitmap.
*
* @param dst 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 (dstX, dstY) the upper-left point of the dest bitmap in terms of
* the coordinate in the original bitmap.
* @param (width, height) the width and height of the unsampled dst.
* @param (srcX, srcY) the upper-left point of the src bitimap in terms of
* the coordinate in the original bitmap.
*/
void cropBitmap(SkBitmap *dst, SkBitmap *src, int sampleSize,
int dstX, int dstY, int width, int height,
int srcX, int srcY);
/** 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.
@ -346,6 +417,14 @@ private:
bool fDitherImage;
bool fUsePrefTable;
mutable bool fShouldCancelDecode;
bool fPreferQualityOverSpeed;
/** 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* sFormatName[];
// illegal
SkImageDecoder(const SkImageDecoder&);
@ -396,5 +475,6 @@ DECLARE_DECODER_CREATOR(ICOImageDecoder);
DECLARE_DECODER_CREATOR(JPEGImageDecoder);
DECLARE_DECODER_CREATOR(PNGImageDecoder);
DECLARE_DECODER_CREATOR(WBMPImageDecoder);
DECLARE_DECODER_CREATOR(WEBPImageDecoder);
#endif

View File

@ -17,7 +17,8 @@ class SkImageEncoder {
public:
enum Type {
kJPEG_Type,
kPNG_Type
kPNG_Type,
kWEBP_Type
};
static SkImageEncoder* Create(Type);
@ -80,5 +81,6 @@ protected:
// not all of these will be available
DECLARE_ENCODER_CREATOR(JPEGImageEncoder);
DECLARE_ENCODER_CREATOR(PNGImageEncoder);
DECLARE_ENCODER_CREATOR(WEBPImageEncoder);
#endif

View File

@ -0,0 +1,14 @@
/*
* Copyright 2011 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.
*/
#include "SkBitmapRegionDecoder.h"
bool SkBitmapRegionDecoder::decodeRegion(SkBitmap* bitmap, const SkIRect& rect,
SkBitmap::Config pref, int sampleSize) {
fDecoder->setSampleSize(sampleSize);
return fDecoder->decodeRegion(bitmap, rect, pref);
}

View File

@ -12,11 +12,23 @@
#include "SkPixelRef.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkCanvas.h"
SK_DEFINE_INST_COUNT(SkImageDecoder::Peeker)
SK_DEFINE_INST_COUNT(SkImageDecoder::Chooser)
SK_DEFINE_INST_COUNT(SkImageDecoderFactory)
const char *SkImageDecoder::sFormatName[] = {
"Unknown Format",
"BMP",
"GIF",
"ICO",
"JPEG",
"PNG",
"WBMP",
"WEBP",
};
static SkBitmap::Config gDeviceConfig = SkBitmap::kNo_Config;
SkBitmap::Config SkImageDecoder::GetDeviceConfig()
@ -34,7 +46,7 @@ void SkImageDecoder::SetDeviceConfig(SkBitmap::Config config)
SkImageDecoder::SkImageDecoder()
: fPeeker(NULL), fChooser(NULL), fAllocator(NULL), fSampleSize(1),
fDefaultPref(SkBitmap::kNo_Config), fDitherImage(true),
fUsePrefTable(false) {
fUsePrefTable(false),fPreferQualityOverSpeed(false) {
}
SkImageDecoder::~SkImageDecoder() {
@ -47,6 +59,11 @@ SkImageDecoder::Format SkImageDecoder::getFormat() const {
return kUnknown_Format;
}
const char* SkImageDecoder::getFormatName() const {
SkASSERT(SK_ARRAY_COUNT(sFormatName) == kLastKnownFormat);
return sFormatName[this->getFormat()];
}
SkImageDecoder::Peeker* SkImageDecoder::setPeeker(Peeker* peeker) {
SkRefCnt_SafeAssign(fPeeker, peeker);
return peeker;
@ -129,16 +146,22 @@ SkBitmap::Config SkImageDecoder::getPrefConfig(SrcDepth srcDepth,
}
bool SkImageDecoder::decode(SkStream* stream, SkBitmap* bm,
SkBitmap::Config pref, Mode mode) {
// pass a temporary bitmap, so that if we return false, we are assured of
// leaving the caller's bitmap untouched.
SkBitmap tmp;
SkBitmap::Config pref, Mode mode, bool reuseBitmap) {
// we reset this to false before calling onDecode
fShouldCancelDecode = false;
// assign this, for use by getPrefConfig(), in case fUsePrefTable is false
fDefaultPref = pref;
if (reuseBitmap) {
SkAutoLockPixels alp(*bm);
if (NULL != bm->getPixels()) {
return this->onDecode(stream, bm, mode);
}
}
// pass a temporary bitmap, so that if we return false, we are assured of
// leaving the caller's bitmap untouched.
SkBitmap tmp;
if (!this->onDecode(stream, &tmp, mode)) {
return false;
}
@ -146,6 +169,55 @@ bool SkImageDecoder::decode(SkStream* stream, SkBitmap* bm,
return true;
}
bool SkImageDecoder::decodeRegion(SkBitmap* bm, const SkIRect& rect,
SkBitmap::Config pref) {
// we reset this to false before calling onDecodeRegion
fShouldCancelDecode = false;
// assign this, for use by getPrefConfig(), in case fUsePrefTable is false
fDefaultPref = pref;
return this->onDecodeRegion(bm, rect);
}
bool SkImageDecoder::buildTileIndex(SkStream* stream,
int *width, int *height) {
// we reset this to false before calling onBuildTileIndex
fShouldCancelDecode = false;
return this->onBuildTileIndex(stream, width, height);
}
void SkImageDecoder::cropBitmap(SkBitmap *dst, SkBitmap *src, int sampleSize,
int dstX, int dstY, int width, int height,
int srcX, int srcY) {
int w = width / sampleSize;
int h = height / sampleSize;
// if the destination has no pixels then we must allocate them.
if (dst->isNull()) {
dst->setConfig(src->getConfig(), w, h);
dst->setIsOpaque(src->isOpaque());
if (!this->allocPixelRef(dst, NULL)) {
SkDEBUGF(("failed to allocate pixels needed to crop the bitmap"));
return;
}
}
// check to see if the destination is large enough to decode the desired
// region. If this assert fails we will just draw as much of the source
// into the destination that we can.
SkASSERT(dst->width() >= w && dst->height() >= h);
// Set the Src_Mode for the paint to prevent transparency issue in the
// dest in the event that the dest was being re-used.
SkPaint paint;
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
SkCanvas canvas(*dst);
canvas.drawSprite(*src, (srcX - dstX) / sampleSize,
(srcY - dstY) / sampleSize,
&paint);
}
///////////////////////////////////////////////////////////////////////////////
bool SkImageDecoder::DecodeFile(const char file[], SkBitmap* bm,

View File

@ -19,12 +19,15 @@ class SkBMPImageDecoder : public SkImageDecoder {
public:
SkBMPImageDecoder() {}
virtual Format getFormat() const {
virtual Format getFormat() const SK_OVERRIDE {
return kBMP_Format;
}
protected:
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode);
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode) SK_OVERRIDE;
private:
typedef SkImageDecoder INHERITED;
};
///////////////////////////////////////////////////////////////////////////////
@ -115,11 +118,18 @@ bool SkBMPImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
SkScaledBitmapSampler sampler(width, height, getSampleSize());
bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
bm->setIsOpaque(true);
if (justBounds) {
bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
bm->setIsOpaque(true);
return true;
}
// No Bitmap reuse supported for this format
if (!bm->isNull()) {
return false;
}
bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight());
bm->setIsOpaque(true);
if (!this->allocPixelRef(bm, NULL)) {
return false;

View File

@ -18,12 +18,15 @@
class SkGIFImageDecoder : public SkImageDecoder {
public:
virtual Format getFormat() const {
virtual Format getFormat() const SK_OVERRIDE {
return kGIF_Format;
}
protected:
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode);
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode mode) SK_OVERRIDE;
private:
typedef SkImageDecoder INHERITED;
};
static const uint8_t gStartingIterlaceYValue[] = {
@ -201,11 +204,18 @@ bool SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, Mode mode) {
width, height)) {
return error_return(gif, *bm, "chooseFromOneChoice");
}
if (SkImageDecoder::kDecodeBounds_Mode == mode) {
bm->setConfig(SkBitmap::kIndex8_Config, width, height);
return true;
}
// No Bitmap reuse supported for this format
if (!bm->isNull()) {
return false;
}
bm->setConfig(SkBitmap::kIndex8_Config, width, height);
if (SkImageDecoder::kDecodeBounds_Mode == mode)
return true;
SavedImage* image = &gif->SavedImages[gif->ImageCount-1];
const GifImageDesc& desc = image->ImageDesc;

View File

@ -16,19 +16,16 @@ class SkICOImageDecoder : public SkImageDecoder {
public:
SkICOImageDecoder();
virtual Format getFormat() const {
virtual Format getFormat() const SK_OVERRIDE {
return kICO_Format;
}
protected:
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
};
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
#if 0 // UNUSED
SkImageDecoder* SkCreateICOImageDecoder() {
return new SkICOImageDecoder;
}
#endif
private:
typedef SkImageDecoder INHERITED;
};
/////////////////////////////////////////////////////////////////////////////////////////
@ -235,12 +232,16 @@ bool SkICOImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode)
//if the andbitmap (mask) is all zeroes, then we can easily do an index bitmap
//however, with small images with large colortables, maybe it's better to still do argb_8888
bm->setConfig(SkBitmap::kARGB_8888_Config, w, h, calculateRowBytesFor8888(w, bitCount));
if (SkImageDecoder::kDecodeBounds_Mode == mode) {
bm->setConfig(SkBitmap::kARGB_8888_Config, w, h, calculateRowBytesFor8888(w, bitCount));
delete[] colors;
return true;
}
// No Bitmap reuse supported for this format
if (!bm->isNull()) {
return false;
}
bm->setConfig(SkBitmap::kARGB_8888_Config, w, h, calculateRowBytesFor8888(w, bitCount));
if (!this->allocPixelRef(bm, NULL))
{

View File

@ -23,14 +23,52 @@ extern "C" {
#include "png.h"
}
class SkPNGImageIndex {
public:
SkPNGImageIndex(png_structp png_ptr, png_infop info_ptr) {
this->png_ptr = png_ptr;
this->info_ptr = info_ptr;
}
~SkPNGImageIndex() {
if (NULL != png_ptr) {
png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
}
}
png_structp png_ptr;
png_infop info_ptr;
};
class SkPNGImageDecoder : public SkImageDecoder {
public:
virtual Format getFormat() const {
SkPNGImageDecoder() {
fImageIndex = NULL;
}
virtual Format getFormat() const SK_OVERRIDE {
return kPNG_Format;
}
virtual ~SkPNGImageDecoder() {
SkDELETE(fImageIndex);
}
protected:
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
#ifdef SK_BUILD_FOR_ANDROID
virtual bool onBuildTileIndex(SkStream *stream, int *width, int *height) SK_OVERRIDE;
virtual bool onDecodeRegion(SkBitmap* bitmap, const SkIRect& region) SK_OVERRIDE;
#endif
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
private:
SkPNGImageIndex* fImageIndex;
bool onDecodeInit(SkStream* stream, png_structp *png_ptrp, png_infop *info_ptrp);
bool decodePalette(png_structp png_ptr, png_infop info_ptr, bool *hasAlphap,
bool *reallyHasAlphap, SkColorTable **colorTablep);
bool getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
SkBitmap::Config *config, bool *hasAlpha,
bool *doDither, SkPMColor *theTranspColor);
typedef SkImageDecoder INHERITED;
};
#ifndef png_jmpbuf
@ -43,7 +81,7 @@ protected:
struct PNGAutoClean {
PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
~PNGAutoClean() {
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
}
private:
png_structp png_ptr;
@ -51,13 +89,21 @@ private:
};
static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
SkStream* sk_stream = (SkStream*)png_get_io_ptr(png_ptr);
SkStream* sk_stream = (SkStream*) png_ptr->io_ptr;
size_t bytes = sk_stream->read(data, length);
if (bytes != length) {
png_error(png_ptr, "Read Error!");
}
}
#ifdef SK_BUILD_FOR_ANDROID
static void sk_seek_fn(png_structp png_ptr, png_uint_32 offset) {
SkStream* sk_stream = (SkStream*) png_ptr->io_ptr;
sk_stream->rewind();
(void)sk_stream->skip(offset);
}
#endif
static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
SkImageDecoder::Peeker* peeker =
(SkImageDecoder::Peeker*)png_get_user_chunk_ptr(png_ptr);
@ -67,16 +113,14 @@ static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
}
static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
#if 0
SkDebugf("------ png error %s\n", msg);
#endif
SkDEBUGF(("------ png error %s\n", msg));
longjmp(png_jmpbuf(png_ptr), 1);
}
static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
for (int i = 0; i < count; i++) {
uint8_t* tmp = storage;
png_read_rows(png_ptr, &tmp, NULL, 1);
png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
}
}
@ -128,10 +172,8 @@ static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
return false;
}
bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
Mode mode) {
// SkAutoTrace apr("SkPNGImageDecoder::onDecode");
bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
png_infop *info_ptrp) {
/* Create and initialize the png_struct with the desired error handler
* functions. If you want to use the default stderr and longjump method,
* you can supply NULL for the last three parameters. We also supply the
@ -143,15 +185,15 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
if (png_ptr == NULL) {
return false;
}
*png_ptrp = png_ptr;
/* Allocate/initialize the memory for image information. */
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
png_destroy_read_struct(&png_ptr, NULL, NULL);
png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
return false;
}
PNGAutoClean autoClean(png_ptr, info_ptr);
*info_ptrp = info_ptr;
/* Set error handling if you are using the setjmp/longjmp method (this is
* the normal method of doing things with libpng). REQUIRED unless you
@ -165,6 +207,9 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
* png_init_io() here you would call:
*/
png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
#ifdef SK_BUILD_FOR_ANDROID
png_set_seek_fn(png_ptr, sk_seek_fn);
#endif
/* where user_io_ptr is a structure you want available to the callbacks */
/* If we have already read some of the signature */
// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
@ -179,138 +224,77 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
* PNG file before the first IDAT (image data chunk). */
png_read_info(png_ptr, info_ptr);
png_uint_32 origWidth, origHeight;
int bit_depth, color_type, interlace_type;
png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bit_depth, &color_type,
&interlace_type, NULL, NULL);
int bitDepth, colorType;
png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
&colorType, int_p_NULL, int_p_NULL, int_p_NULL);
/* tell libpng to strip 16 bit/color files down to 8 bits/color */
if (bit_depth == 16) {
if (bitDepth == 16) {
png_set_strip_16(png_ptr);
}
/* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
* byte into separate bytes (useful for paletted and grayscale images). */
if (bit_depth < 8) {
if (bitDepth < 8) {
png_set_packing(png_ptr);
}
/* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
png_set_expand_gray_1_2_4_to_8(png_ptr);
if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
png_set_gray_1_2_4_to_8(png_ptr);
}
/* Make a grayscale image into RGB. */
if (color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
png_set_gray_to_rgb(png_ptr);
}
return true;
}
bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
Mode mode) {
png_structp png_ptr;
png_infop info_ptr;
if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
return false;
}
if (setjmp(png_jmpbuf(png_ptr))) {
return false;
}
PNGAutoClean autoClean(png_ptr, info_ptr);
png_uint_32 origWidth, origHeight;
int bitDepth, colorType, interlaceType;
png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
&colorType, &interlaceType, int_p_NULL, int_p_NULL);
SkBitmap::Config config;
bool hasAlpha = false;
bool doDither = this->getDitherImage();
SkPMColor theTranspColor = 0; // 0 tells us not to try to match
// check for sBIT chunk data, in case we should disable dithering because
// our data is not truely 8bits per component
if (doDither) {
png_color_8p sig_bit = NULL;
bool has_sbit = PNG_INFO_sBIT == png_get_sBIT(png_ptr, info_ptr,
&sig_bit);
#if 0
if (has_sbit) {
SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
sig_bit->blue, sig_bit->alpha);
}
#endif
// 0 seems to indicate no information available
if (has_sbit && pos_le(sig_bit->red, SK_R16_BITS) &&
pos_le(sig_bit->green, SK_G16_BITS) &&
pos_le(sig_bit->blue, SK_B16_BITS)) {
doDither = false;
}
}
if (color_type == PNG_COLOR_TYPE_PALETTE) {
bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
config = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
// now see if we can upscale to their requested config
if (!canUpscalePaletteToConfig(config, paletteHasAlpha)) {
config = SkBitmap::kIndex8_Config;
}
} else {
png_color_16p transpColor = NULL;
int numTransp = 0;
png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
if (valid && numTransp == 1 && transpColor != NULL) {
/* Compute our transparent color, which we'll match against later.
We don't really handle 16bit components properly here, since we
do our compare *after* the values have been knocked down to 8bit
which means we will find more matches than we should. The real
fix seems to be to see the actual 16bit components, do the
compare, and then knock it down to 8bits ourselves.
*/
if (color_type & PNG_COLOR_MASK_COLOR) {
if (16 == bit_depth) {
theTranspColor = SkPackARGB32(0xFF, transpColor->red >> 8,
transpColor->green >> 8, transpColor->blue >> 8);
} else {
theTranspColor = SkPackARGB32(0xFF, transpColor->red,
transpColor->green, transpColor->blue);
}
} else { // gray
if (16 == bit_depth) {
theTranspColor = SkPackARGB32(0xFF, transpColor->gray >> 8,
transpColor->gray >> 8, transpColor->gray >> 8);
} else {
theTranspColor = SkPackARGB32(0xFF, transpColor->gray,
transpColor->gray, transpColor->gray);
}
}
}
if (valid ||
PNG_COLOR_TYPE_RGB_ALPHA == color_type ||
PNG_COLOR_TYPE_GRAY_ALPHA == color_type) {
hasAlpha = true;
}
config = this->getPrefConfig(k32Bit_SrcDepth, hasAlpha);
// now match the request against our capabilities
if (hasAlpha) {
if (config != SkBitmap::kARGB_4444_Config) {
config = SkBitmap::kARGB_8888_Config;
}
} else {
if (config != SkBitmap::kRGB_565_Config &&
config != SkBitmap::kARGB_4444_Config) {
config = SkBitmap::kARGB_8888_Config;
}
}
}
// sanity check for size
{
Sk64 size;
size.setMul(origWidth, origHeight);
if (size.isNeg() || !size.is32()) {
return false;
}
// now check that if we are 4-bytes per pixel, we also don't overflow
if (size.get32() > (0x7FFFFFFF >> 2)) {
return false;
}
}
if (!this->chooseFromOneChoice(config, origWidth, origHeight)) {
if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
return false;
}
const int sampleSize = this->getSampleSize();
SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
decodedBitmap->setConfig(config, sampler.scaledWidth(),
sampler.scaledHeight(), 0);
decodedBitmap->lockPixels();
void* rowptr = (void*) decodedBitmap->getPixels();
bool reuseBitmap = (rowptr != NULL);
decodedBitmap->unlockPixels();
if (reuseBitmap && (sampler.scaledWidth() != decodedBitmap->width() ||
sampler.scaledHeight() != decodedBitmap->height())) {
// Dimensions must match
return false;
}
if (!reuseBitmap) {
decodedBitmap->setConfig(config, sampler.scaledWidth(),
sampler.scaledHeight(), 0);
}
if (SkImageDecoder::kDecodeBounds_Mode == mode) {
return true;
}
@ -323,91 +307,36 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
bool reallyHasAlpha = false;
SkColorTable* colorTable = NULL;
if (color_type == PNG_COLOR_TYPE_PALETTE) {
int num_palette;
png_colorp palette;
png_bytep trans;
int num_trans;
png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
/* BUGGY IMAGE WORKAROUND
We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
which is a problem since we use the byte as an index. To work around this we grow
the colortable by 1 (if its < 256) and duplicate the last color into that slot.
*/
int colorCount = num_palette + (num_palette < 256);
colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
SkPMColor* colorPtr = colorTable->lockColors();
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
hasAlpha = (num_trans > 0);
} else {
num_trans = 0;
colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
}
// check for bad images that might make us crash
if (num_trans > num_palette) {
num_trans = num_palette;
}
int index = 0;
int transLessThanFF = 0;
for (; index < num_trans; index++) {
transLessThanFF |= (int)*trans - 0xFF;
*colorPtr++ = SkPreMultiplyARGB(*trans++, palette->red, palette->green, palette->blue);
palette++;
}
reallyHasAlpha |= (transLessThanFF < 0);
for (; index < num_palette; index++) {
*colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
palette++;
}
// see BUGGY IMAGE WORKAROUND comment above
if (num_palette < 256) {
*colorPtr = colorPtr[-1];
}
colorTable->unlockColors(true);
if (colorType == PNG_COLOR_TYPE_PALETTE) {
decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
}
SkAutoUnref aur(colorTable);
if (!this->allocPixelRef(decodedBitmap,
SkBitmap::kIndex8_Config == config ?
colorTable : NULL)) {
return false;
if (!reuseBitmap) {
if (!this->allocPixelRef(decodedBitmap,
SkBitmap::kIndex8_Config == config ? colorTable : NULL)) {
return false;
}
}
SkAutoLockPixels alp(*decodedBitmap);
/* swap the RGBA or GA data to ARGB or AG (or BGRA to ABGR) */
// if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
// ; // png_set_swap_alpha(png_ptr);
/* swap bytes of 16 bit files to least significant byte first */
// png_set_swap(png_ptr);
/* Add filler (or alpha) byte (before/after each RGB triplet) */
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY) {
if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY) {
png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
}
/* Turn on interlace handling. REQUIRED if you are not using
* png_read_image(). To see how to handle interlacing passes,
* see the png_read_row() method below:
* png_read_image(). To see how to handle interlacing passes,
* see the png_read_row() method below:
*/
const int number_passes = interlace_type != PNG_INTERLACE_NONE ?
png_set_interlace_handling(png_ptr) : 1;
const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
png_set_interlace_handling(png_ptr) : 1;
/* Optional call to gamma correct and add the background to the palette
* and update info structure. REQUIRED if you are expecting libpng to
* update the palette for you (ie you selected such a transform above).
* and update info structure. REQUIRED if you are expecting libpng to
* update the palette for you (ie you selected such a transform above).
*/
png_read_update_info(png_ptr, info_ptr);
@ -415,7 +344,7 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
for (int i = 0; i < number_passes; i++) {
for (png_uint_32 y = 0; y < origHeight; y++) {
uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
png_read_rows(png_ptr, &bmRow, NULL, 1);
png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
}
}
} else {
@ -444,21 +373,21 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
if (number_passes > 1) {
SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
uint8_t* base = (uint8_t*)storage.get();
size_t rb = origWidth * srcBytesPerPixel;
size_t rowBytes = origWidth * srcBytesPerPixel;
for (int i = 0; i < number_passes; i++) {
uint8_t* row = base;
for (png_uint_32 y = 0; y < origHeight; y++) {
uint8_t* bmRow = row;
png_read_rows(png_ptr, &bmRow, NULL, 1);
row += rb;
png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
row += rowBytes;
}
}
// now sample it
base += sampler.srcY0() * rb;
base += sampler.srcY0() * rowBytes;
for (int y = 0; y < height; y++) {
reallyHasAlpha |= sampler.next(base);
base += sampler.srcDY() * rb;
base += sampler.srcDY() * rowBytes;
}
} else {
SkAutoMalloc storage(origWidth * srcBytesPerPixel);
@ -467,7 +396,7 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
for (int y = 0; y < height; y++) {
uint8_t* tmp = srcRow;
png_read_rows(png_ptr, &tmp, NULL, 1);
png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reallyHasAlpha |= sampler.next(srcRow);
if (y < height - 1) {
skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
@ -489,16 +418,405 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
}
decodedBitmap->setIsOpaque(!reallyHasAlpha);
if (reuseBitmap) {
decodedBitmap->notifyPixelsChanged();
}
return true;
}
bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr,
SkBitmap::Config *configp, bool *hasAlphap,
bool *doDitherp, SkPMColor *theTranspColorp) {
png_uint_32 origWidth, origHeight;
int bitDepth, colorType;
png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
&colorType, int_p_NULL, int_p_NULL, int_p_NULL);
// check for sBIT chunk data, in case we should disable dithering because
// our data is not truely 8bits per component
if (*doDitherp) {
#if 0
SkDebugf("----- sBIT %d %d %d %d\n", info_ptr->sig_bit.red,
info_ptr->sig_bit.green, info_ptr->sig_bit.blue,
info_ptr->sig_bit.alpha);
#endif
// 0 seems to indicate no information available
if (pos_le(info_ptr->sig_bit.red, SK_R16_BITS) &&
pos_le(info_ptr->sig_bit.green, SK_G16_BITS) &&
pos_le(info_ptr->sig_bit.blue, SK_B16_BITS)) {
*doDitherp = false;
}
}
if (colorType == PNG_COLOR_TYPE_PALETTE) {
bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
*configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha);
// now see if we can upscale to their requested config
if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) {
*configp = SkBitmap::kIndex8_Config;
}
} else {
png_color_16p transpColor = NULL;
int numTransp = 0;
png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor);
bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
if (valid && numTransp == 1 && transpColor != NULL) {
/* Compute our transparent color, which we'll match against later.
We don't really handle 16bit components properly here, since we
do our compare *after* the values have been knocked down to 8bit
which means we will find more matches than we should. The real
fix seems to be to see the actual 16bit components, do the
compare, and then knock it down to 8bits ourselves.
*/
if (colorType & PNG_COLOR_MASK_COLOR) {
if (16 == bitDepth) {
*theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
transpColor->green >> 8,
transpColor->blue >> 8);
} else {
*theTranspColorp = SkPackARGB32(0xFF, transpColor->red,
transpColor->green,
transpColor->blue);
}
} else { // gray
if (16 == bitDepth) {
*theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
transpColor->gray >> 8,
transpColor->gray >> 8);
} else {
*theTranspColorp = SkPackARGB32(0xFF, transpColor->gray,
transpColor->gray,
transpColor->gray);
}
}
}
if (valid ||
PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
*hasAlphap = true;
}
*configp = this->getPrefConfig(k32Bit_SrcDepth, *hasAlphap);
// now match the request against our capabilities
if (*hasAlphap) {
if (*configp != SkBitmap::kARGB_4444_Config) {
*configp = SkBitmap::kARGB_8888_Config;
}
} else {
if (*configp != SkBitmap::kRGB_565_Config &&
*configp != SkBitmap::kARGB_4444_Config) {
*configp = SkBitmap::kARGB_8888_Config;
}
}
}
// sanity check for size
{
Sk64 size;
size.setMul(origWidth, origHeight);
if (size.isNeg() || !size.is32()) {
return false;
}
// now check that if we are 4-bytes per pixel, we also don't overflow
if (size.get32() > (0x7FFFFFFF >> 2)) {
return false;
}
}
return this->chooseFromOneChoice(*configp, origWidth, origHeight);
}
bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
bool *hasAlphap, bool *reallyHasAlphap,
SkColorTable **colorTablep) {
int numPalette;
png_colorp palette;
png_bytep trans;
int numTrans;
bool reallyHasAlpha = false;
SkColorTable* colorTable = NULL;
png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
/* BUGGY IMAGE WORKAROUND
We hit some images (e.g. fruit_.png) who contain bytes that are == colortable_count
which is a problem since we use the byte as an index. To work around this we grow
the colortable by 1 (if its < 256) and duplicate the last color into that slot.
*/
int colorCount = numPalette + (numPalette < 256);
colorTable = SkNEW_ARGS(SkColorTable, (colorCount));
SkPMColor* colorPtr = colorTable->lockColors();
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, NULL);
*hasAlphap = (numTrans > 0);
} else {
numTrans = 0;
colorTable->setFlags(colorTable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
}
// check for bad images that might make us crash
if (numTrans > numPalette) {
numTrans = numPalette;
}
int index = 0;
int transLessThanFF = 0;
for (; index < numTrans; index++) {
transLessThanFF |= (int)*trans - 0xFF;
*colorPtr++ = SkPreMultiplyARGB(*trans++, palette->red, palette->green, palette->blue);
palette++;
}
reallyHasAlpha |= (transLessThanFF < 0);
for (; index < numPalette; index++) {
*colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
palette++;
}
// see BUGGY IMAGE WORKAROUND comment above
if (numPalette < 256) {
*colorPtr = colorPtr[-1];
}
colorTable->unlockColors(true);
*colorTablep = colorTable;
*reallyHasAlphap = reallyHasAlpha;
return true;
}
#ifdef SK_BUILD_FOR_ANDROID
bool SkPNGImageDecoder::onBuildTileIndex(SkStream* sk_stream, int *width, int *height) {
png_structp png_ptr;
png_infop info_ptr;
if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
return false;
}
if (setjmp(png_jmpbuf(png_ptr)) != 0) {
png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
return false;
}
png_uint_32 origWidth, origHeight;
int bitDepth, colorType;
png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
&colorType, int_p_NULL, int_p_NULL, int_p_NULL);
*width = origWidth;
*height = origHeight;
png_build_index(png_ptr);
if (fImageIndex) {
SkDELETE(fImageIndex);
}
fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (png_ptr, info_ptr));
return true;
}
bool SkPNGImageDecoder::onDecodeRegion(SkBitmap* bm, const SkIRect& region) {
png_structp png_ptr = fImageIndex->png_ptr;
png_infop info_ptr = fImageIndex->info_ptr;
if (setjmp(png_jmpbuf(png_ptr))) {
return false;
}
png_uint_32 origWidth, origHeight;
int bitDepth, colorType, interlaceType;
png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
&colorType, &interlaceType, int_p_NULL, int_p_NULL);
SkIRect rect = SkIRect::MakeWH(origWidth, origHeight);
if (!rect.intersect(region)) {
// If the requested region is entirely outsides the image, just
// returns false
return false;
}
SkBitmap::Config config;
bool hasAlpha = false;
bool doDither = this->getDitherImage();
SkPMColor theTranspColor = 0; // 0 tells us not to try to match
if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) {
return false;
}
const int sampleSize = this->getSampleSize();
SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize);
SkBitmap decodedBitmap;
decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight(), 0);
// from here down we are concerned with colortables and pixels
// we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
// to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
// draw lots faster if we can flag the bitmap has being opaque
bool reallyHasAlpha = false;
SkColorTable* colorTable = NULL;
if (colorType == PNG_COLOR_TYPE_PALETTE) {
decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
}
SkAutoUnref aur(colorTable);
// Check ahead of time if the swap(dest, src) is possible.
// 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() / sampleSize;
int h = rect.height() / sampleSize;
const bool swapOnly = (rect == region) && (w == decodedBitmap.width()) &&
(h == decodedBitmap.height()) && bm->isNull();
const bool needColorTable = SkBitmap::kIndex8_Config == config;
if (swapOnly) {
if (!this->allocPixelRef(&decodedBitmap, needColorTable ? colorTable : NULL)) {
return false;
}
} else {
if (!decodedBitmap.allocPixels(NULL, needColorTable ? colorTable : NULL)) {
return false;
}
}
SkAutoLockPixels alp(decodedBitmap);
/* Add filler (or alpha) byte (before/after each RGB triplet) */
if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY) {
png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
}
/* Turn on interlace handling. REQUIRED if you are not using
* png_read_image(). To see how to handle interlacing passes,
* see the png_read_row() method below:
*/
const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
png_set_interlace_handling(png_ptr) : 1;
/* Optional call to gamma correct and add the background to the palette
* and update info structure. REQUIRED if you are expecting libpng to
* update the palette for you (ie you selected such a transform above).
*/
png_ptr->pass = 0;
png_read_update_info(png_ptr, info_ptr);
int actualTop = rect.fTop;
if (SkBitmap::kIndex8_Config == config && 1 == sampleSize) {
for (int i = 0; i < number_passes; i++) {
png_configure_decoder(png_ptr, &actualTop, i);
for (int j = 0; j < rect.fTop - actualTop; j++) {
uint8_t* bmRow = decodedBitmap.getAddr8(0, 0);
png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
}
for (png_uint_32 y = 0; y < origHeight; y++) {
uint8_t* bmRow = decodedBitmap.getAddr8(0, y);
png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
}
}
} else {
SkScaledBitmapSampler::SrcConfig sc;
int srcBytesPerPixel = 4;
if (colorTable != NULL) {
sc = SkScaledBitmapSampler::kIndex;
srcBytesPerPixel = 1;
} else if (hasAlpha) {
sc = SkScaledBitmapSampler::kRGBA;
} else {
sc = SkScaledBitmapSampler::kRGBX;
}
/* We have to pass the colortable explicitly, since we may have one
even if our decodedBitmap doesn't, due to the request that we
upscale png's palette to a direct model
*/
SkAutoLockColors ctLock(colorTable);
if (!sampler.begin(&decodedBitmap, sc, doDither, ctLock.colors())) {
return false;
}
const int height = decodedBitmap.height();
if (number_passes > 1) {
SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
uint8_t* base = (uint8_t*)storage.get();
size_t rb = origWidth * srcBytesPerPixel;
for (int i = 0; i < number_passes; i++) {
png_configure_decoder(png_ptr, &actualTop, i);
for (int j = 0; j < rect.fTop - actualTop; j++) {
uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
}
uint8_t* row = base;
for (int32_t y = 0; y < rect.height(); y++) {
uint8_t* bmRow = row;
png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
row += rb;
}
}
// now sample it
base += sampler.srcY0() * rb;
for (int y = 0; y < height; y++) {
reallyHasAlpha |= sampler.next(base);
base += sampler.srcDY() * rb;
}
} else {
SkAutoMalloc storage(origWidth * srcBytesPerPixel);
uint8_t* srcRow = (uint8_t*)storage.get();
png_configure_decoder(png_ptr, &actualTop, 0);
skip_src_rows(png_ptr, srcRow, sampler.srcY0());
for (int i = 0; i < rect.fTop - actualTop; i++) {
uint8_t* bmRow = (uint8_t*)decodedBitmap.getPixels();
png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
}
for (int y = 0; y < height; y++) {
uint8_t* tmp = srcRow;
png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
reallyHasAlpha |= sampler.next(srcRow);
if (y < height - 1) {
skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
}
}
}
}
if (0 != theTranspColor) {
reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor);
}
decodedBitmap.setIsOpaque(!reallyHasAlpha);
if (swapOnly) {
bm->swap(decodedBitmap);
} else {
cropBitmap(bm, &decodedBitmap, sampleSize, region.x(), region.y(),
region.width(), region.height(), 0, rect.y());
}
return true;
}
#endif
///////////////////////////////////////////////////////////////////////////////
#include "SkColorPriv.h"
#include "SkUnPreMultiply.h"
static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
SkWStream* sk_stream = (SkWStream*)png_ptr->io_ptr;
if (!sk_stream->write(data, len)) {
png_error(png_ptr, "sk_write_fn Error!");
}
@ -609,12 +927,14 @@ static inline int pack_palette(SkColorTable* ctable,
class SkPNGImageEncoder : public SkImageEncoder {
protected:
virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality);
virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
private:
bool doEncode(SkWStream* stream, const SkBitmap& bm,
const bool& hasAlpha, int colorType,
int bitDepth, SkBitmap::Config config,
png_color_8& sig_bit);
typedef SkImageEncoder INHERITED;
};
bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap,
@ -697,7 +1017,7 @@ bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
info_ptr = png_create_info_struct(png_ptr);
if (NULL == info_ptr) {
png_destroy_write_struct(&png_ptr, NULL);
png_destroy_write_struct(&png_ptr, png_infopp_NULL);
return false;
}
@ -709,7 +1029,7 @@ bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
return false;
}
png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, NULL);
png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);
/* Set the image information here. Width and height are up to 2^31,
* bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on

View File

@ -0,0 +1,595 @@
/*
* Copyright 2010, 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
*
* 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
* limitations under the License.
*/
#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkColorPriv.h"
#include "SkScaledBitmapSampler.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkUtils.h"
#include "SkTScopedPtr.h"
// A WebP decoder only, on top of (subset of) libwebp
// For more information on WebP image format, and libwebp library, see:
// http://code.google.com/speed/webp/
// http://www.webmproject.org/code/#libwebp_webp_image_decoder_library
// http://review.webmproject.org/gitweb?p=libwebp.git
#include <stdio.h>
extern "C" {
// If moving libwebp out of skia source tree, path for webp headers must be
// updated accordingly. Here, we enforce using local copy in webp sub-directory.
#include "webp/decode.h"
#include "webp/encode.h"
}
#ifdef ANDROID
#include <cutils/properties.h>
// Key to lookup the size of memory buffer set in system property
static const char KEY_MEM_CAP[] = "ro.media.dec.webp.memcap";
#endif
// this enables timing code to report milliseconds for a decode
//#define TIME_DECODE
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Define VP8 I/O on top of Skia stream
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static const size_t WEBP_VP8_HEADER_SIZE = 64;
static const size_t WEBP_IDECODE_BUFFER_SZ = (1 << 16);
// Parse headers of RIFF container, and check for valid Webp (VP8) content.
static bool webp_parse_header(SkStream* stream, int* width, int* height, int* alpha) {
unsigned char buffer[WEBP_VP8_HEADER_SIZE];
const uint32_t contentSize = stream->getLength();
const size_t len = stream->read(buffer, WEBP_VP8_HEADER_SIZE);
const uint32_t read_bytes =
(contentSize < WEBP_VP8_HEADER_SIZE) ? contentSize : WEBP_VP8_HEADER_SIZE;
if (len != read_bytes) {
return false; // can't read enough
}
WebPBitstreamFeatures features;
VP8StatusCode status = WebPGetFeatures(buffer, read_bytes, &features);
if (VP8_STATUS_OK != status) {
return false; // Invalid WebP file.
}
*width = features.width;
*height = features.height;
*alpha = features.has_alpha;
// sanity check for image size that's about to be decoded.
{
Sk64 size;
size.setMul(*width, *height);
if (size.isNeg() || !size.is32()) {
return false;
}
// now check that if we are 4-bytes per pixel, we also don't overflow
if (size.get32() > (0x7FFFFFFF >> 2)) {
return false;
}
}
return true;
}
class SkWEBPImageDecoder: public SkImageDecoder {
public:
SkWEBPImageDecoder() {
fInputStream = NULL;
fOrigWidth = 0;
fOrigHeight = 0;
fHasAlpha = 0;
}
virtual ~SkWEBPImageDecoder() {
SkSafeUnref(fInputStream);
}
virtual Format getFormat() const SK_OVERRIDE {
return kWEBP_Format;
}
protected:
virtual bool onBuildTileIndex(SkStream *stream, int *width, int *height) SK_OVERRIDE;
virtual bool onDecodeRegion(SkBitmap* bitmap, const SkIRect& rect) SK_OVERRIDE;
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
private:
bool setDecodeConfig(SkBitmap* decodedBitmap, int width, int height);
SkStream* fInputStream;
int fOrigWidth;
int fOrigHeight;
int fHasAlpha;
typedef SkImageDecoder INHERITED;
};
//////////////////////////////////////////////////////////////////////////
#ifdef TIME_DECODE
#include "SkTime.h"
class AutoTimeMillis {
public:
AutoTimeMillis(const char label[]) :
fLabel(label) {
if (NULL == fLabel) {
fLabel = "";
}
fNow = SkTime::GetMSecs();
}
~AutoTimeMillis() {
SkDebugf("---- Time (ms): %s %d\n", fLabel, SkTime::GetMSecs() - fNow);
}
private:
const char* fLabel;
SkMSec fNow;
};
#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 SkBitmap& bm, const char msg[]) {
SkDEBUGF(("libwebp error %s [%d %d]", msg, bm.width(), bm.height()));
return false; // must always return false
}
static WEBP_CSP_MODE webp_decode_mode(const SkBitmap* decodedBitmap, int hasAlpha) {
WEBP_CSP_MODE mode = MODE_LAST;
SkBitmap::Config config = decodedBitmap->config();
// For images that have alpha, choose appropriate color mode (MODE_rgbA,
// MODE_rgbA_4444) that pre-multiplies RGB pixel values with transparency
// factor (alpha).
if (config == SkBitmap::kARGB_8888_Config) {
mode = hasAlpha ? MODE_rgbA : MODE_RGBA;
} else if (config == SkBitmap::kARGB_4444_Config) {
mode = hasAlpha ? MODE_rgbA_4444 : MODE_RGBA_4444;
} else if (config == SkBitmap::kRGB_565_Config) {
mode = MODE_RGB_565;
}
SkASSERT(MODE_LAST != mode);
return mode;
}
// Incremental WebP image decoding. Reads input buffer of 64K size iteratively
// and decodes this block to appropriate color-space as per config object.
static bool webp_idecode(SkStream* stream, WebPDecoderConfig* config) {
WebPIDecoder* idec = WebPIDecode(NULL, 0, config);
if (NULL == idec) {
WebPFreeDecBuffer(&config->output);
return false;
}
stream->rewind();
const uint32_t contentSize = stream->getLength();
const uint32_t readBufferSize = (contentSize < WEBP_IDECODE_BUFFER_SZ) ?
contentSize : WEBP_IDECODE_BUFFER_SZ;
SkAutoMalloc srcStorage(readBufferSize);
unsigned char* input = (uint8_t*)srcStorage.get();
if (NULL == input) {
WebPIDelete(idec);
WebPFreeDecBuffer(&config->output);
return false;
}
uint32_t bytesRemaining = contentSize;
while (bytesRemaining > 0) {
const uint32_t bytesToRead = (bytesRemaining < WEBP_IDECODE_BUFFER_SZ) ?
bytesRemaining : WEBP_IDECODE_BUFFER_SZ;
const size_t bytesRead = stream->read(input, bytesToRead);
if (0 == bytesRead) {
break;
}
VP8StatusCode status = WebPIAppend(idec, input, bytesRead);
if (VP8_STATUS_OK == status || VP8_STATUS_SUSPENDED == status) {
bytesRemaining -= bytesRead;
} else {
break;
}
}
srcStorage.free();
WebPIDelete(idec);
WebPFreeDecBuffer(&config->output);
if (bytesRemaining > 0) {
return false;
} else {
return true;
}
}
static bool webp_get_config_resize(WebPDecoderConfig* config,
SkBitmap* decodedBitmap,
int width, int height, int hasAlpha) {
WEBP_CSP_MODE mode = webp_decode_mode(decodedBitmap, hasAlpha);
if (MODE_LAST == mode) {
return false;
}
if (0 == WebPInitDecoderConfig(config)) {
return false;
}
config->output.colorspace = mode;
config->output.u.RGBA.rgba = (uint8_t*)decodedBitmap->getPixels();
config->output.u.RGBA.stride = decodedBitmap->rowBytes();
config->output.u.RGBA.size = decodedBitmap->getSize();
config->output.is_external_memory = 1;
if (width != decodedBitmap->width() || height != decodedBitmap->height()) {
config->options.use_scaling = 1;
config->options.scaled_width = decodedBitmap->width();
config->options.scaled_height = decodedBitmap->height();
}
return true;
}
static bool webp_get_config_resize_crop(WebPDecoderConfig* config,
SkBitmap* decodedBitmap,
const SkIRect& region, int hasAlpha) {
if (!webp_get_config_resize(config, decodedBitmap, region.width(),
region.height(), hasAlpha)) {
return false;
}
config->options.use_cropping = 1;
config->options.crop_left = region.fLeft;
config->options.crop_top = region.fTop;
config->options.crop_width = region.width();
config->options.crop_height = region.height();
return true;
}
bool SkWEBPImageDecoder::setDecodeConfig(SkBitmap* decodedBitmap,
int width, int height) {
SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, fHasAlpha);
// YUV converter supports output in RGB565, RGBA4444 and RGBA8888 formats.
if (fHasAlpha) {
if (config != SkBitmap::kARGB_4444_Config) {
config = SkBitmap::kARGB_8888_Config;
}
} else {
if (config != SkBitmap::kRGB_565_Config &&
config != SkBitmap::kARGB_4444_Config) {
config = SkBitmap::kARGB_8888_Config;
}
}
if (!this->chooseFromOneChoice(config, width, height)) {
return false;
}
decodedBitmap->setConfig(config, width, height, 0);
decodedBitmap->setIsOpaque(!fHasAlpha);
return true;
}
bool SkWEBPImageDecoder::onBuildTileIndex(SkStream* stream,
int *width, int *height) {
int origWidth, origHeight, hasAlpha;
if (!webp_parse_header(stream, &origWidth, &origHeight, &hasAlpha)) {
return false;
}
stream->rewind();
*width = origWidth;
*height = origHeight;
SkRefCnt_SafeAssign(this->fInputStream, stream);
this->fOrigWidth = origWidth;
this->fOrigHeight = origHeight;
this->fHasAlpha = hasAlpha;
return true;
}
static bool is_config_compatible(const SkBitmap& bitmap) {
SkBitmap::Config config = bitmap.config();
return config == SkBitmap::kARGB_4444_Config ||
config == SkBitmap::kRGB_565_Config ||
config == SkBitmap::kARGB_8888_Config;
}
bool SkWEBPImageDecoder::onDecodeRegion(SkBitmap* decodedBitmap,
const SkIRect& region) {
SkIRect rect = SkIRect::MakeWH(fOrigWidth, fOrigHeight);
if (!rect.intersect(region)) {
// If the requested region is entirely outsides the image, return false
return false;
}
const int sampleSize = this->getSampleSize();
SkScaledBitmapSampler sampler(rect.width(), rect.height(), sampleSize);
const int width = sampler.scaledWidth();
const int height = sampler.scaledHeight();
// The image can be decoded directly to decodedBitmap if
// 1. the region is within the image range
// 2. bitmap's config is compatible
// 3. bitmap's size is same as the required region (after sampled)
bool directDecode = (rect == region) &&
(decodedBitmap->isNull() ||
(is_config_compatible(*decodedBitmap) &&
(decodedBitmap->width() == width) &&
(decodedBitmap->height() == height)));
SkTScopedPtr<SkBitmap> adb;
SkBitmap *bitmap = decodedBitmap;
if (!directDecode) {
// allocates a temp bitmap
bitmap = new SkBitmap;
adb.reset(bitmap);
}
if (bitmap->isNull()) {
if (!setDecodeConfig(bitmap, width, height)) {
return false;
}
// alloc from native heap if it is a temp bitmap. (prevent GC)
bool allocResult = (bitmap == decodedBitmap)
? allocPixelRef(bitmap, NULL)
: bitmap->allocPixels();
if (!allocResult) {
return return_false(*decodedBitmap, "allocPixelRef");
}
} else {
// This is also called in setDecodeConfig in above block.
// i.e., when bitmap->isNull() is true.
if (!chooseFromOneChoice(bitmap->config(), width, height)) {
return false;
}
}
SkAutoLockPixels alp(*bitmap);
WebPDecoderConfig config;
if (!webp_get_config_resize_crop(&config, bitmap, rect, fHasAlpha)) {
return false;
}
// Decode the WebP image data stream using WebP incremental decoding for
// the specified cropped image-region.
if (!webp_idecode(this->fInputStream, &config)) {
return false;
}
if (!directDecode) {
cropBitmap(decodedBitmap, bitmap, sampleSize, region.x(), region.y(),
region.width(), region.height(), rect.x(), rect.y());
}
return true;
}
bool SkWEBPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap,
Mode mode) {
#ifdef TIME_DECODE
AutoTimeMillis atm("WEBP Decode");
#endif
int origWidth, origHeight, hasAlpha;
if (!webp_parse_header(stream, &origWidth, &origHeight, &hasAlpha)) {
return false;
}
this->fHasAlpha = hasAlpha;
const int sampleSize = this->getSampleSize();
SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
// If only bounds are requested, done
if (SkImageDecoder::kDecodeBounds_Mode == mode) {
if (!setDecodeConfig(decodedBitmap, sampler.scaledWidth(),
sampler.scaledHeight())) {
return false;
}
return true;
}
// No Bitmap reuse supported for this format
if (!decodedBitmap->isNull()) {
return false;
}
if (!setDecodeConfig(decodedBitmap, sampler.scaledWidth(),
sampler.scaledHeight())) {
return false;
}
if (!this->allocPixelRef(decodedBitmap, NULL)) {
return return_false(*decodedBitmap, "allocPixelRef");
}
SkAutoLockPixels alp(*decodedBitmap);
WebPDecoderConfig config;
if (!webp_get_config_resize(&config, decodedBitmap, origWidth, origHeight,
hasAlpha)) {
return false;
}
// Decode the WebP image data stream using WebP incremental decoding.
return webp_idecode(stream, &config);
}
///////////////////////////////////////////////////////////////////////////////
typedef void (*ScanlineImporter)(const uint8_t* in, uint8_t* out, int width,
const SkPMColor* SK_RESTRICT ctable);
static void ARGB_8888_To_RGB(const uint8_t* in, uint8_t* rgb, int width,
const SkPMColor*) {
const uint32_t* SK_RESTRICT src = (const uint32_t*)in;
for (int i = 0; i < width; ++i) {
const uint32_t c = *src++;
rgb[0] = SkGetPackedR32(c);
rgb[1] = SkGetPackedG32(c);
rgb[2] = SkGetPackedB32(c);
rgb += 3;
}
}
static void RGB_565_To_RGB(const uint8_t* in, uint8_t* rgb, int width,
const SkPMColor*) {
const uint16_t* SK_RESTRICT src = (const uint16_t*)in;
for (int i = 0; i < width; ++i) {
const uint16_t c = *src++;
rgb[0] = SkPacked16ToR32(c);
rgb[1] = SkPacked16ToG32(c);
rgb[2] = SkPacked16ToB32(c);
rgb += 3;
}
}
static void ARGB_4444_To_RGB(const uint8_t* in, uint8_t* rgb, int width,
const SkPMColor*) {
const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)in;
for (int i = 0; i < width; ++i) {
const SkPMColor16 c = *src++;
rgb[0] = SkPacked4444ToR32(c);
rgb[1] = SkPacked4444ToG32(c);
rgb[2] = SkPacked4444ToB32(c);
rgb += 3;
}
}
static void Index8_To_RGB(const uint8_t* in, uint8_t* rgb, int width,
const SkPMColor* SK_RESTRICT ctable) {
const uint8_t* SK_RESTRICT src = (const uint8_t*)in;
for (int i = 0; i < width; ++i) {
const uint32_t c = ctable[*src++];
rgb[0] = SkGetPackedR32(c);
rgb[1] = SkGetPackedG32(c);
rgb[2] = SkGetPackedB32(c);
rgb += 3;
}
}
static ScanlineImporter ChooseImporter(const SkBitmap::Config& config) {
switch (config) {
case SkBitmap::kARGB_8888_Config:
return ARGB_8888_To_RGB;
case SkBitmap::kRGB_565_Config:
return RGB_565_To_RGB;
case SkBitmap::kARGB_4444_Config:
return ARGB_4444_To_RGB;
case SkBitmap::kIndex8_Config:
return Index8_To_RGB;
default:
return NULL;
}
}
static int stream_writer(const uint8_t* data, size_t data_size,
const WebPPicture* const picture) {
SkWStream* const stream = (SkWStream*)picture->custom_ptr;
return stream->write(data, data_size) ? 1 : 0;
}
class SkWEBPImageEncoder : public SkImageEncoder {
protected:
virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
private:
typedef SkImageEncoder INHERITED;
};
bool SkWEBPImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bm,
int quality) {
const SkBitmap::Config config = bm.getConfig();
const ScanlineImporter scanline_import = ChooseImporter(config);
if (NULL == scanline_import) {
return false;
}
SkAutoLockPixels alp(bm);
SkAutoLockColors ctLocker;
if (NULL == bm.getPixels()) {
return false;
}
WebPConfig webp_config;
if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, quality)) {
return false;
}
WebPPicture pic;
WebPPictureInit(&pic);
pic.width = bm.width();
pic.height = bm.height();
pic.writer = stream_writer;
pic.custom_ptr = (void*)stream;
const SkPMColor* colors = ctLocker.lockColors(bm);
const uint8_t* src = (uint8_t*)bm.getPixels();
const int rgbStride = pic.width * 3;
// Import (for each scanline) the bit-map image (in appropriate color-space)
// to RGB color space.
uint8_t* rgb = new uint8_t[rgbStride * pic.height];
for (int y = 0; y < pic.height; ++y) {
scanline_import(src + y * bm.rowBytes(), rgb + y * rgbStride,
pic.width, colors);
}
bool ok = WebPPictureImportRGB(&pic, rgb, rgbStride);
delete[] rgb;
ok = ok && WebPEncode(&webp_config, &pic);
WebPPictureFree(&pic);
return ok;
}
///////////////////////////////////////////////////////////////////////////////
DEFINE_DECODER_CREATOR(WEBPImageDecoder);
DEFINE_ENCODER_CREATOR(WEBPImageEncoder);
///////////////////////////////////////////////////////////////////////////////
#include "SkTRegistry.h"
static SkImageDecoder* sk_libwebp_dfactory(SkStream* stream) {
int width, height, hasAlpha;
if (!webp_parse_header(stream, &width, &height, &hasAlpha)) {
return NULL;
}
// Magic matches, call decoder
return SkNEW(SkWEBPImageDecoder);
}
static SkImageEncoder* sk_libwebp_efactory(SkImageEncoder::Type t) {
return (SkImageEncoder::kWEBP_Type == t) ? SkNEW(SkWEBPImageEncoder) : NULL;
}
static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libwebp_dfactory);
static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libwebp_efactory);

View File

@ -17,12 +17,15 @@
class SkWBMPImageDecoder : public SkImageDecoder {
public:
virtual Format getFormat() const {
virtual Format getFormat() const SK_OVERRIDE {
return kWBMP_Format;
}
protected:
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
private:
typedef SkImageDecoder INHERITED;
};
static bool read_byte(SkStream* stream, uint8_t* data)
@ -107,14 +110,21 @@ bool SkWBMPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap,
int width = head.fWidth;
int height = head.fHeight;
if (SkImageDecoder::kDecodeBounds_Mode == mode) {
decodedBitmap->setConfig(SkBitmap::kIndex8_Config, width, height);
decodedBitmap->setIsOpaque(true);
return true;
}
// No Bitmap reuse supported for this format
if (!decodedBitmap->isNull()) {
return false;
}
// assign these directly, in case we return kDimensions_Result
decodedBitmap->setConfig(SkBitmap::kIndex8_Config, width, height);
decodedBitmap->setIsOpaque(true);
if (SkImageDecoder::kDecodeBounds_Mode == mode)
return true;
const SkPMColor colors[] = { SK_ColorBLACK, SK_ColorWHITE };
SkColorTable* ct = SkNEW_ARGS(SkColorTable, (colors, 2));
SkAutoUnref aur(ct);