From ad57a99ff19294e44fe3af3617657beefb8adedb Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 5 Sep 2024 20:47:41 +0200 Subject: [PATCH] Skeleton code for Transform --- src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 8 + .../Heif/Av1/Transform/Av1CoefficientShape.cs | 12 + .../Transform/Av1DctDctInverseTransformer.cs | 157 +++++++++ .../Av1/Transform/Av1ForwardTransformer.cs | 276 +++++++++++++++ .../Av1/Transform/Av1InverseTransformer.cs | 41 ++- .../Heif/Av1/Transform/Av1SinusConstants.cs | 75 +++++ .../Av1Transform2dFlipConfiguration.cs | 313 ++++++++++++++++++ .../Av1TransformFunctionParameters.cs | 19 ++ .../Av1/Transform/Av1TransformFunctionType.cs | 23 ++ .../Heif/Av1/Transform/Av1TransformType1d.cs | 12 + .../Forward/Av1Adst16ForwardTransformer.cs | 15 + .../Forward/Av1Adst32ForwardTransformer.cs | 15 + .../Forward/Av1Adst4ForwardTransformer.cs | 15 + .../Forward/Av1Adst8ForwardTransformer.cs | 15 + .../Forward/Av1Dct16ForwardTransformer.cs | 15 + .../Forward/Av1Dct32ForwardTransformer.cs | 15 + .../Forward/Av1Dct4ForwardTransformer.cs | 85 +++++ .../Forward/Av1Dct64ForwardTransformer.cs | 15 + .../Forward/Av1Dct8ForwardTransformer.cs | 15 + .../Av1Identity16ForwardTransformer.cs | 15 + .../Av1Identity32ForwardTransformer.cs | 15 + .../Forward/Av1Identity4ForwardTransformer.cs | 15 + .../Av1Identity64ForwardTransformer.cs | 15 + .../Forward/Av1Identity8ForwardTransformer.cs | 15 + .../Transform/ForwardTransformerFactory.cs | 56 ++++ .../Av1/Transform/IAv1ForwardTransformer.cs | 31 ++ .../Transform/InverseTransformerFactory.cs | 19 ++ .../Heif/Av1/Av1ForwardTransformTests.cs | 211 ++++++++++++ .../Formats/Heif/Av1/Av1ReferenceTransform.cs | 273 +++++++++++++++ 29 files changed, 1804 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1CoefficientShape.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionType.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType1d.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/IAv1ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index a023529493..9e4831f01a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; + namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal static class Av1Math @@ -155,4 +157,10 @@ internal static class Av1Math internal static int RoundPowerOf2Signed(int value, int n) => (value < 0) ? -RoundPowerOf2(-value, n) : RoundPowerOf2(value, n); + + internal static int RoundShift(long value, int bit) + { + DebugGuard.MustBeGreaterThanOrEqualTo(bit, 1, nameof(bit)); + return (int)((value + (1L << (bit - 1))) >> bit); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1CoefficientShape.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1CoefficientShape.cs new file mode 100644 index 0000000000..a4f9efc6e5 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1CoefficientShape.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal enum Av1CoefficientShape +{ + Default, + N2, + N4, + OnlyDc +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs new file mode 100644 index 0000000000..e7363ed42b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs @@ -0,0 +1,157 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1DctDctInverseTransformer +{ + private const int UnitQuantizationShift = 2; + + internal static void InverseTransformAdd(ref int coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) + { + Guard.IsTrue(transformFunctionParameters.TransformType == Av1TransformType.DctDct, nameof(transformFunctionParameters.TransformType), "This class implements DCT-DCT transformations only."); + + switch (transformFunctionParameters.TransformSize) + { + case Av1TransformSize.Size4x4: + InverseWhalshHadamard4x4(ref coefficients, ref readBuffer[0], readStride, ref writeBuffer[0], writeStride, transformFunctionParameters.EndOfBuffer, transformFunctionParameters.BitDepth); + break; + default: + throw new NotImplementedException("Only 4x4 transformation size supported for now"); + } + } + + /// + /// SVT: highbd_iwht4x4_add + /// + private static void InverseWhalshHadamard4x4(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int endOfBuffer, int bitDepth) + { + if (endOfBuffer > 1) + { + InverseWhalshHadamard4x4Add16(ref input, ref destinationForRead, strideForRead, ref destinationForWrite, strideForWrite, bitDepth); + } + else + { + InverseWhalshHadamard4x4Add1(ref input, ref destinationForRead, strideForRead, ref destinationForWrite, strideForWrite, bitDepth); + } + } + + /// + /// SVT: svt_av1_highbd_iwht4x4_16_add_c + /// + private static void InverseWhalshHadamard4x4Add16(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int bitDepth) + { + /* 4-point reversible, orthonormal inverse Walsh-Hadamard in 3.5 adds, + 0.5 shifts per pixel. */ + int i; + Span output = stackalloc ushort[16]; + ushort a1, b1, c1, d1, e1; + ref int ip = ref input; + ref ushort op = ref output[0]; + ref ushort opTmp = ref output[0]; + ref ushort destForRead = ref Unsafe.As(ref destinationForRead); + ref ushort destForWrite = ref Unsafe.As(ref destinationForWrite); + + for (i = 0; i < 4; i++) + { + a1 = (ushort)(ip >> UnitQuantizationShift); + c1 = (ushort)(Unsafe.Add(ref ip, 1) >> UnitQuantizationShift); + d1 = (ushort)(Unsafe.Add(ref ip, 2) >> UnitQuantizationShift); + b1 = (ushort)(Unsafe.Add(ref ip, 3) >> UnitQuantizationShift); + a1 += c1; + d1 -= b1; + e1 = (ushort)((a1 - d1) >> 1); + b1 = (ushort)(e1 - b1); + c1 = (ushort)(e1 - c1); + a1 -= b1; + d1 += c1; + op = a1; + Unsafe.Add(ref op, 1) = b1; + Unsafe.Add(ref op, 2) = c1; + Unsafe.Add(ref op, 3) = d1; + ip = ref Unsafe.Add(ref ip, 4); + op = ref Unsafe.Add(ref op, 4); + } + + ip = opTmp; + for (i = 0; i < 4; i++) + { + a1 = (ushort)ip; + c1 = (ushort)Unsafe.Add(ref ip, 4); + d1 = (ushort)Unsafe.Add(ref ip, 8); + b1 = (ushort)Unsafe.Add(ref ip, 12); + a1 += c1; + d1 -= b1; + e1 = (ushort)((a1 - d1) >> 1); + b1 = (ushort)(e1 - b1); + c1 = (ushort)(e1 - c1); + a1 -= b1; + d1 += c1; + /* Disabled in normal build + range_check_value(a1, (int8_t)(bd + 1)); + range_check_value(b1, (int8_t)(bd + 1)); + range_check_value(c1, (int8_t)(bd + 1)); + range_check_value(d1, (int8_t)(bd + 1)); + */ + + destForWrite = ClipPixelAdd(destForRead, a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), b1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 2) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 2), c1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 3) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 3), d1, bitDepth); + + ip = ref Unsafe.Add(ref ip, 1); + destForRead = ref Unsafe.Add(ref destForRead, 1); + destForWrite = ref Unsafe.Add(ref destForWrite, 1); + } + } + + /// + /// SVT: svt_av1_highbd_iwht4x4_1_add_c + /// + private static void InverseWhalshHadamard4x4Add1(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int bitDepth) + { + int i; + ushort a1, e1; + Span tmp = stackalloc int[4]; + ref int ip = ref input; + ref int ipTmp = ref tmp[0]; + ref int op = ref tmp[0]; + ref ushort destForRead = ref Unsafe.As(ref destinationForRead); + ref ushort destForWrite = ref Unsafe.As(ref destinationForWrite); + + a1 = (ushort)(ip >> UnitQuantizationShift); + e1 = (ushort)(a1 >> 1); + a1 -= e1; + op = a1; + Unsafe.Add(ref op, 1) = e1; + Unsafe.Add(ref op, 2) = e1; + Unsafe.Add(ref op, 3) = e1; + + ip = ipTmp; + for (i = 0; i < 4; i++) + { + e1 = (ushort)(ip >> 1); + a1 = (ushort)(ip - e1); + destForWrite = ClipPixelAdd(destForRead, a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 2) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 2), a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 3) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 3), a1, bitDepth); + ip = ref Unsafe.Add(ref ip, 1); + destForRead = ref Unsafe.Add(ref destForRead, 1); + destForWrite = ref Unsafe.Add(ref destForWrite, 1); + } + } + + private static ushort ClipPixelAdd(ushort value, int trans, int bitDepth) + => ClipPixel(value + trans, bitDepth); + + private static ushort ClipPixel(int value, int bitDepth) + => bitDepth switch + { + 10 => (ushort)Av1Math.Clamp(value, 0, 1023), + 12 => (ushort)Av1Math.Clamp(value, 0, 4095), + _ => (ushort)Av1Math.Clamp(value, 0, 255), + }; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs new file mode 100644 index 0000000000..c0c982cc1e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs @@ -0,0 +1,276 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1ForwardTransformer +{ + private const int NewSqrt = 5793; + private const int NewSqrtBitCount = 12; + + private static readonly IAv1ForwardTransformer?[] Transformers = + [ + new Av1Dct4ForwardTransformer(), + new Av1Dct8ForwardTransformer(), + new Av1Dct16ForwardTransformer(), + new Av1Dct32ForwardTransformer(), + new Av1Dct64ForwardTransformer(), + new Av1Adst4ForwardTransformer(), + new Av1Adst8ForwardTransformer(), + new Av1Adst16ForwardTransformer(), + new Av1Adst32ForwardTransformer(), + new Av1Identity4ForwardTransformer(), + new Av1Identity8ForwardTransformer(), + new Av1Identity16ForwardTransformer(), + new Av1Identity32ForwardTransformer(), + new Av1Identity64ForwardTransformer(), + null + ]; + + private static readonly int[] TemporaryCoefficientsBuffer = new int[64 * 64]; + + internal static void Transform2d(Span input, Span coefficients, uint stride, Av1TransformType transformType, Av1TransformSize transformSize, int bitDepth) + { + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + ref int buffer = ref TemporaryCoefficientsBuffer[0]; + IAv1ForwardTransformer? columnTransformer = GetTransformer(config.TransformFunctionTypeColumn); + IAv1ForwardTransformer? rowTransformer = GetTransformer(config.TransformFunctionTypeRow); + if (columnTransformer != null && rowTransformer != null) + { + Transform2dCore(columnTransformer, rowTransformer, ref input[0], stride, ref coefficients[0], config, ref buffer, bitDepth); + } + else + { + throw new InvalidImageContentException($"Cannot find 1d transformer implementation for {config.TransformFunctionTypeColumn} or {config.TransformFunctionTypeRow}."); + } + } + + internal static void Transform2dAvx2(Span input, Span coefficients, uint stride, Av1TransformType transformType, Av1TransformSize transformSize, int bitDepth) + { + switch (transformSize) + { + case Av1TransformSize.Size4x4: + // Too small for intrinsics, use the scalar codepath instead. + Transform2d(input, coefficients, stride, transformType, transformSize, bitDepth); + break; + case Av1TransformSize.Size8x8: + Transform8x8Avx2(input, coefficients, stride, transformType, bitDepth); + break; + default: + Transform2d(input, coefficients, stride, transformType, transformSize, bitDepth); + break; + } + } + + /// + /// SVT: svt_av1_fwd_txfm2d_8x8_avx2 + /// + private static void Transform8x8Avx2(Span input, Span coefficients, uint stride, Av1TransformType transformType, int bitDepth) + { + Av1Transform2dFlipConfiguration config = new(transformType, Av1TransformSize.Size8x8); + Span shift = config.Shift; + Span> inVector = stackalloc Vector256[8]; + Span> outVector = stackalloc Vector256[8]; + ref Vector256 inRef = ref inVector[0]; + ref Vector256 outRef = ref outVector[0]; + switch (transformType) + { + case Av1TransformType.DctDct: + /* Pseudo code + Av1Dct8ForwardTransformer dct8 = new(); + LoadBuffer8x8(ref input[0], ref inRef, stride, 0, 0, shift[0]); + dct8.TransformAvx2(ref inRef, ref outRef, config.CosBitColumn, 1); + Column8x8Rounding(ref outRef, -shift[1]); + Transpose8x8Avx2(ref outRef, ref inRef); + dct8.TransformAvx2(ref inRef, ref outRef, config.CosBitRow, 1); + Transpose8x8Avx2(ref outRef, ref inRef); + WriteBuffer8x8(ref inRef, ref coefficients[0]); + break; + */ + throw new NotImplementedException(); + default: + throw new NotImplementedException(); + } + } + + private static IAv1ForwardTransformer? GetTransformer(Av1TransformFunctionType transformerType) + => Transformers[(int)transformerType]; + + /// + /// SVT: av1_tranform_two_d_core_c + /// + private static void Transform2dCore(TColumn transformFunctionColumn, TRow transformFunctionRow, ref short input, uint inputStride, ref int output, Av1Transform2dFlipConfiguration config, ref int buf, int bitDepth) + where TColumn : IAv1ForwardTransformer + where TRow : IAv1ForwardTransformer + { + int c, r; + + // Note when assigning txfm_size_col, we use the txfm_size from the + // row configuration and vice versa. This is intentionally done to + // accurately perform rectangular transforms. When the transform is + // rectangular, the number of columns will be the same as the + // txfm_size stored in the row cfg struct. It will make no difference + // for square transforms. + int transformColumnCount = config.TransformSize.GetWidth(); + int transformRowCount = config.TransformSize.GetHeight(); + int transformCount = transformColumnCount * transformRowCount; + + // Take the shift from the larger dimension in the rectangular case. + Span shift = config.Shift; + int rectangleType = GetRectangularRatio(transformColumnCount, transformRowCount); + Span stageRangeColumn = stackalloc byte[Av1Transform2dFlipConfiguration.MaxStageNumber]; + Span stageRangeRow = stackalloc byte[Av1Transform2dFlipConfiguration.MaxStageNumber]; + + // assert(cfg->stage_num_col <= MAX_TXFM_STAGE_NUM); + // assert(cfg->stage_num_row <= MAX_TXFM_STAGE_NUM); + config.GenerateStageRange(bitDepth); + + int cosBitColumn = config.CosBitColumn; + int cosBitRow = config.CosBitRow; + + // ASSERT(txfm_func_col != NULL); + // ASSERT(txfm_func_row != NULL); + // use output buffer as temp buffer + ref int tempIn = ref output; + ref int tempOut = ref Unsafe.Add(ref output, transformRowCount); + + // Columns + for (c = 0; c < transformColumnCount; ++c) + { + if (!config.FlipUpsideDown) + { + uint t = (uint)c; + for (r = 0; r < transformRowCount; ++r) + { + Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref input, t); + t += inputStride; + } + } + else + { + uint t = (uint)(c + ((transformRowCount - 1) * (int)inputStride)); + for (r = 0; r < transformRowCount; ++r) + { + // flip upside down + Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref input, t); + t -= inputStride; + } + } + + RoundShiftArray(ref tempIn, transformRowCount, -shift[0]); // NM svt_av1_round_shift_array_c + transformFunctionColumn.Transform(ref tempIn, ref tempOut, cosBitColumn, stageRangeColumn); + RoundShiftArray(ref tempOut, transformRowCount, -shift[1]); // NM svt_av1_round_shift_array_c + if (!config.FlipLeftToRight) + { + int t = c; + for (r = 0; r < transformRowCount; ++r) + { + Unsafe.Add(ref buf, t) = Unsafe.Add(ref tempOut, r); + t += transformColumnCount; + } + } + else + { + int t = transformColumnCount - c - 1; + for (r = 0; r < transformRowCount; ++r) + { + // flip from left to right + Unsafe.Add(ref buf, t) = Unsafe.Add(ref tempOut, r); + t += transformColumnCount; + } + } + } + + // Rows + for (r = 0; r < transformRowCount; ++r) + { + transformFunctionRow.Transform(ref Unsafe.Add(ref buf, r * transformColumnCount), ref Unsafe.Add(ref output, r * transformColumnCount), cosBitRow, stageRangeRow); + RoundShiftArray(ref Unsafe.Add(ref output, r * transformColumnCount), transformColumnCount, -shift[2]); + + if (Math.Abs(rectangleType) == 1) + { + // Multiply everything by Sqrt2 if the transform is rectangular and the + // size difference is a factor of 2. + for (c = 0; c < transformColumnCount; ++c) + { + ref int current = ref Unsafe.Add(ref output, (r * transformColumnCount) + c); + current = Av1Math.RoundShift((long)current * NewSqrt, NewSqrtBitCount); + } + } + } + } + + private static void RoundShiftArray(ref int arr, int size, int bit) + { + if (bit == 0) + { + return; + } + else + { + nuint sz = (nuint)size; + if (bit > 0) + { + for (nuint i = 0; i < sz; i++) + { + ref int a = ref Unsafe.Add(ref arr, i); + a = Av1Math.RoundShift(a, bit); + } + } + else + { + for (nuint i = 0; i < sz; i++) + { + ref int a = ref Unsafe.Add(ref arr, i); + a *= 1 << (-bit); + } + } + } + } + + /// + /// SVT: get_rect_tx_log_ratio + /// + public static int GetRectangularRatio(int col, int row) + { + if (col == row) + { + return 0; + } + + if (col > row) + { + if (col == row * 2) + { + return 1; + } + + if (col == row * 4) + { + return 2; + } + + Guard.IsTrue(false, nameof(row), "Unsupported transform size"); + } + else + { + if (row == col * 2) + { + return -1; + } + + if (row == col * 4) + { + return -2; + } + + Guard.IsTrue(false, nameof(row), "Unsupported transform size"); + } + + return 0; // Invalid + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs index 1f8855602e..edb04aeae6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs @@ -8,8 +8,45 @@ internal class Av1InverseTransformer /// /// SVT: svt_aom_inv_transform_recon8bit /// - public static void Reconstruct8Bit(Span coefficientsBuffer, Span transformBlockReconstructionBuffer1, int reconstructionStride1, Span transformBlockReconstructionBuffer2, int reconstructionStride2, Av1TransformSize transformSize, Av1TransformType transformType, int plane, int numberOfCoefficients, bool isLossless) + public static void Reconstruct8Bit(Span coefficientsBuffer, Span reconstructionBufferRead, int reconstructionReadStride, Span reconstructionBufferWrite, int reconstructionWriteStride, Av1TransformSize transformSize, Av1TransformType transformType, int plane, int numberOfCoefficients, bool isLossless) { - throw new NotImplementedException("Inverse transformation not implemented yet."); + Av1TransformFunctionParameters transformFunctionParameters = new() + { + TransformType = transformType, + TransformSize = transformSize, + EndOfBuffer = numberOfCoefficients, + IsLossless = isLossless, + BitDepth = 8, + Is16BitPipeline = false + }; + + if (reconstructionBufferRead != reconstructionBufferWrite) + { + /* When output pointers to read and write are differents, + * then kernel copy also all buffer from read to write, + * and cannot be limited by End Of Buffer calculations. */ + transformFunctionParameters.EndOfBuffer = GetMaxEndOfBuffer(transformSize); + } + + InverseTransformerFactory.InverseTransformAdd( + ref coefficientsBuffer[0], reconstructionBufferRead, reconstructionReadStride, reconstructionBufferWrite, reconstructionWriteStride, transformFunctionParameters); + } + + /// + /// SVT: av1_get_max_eob + /// + private static int GetMaxEndOfBuffer(Av1TransformSize transformSize) + { + if (transformSize is Av1TransformSize.Size64x64 or Av1TransformSize.Size64x32 or Av1TransformSize.Size32x64) + { + return 1024; + } + + if (transformSize is Av1TransformSize.Size16x64 or Av1TransformSize.Size64x16) + { + return 512; + } + + return transformSize.GetSize2d(); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs new file mode 100644 index 0000000000..432402677f --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal static class Av1SinusConstants +{ + public const int MinimumCosinusBit = 10; + + // av1_cospi_arr[i][j] = (int32_t)round(cos(M_PI*j/128) * (1<<(cos_bit_min+i))); + private static readonly int[][] CosinusPiArray = + [ + [ + 1024, 1024, 1023, 1021, 1019, 1016, 1013, 1009, 1004, 999, 993, 987, 980, 972, 964, 955, + 946, 936, 926, 915, 903, 891, 878, 865, 851, 837, 822, 807, 792, 775, 759, 742, + 724, 706, 688, 669, 650, 630, 610, 590, 569, 548, 526, 505, 483, 460, 438, 415, + 392, 369, 345, 321, 297, 273, 249, 224, 200, 175, 150, 125, 100, 75, 50, 25 + ], + [ + 2048, 2047, 2046, 2042, 2038, 2033, 2026, 2018, 2009, 1998, 1987, 1974, 1960, 1945, 1928, 1911, + 1892, 1872, 1851, 1829, 1806, 1782, 1757, 1730, 1703, 1674, 1645, 1615, 1583, 1551, 1517, 1483, + 1448, 1412, 1375, 1338, 1299, 1260, 1220, 1179, 1138, 1096, 1053, 1009, 965, 921, 876, 830, + 784, 737, 690, 642, 595, 546, 498, 449, 400, 350, 301, 251, 201, 151, 100, 50 + ], + [ + 4096, 4095, 4091, 4085, 4076, 4065, 4052, 4036, 4017, 3996, 3973, 3948, 3920, 3889, 3857, 3822, + 3784, 3745, 3703, 3659, 3612, 3564, 3513, 3461, 3406, 3349, 3290, 3229, 3166, 3102, 3035, 2967, + 2896, 2824, 2751, 2675, 2598, 2520, 2440, 2359, 2276, 2191, 2106, 2019, 1931, 1842, 1751, 1660, + 1567, 1474, 1380, 1285, 1189, 1092, 995, 897, 799, 700, 601, 501, 401, 301, 201, 101 + ], + [ + 8192, 8190, 8182, 8170, 8153, 8130, 8103, 8071, 8035, 7993, 7946, 7895, 7839, 7779, 7713, 7643, + 7568, 7489, 7405, 7317, 7225, 7128, 7027, 6921, 6811, 6698, 6580, 6458, 6333, 6203, 6070, 5933, + 5793, 5649, 5501, 5351, 5197, 5040, 4880, 4717, 4551, 4383, 4212, 4038, 3862, 3683, 3503, 3320, + 3135, 2948, 2760, 2570, 2378, 2185, 1990, 1795, 1598, 1401, 1202, 1003, 803, 603, 402, 201 + ], + [ + 16384, 16379, 16364, 16340, 16305, 16261, 16207, 16143, 16069, 15986, 15893, 15791, 15679, 15557, 15426, 15286, + 15137, 14978, 14811, 14635, 14449, 14256, 14053, 13842, 13623, 13395, 13160, 12916, 12665, 12406, 12140, 11866, + 11585, 11297, 11003, 10702, 10394, 10080, 9760, 9434, 9102, 8765, 8423, 8076, 7723, 7366, 7005, 6639, + 6270, 5897, 5520, 5139, 4756, 4370, 3981, 3590, 3196, 2801, 2404, 2006, 1606, 1205, 804, 402 + ], + [ + 32768, 32758, 32729, 32679, 32610, 32522, 32413, 32286, 32138, 31972, 31786, 31581, 31357, 31114, 30853, 30572, + 30274, 29957, 29622, 29269, 28899, 28511, 28106, 27684, 27246, 26791, 26320, 25833, 25330, 24812, 24279, 23732, + 23170, 22595, 22006, 21403, 20788, 20160, 19520, 18868, 18205, 17531, 16846, 16151, 15447, 14733, 14010, 13279, + 12540, 11793, 11039, 10279, 9512, 8740, 7962, 7180, 6393, 5602, 4808, 4011, 3212, 2411, 1608, 804 + ], + [ + 65536, 65516, 65457, 65358, 65220, 65043, 64827, 64571, 64277, 63944, 63572, 63162, 62714, 62228, 61705, 61145, + 60547, 59914, 59244, 58538, 57798, 57022, 56212, 55368, 54491, 53581, 52639, 51665, 50660, 49624, 48559, 47464, + 46341, 45190, 44011, 42806, 41576, 40320, 39040, 37736, 36410, 35062, 33692, 32303, 30893, 29466, 28020, 26558, + 25080, 23586, 22078, 20557, 19024, 17479, 15924, 14359, 12785, 11204, 9616, 8022, 6424, 4821, 3216, 1608 + ] + ]; + + // svt_aom_eb_av1_sinpi_arr_data[i][j] = (int32_t)round((sqrt(2) * sin(j*Pi/9) * 2 / 3) * (1 + // << (cos_bit_min + i))) modified so that elements j=1,2 sum to element j=4. + private static readonly int[][] SinusPiArray = + [ + [0, 330, 621, 836, 951], + [0, 660, 1241, 1672, 1901], + [0, 1321, 2482, 3344, 3803], + [0, 2642, 4964, 6689, 7606], + [0, 5283, 9929, 13377, 15212], + [0, 10566, 19858, 26755, 30424], + [0, 21133, 39716, 53510, 60849] + ]; + + public static Span CosinusPi(int n) => CosinusPiArray[n - MinimumCosinusBit]; + + public static Span SinusPi(int n) => SinusPiArray[n - MinimumCosinusBit]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs new file mode 100644 index 0000000000..534edd3f1d --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs @@ -0,0 +1,313 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Drawing; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1Transform2dFlipConfiguration +{ + public const int MaxStageNumber = 12; + private const int SmallestTransformSizeLog2 = 2; + + private static readonly Av1TransformType1d[] VerticalType = + [ + Av1TransformType1d.Dct, + Av1TransformType1d.Adst, + Av1TransformType1d.Dct, + Av1TransformType1d.Adst, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.Dct, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.Adst, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.Identity, + Av1TransformType1d.Dct, + Av1TransformType1d.Identity, + Av1TransformType1d.Adst, + Av1TransformType1d.Identity, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.Identity, + ]; + + private static readonly Av1TransformType1d[] HorizontalType = + [ + Av1TransformType1d.Dct, + Av1TransformType1d.Dct, + Av1TransformType1d.Adst, + Av1TransformType1d.Adst, + Av1TransformType1d.Dct, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.Adst, + Av1TransformType1d.Identity, + Av1TransformType1d.Identity, + Av1TransformType1d.Dct, + Av1TransformType1d.Identity, + Av1TransformType1d.Adst, + Av1TransformType1d.Identity, + Av1TransformType1d.FlipAdst, + ]; + + private static readonly int[][] ShiftMap = + [ + [2, 0, 0], // 4x4 + [2, -1, 0], // 8x8 + [2, -2, 0], // 16x16 + [2, -4, 0], // 32x32 + [0, -2, -2], // 64x64 + [2, -1, 0], // 4x8 + [2, -1, 0], // 8x4 + [2, -2, 0], // 8x16 + [2, -2, 0], // 16x8 + [2, -4, 0], // 16x32 + [2, -4, 0], // 32x16 + [0, -2, -2], // 32x64 + [2, -4, -2], // 64x32 + [2, -1, 0], // 4x16 + [2, -1, 0], // 16x4 + [2, -2, 0], // 8x32 + [2, -2, 0], // 32x8 + [0, -2, 0], // 16x64 + [2, -4, 0], // 64x16 + ]; + + private static readonly int[][] CosBitColumnMap = + [[13, 13, 13, 0, 0], [13, 13, 13, 12, 0], [13, 13, 13, 12, 13], [0, 13, 13, 12, 13], [0, 0, 13, 12, 13]]; + + private static readonly int[][] CosBitRowMap = + [[13, 13, 12, 0, 0], [13, 13, 13, 12, 0], [13, 13, 12, 13, 12], [0, 12, 13, 12, 11], [0, 0, 12, 11, 10]]; + + private static readonly Av1TransformFunctionType[][] TransformFunctionTypeMap = + [ + [Av1TransformFunctionType.Dct4, Av1TransformFunctionType.Adst4, Av1TransformFunctionType.Adst4, Av1TransformFunctionType.Identity4], + [Av1TransformFunctionType.Dct8, Av1TransformFunctionType.Adst8, Av1TransformFunctionType.Adst8, Av1TransformFunctionType.Identity8], + [Av1TransformFunctionType.Dct16, Av1TransformFunctionType.Adst16, Av1TransformFunctionType.Adst16, Av1TransformFunctionType.Identity16], + [Av1TransformFunctionType.Dct32, Av1TransformFunctionType.Adst32, Av1TransformFunctionType.Adst32, Av1TransformFunctionType.Identity32], + [Av1TransformFunctionType.Dct64, Av1TransformFunctionType.Invalid, Av1TransformFunctionType.Invalid, Av1TransformFunctionType.Identity64] + ]; + + private static readonly int[] StageNumberList = + [ + 4, // TXFM_TYPE_DCT4 + 6, // TXFM_TYPE_DCT8 + 8, // TXFM_TYPE_DCT16 + 10, // TXFM_TYPE_DCT32 + 12, // TXFM_TYPE_DCT64 + 7, // TXFM_TYPE_ADST4 + 8, // TXFM_TYPE_ADST8 + 10, // TXFM_TYPE_ADST16 + 12, // TXFM_TYPE_ADST32 + 1, // TXFM_TYPE_IDENTITY4 + 1, // TXFM_TYPE_IDENTITY8 + 1, // TXFM_TYPE_IDENTITY16 + 1, // TXFM_TYPE_IDENTITY32 + 1, // TXFM_TYPE_IDENTITY64 + ]; + + private static readonly int[][] RangeMulti2List = + [ + [0, 2, 3, 3], // fdct4_range_mult2 + [0, 2, 4, 5, 5, 5], // fdct8_range_mult2 + [0, 2, 4, 6, 7, 7, 7, 7], // fdct16_range_mult2 + [0, 2, 4, 6, 8, 9, 9, 9, 9, 9], // fdct32_range_mult2 + [0, 2, 4, 6, 8, 10, 11, 11, 11, 11, 11, 11], // fdct64_range_mult2 + [0, 2, 4, 3, 3, 3, 3], // fadst4_range_mult2 + [0, 0, 1, 3, 3, 5, 5, 5], // fadst8_range_mult2 + [0, 0, 1, 3, 3, 5, 5, 7, 7, 7], // fadst16_range_mult2 + [0, 0, 1, 3, 3, 5, 5, 7, 7, 9, 9, 9], // fadst32_range_mult2 + [1], // fidtx4_range_mult2 + [2], // fidtx8_range_mult2 + [3], // fidtx16_range_mult2 + [4], // fidtx32_range_mult2 + [5], // fidtx64_range_mult2 + ]; + + private readonly int[] shift; + + public Av1Transform2dFlipConfiguration(Av1TransformType transformType, Av1TransformSize transformSize) + { + this.TransformSize = transformSize; + this.TransformType = transformType; + this.SetFlip(transformType); + Av1TransformType1d tx_type_1d_col = VerticalType[(int)transformType]; + Av1TransformType1d tx_type_1d_row = HorizontalType[(int)transformType]; + int txw_idx = transformSize.GetBlockWidthLog2() - SmallestTransformSizeLog2; + int txh_idx = transformSize.GetBlockHeightLog2() - SmallestTransformSizeLog2; + this.shift = ShiftMap[(int)transformSize]; + this.CosBitColumn = CosBitColumnMap[txw_idx][txh_idx]; + this.CosBitRow = CosBitRowMap[txw_idx][txh_idx]; + this.TransformFunctionTypeColumn = TransformFunctionTypeMap[txh_idx][(int)tx_type_1d_col]; + this.TransformFunctionTypeRow = TransformFunctionTypeMap[txw_idx][(int)tx_type_1d_row]; + this.StageNumberColumn = StageNumberList[(int)this.TransformFunctionTypeColumn]; + this.StageNumberRow = StageNumberList[(int)this.TransformFunctionTypeRow]; + this.StageRangeColumn = new int[12]; + this.StageRangeRow = new int[12]; + this.NonScaleRange(); + } + + public int CosBitColumn { get; } + + public int CosBitRow { get; } + + public Av1TransformFunctionType TransformFunctionTypeColumn { get; } + + public Av1TransformFunctionType TransformFunctionTypeRow { get; } + + public int StageNumberColumn { get; } + + public int StageNumberRow { get; } + + public Av1TransformSize TransformSize { get; } + + public Av1TransformType TransformType { get; } + + public bool FlipUpsideDown { get; private set; } + + public bool FlipLeftToRight { get; private set; } + + public Span Shift => this.shift; + + public int[] StageRangeColumn { get; } + + public int[] StageRangeRow { get; } + + /// + /// SVT: svt_av1_gen_fwd_stage_range + /// + public void GenerateStageRange(int bitDepth) + { + // Take the shift from the larger dimension in the rectangular case. + Span shift = this.Shift; + + // i < MAX_TXFM_STAGE_NUM will mute above array bounds warning + for (int i = 0; i < this.StageNumberColumn && i < MaxStageNumber; ++i) + { + this.StageRangeColumn[i] = this.StageRangeColumn[i] + shift[0] + bitDepth + 1; + } + + // i < MAX_TXFM_STAGE_NUM will mute above array bounds warning + for (int i = 0; i < this.StageNumberRow && i < MaxStageNumber; ++i) + { + this.StageRangeRow[i] = this.StageRangeRow[i] + shift[0] + shift[1] + bitDepth + 1; + } + } + + /// + /// SVT: is_txfm_allowed + /// + public bool IsAllowed() + { + Av1TransformType[] supportedTypes = + [ + Av1TransformType.DctDct, + Av1TransformType.AdstDct, + Av1TransformType.DctAdst, + Av1TransformType.AdstAdst, + Av1TransformType.FlipAdstDct, + Av1TransformType.DctFlipAdst, + Av1TransformType.FlipAdstFlipAdst, + Av1TransformType.AdstFlipAdst, + Av1TransformType.FlipAdstAdst, + Av1TransformType.Identity, + Av1TransformType.VerticalDct, + Av1TransformType.HorizontalDct, + Av1TransformType.VerticalAdst, + Av1TransformType.HorizontalAdst, + Av1TransformType.VerticalFlipAdst, + Av1TransformType.HorizontalFlipAdst, + ]; + + switch (this.TransformSize) + { + case Av1TransformSize.Size32x32: + supportedTypes = [Av1TransformType.DctDct, Av1TransformType.Identity, Av1TransformType.VerticalDct, Av1TransformType.HorizontalDct]; + break; + case Av1TransformSize.Size32x64: + case Av1TransformSize.Size64x32: + case Av1TransformSize.Size16x64: + case Av1TransformSize.Size64x16: + supportedTypes = [Av1TransformType.DctDct]; + break; + case Av1TransformSize.Size16x32: + case Av1TransformSize.Size32x16: + case Av1TransformSize.Size64x64: + case Av1TransformSize.Size8x32: + case Av1TransformSize.Size32x8: + supportedTypes = [Av1TransformType.DctDct, Av1TransformType.Identity]; + break; + default: + break; + } + + return supportedTypes.Contains(this.TransformType); + } + + private void SetFlip(Av1TransformType transformType) + { + switch (transformType) + { + case Av1TransformType.DctDct: + case Av1TransformType.AdstDct: + case Av1TransformType.DctAdst: + case Av1TransformType.AdstAdst: + this.FlipUpsideDown = false; + this.FlipLeftToRight = false; + break; + case Av1TransformType.Identity: + case Av1TransformType.VerticalDct: + case Av1TransformType.HorizontalDct: + case Av1TransformType.VerticalAdst: + case Av1TransformType.HorizontalAdst: + this.FlipUpsideDown = false; + this.FlipLeftToRight = false; + break; + case Av1TransformType.FlipAdstDct: + case Av1TransformType.FlipAdstAdst: + case Av1TransformType.VerticalFlipAdst: + this.FlipUpsideDown = true; + this.FlipLeftToRight = false; + break; + case Av1TransformType.DctFlipAdst: + case Av1TransformType.AdstFlipAdst: + case Av1TransformType.HorizontalFlipAdst: + this.FlipUpsideDown = false; + this.FlipLeftToRight = true; + break; + case Av1TransformType.FlipAdstFlipAdst: + this.FlipUpsideDown = true; + this.FlipLeftToRight = true; + break; + default: + Guard.IsTrue(false, nameof(transformType), "Unknown transform type for determining flip."); + break; + } + } + + /// + /// SVT: set_fwd_txfm_non_scale_range + /// + private void NonScaleRange() + { + Span range_mult2_col = RangeMulti2List[(int)this.TransformFunctionTypeColumn]; + if (this.TransformFunctionTypeColumn != Av1TransformFunctionType.Invalid) + { + int stage_num_col = this.StageNumberColumn; + for (int i = 0; i < stage_num_col; ++i) + { + this.StageRangeColumn[i] = (range_mult2_col[i] + 1) >> 1; + } + } + + if (this.TransformFunctionTypeRow != Av1TransformFunctionType.Invalid) + { + int stage_num_row = this.StageNumberRow; + Span range_mult2_row = RangeMulti2List[(int)this.TransformFunctionTypeRow]; + for (int i = 0; i < stage_num_row; ++i) + { + this.StageRangeRow[i] = (range_mult2_col[this.StageNumberColumn - 1] + range_mult2_row[i] + 1) >> 1; + } + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionParameters.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionParameters.cs new file mode 100644 index 0000000000..ae24e659c3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionParameters.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1TransformFunctionParameters +{ + public Av1TransformType TransformType { get; internal set; } + + public Av1TransformSize TransformSize { get; internal set; } + + public int EndOfBuffer { get; internal set; } + + public bool IsLossless { get; internal set; } + + public int BitDepth { get; internal set; } + + public bool Is16BitPipeline { get; internal set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionType.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionType.cs new file mode 100644 index 0000000000..cd118454f0 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionType.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal enum Av1TransformFunctionType +{ + Dct4, + Dct8, + Dct16, + Dct32, + Dct64, + Adst4, + Adst8, + Adst16, + Adst32, + Identity4, + Identity8, + Identity16, + Identity32, + Identity64, + Invalid, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType1d.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType1d.cs new file mode 100644 index 0000000000..a201493198 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType1d.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal enum Av1TransformType1d +{ + Dct, + Adst, + FlipAdst, + Identity +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16ForwardTransformer.cs new file mode 100644 index 0000000000..6bb615acd6 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Adst16ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32ForwardTransformer.cs new file mode 100644 index 0000000000..d64cfa0a2f --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Adst32ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4ForwardTransformer.cs new file mode 100644 index 0000000000..fa70450569 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Adst4ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8ForwardTransformer.cs new file mode 100644 index 0000000000..66fad050f9 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Adst8ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16ForwardTransformer.cs new file mode 100644 index 0000000000..4725952b85 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Dct16ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32ForwardTransformer.cs new file mode 100644 index 0000000000..a0178da831 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Dct32ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs new file mode 100644 index 0000000000..db0134c36f --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs @@ -0,0 +1,85 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Dct4ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException("Too small block for Vector implementation, use TransformSse() method instead."); + + /// + /// SVT: fdct4x4_sse4_1 + /// + public static void TransformSse(ref Vector128 input, ref Vector128 output, byte cosBit, int columnNumber) + { + /* + // We only use stage-2 bit; + // shift[0] is used in load_buffer_4x4() + // shift[1] is used in txfm_func_col() + // shift[2] is used in txfm_func_row() + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + Vector128 cospi32 = Vector128.Create(cospi[32]); + Vector128 cospi48 = Vector128.Create(cospi[48]); + Vector128 cospi16 = Vector128.Create(cospi[16]); + Vector128 rnding = Vector128.Create(1 << (cosBit - 1)); + Vector128 s0, s1, s2, s3; + Vector128 u0, u1, u2, u3; + Vector128 v0, v1, v2, v3; + + int endidx = 3 * columnNumber; + s0 = Sse41.Add(input, Unsafe.Add(ref input, endidx)); + s3 = Sse41.Subtract(input, Unsafe.Add(ref input, endidx)); + endidx -= columnNumber; + s1 = Sse41.Add(Unsafe.Add(ref input, columnNumber), Unsafe.Add(ref input, endidx)); + s2 = Sse41.Subtract(Unsafe.Add(ref input, columnNumber), Unsafe.Add(ref input, endidx)); + + // btf_32_sse4_1_type0(cospi32, cospi32, s[01], u[02], bit); + u0 = Sse41.MultiplyLow(s0, cospi32); + u1 = Sse41.MultiplyLow(s1, cospi32); + u2 = Sse41.Add(u0, u1); + v0 = Sse41.Subtract(u0, u1); + + u3 = Sse41.Add(u2, rnding); + v1 = Sse41.Add(v0, rnding); + + u0 = Sse41.ShiftRightArithmetic(u3, cosBit); + u2 = Sse41.ShiftRightArithmetic(v1, cosBit); + + // btf_32_sse4_1_type1(cospi48, cospi16, s[23], u[13], bit); + v0 = Sse41.MultiplyLow(s2, cospi48); + v1 = Sse41.MultiplyLow(s3, cospi16); + v2 = Sse41.Add(v0, v1); + + v3 = Sse41.Add(v2, rnding); + u1 = Sse41.ShiftRightArithmetic(v3, cosBit); + + v0 = Sse41.MultiplyLow(s2, cospi16); + v1 = Sse41.MultiplyLow(s3, cospi48); + v2 = Sse41.Subtract(v1, v0); + + v3 = Sse41.Add(v2, rnding); + u3 = Sse41.ShiftRightArithmetic(v3, cosBit); + + // Note: shift[1] and shift[2] are zeros + + // Transpose 4x4 32-bit + v0 = Sse41.UnpackLow(u0, u1); + v1 = Sse41.UnpackHigh(u0, u1); + v2 = Sse41.UnpackLow(u2, u3); + v3 = Sse41.UnpackHigh(u2, u3); + + output = Sse41.UnpackLow(v0.AsInt64(), v2.AsInt64()).AsInt32(); + Unsafe.Add(ref output, 1) = Sse41.UnpackHigh(v0.AsInt64(), v2.AsInt64()).AsInt32(); + Unsafe.Add(ref output, 2) = Sse41.UnpackLow(v1.AsInt64(), v3.AsInt64()).AsInt32(); + Unsafe.Add(ref output, 3) = Sse41.UnpackHigh(v1.AsInt64(), v3.AsInt64()).AsInt32(); + */ + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64ForwardTransformer.cs new file mode 100644 index 0000000000..63cfeb2a4e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Dct64ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8ForwardTransformer.cs new file mode 100644 index 0000000000..317c1bb4e6 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Dct8ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16ForwardTransformer.cs new file mode 100644 index 0000000000..e3ed9f4a50 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity16ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32ForwardTransformer.cs new file mode 100644 index 0000000000..baba34c906 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity32ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4ForwardTransformer.cs new file mode 100644 index 0000000000..4afabf67f2 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity4ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64ForwardTransformer.cs new file mode 100644 index 0000000000..00ea87cf55 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity64ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8ForwardTransformer.cs new file mode 100644 index 0000000000..224184e53c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity8ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs new file mode 100644 index 0000000000..4463f2d225 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal static class ForwardTransformerFactory +{ + internal static void EstimateTransform( + Span residualBuffer, + uint residualStride, + Span coefficientBuffer, + uint coefficientStride, + Av1TransformSize transformSize, + ref ulong threeQuadEnergy, + int bitDepth, + Av1TransformType transformType, + Av1PlaneType componentType, + Av1CoefficientShape transformCoefficientShape) + { + switch (transformCoefficientShape) + { + case Av1CoefficientShape.Default: + EstimateTransformDefault(residualBuffer, residualStride, coefficientBuffer, coefficientStride, transformSize, ref threeQuadEnergy, bitDepth, transformType, componentType); + break; + case Av1CoefficientShape.N2: + EstimateTransformN2(residualBuffer, residualStride, coefficientBuffer, coefficientStride, transformSize, ref threeQuadEnergy, bitDepth, transformType, componentType); + break; + case Av1CoefficientShape.N4: + EstimateTransformN4(residualBuffer, residualStride, coefficientBuffer, coefficientStride, transformSize, ref threeQuadEnergy, bitDepth, transformType, componentType); + break; + case Av1CoefficientShape.OnlyDc: + EstimateTransformOnlyDc(residualBuffer, residualStride, coefficientBuffer, coefficientStride, transformSize, ref threeQuadEnergy, bitDepth, transformType, componentType); + break; + } + } + + private static void EstimateTransformDefault( + Span residualBuffer, + uint residualStride, + Span coefficientBuffer, + uint coefficientStride, + Av1TransformSize transformSize, + ref ulong threeQuadEnergy, + int bitDepth, + Av1TransformType transformType, + Av1PlaneType componentType) + => Av1ForwardTransformer.Transform2d(residualBuffer, coefficientBuffer, residualStride, transformType, transformSize, bitDepth); + + private static void EstimateTransformN2(Span residualBuffer, uint residualStride, Span coefficientBuffer, uint coefficientStride, Av1TransformSize transformSize, ref ulong threeQuadEnergy, int bitDepth, Av1TransformType transformType, Av1PlaneType componentType) => throw new NotImplementedException(); + + private static void EstimateTransformN4(Span residualBuffer, uint residualStride, Span coefficientBuffer, uint coefficientStride, Av1TransformSize transformSize, ref ulong threeQuadEnergy, int bitDepth, Av1TransformType transformType, Av1PlaneType componentType) => throw new NotImplementedException(); + + private static void EstimateTransformOnlyDc(Span residualBuffer, uint residualStride, Span coefficientBuffer, uint coefficientStride, Av1TransformSize transformSize, ref ulong threeQuadEnergy, int bitDepth, Av1TransformType transformType, Av1PlaneType componentType) => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1ForwardTransformer.cs new file mode 100644 index 0000000000..c7ef675f3d --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1ForwardTransformer.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +/// +/// Implementation of a specific forward transform function. +/// +internal interface IAv1ForwardTransformer +{ + /// + /// Execute the transformation. + /// + /// Input pixels. + /// Output coefficients. + /// The cosinus bit. + /// Stage ranges. + void Transform(ref int input, ref int output, int cosBit, Span stageRange); + + /// + /// Execute the transformation using instructions. + /// + /// Array of input vectors. + /// Array of output coefficients vectors. + /// The cosinus bit. + /// The column number to process. + void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs new file mode 100644 index 0000000000..feb7dbe76c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal static class InverseTransformerFactory +{ + internal static unsafe void InverseTransformAdd(ref int coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) + { + switch (transformFunctionParameters.TransformType) + { + case Av1TransformType.DctDct: + Av1DctDctInverseTransformer.InverseTransformAdd(ref coefficients, readBuffer, readStride, writeBuffer, writeStride, transformFunctionParameters); + break; + default: + throw new InvalidImageContentException("Unknown transform type: " + transformFunctionParameters.TransformType); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs new file mode 100644 index 0000000000..8922920ed8 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs @@ -0,0 +1,211 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +/// +/// SVY: test/FwdTxfm2dTest.cc +/// +[Trait("Format", "Avif")] +public class Av1ForwardTransformTests +{ + private static readonly double[] MaximumAllowedError = + [ + 3, // 4x4 transform + 5, // 8x8 transform + 11, // 16x16 transform + 70, // 32x32 transform + 64, // 64x64 transform + 3.9, // 4x8 transform + 4.3, // 8x4 transform + 12, // 8x16 transform + 12, // 16x8 transform + 32, // 16x32 transform + 46, // 32x16 transform + 136, // 32x64 transform + 136, // 64x32 transform + 5, // 4x16 transform + 6, // 16x4 transform + 21, // 8x32 transform + 13, // 32x8 transform + 30, // 16x64 transform + 36, // 64x16 transform + ]; + + private readonly short[] inputOfTest; + private readonly int[] outputOfTest; + private readonly double[] inputReference; + private readonly double[] outputReference; + + public Av1ForwardTransformTests() + { + this.inputOfTest = new short[64 * 64]; + this.outputOfTest = new int[64 * 64]; + this.inputReference = new double[64 * 64]; + this.outputReference = new double[64 * 64]; + } + + [Theory] + [MemberData(nameof(GetCombinations))] + public void Accuracy2dTest(int txSize, int txType, int maxAllowedError) + { + const int bitDepth = 8; + Random rnd = new(0); + const int testBlockCount = 1; // Originally set to: 1000 + Av1TransformSize transformSize = (Av1TransformSize)txSize; + Av1TransformType transformType = (Av1TransformType)txType; + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + int width = config.TransformSize.GetWidth(); + int height = config.TransformSize.GetHeight(); + int blockSize = width * height; + double scaleFactor = Av1ReferenceTransform.GetScaleFactor(config, width, height); + + for (int ti = 0; ti < testBlockCount; ++ti) + { + // prepare random test data + for (int ni = 0; ni < blockSize; ++ni) + { + this.inputOfTest[ni] = (short)rnd.Next((1 << 10) - 1); + this.inputReference[ni] = this.inputOfTest[ni]; + this.outputReference[ni] = 0; + this.outputOfTest[ni] = 255; + } + + // calculate in forward transform functions + Av1ForwardTransformer.Transform2d( + this.inputOfTest, + this.outputOfTest, + (uint)transformSize.GetWidth(), + transformType, + transformSize, + bitDepth); + + // calculate in reference forward transform functions + Av1ReferenceTransform.ReferenceTransformFunction2d(this.inputReference, this.outputReference, transformType, transformSize, scaleFactor); + + // repack the coefficents for some tx_size + this.RepackCoefficients(width, height); + + // compare for the result is in accuracy + double maximumErrorInTest = 0; + for (int ni = 0; ni < blockSize; ++ni) + { + maximumErrorInTest = Math.Max(maximumErrorInTest, Math.Abs(this.outputOfTest[ni] - Math.Round(this.outputReference[ni]))); + } + + maximumErrorInTest /= scaleFactor; + Assert.True(maxAllowedError >= maximumErrorInTest, $"Forward transform 2d test with transform type: {transformType}, transform size: {transformSize} and loop: {ti}"); + } + } + + // The max txb_width or txb_height is 32, as specified in spec 7.12.3. + // Clear the high frequency coefficents and repack it in linear layout. + private void RepackCoefficients(int tx_width, int tx_height) + { + for (int i = 0; i < 2; ++i) + { + uint e_size = i == 0 ? (uint)sizeof(int) : sizeof(double); + ref byte output = ref (i == 0) ? ref Unsafe.As(ref this.outputOfTest[0]) + : ref Unsafe.As(ref this.outputReference[0]); + + if (tx_width == 64 && tx_height == 64) + { + // tx_size == TX_64X64 + // zero out top-right 32x32 area. + for (uint row = 0; row < 32; ++row) + { + Unsafe.InitBlock(ref Unsafe.Add(ref output, ((row * 64) + 32) * e_size), 0, 32 * e_size); + } + + // zero out the bottom 64x32 area. + Unsafe.InitBlock(ref Unsafe.Add(ref output, 32 * 64 * e_size), 0, 32 * 64 * e_size); + + // Re-pack non-zero coeffs in the first 32x32 indices. + for (uint row = 1; row < 32; ++row) + { + Unsafe.CopyBlock( + ref Unsafe.Add(ref output, row * 32 * e_size), + ref Unsafe.Add(ref output, row * 64 * e_size), + 32 * e_size); + } + } + else if (tx_width == 32 && tx_height == 64) + { + // tx_size == TX_32X64 + // zero out the bottom 32x32 area. + Unsafe.InitBlock(ref Unsafe.Add(ref output, 32 * 32 * e_size), 0, 32 * 32 * e_size); + + // Note: no repacking needed here. + } + else if (tx_width == 64 && tx_height == 32) + { + // tx_size == TX_64X32 + // zero out right 32x32 area. + for (uint row = 0; row < 32; ++row) + { + Unsafe.InitBlock(ref Unsafe.Add(ref output, ((row * 64) + 32) * e_size), 0, 32 * e_size); + } + + // Re-pack non-zero coeffs in the first 32x32 indices. + for (uint row = 1; row < 32; ++row) + { + Unsafe.CopyBlock( + ref Unsafe.Add(ref output, row * 32 * e_size), + ref Unsafe.Add(ref output, row * 64 * e_size), + 32 * e_size); + } + } + else if (tx_width == 16 && tx_height == 64) + { + // tx_size == TX_16X64 + // zero out the bottom 16x32 area. + Unsafe.InitBlock(ref Unsafe.Add(ref output, 16 * 32 * e_size), 0, 16 * 32 * e_size); + + // Note: no repacking needed here. + } + else if (tx_width == 64 && + tx_height == 16) + { + // tx_size == TX_64X16 + // zero out right 32x16 area. + for (uint row = 0; row < 16; ++row) + { + Unsafe.InitBlock(ref Unsafe.Add(ref output, ((row * 64) + 32) * e_size), 0, 32 * e_size); + } + + // Re-pack non-zero coeffs in the first 32x16 indices. + for (uint row = 1; row < 16; ++row) + { + Unsafe.CopyBlock( + ref Unsafe.Add(ref output, row * 32 * e_size), + ref Unsafe.Add(ref output, row * 64 * e_size), + 32 * e_size); + } + } + } + } + + public static TheoryData GetCombinations() + { + TheoryData combinations = []; + for (int s = 0; s < (int)Av1TransformSize.AllSizes; s++) + { + double maxError = MaximumAllowedError[s]; + for (int t = 0; t < (int)Av1TransformType.AllTransformTypes; ++t) + { + Av1TransformType transformType = (Av1TransformType)t; + Av1TransformSize transformSize = (Av1TransformSize)s; + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + if (config.IsAllowed()) + { + combinations.Add(s, t, (int)maxError); + } + } + } + + return combinations; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs new file mode 100644 index 0000000000..f490ead2e6 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs @@ -0,0 +1,273 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +internal class Av1ReferenceTransform +{ + /****************************************************************************** + * SVT file: test/ref/TxfmRef.cc + * + * Reference implementation for txfm, including : + * - reference_dct_1d + * - reference_adst_1d + * - reference_idtx_1d + * - reference_txfm_1d + * - reference_txfm_2d + * - fadst_ref + * + * Original authors: Cidana-Edmond, Cidana-Wenyao + * + ******************************************************************************/ + + public static double GetScaleFactor(Av1Transform2dFlipConfiguration config, int transformWidth, int transformHeight) + { + Span shift = config.Shift; + int amplifyBit = shift[0] + shift[1] + shift[2]; + double scaleFactor = + amplifyBit >= 0 ? (1 << amplifyBit) : (1.0 / (1 << -amplifyBit)); + + // For rectangular transforms, we need to multiply by an extra factor. + int rectType = Av1ForwardTransformer.GetRectangularRatio(transformWidth, transformHeight); + if (Math.Abs(rectType) == 1) + { + scaleFactor *= Math.Pow(2, 0.5); + } + + return scaleFactor; + } + + public static void ReferenceTransformFunction2d(Span input, Span output, Av1TransformType transformType, Av1TransformSize transformSize, double scaleFactor) + { + // Get transform type and size of each dimension. + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + Av1TransformType1d columnType = GetTransformType1d(config.TransformFunctionTypeColumn); + Av1TransformType1d rowType = GetTransformType1d(config.TransformFunctionTypeRow); + int transformWidth = transformSize.GetWidth(); + int transformHeight = transformSize.GetHeight(); + Span tmpInput = new double[transformWidth * transformHeight]; + Span tmpOutput = new double[transformWidth * transformHeight]; + + // second forward transform with row_type + for (int r = 0; r < transformHeight; ++r) + { + ReferenceTransform1d(rowType, input[(r * transformWidth)..], output[(r * transformWidth)..], transformWidth); + } + + // matrix transposition + for (int r = 0; r < transformHeight; ++r) + { + for (int c = 0; c < transformWidth; ++c) + { + tmpInput[(c * transformHeight) + r] = output[(r * transformWidth) + c]; + } + } + + // first forward transform with column_type + for (int c = 0; c < transformWidth; ++c) + { + ReferenceTransform1d( + columnType, + tmpInput[(c * transformHeight)..], + tmpOutput[(c * transformHeight)..], + transformHeight); + } + + // matrix transposition + for (int r = 0; r < transformHeight; ++r) + { + for (int c = 0; c < transformWidth; ++c) + { + output[(c * transformHeight) + r] = tmpOutput[(r * transformWidth) + c]; + } + } + + // appropriate scale + for (int r = 0; r < transformHeight; ++r) + { + for (int c = 0; c < transformWidth; ++c) + { + output[(r * transformWidth) + c] *= scaleFactor; + } + } + } + + private static void Adst4Reference(Span input, Span output) + { + // 16384 * sqrt(2) * sin(kPi/9) * 2 / 3 + const long sinPi19 = 5283; + const long sinPi29 = 9929; + const long sinPi39 = 13377; + const long sinPi49 = 15212; + + long x0, x1, x2, x3; + long s0, s1, s2, s3, s4, s5, s6, s7; + x0 = input[0]; + x1 = input[1]; + x2 = input[2]; + x3 = input[3]; + + if ((x0 | x1 | x2 | x3) == 0L) + { + output[0] = output[1] = output[2] = output[3] = 0; + return; + } + + s0 = sinPi19 * x0; + s1 = sinPi49 * x0; + s2 = sinPi29 * x1; + s3 = sinPi19 * x1; + s4 = sinPi39 * x2; + s5 = sinPi49 * x3; + s6 = sinPi29 * x3; + s7 = x0 + x1 - x3; + + x0 = s0 + s2 + s5; + x1 = sinPi39 * s7; + x2 = s1 - s3 + s6; + x3 = s4; + + s0 = x0 + x3; + s1 = x1; + s2 = x2 - x3; + s3 = x2 - x0 + x3; + + // 1-D transform scaling factor is sqrt(2). + output[0] = Av1Math.RoundShift(s0, 14); + output[1] = Av1Math.RoundShift(s1, 14); + output[2] = Av1Math.RoundShift(s2, 14); + output[3] = Av1Math.RoundShift(s3, 14); + } + + private static void ReferenceIdentity1d(Span input, Span output, int size) + { + const double sqrt2 = 1.4142135623730950488016887242097f; + double scale = 0; + switch (size) + { + case 4: + scale = sqrt2; + break; + case 8: + scale = 2; + break; + case 16: + scale = 2 * sqrt2; + break; + case 32: + scale = 4; + break; + case 64: + scale = 4 * sqrt2; + break; + default: + Assert.Fail(); + break; + } + + for (int k = 0; k < size; ++k) + { + output[k] = input[k] * scale; + } + } + + private static void ReferenceDct1d(Span input, Span output, int size) + { + const double kInvSqrt2 = 0.707106781186547524400844362104f; + for (int k = 0; k < size; ++k) + { + output[k] = 0; + for (int n = 0; n < size; ++n) + { + output[k] += input[n] * Math.Cos(Math.PI * ((2 * n) + 1) * k / (2 * size)); + } + + if (k == 0) + { + output[k] = output[k] * kInvSqrt2; + } + } + } + + private static void ReferenceAdst1d(Span input, Span output, int size) + { + if (size == 4) + { + // Special case. + int[] int_input = new int[4]; + for (int i = 0; i < 4; ++i) + { + int_input[i] = (int)Math.Round(input[i]); + } + + int[] int_output = new int[4]; + Adst4Reference(int_input, int_output); + for (int i = 0; i < 4; ++i) + { + output[i] = int_output[i]; + } + + return; + } + + for (int k = 0; k < size; ++k) + { + output[k] = 0; + for (int n = 0; n < size; ++n) + { + output[k] += input[n] * Math.Sin(Math.PI * ((2 * n) + 1) * ((2 * k) + 1) / (4 * size)); + } + } + } + + private static void ReferenceTransform1d(Av1TransformType1d type, Span input, Span output, int size) + { + switch (type) + { + case Av1TransformType1d.Dct: + ReferenceDct1d(input, output, size); + break; + case Av1TransformType1d.Adst: + case Av1TransformType1d.FlipAdst: + ReferenceAdst1d(input, output, size); + break; + case Av1TransformType1d.Identity: + ReferenceIdentity1d(input, output, size); + break; + default: + Assert.Fail(); + break; + } + } + + private static Av1TransformType1d GetTransformType1d(Av1TransformFunctionType transformFunctionType) + { + switch (transformFunctionType) + { + case Av1TransformFunctionType.Dct4: + case Av1TransformFunctionType.Dct8: + case Av1TransformFunctionType.Dct16: + case Av1TransformFunctionType.Dct32: + case Av1TransformFunctionType.Dct64: + return Av1TransformType1d.Dct; + case Av1TransformFunctionType.Adst4: + case Av1TransformFunctionType.Adst8: + case Av1TransformFunctionType.Adst16: + case Av1TransformFunctionType.Adst32: + return Av1TransformType1d.Adst; + case Av1TransformFunctionType.Identity4: + case Av1TransformFunctionType.Identity8: + case Av1TransformFunctionType.Identity16: + case Av1TransformFunctionType.Identity32: + case Av1TransformFunctionType.Identity64: + return Av1TransformType1d.Identity; + case Av1TransformFunctionType.Invalid: + default: + Assert.Fail(); + return (Av1TransformType1d)5; + } + } +}