Browse Source

Reference YUV converter improvements

pull/2633/head
Ynse Hoornenborg 1 year ago
parent
commit
040b5dd757
  1. 53
      src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs
  2. 33
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceYuvConverter.cs
  3. 8
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1YuvConverterTests.cs

53
src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs

@ -9,6 +9,13 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal class Av1YuvConverter
{
// BT.709 SPecificatiuon constants.
private const int UMax = (int)(0.436 * 255);
private const int VMax = (int)(0.615 * 255);
private const int Wr = (int)(0.2126 * 255);
private const int Wb = (int)(0.0722 * 255);
private const int Wg = 255 - Wr - Wb;
public static void ConvertToRgb<TPixel>(Configuration configuration, Av1FrameBuffer<byte> frameBuffer, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -57,10 +64,10 @@ internal class Av1YuvConverter
{
// Weight multiplied by 256 to exploit full byte resolution, rounded to the nearest integer.
// Using BT.709 specification
const int rvWeight = (int)(1.28033 * 256);
const int guWeight = (int)(-0.21482 * 256);
const int gvWeight = (int)(-0.38059 * 256);
const int buWeight = (int)(2.12798 * 256);
const int rvWeight = (int)(1.28033 * 255);
const int guWeight = (int)(-0.21482 * 255);
const int gvWeight = (int)(-0.38059 * 255);
const int buWeight = (int)(2.12798 * 255);
Guard.NotNull(buffer.BufferY);
Guard.NotNull(buffer.BufferCb);
Guard.NotNull(buffer.BufferCr);
@ -98,9 +105,11 @@ internal class Av1YuvConverter
ref byte vRef = ref vSpan[0];
for (int x = 0; x < image.Width; x++)
{
pixel.R = (byte)Av1Math.Clip3(0, 255, ((yRef << 8) + (rvWeight * vRef)) >> 8);
pixel.G = (byte)Av1Math.Clip3(0, 255, ((yRef << 8) + (guWeight * uRef) + (gvWeight * vRef)) >> 8);
pixel.B = (byte)Av1Math.Clip3(0, 255, ((yRef << 8) + (buWeight * uRef)) >> 8);
int u = uRef; // ((uRef - 127) * 2 * UMax) / 255;
int v = vRef; // ((vRef - 127) * 2 * VMax) / 255;
pixel.R = (byte)Av1Math.Clip3(0, 255, yRef + (v * (255 - Wr) / VMax));
pixel.G = (byte)Av1Math.Clip3(0, 255, yRef - ((u * Wb * (255 - Wb)) / (UMax * Wg)) - ((v * Wr * (255 - Wr)) / (VMax * Wg)));
pixel.B = (byte)Av1Math.Clip3(0, 255, yRef + ((u * (255 - Wb)) / UMax));
pixel = ref Unsafe.Add(ref pixel, 1);
yRef = ref Unsafe.Add(ref yRef, 1);
uRef = ref Unsafe.Add(ref uRef, 1);
@ -113,15 +122,15 @@ internal class Av1YuvConverter
private static void ConvertRgbToYuv444(ImageFrame<Rgb24> image, Av1FrameBuffer<byte> buffer)
{
// Weight multiplied by 256 to exploit full byte resolution, rounded to the nearest integer.
const int yrWeight = (int)(0.2126 * 256);
const int ygWeight = (int)(0.7152 * 256);
const int ybWeight = (int)(0.0722 * 256);
const int urWeight = (int)(-0.09991 * 256);
const int ugWeight = (int)(-0.33609 * 256);
const int ubWeight = (int)(0.436 * 256);
const int vrWeight = (int)(0.615 * 256);
const int vgWeight = (int)(-0.55861 * 256);
const int vbWeight = (int)(-0.05639 * 256);
const int yrWeight = (int)(0.2126 * 255);
const int ygWeight = (int)(0.7152 * 255);
const int ybWeight = (int)(0.0722 * 255);
const int urWeight = (int)(-0.09991 * 255);
const int ugWeight = (int)(-0.33609 * 255);
const int ubWeight = (int)(0.436 * 255);
const int vrWeight = (int)(0.615 * 255);
const int vgWeight = (int)(-0.55861 * 255);
const int vbWeight = (int)(-0.05639 * 255);
Guard.NotNull(buffer.BufferY);
Guard.NotNull(buffer.BufferCb);
Guard.NotNull(buffer.BufferCr);
@ -149,9 +158,15 @@ internal class Av1YuvConverter
ref byte vRef = ref vSpan[0];
for (int x = 0; x < image.Width; x++)
{
yRef = (byte)Av1Math.Clip3(0, 255, ((yrWeight * pixel.R) + (ygWeight * pixel.G) + (ybWeight * pixel.B)) >> 8);
uRef = (byte)Av1Math.Clip3(0, 255, ((urWeight * pixel.R) + (ugWeight * pixel.G) + (ubWeight * pixel.B)) >> 8);
vRef = (byte)Av1Math.Clip3(0, 255, ((vrWeight * pixel.R) + (vgWeight * pixel.G) + (vbWeight * pixel.B)) >> 8);
yRef = (byte)Av1Math.Clip3(0, 255, ((Wr * pixel.R) + (Wg * pixel.G) + (Wb * pixel.B)) / 255);
// Not normalized, where range is [-UMax, UMax] or [-VMax, VMax]
// uRef = (byte)((UMax * (pixel.B - y)) / (255 - Wb));
// vRef = (byte)((VMax * (pixel.R - y)) / (255 - Wr));
// Normalized calculations
uRef = (byte)Av1Math.Clip3(0, 255, ((UMax * (pixel.B - yRef) / (255 - Wb)) + UMax) * 255 / (2 * UMax));
vRef = (byte)Av1Math.Clip3(0, 255, ((VMax * (pixel.R - yRef) / (255 - Wr)) + VMax) * 255 / (2 * VMax));
pixel = ref Unsafe.Add(ref pixel, 1);
yRef = ref Unsafe.Add(ref yRef, 1);
uRef = ref Unsafe.Add(ref uRef, 1);

33
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceYuvConverter.cs

@ -42,16 +42,17 @@ internal class Av1ReferenceYuvConverter
private const double Wg = 1 - Wr - Wb;
public static Span<Rgb24> RgbToYuv(Span<Rgb24> row)
public static Span<Rgb24> RgbToYuv(Span<Rgb24> row, bool normalized)
{
Rgb24[] result = new Rgb24[row.Length];
for (int i = 0; i < row.Length; i++)
{
double[] current = RgbToYuv(row[i], false, true, false);
byte y = (byte)current[0];
byte u = (byte)current[1];
byte v = (byte)current[2];
result[i] = new Rgb24(y, u, v);
double[] current = RgbToYuv(row[i], normalized, true, false);
double y = Math.Max(0, Math.Min(255, Math.Round(current[0])));
double u = Math.Max(0, Math.Min(255, Math.Round(current[1])));
double v = Math.Max(0, Math.Min(255, Math.Round(current[2])));
result[i] = new Rgb24((byte)y, (byte)u, (byte)v);
}
return result;
@ -96,7 +97,7 @@ internal class Av1ReferenceYuvConverter
return [y, u, v];
}
public static Span<Rgb24> YuvToRgb(Av1FrameBuffer<byte> frameBuffer)
public static Span<Rgb24> YuvToRgb(Av1FrameBuffer<byte> frameBuffer, bool normalized)
{
Span<byte> yRow = frameBuffer.BufferY!.DangerousGetSingleSpan();
Span<byte> uRow = frameBuffer.BufferCb!.DangerousGetSingleSpan();
@ -108,13 +109,25 @@ internal class Av1ReferenceYuvConverter
yuv[0] = yRow[i];
yuv[1] = uRow[i];
yuv[2] = vRow[i];
result[i] = YuvToRgb(yuv, false, true, false);
double[] rgb = YuvToRgb(yuv, normalized, true, false);
double r = rgb[0] * 255;
double g = rgb[1] * 255;
double b = rgb[2] * 255;
byte redByte = (byte)Math.Max(0, Math.Min(255, Math.Round(r)));
byte greenByte = (byte)Math.Max(0, Math.Min(255, Math.Round(g)));
byte blueByte = (byte)Math.Max(0, Math.Min(255, Math.Round(b)));
// Assert.True(Math.Abs(redByte - r) < 3, $"Red pixel out of byte range: {redByte} iso {r} from input Y={yuv[0]}, U={yuv[1]} and V={yuv[2]}.");
// Assert.True(Math.Abs(greenByte - g) < 3, $"Green pixel out of byte range: {greenByte} iso {g} from input Y={yuv[0]}, U={yuv[1]} and V={yuv[2]}.");
// Assert.True(Math.Abs(blueByte - b) < 3, $"Blue pixel out of byte range: {blueByte} iso {b} from input Y={yuv[0]}, U={yuv[1]} and V={yuv[2]}.");
result[i] = new Rgb24(redByte, greenByte, blueByte);
}
return result;
}
public static Rgb24 YuvToRgb(double[] yuv, bool normalized = false, bool is_8bit = false, bool is_10bit = false)
public static double[] YuvToRgb(double[] yuv, bool normalized = false, bool is_8bit = false, bool is_10bit = false)
{
double y = yuv[0];
double u = yuv[1];
@ -151,6 +164,6 @@ internal class Av1ReferenceYuvConverter
double g = y - (u * Wb * (1 - Wb) / (Umax * Wg)) - (v * Wr * (1 - Wr) / (Vmax * Wg));
double b = y + (u * (1 - Wb) / Umax);
return new Rgb24((byte)Math.Round(r * 255), (byte)Math.Round(g * 255), (byte)Math.Round(b * 255));
return [r, g, b];
}
}

8
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1YuvConverterTests.cs

@ -89,7 +89,7 @@ public class Av1YuvConverterTests
// Act
Av1YuvConverter.ConvertFromRgb(Configuration.Default, frame, frameBuffer);
Span<Rgb24> referenceOutput = Av1ReferenceYuvConverter.RgbToYuv(memory.Span);
Span<Rgb24> referenceOutput = Av1ReferenceYuvConverter.RgbToYuv(memory.Span, true);
// Assert
Span<Rgb24> actual = new Rgb24[frameBuffer.Width];
@ -105,7 +105,7 @@ public class Av1YuvConverterTests
actual[i] = pixel;
}
Compare(referenceOutput, actual, 1);
Compare(referenceOutput, actual, 3);
}
[Fact]
@ -127,12 +127,12 @@ public class Av1YuvConverterTests
// Act
Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, frame);
Span<Rgb24> referenceOutput = Av1ReferenceYuvConverter.YuvToRgb(frameBuffer);
Span<Rgb24> referenceOutput = Av1ReferenceYuvConverter.YuvToRgb(frameBuffer, false);
// Assert
frame.DangerousTryGetSinglePixelMemory(out Memory<Rgb24> memory);
Span<Rgb24> actual = memory.Span;
Compare(referenceOutput, actual, 1);
Compare(referenceOutput, actual, 3);
}
private static void Compare(Span<Rgb24> referenceOutput, Span<Rgb24> actual, int allowedDifference)

Loading…
Cancel
Save