Commit ea65e680 authored by Dale Curtis's avatar Dale Curtis Committed by Commit Bot

Use new more verbose SkYUVColorSpace enums.

Adds bit_depth to the ToSkYUVColorSpace() function so that we can
use the new bit-depth appropriate BT.2020 Skia color spaces.

R=ccameron, pkasting

Bug: 1108626
Test: New unittests.
Change-Id: I29924719655ce0d80b8d71e5adda891ed9b30050
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2321307
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: default avatarccameron <ccameron@chromium.org>
Reviewed-by: default avatarPeter Kasting <pkasting@chromium.org>
Cr-Commit-Position: refs/heads/master@{#792865}
parent 0b6c1db8
......@@ -402,11 +402,17 @@ void ConvertVideoFrameToRGBPixelsTask(const VideoFrame* video_frame,
uint8_t* pixels = static_cast<uint8_t*>(rgb_pixels) +
row_bytes * chunk_start * rows_per_chunk;
// TODO(hubbe): This should really default to the rec709 colorspace.
// https://crbug.com/828599
// TODO(crbug.com/828599): This should default to BT.709 color space.
SkYUVColorSpace color_space = kRec601_SkYUVColorSpace;
video_frame->ColorSpace().ToSkYUVColorSpace(&color_space);
// Downgrade unsupported color spaces to supported ones. libyuv doesn't have
// support for these, so this is best effort.
if (color_space == kBT2020_8bit_Full_SkYUVColorSpace)
color_space = kBT2020_SkYUVColorSpace;
else if (color_space == kRec709_Full_SkYUVColorSpace)
color_space = kRec709_Limited_SkYUVColorSpace;
if (!video_frame->data(VideoFrame::kUPlane) &&
!video_frame->data(VideoFrame::kVPlane)) {
DCHECK_EQ(format, PIXEL_FORMAT_I420);
......
......@@ -90,36 +90,6 @@ gfx::ColorSpace GetColorSpace(const avifImage* image) {
return gfx::ColorSpace::CreateREC709();
}
// Returns the SkYUVColorSpace that matches |image|->matrixCoefficients and
// |image|->yuvRange.
base::Optional<SkYUVColorSpace> GetSkYUVColorSpace(const avifImage* image) {
const auto matrix =
image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED
? AVIF_MATRIX_COEFFICIENTS_BT709
: image->matrixCoefficients;
if (image->yuvRange == AVIF_RANGE_FULL) {
if (matrix == AVIF_MATRIX_COEFFICIENTS_BT470BG ||
matrix == AVIF_MATRIX_COEFFICIENTS_BT601) {
return kJPEG_SkYUVColorSpace;
}
// TODO(crbug.com/1108626): Expand this list with full range BT709/BT2020 at
// various bit depths.
return base::nullopt;
}
if (matrix == AVIF_MATRIX_COEFFICIENTS_BT470BG ||
matrix == AVIF_MATRIX_COEFFICIENTS_BT601) {
return kRec601_SkYUVColorSpace;
}
if (matrix == AVIF_MATRIX_COEFFICIENTS_BT709) {
return kRec709_SkYUVColorSpace;
}
if (matrix == AVIF_MATRIX_COEFFICIENTS_BT2020_NCL) {
return kBT2020_SkYUVColorSpace;
}
return base::nullopt;
}
// Returns whether media::PaintCanvasVideoRenderer (PCVR) can convert the YUV
// color space of |image| to RGB.
// media::PaintCanvasVideoRenderer::ConvertVideoFrameToRGBPixels() uses libyuv
......@@ -131,15 +101,15 @@ base::Optional<SkYUVColorSpace> GetSkYUVColorSpace(const avifImage* image) {
// 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) {
base::Optional<SkYUVColorSpace> yuv_color_space = GetSkYUVColorSpace(image);
if (!yuv_color_space)
SkYUVColorSpace yuv_color_space;
if (!GetColorSpace(image).ToSkYUVColorSpace(image->depth, &yuv_color_space))
return false;
if (!image->alphaPlane)
return true;
// libyuv supports the alpha channel only with the I420 pixel format, which is
// 8-bit YUV 4:2:0 with kRec601_SkYUVColorSpace.
return image->depth == 8 && image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420 &&
*yuv_color_space == kRec601_SkYUVColorSpace &&
yuv_color_space == kRec601_SkYUVColorSpace &&
image->alphaRange == AVIF_RANGE_FULL;
}
......@@ -405,6 +375,7 @@ void AVIFImageDecoder::DecodeToYUV() {
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];
uint8_t* dst = static_cast<uint8_t*>(image_planes_->Plane(plane));
libyuv::CopyPlane(src, src_row_bytes, dst, dst_row_bytes, width, height);
......@@ -474,6 +445,11 @@ bool AVIFImageDecoder::MatchesAVIFSignature(
return avifPeekCompatibleFileType(&input);
}
gfx::ColorTransform* AVIFImageDecoder::GetColorTransformForTesting() {
UpdateColorTransform(GetColorSpace(decoder_->image), decoder_->image->depth);
return color_transform_.get();
}
void AVIFImageDecoder::DecodeSize() {
// Because avifDecoder cannot read incrementally as data comes in, we cannot
// decode the size until all data is received. When all data is received,
......@@ -678,7 +654,8 @@ bool AVIFImageDecoder::MaybeCreateDemuxer() {
// color space conversion, so we don't decode to YUV.
allow_decode_to_yuv_ = yuv_format != AVIF_PIXEL_FORMAT_YUV400 &&
!decoder_->alphaPresent && decoded_frame_count_ == 1 &&
(yuv_color_space_ = GetSkYUVColorSpace(container)) &&
GetColorSpace(container).ToSkYUVColorSpace(
container->depth, &yuv_color_space_.emplace()) &&
!ColorTransform();
return SetSize(container->width, container->height);
}
......
......@@ -49,6 +49,8 @@ class PLATFORM_EXPORT AVIFImageDecoder final : public ImageDecoder {
// (ftyp) that supports the brand 'avif' or 'avis'.
static bool MatchesAVIFSignature(const FastSharedBufferReader& fast_reader);
gfx::ColorTransform* GetColorTransformForTesting();
private:
// ImageDecoder:
void DecodeSize() override;
......
......@@ -9,6 +9,8 @@
#include <ostream>
#include <vector>
#include "base/bit_cast.h"
#include "base/strings/stringprintf.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
......@@ -438,7 +440,6 @@ StaticColorCheckParam kTestParams[] = {
// such as:
// sRGB ColorPrimaries, BT.2020 TransferFunction and
// BT.609 MatrixCoefficients
// TODO(ryoh): Add YUV422/444 tests.
// TODO(ryoh): Add Mono + Alpha Images.
};
......@@ -468,11 +469,20 @@ void TestInvalidStaticImage(const char* avif_file, ErrorPhase error_phase) {
}
}
void ReadYUV(const char* image_file_path,
float HalfFloatToUnorm(uint16_t h) {
const uint32_t f = ((h & 0x8000) << 16) | (((h & 0x7c00) + 0x1c000) << 13) |
((h & 0x03ff) << 13);
return bit_cast<float>(f);
}
void ReadYUV(const char* file_name,
const IntSize& expected_y_size,
const IntSize& expected_uv_size,
SkColorType color_type) {
scoped_refptr<SharedBuffer> data = ReadFile(image_file_path);
SkColorType color_type,
int bit_depth,
gfx::Point3F* rgb_pixel = nullptr) {
scoped_refptr<SharedBuffer> data =
ReadFile("web_tests/images/resources/avif/", file_name);
ASSERT_TRUE(data);
auto decoder = CreateAVIFDecoder();
......@@ -512,6 +522,68 @@ void ReadYUV(const char* image_file_path,
decoder->DecodeToYUV();
EXPECT_FALSE(decoder->Failed());
if (!rgb_pixel)
return;
if (bit_depth > 8) {
rgb_pixel->set_x(reinterpret_cast<uint16_t*>(planes[0])[0]);
rgb_pixel->set_y(reinterpret_cast<uint16_t*>(planes[1])[0]);
rgb_pixel->set_z(reinterpret_cast<uint16_t*>(planes[2])[0]);
} else {
rgb_pixel->set_x(reinterpret_cast<uint8_t*>(planes[0])[0]);
rgb_pixel->set_y(reinterpret_cast<uint8_t*>(planes[1])[0]);
rgb_pixel->set_z(reinterpret_cast<uint8_t*>(planes[2])[0]);
}
if (color_type == kGray_8_SkColorType ||
color_type == kA16_unorm_SkColorType) {
const double max_channel = (1 << bit_depth) - 1;
rgb_pixel->set_x(rgb_pixel->x() / max_channel);
rgb_pixel->set_y(rgb_pixel->y() / max_channel);
rgb_pixel->set_z(rgb_pixel->z() / max_channel);
} else {
DCHECK_EQ(color_type, kA16_float_SkColorType);
rgb_pixel->set_x(HalfFloatToUnorm(rgb_pixel->x()));
rgb_pixel->set_y(HalfFloatToUnorm(rgb_pixel->y()));
rgb_pixel->set_z(HalfFloatToUnorm(rgb_pixel->z()));
}
// Convert our YUV pixel to RGB to avoid an excessive amounts of test
// expectations. We otherwise need bit_depth * yuv_sampling * color_type.
auto* transform = reinterpret_cast<AVIFImageDecoder*>(decoder.get())
->GetColorTransformForTesting();
transform->Transform(rgb_pixel, 1);
}
void TestYUVRed(const char* file_name,
const IntSize& expected_uv_size,
SkColorType color_type = kGray_8_SkColorType,
int bit_depth = 8) {
SCOPED_TRACE(base::StringPrintf("file_name=%s, color_type=%d", file_name,
int{color_type}));
constexpr IntSize kRedYSize(3, 3);
gfx::Point3F decoded_pixel;
ASSERT_NO_FATAL_FAILURE(ReadYUV(file_name, kRedYSize, expected_uv_size,
color_type, bit_depth, &decoded_pixel));
// Allow the RGB value to be off by one step. 1/max_value is the minimum
// amount of error possible if error exists for integer sources.
//
// For half float values we have additional error from precision limitations,
// which gets worse at the extents of [-0.5, 1] -- which is the case for our R
// channel since we're using a pure red source.
//
// https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Precision_limitations_on_decimal_values_in_[0,_1]
const double kMinError = 1.0 / ((1 << bit_depth) - 1);
const double kError = color_type == kA16_float_SkColorType
? kMinError + std::pow(2, -11)
: kMinError;
EXPECT_NEAR(decoded_pixel.x(), 1, kError); // R
EXPECT_NEAR(decoded_pixel.y(), 0, kMinError); // G
EXPECT_NEAR(decoded_pixel.z(), 0, kMinError); // B
}
} // namespace
......@@ -588,41 +660,45 @@ TEST(StaticAVIFTests, ValidImages) {
TEST(StaticAVIFTests, YUV) {
// 3x3, YUV 4:2:0
ReadYUV("/images/resources/avif/red-limited-range-420-8bpc.avif",
IntSize(3, 3), IntSize(2, 2), kGray_8_SkColorType);
constexpr IntSize kUVSize420(2, 2);
TestYUVRed("red-limited-range-420-8bpc.avif", kUVSize420);
TestYUVRed("red-full-range-420-8bpc.avif", kUVSize420);
// 3x3, YUV 4:2:2
ReadYUV("/images/resources/avif/red-limited-range-422-8bpc.avif",
IntSize(3, 3), IntSize(2, 3), kGray_8_SkColorType);
constexpr IntSize kUVSize422(2, 3);
TestYUVRed("red-limited-range-422-8bpc.avif", kUVSize422);
// 3x3, YUV 4:4:4
ReadYUV("/images/resources/avif/red-limited-range-444-8bpc.avif",
IntSize(3, 3), IntSize(3, 3), kGray_8_SkColorType);
constexpr IntSize kUVSize444(3, 3);
TestYUVRed("red-limited-range-444-8bpc.avif", kUVSize444);
// Full range BT709 color space is uncommon, but should be supported.
TestYUVRed("red-full-range-bt709-444-8bpc.avif", kUVSize444);
for (const auto ct : {kA16_unorm_SkColorType, kA16_float_SkColorType}) {
// 3x3, YUV 4:2:0, 10bpc
ReadYUV("/images/resources/avif/red-limited-range-420-10bpc.avif",
IntSize(3, 3), IntSize(2, 2), ct);
TestYUVRed("red-limited-range-420-10bpc.avif", kUVSize420, ct, 10);
// 3x3, YUV 4:2:2, 10bpc
ReadYUV("/images/resources/avif/red-limited-range-422-10bpc.avif",
IntSize(3, 3), IntSize(2, 3), ct);
TestYUVRed("red-limited-range-422-10bpc.avif", kUVSize422, ct, 10);
// 3x3, YUV 4:4:4, 10bpc
ReadYUV("/images/resources/avif/red-limited-range-444-10bpc.avif",
IntSize(3, 3), IntSize(3, 3), ct);
TestYUVRed("red-limited-range-444-10bpc.avif", kUVSize444, ct, 10);
// 3x3, YUV 4:2:0, 12bpc
ReadYUV("/images/resources/avif/red-limited-range-420-12bpc.avif",
IntSize(3, 3), IntSize(2, 2), ct);
TestYUVRed("red-limited-range-420-12bpc.avif", kUVSize420, ct, 12);
// 3x3, YUV 4:2:2, 12bpc
ReadYUV("/images/resources/avif/red-limited-range-422-12bpc.avif",
IntSize(3, 3), IntSize(2, 3), ct);
TestYUVRed("red-limited-range-422-12bpc.avif", kUVSize422, ct, 12);
// 3x3, YUV 4:4:4, 12bpc
ReadYUV("/images/resources/avif/red-limited-range-444-12bpc.avif",
IntSize(3, 3), IntSize(3, 3), ct);
TestYUVRed("red-limited-range-444-12bpc.avif", kUVSize444, ct, 12);
// Various common color spaces should be supported.
TestYUVRed("red-full-range-bt2020-pq-444-10bpc.avif", kUVSize444, ct, 10);
TestYUVRed("red-full-range-bt2020-pq-444-12bpc.avif", kUVSize444, ct, 12);
TestYUVRed("red-full-range-bt2020-hlg-444-10bpc.avif", kUVSize444, ct, 10);
TestYUVRed("red-full-range-bt2020-hlg-444-12bpc.avif", kUVSize444, ct, 12);
}
}
......
......@@ -40,4 +40,25 @@ avifenc -r f -d 10 -y 444 -s 0 red.png red-full-range-444-10bpc.avif
avifenc -r f -d 12 -y 444 -s 0 red.png red-full-range-444-12bpc.avif
```
### red-full-range-(bt709|bt2020)-(pq|hlg)-444-(10|12)bpc.avif
These are all generated from red.png with the appropriate avifenc command line:
BT.709:
```
avifenc -r f -d 8 -y 444 -s 0 --nclx 1/1/1 red.png red-full-range-bt709-pq-444-8bpc.avif
```
PQ:
```
avifenc -r f -d 10 -y 444 -s 0 --nclx 9/16/9 red.png red-full-range-bt2020-pq-444-10bpc.avif
avifenc -r f -d 10 -y 444 -s 0 --nclx 9/16/9 red.png red-full-range-bt2020-pq-444-12bpc.avif
```
HLG:
```
avifenc -r f -d 10 -y 444 -s 0 --nclx 9/18/9 red.png red-full-range-bt2020-pq-444-10bpc.avif
avifenc -r f -d 10 -y 444 -s 0 --nclx 9/18/9 red.png red-full-range-bt2020-pq-444-12bpc.avif
```
### TODO(crbug.com/960620): Figure out how the rest of files were generated.
......@@ -1139,26 +1139,36 @@ void ColorSpace::GetRangeAdjustMatrix(int bit_depth, SkMatrix44* matrix) const {
}
}
bool ColorSpace::ToSkYUVColorSpace(SkYUVColorSpace* out) const {
if (range_ == RangeID::FULL) {
if (matrix_ == MatrixID::BT470BG || matrix_ == MatrixID::SMPTE170M) {
*out = kJPEG_SkYUVColorSpace;
return true;
}
}
bool ColorSpace::ToSkYUVColorSpace(int bit_depth, SkYUVColorSpace* out) const {
switch (matrix_) {
case MatrixID::BT709:
*out = kRec709_SkYUVColorSpace;
*out = range_ == RangeID::FULL ? kRec709_Full_SkYUVColorSpace
: kRec709_Limited_SkYUVColorSpace;
return true;
case MatrixID::BT470BG:
case MatrixID::SMPTE170M:
*out = kRec601_SkYUVColorSpace;
*out = range_ == RangeID::FULL ? kJPEG_SkYUVColorSpace
: kRec601_Limited_SkYUVColorSpace;
return true;
case MatrixID::BT2020_NCL:
*out = kBT2020_SkYUVColorSpace;
return true;
if (bit_depth == 8) {
*out = range_ == RangeID::FULL ? kBT2020_8bit_Full_SkYUVColorSpace
: kBT2020_8bit_Limited_SkYUVColorSpace;
return true;
}
if (bit_depth == 10) {
*out = range_ == RangeID::FULL ? kBT2020_10bit_Full_SkYUVColorSpace
: kBT2020_10bit_Limited_SkYUVColorSpace;
return true;
}
if (bit_depth == 12) {
*out = range_ == RangeID::FULL ? kBT2020_12bit_Full_SkYUVColorSpace
: kBT2020_12bit_Limited_SkYUVColorSpace;
return true;
}
return false;
default:
break;
......
......@@ -304,10 +304,14 @@ class COLOR_SPACE_EXPORT ColorSpace {
// buffer.
const _GLcolorSpace* AsGLColorSpace() const;
// For YUV color spaces, return the closest SkYUVColorSpace.
// Returns true if a close match is found. Otherwise, leaves *out unchanged
// and returns false.
bool ToSkYUVColorSpace(SkYUVColorSpace* out) const;
// For YUV color spaces, return the closest SkYUVColorSpace. Returns true if a
// close match is found. Otherwise, leaves *out unchanged and returns false.
// If |matrix_id| is MatrixID::BT2020_NCL and |bit_depth| is provided, a bit
// depth appropriate SkYUVColorSpace will be provided.
bool ToSkYUVColorSpace(int bit_depth, SkYUVColorSpace* out) const;
bool ToSkYUVColorSpace(SkYUVColorSpace* out) const {
return ToSkYUVColorSpace(kDefaultBitDepth, out);
}
void GetPrimaryMatrix(skcms_Matrix3x3* to_XYZD50) const;
void GetPrimaryMatrix(SkMatrix44* to_XYZD50) const;
......
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