blob: baa4c877d233e39398947755917656ec1a5c6609 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h"
#include <stdint.h>
#include <cstring>
#include <memory>
#include "base/bits.h"
#include "base/containers/adapters.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/optional.h"
#include "base/timer/elapsed_timer.h"
#include "build/build_config.h"
#include "cc/base/math_util.h"
#include "media/base/video_color_space.h"
#include "media/base/video_frame.h"
#include "media/renderers/paint_canvas_video_renderer.h"
#include "media/video/half_float_maker.h"
#include "third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h"
#include "third_party/blink/renderer/platform/image-decoders/image_animation.h"
#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/libavif/src/include/avif/avif.h"
#include "third_party/libyuv/include/libyuv.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/color_transform.h"
#include "ui/gfx/half_float.h"
#include "ui/gfx/icc_profile.h"
#if defined(ARCH_CPU_BIG_ENDIAN)
#error Blink assumes a little-endian target.
#endif
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
// 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
// space is also used to create the embedded color profile.
gfx::ColorSpace GetColorSpace(const avifImage* image) {
// (As of ISO/IEC 23000-22:2019 Amendment 2) MIAF Section 7.3.6.4 says:
// If a coded image has no associated colour property, the default property
// is defined as having colour_type equal to 'nclx' with properties as
// follows:
// – colour_primaries equal to 1,
// - transfer_characteristics equal to 13,
// - matrix_coefficients equal to 5 or 6 (which are functionally identical),
// and
// - full_range_flag equal to 1.
// ...
// These values correspond to AVIF_COLOR_PRIMARIES_BT709,
// AVIF_TRANSFER_CHARACTERISTICS_SRGB, and AVIF_MATRIX_COEFFICIENTS_BT601,
// respectively.
//
// Note that this only specifies the default color property when the color
// property is absent. It does not really specify the default values for
// colour_primaries, transfer_characteristics, and matrix_coefficients when
// they are equal to 2 (unspecified). But we will interpret it as specifying
// the default values for these variables because we must choose some defaults
// and these are the most reasonable defaults to choose. We also advocate that
// all AVIF decoders choose these defaults:
// https://github.com/AOMediaCodec/av1-avif/issues/84
const auto primaries =
image->colorPrimaries == AVIF_COLOR_PRIMARIES_UNSPECIFIED
? AVIF_COLOR_PRIMARIES_BT709
: image->colorPrimaries;
const auto transfer = image->transferCharacteristics ==
AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED
? AVIF_TRANSFER_CHARACTERISTICS_SRGB
: image->transferCharacteristics;
const auto matrix =
image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED
? AVIF_MATRIX_COEFFICIENTS_BT601
: image->matrixCoefficients;
const auto range = image->yuvRange == AVIF_RANGE_FULL
? gfx::ColorSpace::RangeID::FULL
: gfx::ColorSpace::RangeID::LIMITED;
media::VideoColorSpace color_space(primaries, transfer, matrix, range);
if (color_space.IsSpecified())
return color_space.ToGfxColorSpace();
// media::VideoColorSpace and gfx::ColorSpace do not support CICP
// MatrixCoefficients 12, 13, 14.
DCHECK_GE(matrix, 12);
DCHECK_LE(matrix, 14);
if (image->yuvRange == AVIF_RANGE_FULL)
return gfx::ColorSpace::CreateJpeg();
return gfx::ColorSpace::CreateREC709();
}
// Returns whether media::PaintCanvasVideoRenderer (PCVR) can convert the YUV
// color space of |image| to RGB.
// media::PaintCanvasVideoRenderer::ConvertVideoFrameToRGBPixels() uses libyuv
// for the YUV-to-RGB conversion.
//
// NOTE: Ideally, this function should be a static method of
// media::PaintCanvasVideoRenderer. We did not do that because
// media::PaintCanvasVideoRenderer uses the JPEG matrix coefficients for all
// full-range YUV color spaces, but we want to use the JPEG matrix coefficients
// only for full-range BT.601 YUV.
bool IsColorSpaceSupportedByPCVR(const avifImage* image) {
SkYUVColorSpace yuv_color_space;
if (!GetColorSpace(image).ToSkYUVColorSpace(image->depth, &yuv_color_space))
return false;
const bool color_space_is_supported =
yuv_color_space == kJPEG_Full_SkYUVColorSpace ||
yuv_color_space == kRec601_Limited_SkYUVColorSpace ||
yuv_color_space == kRec709_Limited_SkYUVColorSpace ||
yuv_color_space == kBT2020_8bit_Limited_SkYUVColorSpace;
// libyuv supports the alpha channel only with the I420 pixel format, which is
// 8-bit YUV 4:2:0.
return color_space_is_supported &&
(!image->alphaPlane ||
(image->depth == 8 && image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 &&
image->alphaRange == AVIF_RANGE_FULL));
}
media::VideoPixelFormat AvifToVideoPixelFormat(avifPixelFormat fmt, int depth) {
if (depth != 8 && depth != 10 && depth != 12) {
// Unsupported bit depth.
NOTREACHED();
return media::PIXEL_FORMAT_UNKNOWN;
}
int index = (depth - 8) / 2;
static constexpr media::VideoPixelFormat kYUV420Formats[] = {
media::PIXEL_FORMAT_I420, media::PIXEL_FORMAT_YUV420P10,
media::PIXEL_FORMAT_YUV420P12};
static constexpr media::VideoPixelFormat kYUV422Formats[] = {
media::PIXEL_FORMAT_I422, media::PIXEL_FORMAT_YUV422P10,
media::PIXEL_FORMAT_YUV422P12};
static constexpr media::VideoPixelFormat kYUV444Formats[] = {
media::PIXEL_FORMAT_I444, media::PIXEL_FORMAT_YUV444P10,
media::PIXEL_FORMAT_YUV444P12};
switch (fmt) {
case AVIF_PIXEL_FORMAT_YUV420:
case AVIF_PIXEL_FORMAT_YUV400:
return kYUV420Formats[index];
case AVIF_PIXEL_FORMAT_YUV422:
return kYUV422Formats[index];
case AVIF_PIXEL_FORMAT_YUV444:
return kYUV444Formats[index];
case AVIF_PIXEL_FORMAT_NONE:
NOTREACHED();
return media::PIXEL_FORMAT_UNKNOWN;
}
}
// |y_size| is the width or height of the Y plane. Returns the width or height
// of the U and V planes. |chroma_shift| represents the subsampling of the
// chroma (U and V) planes in the x (for width) or y (for height) direction.
int UVSize(int y_size, int chroma_shift) {
DCHECK(chroma_shift == 0 || chroma_shift == 1);
return (y_size + chroma_shift) >> chroma_shift;
}
inline void WritePixel(float max_channel,
const gfx::Point3F& pixel,
float alpha,
bool premultiply_alpha,
uint32_t* rgba_dest) {
uint8_t r = base::ClampRound<uint8_t>(pixel.x() * 255.0f);
uint8_t g = base::ClampRound<uint8_t>(pixel.y() * 255.0f);
uint8_t b = base::ClampRound<uint8_t>(pixel.z() * 255.0f);
uint8_t a = base::ClampRound<uint8_t>(alpha * 255.0f);
if (premultiply_alpha)
blink::ImageFrame::SetRGBAPremultiply(rgba_dest, r, g, b, a);
else
*rgba_dest = SkPackARGB32NoCheck(a, r, g, b);
}
inline void WritePixel(float max_channel,
const gfx::Point3F& pixel,
float alpha,
bool premultiply_alpha,
uint64_t* rgba_dest) {
float rgba_pixels[4];
rgba_pixels[0] = pixel.x();
rgba_pixels[1] = pixel.y();
rgba_pixels[2] = pixel.z();
rgba_pixels[3] = alpha;
if (premultiply_alpha && alpha != 1.0f) {
rgba_pixels[0] *= alpha;
rgba_pixels[1] *= alpha;
rgba_pixels[2] *= alpha;
}
gfx::FloatToHalfFloat(rgba_pixels, reinterpret_cast<uint16_t*>(rgba_dest),
base::size(rgba_pixels));
}
enum class ColorType { kMono, kColor };
template <ColorType color_type, typename InputType, typename OutputType>
void YUVAToRGBA(const avifImage* image,
const gfx::ColorTransform* transform,
bool premultiply_alpha,
OutputType* rgba_dest) {
avifPixelFormatInfo format_info;
avifGetPixelFormatInfo(image->yuvFormat, &format_info);
gfx::Point3F pixel;
const int max_channel_i = (1 << image->depth) - 1;
const float max_channel = float{max_channel_i};
for (uint32_t j = 0; j < image->height; ++j) {
const int uv_j = j >> format_info.chromaShiftY;
const InputType* y_ptr = reinterpret_cast<InputType*>(
&image->yuvPlanes[AVIF_CHAN_Y][j * image->yuvRowBytes[AVIF_CHAN_Y]]);
const InputType* u_ptr = reinterpret_cast<InputType*>(
&image->yuvPlanes[AVIF_CHAN_U][uv_j * image->yuvRowBytes[AVIF_CHAN_U]]);
const InputType* v_ptr = reinterpret_cast<InputType*>(
&image->yuvPlanes[AVIF_CHAN_V][uv_j * image->yuvRowBytes[AVIF_CHAN_V]]);
const InputType* a_ptr = nullptr;
if (image->alphaPlane) {
a_ptr = reinterpret_cast<InputType*>(
&image->alphaPlane[j * image->alphaRowBytes]);
}
for (uint32_t i = 0; i < image->width; ++i) {
const int uv_i = i >> format_info.chromaShiftX;
pixel.set_x(y_ptr[i] / max_channel);
if (color_type == ColorType::kColor) {
pixel.set_y(u_ptr[uv_i] / max_channel);
pixel.set_z(v_ptr[uv_i] / max_channel);
} else {
pixel.set_y(0.5f);
pixel.set_z(0.5f);
}
transform->Transform(&pixel, 1);
int alpha = max_channel_i;
// TODO(wtc): Use templates or other ways to avoid checking whether the
// image supports alpha and whether alpha is limited range in the inner
// loop.
if (a_ptr) {
alpha = a_ptr[i];
if (image->alphaRange == AVIF_RANGE_LIMITED)
alpha = avifLimitedToFullY(image->depth, alpha);
}
WritePixel(max_channel, pixel, alpha / max_channel, premultiply_alpha,
rgba_dest);
rgba_dest++;
}
}
}
} // namespace
namespace blink {
AVIFImageDecoder::AVIFImageDecoder(AlphaOption alpha_option,
HighBitDepthDecodingOption hbd_option,
const ColorBehavior& color_behavior,
size_t max_decoded_bytes,
AnimationOption animation_option)
: ImageDecoder(alpha_option, hbd_option, color_behavior, max_decoded_bytes),
animation_option_(animation_option) {}
AVIFImageDecoder::~AVIFImageDecoder() = default;
bool AVIFImageDecoder::ImageIsHighBitDepth() {
return bit_depth_ > 8;
}
void AVIFImageDecoder::OnSetData(SegmentReader* data) {
have_parsed_current_data_ = false;
const bool all_data_received = IsAllDataReceived();
avif_io_data_.reader = data_.get();
avif_io_data_.all_data_received = all_data_received;
avif_io_.sizeHint = all_data_received ? data_->size() : kMaxAvifFileSize;
// ImageFrameGenerator::GetYUVAInfo() and ImageFrameGenerator::DecodeToYUV()
// assume that allow_decode_to_yuv_ and other image metadata are available
// after calling ImageDecoder::Create() with data_complete=true.
if (all_data_received)
ParseMetadata();
}
cc::YUVSubsampling AVIFImageDecoder::GetYUVSubsampling() const {
switch (decoder_->image->yuvFormat) {
case AVIF_PIXEL_FORMAT_YUV420:
return cc::YUVSubsampling::k420;
case AVIF_PIXEL_FORMAT_YUV422:
return cc::YUVSubsampling::k422;
case AVIF_PIXEL_FORMAT_YUV444:
return cc::YUVSubsampling::k444;
case AVIF_PIXEL_FORMAT_YUV400:
return cc::YUVSubsampling::kUnknown;
case AVIF_PIXEL_FORMAT_NONE:
NOTREACHED();
return cc::YUVSubsampling::kUnknown;
}
}
IntSize AVIFImageDecoder::DecodedYUVSize(cc::YUVIndex index) const {
DCHECK(IsDecodedSizeAvailable());
if (index == cc::YUVIndex::kU || index == cc::YUVIndex::kV) {
return IntSize(UVSize(Size().Width(), chroma_shift_x_),
UVSize(Size().Height(), chroma_shift_y_));
}
return Size();
}
size_t AVIFImageDecoder::DecodedYUVWidthBytes(cc::YUVIndex index) const {
DCHECK(IsDecodedSizeAvailable());
// Try to return the same width bytes as used by the dav1d library. This will
// allow DecodeToYUV() to copy each plane with a single memcpy() call.
//
// The comments for Dav1dPicAllocator in dav1d/picture.h require the pixel
// width be padded to a multiple of 128 pixels.
int aligned_width = base::bits::Align(Size().Width(), 128);
if (index == cc::YUVIndex::kU || index == cc::YUVIndex::kV) {
aligned_width >>= chroma_shift_x_;
}
// When the stride is a multiple of 1024, dav1d_default_picture_alloc()
// slightly pads the stride to avoid a reduction in cache hit rate in most
// L1/L2 cache implementations. Match that trick here. (Note that this padding
// is not documented in dav1d/picture.h.)
if ((aligned_width & 1023) == 0)
aligned_width += 64;
// High bit depth YUV is stored as a uint16_t, double the number of bytes.
if (bit_depth_ > 8) {
DCHECK_LE(bit_depth_, 16);
aligned_width *= 2;
}
return aligned_width;
}
SkYUVColorSpace AVIFImageDecoder::GetYUVColorSpace() const {
DCHECK(CanDecodeToYUV());
DCHECK_NE(yuv_color_space_, SkYUVColorSpace::kIdentity_SkYUVColorSpace);
return yuv_color_space_;
}
uint8_t AVIFImageDecoder::GetYUVBitDepth() const {
DCHECK(CanDecodeToYUV());
return bit_depth_;
}
void AVIFImageDecoder::DecodeToYUV() {
DCHECK(image_planes_);
DCHECK(CanDecodeToYUV());
DCHECK(IsAllDataReceived());
if (Failed())
return;
DCHECK(decoder_);
DCHECK_EQ(decoded_frame_count_, 1u); // Not animation.
// libavif cannot decode to an external buffer. So we need to copy from
// libavif's internal buffer to |image_planes_|.
// TODO(crbug.com/1099825): Enhance libavif to decode to an external buffer.
if (DecodeImage(0) != AVIF_RESULT_OK) {
SetFailed();
return;
}
const auto* image = decoder_->image;
// Frame size must be equal to container size.
if (Size() != IntSize(image->width, image->height)) {
DVLOG(1) << "Frame size " << IntSize(image->width, image->height)
<< " differs from container size " << Size();
SetFailed();
return;
}
// Frame bit depth must be equal to container bit depth.
if (image->depth != bit_depth_) {
DVLOG(1) << "Frame bit depth must be equal to container bit depth";
SetFailed();
return;
}
// Frame YUV format must be equal to container YUV format.
if (image->yuvFormat != avif_yuv_format_) {
DVLOG(1) << "Frame YUV format must be equal to container YUV format";
SetFailed();
return;
}
DCHECK(!image->alphaPlane);
static_assert(cc::YUVIndex::kY == static_cast<cc::YUVIndex>(AVIF_CHAN_Y), "");
static_assert(cc::YUVIndex::kU == static_cast<cc::YUVIndex>(AVIF_CHAN_U), "");
static_assert(cc::YUVIndex::kV == static_cast<cc::YUVIndex>(AVIF_CHAN_V), "");
// Disable subnormal floats which can occur when converting to half float.
std::unique_ptr<cc::ScopedSubnormalFloatDisabler> disable_subnormals;
const bool is_f16 = image_planes_->color_type() == kA16_float_SkColorType;
if (is_f16)
disable_subnormals = std::make_unique<cc::ScopedSubnormalFloatDisabler>();
const float kHighBitDepthMultiplier =
(is_f16 ? 1.0f : 65535.0f) / ((1 << bit_depth_) - 1);
// Initialize |width| and |height| to the width and height of the luma plane.
uint32_t width = image->width;
uint32_t height = image->height;
// |height| comes from the AV1 sequence header or frame header, which encodes
// max_frame_height_minus_1 and frame_height_minus_1, respectively, as n-bit
// unsigned integers for some n.
DCHECK_GT(height, 0u);
for (size_t plane_index = 0; plane_index < cc::kNumYUVPlanes; ++plane_index) {
const cc::YUVIndex plane = static_cast<cc::YUVIndex>(plane_index);
const size_t src_row_bytes =
base::strict_cast<size_t>(image->yuvRowBytes[plane_index]);
const size_t dst_row_bytes = image_planes_->RowBytes(plane);
if (bit_depth_ == 8) {
DCHECK_EQ(image_planes_->color_type(), kGray_8_SkColorType);
const uint8_t* src = image->yuvPlanes[plane_index];
uint8_t* dst = static_cast<uint8_t*>(image_planes_->Plane(plane));
libyuv::CopyPlane(src, src_row_bytes, dst, dst_row_bytes, width, height);
} else {
DCHECK_GT(bit_depth_, 8u);
DCHECK_LE(bit_depth_, 16u);
const uint16_t* src =
reinterpret_cast<uint16_t*>(image->yuvPlanes[plane_index]);
uint16_t* dst = static_cast<uint16_t*>(image_planes_->Plane(plane));
if (image_planes_->color_type() == kA16_unorm_SkColorType) {
const size_t src_stride = src_row_bytes / 2;
const size_t dst_stride = dst_row_bytes / 2;
for (uint32_t j = 0; j < height; ++j) {
for (uint32_t i = 0; i < width; ++i) {
dst[j * dst_stride + i] =
src[j * src_stride + i] * kHighBitDepthMultiplier + 0.5f;
}
}
} else if (image_planes_->color_type() == kA16_float_SkColorType) {
// Note: Unlike CopyPlane_16, HalfFloatPlane wants the stride in bytes.
libyuv::HalfFloatPlane(src, src_row_bytes, dst, dst_row_bytes,
kHighBitDepthMultiplier, width, height);
} else {
NOTREACHED() << "Unsupported color type: "
<< static_cast<int>(image_planes_->color_type());
}
}
if (plane == cc::YUVIndex::kY) {
// Having processed the luma plane, change |width| and |height| to the
// width and height of the chroma planes.
width = UVSize(width, chroma_shift_x_);
height = UVSize(height, chroma_shift_y_);
}
}
image_planes_->SetHasCompleteScan();
}
int AVIFImageDecoder::RepetitionCount() const {
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);
if (index >= frame_buffer_cache_.size())
return false;
if (IsAllDataReceived())
return true;
avifExtent dataExtent;
if (avifDecoderNthImageMaxExtent(decoder_.get(), index, &dataExtent) !=
AVIF_RESULT_OK) {
return false;
}
return dataExtent.size == 0 ||
dataExtent.offset + dataExtent.size <= data_->size();
}
base::TimeDelta AVIFImageDecoder::FrameDurationAtIndex(size_t index) const {
return index < frame_buffer_cache_.size()
? frame_buffer_cache_[index].Duration()
: base::TimeDelta();
}
bool AVIFImageDecoder::ImageHasBothStillAndAnimatedSubImages() const {
// Per MIAF, all animated AVIF files must have a still image, even if it's
// just a pointer to the first frame of the animation.
if (decoded_frame_count_ > 1)
return true;
// TODO(wtc): We should rely on libavif to tell us if the file has both an
// image and an animation track instead of just checking the major brand.
//
// An AVIF image begins with a file‐type box 'ftyp':
// unsigned int(32) size;
// unsigned int(32) type = boxtype; // boxtype is 'ftyp'
// unsigned int(32) major_brand;
// ...
FastSharedBufferReader fast_reader(data_);
char buf[4];
const char* major_brand = fast_reader.GetConsecutiveData(8, 4, buf);
// The brand 'avis' is an AVIF image sequence (animation) brand.
return memcmp(major_brand, "avis", 4) == 0;
}
// static
bool AVIFImageDecoder::MatchesAVIFSignature(
const FastSharedBufferReader& fast_reader) {
// avifPeekCompatibleFileType() clamps compatible brands at 32 when reading in
// the ftyp box in ISO BMFF for the 'avif' or 'avis' brand. So the maximum
// number of bytes read is 144 bytes (size 4 bytes, type 4 bytes, major brand
// 4 bytes, minor version 4 bytes, and 4 bytes * 32 compatible brands).
char buffer[144];
avifROData input;
input.size = std::min(sizeof(buffer), fast_reader.size());
input.data = reinterpret_cast<const uint8_t*>(
fast_reader.GetConsecutiveData(0, input.size, buffer));
return avifPeekCompatibleFileType(&input);
}
gfx::ColorTransform* AVIFImageDecoder::GetColorTransformForTesting() {
UpdateColorTransform(GetColorSpace(decoder_->image), decoder_->image->depth);
return color_transform_.get();
}
void AVIFImageDecoder::ParseMetadata() {
if (!UpdateDemuxer())
SetFailed();
}
void AVIFImageDecoder::DecodeSize() {
ParseMetadata();
}
size_t AVIFImageDecoder::DecodeFrameCount() {
if (!Failed())
ParseMetadata();
return IsDecodedSizeAvailable() ? decoded_frame_count_
: frame_buffer_cache_.size();
}
void AVIFImageDecoder::InitializeNewFrame(size_t index) {
auto& buffer = frame_buffer_cache_[index];
if (decode_to_half_float_)
buffer.SetPixelFormat(ImageFrame::PixelFormat::kRGBA_F16);
// For AVIFs, the frame always fills the entire image.
buffer.SetOriginalFrameRect(IntRect(IntPoint(), Size()));
avifImageTiming timing;
auto ret = avifDecoderNthImageTiming(decoder_.get(), index, &timing);
DCHECK_EQ(ret, AVIF_RESULT_OK);
buffer.SetDuration(base::TimeDelta::FromSecondsD(timing.duration));
}
void AVIFImageDecoder::Decode(size_t index) {
if (Failed())
return;
UpdateAggressivePurging(index);
auto ret = DecodeImage(index);
if (ret != AVIF_RESULT_OK) {
if (ret != AVIF_RESULT_WAITING_ON_IO)
SetFailed();
return;
}
const auto* image = decoder_->image;
// Frame size must be equal to container size.
if (Size() != IntSize(image->width, image->height)) {
DVLOG(1) << "Frame size " << IntSize(image->width, image->height)
<< " differs from container size " << Size();
SetFailed();
return;
}
// Frame bit depth must be equal to container bit depth.
if (image->depth != bit_depth_) {
DVLOG(1) << "Frame bit depth must be equal to container bit depth";
SetFailed();
return;
}
// Frame YUV format must be equal to container YUV format.
if (image->yuvFormat != avif_yuv_format_) {
DVLOG(1) << "Frame YUV format must be equal to container YUV format";
SetFailed();
return;
}
ImageFrame& buffer = frame_buffer_cache_[index];
DCHECK_EQ(buffer.GetStatus(), ImageFrame::kFrameEmpty);
if (!InitFrameBuffer(index)) {
DVLOG(1) << "Failed to create frame buffer...";
SetFailed();
return;
}
if (!RenderImage(image, &buffer)) {
SetFailed();
return;
}
ColorCorrectImage(&buffer);
buffer.SetPixelsChanged(true);
buffer.SetHasAlpha(!!image->alphaPlane);
buffer.SetStatus(ImageFrame::kFrameComplete);
PostDecodeProcessing(index);
}
bool AVIFImageDecoder::CanReusePreviousFrameBuffer(size_t index) const {
// (a) Technically we can reuse the bitmap of the previous frame because the
// AVIF decoder handles frame dependence internally and we never need to
// preserve previous frames to decode later ones, and (b) since this function
// will not currently be called, this is really more for the reader than any
// functional purpose.
return true;
}
// static
avifResult AVIFImageDecoder::ReadFromSegmentReader(avifIO* io,
uint32_t read_flags,
uint64_t offset,
size_t size,
avifROData* out) {
if (read_flags != 0) {
// Unsupported read_flags
return AVIF_RESULT_IO_ERROR;
}
AvifIOData* io_data = static_cast<AvifIOData*>(io->data);
// Sanitize/clamp incoming request
if (offset > io_data->reader->size()) {
// The offset is past the end of the buffer or available data.
return io_data->all_data_received ? AVIF_RESULT_IO_ERROR
: AVIF_RESULT_WAITING_ON_IO;
}
// It is more convenient to work with a variable of the size_t type. Since
// offset <= io_data->reader->size() <= SIZE_MAX, this cast is safe.
size_t position = static_cast<size_t>(offset);
const size_t available_size = io_data->reader->size() - position;
if (size > available_size) {
if (!io_data->all_data_received)
return AVIF_RESULT_WAITING_ON_IO;
size = available_size;
}
out->size = size;
const char* data;
size_t data_size = io_data->reader->GetSomeData(data, position);
if (data_size >= size) {
out->data = reinterpret_cast<const uint8_t*>(data);
return AVIF_RESULT_OK;
}
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) {
DVLOG(1) << "avifDecoderParse failed: " << avifResultToString(ret);
return false;
}
// Image metadata is available in decoder_->image after avifDecoderParse()
// even though decoder_->imageIndex is invalid (-1).
DCHECK_EQ(decoder_->imageIndex, -1);
// This variable is named |container| to emphasize the fact that the current
// contents of decoder_->image come from the container, not any frame.
const auto* container = decoder_->image;
// The container width and container height are read from either the tkhd
// (track header) box of a track or the ispe (image spatial extents) property
// of an image item, both of which are mandatory in the spec.
if (container->width == 0 || container->height == 0) {
DVLOG(1) << "Container width and height must be present";
return false;
}
// The container depth is read from either the av1C box of a track or the av1C
// property of an image item, both of which are mandatory in the spec.
if (container->depth == 0) {
DVLOG(1) << "Container depth must be present";
return false;
}
DCHECK_GT(decoder_->imageCount, 0);
decoded_frame_count_ = decoder_->imageCount;
bit_depth_ = container->depth;
decode_to_half_float_ =
ImageIsHighBitDepth() &&
high_bit_depth_decoding_option_ == kHighBitDepthToHalfFloat;
avif_yuv_format_ = container->yuvFormat;
avifPixelFormatInfo format_info;
avifGetPixelFormatInfo(container->yuvFormat, &format_info);
chroma_shift_x_ = format_info.chromaShiftX;
chroma_shift_y_ = format_info.chromaShiftY;
// SetEmbeddedColorProfile() must be called before IsSizeAvailable() becomes
// true. So call SetEmbeddedColorProfile() before calling SetSize(). The color
// profile is either an ICC profile or the CICP color description.
if (!IgnoresColorSpace()) {
// The CICP color description is always present because we can always get it
// from the AV1 sequence header for the frames. If an ICC profile is
// present, use it instead of the CICP color description.
if (container->icc.size) {
std::unique_ptr<ColorProfile> profile =
ColorProfile::Create(container->icc.data, container->icc.size);
if (!profile) {
DVLOG(1) << "Failed to parse image ICC profile";
return false;
}
uint32_t data_color_space = profile->GetProfile()->data_color_space;
const bool is_mono = container->yuvFormat == AVIF_PIXEL_FORMAT_YUV400;
if (is_mono) {
if (data_color_space != skcms_Signature_Gray &&
data_color_space != skcms_Signature_RGB)
profile = nullptr;
} else {
if (data_color_space != skcms_Signature_RGB)
profile = nullptr;
}
if (!profile) {
DVLOG(1)
<< "Image contains ICC profile that does not match its color space";
return false;
}
SetEmbeddedColorProfile(std::move(profile));
} else if (container->colorPrimaries != AVIF_COLOR_PRIMARIES_UNSPECIFIED ||
container->transferCharacteristics !=
AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED) {
gfx::ColorSpace frame_cs = GetColorSpace(container);
sk_sp<SkColorSpace> sk_color_space =
frame_cs.GetAsFullRangeRGB().ToSkColorSpace();
skcms_ICCProfile profile;
sk_color_space->toProfile(&profile);
SetEmbeddedColorProfile(std::make_unique<ColorProfile>(profile));
}
}
// |angle| * 90 specifies the angle of anti-clockwise rotation in degrees.
// Legal values: [0-3].
int angle = 0;
if (container->transformFlags & AVIF_TRANSFORM_IROT)
angle = container->irot.angle;
// |axis| specifies the axis for the mirroring operation.
// -1: No mirroring.
// 0: Mirror about a vertical axis ("left-to-right").
// 1: Mirror about a horizontal axis ("top-to-bottom").
int axis = -1;
if (container->transformFlags & AVIF_TRANSFORM_IMIR)
axis = container->imir.axis;
// MIAF Section 7.3.6.7 (Clean aperture, rotation and mirror) says:
// These properties, if used, shall be indicated to be applied in the
// following order: clean aperture first, then rotation, then mirror.
//
// In the kAxisAngleToOrientation array, the first dimension is axis (with an
// offset of 1). The second dimension is angle.
constexpr ImageOrientationEnum kAxisAngleToOrientation[3][4] = {
// No mirroring.
{ImageOrientationEnum::kOriginTopLeft,
ImageOrientationEnum::kOriginLeftBottom,
ImageOrientationEnum::kOriginBottomRight,
ImageOrientationEnum::kOriginRightTop},
// Mirror about a vertical axis ("left-to-right"). Change Left<->Right in
// the first row.
{ImageOrientationEnum::kOriginTopRight,
ImageOrientationEnum::kOriginRightBottom,
ImageOrientationEnum::kOriginBottomLeft,
ImageOrientationEnum::kOriginLeftTop},
// Mirror about a horizontal axis ("top-to-bottom"). Change Top<->Bottom
// in the first row.
{ImageOrientationEnum::kOriginBottomLeft,
ImageOrientationEnum::kOriginLeftTop,
ImageOrientationEnum::kOriginTopRight,
ImageOrientationEnum::kOriginRightBottom},
};
orientation_ = kAxisAngleToOrientation[axis + 1][angle];
// Determine whether the image can be decoded to YUV.
// * Alpha channel is not supported.
// * Multi-frame images (animations) are not supported. (The DecodeToYUV()
// method does not have an 'index' parameter.)
// * If ColorTransform() returns a non-null pointer, the decoder has to do a
// color space conversion, so we don't decode to YUV.
allow_decode_to_yuv_ = avif_yuv_format_ != AVIF_PIXEL_FORMAT_YUV400 &&
!decoder_->alphaPresent && decoded_frame_count_ == 1 &&
GetColorSpace(container).ToSkYUVColorSpace(
container->depth, &yuv_color_space_) &&
!ColorTransform();
return SetSize(container->width, container->height);
}
avifResult AVIFImageDecoder::DecodeImage(size_t index) {
const auto ret = avifDecoderNthImage(decoder_.get(), index);
// |index| should be less than what DecodeFrameCount() returns, so we should
// not get the AVIF_RESULT_NO_IMAGES_REMAINING error.
DCHECK_NE(ret, AVIF_RESULT_NO_IMAGES_REMAINING);
return ret;
}
void AVIFImageDecoder::UpdateColorTransform(const gfx::ColorSpace& frame_cs,
int bit_depth) {
if (color_transform_ && color_transform_->GetSrcColorSpace() == frame_cs)
return;
// For YUV-to-RGB color conversion we can pass an invalid dst color space to
// skip the code for full color conversion.
color_transform_ = gfx::ColorTransform::NewColorTransform(
frame_cs, bit_depth, gfx::ColorSpace(), bit_depth,
gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
}
bool AVIFImageDecoder::RenderImage(const avifImage* image, ImageFrame* buffer) {
const gfx::ColorSpace frame_cs = GetColorSpace(image);
const bool is_mono = image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400;
const bool premultiply_alpha = buffer->PremultiplyAlpha();
if (decode_to_half_float_) {
UpdateColorTransform(frame_cs, image->depth);
uint64_t* rgba_hhhh = buffer->GetAddrF16(0, 0);
// Color and format convert from YUV HBD -> RGBA half float.
if (is_mono) {
YUVAToRGBA<ColorType::kMono, uint16_t>(image, color_transform_.get(),
premultiply_alpha, rgba_hhhh);
} else {
// TODO(crbug.com/1099820): Add fast path for 10bit 4:2:0 using libyuv.
YUVAToRGBA<ColorType::kColor, uint16_t>(image, color_transform_.get(),
premultiply_alpha, rgba_hhhh);
}
return true;
}
uint32_t* rgba_8888 = buffer->GetAddr(0, 0);
// Call media::PaintCanvasVideoRenderer (PCVR) if the color space is
// supported.
if (IsColorSpaceSupportedByPCVR(image)) {
// Create temporary frame wrapping the YUVA planes.
scoped_refptr<media::VideoFrame> frame;
auto pixel_format = AvifToVideoPixelFormat(image->yuvFormat, image->depth);
if (pixel_format == media::PIXEL_FORMAT_UNKNOWN)
return false;
auto size = gfx::Size(image->width, image->height);
if (image->alphaPlane) {
DCHECK_EQ(pixel_format, media::PIXEL_FORMAT_I420);
pixel_format = media::PIXEL_FORMAT_I420A;
frame = media::VideoFrame::WrapExternalYuvaData(
pixel_format, size, gfx::Rect(size), size, image->yuvRowBytes[0],
image->yuvRowBytes[1], image->yuvRowBytes[2], image->alphaRowBytes,
image->yuvPlanes[0], image->yuvPlanes[1], image->yuvPlanes[2],
image->alphaPlane, base::TimeDelta());
} else {
frame = media::VideoFrame::WrapExternalYuvData(
pixel_format, size, gfx::Rect(size), size, image->yuvRowBytes[0],
image->yuvRowBytes[1], image->yuvRowBytes[2], image->yuvPlanes[0],
image->yuvPlanes[1], image->yuvPlanes[2], base::TimeDelta());
}
frame->set_color_space(frame_cs);
// Really only handles 709, 601, 2020, JPEG 8-bit conversions and uses
// libyuv under the hood, so is much faster than our manual path.
//
// Technically has support for 10-bit 4:2:0 and 4:2:2, but not to
// half-float and only has support for 4:4:4 and 12-bit by down-shifted
// copies.
//
// https://bugs.chromium.org/p/libyuv/issues/detail?id=845
media::PaintCanvasVideoRenderer::ConvertVideoFrameToRGBPixels(
frame.get(), rgba_8888, frame->visible_rect().width() * 4,
premultiply_alpha);
return true;
}
UpdateColorTransform(frame_cs, image->depth);
if (ImageIsHighBitDepth()) {
if (is_mono) {
YUVAToRGBA<ColorType::kMono, uint16_t>(image, color_transform_.get(),
premultiply_alpha, rgba_8888);
} else {
YUVAToRGBA<ColorType::kColor, uint16_t>(image, color_transform_.get(),
premultiply_alpha, rgba_8888);
}
} else {
if (is_mono) {
YUVAToRGBA<ColorType::kMono, uint8_t>(image, color_transform_.get(),
premultiply_alpha, rgba_8888);
} else {
YUVAToRGBA<ColorType::kColor, uint8_t>(image, color_transform_.get(),
premultiply_alpha, rgba_8888);
}
}
return true;
}
void AVIFImageDecoder::ColorCorrectImage(ImageFrame* buffer) {
// Postprocess the image data according to the profile.
const ColorProfileTransform* const transform = ColorTransform();
if (!transform)
return;
const auto alpha_format = (buffer->HasAlpha() && buffer->PremultiplyAlpha())
? skcms_AlphaFormat_PremulAsEncoded
: skcms_AlphaFormat_Unpremul;
if (decode_to_half_float_) {
const skcms_PixelFormat color_format = skcms_PixelFormat_RGBA_hhhh;
for (int y = 0; y < Size().Height(); ++y) {
ImageFrame::PixelDataF16* const row = buffer->GetAddrF16(0, y);
const bool success = skcms_Transform(
row, color_format, alpha_format, transform->SrcProfile(), row,
color_format, alpha_format, transform->DstProfile(), Size().Width());
DCHECK(success);
}
} else {
const skcms_PixelFormat color_format = XformColorFormat();
for (int y = 0; y < Size().Height(); ++y) {
ImageFrame::PixelData* const row = buffer->GetAddr(0, y);
const bool success = skcms_Transform(
row, color_format, alpha_format, transform->SrcProfile(), row,
color_format, alpha_format, transform->DstProfile(), Size().Width());
DCHECK(success);
}
}
}
} // namespace blink