mirror of https://github.com/SixLabors/ImageSharp
3 changed files with 241 additions and 2 deletions
@ -0,0 +1,163 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
||||
|
|
||||
|
internal class Av1YuvConverter |
||||
|
{ |
||||
|
public static void ConvertToRgb<TPixel>(Configuration configuration, Av1FrameBuffer<byte> frameBuffer, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
using Image<Rgb24> rgbImage = new(image.Width, image.Height); |
||||
|
ImageFrame<Rgb24> rgbFrame = rgbImage.Frames.RootFrame; |
||||
|
|
||||
|
// TODO: Support YUV420 and YUV420 also.
|
||||
|
if (frameBuffer.ColorFormat != Av1ColorFormat.Yuv444) |
||||
|
{ |
||||
|
throw new NotSupportedException("Only able to convert YUV444 to RGB."); |
||||
|
} |
||||
|
|
||||
|
ConvertYuvToRgb(frameBuffer, rgbFrame, false); |
||||
|
image.ProcessPixelRows(rgbFrame, (resultAcc, rgbAcc) => |
||||
|
{ |
||||
|
for (int y = 0; y < rgbImage.Height; y++) |
||||
|
{ |
||||
|
Span<Rgb24> rgbRow = rgbAcc.GetRowSpan(y); |
||||
|
Span<TPixel> resultRow = resultAcc.GetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.FromRgb24(configuration, rgbRow, resultRow); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void ConvertFromRgb<TPixel>(Configuration configuration, ImageFrame<TPixel> image, Av1FrameBuffer<byte> frameBuffer) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
using Image<Rgb24> rgbImage = new(image.Width, image.Height); |
||||
|
ImageFrame<Rgb24> rgbFrame = rgbImage.Frames.RootFrame; |
||||
|
|
||||
|
image.ProcessPixelRows(rgbFrame, (sourceAcc, rgbAcc) => |
||||
|
{ |
||||
|
for (int y = 0; y < rgbImage.Height; y++) |
||||
|
{ |
||||
|
Span<Rgb24> rgbRow = rgbAcc.GetRowSpan(y); |
||||
|
Span<TPixel> sourceRow = sourceAcc.GetRowSpan(y); |
||||
|
PixelOperations<TPixel>.Instance.ToRgb24(configuration, sourceRow, rgbRow); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// TODO: Support YUV422 and YUV420 also.
|
||||
|
ConvertRgbToYuv444(rgbFrame, frameBuffer); |
||||
|
} |
||||
|
|
||||
|
private static void ConvertYuvToRgb(Av1FrameBuffer<byte> buffer, ImageFrame<Rgb24> image, bool isSubsampled) |
||||
|
{ |
||||
|
// 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); |
||||
|
Guard.NotNull(buffer.BufferY); |
||||
|
Guard.NotNull(buffer.BufferCb); |
||||
|
Guard.NotNull(buffer.BufferCr); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferY.Width, image.Width, nameof(buffer)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferY.Height, image.Height, nameof(buffer)); |
||||
|
if (isSubsampled) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCb.Width, image.Width >> 1, nameof(buffer)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCb.Height, image.Height >> 1, nameof(buffer)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCr.Width, image.Width >> 1, nameof(buffer)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCr.Height, image.Height >> 1, nameof(buffer)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCb.Width, image.Width, nameof(buffer)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCb.Height, image.Height, nameof(buffer)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCr.Width, image.Width, nameof(buffer)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCr.Height, image.Height, nameof(buffer)); |
||||
|
} |
||||
|
|
||||
|
image.ProcessPixelRows(accessor => |
||||
|
{ |
||||
|
Buffer2D<byte> yBuffer = buffer.BufferY; |
||||
|
Buffer2D<byte> uBuffer = buffer.BufferCb; |
||||
|
Buffer2D<byte> vBuffer = buffer.BufferCr; |
||||
|
for (int y = 0; y < image.Height; y++) |
||||
|
{ |
||||
|
Span<Rgb24> rgbRow = accessor.GetRowSpan(y); |
||||
|
ref Rgb24 pixel = ref rgbRow[0]; |
||||
|
Span<byte> ySpan = yBuffer.DangerousGetRowSpan(y); |
||||
|
ref byte yRef = ref ySpan[0]; |
||||
|
Span<byte> uSpan = uBuffer.DangerousGetRowSpan(y); |
||||
|
ref byte uRef = ref uSpan[0]; |
||||
|
Span<byte> vSpan = vBuffer.DangerousGetRowSpan(y); |
||||
|
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); |
||||
|
pixel = ref Unsafe.Add(ref pixel, 1); |
||||
|
yRef = ref Unsafe.Add(ref yRef, 1); |
||||
|
uRef = ref Unsafe.Add(ref uRef, 1); |
||||
|
vRef = ref Unsafe.Add(ref vRef, 1); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
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); |
||||
|
Guard.NotNull(buffer.BufferY); |
||||
|
Guard.NotNull(buffer.BufferCb); |
||||
|
Guard.NotNull(buffer.BufferCr); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferY.Width, image.Width, nameof(buffer)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferY.Height, image.Height, nameof(buffer)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCb.Width, image.Width, nameof(buffer)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCb.Height, image.Height, nameof(buffer)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCr.Width, image.Width, nameof(buffer)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCr.Height, image.Height, nameof(buffer)); |
||||
|
|
||||
|
image.ProcessPixelRows(accessor => |
||||
|
{ |
||||
|
Buffer2D<byte> yBuffer = buffer.BufferY; |
||||
|
Buffer2D<byte> uBuffer = buffer.BufferCb; |
||||
|
Buffer2D<byte> vBuffer = buffer.BufferCr; |
||||
|
for (int y = 0; y < image.Height; y++) |
||||
|
{ |
||||
|
Span<Rgb24> rgbRow = accessor.GetRowSpan(y); |
||||
|
ref Rgb24 pixel = ref rgbRow[0]; |
||||
|
Span<byte> ySpan = yBuffer.DangerousGetRowSpan(y); |
||||
|
ref byte yRef = ref ySpan[0]; |
||||
|
Span<byte> uSpan = uBuffer.DangerousGetRowSpan(y); |
||||
|
ref byte uRef = ref uSpan[0]; |
||||
|
Span<byte> vSpan = vBuffer.DangerousGetRowSpan(y); |
||||
|
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); |
||||
|
pixel = ref Unsafe.Add(ref pixel, 1); |
||||
|
yRef = ref Unsafe.Add(ref yRef, 1); |
||||
|
uRef = ref Unsafe.Add(ref uRef, 1); |
||||
|
vRef = ref Unsafe.Add(ref vRef, 1); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using Iced.Intel; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; |
||||
|
|
||||
|
[Trait("Format", "Avif")] |
||||
|
public class Av1YuvConverterTests |
||||
|
{ |
||||
|
[Theory] |
||||
|
[InlineData(255, 255, 255)] |
||||
|
[InlineData(0, 0, 0)] |
||||
|
[InlineData(42, 42, 42)] |
||||
|
[InlineData(42, 0, 0)] |
||||
|
[InlineData(42, 42, 0)] |
||||
|
[InlineData(42, 0, 42)] |
||||
|
[InlineData(0, 42, 42)] |
||||
|
[InlineData(0, 0, 42)] |
||||
|
[InlineData(50, 100, 150)] |
||||
|
public void RoundTripSinglePixel(byte r, byte g, byte b) |
||||
|
{ |
||||
|
// Assign
|
||||
|
using Image<Rgb24> image = new(1, 1); |
||||
|
ImageFrame<Rgb24> frame = image.Frames.RootFrame; |
||||
|
frame.DangerousTryGetSinglePixelMemory(out Memory<Rgb24> memory); |
||||
|
memory.Span[0] = new Rgb24(r, g, b); |
||||
|
ObuSequenceHeader sequenceHeader = new(); |
||||
|
sequenceHeader.MaxFrameWidth = 1; |
||||
|
sequenceHeader.MaxFrameHeight = 1; |
||||
|
Av1FrameBuffer<byte> frameBuffer = new(Configuration.Default, sequenceHeader, Av1ColorFormat.Yuv444, false); |
||||
|
using Image<Rgb24> actual = new(image.Width, image.Height); |
||||
|
|
||||
|
// Act
|
||||
|
Av1YuvConverter.ConvertFromRgb(Configuration.Default, frame, frameBuffer); |
||||
|
Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, actual.Frames.RootFrame); |
||||
|
|
||||
|
// Assert
|
||||
|
actual.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory<Rgb24> actualMemory); |
||||
|
Rgb24 actualPixel = actualMemory.Span[0]; |
||||
|
Assert.Equal(r, actualPixel.R, 2d); |
||||
|
Assert.Equal(g, actualPixel.G, 2d); |
||||
|
Assert.Equal(b, actualPixel.B, 2d); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithFile(TestImages.Jpeg.Baseline.Winter444_Interleaved, PixelTypes.Rgb24)] |
||||
|
public void RoundTrip(TestImageProvider<Rgb24> provider) |
||||
|
{ |
||||
|
// Assign
|
||||
|
using Image<Rgb24> image = provider.GetImage(); |
||||
|
ImageFrame<Rgb24> frame = image.Frames.RootFrame; |
||||
|
ObuSequenceHeader sequenceHeader = new(); |
||||
|
sequenceHeader.MaxFrameWidth = image.Width; |
||||
|
sequenceHeader.MaxFrameHeight = image.Height; |
||||
|
Av1FrameBuffer<byte> frameBuffer = new(Configuration.Default, sequenceHeader, Av1ColorFormat.Yuv444, false); |
||||
|
using Image<Rgb24> actual = new(image.Width, image.Height); |
||||
|
|
||||
|
// Act
|
||||
|
Av1YuvConverter.ConvertFromRgb(Configuration.Default, frame, frameBuffer); |
||||
|
Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, actual.Frames.RootFrame); |
||||
|
|
||||
|
// Assert
|
||||
|
ImageComparer.Tolerant(0.002F).VerifySimilarity(image, actual); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue