From 796ea84006641cd5ccd3a4e38afb4ef3d601098d Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 22 Oct 2024 21:42:10 +0200 Subject: [PATCH] Implementation of some inverse transformers --- .../Formats/Heif/Av1/Av1Constants.cs | 5 + .../Av1Transform2dFlipConfiguration.cs | 1 + .../Inverse/Av1Adst4Inverse1dTransformer.cs | 78 +++++++ .../Inverse/Av1Dct4Inverse1dTransformer.cs | 86 ++++++++ .../Av1Identity16Inverse1dTransformer.cs | 43 ++++ .../Av1Identity32Inverse1dTransformer.cs | 36 ++++ .../Av1Identity4Inverse1dTransformer.cs | 34 +++ .../Av1Identity64Inverse1dTransformer.cs | 44 ++++ .../Av1Identity8Inverse1dTransformer.cs | 31 +++ .../Heif/Av1/Av1InverseTransformTests.cs | 196 ++++++++++++++++++ 10 files changed, 554 insertions(+) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Adst4Inverse1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Dct4Inverse1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity16Inverse1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity32Inverse1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity4Inverse1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity64Inverse1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity8Inverse1dTransformer.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs 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; + } +}