aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVikas Arora <vikasa@google.com>2011-07-06 15:41:16 +0530
committerVikas Arora <vikasa@google.com>2011-07-07 12:50:13 +0530
commit82ed38078af4a2b643415a76e84f88754f804709 (patch)
tree56f7249a780f107e2e15ff9b19aa1915795b09b0
parentcc8395159cea368f09a2fce36229c7bc93828c9a (diff)
downloadskia-82ed38078af4a2b643415a76e84f88754f804709.tar.gz
Add support for WebP incremental decoding.
Updated the Android-Skia-WebP decoder with following changes: - Added support for incremental WebP decoding. So instead of allocating input buffer corresponding to the whole image (could be few MBs), the input is read iteratively in a buffer size of 64KB. - Refactored onDecode method (O(200) lines) to a smaller (O(30) lines) method with 2-3 helper functions. - Removed low (byte-level) parsing from this code and calling WebP public APIs for the same (like WebPGetInfo). - Incorporated Pascal's feedback. - Few more feedback from Pascal. Change-Id: I104822ec3922eca1efc19422908f07770c4c140b
-rw-r--r--src/images/SkImageDecoder_libwebp.cpp292
1 files changed, 106 insertions, 186 deletions
diff --git a/src/images/SkImageDecoder_libwebp.cpp b/src/images/SkImageDecoder_libwebp.cpp
index efbb696e73..6f8dfcc4d9 100644
--- a/src/images/SkImageDecoder_libwebp.cpp
+++ b/src/images/SkImageDecoder_libwebp.cpp
@@ -77,66 +77,34 @@ static uint32_t getint32l(unsigned char *in) {
return result;
}
-// Parse headers of RIFF container, and check for valid Webp (VP8) content
-// return VP8 chunk content size on success, 0 on error.
-static const size_t WEBP_HEADER_SIZE = 20;
-static const size_t VP8_HEADER_SIZE = 10;
-
-static uint32_t webp_parse_header(SkStream* stream) {
- unsigned char buffer[WEBP_HEADER_SIZE];
- size_t len;
- uint32_t totalSize;
- uint32_t contentSize;
-
- // RIFF container for WEBP image should always have:
- // 0 "RIFF" 4-byte tag
- // 4 size of image data (including metadata) starting at offset 8
- // 8 "WEBP" the form-type signature
- // 12 "VP8 " 4-bytes tags, describing the raw video format used
- // 16 size of the raw VP8 image data, starting at offset 20
- // 20 the VP8 bytes
- // First check for RIFF top chunk, consuming only 8 bytes
- len = stream->read(buffer, 8);
- if (len != 8) {
- return 0; // can't read enough
- }
- // Inline magic matching instead of memcmp()
- if (buffer[0] != 'R' || buffer[1] != 'I' || buffer[2] != 'F' || buffer[3] != 'F') {
- return 0;
- }
+static const size_t WEBP_VP8_HEADER_SIZE = 30;
+static const size_t WEBP_IDECODE_BUFFER_SZ = (1 << 16);
- totalSize = getint32l(buffer + 4);
- if (totalSize < (int) (WEBP_HEADER_SIZE - 8)) {
- return 0;
+// Parse headers of RIFF container, and check for valid Webp (VP8) content.
+static bool webp_parse_header(SkStream* stream, int* width, int* height) {
+ unsigned char buffer[WEBP_VP8_HEADER_SIZE];
+ const size_t len = stream->read(buffer, WEBP_VP8_HEADER_SIZE);
+ if (len != WEBP_VP8_HEADER_SIZE) {
+ return false; // can't read enough
}
- // If RIFF header found, check for RIFF content to start with WEBP/VP8 chunk
- len = stream->read(buffer + 8, WEBP_HEADER_SIZE - 8);
- if (len != (int) (WEBP_HEADER_SIZE - 8)) {
- return 0;
- }
- if (buffer[8] != 'W' || buffer[9] != 'E' || buffer[10] != 'B' || buffer[11] != 'P') {
- return 0;
- }
- if (buffer[12] != 'V' || buffer[13] != 'P' || buffer[14] != '8' || buffer[15] != ' ') {
- return 0;
+ if (WebPGetInfo(buffer, WEBP_VP8_HEADER_SIZE, width, height) == 0) {
+ return false; // Invalid WebP file.
}
- // Magic matches, extract content size
- contentSize = getint32l(buffer + 16);
-
- // Check consistency of reported sizes
- if (contentSize <= 0 || contentSize > 0x7fffffff) {
- return 0;
- }
- if (totalSize < 12 + contentSize) {
- return 0;
- }
- if (contentSize & 1) {
- return 0;
+ // 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 contentSize;
+ return true;
}
class SkWEBPImageDecoder: public SkImageDecoder {
@@ -147,6 +115,9 @@ public:
protected:
virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode);
+
+private:
+ bool setDecodeConfig(SkBitmap* decodedBitmap, int width, int height);
};
//////////////////////////////////////////////////////////////////////////
@@ -446,70 +417,84 @@ static int block_setup(VP8Io* io) {
static void block_teardown(const VP8Io* io) {
}
-bool SkWEBPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap, Mode mode) {
-#ifdef TIME_DECODE
- AutoTimeMillis atm("WEBP Decode");
-#endif
-
- // libwebp doesn't provide a way to override all I/O with a custom
- // implementation. For initial implementation, let's go the "dirty"
- // way, by loading image file content in memory before decoding it
- int origWidth, origHeight;
- size_t len;
-
- bool hasAlpha = false;
-
- uint32_t contentSize;
- unsigned char buffer[VP8_HEADER_SIZE];
- unsigned char *input;
-
- // Check header
- contentSize = webp_parse_header(stream);
- if (contentSize <= VP8_HEADER_SIZE) {
+static bool webp_init_custom_io(WebPIDecoder* idec, SkBitmap* decodedBitmap) {
+ if (idec == NULL) {
return false;
}
- //* Extract information
- len = stream->read(buffer, VP8_HEADER_SIZE);
- if (len != VP8_HEADER_SIZE) {
- return false;
- }
+ WEBPImage pSrc;
+ // Custom Put callback need reference to target image.
+ pSrc.image = decodedBitmap;
- // check signature
- if (buffer[3] != 0x9d || buffer[4] != 0x01 || buffer[5] != 0x2a) {
+ if (!WebPISetIOHooks(idec, block_put, block_setup, block_teardown,
+ (void*)&pSrc)) {
return false;
}
- const uint32_t bits = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16);
- const int key_frame = !(bits & 1);
+ return true;
+}
+
+// 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, SkBitmap* decodedBitmap) {
+ SkAutoLockPixels alp(*decodedBitmap);
- origWidth = ((buffer[7] << 8) | buffer[6]) & 0x3fff;
- origHeight = ((buffer[9] << 8) | buffer[8]) & 0x3fff;
+ stream->rewind();
+ const uint32_t contentSize = stream->getLength();
- if (origWidth <= 0 || origHeight <= 0) {
+ WebPIDecoder* idec = WebPINew(MODE_YUV);
+ if (idec == NULL) {
return false;
}
- if (!key_frame) {
- // Not a keyframe
+ if (!webp_init_custom_io(idec, decodedBitmap)) {
+ WebPIDelete(idec);
return false;
}
- if (((bits >> 1) & 7) > 3) {
- // unknown profile
- return false;
+ uint32_t read_buffer_size = contentSize;
+ if (read_buffer_size > WEBP_IDECODE_BUFFER_SZ) {
+ read_buffer_size = WEBP_IDECODE_BUFFER_SZ;
}
- if (!((bits >> 4) & 1)) {
- // first frame is invisible
+ SkAutoMalloc srcStorage(read_buffer_size);
+ unsigned char* input = (uint8_t*)srcStorage.get();
+ if (input == NULL) {
+ WebPIDelete(idec);
return false;
}
- if (((bits >> 5)) >= contentSize) {
- // partition_length inconsistent size information
+
+ uint32_t bytes_remaining = contentSize;
+ while (bytes_remaining > 0) {
+ const uint32_t bytes_to_read =
+ (bytes_remaining > WEBP_IDECODE_BUFFER_SZ) ?
+ WEBP_IDECODE_BUFFER_SZ : bytes_remaining;
+
+ const size_t bytes_read = stream->read(input, bytes_to_read);
+ if (bytes_read == 0) {
+ break;
+ }
+
+ VP8StatusCode status = WebPIAppend(idec, input, bytes_read);
+ if (status == VP8_STATUS_OK || status == VP8_STATUS_SUSPENDED) {
+ bytes_remaining -= bytes_read;
+ } else {
+ break;
+ }
+ }
+ srcStorage.free();
+ WebPIDelete(idec);
+
+ if (bytes_remaining > 0) {
return false;
+ } else {
+ return true;
}
+}
- SkBitmap::Config config;
- config = this->getPrefConfig(k32Bit_SrcDepth, hasAlpha);
+bool SkWEBPImageDecoder::setDecodeConfig(SkBitmap* decodedBitmap,
+ int origWidth, int origHeight) {
+ bool hasAlpha = false;
+ SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, hasAlpha);
// only accept prefConfig if it makes sense for us. YUV converter
// supports output in RGB565, RGBA4444 and RGBA8888 formats.
@@ -518,121 +503,55 @@ bool SkWEBPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap, Mod
config = SkBitmap::kARGB_8888_Config;
}
} else {
- if (config != SkBitmap::kRGB_565_Config && config != SkBitmap::kARGB_4444_Config) {
+ if (config != SkBitmap::kRGB_565_Config &&
+ config != SkBitmap::kARGB_4444_Config) {
config = SkBitmap::kARGB_8888_Config;
}
}
- // sanity check for size
- {
- Sk64 size;
- size.setMul(origWidth, origHeight);
- 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;
- }
- }
if (!this->chooseFromOneChoice(config, origWidth, origHeight)) {
return false;
}
- // TODO: may add support for sampler, to load previeww/thumbnails faster
- // Note that, as documented, an image decoder may decide to ignore sample hint from requested
- // config, so this implementation is still valid and safe even without handling it at all. Several
- // other Skia image decoders just ignore this optional feature as well.
-#if 0
- SkScaledBitmapSampler sampler(origWidth, origHeight, getSampleSize());
- decodedBitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight(), 0);
-#else
decodedBitmap->setConfig(config, origWidth, origHeight, 0);
-#endif
-
- // If only bounds are requested, done
- if (SkImageDecoder::kDecodeBounds_Mode == mode) {
- return true;
- }
// Current WEBP specification has no support for alpha layer.
decodedBitmap->setIsOpaque(true);
- if (!this->allocPixelRef(decodedBitmap, NULL)) {
- return return_false(*decodedBitmap, "allocPixelRef");
- }
- SkAutoLockPixels alp(*decodedBitmap);
+ return true;
+}
- // libwebp doesn't provide a way to override all I/O with a custom
- // implementation. For initial implementation, let's go the "dirty"
- // way, by loading image file content (actually only VP8 chunk) into
- // memory before decoding it */
- SkAutoMalloc srcStorage(contentSize);
- input = (uint8_t*) srcStorage.get();
- if (input == NULL) {
- return return_false(*decodedBitmap, "failed to allocate read buffer");
- }
-
- len = stream->read(input + VP8_HEADER_SIZE, contentSize - VP8_HEADER_SIZE);
-#ifdef WEBPCONV_MISSING_PADDING
- // Some early (yet widely spread) version of webpconv utility
- // had a bug causing mandatory padding byte to not be written to
- // file when content size was odd, while total size was reporting
- // actual file size correctly. Since many webp around may have been
- // generated with this version of webpconv, work around this issue
- // by adding padding here. */
- // TODO: remove this whenever work-around may be considered obsolete
- if (len == contentSize - VP8_HEADER_SIZE - 1) {
- input[VP8_HEADER_SIZE + len] = 0;
- len++;
- }
+
+bool SkWEBPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap,
+ Mode mode) {
+#ifdef TIME_DECODE
+ AutoTimeMillis atm("WEBP Decode");
#endif
- if (len != contentSize - VP8_HEADER_SIZE) {
+
+ int origWidth, origHeight;
+ if (!webp_parse_header(stream, &origWidth, &origHeight)) {
return false;
}
- memcpy(input, buffer, VP8_HEADER_SIZE);
-
- WEBPImage pSrc;
- VP8Decoder* dec;
- VP8Io io;
- // Custom Put callback need reference to target image
- pSrc.image = decodedBitmap;
-
- // Keep reference to input stream, in case we find a way to not preload stream
- // content in memory. So far, stream content has already been consumed, and this
- // won't be used, but this is left for future usage.
- pSrc.stream = stream;
-
- dec = VP8New();
- if (dec == NULL) {
+ if (!setDecodeConfig(decodedBitmap, origWidth, origHeight)) {
return false;
}
- VP8InitIo(&io);
- io.data = input;
- io.data_size = contentSize;
-
- io.opaque = (void*) &pSrc;
- io.put = block_put;
- io.setup = block_setup;
- io.teardown = block_teardown;
+ // If only bounds are requested, done
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return true;
+ }
- if (!VP8GetHeaders(dec, &io)) {
- VP8Delete(dec);
- return false;
+ if (!this->allocPixelRef(decodedBitmap, NULL)) {
+ return return_false(*decodedBitmap, "allocPixelRef");
}
- if (!VP8Decode(dec, &io)) {
- VP8Delete(dec);
+ // Decode the WebP image data stream using WebP incremental decoding.
+ if (!webp_idecode(stream, decodedBitmap)) {
return false;
}
- VP8Delete(dec);
-
- // SkDebugf("------------------- bm2 size %d [%d %d] %d\n",
- // bm->getSize(), bm->width(), bm->height(), bm->config());
return true;
}
@@ -768,8 +687,9 @@ bool SkWEBPImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bm,
#include "SkTRegistry.h"
static SkImageDecoder* DFactory(SkStream* stream) {
- if (webp_parse_header(stream) <= 0) {
- return NULL;
+ int width, height;
+ if (!webp_parse_header(stream, &width, &height)) {
+ return false;
}
// Magic matches, call decoder