diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
index a31bb137fe..fc4915e537 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
@@ -175,4 +175,9 @@ internal static class Av1Constants
public const int QuantificationMatrixLevelCount = 4;
public const int AngleStep = 3;
+
+ ///
+ /// Maximum number of stages in a 1-dimensioanl transform function.
+ ///
+ public const int MaxTransformStageNumber = 12;
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs
index d4bca87659..b1abf23245 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs
@@ -179,6 +179,7 @@ internal class Av1Transform2dFlipConfiguration
///
/// SVT: svt_av1_gen_fwd_stage_range
+ /// SVT: svt_av1_gen_inv_stage_range
///
public void GenerateStageRange(int bitDepth)
{
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Adst4Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Adst4Inverse1dTransformer.cs
new file mode 100644
index 0000000000..3c0fa7d566
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Adst4Inverse1dTransformer.cs
@@ -0,0 +1,78 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse;
+
+internal class Av1Adst4Inverse1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 4, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 4, nameof(output));
+ TransformScalar(ref input[0], ref output[0], cosBit, stageRange);
+ }
+
+ ///
+ /// SVT: svt_av1_iadst4_new
+ ///
+ private static void TransformScalar(ref int input, ref int output, int cosBit, Span stageRange)
+ {
+ int bit = cosBit;
+ Span sinpi = Av1SinusConstants.SinusPi(bit);
+ int s0, s1, s2, s3, s4, s5, s6, s7;
+
+ int x0 = input;
+ int x1 = Unsafe.Add(ref input, 1);
+ int x2 = Unsafe.Add(ref input, 2);
+ int x3 = Unsafe.Add(ref input, 3);
+
+ if (!(x0 != 0 | x1 != 0 | x2 != 0 | x3 != 0))
+ {
+ output = 0;
+ Unsafe.Add(ref output, 1) = 0;
+ Unsafe.Add(ref output, 2) = 0;
+ Unsafe.Add(ref output, 3) = 0;
+ return;
+ }
+
+ Guard.IsTrue(sinpi[1] + sinpi[2] == sinpi[4], nameof(sinpi), "Sinus Pi check failed.");
+
+ s0 = sinpi[1] * x0;
+ s1 = sinpi[2] * x0;
+ s2 = sinpi[3] * x1;
+ s3 = sinpi[4] * x2;
+ s4 = sinpi[1] * x2;
+ s5 = sinpi[2] * x3;
+ s6 = sinpi[4] * x3;
+
+ s7 = (x0 - x2) + x3;
+
+ // stage 3
+ s0 = s0 + s3;
+ s1 = s1 - s4;
+ s3 = s2;
+ s2 = sinpi[3] * s7;
+
+ // stage 4
+ s0 = s0 + s5;
+ s1 = s1 - s6;
+
+ // stage 5
+ x0 = s0 + s3;
+ x1 = s1 + s3;
+ x2 = s2;
+ x3 = s0 + s1;
+
+ // stage 6
+ x3 = x3 - s3;
+
+ output = Av1Math.RoundShift(x0, bit);
+ Unsafe.Add(ref output, 1) = Av1Math.RoundShift(x1, bit);
+ Unsafe.Add(ref output, 2) = Av1Math.RoundShift(x2, bit);
+ Unsafe.Add(ref output, 3) = Av1Math.RoundShift(x3, bit);
+
+ // range_check_buf(6, input, output, 4, stage_range[6]);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Dct4Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Dct4Inverse1dTransformer.cs
new file mode 100644
index 0000000000..ca19e188b5
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Dct4Inverse1dTransformer.cs
@@ -0,0 +1,86 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse;
+
+internal class Av1Dct4Inverse1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 4, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 4, nameof(output));
+ TransformScalar(ref input[0], ref output[0], cosBit, stageRange);
+ }
+
+ ///
+ /// SVT: svt_av1_idct4_new
+ ///
+ private static void TransformScalar(ref int input, ref int output, int cosBit, Span stageRange)
+ {
+ Span cospi = Av1SinusConstants.CosinusPi(cosBit);
+ int stage = 0;
+ Span temp0 = stackalloc int[4];
+ Span temp1 = stackalloc int[4];
+
+ // stage 0;
+
+ // stage 1;
+ stage++;
+ temp0[0] = input;
+ temp0[1] = Unsafe.Add(ref input, 2);
+ temp0[2] = Unsafe.Add(ref input, 1);
+ temp0[3] = Unsafe.Add(ref input, 3);
+
+ // range_check_buf(stage, input, bf1, size, stage_range[stage]);
+
+ // stage 2
+ stage++;
+ temp1[0] = HalfButterfly(cospi[32], temp0[0], cospi[32], temp0[1], cosBit);
+ temp1[1] = HalfButterfly(cospi[32], temp0[0], -cospi[32], temp0[1], cosBit);
+ temp1[2] = HalfButterfly(cospi[48], temp0[2], -cospi[16], temp0[3], cosBit);
+ temp1[3] = HalfButterfly(cospi[16], temp0[2], cospi[48], temp0[3], cosBit);
+
+ // range_check_buf(stage, input, bf1, size, stage_range[stage]);
+
+ // stage 3
+ stage++;
+ Unsafe.Add(ref output, 0) = ClampValue(temp1[0] + temp1[3], stageRange[stage]);
+ Unsafe.Add(ref output, 1) = ClampValue(temp1[1] + temp1[2], stageRange[stage]);
+ Unsafe.Add(ref output, 2) = ClampValue(temp1[1] - temp1[2], stageRange[stage]);
+ Unsafe.Add(ref output, 3) = ClampValue(temp1[0] - temp1[3], stageRange[stage]);
+ }
+
+ internal static int ClampValue(int value, byte bit)
+ {
+ if (bit <= 0)
+ {
+ return value; // Do nothing for invalid clamp bit.
+ }
+
+ long max_value = (1L << (bit - 1)) - 1;
+ long min_value = -(1L << (bit - 1));
+ return (int)Av1Math.Clamp(value, min_value, max_value);
+ }
+
+ internal static int HalfButterfly(int w0, int in0, int w1, int in1, int bit)
+ {
+ long result64 = (long)(w0 * in0) + (w1 * in1);
+ long intermediate = result64 + (1L << (bit - 1));
+
+ // NOTE(david.barker): The value 'result_64' may not necessarily fit
+ // into 32 bits. However, the result of this function is nominally
+ // ROUND_POWER_OF_TWO_64(result_64, bit)
+ // and that is required to fit into stage_range[stage] many bits
+ // (checked by range_check_buf()).
+ //
+ // Here we've unpacked that rounding operation, and it can be shown
+ // that the value of 'intermediate' here *does* fit into 32 bits
+ // for any conformant bitstream.
+ // The upshot is that, if you do all this calculation using
+ // wrapping 32-bit arithmetic instead of (non-wrapping) 64-bit arithmetic,
+ // then you'll still get the correct result.
+ return (int)(intermediate >> bit);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity16Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity16Inverse1dTransformer.cs
new file mode 100644
index 0000000000..14071f4d29
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity16Inverse1dTransformer.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse;
+
+internal class Av1Identity16Inverse1dTransformer : IAv1Forward1dTransformer
+{
+ private const long Sqrt2Times2 = Av1Identity4Inverse1dTransformer.Sqrt2 >> 1;
+
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 16, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 16, nameof(output));
+ TransformScalar(ref input[0], ref output[0]);
+ }
+
+ ///
+ /// SVT: svt_av1_iidentity16_c
+ ///
+ private static void TransformScalar(ref int input, ref int output)
+ {
+ // Normal input should fit into 32-bit. Cast to 64-bit here to avoid
+ // overflow with corrupted/fuzzed input. The same for av1_iidentity/16/64_c.
+ output = Av1Math.RoundShift(Sqrt2Times2 * input, Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 1) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 1), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 2) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 2), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 3) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 3), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 4) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 4), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 5) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 5), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 6) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 6), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 7) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 7), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 8) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 8), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 9) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 9), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 10) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 10), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 11) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 11), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 12) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 12), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 13) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 13), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 14) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 14), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 15) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 15), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity32Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity32Inverse1dTransformer.cs
new file mode 100644
index 0000000000..22ed590ba5
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity32Inverse1dTransformer.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse;
+
+internal class Av1Identity32Inverse1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 32, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 32, nameof(output));
+ ref int inputRef = ref input[0];
+ ref int outputRef = ref output[0];
+ TransformScalar(ref inputRef, ref outputRef);
+ TransformScalar(ref Unsafe.Add(ref inputRef, 8), ref Unsafe.Add(ref outputRef, 8));
+ TransformScalar(ref Unsafe.Add(ref inputRef, 16), ref Unsafe.Add(ref outputRef, 16));
+ TransformScalar(ref Unsafe.Add(ref inputRef, 24), ref Unsafe.Add(ref outputRef, 24));
+ }
+
+ ///
+ /// SVT: svt_av1_iidentity32_c
+ ///
+ private static void TransformScalar(ref int input, ref int output)
+ {
+ output = input << 2;
+ Unsafe.Add(ref output, 1) = Unsafe.Add(ref input, 1) << 2;
+ Unsafe.Add(ref output, 2) = Unsafe.Add(ref input, 2) << 2;
+ Unsafe.Add(ref output, 3) = Unsafe.Add(ref input, 3) << 2;
+ Unsafe.Add(ref output, 4) = Unsafe.Add(ref input, 4) << 2;
+ Unsafe.Add(ref output, 5) = Unsafe.Add(ref input, 5) << 2;
+ Unsafe.Add(ref output, 6) = Unsafe.Add(ref input, 6) << 2;
+ Unsafe.Add(ref output, 7) = Unsafe.Add(ref input, 7) << 2;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity4Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity4Inverse1dTransformer.cs
new file mode 100644
index 0000000000..6547f16ae0
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity4Inverse1dTransformer.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse;
+
+internal class Av1Identity4Inverse1dTransformer : IAv1Forward1dTransformer
+{
+ internal const int Sqrt2Bits = 12;
+
+ // 2^12 * sqrt(2)
+ internal const long Sqrt2 = 5793;
+
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 4, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 4, nameof(output));
+ TransformScalar(ref input[0], ref output[0]);
+ }
+
+ ///
+ /// SVT: svt_av1_iidentity4_c
+ ///
+ private static void TransformScalar(ref int input, ref int output)
+ {
+ // Normal input should fit into 32-bit. Cast to 64-bit here to avoid
+ // overflow with corrupted/fuzzed input. The same for av1_iidentity/16/64_c.
+ output = Av1Math.RoundShift(Sqrt2 * input, Sqrt2Bits);
+ Unsafe.Add(ref output, 1) = Av1Math.RoundShift(Sqrt2 * Unsafe.Add(ref input, 1), Sqrt2Bits);
+ Unsafe.Add(ref output, 2) = Av1Math.RoundShift(Sqrt2 * Unsafe.Add(ref input, 2), Sqrt2Bits);
+ Unsafe.Add(ref output, 3) = Av1Math.RoundShift(Sqrt2 * Unsafe.Add(ref input, 3), Sqrt2Bits);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity64Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity64Inverse1dTransformer.cs
new file mode 100644
index 0000000000..7c75fa2a68
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity64Inverse1dTransformer.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse;
+
+internal class Av1Identity64Inverse1dTransformer : IAv1Forward1dTransformer
+{
+ private const long Sqrt2Times4 = Av1Identity4Inverse1dTransformer.Sqrt2 >> 2;
+
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 64, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 64, nameof(output));
+ ref int inputRef = ref input[0];
+ ref int outputRef = ref output[0];
+ TransformScalar(ref inputRef, ref outputRef);
+ TransformScalar(ref Unsafe.Add(ref inputRef, 8), ref Unsafe.Add(ref outputRef, 8));
+ TransformScalar(ref Unsafe.Add(ref inputRef, 16), ref Unsafe.Add(ref outputRef, 16));
+ TransformScalar(ref Unsafe.Add(ref inputRef, 24), ref Unsafe.Add(ref outputRef, 24));
+ TransformScalar(ref Unsafe.Add(ref inputRef, 32), ref Unsafe.Add(ref outputRef, 32));
+ TransformScalar(ref Unsafe.Add(ref inputRef, 40), ref Unsafe.Add(ref outputRef, 40));
+ TransformScalar(ref Unsafe.Add(ref inputRef, 48), ref Unsafe.Add(ref outputRef, 48));
+ TransformScalar(ref Unsafe.Add(ref inputRef, 56), ref Unsafe.Add(ref outputRef, 56));
+ }
+
+ ///
+ /// SVT: svt_av1_iidentity64_c
+ ///
+ private static void TransformScalar(ref int input, ref int output)
+ {
+ // Normal input should fit into 32-bit. Cast to 64-bit here to avoid
+ // overflow with corrupted/fuzzed input. The same for av1_iidentity/16/64_c.
+ output = Av1Math.RoundShift(Sqrt2Times4 * input, Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 1) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 1), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 2) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 2), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 3) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 3), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 4) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 4), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 5) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 5), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 6) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 6), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ Unsafe.Add(ref output, 7) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 7), Av1Identity4Inverse1dTransformer.Sqrt2Bits);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity8Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity8Inverse1dTransformer.cs
new file mode 100644
index 0000000000..5528f03dd7
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity8Inverse1dTransformer.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse;
+
+internal class Av1Identity8Inverse1dTransformer : IAv1Forward1dTransformer
+{
+ public void Transform(Span input, Span output, int cosBit, Span stageRange)
+ {
+ Guard.MustBeSizedAtLeast(input, 8, nameof(input));
+ Guard.MustBeSizedAtLeast(output, 8, nameof(output));
+ TransformScalar(ref input[0], ref output[0]);
+ }
+
+ ///
+ /// SVT: svt_av1_iidentity8_c
+ ///
+ private static void TransformScalar(ref int input, ref int output)
+ {
+ output = input << 1;
+ Unsafe.Add(ref output, 1) = Unsafe.Add(ref input, 1) << 1;
+ Unsafe.Add(ref output, 2) = Unsafe.Add(ref input, 2) << 1;
+ Unsafe.Add(ref output, 3) = Unsafe.Add(ref input, 3) << 1;
+ Unsafe.Add(ref output, 4) = Unsafe.Add(ref input, 4) << 1;
+ Unsafe.Add(ref output, 5) = Unsafe.Add(ref input, 5) << 1;
+ Unsafe.Add(ref output, 6) = Unsafe.Add(ref input, 6) << 1;
+ Unsafe.Add(ref output, 7) = Unsafe.Add(ref input, 7) << 1;
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs
new file mode 100644
index 0000000000..f25082b4d7
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs
@@ -0,0 +1,196 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
+
+///
+/// SVT: test/InvTxfm1dTest.cc
+/// SVT: test/InvTxfm2dAsmTest.cc
+///
+[Trait("Format", "Avif")]
+public class Av1InverseTransformTests
+{
+ [Fact]
+ public void AccuracyOfDct1dTransformSize4Test()
+ => AssertAccuracy1d(Av1TransformType.DctDct, Av1TransformSize.Size4x4, 1);
+
+ // [Fact]
+ public void AccuracyOfDct1dTransformSize8Test()
+ => AssertAccuracy1d(Av1TransformType.DctDct, Av1TransformSize.Size8x8, 1, 2);
+
+ // [Fact]
+ public void AccuracyOfDct1dTransformSize16Test()
+ => AssertAccuracy1d(Av1TransformType.DctDct, Av1TransformSize.Size16x16, 1, 3);
+
+ // [Fact]
+ public void AccuracyOfDct1dTransformSize32Test()
+ => AssertAccuracy1d(Av1TransformType.DctDct, Av1TransformSize.Size32x32, 1, 4);
+
+ // [Fact]
+ public void AccuracyOfDct1dTransformSize64Test()
+ => AssertAccuracy1d(Av1TransformType.DctDct, Av1TransformSize.Size64x64, 1, 5);
+
+ [Fact]
+ public void AccuracyOfAdst1dTransformSize4Test()
+ => AssertAccuracy1d(Av1TransformType.AdstAdst, Av1TransformSize.Size4x4, 1);
+
+ // [Fact]
+ public void AccuracyOfAdst1dTransformSize8Test()
+ => AssertAccuracy1d(Av1TransformType.AdstAdst, Av1TransformSize.Size8x8, 1, 2);
+
+ // [Fact]
+ public void AccuracyOfAdst1dTransformSize16Test()
+ => AssertAccuracy1d(Av1TransformType.AdstAdst, Av1TransformSize.Size16x16, 1, 3);
+
+ // [Fact]
+ public void AccuracyOfAdst1dTransformSize32Test()
+ => AssertAccuracy1d(Av1TransformType.AdstAdst, Av1TransformSize.Size32x32, 1, 3);
+
+ [Fact]
+ public void AccuracyOfIdentity1dTransformSize4Test()
+ => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size4x4, 1);
+
+ [Fact]
+ public void AccuracyOfIdentity1dTransformSize8Test()
+ => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size8x8, 2);
+
+ [Fact]
+ public void AccuracyOfIdentity1dTransformSize16Test()
+ => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size16x16, 1);
+
+ [Fact]
+ public void AccuracyOfIdentity1dTransformSize32Test()
+ => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size32x32, 4);
+
+ [Fact]
+ public void AccuracyOfIdentity1dTransformSize64Test()
+ => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size64x64, 4);
+
+ [Fact]
+ public void AccuracyOfEchoTransformSize4Test()
+ => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size4x4, 0, new EchoTestTransformer(), new EchoTestTransformer());
+
+ private static void AssertAccuracy1d(
+ Av1TransformType transformType,
+ Av1TransformSize transformSize,
+ int scaleLog2,
+ int allowedError = 1)
+ {
+ Av1Transform2dFlipConfiguration config = new(transformType, transformSize);
+ IAv1Forward1dTransformer forward = GetForwardTransformer(config.TransformFunctionTypeColumn);
+ IAv1Forward1dTransformer inverse = GetInverseTransformer(config.TransformFunctionTypeColumn);
+ AssertAccuracy1d(transformType, transformSize, scaleLog2, forward, inverse, allowedError);
+ }
+
+ private static void AssertAccuracy1d(
+ Av1TransformType transformType,
+ Av1TransformSize transformSize,
+ int scaleLog2,
+ IAv1Forward1dTransformer forwardTransformer,
+ IAv1Forward1dTransformer inverseTransformer,
+ int allowedError = 1)
+ {
+ const int bitDepth = 10;
+ Random rnd = new(0);
+ const int testBlockCount = 100; // Originally set to: 5000
+ Av1Transform2dFlipConfiguration config = new(transformType, transformSize);
+ config.GenerateStageRange(bitDepth);
+ int width = config.TransformSize.GetWidth();
+
+ int[] inputOfTest = new int[width];
+ int[] outputOfTest = new int[width];
+ int[] outputReference = new int[width];
+ for (int ti = 0; ti < testBlockCount; ++ti)
+ {
+ // prepare random test data
+ for (int ni = 0; ni < width; ++ni)
+ {
+ inputOfTest[ni] = (short)rnd.Next((1 << bitDepth) - 1);
+ outputReference[ni] = 0;
+ outputOfTest[ni] = 255;
+ }
+
+ // calculate in forward transform functions
+ forwardTransformer.Transform(
+ inputOfTest,
+ outputReference,
+ config.CosBitColumn,
+ config.StageRangeColumn);
+
+ // calculate in inverse transform functions
+ inverseTransformer.Transform(
+ outputReference,
+ outputOfTest,
+ config.CosBitColumn,
+ config.StageRangeColumn);
+
+ // Assert
+ Assert.True(CompareWithError(inputOfTest, outputOfTest.Select(x => x >> scaleLog2).ToArray(), allowedError), $"Error: {GetMaximumError(inputOfTest, outputOfTest)}");
+ }
+ }
+
+ private static IAv1Forward1dTransformer GetForwardTransformer(Av1TransformFunctionType func) =>
+ func switch
+ {
+ Av1TransformFunctionType.Dct4 => new Av1Dct4Forward1dTransformer(),
+ Av1TransformFunctionType.Dct8 => new Av1Dct8Forward1dTransformer(),
+ Av1TransformFunctionType.Dct16 => new Av1Dct16Forward1dTransformer(),
+ Av1TransformFunctionType.Dct32 => new Av1Dct32Forward1dTransformer(),
+ Av1TransformFunctionType.Dct64 => new Av1Dct64Forward1dTransformer(),
+ Av1TransformFunctionType.Adst4 => new Av1Adst4Forward1dTransformer(),
+ Av1TransformFunctionType.Adst8 => new Av1Adst8Forward1dTransformer(),
+ Av1TransformFunctionType.Adst16 => new Av1Adst16Forward1dTransformer(),
+ Av1TransformFunctionType.Adst32 => new Av1Adst32Forward1dTransformer(),
+ Av1TransformFunctionType.Identity4 => new Av1Identity4Forward1dTransformer(),
+ Av1TransformFunctionType.Identity8 => new Av1Identity8Forward1dTransformer(),
+ Av1TransformFunctionType.Identity16 => new Av1Identity16Forward1dTransformer(),
+ Av1TransformFunctionType.Identity32 => new Av1Identity32Forward1dTransformer(),
+ Av1TransformFunctionType.Identity64 => new Av1Identity64Forward1dTransformer(),
+ Av1TransformFunctionType.Invalid => null,
+ _ => null,
+ };
+
+ private static IAv1Forward1dTransformer GetInverseTransformer(Av1TransformFunctionType func) =>
+ func switch
+ {
+ Av1TransformFunctionType.Dct4 => new Av1Dct4Inverse1dTransformer(),
+ Av1TransformFunctionType.Dct8 => null, // new Av1Dct8Inverse1dTransformer(),
+ Av1TransformFunctionType.Dct16 => null, // new Av1Dct16Inverse1dTransformer(),
+ Av1TransformFunctionType.Dct32 => null, // new Av1Dct32Inverse1dTransformer(),
+ Av1TransformFunctionType.Dct64 => null, // new Av1Dct64Inverse1dTransformer(),
+ Av1TransformFunctionType.Adst4 => new Av1Adst4Inverse1dTransformer(),
+ Av1TransformFunctionType.Adst8 => null, // new Av1Adst8Inverse1dTransformer(),
+ Av1TransformFunctionType.Adst16 => null, // new Av1Adst16Inverse1dTransformer(),
+ Av1TransformFunctionType.Adst32 => null, // new Av1Adst32Inverse1dTransformer(),
+ Av1TransformFunctionType.Identity4 => new Av1Identity4Inverse1dTransformer(),
+ Av1TransformFunctionType.Identity8 => new Av1Identity8Inverse1dTransformer(),
+ Av1TransformFunctionType.Identity16 => new Av1Identity16Inverse1dTransformer(),
+ Av1TransformFunctionType.Identity32 => new Av1Identity32Inverse1dTransformer(),
+ Av1TransformFunctionType.Identity64 => new Av1Identity64Inverse1dTransformer(),
+ Av1TransformFunctionType.Invalid => null,
+ _ => null,
+ };
+
+ private static bool CompareWithError(Span expected, Span actual, int allowedError)
+ {
+ // compare for the result is within accuracy
+ int maximumErrorInTest = GetMaximumError(expected, actual);
+ return maximumErrorInTest <= allowedError;
+ }
+
+ private static int GetMaximumError(Span expected, Span actual)
+ {
+ int maximumErrorInTest = 0;
+ int count = Math.Min(expected.Length, 32);
+ for (int ni = 0; ni < count; ++ni)
+ {
+ maximumErrorInTest = Math.Max(maximumErrorInTest, Math.Abs(actual[ni] - expected[ni]));
+ }
+
+ return maximumErrorInTest;
+ }
+}