diff options
author | 2015-06-18 12:53:43 -0700 | |
---|---|---|
committer | 2015-06-18 12:53:43 -0700 | |
commit | 6f5e619b877b0dc1a42910637a6ffe37add98001 (patch) | |
tree | 47d908d1994934b33f33282353ae20830078f008 /src | |
parent | bedd0e87e6e993ebb42d9867fe4651b5c95f47f9 (diff) |
Add SkWebpCodec, for decoding .webp images.
Based on SkImageDecoder_libwebp.
TODO:
Support YUV? (Longer term - may influence our API for SkImageGenerator)
BUG=skia:3257
Review URL: https://codereview.chromium.org/1044433002
Diffstat (limited to 'src')
-rw-r--r-- | src/codec/SkCodec.cpp | 2 | ||||
-rw-r--r-- | src/codec/SkWebpCodec.cpp | 200 | ||||
-rw-r--r-- | src/codec/SkWebpCodec.h | 38 |
3 files changed, 240 insertions, 0 deletions
diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp index c4adf729e4..93db0e58ee 100644 --- a/src/codec/SkCodec.cpp +++ b/src/codec/SkCodec.cpp @@ -15,6 +15,7 @@ #include "SkCodecPriv.h" #include "SkJpegCodec.h" #include "SkStream.h" +#include "SkWebpCodec.h" struct DecoderProc { bool (*IsFormat)(SkStream*); @@ -24,6 +25,7 @@ struct DecoderProc { static const DecoderProc gDecoderProcs[] = { { SkPngCodec::IsPng, SkPngCodec::NewFromStream }, { SkJpegCodec::IsJpeg, SkJpegCodec::NewFromStream }, + { SkWebpCodec::IsWebp, SkWebpCodec::NewFromStream }, { SkGifCodec::IsGif, SkGifCodec::NewFromStream }, { SkIcoCodec::IsIco, SkIcoCodec::NewFromStream }, { SkBmpCodec::IsBmp, SkBmpCodec::NewFromStream }, diff --git a/src/codec/SkWebpCodec.cpp b/src/codec/SkWebpCodec.cpp new file mode 100644 index 0000000000..b02015c7a7 --- /dev/null +++ b/src/codec/SkWebpCodec.cpp @@ -0,0 +1,200 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkWebpCodec.h" +#include "SkImageGenerator.h" +#include "SkTemplates.h" + +// A WebP decoder on top of (subset of) libwebp +// For more information on WebP image format, and libwebp library, see: +// https://code.google.com/speed/webp/ +// http://www.webmproject.org/code/#libwebp-webp-image-library +// https://chromium.googlesource.com/webm/libwebp + +// 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" + +bool SkWebpCodec::IsWebp(SkStream* stream) { + // WEBP starts with the following: + // RIFFXXXXWEBPVP + // Where XXXX is unspecified. + const char LENGTH = 14; + char bytes[LENGTH]; + if (stream->read(&bytes, LENGTH) != LENGTH) { + return false; + } + return !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "WEBPVP", 6); +} + +static const size_t WEBP_VP8_HEADER_SIZE = 30; + +// Parse headers of RIFF container, and check for valid Webp (VP8) content. +// NOTE: This calls peek instead of read, since onGetPixels will need these +// bytes again. +static bool webp_parse_header(SkStream* stream, SkImageInfo* info) { + unsigned char buffer[WEBP_VP8_HEADER_SIZE]; + if (!stream->peek(buffer, WEBP_VP8_HEADER_SIZE)) { + return false; + } + + WebPBitstreamFeatures features; + VP8StatusCode status = WebPGetFeatures(buffer, WEBP_VP8_HEADER_SIZE, &features); + if (VP8_STATUS_OK != status) { + return false; // Invalid WebP file. + } + + // sanity check for image size that's about to be decoded. + { + const int64_t size = sk_64_mul(features.width, features.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; + } + } + + if (info) { + // FIXME: Is N32 the right type? + // Is unpremul the right type? Clients of SkImageGenerator may assume it's the + // best type, when Skia currently cannot draw unpremul (and raster is faster + // with premul). + *info = SkImageInfo::Make(features.width, features.height, kN32_SkColorType, + SkToBool(features.has_alpha) ? kUnpremul_SkAlphaType + : kOpaque_SkAlphaType); + } + return true; +} + +SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) { + SkAutoTDelete<SkStream> streamDeleter(stream); + SkImageInfo info; + if (webp_parse_header(stream, &info)) { + return SkNEW_ARGS(SkWebpCodec, (info, streamDeleter.detach())); + } + return NULL; +} + +static bool conversion_possible(const SkImageInfo& dst, const SkImageInfo& src) { + switch (dst.colorType()) { + // Both byte orders are supported. + case kBGRA_8888_SkColorType: + case kRGBA_8888_SkColorType: + break; + default: + return false; + } + if (dst.profileType() != src.profileType()) { + return false; + } + if (dst.alphaType() == src.alphaType()) { + return true; + } + return kPremul_SkAlphaType == dst.alphaType() && + kUnpremul_SkAlphaType == src.alphaType(); +} + +SkISize SkWebpCodec::onGetScaledDimensions(float desiredScale) const { + SkISize dim = this->getInfo().dimensions(); + dim.fWidth = SkScalarRoundToInt(desiredScale * dim.fWidth); + dim.fHeight = SkScalarRoundToInt(desiredScale * dim.fHeight); + return dim; +} + +static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) { + switch (ct) { + case kBGRA_8888_SkColorType: + return premultiply ? MODE_bgrA : MODE_BGRA; + case kRGBA_8888_SkColorType: + return premultiply ? MODE_rgbA : MODE_RGBA; + default: + return MODE_LAST; + } +} + +// The WebP decoding API allows us to incrementally pass chunks of bytes as we receive them to the +// decoder with WebPIAppend. In order to do so, we need to read chunks from the SkStream. This size +// is arbitrary. +static const size_t BUFFER_SIZE = 4096; + +SkImageGenerator::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, + size_t rowBytes, const Options&, SkPMColor*, + int*) { + switch (this->rewindIfNeeded()) { + case kCouldNotRewind_RewindState: + return kCouldNotRewind; + case kRewound_RewindState: + // Rewound to the beginning. Since creation only does a peek, the stream is at the + // correct position. + break; + case kNoRewindNecessary_RewindState: + // Already at the right spot for decoding. + break; + } + + if (!conversion_possible(dstInfo, this->getInfo())) { + return kInvalidConversion; + } + + WebPDecoderConfig config; + if (0 == WebPInitDecoderConfig(&config)) { + // ABI mismatch. + // FIXME: New enum for this? + return kInvalidInput; + } + + // Free any memory associated with the buffer. Must be called last, so we declare it first. + SkAutoTCallVProc<WebPDecBuffer, WebPFreeDecBuffer> autoFree(&(config.output)); + + SkISize dimensions = dstInfo.dimensions(); + if (this->getInfo().dimensions() != dimensions) { + // Caller is requesting scaling. + config.options.use_scaling = 1; + config.options.scaled_width = dimensions.width(); + config.options.scaled_height = dimensions.height(); + } + + config.output.colorspace = webp_decode_mode(dstInfo.colorType(), + dstInfo.alphaType() == kPremul_SkAlphaType); + config.output.u.RGBA.rgba = (uint8_t*) dst; + config.output.u.RGBA.stride = (int) rowBytes; + config.output.u.RGBA.size = dstInfo.getSafeSize(rowBytes); + config.output.is_external_memory = 1; + + SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(NULL, 0, &config)); + if (!idec) { + return kInvalidInput; + } + + SkAutoMalloc storage(BUFFER_SIZE); + uint8_t* buffer = static_cast<uint8_t*>(storage.get()); + while (true) { + const size_t bytesRead = stream()->read(buffer, BUFFER_SIZE); + if (0 == bytesRead) { + // FIXME: Maybe this is an incomplete image? How to decide? Based + // on the number of rows decoded? We can know the number of rows + // decoded using WebPIDecGetRGB. + return kInvalidInput; + } + + switch (WebPIAppend(idec, buffer, bytesRead)) { + case VP8_STATUS_OK: + return kSuccess; + case VP8_STATUS_SUSPENDED: + // Break out of the switch statement. Continue the loop. + break; + default: + return kInvalidInput; + } + } +} + +SkWebpCodec::SkWebpCodec(const SkImageInfo& info, SkStream* stream) + : INHERITED(info, stream) {} diff --git a/src/codec/SkWebpCodec.h b/src/codec/SkWebpCodec.h new file mode 100644 index 0000000000..9ea6a94ecb --- /dev/null +++ b/src/codec/SkWebpCodec.h @@ -0,0 +1,38 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkWebpCodec_DEFINED +#define SkWebpCodec_DEFINED + +#include "SkCodec.h" +#include "SkEncodedFormat.h" +#include "SkImageInfo.h" +#include "SkTypes.h" + +class SkStream; + +class SkWebpCodec final : public SkCodec { +public: + // Assumes IsWebp was called and returned true. + static SkCodec* NewFromStream(SkStream*); + static bool IsWebp(SkStream*); +protected: + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*) + override; + SkEncodedFormat onGetEncodedFormat() const override { return kWEBP_SkEncodedFormat; } + + bool onReallyHasAlpha() const override { + return this->getInfo().alphaType() != kOpaque_SkAlphaType; + } + + SkISize onGetScaledDimensions(float desiredScale) const override; +private: + SkWebpCodec(const SkImageInfo&, SkStream*); + + typedef SkCodec INHERITED; +}; +#endif // SkWebpCodec_DEFINED |