Commit 6075df5b authored by Wan-Teh Chang's avatar Wan-Teh Chang Committed by Commit Bot

Implement our own avifIO reader.

The avifIO reader allows blink::AVIFImageDecoder to support incremental
decoding partially. blink::AVIFImageDecoder still cannot output parts
of a frame incrementally (such as row by row), but for a multi-frame
image, it can output full frames incrementally. In addition, it can
decode image metadata (such as image size and frame count)
incrementally before IsAllDataReceived() becomes true.

Implement the FrameIsReceivedAtIndex() method. Change DecodeFrameCount()
to return increasing frame count as frames are received.

This change requires including avif.h in avif_image_decoder.h. As a
result, forward declarations of libavif types are removed from
avif_image_decoder.h.

TestInvalidStaticImage() needs to call IsSizeAvailable() explicitly
because blink::ImageDecoder::SetData() with all_data_received=true no
longer causes size to be decoded.

Bug: 1136922
Bug: 1148577
Change-Id: I089f34711547f31030a1ea39d1f58f42106582e7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2530221
Commit-Queue: Wan-Teh Chang <wtc@google.com>
Reviewed-by: default avatarPeter Kasting <pkasting@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#827569}
parent 2ade52bc
...@@ -29,7 +29,6 @@ ...@@ -29,7 +29,6 @@
#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/libavif/src/include/avif/avif.h" #include "third_party/libavif/src/include/avif/avif.h"
#include "third_party/libyuv/include/libyuv.h" #include "third_party/libyuv/include/libyuv.h"
#include "third_party/skia/include/core/SkData.h"
#include "ui/gfx/color_space.h" #include "ui/gfx/color_space.h"
#include "ui/gfx/color_transform.h" #include "ui/gfx/color_transform.h"
#include "ui/gfx/half_float.h" #include "ui/gfx/half_float.h"
...@@ -41,6 +40,11 @@ ...@@ -41,6 +40,11 @@
namespace { namespace {
// The maximum AVIF file size we are willing to decode. This helps libavif
// detect invalid sizes and offsets in an AVIF file before the file size is
// known.
constexpr uint64_t kMaxAvifFileSize = 0x10000000; // 256 MB
// Builds a gfx::ColorSpace from the ITU-T H.273 (CICP) color description in the // Builds a gfx::ColorSpace from the ITU-T H.273 (CICP) color description in the
// image. This color space is used to create the gfx::ColorTransform for the // image. This color space is used to create the gfx::ColorTransform for the
// YUV-to-RGB conversion. If the image does not have an ICC profile, this color // YUV-to-RGB conversion. If the image does not have an ICC profile, this color
...@@ -271,11 +275,11 @@ bool AVIFImageDecoder::ImageIsHighBitDepth() { ...@@ -271,11 +275,11 @@ bool AVIFImageDecoder::ImageIsHighBitDepth() {
} }
void AVIFImageDecoder::OnSetData(SegmentReader* data) { void AVIFImageDecoder::OnSetData(SegmentReader* data) {
// avifDecoder requires all the data be available before reading and cannot have_parsed_current_data_ = false;
// read incrementally as data comes in. See const bool all_data_received = IsAllDataReceived();
// https://github.com/AOMediaCodec/libavif/issues/11. avif_io_data_.reader = data_.get();
if (IsAllDataReceived() && !MaybeCreateDemuxer()) avif_io_data_.all_data_received = all_data_received;
SetFailed(); avif_io_.sizeHint = all_data_received ? data_->size() : kMaxAvifFileSize;
} }
cc::YUVSubsampling AVIFImageDecoder::GetYUVSubsampling() const { cc::YUVSubsampling AVIFImageDecoder::GetYUVSubsampling() const {
...@@ -355,7 +359,7 @@ void AVIFImageDecoder::DecodeToYUV() { ...@@ -355,7 +359,7 @@ void AVIFImageDecoder::DecodeToYUV() {
// libavif cannot decode to an external buffer. So we need to copy from // libavif cannot decode to an external buffer. So we need to copy from
// libavif's internal buffer to |image_planes_|. // libavif's internal buffer to |image_planes_|.
// TODO(crbug.com/1099825): Enhance libavif to decode to an external buffer. // TODO(crbug.com/1099825): Enhance libavif to decode to an external buffer.
if (!DecodeImage(0)) { if (DecodeImage(0) != AVIF_RESULT_OK) {
SetFailed(); SetFailed();
return; return;
} }
...@@ -449,6 +453,18 @@ int AVIFImageDecoder::RepetitionCount() const { ...@@ -449,6 +453,18 @@ int AVIFImageDecoder::RepetitionCount() const {
return decoded_frame_count_ > 1 ? kAnimationLoopInfinite : kAnimationNone; return decoded_frame_count_ > 1 ? kAnimationLoopInfinite : kAnimationNone;
} }
bool AVIFImageDecoder::FrameIsReceivedAtIndex(size_t index) const {
if (!IsDecodedSizeAvailable())
return false;
if (decoded_frame_count_ == 1)
return ImageDecoder::FrameIsReceivedAtIndex(index);
// frame_buffer_cache_.size() is equal to the return value of
// DecodeFrameCount(). Since DecodeFrameCount() returns the number of frames
// whose encoded data have been received, we can return true if |index| is
// valid for frame_buffer_cache_. See crbug.com/1148577.
return index < frame_buffer_cache_.size();
}
base::TimeDelta AVIFImageDecoder::FrameDurationAtIndex(size_t index) const { base::TimeDelta AVIFImageDecoder::FrameDurationAtIndex(size_t index) const {
return index < frame_buffer_cache_.size() return index < frame_buffer_cache_.size()
? frame_buffer_cache_[index].Duration() ? frame_buffer_cache_[index].Duration()
...@@ -496,15 +512,31 @@ gfx::ColorTransform* AVIFImageDecoder::GetColorTransformForTesting() { ...@@ -496,15 +512,31 @@ gfx::ColorTransform* AVIFImageDecoder::GetColorTransformForTesting() {
return color_transform_.get(); return color_transform_.get();
} }
void AVIFImageDecoder::ParseMetadata() {
if (!UpdateDemuxer())
SetFailed();
}
void AVIFImageDecoder::DecodeSize() { void AVIFImageDecoder::DecodeSize() {
// Because avifDecoder cannot read incrementally as data comes in, we cannot ParseMetadata();
// decode the size until all data is received. When all data is received,
// OnSetData() decodes the size right away. So DecodeSize() doesn't need to do
// anything.
} }
size_t AVIFImageDecoder::DecodeFrameCount() { size_t AVIFImageDecoder::DecodeFrameCount() {
return Failed() ? frame_buffer_cache_.size() : decoded_frame_count_; if (!Failed())
ParseMetadata();
if (!IsDecodedSizeAvailable())
return frame_buffer_cache_.size();
if (decoded_frame_count_ == 1 || IsAllDataReceived())
return decoded_frame_count_;
// For a multi-frame image, Chrome expects DecodeFrameCount() to return the
// number of frames whose encoded data have been received.
size_t index;
for (index = frame_buffer_cache_.size(); index < decoded_frame_count_;
++index) {
if (avifDecoderNthImageReady(decoder_.get(), index) != AVIF_RESULT_OK)
break;
}
return index;
} }
void AVIFImageDecoder::InitializeNewFrame(size_t index) { void AVIFImageDecoder::InitializeNewFrame(size_t index) {
...@@ -522,16 +554,15 @@ void AVIFImageDecoder::InitializeNewFrame(size_t index) { ...@@ -522,16 +554,15 @@ void AVIFImageDecoder::InitializeNewFrame(size_t index) {
} }
void AVIFImageDecoder::Decode(size_t index) { void AVIFImageDecoder::Decode(size_t index) {
// TODO(dalecurtis): For fragmented AVIF image sequence files we probably want if (Failed())
// to allow partial decoding. Depends on if we see frequent use of multi-track
// images where there's lots to ignore.
if (Failed() || !IsAllDataReceived())
return; return;
UpdateAggressivePurging(index); UpdateAggressivePurging(index);
if (!DecodeImage(index)) { auto ret = DecodeImage(index);
SetFailed(); if (ret != AVIF_RESULT_OK) {
if (ret != AVIF_RESULT_WAITING_ON_IO)
SetFailed();
return; return;
} }
...@@ -587,46 +618,102 @@ bool AVIFImageDecoder::CanReusePreviousFrameBuffer(size_t index) const { ...@@ -587,46 +618,102 @@ bool AVIFImageDecoder::CanReusePreviousFrameBuffer(size_t index) const {
return true; return true;
} }
bool AVIFImageDecoder::MaybeCreateDemuxer() { // static
if (decoder_) avifResult AVIFImageDecoder::ReadFromSegmentReader(avifIO* io,
return true; uint32_t read_flags,
uint64_t offset,
size_t size,
avifROData* out) {
if (read_flags != 0) {
// Unsupported read_flags
return AVIF_RESULT_IO_ERROR;
}
decoder_ = std::unique_ptr<avifDecoder, void (*)(avifDecoder*)>( AvifIOData* io_data = static_cast<AvifIOData*>(io->data);
avifDecoderCreate(), avifDecoderDestroy);
if (!decoder_)
return false;
// TODO(dalecurtis): This may create a second copy of the media data in // Sanitize/clamp incoming request
// memory, which is not great. libavif should provide a read() based API: if (offset > io_data->reader->size()) {
// https://github.com/AOMediaCodec/libavif/issues/11 // The offset is past the end of the buffer or available data.
image_data_ = data_->GetAsSkData(); return io_data->all_data_received ? AVIF_RESULT_IO_ERROR
if (!image_data_) : AVIF_RESULT_WAITING_ON_IO;
return false; }
// TODO(wtc): Currently libavif always prioritizes the animation, but that's // It is more convenient to work with a variable of the size_t type. Since
// not correct. It should instead select animation or still image based on the // offset <= io_data->reader->size() <= SIZE_MAX, this cast is safe.
// preferred and major brands listed in the file. size_t position = static_cast<size_t>(offset);
if (animation_option_ != AnimationOption::kUnspecified && const size_t available_size = io_data->reader->size() - position;
avifDecoderSetSource( if (size > available_size) {
decoder_.get(), animation_option_ == AnimationOption::kPreferAnimation if (!io_data->all_data_received)
? AVIF_DECODER_SOURCE_TRACKS return AVIF_RESULT_WAITING_ON_IO;
: AVIF_DECODER_SOURCE_PRIMARY_ITEM) != size = available_size;
AVIF_RESULT_OK) {
return false;
} }
// Chrome doesn't use XMP and Exif metadata. Ignoring XMP and Exif will ensure out->size = size;
// avifDecoderParse() isn't waiting for some tiny Exif payload hiding at the const char* data;
// end of a file. size_t data_size = io_data->reader->GetSomeData(data, position);
decoder_->ignoreXMP = AVIF_TRUE; if (data_size >= size) {
decoder_->ignoreExif = AVIF_TRUE; out->data = reinterpret_cast<const uint8_t*>(data);
auto ret = avifDecoderSetIOMemory(decoder_.get(), image_data_->bytes(), return AVIF_RESULT_OK;
image_data_->size());
if (ret != AVIF_RESULT_OK) {
DVLOG(1) << "avifDecoderSetIOMemory failed: " << avifResultToString(ret);
return false;
} }
ret = avifDecoderParse(decoder_.get());
io_data->buffer.clear();
io_data->buffer.reserve(size);
while (size != 0) {
data_size = io_data->reader->GetSomeData(data, position);
size_t copy_size = std::min(data_size, size);
io_data->buffer.insert(io_data->buffer.end(), data, data + copy_size);
position += copy_size;
size -= copy_size;
}
out->data = io_data->buffer.data();
return AVIF_RESULT_OK;
}
bool AVIFImageDecoder::UpdateDemuxer() {
DCHECK(!Failed());
if (IsDecodedSizeAvailable())
return true;
if (have_parsed_current_data_)
return true;
have_parsed_current_data_ = true;
if (!decoder_) {
decoder_ = std::unique_ptr<avifDecoder, void (*)(avifDecoder*)>(
avifDecoderCreate(), avifDecoderDestroy);
if (!decoder_)
return false;
// TODO(wtc): Currently libavif always prioritizes the animation, but that's
// not correct. It should instead select animation or still image based on
// the preferred and major brands listed in the file.
if (animation_option_ != AnimationOption::kUnspecified &&
avifDecoderSetSource(
decoder_.get(),
animation_option_ == AnimationOption::kPreferAnimation
? AVIF_DECODER_SOURCE_TRACKS
: AVIF_DECODER_SOURCE_PRIMARY_ITEM) != AVIF_RESULT_OK) {
return false;
}
// Chrome doesn't use XMP and Exif metadata. Ignoring XMP and Exif will
// ensure avifDecoderParse() isn't waiting for some tiny Exif payload hiding
// at the end of a file.
decoder_->ignoreXMP = AVIF_TRUE;
decoder_->ignoreExif = AVIF_TRUE;
avif_io_.destroy = nullptr;
avif_io_.read = ReadFromSegmentReader;
avif_io_.write = nullptr;
avif_io_.persistent = AVIF_FALSE;
avif_io_.data = &avif_io_data_;
avifDecoderSetIO(decoder_.get(), &avif_io_);
}
auto ret = avifDecoderParse(decoder_.get());
if (ret == AVIF_RESULT_WAITING_ON_IO)
return true;
if (ret != AVIF_RESULT_OK) { if (ret != AVIF_RESULT_OK) {
DVLOG(1) << "avifDecoderParse failed: " << avifResultToString(ret); DVLOG(1) << "avifDecoderParse failed: " << avifResultToString(ret);
return false; return false;
...@@ -724,12 +811,12 @@ bool AVIFImageDecoder::MaybeCreateDemuxer() { ...@@ -724,12 +811,12 @@ bool AVIFImageDecoder::MaybeCreateDemuxer() {
return SetSize(container->width, container->height); return SetSize(container->width, container->height);
} }
bool AVIFImageDecoder::DecodeImage(size_t index) { avifResult AVIFImageDecoder::DecodeImage(size_t index) {
const auto ret = avifDecoderNthImage(decoder_.get(), index); const auto ret = avifDecoderNthImage(decoder_.get(), index);
// |index| should be less than what DecodeFrameCount() returns, so we should // |index| should be less than what DecodeFrameCount() returns, so we should
// not get the AVIF_RESULT_NO_IMAGES_REMAINING error. // not get the AVIF_RESULT_NO_IMAGES_REMAINING error.
DCHECK_NE(ret, AVIF_RESULT_NO_IMAGES_REMAINING); DCHECK_NE(ret, AVIF_RESULT_NO_IMAGES_REMAINING);
return ret == AVIF_RESULT_OK; return ret;
} }
void AVIFImageDecoder::UpdateColorTransform(const gfx::ColorSpace& frame_cs, void AVIFImageDecoder::UpdateColorTransform(const gfx::ColorSpace& frame_cs,
......
...@@ -8,14 +8,11 @@ ...@@ -8,14 +8,11 @@
#include <memory> #include <memory>
#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" #include "third_party/blink/renderer/platform/image-decoders/image_decoder.h"
#include "third_party/skia/include/core/SkBitmap.h" #include "third_party/libavif/src/include/avif/avif.h"
#include "third_party/skia/include/core/SkData.h" #include "third_party/skia/include/core/SkImageInfo.h"
#include "ui/gfx/color_space.h" #include "ui/gfx/color_space.h"
#include "ui/gfx/color_transform.h" #include "ui/gfx/color_transform.h"
struct avifDecoder;
struct avifImage;
namespace blink { namespace blink {
class FastSharedBufferReader; class FastSharedBufferReader;
...@@ -42,6 +39,7 @@ class PLATFORM_EXPORT AVIFImageDecoder final : public ImageDecoder { ...@@ -42,6 +39,7 @@ class PLATFORM_EXPORT AVIFImageDecoder final : public ImageDecoder {
uint8_t GetYUVBitDepth() const override; uint8_t GetYUVBitDepth() const override;
void DecodeToYUV() override; void DecodeToYUV() override;
int RepetitionCount() const override; int RepetitionCount() const override;
bool FrameIsReceivedAtIndex(size_t) const override;
base::TimeDelta FrameDurationAtIndex(size_t) const override; base::TimeDelta FrameDurationAtIndex(size_t) const override;
bool ImageHasBothStillAndAnimatedSubImages() const override; bool ImageHasBothStillAndAnimatedSubImages() const override;
...@@ -52,6 +50,14 @@ class PLATFORM_EXPORT AVIFImageDecoder final : public ImageDecoder { ...@@ -52,6 +50,14 @@ class PLATFORM_EXPORT AVIFImageDecoder final : public ImageDecoder {
gfx::ColorTransform* GetColorTransformForTesting(); gfx::ColorTransform* GetColorTransformForTesting();
private: private:
struct AvifIOData {
blink::SegmentReader* reader = nullptr;
std::vector<uint8_t> buffer;
bool all_data_received = false;
};
void ParseMetadata();
// ImageDecoder: // ImageDecoder:
void DecodeSize() override; void DecodeSize() override;
size_t DecodeFrameCount() override; size_t DecodeFrameCount() override;
...@@ -59,12 +65,19 @@ class PLATFORM_EXPORT AVIFImageDecoder final : public ImageDecoder { ...@@ -59,12 +65,19 @@ class PLATFORM_EXPORT AVIFImageDecoder final : public ImageDecoder {
void Decode(size_t) override; void Decode(size_t) override;
bool CanReusePreviousFrameBuffer(size_t) const override; bool CanReusePreviousFrameBuffer(size_t) const override;
// Creates |decoder_| and decodes the size and frame count. // Implements avifIOReadFunc, the |read| function in the avifIO struct.
bool MaybeCreateDemuxer(); static avifResult ReadFromSegmentReader(avifIO* io,
uint32_t read_flags,
uint64_t offset,
size_t size,
avifROData* out);
// Creates |decoder_| if not yet created and decodes the size and frame count.
bool UpdateDemuxer();
// Decodes the frame at index |index|. The decoded frame is available in // Decodes the frame at index |index|. The decoded frame is available in
// decoder_->image. Returns whether decoding completed successfully. // decoder_->image.
bool DecodeImage(size_t index); avifResult DecodeImage(size_t index);
// Updates or creates |color_transform_| for YUV-to-RGB conversion. // Updates or creates |color_transform_| for YUV-to-RGB conversion.
void UpdateColorTransform(const gfx::ColorSpace& frame_cs, int bit_depth); void UpdateColorTransform(const gfx::ColorSpace& frame_cs, int bit_depth);
...@@ -77,24 +90,24 @@ class PLATFORM_EXPORT AVIFImageDecoder final : public ImageDecoder { ...@@ -77,24 +90,24 @@ class PLATFORM_EXPORT AVIFImageDecoder final : public ImageDecoder {
// desired. // desired.
void ColorCorrectImage(ImageFrame* buffer); void ColorCorrectImage(ImageFrame* buffer);
bool have_parsed_current_data_ = false;
// The bit depth from the container. // The bit depth from the container.
uint8_t bit_depth_ = 0; uint8_t bit_depth_ = 0;
bool decode_to_half_float_ = false; bool decode_to_half_float_ = false;
// The YUV format from the container. Stores an avifPixelFormat enum value.
// Declared as uint8_t because we can't forward-declare an enum type in C++.
uint8_t avif_yuv_format_ = 0; // AVIF_PIXEL_FORMAT_NONE
uint8_t chroma_shift_x_ = 0; uint8_t chroma_shift_x_ = 0;
uint8_t chroma_shift_y_ = 0; uint8_t chroma_shift_y_ = 0;
// The YUV format from the container.
avifPixelFormat avif_yuv_format_ = AVIF_PIXEL_FORMAT_NONE;
size_t decoded_frame_count_ = 0; size_t decoded_frame_count_ = 0;
SkYUVColorSpace yuv_color_space_ = SkYUVColorSpace::kIdentity_SkYUVColorSpace; SkYUVColorSpace yuv_color_space_ = SkYUVColorSpace::kIdentity_SkYUVColorSpace;
std::unique_ptr<avifDecoder, void (*)(avifDecoder*)> decoder_{nullptr, std::unique_ptr<avifDecoder, void (*)(avifDecoder*)> decoder_{nullptr,
nullptr}; nullptr};
avifIO avif_io_ = {};
AvifIOData avif_io_data_;
std::unique_ptr<gfx::ColorTransform> color_transform_; std::unique_ptr<gfx::ColorTransform> color_transform_;
const AnimationOption animation_option_; const AnimationOption animation_option_;
sk_sp<SkData> image_data_;
}; };
} // namespace blink } // namespace blink
......
...@@ -480,10 +480,12 @@ void TestInvalidStaticImage(const char* avif_file, ErrorPhase error_phase) { ...@@ -480,10 +480,12 @@ void TestInvalidStaticImage(const char* avif_file, ErrorPhase error_phase) {
decoder->SetData(data.get(), true); decoder->SetData(data.get(), true);
if (error_phase == ErrorPhase::kParse) { if (error_phase == ErrorPhase::kParse) {
EXPECT_FALSE(decoder->IsSizeAvailable());
EXPECT_TRUE(decoder->Failed()); EXPECT_TRUE(decoder->Failed());
EXPECT_EQ(0u, decoder->FrameCount()); EXPECT_EQ(0u, decoder->FrameCount());
EXPECT_FALSE(decoder->DecodeFrameBufferAtIndex(0)); EXPECT_FALSE(decoder->DecodeFrameBufferAtIndex(0));
} else { } else {
EXPECT_TRUE(decoder->IsSizeAvailable());
EXPECT_FALSE(decoder->Failed()); EXPECT_FALSE(decoder->Failed());
EXPECT_GT(decoder->FrameCount(), 0u); EXPECT_GT(decoder->FrameCount(), 0u);
ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment