diff options
Diffstat (limited to 'src/codec/SkWebpCodec.cpp')
-rw-r--r-- | src/codec/SkWebpCodec.cpp | 419 |
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); +} |