aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/codec/SkWebpCodec.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/codec/SkWebpCodec.cpp')
-rw-r--r--src/codec/SkWebpCodec.cpp419
1 files changed, 360 insertions, 59 deletions
diff --git a/src/codec/SkWebpCodec.cpp b/src/codec/SkWebpCodec.cpp
index ab0b91b112..c99d8a7689 100644
--- a/src/codec/SkWebpCodec.cpp
+++ b/src/codec/SkWebpCodec.cpp
@@ -5,8 +5,11 @@
* found in the LICENSE file.
*/
+#include "SkBitmap.h"
+#include "SkCanvas.h"
#include "SkCodecPriv.h"
#include "SkColorSpaceXform.h"
+#include "SkRasterPipeline.h"
#include "SkSampler.h"
#include "SkStreamPriv.h"
#include "SkTemplates.h"
@@ -32,10 +35,12 @@ bool SkWebpCodec::IsWebp(const void* buf, size_t bytesRead) {
return bytesRead >= 14 && !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "WEBPVP", 6);
}
+static SkAlphaType alpha_type(bool hasAlpha) {
+ return hasAlpha ? kUnpremul_SkAlphaType : kOpaque_SkAlphaType;
+}
+
// 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.
-// Returns an SkWebpCodec on success;
+// Returns an SkWebpCodec on success
SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) {
std::unique_ptr<SkStream> streamDeleter(stream);
@@ -90,8 +95,6 @@ SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) {
}
// Get the first frame and its "features" to determine the color and alpha types.
- // Since we do not yet support animated webp, this is the only frame that we will
- // decode.
WebPIterator frame;
SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame);
if (!WebPDemuxGetFrame(demux, 1, &frame)) {
@@ -104,23 +107,32 @@ SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) {
return nullptr;
}
+ const bool hasAlpha = SkToBool(frame.has_alpha)
+ || frame.width != width || frame.height != height;
SkEncodedInfo::Color color;
SkEncodedInfo::Alpha alpha;
switch (features.format) {
case 0:
// This indicates a "mixed" format. We could see this for
// animated webps (multiple fragments).
- // I believe that this is a rare case.
// We could also guess kYUV here, but I think it makes more
// sense to guess kBGRA which is likely closer to the final
// output. Otherwise, we might end up converting
// BGRA->YUVA->BGRA.
- color = SkEncodedInfo::kBGRA_Color;
- alpha = SkEncodedInfo::kUnpremul_Alpha;
+ // Fallthrough:
+ case 2:
+ // This is the lossless format (BGRA).
+ if (hasAlpha) {
+ color = SkEncodedInfo::kBGRA_Color;
+ alpha = SkEncodedInfo::kUnpremul_Alpha;
+ } else {
+ color = SkEncodedInfo::kBGRX_Color;
+ alpha = SkEncodedInfo::kOpaque_Alpha;
+ }
break;
case 1:
// This is the lossy format (YUV).
- if (SkToBool(features.has_alpha) || frame.width != width || frame.height != height) {
+ if (hasAlpha) {
color = SkEncodedInfo::kYUVA_Color;
alpha = SkEncodedInfo::kUnpremul_Alpha;
} else {
@@ -128,11 +140,6 @@ SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) {
alpha = SkEncodedInfo::kOpaque_Alpha;
}
break;
- case 2:
- // This is the lossless format (BGRA).
- color = SkEncodedInfo::kBGRA_Color;
- alpha = SkEncodedInfo::kUnpremul_Alpha;
- break;
default:
return nullptr;
}
@@ -160,8 +167,9 @@ 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) {
+static WEBP_CSP_MODE webp_decode_mode(const SkImageInfo& info) {
+ const bool premultiply = info.alphaType() == kPremul_SkAlphaType;
+ switch (info.colorType()) {
case kBGRA_8888_SkColorType:
return premultiply ? MODE_bgrA : MODE_BGRA;
case kRGBA_8888_SkColorType:
@@ -173,6 +181,12 @@ static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) {
}
}
+SkWebpCodec::Frame* SkWebpCodec::FrameHolder::appendNewFrame(bool hasAlpha) {
+ const int i = this->size();
+ fFrames.emplace_back(i, hasAlpha);
+ return &fFrames[i];
+}
+
bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const {
if (!desiredSubset) {
return false;
@@ -191,13 +205,210 @@ bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const {
return true;
}
+int SkWebpCodec::onGetRepetitionCount() {
+ auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS);
+ if (!(flags & ANIMATION_FLAG)) {
+ return 0;
+ }
+
+ const int repCount = WebPDemuxGetI(fDemux.get(), WEBP_FF_LOOP_COUNT);
+ if (0 == repCount) {
+ return kRepetitionCountInfinite;
+ }
+
+ return repCount;
+}
+
+int SkWebpCodec::onGetFrameCount() {
+ auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS);
+ if (!(flags & ANIMATION_FLAG)) {
+ return 1;
+ }
+
+ const uint32_t oldFrameCount = fFrameHolder.size();
+ if (fFailed) {
+ return oldFrameCount;
+ }
+
+ const uint32_t frameCount = WebPDemuxGetI(fDemux, WEBP_FF_FRAME_COUNT);
+ if (oldFrameCount == frameCount) {
+ // We have already parsed this.
+ return frameCount;
+ }
+
+ fFrameHolder.reserve(frameCount);
+
+ for (uint32_t i = oldFrameCount; i < frameCount; i++) {
+ WebPIterator iter;
+ SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoIter(&iter);
+
+ if (!WebPDemuxGetFrame(fDemux.get(), i + 1, &iter)) {
+ fFailed = true;
+ break;
+ }
+
+ // libwebp only reports complete frames of an animated image.
+ SkASSERT(iter.complete);
+
+ Frame* frame = fFrameHolder.appendNewFrame(iter.has_alpha);
+ frame->setXYWH(iter.x_offset, iter.y_offset, iter.width, iter.height);
+ frame->setDisposalMethod(iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ?
+ SkCodecAnimation::RestoreBGColor_DisposalMethod :
+ SkCodecAnimation::Keep_DisposalMethod);
+ frame->setDuration(iter.duration);
+ if (WEBP_MUX_BLEND != iter.blend_method) {
+ frame->setBlend(SkCodecAnimation::Blend::kBG);
+ }
+ fFrameHolder.setAlphaAndRequiredFrame(frame);
+ }
+
+ return fFrameHolder.size();
+
+}
+
+const SkFrame* SkWebpCodec::FrameHolder::onGetFrame(int i) const {
+ return static_cast<const SkFrame*>(this->frame(i));
+}
+
+const SkWebpCodec::Frame* SkWebpCodec::FrameHolder::frame(int i) const {
+ SkASSERT(i >= 0 && i < this->size());
+ return &fFrames[i];
+}
+
+bool SkWebpCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const {
+ if (i >= fFrameHolder.size()) {
+ return false;
+ }
+
+ const Frame* frame = fFrameHolder.frame(i);
+ if (!frame) {
+ return false;
+ }
+
+ if (frameInfo) {
+ frameInfo->fRequiredFrame = frame->getRequiredFrame();
+ frameInfo->fDuration = frame->getDuration();
+ // libwebp only reports fully received frames for an
+ // animated image.
+ frameInfo->fFullyReceived = true;
+ frameInfo->fAlphaType = alpha_type(frame->hasAlpha());
+ }
+
+ return true;
+}
+
+static bool is_8888(SkColorType colorType) {
+ switch (colorType) {
+ case kRGBA_8888_SkColorType:
+ case kBGRA_8888_SkColorType:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void pick_memory_stages(SkColorType ct, SkRasterPipeline::StockStage* load,
+ SkRasterPipeline::StockStage* store) {
+ switch(ct) {
+ case kUnknown_SkColorType:
+ case kAlpha_8_SkColorType:
+ case kARGB_4444_SkColorType:
+ case kIndex_8_SkColorType:
+ case kGray_8_SkColorType:
+ SkASSERT(false);
+ break;
+ case kRGB_565_SkColorType:
+ if (load) *load = SkRasterPipeline::load_565;
+ if (store) *store = SkRasterPipeline::store_565;
+ break;
+ case kRGBA_8888_SkColorType:
+ case kBGRA_8888_SkColorType:
+ if (load) *load = SkRasterPipeline::load_8888;
+ if (store) *store = SkRasterPipeline::store_8888;
+ break;
+ case kRGBA_F16_SkColorType:
+ if (load) *load = SkRasterPipeline::load_f16;
+ if (store) *store = SkRasterPipeline::store_f16;
+ break;
+ }
+}
+
+static void blend_line(SkColorType dstCT, void* dst,
+ SkColorType srcCT, void* src,
+ bool needsSrgbToLinear, SkAlphaType at,
+ int width) {
+ // Setup conversion from the source and dest, which will be the same.
+ SkRasterPipeline convert_to_linear_premul;
+ if (needsSrgbToLinear) {
+ convert_to_linear_premul.append_from_srgb(at);
+ }
+ if (kUnpremul_SkAlphaType == at) {
+ // srcover assumes premultiplied inputs.
+ convert_to_linear_premul.append(SkRasterPipeline::premul);
+ }
+
+ SkRasterPipeline p;
+ SkRasterPipeline::StockStage load_dst, store_dst;
+ pick_memory_stages(dstCT, &load_dst, &store_dst);
+
+ // Load the final dst.
+ p.append(load_dst, dst);
+ p.extend(convert_to_linear_premul);
+ p.append(SkRasterPipeline::move_src_dst);
+
+ // Load the src.
+ SkRasterPipeline::StockStage load_src;
+ pick_memory_stages(srcCT, &load_src, nullptr);
+ p.append(load_src, src);
+ if (dstCT != srcCT) {
+ SkASSERT(kBGRA_8888_SkColorType == srcCT);
+ p.append(SkRasterPipeline::swap_rb);
+ }
+ p.extend(convert_to_linear_premul);
+
+ p.append(SkRasterPipeline::srcover);
+
+ // Convert back to dst.
+ if (kUnpremul_SkAlphaType == at) {
+ p.append(SkRasterPipeline::unpremul);
+ }
+ if (needsSrgbToLinear) {
+ p.append(SkRasterPipeline::to_srgb);
+ }
+ p.append(store_dst, dst);
+
+ p.run(0, width);
+}
+
SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
const Options& options, SkPMColor*, int*,
int* rowsDecodedPtr) {
- if (!conversion_possible(dstInfo, this->getInfo()) ||
- !this->initializeColorXform(dstInfo, options.fPremulBehavior))
+ // Ensure that we have parsed this far.
+ const int index = options.fFrameIndex;
+ if (index >= this->onGetFrameCount()) {
+ return kIncompleteInput;
+ }
+
+ const auto& srcInfo = this->getInfo();
{
- return kInvalidConversion;
+ auto info = srcInfo;
+ if (index > 0) {
+ auto alphaType = alpha_type(fFrameHolder.frame(index)->hasAlpha());
+ info = info.makeAlphaType(alphaType);
+ }
+ if (!conversion_possible(dstInfo, info) ||
+ !this->initializeColorXform(dstInfo, options.fPremulBehavior))
+ {
+ return kInvalidConversion;
+ }
+ }
+
+ if (index > 0 && (options.fSubset || dstInfo.dimensions() != srcInfo.dimensions())) {
+ // Subsetting and scaling are tricky when asking for frames beyond frame 0. In order to
+ // support it, we'll need to determine the proper rectangle for a
+ // WEBP_MUX_DISPOSE_BACKGROUND required frame before erasing it. (Currently the order
+ // is backwards.) Disable until it becomes clear that supporting it is important.
+ return kUnimplemented;
}
WebPDecoderConfig config;
@@ -212,16 +423,46 @@ SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
WebPIterator frame;
SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame);
- // If this succeeded in NewFromStream(), it should succeed again here.
- SkAssertResult(WebPDemuxGetFrame(fDemux, 1, &frame));
+ // If this succeeded in onGetFrameCount(), it should succeed again here.
+ SkAssertResult(WebPDemuxGetFrame(fDemux, index + 1, &frame));
+ const int requiredFrame = index == 0 ? kNone : fFrameHolder.frame(index)->getRequiredFrame();
// Get the frameRect. libwebp will have already signaled an error if this is not fully
// contained by the canvas.
auto frameRect = SkIRect::MakeXYWH(frame.x_offset, frame.y_offset, frame.width, frame.height);
- SkASSERT(this->getInfo().bounds().contains(frameRect));
- bool frameIsSubset = frameRect.size() != this->getInfo().dimensions();
- if (frameIsSubset) {
- SkSampler::Fill(dstInfo, dst, rowBytes, 0, options.fZeroInitialized);
+ SkASSERT(srcInfo.bounds().contains(frameRect));
+ const bool frameIsSubset = frameRect != srcInfo.bounds();
+ if (kNone == requiredFrame) {
+ if (frameIsSubset) {
+ SkSampler::Fill(dstInfo, dst, rowBytes, 0, options.fZeroInitialized);
+ }
+ } else {
+ if (!options.fHasPriorFrame) {
+ Options prevFrameOpts(options);
+ prevFrameOpts.fFrameIndex = requiredFrame;
+ const auto result = this->getPixels(dstInfo, dst, rowBytes, &prevFrameOpts,
+ nullptr, nullptr);
+ switch (result) {
+ case kSuccess:
+ break;
+ case kIncompleteInput:
+ return kInvalidInput;
+ default:
+ return result;
+ }
+ }
+
+ // Dispose bg color
+ const Frame* priorFrame = fFrameHolder.frame(requiredFrame);
+ if (priorFrame->getDisposalMethod() == SkCodecAnimation::RestoreBGColor_DisposalMethod) {
+ // FIXME: If we add support for scaling/subsets, this rectangle needs to be adjusted.
+ const auto priorRect = priorFrame->frameRect();
+ const auto info = dstInfo.makeWH(priorRect.width(), priorRect.height());
+ const size_t bpp = SkColorTypeBytesPerPixel(dstInfo.colorType());
+ const size_t offset = priorRect.x() * bpp + priorRect.y() * rowBytes;
+ auto* eraseDst = SkTAddOffset<void>(dst, offset);
+ SkSampler::Fill(info, eraseDst, rowBytes, 0, kNo_ZeroInitialized);
+ }
}
int dstX = frameRect.x();
@@ -265,7 +506,7 @@ SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
// Ignore the frame size and offset when determining if scaling is necessary.
int scaledWidth = subsetWidth;
int scaledHeight = subsetHeight;
- SkISize srcSize = options.fSubset ? options.fSubset->size() : this->getInfo().dimensions();
+ SkISize srcSize = options.fSubset ? options.fSubset->size() : srcInfo.dimensions();
if (srcSize != dstInfo.dimensions()) {
config.options.use_scaling = 1;
@@ -291,32 +532,55 @@ SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
config.options.scaled_height = scaledHeight;
}
- // Swizzling between RGBA and BGRA is zero cost in a color transform. So when we have a
- // color transform, we should decode to whatever is easiest for libwebp, and then let the
- // color transform swizzle if necessary.
- // Lossy webp is encoded as YUV (so RGBA and BGRA are the same cost). Lossless webp is
- // encoded as BGRA. This means decoding to BGRA is either faster or the same cost as RGBA.
- config.output.colorspace = this->colorXform() ? MODE_BGRA :
- webp_decode_mode(dstInfo.colorType(), dstInfo.alphaType() == kPremul_SkAlphaType);
+ const bool blendWithPrevFrame = requiredFrame != kNone && frame.blend_method == WEBP_MUX_BLEND
+ && frame.has_alpha;
+ if (blendWithPrevFrame && options.fPremulBehavior == SkTransferFunctionBehavior::kRespect) {
+ // Blending is done with SkRasterPipeline, which requires a color space that is valid for
+ // rendering.
+ const auto* cs = dstInfo.colorSpace();
+ if (!cs || (!cs->gammaCloseToSRGB() && !cs->gammaIsLinear())) {
+ return kInvalidConversion;
+ }
+ }
+
+ SkBitmap webpDst;
+ if ((this->colorXform() && !is_8888(dstInfo.colorType())) || blendWithPrevFrame) {
+ // Swizzling between RGBA and BGRA is zero cost in a color transform. So when we have a
+ // color transform, we should decode to whatever is easiest for libwebp, and then let the
+ // color transform swizzle if necessary.
+ // Lossy webp is encoded as YUV (so RGBA and BGRA are the same cost). Lossless webp is
+ // encoded as BGRA. This means decoding to BGRA is either faster or the same cost as RGBA.
+ auto info = dstInfo.makeColorType(kBGRA_8888_SkColorType);
+ if (info.alphaType() == kPremul_SkAlphaType && this->colorXform()) {
+ info = info.makeAlphaType(kUnpremul_SkAlphaType);
+ }
+
+ // We will decode the entire image and then perform the color transform. libwebp
+ // does not provide a row-by-row API. This is a shame particularly when we do not want
+ // 8888, since we will need to create another image sized buffer.
+ webpDst.allocPixels(info);
+ } else {
+ // libwebp can decode directly into the output memory.
+ auto info = dstInfo;
+ if (this->colorXform()) {
+ SkASSERT(is_8888(dstInfo.colorType()));
+ // As above, BGRA is faster or the same cost as RGBA for libwebp,
+ // and we're going to transform, so tell libwebp to use BGRA.
+ info = info.makeColorType(kBGRA_8888_SkColorType);
+ }
+ webpDst.installPixels(info, dst, rowBytes);
+ }
+
+ if (!frame.has_alpha) {
+ webpDst.setAlphaType(kOpaque_SkAlphaType);
+ }
+
+ config.output.colorspace = webp_decode_mode(webpDst.info());
config.output.is_external_memory = 1;
- // We will decode the entire image and then perform the color transform. libwebp
- // does not provide a row-by-row API. This is a shame particularly when we do not want
- // 8888, since we will need to create another image sized buffer.
- SkAutoTMalloc<uint32_t> pixels;
- bool needsCopy = this->colorXform() && kRGBA_8888_SkColorType != dstInfo.colorType() &&
- kBGRA_8888_SkColorType != dstInfo.colorType();
- void* webpDst = needsCopy ? pixels.reset(dstInfo.width() * dstInfo.height()) : dst;
- size_t webpRowBytes = needsCopy ? dstInfo.width() * sizeof(uint32_t) : rowBytes;
- size_t totalBytes = needsCopy ? webpRowBytes * dstInfo.height()
- : dstInfo.getSafeSize(webpRowBytes);
- size_t dstBpp = SkColorTypeBytesPerPixel(dstInfo.colorType());
- size_t webpBpp = needsCopy ? sizeof(uint32_t) : dstBpp;
-
- size_t offset = dstX * webpBpp + dstY * webpRowBytes;
- config.output.u.RGBA.rgba = SkTAddOffset<uint8_t>(webpDst, offset);
- config.output.u.RGBA.stride = (int) webpRowBytes;
- config.output.u.RGBA.size = totalBytes - offset;
+ config.output.u.RGBA.rgba = reinterpret_cast<uint8_t*>(webpDst.getAddr(dstX, dstY));
+ config.output.u.RGBA.stride = static_cast<int>(webpDst.rowBytes());
+ config.output.u.RGBA.size = webpDst.getSafeSize();
SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &config));
if (!idec) {
@@ -339,21 +603,55 @@ SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
return kInvalidInput;
}
+ // We're only transforming the new part of the frame, so no need to worry about the
+ // final composited alpha.
+ const auto srcAlpha = 0 == index ? srcInfo.alphaType() : alpha_type(frame.has_alpha);
+ const auto xformAlphaType = select_xform_alpha(dstInfo.alphaType(), srcAlpha);
+ const bool needsSrgbToLinear = dstInfo.gammaCloseToSRGB() &&
+ options.fPremulBehavior == SkTransferFunctionBehavior::kRespect;
+
+ const size_t dstBpp = SkColorTypeBytesPerPixel(dstInfo.colorType());
+ dst = SkTAddOffset<void>(dst, dstBpp * dstX + rowBytes * dstY);
+ const size_t srcRowBytes = config.output.u.RGBA.stride;
+
+ const auto dstCT = dstInfo.colorType();
if (this->colorXform()) {
- SkColorSpaceXform::ColorFormat dstColorFormat = select_xform_format(dstInfo.colorType());
- SkAlphaType xformAlphaType = select_xform_alpha(dstInfo.alphaType(),
- this->getInfo().alphaType());
+ const auto dstColorFormat = select_xform_format(dstInfo.colorType());
+ const auto srcColorFormat = SkColorSpaceXform::kBGRA_8888_ColorFormat;
+ SkASSERT(select_xform_format(webpDst.colorType()) == srcColorFormat);
uint32_t* xformSrc = (uint32_t*) config.output.u.RGBA.rgba;
- void* xformDst = SkTAddOffset<void>(dst, dstBpp * dstX + rowBytes * dstY);
- size_t srcRowBytes = config.output.u.RGBA.stride;
+ SkBitmap tmp;
+ void* xformDst;
+
+ if (blendWithPrevFrame) {
+ // Xform into temporary bitmap big enough for one row.
+ tmp.allocPixels(dstInfo.makeWH(scaledWidth, 1));
+ xformDst = tmp.getPixels();
+ } else {
+ xformDst = dst;
+ }
for (int y = 0; y < rowsDecoded; y++) {
SkAssertResult(this->colorXform()->apply(dstColorFormat, xformDst,
- SkColorSpaceXform::kBGRA_8888_ColorFormat, xformSrc, scaledWidth,
- xformAlphaType));
- xformDst = SkTAddOffset<void>(xformDst, rowBytes);
+ srcColorFormat, xformSrc, scaledWidth, xformAlphaType));
+ if (blendWithPrevFrame) {
+ blend_line(dstCT, &dst, dstCT, &xformDst, needsSrgbToLinear, xformAlphaType,
+ scaledWidth);
+ dst = SkTAddOffset<void>(dst, rowBytes);
+ } else {
+ xformDst = SkTAddOffset<void>(xformDst, rowBytes);
+ }
xformSrc = SkTAddOffset<uint32_t>(xformSrc, srcRowBytes);
}
+ } else if (blendWithPrevFrame) {
+ const uint8_t* src = config.output.u.RGBA.rgba;
+
+ for (int y = 0; y < rowsDecoded; y++) {
+ blend_line(dstCT, &dst, webpDst.colorType(), &src, needsSrgbToLinear,
+ xformAlphaType, scaledWidth);
+ src = SkTAddOffset<const uint8_t>(src, srcRowBytes);
+ dst = SkTAddOffset<void>(dst, rowBytes);
+ }
}
return result;
@@ -365,4 +663,7 @@ SkWebpCodec::SkWebpCodec(int width, int height, const SkEncodedInfo& info,
: INHERITED(width, height, info, stream, std::move(colorSpace))
, fDemux(demux)
, fData(std::move(data))
-{}
+ , fFailed(false)
+{
+ fFrameHolder.setScreenSize(width, height);
+}