diff options
author | commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-03-14 14:42:18 +0000 |
---|---|---|
committer | commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-03-14 14:42:18 +0000 |
commit | a936e37cc76614868f5b489395bceeb340cc04cd (patch) | |
tree | 9cb0b81655baa1dc494079d2ae2aee37b3a084c2 | |
parent | 5439ea2e193c96274fb00ba0564b239e233d60a2 (diff) |
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
-rw-r--r-- | DEPS | 1 | ||||
-rw-r--r-- | gyp/images.gyp | 7 | ||||
-rw-r--r-- | gyp/libwebp.gyp | 174 | ||||
-rw-r--r-- | include/images/SkBitmapRegionDecoder.h | 54 | ||||
-rw-r--r-- | include/images/SkImageDecoder.h | 88 | ||||
-rw-r--r-- | include/images/SkImageEncoder.h | 4 | ||||
-rw-r--r-- | src/images/SkBitmapRegionDecoder.cpp | 14 | ||||
-rw-r--r-- | src/images/SkImageDecoder.cpp | 84 | ||||
-rw-r--r-- | src/images/SkImageDecoder_libbmp.cpp | 18 | ||||
-rw-r--r-- | src/images/SkImageDecoder_libgif.cpp | 20 | ||||
-rw-r--r-- | src/images/SkImageDecoder_libico.cpp | 21 | ||||
-rw-r--r-- | src/images/SkImageDecoder_libpng.cpp | 628 | ||||
-rw-r--r-- | src/images/SkImageDecoder_libwebp.cpp | 595 | ||||
-rw-r--r-- | src/images/SkImageDecoder_wbmp.cpp | 22 |
14 files changed, 1539 insertions, 191 deletions
@@ -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 = [ diff --git a/gyp/images.gyp b/gyp/images.gyp index b5447a27a5..32c7459d80 100644 --- a/gyp/images.gyp +++ b/gyp/images.gyp @@ -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', diff --git a/gyp/libwebp.gyp b/gyp/libwebp.gyp new file mode 100644 index 0000000000..ad3f47a45e --- /dev/null +++ b/gyp/libwebp.gyp @@ -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', + ], + }, + } + ], + }], + ], +} diff --git a/include/images/SkBitmapRegionDecoder.h b/include/images/SkBitmapRegionDecoder.h new file mode 100644 index 0000000000..0e20a5a216 --- /dev/null +++ b/include/images/SkBitmapRegionDecoder.h @@ -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 diff --git a/include/images/SkImageDecoder.h b/include/images/SkImageDecoder.h index 6d1bb7241d..8981b9542b 100644 --- a/include/images/SkImageDecoder.h +++ b/include/images/SkImageDecoder.h @@ -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 diff --git a/include/images/SkImageEncoder.h b/include/images/SkImageEncoder.h index 3fbfefa5f7..d4d7169565 100644 --- a/include/images/SkImageEncoder.h +++ b/include/images/SkImageEncoder.h @@ -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 diff --git a/src/images/SkBitmapRegionDecoder.cpp b/src/images/SkBitmapRegionDecoder.cpp new file mode 100644 index 0000000000..4cf1cca45c --- /dev/null +++ b/src/images/SkBitmapRegionDecoder.cpp @@ -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); +} diff --git a/src/images/SkImageDecoder.cpp b/src/images/SkImageDecoder.cpp index 7b4267615f..1e3b03929f 100644 --- a/src/images/SkImageDecoder.cpp +++ b/src/images/SkImageDecoder.cpp @@ -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, diff --git a/src/images/SkImageDecoder_libbmp.cpp b/src/images/SkImageDecoder_libbmp.cpp index 3aa834a6b5..1dcea8ad64 100644 --- a/src/images/SkImageDecoder_libbmp.cpp +++ b/src/images/SkImageDecoder_libbmp.cpp @@ -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; diff --git a/src/images/SkImageDecoder_libgif.cpp b/src/images/SkImageDecoder_libgif.cpp index f81c601093..02bc67b7de 100644 --- a/src/images/SkImageDecoder_libgif.cpp +++ b/src/images/SkImageDecoder_libgif.cpp @@ -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"); } - - bm->setConfig(SkBitmap::kIndex8_Config, width, height); - if (SkImageDecoder::kDecodeBounds_Mode == mode) + + 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); SavedImage* image = &gif->SavedImages[gif->ImageCount-1]; const GifImageDesc& desc = image->ImageDesc; diff --git a/src/images/SkImageDecoder_libico.cpp b/src/images/SkImageDecoder_libico.cpp index edfaa2e62b..14f575baba 100644 --- a/src/images/SkImageDecoder_libico.cpp +++ b/src/images/SkImageDecoder_libico.cpp @@ -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)) { diff --git a/src/images/SkImageDecoder_libpng.cpp b/src/images/SkImageDecoder_libpng.cpp index cca69fac10..501a0d69f7 100644 --- a/src/images/SkImageDecoder_libpng.cpp +++ b/src/images/SkImageDecoder_libpng.cpp @@ -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,65 +224,242 @@ 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 + if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) { + return false; + } + + const int sampleSize = this->getSampleSize(); + SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize); + + 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; + } + + // 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); + + if (!reuseBitmap) { + if (!this->allocPixelRef(decodedBitmap, + SkBitmap::kIndex8_Config == config ? 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_read_update_info(png_ptr, info_ptr); + + if (SkBitmap::kIndex8_Config == config && 1 == sampleSize) { + 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, 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 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, png_bytepp_NULL, 1); + row += rowBytes; + } + } + // now sample it + base += sampler.srcY0() * rowBytes; + for (int y = 0; y < height; y++) { + reallyHasAlpha |= sampler.next(base); + base += sampler.srcDY() * rowBytes; + } + } else { + SkAutoMalloc storage(origWidth * srcBytesPerPixel); + uint8_t* srcRow = (uint8_t*)storage.get(); + skip_src_rows(png_ptr, srcRow, sampler.srcY0()); + + 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); + } + } + + // skip the rest of the rows (if any) + png_uint_32 read = (height - 1) * sampler.srcDY() + + sampler.srcY0() + 1; + SkASSERT(read <= origHeight); + skip_src_rows(png_ptr, srcRow, origHeight - read); + } + } + + /* read rest of file, and get additional chunks in info_ptr - REQUIRED */ + png_read_end(png_ptr, info_ptr); + + if (0 != theTranspColor) { + 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 (doDither) { - png_color_8p sig_bit = NULL; - bool has_sbit = PNG_INFO_sBIT == png_get_sBIT(png_ptr, info_ptr, - &sig_bit); + if (*doDitherp) { #if 0 - if (has_sbit) { - SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green, - sig_bit->blue, sig_bit->alpha); - } + 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 (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 (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 (color_type == PNG_COLOR_TYPE_PALETTE) { + if (colorType == PNG_COLOR_TYPE_PALETTE) { bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr); - config = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha); + *configp = this->getPrefConfig(kIndex_SrcDepth, paletteHasAlpha); // now see if we can upscale to their requested config - if (!canUpscalePaletteToConfig(config, paletteHasAlpha)) { - config = SkBitmap::kIndex8_Config; + if (!canUpscalePaletteToConfig(*configp, paletteHasAlpha)) { + *configp = SkBitmap::kIndex8_Config; } } else { - png_color_16p transpColor = NULL; - int numTransp = 0; + png_color_16p transpColor = NULL; + int numTransp = 0; png_get_tRNS(png_ptr, info_ptr, NULL, &numTransp, &transpColor); @@ -251,40 +473,44 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, 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); + if (colorType & PNG_COLOR_MASK_COLOR) { + if (16 == bitDepth) { + *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8, + transpColor->green >> 8, + transpColor->blue >> 8); } else { - theTranspColor = SkPackARGB32(0xFF, transpColor->red, - transpColor->green, transpColor->blue); + *theTranspColorp = 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); + if (16 == bitDepth) { + *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8, + transpColor->gray >> 8, + transpColor->gray >> 8); } else { - theTranspColor = SkPackARGB32(0xFF, transpColor->gray, - transpColor->gray, transpColor->gray); + *theTranspColorp = 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; + PNG_COLOR_TYPE_RGB_ALPHA == colorType || + PNG_COLOR_TYPE_GRAY_ALPHA == colorType) { + *hasAlphap = true; } - config = this->getPrefConfig(k32Bit_SrcDepth, hasAlpha); + *configp = this->getPrefConfig(k32Bit_SrcDepth, *hasAlphap); // now match the request against our capabilities - if (hasAlpha) { - if (config != SkBitmap::kARGB_4444_Config) { - config = SkBitmap::kARGB_8888_Config; + if (*hasAlphap) { + if (*configp != SkBitmap::kARGB_4444_Config) { + *configp = SkBitmap::kARGB_8888_Config; } } else { - if (config != SkBitmap::kRGB_565_Config && - config != SkBitmap::kARGB_4444_Config) { - config = SkBitmap::kARGB_8888_Config; + if (*configp != SkBitmap::kRGB_565_Config && + *configp != SkBitmap::kARGB_4444_Config) { + *configp = SkBitmap::kARGB_8888_Config; } } } @@ -302,99 +528,172 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, } } - if (!this->chooseFromOneChoice(config, origWidth, origHeight)) { - 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; } - const int sampleSize = this->getSampleSize(); - SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize); + int index = 0; + int transLessThanFF = 0; - decodedBitmap->setConfig(config, sampler.scaledWidth(), - sampler.scaledHeight(), 0); - if (SkImageDecoder::kDecodeBounds_Mode == mode) { - return true; + for (; index < numTrans; index++) { + transLessThanFF |= (int)*trans - 0xFF; + *colorPtr++ = SkPreMultiplyARGB(*trans++, palette->red, palette->green, palette->blue); + palette++; } + reallyHasAlpha |= (transLessThanFF < 0); - // from here down we are concerned with colortables and pixels + for (; index < numPalette; index++) { + *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue); + palette++; + } - // 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; + // 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 - if (color_type == PNG_COLOR_TYPE_PALETTE) { - int num_palette; - png_colorp palette; - png_bytep trans; - int num_trans; +bool SkPNGImageDecoder::onBuildTileIndex(SkStream* sk_stream, int *width, int *height) { + png_structp png_ptr; + png_infop info_ptr; - png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); + if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) { + return false; + } - /* BUGGY IMAGE WORKAROUND + if (setjmp(png_jmpbuf(png_ptr)) != 0) { + png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL); + return false; + } - 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); + 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); - colorTable = SkNEW_ARGS(SkColorTable, (colorCount)); + *width = origWidth; + *height = origHeight; - 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; - } + png_build_index(png_ptr); - int index = 0; - int transLessThanFF = 0; + if (fImageIndex) { + SkDELETE(fImageIndex); + } + fImageIndex = SkNEW_ARGS(SkPNGImageIndex, (png_ptr, info_ptr)); - for (; index < num_trans; index++) { - transLessThanFF |= (int)*trans - 0xFF; - *colorPtr++ = SkPreMultiplyARGB(*trans++, palette->red, palette->green, palette->blue); - palette++; - } - reallyHasAlpha |= (transLessThanFF < 0); + return true; +} - for (; index < num_palette; index++) { - *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue); - palette++; - } +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; + } - // see BUGGY IMAGE WORKAROUND comment above - if (num_palette < 256) { - *colorPtr = colorPtr[-1]; - } - colorTable->unlockColors(true); + 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; } - SkAutoUnref aur(colorTable); + SkBitmap::Config config; + bool hasAlpha = false; + bool doDither = this->getDitherImage(); + SkPMColor theTranspColor = 0; // 0 tells us not to try to match - if (!this->allocPixelRef(decodedBitmap, - SkBitmap::kIndex8_Config == config ? - colorTable : NULL)) { + if (!getBitmapConfig(png_ptr, info_ptr, &config, &hasAlpha, &doDither, &theTranspColor)) { return false; } - SkAutoLockPixels alp(*decodedBitmap); + 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); + } - /* 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); + SkAutoUnref aur(colorTable); - /* swap bytes of 16 bit files to least significant byte first */ - // png_set_swap(png_ptr); + // 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 (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); } @@ -402,20 +701,28 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, * 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). */ + 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, NULL, 1); + uint8_t* bmRow = decodedBitmap.getAddr8(0, y); + png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1); } } } else { @@ -436,10 +743,10 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, upscale png's palette to a direct model */ SkAutoLockColors ctLock(colorTable); - if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors())) { + if (!sampler.begin(&decodedBitmap, sc, doDither, ctLock.colors())) { return false; } - const int height = decodedBitmap->height(); + const int height = decodedBitmap.height(); if (number_passes > 1) { SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel); @@ -447,10 +754,15 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, 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 (png_uint_32 y = 0; y < origHeight; y++) { + for (int32_t y = 0; y < rect.height(); y++) { uint8_t* bmRow = row; - png_read_rows(png_ptr, &bmRow, NULL, 1); + png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1); row += rb; } } @@ -463,34 +775,40 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, } 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, 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); } } - - // skip the rest of the rows (if any) - png_uint_32 read = (height - 1) * sampler.srcDY() + - sampler.srcY0() + 1; - SkASSERT(read <= origHeight); - skip_src_rows(png_ptr, srcRow, origHeight - read); } } - /* read rest of file, and get additional chunks in info_ptr - REQUIRED */ - png_read_end(png_ptr, info_ptr); - if (0 != theTranspColor) { - reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor); + reallyHasAlpha |= substituteTranspColor(&decodedBitmap, theTranspColor); } - decodedBitmap->setIsOpaque(!reallyHasAlpha); + 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 /////////////////////////////////////////////////////////////////////////////// @@ -498,7 +816,7 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, #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 diff --git a/src/images/SkImageDecoder_libwebp.cpp b/src/images/SkImageDecoder_libwebp.cpp new file mode 100644 index 0000000000..d4e40c0cd7 --- /dev/null +++ b/src/images/SkImageDecoder_libwebp.cpp @@ -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); diff --git a/src/images/SkImageDecoder_wbmp.cpp b/src/images/SkImageDecoder_wbmp.cpp index c475ec126e..5e395e592f 100644 --- a/src/images/SkImageDecoder_wbmp.cpp +++ b/src/images/SkImageDecoder_wbmp.cpp @@ -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); |