diff -r 000000000000 -r 6474c204b198 gfx/skia/trunk/src/images/SkImageDecoder_libwebp.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gfx/skia/trunk/src/images/SkImageDecoder_libwebp.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,683 @@ +/* + * 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" + +// 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 +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" +} + +// 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]; + size_t bytesToRead = WEBP_VP8_HEADER_SIZE; + size_t totalBytesRead = 0; + do { + unsigned char* dst = buffer + totalBytesRead; + const size_t bytesRead = stream->read(dst, bytesToRead); + if (0 == bytesRead) { + // Could not read any bytes. Check to see if we are at the end (exit + // condition), and continue reading if not. Important for streams + // that do not have all the data ready. + continue; + } + bytesToRead -= bytesRead; + totalBytesRead += bytesRead; + SkASSERT(bytesToRead + totalBytesRead == WEBP_VP8_HEADER_SIZE); + } while (!stream->isAtEnd() && bytesToRead > 0); + + WebPBitstreamFeatures features; + VP8StatusCode status = WebPGetFeatures(buffer, totalBytesRead, &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. + { + int64_t size = sk_64_mul(*width, *height); + if (!sk_64_isS32(size)) { + return false; + } + // now check that if we are 4-bytes per pixel, we also don't overflow + if (sk_64_asS32(size) > (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(SkStreamRewindable *stream, int *width, int *height) SK_OVERRIDE; + virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& rect) SK_OVERRIDE; + virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE; + +private: + /** + * Called when determining the output config to request to webp. + * If the image does not have alpha, there is no need to premultiply. + * If the caller wants unpremultiplied colors, that is respected. + */ + bool shouldPremultiply() const { + return SkToBool(fHasAlpha) && !this->getRequireUnpremultipliedColors(); + } + + 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, bool premultiply) { + WEBP_CSP_MODE mode = MODE_LAST; + SkBitmap::Config config = decodedBitmap->config(); + + if (config == SkBitmap::kARGB_8888_Config) { + #if SK_PMCOLOR_BYTE_ORDER(B,G,R,A) + mode = premultiply ? MODE_bgrA : MODE_BGRA; + #elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A) + mode = premultiply ? MODE_rgbA : MODE_RGBA; + #else + #error "Skia uses BGRA or RGBA byte order" + #endif + } else if (config == SkBitmap::kARGB_4444_Config) { + mode = premultiply ? 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; + } + + if (!stream->rewind()) { + SkDebugf("Failed to rewind webp stream!"); + return false; + } + const size_t readBufferSize = stream->hasLength() ? + SkTMin(stream->getLength(), WEBP_IDECODE_BUFFER_SZ) : WEBP_IDECODE_BUFFER_SZ; + SkAutoMalloc srcStorage(readBufferSize); + unsigned char* input = (uint8_t*)srcStorage.get(); + if (NULL == input) { + WebPIDelete(idec); + WebPFreeDecBuffer(&config->output); + return false; + } + + bool success = true; + VP8StatusCode status = VP8_STATUS_SUSPENDED; + do { + const size_t bytesRead = stream->read(input, readBufferSize); + if (0 == bytesRead) { + success = false; + break; + } + + status = WebPIAppend(idec, input, bytesRead); + if (VP8_STATUS_OK != status && VP8_STATUS_SUSPENDED != status) { + success = false; + break; + } + } while (VP8_STATUS_OK != status); + srcStorage.free(); + WebPIDelete(idec); + WebPFreeDecBuffer(&config->output); + + return success; +} + +static bool webp_get_config_resize(WebPDecoderConfig* config, + SkBitmap* decodedBitmap, + int width, int height, bool premultiply) { + WEBP_CSP_MODE mode = webp_decode_mode(decodedBitmap, premultiply); + 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 = (int) 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, bool premultiply) { + + if (!webp_get_config_resize(config, decodedBitmap, region.width(), + region.height(), premultiply)) { + 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, SkToBool(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; + } + + return decodedBitmap->setConfig(config, width, height, 0, + fHasAlpha ? kPremul_SkAlphaType : kOpaque_SkAlphaType); +} + +bool SkWEBPImageDecoder::onBuildTileIndex(SkStreamRewindable* stream, + int *width, int *height) { + int origWidth, origHeight, hasAlpha; + if (!webp_parse_header(stream, &origWidth, &origHeight, &hasAlpha)) { + return false; + } + + if (!stream->rewind()) { + SkDebugf("Failed to rewind webp stream!"); + return false; + } + + *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::onDecodeSubset(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))); + + SkBitmap tmpBitmap; + SkBitmap *bitmap = decodedBitmap; + + if (!directDecode) { + bitmap = &tmpBitmap; + } + + 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, + this->shouldPremultiply())) { + 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 (!setDecodeConfig(decodedBitmap, sampler.scaledWidth(), + sampler.scaledHeight())) { + return false; + } + + // If only bounds are requested, done + if (SkImageDecoder::kDecodeBounds_Mode == mode) { + return true; + } + + if (!this->allocPixelRef(decodedBitmap, NULL)) { + return return_false(*decodedBitmap, "allocPixelRef"); + } + + SkAutoLockPixels alp(*decodedBitmap); + + WebPDecoderConfig config; + if (!webp_get_config_resize(&config, decodedBitmap, origWidth, origHeight, + this->shouldPremultiply())) { + return false; + } + + // Decode the WebP image data stream using WebP incremental decoding. + return webp_idecode(stream, &config); +} + +/////////////////////////////////////////////////////////////////////////////// + +#include "SkUnPreMultiply.h" + +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 ARGB_8888_To_RGBA(const uint8_t* in, uint8_t* rgb, int width, + const SkPMColor*) { + const uint32_t* SK_RESTRICT src = (const uint32_t*)in; + const SkUnPreMultiply::Scale* SK_RESTRICT table = + SkUnPreMultiply::GetScaleTable(); + for (int i = 0; i < width; ++i) { + const uint32_t c = *src++; + uint8_t a = SkGetPackedA32(c); + uint8_t r = SkGetPackedR32(c); + uint8_t g = SkGetPackedG32(c); + uint8_t b = SkGetPackedB32(c); + if (0 != a && 255 != a) { + SkUnPreMultiply::Scale scale = table[a]; + r = SkUnPreMultiply::ApplyScale(scale, r); + g = SkUnPreMultiply::ApplyScale(scale, g); + b = SkUnPreMultiply::ApplyScale(scale, b); + } + rgb[0] = r; + rgb[1] = g; + rgb[2] = b; + rgb[3] = a; + rgb += 4; + } +} + +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 ARGB_4444_To_RGBA(const uint8_t* in, uint8_t* rgb, int width, + const SkPMColor*) { + const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)in; + const SkUnPreMultiply::Scale* SK_RESTRICT table = + SkUnPreMultiply::GetScaleTable(); + for (int i = 0; i < width; ++i) { + const SkPMColor16 c = *src++; + uint8_t a = SkPacked4444ToA32(c); + uint8_t r = SkPacked4444ToR32(c); + uint8_t g = SkPacked4444ToG32(c); + uint8_t b = SkPacked4444ToB32(c); + if (0 != a && 255 != a) { + SkUnPreMultiply::Scale scale = table[a]; + r = SkUnPreMultiply::ApplyScale(scale, r); + g = SkUnPreMultiply::ApplyScale(scale, g); + b = SkUnPreMultiply::ApplyScale(scale, b); + } + rgb[0] = r; + rgb[1] = g; + rgb[2] = b; + rgb[3] = a; + rgb += 4; + } +} + +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, + bool hasAlpha, + int* bpp) { + switch (config) { + case SkBitmap::kARGB_8888_Config: + if (hasAlpha) { + *bpp = 4; + return ARGB_8888_To_RGBA; + } else { + *bpp = 3; + return ARGB_8888_To_RGB; + } + case SkBitmap::kARGB_4444_Config: + if (hasAlpha) { + *bpp = 4; + return ARGB_4444_To_RGBA; + } else { + *bpp = 3; + return ARGB_4444_To_RGB; + } + case SkBitmap::kRGB_565_Config: + *bpp = 3; + return RGB_565_To_RGB; + case SkBitmap::kIndex8_Config: + *bpp = 3; + 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.config(); + const bool hasAlpha = !bm.isOpaque(); + int bpp = -1; + const ScanlineImporter scanline_import = ChooseImporter(config, hasAlpha, + &bpp); + if (NULL == scanline_import) { + return false; + } + if (-1 == bpp) { + return false; + } + + SkAutoLockPixels alp(bm); + SkAutoLockColors ctLocker; + if (NULL == bm.getPixels()) { + return false; + } + + WebPConfig webp_config; + if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, (float) 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 * bpp; + + // 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; + if (bpp == 3) { + ok = SkToBool(WebPPictureImportRGB(&pic, rgb, rgbStride)); + } else { + ok = SkToBool(WebPPictureImportRGBA(&pic, rgb, rgbStride)); + } + delete[] rgb; + + ok = ok && WebPEncode(&webp_config, &pic); + WebPPictureFree(&pic); + + return ok; +} + + +/////////////////////////////////////////////////////////////////////////////// +DEFINE_DECODER_CREATOR(WEBPImageDecoder); +DEFINE_ENCODER_CREATOR(WEBPImageEncoder); +/////////////////////////////////////////////////////////////////////////////// + +static SkImageDecoder* sk_libwebp_dfactory(SkStreamRewindable* stream) { + int width, height, hasAlpha; + if (!webp_parse_header(stream, &width, &height, &hasAlpha)) { + return NULL; + } + + // Magic matches, call decoder + return SkNEW(SkWEBPImageDecoder); +} + +static SkImageDecoder::Format get_format_webp(SkStreamRewindable* stream) { + int width, height, hasAlpha; + if (webp_parse_header(stream, &width, &height, &hasAlpha)) { + return SkImageDecoder::kWEBP_Format; + } + return SkImageDecoder::kUnknown_Format; +} + +static SkImageEncoder* sk_libwebp_efactory(SkImageEncoder::Type t) { + return (SkImageEncoder::kWEBP_Type == t) ? SkNEW(SkWEBPImageEncoder) : NULL; +} + +static SkImageDecoder_DecodeReg gDReg(sk_libwebp_dfactory); +static SkImageDecoder_FormatReg gFormatReg(get_format_webp); +static SkImageEncoder_EncodeReg gEReg(sk_libwebp_efactory);