From 34e8fc230c67b75de7feb3590ffd50bd4e23fce2 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 23 Oct 2024 22:16:44 +0200 Subject: [PATCH] 2-dimensional inverse transform implementation --- .../Transform/Av1DctDctInverseTransformer.cs | 157 -------- ...ory.cs => Av1ForwardTransformerFactory.cs} | 2 +- .../Av1/Transform/Av1Inverse2dTransformer.cs | 363 ++++++++++++++++++ .../Av1/Transform/Av1InverseTransformMath.cs | 91 ++++- .../Av1/Transform/Av1InverseTransformer.cs | 4 +- .../Transform/Av1InverseTransformerFactory.cs | 38 ++ .../Transform/Av1TransformSizeExtensions.cs | 39 ++ .../Transform/InverseTransformerFactory.cs | 19 - 8 files changed, 533 insertions(+), 180 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs rename src/ImageSharp/Formats/Heif/Av1/Transform/{ForwardTransformerFactory.cs => Av1ForwardTransformerFactory.cs} (98%) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs deleted file mode 100644 index e7363ed42b..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs +++ /dev/null @@ -1,157 +0,0 @@ -// 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/ForwardTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformerFactory.cs similarity index 98% rename from src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformerFactory.cs index 4463f2d225..c8664655d7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformerFactory.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -internal static class ForwardTransformerFactory +internal static class Av1ForwardTransformerFactory { internal static void EstimateTransform( Span residualBuffer, diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs new file mode 100644 index 0000000000..97371b376b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs @@ -0,0 +1,363 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1Inverse2dTransformer +{ + private const int UnitQuantizationShift = 2; + + /// + /// SVT: inv_txfm2d_add_c + /// + internal static void InverseTransform2dAdd( + Span input, + Span outputForRead, + int strideForRead, + Span outputForWrite, + int strideForWrite, + Av1Transform2dFlipConfiguration config, + Span transformFunctionBuffer, + int bitDepth) + { + // 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 transformWidth = config.TransformSize.GetWidth(); + int transformHeight = config.TransformSize.GetHeight(); + + // Take the shift from the larger dimension in the rectangular case. + Span shift = config.Shift; + int rectangleType = config.TransformSize.GetRectangleLogRatio(); + config.GenerateStageRange(bitDepth); + + int cosBitColumn = config.CosBitColumn; + int cosBitRow = config.CosBitRow; + IAv1Forward1dTransformer? functionColumn = Av1InverseTransformerFactory.GetTransformer(config.TransformFunctionTypeColumn); + IAv1Forward1dTransformer? functionRow = Av1InverseTransformerFactory.GetTransformer(config.TransformFunctionTypeRow); + Guard.NotNull(functionColumn); + Guard.NotNull(functionRow); + + // txfm_buf's length is txfm_size_row * txfm_size_col + 2 * MAX(txfm_size_row, txfm_size_col) + // it is used for intermediate data buffering + int bufferOffset = Math.Max(transformHeight, transformWidth); + Guard.MustBeSizedAtLeast(transformFunctionBuffer, (transformHeight * transformWidth) + (2 * bufferOffset), nameof(transformFunctionBuffer)); + Span tempIn = transformFunctionBuffer; + Span tempOut = tempIn.Slice(bufferOffset); + Span buf = tempOut.Slice(bufferOffset); + Span bufPtr = buf; + int c, r; + + // Rows + for (r = 0; r < transformHeight; ++r) + { + if (Math.Abs(rectangleType) == 1) + { + for (c = 0; c < transformWidth; ++c) + { + tempIn[c] = Av1Math.RoundShift((long)input[c] * Av1InverseTransformMath.NewInverseSqrt2, Av1InverseTransformMath.NewSqrt2BitCount); + } + + Av1InverseTransformMath.ClampBuffer(tempIn, transformWidth, (byte)(bitDepth + 8)); + functionRow.Transform(tempIn, bufPtr, cosBitRow, config.StageRangeRow); + } + else + { + for (c = 0; c < transformWidth; ++c) + { + tempIn[c] = input[c]; + } + + Av1InverseTransformMath.ClampBuffer(tempIn, transformWidth, (byte)(bitDepth + 8)); + functionRow.Transform(tempIn, bufPtr, cosBitRow, config.StageRangeRow); + } + + Av1InverseTransformMath.RoundShiftArray(bufPtr, transformWidth, -shift[0]); + input.Slice(transformWidth); + bufPtr.Slice(transformWidth); + } + + // Columns + for (c = 0; c < transformWidth; ++c) + { + if (!config.FlipLeftToRight) + { + for (r = 0; r < transformHeight; ++r) + { + tempIn[r] = buf[(r * transformWidth) + c]; + } + } + else + { + // flip left right + for (r = 0; r < transformHeight; ++r) + { + tempIn[r] = buf[(r * transformWidth) + (transformWidth - c - 1)]; + } + } + + Av1InverseTransformMath.ClampBuffer(tempIn, transformHeight, (byte)Math.Max(bitDepth + 6, 16)); + functionColumn.Transform(tempIn, tempOut, cosBitColumn, config.StageRangeColumn); + Av1InverseTransformMath.RoundShiftArray(tempOut, transformHeight, -shift[1]); + if (!config.FlipUpsideDown) + { + for (r = 0; r < transformHeight; ++r) + { + outputForWrite[(r * strideForWrite) + c] = + Av1InverseTransformMath.ClipPixelAdd(outputForRead[(r * strideForRead) + c], tempOut[r], bitDepth); + } + } + else + { + // flip upside down + for (r = 0; r < transformHeight; ++r) + { + outputForWrite[(r * strideForWrite) + c] = Av1InverseTransformMath.ClipPixelAdd( + outputForRead[(r * strideForRead) + c], tempOut[transformHeight - r - 1], bitDepth); + } + } + } + } + + /// + /// SVT: inv_txfm2d_add_c + /// + internal static void InverseTransform2dAdd( + Span input, + Span outputForRead, + int strideForRead, + Span outputForWrite, + int strideForWrite, + Av1Transform2dFlipConfiguration config, + Span transformFunctionBuffer) + { + const int bitDepth = 8; + + // 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 transformWidth = config.TransformSize.GetWidth(); + int transformHeight = config.TransformSize.GetHeight(); + + // Take the shift from the larger dimension in the rectangular case. + Span shift = config.Shift; + int rectangleType = config.TransformSize.GetRectangleLogRatio(); + config.GenerateStageRange(bitDepth); + + int cosBitColumn = config.CosBitColumn; + int cosBitRow = config.CosBitRow; + IAv1Forward1dTransformer? functionColumn = Av1InverseTransformerFactory.GetTransformer(config.TransformFunctionTypeColumn); + IAv1Forward1dTransformer? functionRow = Av1InverseTransformerFactory.GetTransformer(config.TransformFunctionTypeRow); + Guard.NotNull(functionColumn); + Guard.NotNull(functionRow); + + // txfm_buf's length is txfm_size_row * txfm_size_col + 2 * MAX(txfm_size_row, txfm_size_col) + // it is used for intermediate data buffering + int bufferOffset = Math.Max(transformHeight, transformWidth); + Guard.MustBeSizedAtLeast(transformFunctionBuffer, (transformHeight * transformWidth) + (2 * bufferOffset), nameof(transformFunctionBuffer)); + Span tempIn = transformFunctionBuffer; + Span tempOut = tempIn.Slice(bufferOffset); + Span buf = tempOut.Slice(bufferOffset); + Span bufPtr = buf; + int c, r; + + // Rows + for (r = 0; r < transformHeight; ++r) + { + if (Math.Abs(rectangleType) == 1) + { + for (c = 0; c < transformWidth; ++c) + { + tempIn[c] = Av1Math.RoundShift((long)input[c] * Av1InverseTransformMath.NewInverseSqrt2, Av1InverseTransformMath.NewSqrt2BitCount); + } + + Av1InverseTransformMath.ClampBuffer(tempIn, transformWidth, (byte)(bitDepth + 8)); + functionRow.Transform(tempIn, bufPtr, cosBitRow, config.StageRangeRow); + } + else + { + for (c = 0; c < transformWidth; ++c) + { + tempIn[c] = input[c]; + } + + Av1InverseTransformMath.ClampBuffer(tempIn, transformWidth, (byte)(bitDepth + 8)); + functionRow.Transform(tempIn, bufPtr, cosBitRow, config.StageRangeRow); + } + + Av1InverseTransformMath.RoundShiftArray(bufPtr, transformWidth, -shift[0]); + input.Slice(transformWidth); + bufPtr.Slice(transformWidth); + } + + // Columns + for (c = 0; c < transformWidth; ++c) + { + if (!config.FlipLeftToRight) + { + for (r = 0; r < transformHeight; ++r) + { + tempIn[r] = buf[(r * transformWidth) + c]; + } + } + else + { + // flip left right + for (r = 0; r < transformHeight; ++r) + { + tempIn[r] = buf[(r * transformWidth) + (transformWidth - c - 1)]; + } + } + + Av1InverseTransformMath.ClampBuffer(tempIn, transformHeight, (byte)Math.Max(bitDepth + 6, 16)); + functionColumn.Transform(tempIn, tempOut, cosBitColumn, config.StageRangeColumn); + Av1InverseTransformMath.RoundShiftArray(tempOut, transformHeight, -shift[1]); + if (!config.FlipUpsideDown) + { + for (r = 0; r < transformHeight; ++r) + { + outputForWrite[(r * strideForWrite) + c] = + Av1InverseTransformMath.ClipPixelAdd(outputForRead[(r * strideForRead) + c], tempOut[r]); + } + } + else + { + // flip upside down + for (r = 0; r < transformHeight; ++r) + { + outputForWrite[(r * strideForWrite) + c] = Av1InverseTransformMath.ClipPixelAdd( + outputForRead[(r * strideForRead) + c], tempOut[transformHeight - r - 1]); + } + } + } + } + + /// + /// 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 = Av1InverseTransformMath.ClipPixelAdd(destForRead, a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), b1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 2) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 2), c1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 3) = Av1InverseTransformMath.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 = Av1InverseTransformMath.ClipPixelAdd(destForRead, a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 2) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 2), a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 3) = Av1InverseTransformMath.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); + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs index 52ee9c26c6..c8c7a04d3e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs @@ -1,10 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +using System; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal static class Av1InverseTransformMath { + public const int NewInverseSqrt2 = 2896; + public const int NewSqrt2BitCount = 12; + public static readonly int[,] AcQLookup = new int[3, 256] { { @@ -145,4 +150,88 @@ internal static class Av1InverseTransformMath quantization = m - (1 << 16); shift = 1 << (16 - l); } + + public static byte ClipPixelAdd(byte dest, long trans) + { + trans = CheckRange(trans, 8); + return (byte)ClipPixelHighBitDepth(dest + trans, 8); + } + + public static ushort ClipPixelAdd(ushort dest, long trans, int bitDepth) + { + trans = CheckRange(trans, bitDepth); + return ClipPixelHighBitDepth(dest + trans, bitDepth); + } + + private static ushort ClipPixelHighBitDepth(long val, int bd) + { + switch (bd) + { + case 8: + default: + return (ushort)Av1Math.Clamp(val, 0, 255); + case 10: + return (ushort)Av1Math.Clamp(val, 0, 1023); + case 12: + return (ushort)Av1Math.Clamp(val, 0, 4095); + } + } + + public static void RoundShiftArray(Span arr, int size, int bit) + { + int i; + if (bit == 0) + { + return; + } + else + { + if (bit > 0) + { + for (i = 0; i < size; i++) + { + arr[i] = Av1Math.RoundShift(arr[i], bit); + } + } + else + { + for (i = 0; i < size; i++) + { + arr[i] = arr[i] * (1 << (-bit)); + } + } + } + } + + internal static void ClampBuffer(Span buffer, int size, byte bit) + { + for (int i = 0; i < size; i++) + { + buffer[i] = ClampValue(buffer[i], bit); + } + } + + private 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); + } + + private static long CheckRange(long input, int bd) + { + // AV1 TX case + // - 8 bit: signed 16 bit integer + // - 10 bit: signed 18 bit integer + // - 12 bit: signed 20 bit integer + // - max quantization error = 1828 << (bd - 8) + int int_max = (1 << (7 + bd)) - 1 + (914 << (bd - 7)); + int int_min = -int_max - 1; + return Av1Math.Clamp(input, int_min, int_max); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs index edb04aeae6..ed05dc9e6d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs @@ -28,8 +28,8 @@ internal class Av1InverseTransformer transformFunctionParameters.EndOfBuffer = GetMaxEndOfBuffer(transformSize); } - InverseTransformerFactory.InverseTransformAdd( - ref coefficientsBuffer[0], reconstructionBufferRead, reconstructionReadStride, reconstructionBufferWrite, reconstructionWriteStride, transformFunctionParameters); + Av1InverseTransformerFactory.InverseTransformAdd( + coefficientsBuffer, reconstructionBufferRead, reconstructionReadStride, reconstructionBufferWrite, reconstructionWriteStride, transformFunctionParameters); } /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs new file mode 100644 index 0000000000..1f73edae1b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal static class Av1InverseTransformerFactory +{ + public static unsafe void InverseTransformAdd(Span coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) + { + Guard.MustBeLessThanOrEqualTo(transformFunctionParameters.BitDepth, 8, nameof(transformFunctionParameters)); + Guard.IsFalse(transformFunctionParameters.Is16BitPipeline, nameof(transformFunctionParameters), "Calling 8-bit pipeline while 16-bit is requested."); + int width = transformFunctionParameters.TransformSize.GetWidth(); + int height = transformFunctionParameters.TransformSize.GetHeight(); + Span buffer = new int[(width * height) + (2 * Math.Max(width, height))]; + Av1Transform2dFlipConfiguration config = new(transformFunctionParameters.TransformType, transformFunctionParameters.TransformSize); + Av1Inverse2dTransformer.InverseTransform2dAdd(coefficients, readBuffer, readStride, writeBuffer, writeStride, config, buffer); + } + + public static unsafe void InverseTransformAdd(Span coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) + { + Guard.IsTrue(transformFunctionParameters.Is16BitPipeline, nameof(transformFunctionParameters), "Calling 16-bit pipeline while 8-bit is requested."); + int width = transformFunctionParameters.TransformSize.GetWidth(); + int height = transformFunctionParameters.TransformSize.GetHeight(); + Span buffer = new int[(width * height) + (2 * Math.Max(width, height))]; + Av1Transform2dFlipConfiguration config = new(transformFunctionParameters.TransformType, transformFunctionParameters.TransformSize); + Av1Inverse2dTransformer.InverseTransform2dAdd(coefficients, readBuffer, readStride, writeBuffer, writeStride, config, buffer, transformFunctionParameters.BitDepth); + } + + internal static IAv1Forward1dTransformer? GetTransformer(Av1TransformFunctionType type) => type switch + { + Av1TransformFunctionType.Dct4 => new Av1Dct4Inverse1dTransformer(), + Av1TransformFunctionType.Adst4 => new Av1Adst4Inverse1dTransformer(), + Av1TransformFunctionType.Identity4 => new Av1Identity4Inverse1dTransformer(), + _ => null + }; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs index 3766c5a6d8..3341d2fc80 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs @@ -172,4 +172,43 @@ internal static class Av1TransformSizeExtensions public static int GetBlockWidthLog2(this Av1TransformSize size) => BlockWidthLog2[(int)GetAdjusted(size)]; public static int GetBlockHeightLog2(this Av1TransformSize size) => BlockHeightLog2[(int)GetAdjusted(size)]; + + public static int GetRectangleLogRatio(this Av1TransformSize size) + { + int col = GetWidth(size); + int row = GetHeight(size); + if (col == row) + { + return 0; + } + + if (col > row) + { + if (col == row * 2) + { + return 1; + } + + if (col == row * 4) + { + return 2; + } + + throw new InvalidImageContentException("Unsupported transform size"); + } + else + { + if (row == col * 2) + { + return -1; + } + + if (row == col * 4) + { + return -2; + } + + throw new InvalidImageContentException("Unsupported transform size"); + } + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs deleted file mode 100644 index feb7dbe76c..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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); - } - } -}