Commit 24caca04 authored by Wan-Teh Chang's avatar Wan-Teh Chang Committed by Commit Bot

Consider bit depths for range adjust matrices

Add variants of the gfx::ColorSpace::GetRangeAdjustMatrix() and
gfx::ColorTransform::NewColorTransform() methods that take bit depths as
input parameters, so that the range adjustment for high bit depths can
be performed accurately.

Use the new gfx::ColorTransform::NewColorTransform() method in
AVIFImageDecoder.

Tested:
gfx_unittests --gtest_filter=*ColorSpace*
blink_platform_unittests --gtest_filter=*AVIF*

Change-Id: Ib36ba6c05d5d7da3a4d0715ad5880336c0b1693c
Bug: 1087192
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2213127
Commit-Queue: Wan-Teh Chang <wtc@google.com>
Reviewed-by: default avatarccameron <ccameron@chromium.org>
Reviewed-by: default avatarPeter Kasting <pkasting@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#773334}
parent bd7c3ffe
......@@ -183,24 +183,11 @@ void YUVAToRGBA(const avifImage* image,
for (uint32_t i = 0; i < image->width; ++i) {
const int uv_i = i >> format_info.chromaShiftX;
// TODO(wtc): Use templates or other ways to avoid doing this comparison
// and checking whether the image supports alpha in the inner loop.
if (image->yuvRange == AVIF_RANGE_LIMITED) {
pixel.set_x(avifLimitedToFullY(image->depth, y_ptr[i]) / max_channel);
if (color_type == ColorType::kColor) {
pixel.set_y(avifLimitedToFullUV(image->depth, u_ptr[uv_i]) /
max_channel);
pixel.set_z(avifLimitedToFullUV(image->depth, v_ptr[uv_i]) /
max_channel);
}
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_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);
}
}
if (color_type == ColorType::kMono) {
pixel.set_y(0.5f);
pixel.set_z(0.5f);
}
......@@ -208,6 +195,9 @@ void YUVAToRGBA(const avifImage* image,
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)
......@@ -469,33 +459,27 @@ bool AVIFImageDecoder::DecodeImage(size_t index) {
return true;
}
void AVIFImageDecoder::UpdateColorTransform(const gfx::ColorSpace& frame_cs) {
DCHECK_EQ(frame_cs.GetRangeID(), gfx::ColorSpace::RangeID::FULL);
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, frame_cs.GetAsFullRangeRGB(),
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);
// Although gfx::ColorTransform can perform range adjustment (from limited
// range to full range), it uses the 8-bit equations for all bit depths, which
// are not very accurate for high bit depths. So YUVAToRGBA() performs range
// adjustment (using libavif) before calling gfx::ColorTransform::Transform().
// Therefore, the source color space passed to UpdateColorTransform() should
// be full range.
const gfx::ColorSpace frame_cs_full_range = frame_cs.GetWithMatrixAndRange(
frame_cs.GetMatrixID(), gfx::ColorSpace::RangeID::FULL);
const bool is_mono = !image->yuvPlanes[AVIF_CHAN_U];
const bool premultiply_alpha = buffer->PremultiplyAlpha();
// TODO(dalecurtis): We should decode to YUV when possible. Currently the
// YUV path seems to only support still-image YUV8.
if (decode_to_half_float_) {
UpdateColorTransform(frame_cs_full_range);
UpdateColorTransform(frame_cs, image->depth);
uint64_t* rgba_hhhh = buffer->GetAddrF16(0, 0);
......@@ -564,7 +548,7 @@ bool AVIFImageDecoder::RenderImage(const avifImage* image, ImageFrame* buffer) {
return true;
}
UpdateColorTransform(frame_cs_full_range);
UpdateColorTransform(frame_cs, image->depth);
if (ImageIsHighBitDepth()) {
if (is_mono) {
YUVAToRGBA<ColorType::kMono, uint16_t>(image, color_transform_.get(),
......
......@@ -57,7 +57,7 @@ class PLATFORM_EXPORT AVIFImageDecoder final : public ImageDecoder {
bool DecodeImage(size_t index);
// Updates or creates |color_transform_| for YUV-to-RGB conversion.
void UpdateColorTransform(const gfx::ColorSpace& frame_cs);
void UpdateColorTransform(const gfx::ColorSpace& frame_cs, int bit_depth);
// Renders |image| in |buffer|. Returns whether |image| was rendered
// successfully.
......
......@@ -1080,7 +1080,8 @@ void ColorSpace::GetTransferMatrix(SkMatrix44* matrix) const {
matrix->setRowMajorf(data);
}
void ColorSpace::GetRangeAdjustMatrix(SkMatrix44* matrix) const {
void ColorSpace::GetRangeAdjustMatrix(int bit_depth, SkMatrix44* matrix) const {
DCHECK_GE(bit_depth, 8);
switch (range_) {
case RangeID::FULL:
case RangeID::INVALID:
......@@ -1092,33 +1093,21 @@ void ColorSpace::GetRangeAdjustMatrix(SkMatrix44* matrix) const {
break;
}
// Note: The values below assume an 8-bit range and aren't entirely correct
// for higher bit depths. They are close enough though (with a relative error
// of ~2.9% for 10-bit and ~3.7% for 12-bit) that it's not worth adding a
// |bit_depth| field to gfx::ColorSpace yet.
//
// The limited ranges are [64,940] and [256, 3760] for 10 and 12 bit content
// respectively. So the final values end up being:
//
// 16 / 255 = 0.06274509803921569
// 64 / 1023 = 0.06256109481915934
// 256 / 4095 = 0.06251526251526252
//
// 235 / 255 = 0.9215686274509803
// 940 / 1023 = 0.9188660801564027
// 3760 / 4095 = 0.9181929181929182
//
// Relative error (same for min/max):
// 10 bit: abs(16/235 - 64/1023)/(64/1023) = 0.0029411764705882222
// 12 bit: abs(16/235 - 256/4095)/(256/4095) = 0.003676470588235281
// See ITU-T H.273 (2016), Section 8.3. The following is derived from
// Equations 20-31.
const int shift = bit_depth - 8;
const float a_y = 219 << shift;
const float c = (1 << bit_depth) - 1;
const float scale_y = c / a_y;
switch (matrix_) {
case MatrixID::RGB:
case MatrixID::GBR:
case MatrixID::INVALID:
case MatrixID::YCOCG:
matrix->setScale(255.0f/219.0f, 255.0f/219.0f, 255.0f/219.0f);
matrix->postTranslate(-16.0f/219.0f, -16.0f/219.0f, -16.0f/219.0f);
case MatrixID::YCOCG: {
matrix->setScale(scale_y, scale_y, scale_y);
matrix->postTranslate(-16.0f / 219.0f, -16.0f / 219.0f, -16.0f / 219.0f);
break;
}
case MatrixID::BT709:
case MatrixID::FCC:
......@@ -1127,10 +1116,14 @@ void ColorSpace::GetRangeAdjustMatrix(SkMatrix44* matrix) const {
case MatrixID::SMPTE240M:
case MatrixID::BT2020_NCL:
case MatrixID::BT2020_CL:
case MatrixID::YDZDX:
matrix->setScale(255.0f/219.0f, 255.0f/224.0f, 255.0f/224.0f);
matrix->postTranslate(-16.0f/219.0f, -15.5f/224.0f, -15.5f/224.0f);
case MatrixID::YDZDX: {
const float a_uv = 224 << shift;
const float scale_uv = c / a_uv;
const float translate_uv = (a_uv - c) / (2.0f * a_uv);
matrix->setScale(scale_y, scale_uv, scale_uv);
matrix->postTranslate(-16.0f / 219.0f, translate_uv, translate_uv);
break;
}
}
}
......
......@@ -305,7 +305,35 @@ class COLOR_SPACE_EXPORT ColorSpace {
// For most formats, this is the RGB to YUV matrix.
void GetTransferMatrix(SkMatrix44* matrix) const;
void GetRangeAdjustMatrix(SkMatrix44* matrix) const;
// Returns the range adjust matrix that converts from |range_| to full range
// for |bit_depth|.
void GetRangeAdjustMatrix(int bit_depth, SkMatrix44* matrix) const;
// Returns the range adjust matrix that converts from |range_| to full range
// for bit depth 8.
//
// WARNING: The returned matrix assumes an 8-bit range and isn't entirely
// correct for higher bit depths, with a relative error of ~2.9% for 10-bit
// and ~3.7% for 12-bit. Use the above GetRangeAdjustMatrix() method instead.
//
// The limited ranges are [64,940] and [256, 3760] for 10 and 12 bit content
// respectively. So the final values end up being:
//
// 16 / 255 = 0.06274509803921569
// 64 / 1023 = 0.06256109481915934
// 256 / 4095 = 0.06251526251526252
//
// 235 / 255 = 0.9215686274509803
// 940 / 1023 = 0.9188660801564027
// 3760 / 4095 = 0.9181929181929182
//
// Relative error (same for min/max):
// 10 bit: abs(16/235 - 64/1023)/(64/1023) = 0.0029411764705882222
// 12 bit: abs(16/235 - 256/4095)/(256/4095) = 0.003676470588235281
void GetRangeAdjustMatrix(SkMatrix44* matrix) const {
GetRangeAdjustMatrix(kDefaultBitDepth, matrix);
}
// Returns the current primary ID.
// Note: if SetCustomPrimaries() has been used, the primary ID returned
......@@ -327,6 +355,9 @@ class COLOR_SPACE_EXPORT ColorSpace {
bool HasExtendedSkTransferFn() const;
private:
// The default bit depth assumed by GetRangeAdjustMatrix().
static constexpr int kDefaultBitDepth = 8;
static void GetPrimaryMatrix(PrimaryID, skcms_Matrix3x3* to_XYZD50);
static bool GetTransferFunction(TransferID, skcms_TransferFunction* fn);
static size_t TransferParamCount(TransferID);
......
......@@ -81,6 +81,104 @@ TEST(ColorSpace, RGBToYUV) {
}
}
TEST(ColorSpace, RangeAdjust) {
const size_t kNumTestYUVs = 2;
SkVector4 test_yuvs[kNumTestYUVs] = {
SkVector4(1.f, 1.f, 1.f, 1.f),
SkVector4(0.f, 0.f, 0.f, 1.f),
};
const size_t kNumBitDepths = 3;
int bit_depths[kNumBitDepths] = {8, 10, 12};
const size_t kNumColorSpaces = 3;
ColorSpace color_spaces[kNumColorSpaces] = {
ColorSpace::CreateREC601(),
ColorSpace::CreateJpeg(),
ColorSpace(ColorSpace::PrimaryID::INVALID,
ColorSpace::TransferID::INVALID, ColorSpace::MatrixID::YCOCG,
ColorSpace::RangeID::LIMITED),
};
SkVector4 expected_yuvs[kNumColorSpaces][kNumBitDepths][kNumTestYUVs] = {
// REC601
{
// 8bpc
{
SkVector4(235.f / 255.f, 239.5f / 255.f, 239.5f / 255.f, 1.0000f),
SkVector4(16.f / 255.f, 15.5f / 255.f, 15.5f / 255.f, 1.0000f),
},
// 10bpc
{
SkVector4(940.f / 1023.f, 959.5f / 1023.f, 959.5f / 1023.f,
1.0000f),
SkVector4(64.f / 1023.f, 63.5f / 1023.f, 63.5f / 1023.f, 1.0000f),
},
// 12bpc
{
SkVector4(3760.f / 4095.f, 3839.5f / 4095.f, 3839.5f / 4095.f,
1.0000f),
SkVector4(256.f / 4095.f, 255.5f / 4095.f, 255.5f / 4095.f,
1.0000f),
},
},
// Jpeg
{
// 8bpc
{
SkVector4(1.0000f, 1.0000f, 1.0000f, 1.0000f),
SkVector4(0.0000f, 0.0000f, 0.0000f, 1.0000f),
},
// 10bpc
{
SkVector4(1.0000f, 1.0000f, 1.0000f, 1.0000f),
SkVector4(0.0000f, 0.0000f, 0.0000f, 1.0000f),
},
// 12bpc
{
SkVector4(1.0000f, 1.0000f, 1.0000f, 1.0000f),
SkVector4(0.0000f, 0.0000f, 0.0000f, 1.0000f),
},
},
// YCoCg
{
// 8bpc
{
SkVector4(235.f / 255.f, 235.f / 255.f, 235.f / 255.f, 1.0000f),
SkVector4(16.f / 255.f, 16.f / 255.f, 16.f / 255.f, 1.0000f),
},
// 10bpc
{
SkVector4(940.f / 1023.f, 940.f / 1023.f, 940.f / 1023.f,
1.0000f),
SkVector4(64.f / 1023.f, 64.f / 1023.f, 64.f / 1023.f, 1.0000f),
},
// 12bpc
{
SkVector4(3760.f / 4095.f, 3760.f / 4095.f, 3760.f / 4095.f,
1.0000f),
SkVector4(256.f / 4095.f, 256.f / 4095.f, 256.f / 4095.f,
1.0000f),
},
},
};
for (size_t i = 0; i < kNumColorSpaces; ++i) {
for (size_t j = 0; j < kNumBitDepths; ++j) {
SkMatrix44 range_adjust;
color_spaces[i].GetRangeAdjustMatrix(bit_depths[j], &range_adjust);
SkMatrix44 range_adjust_inv;
range_adjust.invert(&range_adjust_inv);
for (size_t k = 0; k < kNumTestYUVs; ++k) {
SkVector4 yuv = range_adjust_inv * test_yuvs[k];
EXPECT_LT(Diff(yuv, expected_yuvs[i][j][k]), kEpsilon);
}
}
}
}
TEST(ColorSpace, RasterAndBlend) {
ColorSpace display_color_space;
......
......@@ -170,9 +170,10 @@ Transform GetTransferMatrix(const gfx::ColorSpace& color_space) {
return Transform(transfer_matrix);
}
Transform GetRangeAdjustMatrix(const gfx::ColorSpace& color_space) {
Transform GetRangeAdjustMatrix(const gfx::ColorSpace& color_space,
int bit_depth) {
SkMatrix44 range_adjust_matrix;
color_space.GetRangeAdjustMatrix(&range_adjust_matrix);
color_space.GetRangeAdjustMatrix(bit_depth, &range_adjust_matrix);
return Transform(range_adjust_matrix);
}
......@@ -224,7 +225,9 @@ class ColorTransformStep {
class ColorTransformInternal : public ColorTransform {
public:
ColorTransformInternal(const ColorSpace& src,
int src_bit_depth,
const ColorSpace& dst,
int dst_bit_depth,
Intent intent);
~ColorTransformInternal() override;
......@@ -243,7 +246,9 @@ class ColorTransformInternal : public ColorTransform {
private:
void AppendColorSpaceToColorSpaceTransform(const ColorSpace& src,
const ColorSpace& dst);
int src_bit_depth,
const ColorSpace& dst,
int dst_bit_depth);
void Simplify();
std::list<std::unique_ptr<ColorTransformStep>> steps_;
......@@ -891,9 +896,11 @@ class ColorTransformFromBT2020CL : public ColorTransformStep {
void ColorTransformInternal::AppendColorSpaceToColorSpaceTransform(
const ColorSpace& src,
const ColorSpace& dst) {
steps_.push_back(
std::make_unique<ColorTransformMatrix>(GetRangeAdjustMatrix(src)));
int src_bit_depth,
const ColorSpace& dst,
int dst_bit_depth) {
steps_.push_back(std::make_unique<ColorTransformMatrix>(
GetRangeAdjustMatrix(src, src_bit_depth)));
if (src.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) {
// BT2020 CL is a special case.
......@@ -972,18 +979,21 @@ void ColorTransformInternal::AppendColorSpaceToColorSpaceTransform(
}
steps_.push_back(std::make_unique<ColorTransformMatrix>(
Invert(GetRangeAdjustMatrix(dst))));
Invert(GetRangeAdjustMatrix(dst, dst_bit_depth))));
}
ColorTransformInternal::ColorTransformInternal(const ColorSpace& src,
int src_bit_depth,
const ColorSpace& dst,
int dst_bit_depth,
Intent intent)
: src_(src), dst_(dst) {
// If no source color space is specified, do no transformation.
// TODO(ccameron): We may want dst assume sRGB at some point in the future.
if (!src_.IsValid())
return;
AppendColorSpaceToColorSpaceTransform(src_, dst_);
AppendColorSpaceToColorSpaceTransform(src_, src_bit_depth, dst_,
dst_bit_depth);
if (intent != Intent::TEST_NO_OPT)
Simplify();
}
......@@ -1046,10 +1056,12 @@ void ColorTransformInternal::Simplify() {
// static
std::unique_ptr<ColorTransform> ColorTransform::NewColorTransform(
const ColorSpace& src,
int src_bit_depth,
const ColorSpace& dst,
int dst_bit_depth,
Intent intent) {
return std::unique_ptr<ColorTransform>(
new ColorTransformInternal(src, dst, intent));
return std::make_unique<ColorTransformInternal>(src, src_bit_depth, dst,
dst_bit_depth, intent);
}
ColorTransform::ColorTransform() {}
......
......@@ -52,10 +52,25 @@ class GFX_EXPORT ColorTransform {
// for YUV to RGB color conversion.
static std::unique_ptr<ColorTransform> NewColorTransform(
const ColorSpace& src,
int src_bit_depth,
const ColorSpace& dst,
int dst_bit_depth,
Intent intent);
// Assumes bit depth 8. For higher bit depths, use above NewColorTransform()
// method instead.
static std::unique_ptr<ColorTransform> NewColorTransform(
const ColorSpace& src,
const ColorSpace& dst,
Intent intent) {
return NewColorTransform(src, kDefaultBitDepth, dst, kDefaultBitDepth,
intent);
}
private:
// The default bit depth assumed by NewColorTransform().
static constexpr int kDefaultBitDepth = 8;
DISALLOW_COPY_AND_ASSIGN(ColorTransform);
};
......
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