aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar scroggo <scroggo@google.com>2015-06-18 12:53:43 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2015-06-18 12:53:43 -0700
commit6f5e619b877b0dc1a42910637a6ffe37add98001 (patch)
tree47d908d1994934b33f33282353ae20830078f008 /src
parentbedd0e87e6e993ebb42d9867fe4651b5c95f47f9 (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.cpp2
-rw-r--r--src/codec/SkWebpCodec.cpp200
-rw-r--r--src/codec/SkWebpCodec.h38
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