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
diff --git a/src/codec/SkWebpCodec.cpp b/src/codec/SkWebpCodec.cpp
new file mode 100644
index 0000000..b02015c
--- /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) {}