mirror of https://github.com/SixLabors/ImageSharp
2 changed files with 314 additions and 5 deletions
@ -0,0 +1,156 @@ |
|||||
|
// 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) |
||||
|
{ |
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
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) |
||||
|
{ |
||||
|
Span<byte> yRow = frameBuffer.BufferY!.DangerousGetSingleSpan(); |
||||
|
Span<byte> uRow = frameBuffer.BufferCb!.DangerousGetSingleSpan(); |
||||
|
Span<byte> vRow = frameBuffer.BufferCr!.DangerousGetSingleSpan(); |
||||
|
Rgb24[] result = new Rgb24[yRow.Length]; |
||||
|
double[] yuv = new double[3]; |
||||
|
for (int i = 0; i < yRow.Length; i++) |
||||
|
{ |
||||
|
yuv[0] = yRow[i]; |
||||
|
yuv[1] = uRow[i]; |
||||
|
yuv[2] = vRow[i]; |
||||
|
result[i] = YuvToRgb(yuv, false, true, false); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static Rgb24 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 new Rgb24((byte)Math.Round(r * 255), (byte)Math.Round(g * 255), (byte)Math.Round(b * 255)); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue