Index: src/codec/SkWebpCodec.cpp |
diff --git a/src/codec/SkWebpCodec.cpp b/src/codec/SkWebpCodec.cpp |
index c28d077bb329826750ebca4bb92ac2f005e2ac02..c12b1df5edf955d547aad34892d441544528f485 100644 |
--- a/src/codec/SkWebpCodec.cpp |
+++ b/src/codec/SkWebpCodec.cpp |
@@ -7,6 +7,7 @@ |
#include "SkCodecPriv.h" |
#include "SkWebpCodec.h" |
+#include "SkStreamPriv.h" |
#include "SkTemplates.h" |
// A WebP decoder on top of (subset of) libwebp |
@@ -36,26 +37,55 @@ bool SkWebpCodec::IsWebp(const void* buf, size_t bytesRead) { |
SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) { |
SkAutoTDelete<SkStream> streamDeleter(stream); |
- unsigned char buffer[WEBP_VP8_HEADER_SIZE]; |
- SkASSERT(WEBP_VP8_HEADER_SIZE <= SkCodec::MinBufferedBytesNeeded()); |
+ // Webp demux needs a contiguous data buffer. |
+ sk_sp<SkData> data = nullptr; |
+ if (stream->getMemoryBase()) { |
+ // It is safe to make without copy because we'll hold onto the stream. |
+ data = SkData::MakeWithoutCopy(stream->getMemoryBase(), stream->getLength()); |
+ } else { |
+ data = SkCopyStreamToData(stream); |
- const size_t bytesPeeked = stream->peek(buffer, WEBP_VP8_HEADER_SIZE); |
- if (bytesPeeked != WEBP_VP8_HEADER_SIZE) { |
- // Use read + rewind as a backup |
- if (stream->read(buffer, WEBP_VP8_HEADER_SIZE) != WEBP_VP8_HEADER_SIZE |
- || !stream->rewind()) |
+ // If we are forced to copy the stream to a data, we can go ahead and delete the stream. |
+ streamDeleter.reset(nullptr); |
+ } |
+ |
+ // It's a little strange that the |demux| will outlive |webpData|, though it needs the |
+ // pointer in |webpData| to remain valid. This works because the pointer remains valid |
+ // until the SkData is freed. |
+ WebPData webpData = { data->bytes(), data->size() }; |
+ SkAutoTCallVProc<WebPDemuxer, WebPDemuxDelete> demux(WebPDemuxPartial(&webpData, nullptr)); |
+ if (nullptr == demux) { |
return nullptr; |
} |
- WebPBitstreamFeatures features; |
- VP8StatusCode status = WebPGetFeatures(buffer, WEBP_VP8_HEADER_SIZE, &features); |
- if (VP8_STATUS_OK != status) { |
- return nullptr; // Invalid WebP file. |
+ WebPChunkIterator chunkIterator; |
+ SkAutoTCallVProc<WebPChunkIterator, WebPDemuxReleaseChunkIterator> autoCI(&chunkIterator); |
+ sk_sp<SkColorSpace> colorSpace = nullptr; |
+ if (WebPDemuxGetChunk(demux, "ICCP", 1, &chunkIterator)) { |
+ colorSpace = SkColorSpace::NewICC(chunkIterator.chunk.bytes, chunkIterator.chunk.size); |
+ } |
+ |
+ if (!colorSpace) { |
+ colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); |
+ } |
+ |
+ // Since we do not yet support animation, we get the |width|, |height|, |color|, and |alpha| |
+ // from the first frame. It's the only frame we will decode. |
+ // |
+ // TODO: |
+ // When we support animation, we'll want to report the canvas width and canvas height instead. |
+ // We can get these from the |demux| directly. |
+ // What |color| and |alpha| will we want to report though? WebP allows different frames |
+ // to be encoded in different ways, making the encoded format difficult to describe. |
+ WebPIterator frame; |
+ SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame); |
+ if (!WebPDemuxGetFrame(demux, 1, &frame)) { |
+ return nullptr; |
} |
- // sanity check for image size that's about to be decoded. |
+ // Sanity check for image size that's about to be decoded. |
{ |
- const int64_t size = sk_64_mul(features.width, features.height); |
+ const int64_t size = sk_64_mul(frame.width, frame.height); |
if (!sk_64_isS32(size)) { |
return nullptr; |
} |
@@ -65,6 +95,16 @@ SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) { |
} |
} |
+ // TODO: |
+ // The only reason we actually need to call WebPGetFeatures() is to get the |features.format|. |
+ // This call actually re-reads the frame header. Should we suggest that libwebp expose |
+ // the format on the |frame|? |
+ WebPBitstreamFeatures features; |
+ VP8StatusCode status = WebPGetFeatures(frame.fragment.bytes, frame.fragment.size, &features); |
+ if (VP8_STATUS_OK != status) { |
+ return nullptr; |
+ } |
+ |
SkEncodedInfo::Color color; |
SkEncodedInfo::Alpha alpha; |
switch (features.format) { |
@@ -98,30 +138,9 @@ SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) { |
return nullptr; |
} |
- // FIXME (msarett): |
- // Temporary strategy for getting ICC profiles from webps. Once the incremental decoding |
- // API lands, we will use the WebPDemuxer to manage the entire decode. |
- sk_sp<SkColorSpace> colorSpace = nullptr; |
- const void* memory = stream->getMemoryBase(); |
- if (memory) { |
- WebPData data = { (const uint8_t*) memory, stream->getLength() }; |
- WebPDemuxState state; |
- SkAutoTCallVProc<WebPDemuxer, WebPDemuxDelete> demux(WebPDemuxPartial(&data, &state)); |
- |
- WebPChunkIterator chunkIterator; |
- SkAutoTCallVProc<WebPChunkIterator, WebPDemuxReleaseChunkIterator> autoCI(&chunkIterator); |
- if (demux && WebPDemuxGetChunk(demux, "ICCP", 1, &chunkIterator)) { |
- colorSpace = SkColorSpace::NewICC(chunkIterator.chunk.bytes, chunkIterator.chunk.size); |
- } |
- } |
- |
- if (!colorSpace) { |
- colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); |
- } |
- |
SkEncodedInfo info = SkEncodedInfo::Make(color, alpha, 8); |
- return new SkWebpCodec(features.width, features.height, info, colorSpace, |
- streamDeleter.release()); |
+ return new SkWebpCodec(features.width, features.height, info, std::move(colorSpace), |
+ streamDeleter.release(), demux.release(), std::move(data)); |
} |
// This version is slightly different from SkCodecPriv's version of conversion_possible. It |
@@ -158,7 +177,6 @@ bool SkWebpCodec::onDimensionsSupported(const SkISize& dim) { |
&& dim.height() >= 1 && dim.height() <= info.height(); |
} |
- |
static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) { |
switch (ct) { |
case kBGRA_8888_SkColorType: |
@@ -172,11 +190,6 @@ static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) { |
} |
} |
-// 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; |
- |
bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const { |
if (!desiredSubset) { |
return false; |
@@ -263,33 +276,31 @@ SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, |
config.output.u.RGBA.size = dstInfo.getSafeSize(rowBytes); |
config.output.is_external_memory = 1; |
+ WebPIterator frame; |
+ SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame); |
+ // If this succeeded in NewFromStream(), it should succeed again here. |
+ SkAssertResult(WebPDemuxGetFrame(fDemux, 1, &frame)); |
+ |
SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &config)); |
if (!idec) { |
return kInvalidInput; |
} |
- SkAutoTMalloc<uint8_t> storage(BUFFER_SIZE); |
- uint8_t* buffer = storage.get(); |
- while (true) { |
- const size_t bytesRead = stream()->read(buffer, BUFFER_SIZE); |
- if (0 == bytesRead) { |
- WebPIDecGetRGB(idec, rowsDecoded, NULL, NULL, NULL); |
+ switch (WebPIUpdate(idec, frame.fragment.bytes, frame.fragment.size)) { |
+ case VP8_STATUS_OK: |
+ return kSuccess; |
+ case VP8_STATUS_SUSPENDED: |
+ WebPIDecGetRGB(idec, rowsDecoded, nullptr, nullptr, nullptr); |
return kIncompleteInput; |
- } |
- |
- 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; |
- } |
+ default: |
+ return kInvalidInput; |
} |
} |
SkWebpCodec::SkWebpCodec(int width, int height, const SkEncodedInfo& info, |
- sk_sp<SkColorSpace> colorSpace, SkStream* stream) |
- : INHERITED(width, height, info, stream, colorSpace) |
+ sk_sp<SkColorSpace> colorSpace, SkStream* stream, WebPDemuxer* demux, |
+ sk_sp<SkData> data) |
+ : INHERITED(width, height, info, stream, std::move(colorSpace)) |
+ , fDemux(demux) |
+ , fData(std::move(data)) |
{} |