📷 A modern, cross-platform, 2D Graphics library for .NET
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.
 
 

151 lines
5.6 KiB

// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
[Trait("Format", "Avif")]
public class Av1ChromaFromLumaTests
{
[Theory]
[InlineData(false, false, new short[] { 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128 })]
[InlineData(true, false, new short[] { 12, 28, 44, 60, 76, 92, 108, 124 })]
[InlineData(true, true, new short[] { 28, 44, 92, 108 })]
public void Store8BitMatchesLibaomSubsampling(bool subX, bool subY, short[] expected)
{
ObuColorConfig colorConfig = new() { SubSamplingX = subX, SubSamplingY = subY };
Av1ChromaFromLumaContext context = new(colorConfig);
byte[] input = Enumerable.Range(1, 16).Select(x => (byte)x).ToArray();
context.Store(input, 4, 0, 0, Av1TransformSize.Size4x4, Av1BlockSize.Block4x4, 0, 0);
int width = 4 >> (subX ? 1 : 0);
int height = 4 >> (subY ? 1 : 0);
Assert.Equal(expected, GetBlock(context.Q3Buffer, width, height));
}
[Fact]
public void StoreHighBitDepthPreservesTwelveBitQ3Range()
{
ObuColorConfig colorConfig = new();
Av1ChromaFromLumaContext context = new(colorConfig);
short[] input = Enumerable.Repeat((short)4095, 16).ToArray();
context.Store(input, 4, 0, 0, Av1TransformSize.Size4x4, Av1BlockSize.Block4x4, 0, 0);
Assert.All(GetBlock(context.Q3Buffer, 4, 4), value => Assert.Equal(32760, value));
}
[Fact]
public void StoreCombinesSub8x8LumaBeforeSubtractingAverage()
{
ObuColorConfig colorConfig = new() { SubSamplingX = true, SubSamplingY = true };
Av1ChromaFromLumaContext context = new(colorConfig);
context.Store(Enumerable.Repeat((byte)10, 16).ToArray(), 4, 0, 0, Av1TransformSize.Size4x4, Av1BlockSize.Block4x4, 0, 0);
context.Store(Enumerable.Repeat((byte)20, 16).ToArray(), 4, 0, 0, Av1TransformSize.Size4x4, Av1BlockSize.Block4x4, 0, 1);
context.Store(Enumerable.Repeat((byte)30, 16).ToArray(), 4, 0, 0, Av1TransformSize.Size4x4, Av1BlockSize.Block4x4, 1, 0);
context.Store(Enumerable.Repeat((byte)40, 16).ToArray(), 4, 0, 0, Av1TransformSize.Size4x4, Av1BlockSize.Block4x4, 1, 1);
context.ComputeParameters(Av1TransformSize.Size4x4);
short[] expected =
[
-120, -120, -40, -40,
-120, -120, -40, -40,
40, 40, 120, 120,
40, 40, 120, 120
];
Assert.Equal(expected, GetBlock(context.Q3Buffer, 4, 4));
}
[Fact]
public void ComputeParametersPadsFrameEdgeBeforeSubtractingAverage()
{
ObuColorConfig colorConfig = new();
Av1ChromaFromLumaContext context = new(colorConfig);
byte[] input = Enumerable.Range(1, 16).Select(x => (byte)x).ToArray();
context.Store(input, 4, 0, 0, Av1TransformSize.Size4x4, Av1BlockSize.Block4x4, 0, 0);
context.ComputeParameters(Av1TransformSize.Size8x8);
short[] actual = GetBlock(context.Q3Buffer, 8, 8);
Assert.Equal(-90, actual[0]);
Assert.Equal(-66, actual[7]);
Assert.Equal(6, actual[56]);
Assert.Equal(30, actual[63]);
Assert.Equal(0, actual.Sum(x => x));
}
[Fact]
public void Predict8BitAddsScaledLumaAndClips()
{
short[] lumaQ3 = new short[32 * 32];
new short[] { -64, -32, 64, 64 }.CopyTo(lumaQ3, 0);
byte[] dcPrediction = [0, 128, 250, 255];
byte[] destination = new byte[4];
Av1PredictionDecoder.ChromaFromLumaPredict(lumaQ3, dcPrediction, 4, destination, 4, 8, Av1BitDepth.EightBit, 4, 1);
Assert.Equal(new byte[] { 0, 124, 255, 255 }, destination);
}
[Theory]
[InlineData((int)Av1BitDepth.TenBit, 1023)]
[InlineData((int)Av1BitDepth.TwelveBit, 4095)]
public void PredictHighBitDepthAddsScaledLumaAndClips(int bitDepthIndex, short maximum)
{
short[] lumaQ3 = new short[32 * 32];
new short[] { -128, -64, 64, 128 }.CopyTo(lumaQ3, 0);
short[] dcPrediction = [5, (short)(maximum / 2), (short)(maximum - 5), maximum];
short[] destination = new short[4];
Av1PredictionDecoder.ChromaFromLumaPredict(
lumaQ3,
dcPrediction,
4,
destination,
4,
16,
(Av1BitDepth)bitDepthIndex,
4,
1);
Assert.Equal(new short[] { 0, (short)((maximum / 2) - 16), maximum, maximum }, destination);
}
[Theory]
[InlineData((int)Av1Plane.U, 3)]
[InlineData((int)Av1Plane.V, 4)]
public void AlphaIndexSelectsMagnitudeForRequestedChromaPlane(int planeIndex, int expected)
{
// U occupies the high nibble and V occupies the low nibble in the packed AV1 alpha index.
const int alphaIndex = 0x23;
const int bothPositiveJointSign = 7;
int actual = Av1PredictionDecoder.ChromaFromLumaIndexToAlpha(
alphaIndex,
bothPositiveJointSign,
(Av1Plane)planeIndex);
Assert.Equal(expected, actual);
}
private static short[] GetBlock(short[] buffer, int width, int height)
{
short[] result = new short[width * height];
for (int y = 0; y < height; y++)
{
buffer.AsSpan(y * 32, width).CopyTo(result.AsSpan(y * width, width));
}
return result;
}
}