/* * 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 "include/codec/SkAndroidCodec.h" #include "include/codec/SkCodec.h" #include "include/core/SkBitmap.h" #include "include/core/SkColorSpace.h" #include "include/core/SkData.h" #include "include/core/SkImage.h" #include "include/private/SkHalf.h" #include "src/codec/SkBmpCodec.h" #include "src/codec/SkCodecPriv.h" #include "src/codec/SkFrameHolder.h" #ifdef SK_HAS_HEIF_LIBRARY #include "src/codec/SkHeifCodec.h" #endif #include "src/codec/SkIcoCodec.h" #include "src/codec/SkJpegCodec.h" #ifdef SK_CODEC_DECODES_PNG #include "src/codec/SkPngCodec.h" #endif #include "include/core/SkStream.h" #include "src/codec/SkRawCodec.h" #include "src/codec/SkWbmpCodec.h" #include "src/codec/SkWebpCodec.h" #ifdef SK_HAS_WUFFS_LIBRARY #include "src/codec/SkWuffsCodec.h" #elif defined(SK_USE_LIBGIFCODEC) #include "SkGifCodec.h" #endif struct DecoderProc { bool (*IsFormat)(const void*, size_t); std::unique_ptr (*MakeFromStream)(std::unique_ptr, SkCodec::Result*); }; static std::vector* decoders() { static auto* decoders = new std::vector { #ifdef SK_CODEC_DECODES_JPEG { SkJpegCodec::IsJpeg, SkJpegCodec::MakeFromStream }, #endif #ifdef SK_CODEC_DECODES_WEBP { SkWebpCodec::IsWebp, SkWebpCodec::MakeFromStream }, #endif #ifdef SK_HAS_WUFFS_LIBRARY { SkWuffsCodec_IsFormat, SkWuffsCodec_MakeFromStream }, #elif defined(SK_USE_LIBGIFCODEC) { SkGifCodec::IsGif, SkGifCodec::MakeFromStream }, #endif #ifdef SK_CODEC_DECODES_PNG { SkIcoCodec::IsIco, SkIcoCodec::MakeFromStream }, #endif { SkBmpCodec::IsBmp, SkBmpCodec::MakeFromStream }, { SkWbmpCodec::IsWbmp, SkWbmpCodec::MakeFromStream }, }; return decoders; } void SkCodec::Register( bool (*peek)(const void*, size_t), std::unique_ptr (*make)(std::unique_ptr, SkCodec::Result*)) { decoders()->push_back(DecoderProc{peek, make}); } std::unique_ptr SkCodec::MakeFromStream( std::unique_ptr stream, Result* outResult, SkPngChunkReader* chunkReader, SelectionPolicy selectionPolicy) { Result resultStorage; if (!outResult) { outResult = &resultStorage; } if (!stream) { *outResult = kInvalidInput; return nullptr; } if (selectionPolicy != SelectionPolicy::kPreferStillImage && selectionPolicy != SelectionPolicy::kPreferAnimation) { *outResult = kInvalidParameters; return nullptr; } constexpr size_t bytesToRead = MinBufferedBytesNeeded(); char buffer[bytesToRead]; size_t bytesRead = stream->peek(buffer, bytesToRead); // It is also possible to have a complete image less than bytesToRead bytes // (e.g. a 1 x 1 wbmp), meaning peek() would return less than bytesToRead. // Assume that if bytesRead < bytesToRead, but > 0, the stream is shorter // than bytesToRead, so pass that directly to the decoder. // It also is possible the stream uses too small a buffer for peeking, but // we trust the caller to use a large enough buffer. if (0 == bytesRead) { // TODO: After implementing peek in CreateJavaOutputStreamAdaptor.cpp, this // printf could be useful to notice failures. // SkCodecPrintf("Encoded image data failed to peek!\n"); // It is possible the stream does not support peeking, but does support // rewinding. // Attempt to read() and pass the actual amount read to the decoder. bytesRead = stream->read(buffer, bytesToRead); if (!stream->rewind()) { SkCodecPrintf("Encoded image data could not peek or rewind to determine format!\n"); *outResult = kCouldNotRewind; return nullptr; } } // PNG is special, since we want to be able to supply an SkPngChunkReader. // But this code follows the same pattern as the loop. #ifdef SK_CODEC_DECODES_PNG if (SkPngCodec::IsPng(buffer, bytesRead)) { return SkPngCodec::MakeFromStream(std::move(stream), outResult, chunkReader); } else #endif { for (DecoderProc proc : *decoders()) { if (proc.IsFormat(buffer, bytesRead)) { return proc.MakeFromStream(std::move(stream), outResult); } } #ifdef SK_HAS_HEIF_LIBRARY SkEncodedImageFormat format; if (SkHeifCodec::IsSupported(buffer, bytesRead, &format)) { return SkHeifCodec::MakeFromStream(std::move(stream), selectionPolicy, format, outResult); } #endif #ifdef SK_CODEC_DECODES_RAW // Try to treat the input as RAW if all the other checks failed. return SkRawCodec::MakeFromStream(std::move(stream), outResult); #endif } if (bytesRead < bytesToRead) { *outResult = kIncompleteInput; } else { *outResult = kUnimplemented; } return nullptr; } std::unique_ptr SkCodec::MakeFromData(sk_sp data, SkPngChunkReader* reader) { if (!data) { return nullptr; } return MakeFromStream(SkMemoryStream::Make(std::move(data)), nullptr, reader); } SkCodec::SkCodec(SkEncodedInfo&& info, XformFormat srcFormat, std::unique_ptr stream, SkEncodedOrigin origin) : fEncodedInfo(std::move(info)) , fSrcXformFormat(srcFormat) , fStream(std::move(stream)) , fNeedsRewind(false) , fOrigin(origin) , fDstInfo() , fOptions() , fCurrScanline(-1) , fStartedIncrementalDecode(false) , fAndroidCodecHandlesFrameIndex(false) {} SkCodec::~SkCodec() {} bool SkCodec::queryYUVAInfo(const SkYUVAPixmapInfo::SupportedDataTypes& supportedDataTypes, SkYUVAPixmapInfo* yuvaPixmapInfo) const { if (!yuvaPixmapInfo) { return false; } return this->onQueryYUVAInfo(supportedDataTypes, yuvaPixmapInfo) && yuvaPixmapInfo->isSupported(supportedDataTypes); } SkCodec::Result SkCodec::getYUVAPlanes(const SkYUVAPixmaps& yuvaPixmaps) { if (!yuvaPixmaps.isValid()) { return kInvalidInput; } if (!this->rewindIfNeeded()) { return kCouldNotRewind; } return this->onGetYUVAPlanes(yuvaPixmaps); } bool SkCodec::conversionSupported(const SkImageInfo& dst, bool srcIsOpaque, bool needsColorXform) { if (!valid_alpha(dst.alphaType(), srcIsOpaque)) { return false; } switch (dst.colorType()) { case kRGBA_8888_SkColorType: case kBGRA_8888_SkColorType: case kRGBA_F16_SkColorType: return true; case kRGB_565_SkColorType: return srcIsOpaque; case kGray_8_SkColorType: return SkEncodedInfo::kGray_Color == fEncodedInfo.color() && srcIsOpaque; case kAlpha_8_SkColorType: // conceptually we can convert anything into alpha_8, but we haven't actually coded // all of those other conversions yet. return SkEncodedInfo::kXAlpha_Color == fEncodedInfo.color(); default: return false; } } bool SkCodec::rewindIfNeeded() { // Store the value of fNeedsRewind so we can update it. Next read will // require a rewind. const bool needsRewind = fNeedsRewind; fNeedsRewind = true; if (!needsRewind) { return true; } // startScanlineDecode will need to be called before decoding scanlines. fCurrScanline = -1; // startIncrementalDecode will need to be called before incrementalDecode. fStartedIncrementalDecode = false; // Some codecs do not have a stream. They may hold onto their own data or another codec. // They must handle rewinding themselves. if (fStream && !fStream->rewind()) { return false; } return this->onRewind(); } static SkIRect frame_rect_on_screen(SkIRect frameRect, const SkIRect& screenRect) { if (!frameRect.intersect(screenRect)) { return SkIRect::MakeEmpty(); } return frameRect; } bool zero_rect(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes, SkISize srcDimensions, SkIRect prevRect) { const auto dimensions = dstInfo.dimensions(); if (dimensions != srcDimensions) { SkRect src = SkRect::Make(srcDimensions); SkRect dst = SkRect::Make(dimensions); SkMatrix map = SkMatrix::RectToRect(src, dst); SkRect asRect = SkRect::Make(prevRect); if (!map.mapRect(&asRect)) { return false; } asRect.roundOut(&prevRect); } if (!prevRect.intersect(SkIRect::MakeSize(dimensions))) { // Nothing to zero, due to scaling or bad frame rect. return true; } const SkImageInfo info = dstInfo.makeDimensions(prevRect.size()); const size_t bpp = dstInfo.bytesPerPixel(); const size_t offset = prevRect.x() * bpp + prevRect.y() * rowBytes; void* eraseDst = SkTAddOffset(pixels, offset); SkSampler::Fill(info, eraseDst, rowBytes, SkCodec::kNo_ZeroInitialized); return true; } SkCodec::Result SkCodec::handleFrameIndex(const SkImageInfo& info, void* pixels, size_t rowBytes, const Options& options, SkAndroidCodec* androidCodec) { if (androidCodec) { // This is never set back to false. If SkAndroidCodec is calling this method, its fCodec // should never call it directly. fAndroidCodecHandlesFrameIndex = true; } else if (fAndroidCodecHandlesFrameIndex) { return kSuccess; } if (!this->rewindIfNeeded()) { return kCouldNotRewind; } const int index = options.fFrameIndex; if (0 == index) { return this->initializeColorXform(info, fEncodedInfo.alpha(), fEncodedInfo.opaque()) ? kSuccess : kInvalidConversion; } if (index < 0) { return kInvalidParameters; } if (options.fSubset) { // If we add support for this, we need to update the code that zeroes // a kRestoreBGColor frame. return kInvalidParameters; } if (index >= this->onGetFrameCount()) { return kIncompleteInput; } const auto* frameHolder = this->getFrameHolder(); SkASSERT(frameHolder); const auto* frame = frameHolder->getFrame(index); SkASSERT(frame); const int requiredFrame = frame->getRequiredFrame(); if (requiredFrame != kNoFrame) { const SkFrame* preppedFrame = nullptr; if (options.fPriorFrame == kNoFrame) { Result result = kInternalError; if (androidCodec) { #ifdef SK_HAS_ANDROID_CODEC SkAndroidCodec::AndroidOptions prevFrameOptions( reinterpret_cast(options)); prevFrameOptions.fFrameIndex = requiredFrame; result = androidCodec->getAndroidPixels(info, pixels, rowBytes, &prevFrameOptions); #endif } else { Options prevFrameOptions(options); prevFrameOptions.fFrameIndex = requiredFrame; result = this->getPixels(info, pixels, rowBytes, &prevFrameOptions); } if (result != kSuccess) { return result; } preppedFrame = frameHolder->getFrame(requiredFrame); } else { // Check for a valid frame as a starting point. Alternatively, we could // treat an invalid frame as not providing one, but rejecting it will // make it easier to catch the mistake. if (options.fPriorFrame < requiredFrame || options.fPriorFrame >= index) { return kInvalidParameters; } preppedFrame = frameHolder->getFrame(options.fPriorFrame); } SkASSERT(preppedFrame); switch (preppedFrame->getDisposalMethod()) { case SkCodecAnimation::DisposalMethod::kRestorePrevious: SkASSERT(options.fPriorFrame != kNoFrame); return kInvalidParameters; case SkCodecAnimation::DisposalMethod::kRestoreBGColor: // If a frame after the required frame is provided, there is no // need to clear, since it must be covered by the desired frame. // FIXME: If the required frame is kRestoreBGColor, we don't actually need to decode // it, since we'll just clear it to transparent. Instead, we could decode *its* // required frame and then clear. if (preppedFrame->frameId() == requiredFrame) { SkIRect preppedRect = preppedFrame->frameRect(); if (!zero_rect(info, pixels, rowBytes, this->dimensions(), preppedRect)) { return kInternalError; } } break; default: break; } } return this->initializeColorXform(info, frame->reportedAlpha(), !frame->hasAlpha()) ? kSuccess : kInvalidConversion; } SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, const Options* options) { if (kUnknown_SkColorType == info.colorType()) { return kInvalidConversion; } if (nullptr == pixels) { return kInvalidParameters; } if (rowBytes < info.minRowBytes()) { return kInvalidParameters; } // Default options. Options optsStorage; if (nullptr == options) { options = &optsStorage; } else { if (options->fSubset) { SkIRect subset(*options->fSubset); if (!this->onGetValidSubset(&subset) || subset != *options->fSubset) { // FIXME: How to differentiate between not supporting subset at all // and not supporting this particular subset? return kUnimplemented; } } } const Result frameIndexResult = this->handleFrameIndex(info, pixels, rowBytes, *options); if (frameIndexResult != kSuccess) { return frameIndexResult; } // FIXME: Support subsets somehow? Note that this works for SkWebpCodec // because it supports arbitrary scaling/subset combinations. if (!this->dimensionsSupported(info.dimensions())) { return kInvalidScale; } fDstInfo = info; fOptions = *options; // On an incomplete decode, the subclass will specify the number of scanlines that it decoded // successfully. int rowsDecoded = 0; const Result result = this->onGetPixels(info, pixels, rowBytes, *options, &rowsDecoded); // A return value of kIncompleteInput indicates a truncated image stream. // In this case, we will fill any uninitialized memory with a default value. // Some subclasses will take care of filling any uninitialized memory on // their own. They indicate that all of the memory has been filled by // setting rowsDecoded equal to the height. if ((kIncompleteInput == result || kErrorInInput == result) && rowsDecoded != info.height()) { // FIXME: (skbug.com/5772) fillIncompleteImage will fill using the swizzler's width, unless // there is a subset. In that case, it will use the width of the subset. From here, the // subset will only be non-null in the case of SkWebpCodec, but it treats the subset // differenty from the other codecs, and it needs to use the width specified by the info. // Set the subset to null so SkWebpCodec uses the correct width. fOptions.fSubset = nullptr; this->fillIncompleteImage(info, pixels, rowBytes, options->fZeroInitialized, info.height(), rowsDecoded); } return result; } std::tuple, SkCodec::Result> SkCodec::getImage(const SkImageInfo& info, const Options* options) { SkBitmap bm; if (!bm.tryAllocPixels(info)) { return {nullptr, kInternalError}; } Result result = this->getPixels(info, bm.getPixels(), bm.rowBytes(), options); switch (result) { case kSuccess: case kIncompleteInput: case kErrorInInput: bm.setImmutable(); return {bm.asImage(), result}; default: break; } return {nullptr, result}; } std::tuple, SkCodec::Result> SkCodec::getImage() { return this->getImage(this->getInfo(), nullptr); } SkCodec::Result SkCodec::startIncrementalDecode(const SkImageInfo& info, void* pixels, size_t rowBytes, const SkCodec::Options* options) { fStartedIncrementalDecode = false; if (kUnknown_SkColorType == info.colorType()) { return kInvalidConversion; } if (nullptr == pixels) { return kInvalidParameters; } // Set options. Options optsStorage; if (nullptr == options) { options = &optsStorage; } else { if (options->fSubset) { SkIRect size = SkIRect::MakeSize(info.dimensions()); if (!size.contains(*options->fSubset)) { return kInvalidParameters; } const int top = options->fSubset->top(); const int bottom = options->fSubset->bottom(); if (top < 0 || top >= info.height() || top >= bottom || bottom > info.height()) { return kInvalidParameters; } } } const Result frameIndexResult = this->handleFrameIndex(info, pixels, rowBytes, *options); if (frameIndexResult != kSuccess) { return frameIndexResult; } if (!this->dimensionsSupported(info.dimensions())) { return kInvalidScale; } fDstInfo = info; fOptions = *options; const Result result = this->onStartIncrementalDecode(info, pixels, rowBytes, fOptions); if (kSuccess == result) { fStartedIncrementalDecode = true; } else if (kUnimplemented == result) { // FIXME: This is temporarily necessary, until we transition SkCodec // implementations from scanline decoding to incremental decoding. // SkAndroidCodec will first attempt to use incremental decoding, but // will fall back to scanline decoding if incremental returns // kUnimplemented. rewindIfNeeded(), above, set fNeedsRewind to true // (after potentially rewinding), but we do not want the next call to // startScanlineDecode() to do a rewind. fNeedsRewind = false; } return result; } SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& info, const SkCodec::Options* options) { // Reset fCurrScanline in case of failure. fCurrScanline = -1; // Set options. Options optsStorage; if (nullptr == options) { options = &optsStorage; } else if (options->fSubset) { SkIRect size = SkIRect::MakeSize(info.dimensions()); if (!size.contains(*options->fSubset)) { return kInvalidInput; } // We only support subsetting in the x-dimension for scanline decoder. // Subsetting in the y-dimension can be accomplished using skipScanlines(). if (options->fSubset->top() != 0 || options->fSubset->height() != info.height()) { return kInvalidInput; } } // Scanline decoding only supports decoding the first frame. if (options->fFrameIndex != 0) { return kUnimplemented; } // The void* dst and rowbytes in handleFrameIndex or only used for decoding prior // frames, which is not supported here anyway, so it is safe to pass nullptr/0. const Result frameIndexResult = this->handleFrameIndex(info, nullptr, 0, *options); if (frameIndexResult != kSuccess) { return frameIndexResult; } // FIXME: Support subsets somehow? if (!this->dimensionsSupported(info.dimensions())) { return kInvalidScale; } const Result result = this->onStartScanlineDecode(info, *options); if (result != SkCodec::kSuccess) { return result; } // FIXME: See startIncrementalDecode. That method set fNeedsRewind to false // so that when onStartScanlineDecode calls rewindIfNeeded it would not // rewind. But it also relies on that call to rewindIfNeeded to set // fNeedsRewind to true for future decodes. When // fAndroidCodecHandlesFrameIndex is true, that call to rewindIfNeeded is // skipped, so this method sets it back to true. SkASSERT(fAndroidCodecHandlesFrameIndex || fNeedsRewind); fNeedsRewind = true; fCurrScanline = 0; fDstInfo = info; fOptions = *options; return kSuccess; } int SkCodec::getScanlines(void* dst, int countLines, size_t rowBytes) { if (fCurrScanline < 0) { return 0; } SkASSERT(!fDstInfo.isEmpty()); if (countLines <= 0 || fCurrScanline + countLines > fDstInfo.height()) { return 0; } const int linesDecoded = this->onGetScanlines(dst, countLines, rowBytes); if (linesDecoded < countLines) { this->fillIncompleteImage(this->dstInfo(), dst, rowBytes, this->options().fZeroInitialized, countLines, linesDecoded); } fCurrScanline += countLines; return linesDecoded; } bool SkCodec::skipScanlines(int countLines) { if (fCurrScanline < 0) { return false; } SkASSERT(!fDstInfo.isEmpty()); if (countLines < 0 || fCurrScanline + countLines > fDstInfo.height()) { // Arguably, we could just skip the scanlines which are remaining, // and return true. We choose to return false so the client // can catch their bug. return false; } bool result = this->onSkipScanlines(countLines); fCurrScanline += countLines; return result; } int SkCodec::outputScanline(int inputScanline) const { SkASSERT(0 <= inputScanline && inputScanline < fEncodedInfo.height()); return this->onOutputScanline(inputScanline); } int SkCodec::onOutputScanline(int inputScanline) const { switch (this->getScanlineOrder()) { case kTopDown_SkScanlineOrder: return inputScanline; case kBottomUp_SkScanlineOrder: return fEncodedInfo.height() - inputScanline - 1; default: // This case indicates an interlaced gif and is implemented by SkGifCodec. SkASSERT(false); return 0; } } void SkCodec::fillIncompleteImage(const SkImageInfo& info, void* dst, size_t rowBytes, ZeroInitialized zeroInit, int linesRequested, int linesDecoded) { if (kYes_ZeroInitialized == zeroInit) { return; } const int linesRemaining = linesRequested - linesDecoded; SkSampler* sampler = this->getSampler(false); const int fillWidth = sampler ? sampler->fillWidth() : fOptions.fSubset ? fOptions.fSubset->width() : info.width() ; void* fillDst = this->getScanlineOrder() == kBottomUp_SkScanlineOrder ? dst : SkTAddOffset(dst, linesDecoded * rowBytes); const auto fillInfo = info.makeWH(fillWidth, linesRemaining); SkSampler::Fill(fillInfo, fillDst, rowBytes, kNo_ZeroInitialized); } bool sk_select_xform_format(SkColorType colorType, bool forColorTable, skcms_PixelFormat* outFormat) { SkASSERT(outFormat); switch (colorType) { case kRGBA_8888_SkColorType: *outFormat = skcms_PixelFormat_RGBA_8888; break; case kBGRA_8888_SkColorType: *outFormat = skcms_PixelFormat_BGRA_8888; break; case kRGB_565_SkColorType: if (forColorTable) { #ifdef SK_PMCOLOR_IS_RGBA *outFormat = skcms_PixelFormat_RGBA_8888; #else *outFormat = skcms_PixelFormat_BGRA_8888; #endif break; } *outFormat = skcms_PixelFormat_BGR_565; break; case kRGBA_F16_SkColorType: *outFormat = skcms_PixelFormat_RGBA_hhhh; break; case kGray_8_SkColorType: *outFormat = skcms_PixelFormat_G_8; break; default: return false; } return true; } bool SkCodec::initializeColorXform(const SkImageInfo& dstInfo, SkEncodedInfo::Alpha encodedAlpha, bool srcIsOpaque) { fXformTime = kNo_XformTime; bool needsColorXform = false; if (this->usesColorXform()) { if (kRGBA_F16_SkColorType == dstInfo.colorType()) { needsColorXform = true; if (dstInfo.colorSpace()) { dstInfo.colorSpace()->toProfile(&fDstProfile); } else { // Use the srcProfile to avoid conversion. const auto* srcProfile = fEncodedInfo.profile(); fDstProfile = srcProfile ? *srcProfile : *skcms_sRGB_profile(); } } else if (dstInfo.colorSpace()) { dstInfo.colorSpace()->toProfile(&fDstProfile); const auto* srcProfile = fEncodedInfo.profile(); if (!srcProfile) { srcProfile = skcms_sRGB_profile(); } if (!skcms_ApproximatelyEqualProfiles(srcProfile, &fDstProfile) ) { needsColorXform = true; } } } if (!this->conversionSupported(dstInfo, srcIsOpaque, needsColorXform)) { return false; } if (needsColorXform) { fXformTime = SkEncodedInfo::kPalette_Color != fEncodedInfo.color() || kRGBA_F16_SkColorType == dstInfo.colorType() ? kDecodeRow_XformTime : kPalette_XformTime; if (!sk_select_xform_format(dstInfo.colorType(), fXformTime == kPalette_XformTime, &fDstXformFormat)) { return false; } if (encodedAlpha == SkEncodedInfo::kUnpremul_Alpha && dstInfo.alphaType() == kPremul_SkAlphaType) { fDstXformAlphaFormat = skcms_AlphaFormat_PremulAsEncoded; } else { fDstXformAlphaFormat = skcms_AlphaFormat_Unpremul; } } return true; } void SkCodec::applyColorXform(void* dst, const void* src, int count) const { // It is okay for srcProfile to be null. This will use sRGB. const auto* srcProfile = fEncodedInfo.profile(); SkAssertResult(skcms_Transform(src, fSrcXformFormat, skcms_AlphaFormat_Unpremul, srcProfile, dst, fDstXformFormat, fDstXformAlphaFormat, &fDstProfile, count)); } std::vector SkCodec::getFrameInfo() { const int frameCount = this->getFrameCount(); SkASSERT(frameCount >= 0); if (frameCount <= 0) { return std::vector{}; } if (frameCount == 1 && !this->onGetFrameInfo(0, nullptr)) { // Not animated. return std::vector{}; } std::vector result(frameCount); for (int i = 0; i < frameCount; ++i) { SkAssertResult(this->onGetFrameInfo(i, &result[i])); } return result; } const char* SkCodec::ResultToString(Result result) { switch (result) { case kSuccess: return "success"; case kIncompleteInput: return "incomplete input"; case kErrorInInput: return "error in input"; case kInvalidConversion: return "invalid conversion"; case kInvalidScale: return "invalid scale"; case kInvalidParameters: return "invalid parameters"; case kInvalidInput: return "invalid input"; case kCouldNotRewind: return "could not rewind"; case kInternalError: return "internal error"; case kUnimplemented: return "unimplemented"; default: SkASSERT(false); return "bogus result value"; } } void SkFrame::fillIn(SkCodec::FrameInfo* frameInfo, bool fullyReceived) const { SkASSERT(frameInfo); frameInfo->fRequiredFrame = fRequiredFrame; frameInfo->fDuration = fDuration; frameInfo->fFullyReceived = fullyReceived; frameInfo->fAlphaType = fHasAlpha ? kUnpremul_SkAlphaType : kOpaque_SkAlphaType; frameInfo->fHasAlphaWithinBounds = this->reportedAlpha() != SkEncodedInfo::kOpaque_Alpha; frameInfo->fDisposalMethod = fDisposalMethod; frameInfo->fBlend = fBlend; frameInfo->fFrameRect = fRect; } static bool independent(const SkFrame& frame) { return frame.getRequiredFrame() == SkCodec::kNoFrame; } static bool restore_bg(const SkFrame& frame) { return frame.getDisposalMethod() == SkCodecAnimation::DisposalMethod::kRestoreBGColor; } // As its name suggests, this method computes a frame's alpha (e.g. completely // opaque, unpremul, binary) and its required frame (a preceding frame that // this frame depends on, to draw the complete image at this frame's point in // the animation stream), and calls this frame's setter methods with that // computed information. // // A required frame of kNoFrame means that this frame is independent: drawing // the complete image at this frame's point in the animation stream does not // require first preparing the pixel buffer based on another frame. Instead, // drawing can start from an uninitialized pixel buffer. // // "Uninitialized" is from the SkCodec's caller's point of view. In the SkCodec // implementation, for independent frames, first party Skia code (in src/codec) // will typically fill the buffer with a uniform background color (e.g. // transparent black) before calling into third party codec-specific code (e.g. // libjpeg or libpng). Pixels outside of the frame's rect will remain this // background color after drawing this frame. For incomplete decodes, pixels // inside that rect may be (at least temporarily) set to that background color. // In an incremental decode, later passes may then overwrite that background // color. // // Determining kNoFrame or otherwise involves testing a number of conditions // sequentially. The first satisfied condition results in setting the required // frame to kNoFrame (an "INDx" condition) or to a non-negative frame number (a // "DEPx" condition), and the function returning early. Those "INDx" and "DEPx" // labels also map to comments in the function body. // // - IND1: this frame is the first frame. // - IND2: this frame fills out the whole image, and it is completely opaque // or it overwrites (not blends with) the previous frame. // - IND3: all preceding frames' disposals are kRestorePrevious. // - IND4: the prevFrame's disposal is kRestoreBGColor, and it fills out the // whole image or it is itself otherwise independent. // - DEP5: this frame reports alpha (it is not completely opaque) and it // blends with (not overwrites) the previous frame. // - IND6: this frame's rect covers the rects of all preceding frames back to // and including the most recent independent frame before this frame. // - DEP7: unconditional. // // The "prevFrame" variable initially points to the previous frame (also known // as the prior frame), but that variable may iterate further backwards over // the course of this computation. void SkFrameHolder::setAlphaAndRequiredFrame(SkFrame* frame) { const bool reportsAlpha = frame->reportedAlpha() != SkEncodedInfo::kOpaque_Alpha; const auto screenRect = SkIRect::MakeWH(fScreenWidth, fScreenHeight); const auto frameRect = frame_rect_on_screen(frame->frameRect(), screenRect); const int i = frame->frameId(); if (0 == i) { frame->setHasAlpha(reportsAlpha || frameRect != screenRect); frame->setRequiredFrame(SkCodec::kNoFrame); // IND1 return; } const bool blendWithPrevFrame = frame->getBlend() == SkCodecAnimation::Blend::kSrcOver; if ((!reportsAlpha || !blendWithPrevFrame) && frameRect == screenRect) { frame->setHasAlpha(reportsAlpha); frame->setRequiredFrame(SkCodec::kNoFrame); // IND2 return; } const SkFrame* prevFrame = this->getFrame(i-1); while (prevFrame->getDisposalMethod() == SkCodecAnimation::DisposalMethod::kRestorePrevious) { const int prevId = prevFrame->frameId(); if (0 == prevId) { frame->setHasAlpha(true); frame->setRequiredFrame(SkCodec::kNoFrame); // IND3 return; } prevFrame = this->getFrame(prevId - 1); } const bool clearPrevFrame = restore_bg(*prevFrame); auto prevFrameRect = frame_rect_on_screen(prevFrame->frameRect(), screenRect); if (clearPrevFrame) { if (prevFrameRect == screenRect || independent(*prevFrame)) { frame->setHasAlpha(true); frame->setRequiredFrame(SkCodec::kNoFrame); // IND4 return; } } if (reportsAlpha && blendWithPrevFrame) { // Note: We could be more aggressive here. If prevFrame clears // to background color and covers its required frame (and that // frame is independent), prevFrame could be marked independent. // Would this extra complexity be worth it? frame->setRequiredFrame(prevFrame->frameId()); // DEP5 frame->setHasAlpha(prevFrame->hasAlpha() || clearPrevFrame); return; } while (frameRect.contains(prevFrameRect)) { const int prevRequiredFrame = prevFrame->getRequiredFrame(); if (prevRequiredFrame == SkCodec::kNoFrame) { frame->setRequiredFrame(SkCodec::kNoFrame); // IND6 frame->setHasAlpha(true); return; } prevFrame = this->getFrame(prevRequiredFrame); prevFrameRect = frame_rect_on_screen(prevFrame->frameRect(), screenRect); } frame->setRequiredFrame(prevFrame->frameId()); // DEP7 if (restore_bg(*prevFrame)) { frame->setHasAlpha(true); return; } SkASSERT(prevFrame->getDisposalMethod() == SkCodecAnimation::DisposalMethod::kKeep); frame->setHasAlpha(prevFrame->hasAlpha() || (reportsAlpha && !blendWithPrevFrame)); }