| /* | |
| * Copyright 2022 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "src/codec/SkAvifCodec.h" | |
| #include "include/codec/SkCodec.h" | |
| #include "include/codec/SkCodecAnimation.h" | |
| #include "include/core/SkColorType.h" | |
| #include "include/core/SkImageInfo.h" | |
| #include "include/core/SkSize.h" | |
| #include "include/core/SkStream.h" | |
| #include "include/core/SkTypes.h" | |
| #include "modules/skcms/skcms.h" | |
| #include "src/core/SkStreamPriv.h" | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <utility> | |
| #include "avif/avif.h" | |
| #include "avif/internal.h" | |
| void AvifDecoderDeleter::operator()(avifDecoder* decoder) const { | |
| if (decoder != nullptr) { | |
| avifDecoderDestroy(decoder); | |
| } | |
| } | |
| bool SkAvifCodec::IsAvif(const void* buffer, size_t bytesRead) { | |
| avifROData avifData = {static_cast<const uint8_t*>(buffer), bytesRead}; | |
| bool isAvif = avifPeekCompatibleFileType(&avifData) == AVIF_TRUE; | |
| if (isAvif) return true; | |
| // Peeking sometimes fails if the ftyp box is too large. Check the signature | |
| // just to be sure. | |
| const char* bytes = static_cast<const char*>(buffer); | |
| isAvif = bytesRead >= 12 && !memcmp(&bytes[4], "ftyp", 4) && | |
| (!memcmp(&bytes[8], "avif", 4) || !memcmp(&bytes[8], "avis", 4)); | |
| return isAvif; | |
| } | |
| std::unique_ptr<SkCodec> SkAvifCodec::MakeFromStream(std::unique_ptr<SkStream> stream, | |
| Result* result) { | |
| AvifDecoder avifDecoder(avifDecoderCreate()); | |
| if (avifDecoder == nullptr) { | |
| *result = kInternalError; | |
| return nullptr; | |
| } | |
| avifDecoder->ignoreXMP = AVIF_TRUE; | |
| avifDecoder->ignoreExif = AVIF_TRUE; | |
| avifDecoder->allowProgressive = AVIF_FALSE; | |
| avifDecoder->allowIncremental = AVIF_FALSE; | |
| avifDecoder->strictFlags = AVIF_STRICT_DISABLED; | |
| // TODO(vigneshv): Enable threading based on number of CPU cores available. | |
| avifDecoder->maxThreads = 1; | |
| // libavif 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.get()); | |
| // If we are forced to copy the stream to a data, we can go ahead and | |
| // delete the stream. | |
| stream.reset(nullptr); | |
| } | |
| avifResult res = avifDecoderSetIOMemory(avifDecoder.get(), data->bytes(), data->size()); | |
| if (res != AVIF_RESULT_OK) { | |
| *result = kInternalError; | |
| return nullptr; | |
| } | |
| res = avifDecoderParse(avifDecoder.get()); | |
| if (res != AVIF_RESULT_OK) { | |
| *result = kInvalidInput; | |
| return nullptr; | |
| } | |
| std::unique_ptr<SkEncodedInfo::ICCProfile> profile = nullptr; | |
| // TODO(vigneshv): Get ICC Profile from the avif decoder. | |
| const int bitsPerComponent = avifDecoder->image->depth > 8 ? 16 : 8; | |
| SkEncodedInfo::Color color; | |
| SkEncodedInfo::Alpha alpha; | |
| if (avifDecoder->alphaPresent) { | |
| color = SkEncodedInfo::kRGBA_Color; | |
| alpha = SkEncodedInfo::kUnpremul_Alpha; | |
| } else { | |
| color = SkEncodedInfo::kRGB_Color; | |
| alpha = SkEncodedInfo::kOpaque_Alpha; | |
| } | |
| SkEncodedInfo info = SkEncodedInfo::Make(avifDecoder->image->width, | |
| avifDecoder->image->height, | |
| color, | |
| alpha, | |
| bitsPerComponent, | |
| std::move(profile), | |
| avifDecoder->image->depth); | |
| bool animation = avifDecoder->imageCount > 1; | |
| *result = kSuccess; | |
| return std::unique_ptr<SkCodec>(new SkAvifCodec(std::move(info), | |
| std::move(stream), | |
| std::move(data), | |
| std::move(avifDecoder), | |
| kDefault_SkEncodedOrigin, | |
| animation)); | |
| } | |
| SkAvifCodec::SkAvifCodec(SkEncodedInfo&& info, | |
| std::unique_ptr<SkStream> stream, | |
| sk_sp<SkData> data, | |
| AvifDecoder avifDecoder, | |
| SkEncodedOrigin origin, | |
| bool useAnimation) | |
| : INHERITED(std::move(info), skcms_PixelFormat_RGBA_8888, std::move(stream), origin) | |
| , fData(std::move(data)) | |
| , fAvifDecoder(std::move(avifDecoder)) | |
| , fUseAnimation(useAnimation) {} | |
| int SkAvifCodec::onGetFrameCount() { | |
| if (!fUseAnimation) { | |
| return 1; | |
| } | |
| if (fFrameHolder.size() == 0) { | |
| if (fAvifDecoder->imageCount <= 1) { | |
| fUseAnimation = false; | |
| return 1; | |
| } | |
| fFrameHolder.reserve(fAvifDecoder->imageCount); | |
| for (int i = 0; i < fAvifDecoder->imageCount; i++) { | |
| Frame* frame = fFrameHolder.appendNewFrame(fAvifDecoder->alphaPresent == AVIF_TRUE); | |
| frame->setXYWH(0, 0, fAvifDecoder->image->width, fAvifDecoder->image->height); | |
| frame->setDisposalMethod(SkCodecAnimation::DisposalMethod::kKeep); | |
| avifImageTiming timing; | |
| avifDecoderNthImageTiming(fAvifDecoder.get(), i, &timing); | |
| frame->setDuration(timing.duration * 1000); | |
| frame->setRequiredFrame(SkCodec::kNoFrame); | |
| frame->setHasAlpha(fAvifDecoder->alphaPresent == AVIF_TRUE); | |
| } | |
| } | |
| return fFrameHolder.size(); | |
| } | |
| const SkFrame* SkAvifCodec::FrameHolder::onGetFrame(int i) const { | |
| return static_cast<const SkFrame*>(this->frame(i)); | |
| } | |
| SkAvifCodec::Frame* SkAvifCodec::FrameHolder::appendNewFrame(bool hasAlpha) { | |
| const int i = this->size(); | |
| fFrames.emplace_back(i, | |
| hasAlpha ? SkEncodedInfo::kUnpremul_Alpha : SkEncodedInfo::kOpaque_Alpha); | |
| return &fFrames[i]; | |
| } | |
| const SkAvifCodec::Frame* SkAvifCodec::FrameHolder::frame(int i) const { | |
| SkASSERT(i >= 0 && i < this->size()); | |
| return &fFrames[i]; | |
| } | |
| bool SkAvifCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const { | |
| if (i >= fFrameHolder.size()) { | |
| return false; | |
| } | |
| const Frame* frame = fFrameHolder.frame(i); | |
| if (!frame) { | |
| return false; | |
| } | |
| if (frameInfo) { | |
| frame->fillIn(frameInfo, true); | |
| } | |
| return true; | |
| } | |
| int SkAvifCodec::onGetRepetitionCount() { return kRepetitionCountInfinite; } | |
| SkCodec::Result SkAvifCodec::onGetPixels(const SkImageInfo& dstInfo, | |
| void* dst, | |
| size_t dstRowBytes, | |
| const Options& options, | |
| int* rowsDecoded) { | |
| if (options.fSubset) { | |
| return kUnimplemented; | |
| } | |
| const SkColorType dstColorType = dstInfo.colorType(); | |
| if (dstColorType != kRGBA_8888_SkColorType && dstColorType != kRGBA_F16_SkColorType) { | |
| // TODO(vigneshv): Check if more color types need to be supported. | |
| // Currently android supports at least RGB565 and BGRA8888 which is not | |
| // supported here. | |
| return kUnimplemented; | |
| } | |
| avifResult result = avifDecoderNthImage(fAvifDecoder.get(), options.fFrameIndex); | |
| if (result != AVIF_RESULT_OK) { | |
| return kInvalidInput; | |
| } | |
| if (this->dimensions() != dstInfo.dimensions()) { | |
| if (!avifImageScale(fAvifDecoder->image, | |
| dstInfo.width(), | |
| dstInfo.height(), | |
| fAvifDecoder->imageSizeLimit, | |
| &fAvifDecoder->diag)) { | |
| return kInvalidInput; | |
| } | |
| } | |
| avifRGBImage rgbImage; | |
| avifRGBImageSetDefaults(&rgbImage, fAvifDecoder->image); | |
| if (dstColorType == kRGBA_8888_SkColorType) { | |
| rgbImage.depth = 8; | |
| } else if (dstColorType == kRGBA_F16_SkColorType) { | |
| rgbImage.depth = 16; | |
| rgbImage.isFloat = AVIF_TRUE; | |
| } | |
| rgbImage.pixels = static_cast<uint8_t*>(dst); | |
| rgbImage.rowBytes = dstRowBytes; | |
| rgbImage.chromaUpsampling = AVIF_CHROMA_UPSAMPLING_FASTEST; | |
| result = avifImageYUVToRGB(fAvifDecoder->image, &rgbImage); | |
| if (result != AVIF_RESULT_OK) { | |
| return kInvalidInput; | |
| } | |
| *rowsDecoded = fAvifDecoder->image->height; | |
| return kSuccess; | |
| } |