mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
5.9 KiB
170 lines
5.9 KiB
// Copyright (c) Six Labors.
|
|
// Licensed under the Six Labors Split License.
|
|
|
|
using System.Numerics;
|
|
using SixLabors.ImageSharp.Formats.Heif.Av1;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
|
|
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
|
|
|
|
/// <summary>
|
|
/// This simulates converting 24-bit RGB values to YUV, then back to 24-bit RGB.
|
|
/// Using BT.709 transfer functions: https://en.wikipedia.org/wiki/Rec._709
|
|
///
|
|
/// It demonstrates that converting to 30-bit YUV then back to 24-bit RGB is lossy.
|
|
/// Using 10 bits per YUV value appears to be lossless.
|
|
///
|
|
/// Converting RGB (24-bit) -> YUV(64-bit floats per channel, normalized[0 - 1]) -> RGB(24-bit)
|
|
/// Found 0 inaccurate conversions out of 16581375 RGB values
|
|
///
|
|
/// Converting RGB(24-bit) -> YUV(30-bit) -> RGB(24-bit)
|
|
/// Found 0 inaccurate conversions out of 16581375 RGB values
|
|
///
|
|
/// Converting RGB(24-bit) -> YUV(24-bit) -> RGB(24-bit)
|
|
/// Found 4058422 accurate conversions out of 16581375 RGB values
|
|
/// Found 12522953 inaccurate conversions out of 16581375 RGB values
|
|
/// Off by: {1: 8786792, 2: 3727753, 3: 8408}
|
|
/// </summary>
|
|
/// <remarks> Ported from Python to C# from: https://gist.github.com/linrock/5be4f365c9c9e61eee9e8984ba13cb25.</remarks>
|
|
internal class Av1ReferenceYuvConverter
|
|
{
|
|
// The range of UV values in BT.709 is [-Umax, Umax] and [-Vmax, Vmax]
|
|
private const double Umax = 0.436;
|
|
private const double Vmax = 0.615;
|
|
|
|
// Constants used in BT.709
|
|
private const double Wr = 0.2126;
|
|
private const double Wb = 0.0722;
|
|
|
|
// Constants used in BT.601
|
|
// private const double Wr = 0.299;
|
|
// private const double Wb = 0.114;
|
|
|
|
private const double Wg = 1 - Wr - Wb;
|
|
|
|
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], 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;
|
|
}
|
|
|
|
public static double[] RgbToYuv(Rgb24 rgb, bool normalize = false, bool is_8bit = false, bool is_10bit = false)
|
|
{
|
|
double r = rgb.R / 255.0;
|
|
double g = rgb.G / 255.0;
|
|
double b = rgb.B / 255.0;
|
|
double y = (Wr * r) + (Wg * g) + (Wb * b);
|
|
double u = Umax * (b - y) / (1 - Wb);
|
|
double v = Vmax * (r - y) / (1 - Wr);
|
|
|
|
// y[0, 1] u[-Umax, Umax] v[-Vmax, Vmax]
|
|
if (normalize)
|
|
{
|
|
u = (u + Umax) / (2 * Umax);
|
|
v = (v + Vmax) / (2 * Vmax);
|
|
|
|
// y[0, 1] u[0, 1] v[0, 1]
|
|
}
|
|
|
|
if (is_8bit)
|
|
{
|
|
y = Math.Round(y * 255);
|
|
u = Math.Round(u * 255);
|
|
v = Math.Round(v * 255);
|
|
|
|
// y[0, 255] u[0, 255] v[0, 255]
|
|
}
|
|
|
|
if (is_10bit)
|
|
{
|
|
y = Math.Round(y * 1023);
|
|
u = Math.Round(u * 1023);
|
|
v = Math.Round(v * 1023);
|
|
|
|
// y[0, 1023] u[0, 1023] v[0, 1023]
|
|
}
|
|
|
|
return [y, u, v];
|
|
}
|
|
|
|
public static Span<Rgb24> YuvToRgb(Av1FrameBuffer<byte> frameBuffer, bool normalized)
|
|
{
|
|
Point pixelPosition = new Point(0, 1);
|
|
Span<byte> yRow = frameBuffer.DeriveBlockPointer(Av1Plane.Y, pixelPosition, 0, 0, out int _);
|
|
Span<byte> uRow = frameBuffer.DeriveBlockPointer(Av1Plane.U, pixelPosition, 0, 0, out int _);
|
|
Span<byte> vRow = frameBuffer.DeriveBlockPointer(Av1Plane.V, pixelPosition, 0, 0, out int _);
|
|
Rgb24[] result = new Rgb24[frameBuffer.Width];
|
|
double[] yuv = new double[3];
|
|
for (int i = 0; i < frameBuffer.Width; i++)
|
|
{
|
|
yuv[0] = yRow[i];
|
|
yuv[1] = uRow[i];
|
|
yuv[2] = vRow[i];
|
|
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 double[] YuvToRgb(double[] yuv, bool normalized = false, bool is_8bit = false, bool is_10bit = false)
|
|
{
|
|
double y = yuv[0];
|
|
double u = yuv[1];
|
|
double v = yuv[2];
|
|
if (is_8bit)
|
|
{
|
|
// y[0, 255] u[0, 255] v[0, 255]
|
|
y /= 255.0;
|
|
u /= 255.0;
|
|
v /= 255.0;
|
|
}
|
|
|
|
if (is_10bit)
|
|
{
|
|
// y[0, 1023] u[0, 1023] v[0, 1023]
|
|
y /= 1023.0;
|
|
u /= 1023.0;
|
|
v /= 1023.0;
|
|
}
|
|
|
|
if (normalized)
|
|
{
|
|
// y [0, 1], u [0, 1], v[0, 1]
|
|
u = (u - 0.5) * 2 * Umax;
|
|
v = (v - 0.5) * 2 * Vmax;
|
|
|
|
// y [0, 1], u [-Umax, Umax], v[-Vmax, Vmax]
|
|
}
|
|
|
|
// r = y + 1.28033 * v
|
|
// g = y - 0.21482 * u - 0.38059 * v
|
|
// b = y + 2.12798 * u
|
|
double r = y + (v * (1 - Wr) / Vmax);
|
|
double g = y - (u * Wb * (1 - Wb) / (Umax * Wg)) - (v * Wr * (1 - Wr) / (Vmax * Wg));
|
|
double b = y + (u * (1 - Wb) / Umax);
|
|
|
|
return [r, g, b];
|
|
}
|
|
}
|
|
|