Browse Source

2-dimensional inverse transform implementation

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
34e8fc230c
  1. 157
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs
  2. 2
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformerFactory.cs
  3. 363
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs
  4. 91
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs
  5. 4
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs
  6. 38
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs
  7. 39
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs
  8. 19
      src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs

157
src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs

@ -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<byte> readBuffer, int readStride, Span<byte> 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");
}
}
/// <summary>
/// SVT: highbd_iwht4x4_add
/// </summary>
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);
}
}
/// <summary>
/// SVT: svt_av1_highbd_iwht4x4_16_add_c
/// </summary>
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<ushort> 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<byte, ushort>(ref destinationForRead);
ref ushort destForWrite = ref Unsafe.As<byte, ushort>(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);
}
}
/// <summary>
/// SVT: svt_av1_highbd_iwht4x4_1_add_c
/// </summary>
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<int> 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<byte, ushort>(ref destinationForRead);
ref ushort destForWrite = ref Unsafe.As<byte, ushort>(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),
};
}

2
src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs → 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; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
internal static class ForwardTransformerFactory internal static class Av1ForwardTransformerFactory
{ {
internal static void EstimateTransform( internal static void EstimateTransform(
Span<short> residualBuffer, Span<short> residualBuffer,

363
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;
/// <summary>
/// SVT: inv_txfm2d_add_c
/// </summary>
internal static void InverseTransform2dAdd(
Span<int> input,
Span<ushort> outputForRead,
int strideForRead,
Span<ushort> outputForWrite,
int strideForWrite,
Av1Transform2dFlipConfiguration config,
Span<int> 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<int> 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<int> tempIn = transformFunctionBuffer;
Span<int> tempOut = tempIn.Slice(bufferOffset);
Span<int> buf = tempOut.Slice(bufferOffset);
Span<int> 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);
}
}
}
}
/// <summary>
/// SVT: inv_txfm2d_add_c
/// </summary>
internal static void InverseTransform2dAdd(
Span<int> input,
Span<byte> outputForRead,
int strideForRead,
Span<byte> outputForWrite,
int strideForWrite,
Av1Transform2dFlipConfiguration config,
Span<int> 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<int> 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<int> tempIn = transformFunctionBuffer;
Span<int> tempOut = tempIn.Slice(bufferOffset);
Span<int> buf = tempOut.Slice(bufferOffset);
Span<int> 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]);
}
}
}
}
/// <summary>
/// SVT: highbd_iwht4x4_add
/// </summary>
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);
}
}
/// <summary>
/// SVT: svt_av1_highbd_iwht4x4_16_add_c
/// </summary>
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<ushort> 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<byte, ushort>(ref destinationForRead);
ref ushort destForWrite = ref Unsafe.As<byte, ushort>(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);
}
}
/// <summary>
/// SVT: svt_av1_highbd_iwht4x4_1_add_c
/// </summary>
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<int> 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<byte, ushort>(ref destinationForRead);
ref ushort destForWrite = ref Unsafe.As<byte, ushort>(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);
}
}
}

91
src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs

@ -1,10 +1,15 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // 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 internal static class Av1InverseTransformMath
{ {
public const int NewInverseSqrt2 = 2896;
public const int NewSqrt2BitCount = 12;
public static readonly int[,] AcQLookup = new int[3, 256] public static readonly int[,] AcQLookup = new int[3, 256]
{ {
{ {
@ -145,4 +150,88 @@ internal static class Av1InverseTransformMath
quantization = m - (1 << 16); quantization = m - (1 << 16);
shift = 1 << (16 - l); 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<int> 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<int> 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);
}
} }

4
src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs

@ -28,8 +28,8 @@ internal class Av1InverseTransformer
transformFunctionParameters.EndOfBuffer = GetMaxEndOfBuffer(transformSize); transformFunctionParameters.EndOfBuffer = GetMaxEndOfBuffer(transformSize);
} }
InverseTransformerFactory.InverseTransformAdd( Av1InverseTransformerFactory.InverseTransformAdd(
ref coefficientsBuffer[0], reconstructionBufferRead, reconstructionReadStride, reconstructionBufferWrite, reconstructionWriteStride, transformFunctionParameters); coefficientsBuffer, reconstructionBufferRead, reconstructionReadStride, reconstructionBufferWrite, reconstructionWriteStride, transformFunctionParameters);
} }
/// <summary> /// <summary>

38
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<int> coefficients, Span<byte> readBuffer, int readStride, Span<byte> 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<int> 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<int> coefficients, Span<ushort> readBuffer, int readStride, Span<ushort> 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<int> 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
};
}

39
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 GetBlockWidthLog2(this Av1TransformSize size) => BlockWidthLog2[(int)GetAdjusted(size)];
public static int GetBlockHeightLog2(this Av1TransformSize size) => BlockHeightLog2[(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");
}
}
} }

19
src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs

@ -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<byte> readBuffer, int readStride, Span<byte> 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);
}
}
}
Loading…
Cancel
Save