diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
index e899a7dfcb..d5187475d8 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
@@ -202,4 +202,8 @@ internal static class Av1Math
internal static void SetBit(ref int endOfBlockExtra, int n)
=> endOfBlockExtra |= 1 << n;
+
+ internal static int AbsoluteDifference(int a, int b) => (a > b) ? a - b : b - a;
+
+ internal static int DivideRound(int value, int bitCount) => (value + (1 << (bitCount - 1))) >> bitCount;
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
index 4cf648f0da..6965f31a6f 100644
--- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
@@ -243,6 +243,10 @@ internal class ObuReader
}
}
+ ///
+ /// 5.9.9. Compute image size function.
+ ///
+ /// SVT: compute_image_size
private void ComputeImageSize(ObuSequenceHeader sequenceHeader)
{
ObuFrameHeader frameHeader = this.FrameHeader!;
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs
index f91735fc7d..c7e9d2a345 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs
@@ -8,19 +8,19 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1DcPredictor : IAv1Predictor
{
- private readonly uint blockWidth;
- private readonly uint blockHeight;
+ private readonly nuint blockWidth;
+ private readonly nuint blockHeight;
public Av1DcPredictor(Size blockSize)
{
- this.blockWidth = (uint)blockSize.Width;
- this.blockHeight = (uint)blockSize.Height;
+ this.blockWidth = (nuint)blockSize.Width;
+ this.blockHeight = (nuint)blockSize.Height;
}
public Av1DcPredictor(Av1TransformSize transformSize)
{
- this.blockWidth = (uint)transformSize.GetWidth();
- this.blockHeight = (uint)transformSize.GetHeight();
+ this.blockWidth = (nuint)transformSize.GetWidth();
+ this.blockHeight = (nuint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left)
@@ -36,21 +36,22 @@ internal class Av1DcPredictor : IAv1Predictor
ref byte leftRef = ref left[0];
ref byte aboveRef = ref above[0];
ref byte destinationRef = ref destination[0];
- uint count = this.blockWidth + this.blockHeight;
- for (uint i = 0; i < this.blockWidth; i++)
+ uint count = (uint)(this.blockWidth + this.blockHeight);
+ uint width = (uint)this.blockWidth;
+ for (nuint i = 0; i < this.blockWidth; i++)
{
sum += Unsafe.Add(ref aboveRef, i);
}
- for (uint i = 0; i < this.blockHeight; i++)
+ for (nuint i = 0; i < this.blockHeight; i++)
{
sum += Unsafe.Add(ref leftRef, i);
}
byte expectedDc = (byte)((sum + (count >> 1)) / count);
- for (uint r = 0; r < this.blockHeight; r++)
+ for (nuint r = 0; r < this.blockHeight; r++)
{
- Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth);
+ Unsafe.InitBlock(ref destinationRef, expectedDc, width);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone1Predictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone1Predictor.cs
new file mode 100644
index 0000000000..86212dd09e
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone1Predictor.cs
@@ -0,0 +1,79 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1DirectionalZone1Predictor
+{
+ private readonly nuint blockWidth;
+ private readonly nuint blockHeight;
+
+ public Av1DirectionalZone1Predictor(Size blockSize)
+ {
+ this.blockWidth = (nuint)blockSize.Width;
+ this.blockHeight = (nuint)blockSize.Height;
+ }
+
+ public Av1DirectionalZone1Predictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (nuint)transformSize.GetWidth();
+ this.blockHeight = (nuint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, bool upsampleAbove, int dx)
+ => new Av1DirectionalZone1Predictor(transformSize).PredictScalar(destination, stride, above, upsampleAbove, dx);
+
+ ///
+ /// SVT: svt_av1_dr_prediction_z1_c
+ ///
+ public void PredictScalar(Span destination, nuint stride, Span above, bool upsample, int dx)
+ {
+ Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
+ Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
+ int upsampleAbove = upsample ? 1 : 0;
+ ref byte aboveRef = ref above[0];
+ ref byte destinationRef = ref destination[0];
+ int maxBasisX = (((int)this.blockWidth + (int)this.blockHeight) - 1) << upsampleAbove;
+ int fractionBitCount = 6 - upsampleAbove;
+ int basisIncrement = 1 << upsampleAbove;
+ int x = dx;
+ for (nuint r = 0; r < this.blockHeight; ++r)
+ {
+ int basis = x >> fractionBitCount, shift = ((x << upsampleAbove) & 0x3F) >> 1;
+
+ if (basis >= maxBasisX)
+ {
+ for (nuint i = r; i < this.blockHeight; ++i)
+ {
+ Unsafe.InitBlock(ref destinationRef, Unsafe.Add(ref aboveRef, maxBasisX), (uint)this.blockWidth);
+ destinationRef = ref Unsafe.Add(ref destinationRef, stride);
+ }
+
+ return;
+ }
+
+ for (nuint c = 0; c < this.blockWidth; ++c)
+ {
+ if (basis < maxBasisX)
+ {
+ int val;
+ val = (Unsafe.Add(ref aboveRef, basis) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basis + 1) * shift);
+ val = Av1Math.RoundPowerOf2(val, 5);
+ Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.Clamp(val, 0, 255);
+ }
+ else
+ {
+ Unsafe.Add(ref destinationRef, c) = Unsafe.Add(ref aboveRef, maxBasisX);
+ }
+
+ basis += basisIncrement;
+ }
+
+ x += dx;
+ destinationRef = ref Unsafe.Add(ref destinationRef, stride);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone2Predictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone2Predictor.cs
new file mode 100644
index 0000000000..1daec749cb
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone2Predictor.cs
@@ -0,0 +1,79 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1DirectionalZone2Predictor
+{
+ private readonly nuint blockWidth;
+ private readonly nuint blockHeight;
+
+ public Av1DirectionalZone2Predictor(Size blockSize)
+ {
+ this.blockWidth = (nuint)blockSize.Width;
+ this.blockHeight = (nuint)blockSize.Height;
+ }
+
+ public Av1DirectionalZone2Predictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (nuint)transformSize.GetWidth();
+ this.blockHeight = (nuint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left, bool upsampleAbove, bool upsampleLeft, int dx, int dy)
+ => new Av1DirectionalZone2Predictor(transformSize).PredictScalar(destination, stride, above, left, upsampleAbove, upsampleAbove, dx, dy);
+
+ ///
+ /// SVT: svt_av1_dr_prediction_z1_c
+ ///
+ public void PredictScalar(Span destination, nuint stride, Span above, Span left, bool doUpsampleAbove, bool doUpsampleLeft, int dx, int dy)
+ {
+ Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
+ Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
+ Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
+ Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
+ int upsampleAbove = doUpsampleAbove ? 1 : 0;
+ int upsampleLeft = doUpsampleLeft ? 1 : 0;
+ ref byte aboveRef = ref above[0];
+ ref byte leftRef = ref left[0];
+ ref byte destinationRef = ref destination[0];
+ int minBasisX = -(1 << upsampleAbove);
+ int fractionBitCountX = 6 - upsampleAbove;
+ int fractionBitCountY = 6 - upsampleLeft;
+ int basisIncrementX = 1 << upsampleAbove;
+ int x = -dx;
+ for (nuint r = 0; r < this.blockHeight; ++r)
+ {
+ int val;
+ int base1 = x >> fractionBitCountX;
+ int y = ((int)r << 6) - dy;
+ for (nuint c = 0; c < this.blockWidth; ++c, base1 += basisIncrementX, y -= dy)
+ {
+ if (base1 >= minBasisX)
+ {
+ int shift1 = ((x * (1 << upsampleAbove)) & 0x3F) >> 1;
+ val = (Unsafe.Add(ref aboveRef, base1) * (32 - shift1)) + (Unsafe.Add(ref aboveRef, base1 + 1) * shift1);
+ val = Av1Math.RoundPowerOf2(val, 5);
+ }
+ else
+ {
+ int base2 = y >> fractionBitCountY;
+ Guard.MustBeGreaterThanOrEqualTo(base2, -(1 << upsampleLeft), nameof(base2));
+ int shift2 = ((y * (1 << upsampleLeft)) & 0x3F) >> 1;
+ val = (Unsafe.Add(ref leftRef, base2) * (32 - shift2)) + (Unsafe.Add(ref leftRef, base2 + 1) * shift2);
+ val = Av1Math.RoundPowerOf2(val, 5);
+ }
+
+ Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.Clamp(val, 0, 255);
+ }
+
+ x -= dx;
+ destinationRef = ref Unsafe.Add(ref destinationRef, stride);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone3Predictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone3Predictor.cs
new file mode 100644
index 0000000000..71fde329ce
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone3Predictor.cs
@@ -0,0 +1,78 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1DirectionalZone3Predictor
+{
+ private readonly nuint blockWidth;
+ private readonly nuint blockHeight;
+
+ public Av1DirectionalZone3Predictor(Size blockSize)
+ {
+ this.blockWidth = (nuint)blockSize.Width;
+ this.blockHeight = (nuint)blockSize.Height;
+ }
+
+ public Av1DirectionalZone3Predictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (nuint)transformSize.GetWidth();
+ this.blockHeight = (nuint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span left, bool upsampleAbove, int dx, int dy)
+ => new Av1DirectionalZone3Predictor(transformSize).PredictScalar(destination, stride, left, upsampleAbove, dx, dy);
+
+ ///
+ /// SVT: svt_av1_dr_prediction_z3_c
+ ///
+ public void PredictScalar(Span destination, nuint stride, Span left, bool upsample, int dx, int dy)
+ {
+ Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
+ Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
+ Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
+ int upsampleLeft = upsample ? 1 : 0;
+ ref byte leftRef = ref left[0];
+ ref byte destinationRef = ref destination[0];
+ Guard.IsTrue(dx == 1, nameof(dx), "Dx expected to be always equal to 1 for directional Zone 3 prediction.");
+ Guard.MustBeGreaterThan(dy, 0, nameof(dy));
+
+ int maxBasisY = ((int)this.blockWidth + (int)this.blockHeight - 1) << upsampleLeft;
+ int fractionBitCount = 6 - upsampleLeft;
+ int basisIncrement = 1 << upsampleLeft;
+ int y = dy;
+ for (nuint c = 0; c < this.blockWidth; ++c)
+ {
+ int basis = y >> fractionBitCount;
+ int shift = ((y << upsampleLeft) & 0x3F) >> 1;
+
+ for (nuint r = 0; r < this.blockHeight; ++r)
+ {
+ if (basis < maxBasisY)
+ {
+ int val;
+ val = (Unsafe.Add(ref leftRef, basis) * (32 - shift)) + (Unsafe.Add(ref leftRef, basis + 1) * shift);
+ val = Av1Math.RoundPowerOf2(val, 5);
+ Unsafe.Add(ref destinationRef, (r * stride) + c) = (byte)Av1Math.Clamp(val, 0, 255);
+ }
+ else
+ {
+ for (; r < this.blockHeight; ++r)
+ {
+ Unsafe.Add(ref destinationRef, (r * stride) + c) = left[maxBasisY];
+ }
+
+ break;
+ }
+
+ basis += basisIncrement;
+ }
+
+ y += dy;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HorizontalPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HorizontalPredictor.cs
new file mode 100644
index 0000000000..75f96f22b1
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HorizontalPredictor.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1HorizontalPredictor : IAv1Predictor
+{
+ private readonly nuint blockWidth;
+ private readonly nuint blockHeight;
+
+ public Av1HorizontalPredictor(Size blockSize)
+ {
+ this.blockWidth = (nuint)blockSize.Width;
+ this.blockHeight = (nuint)blockSize.Height;
+ }
+
+ public Av1HorizontalPredictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (nuint)transformSize.GetWidth();
+ this.blockHeight = (nuint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left)
+ => new Av1HorizontalPredictor(transformSize).PredictScalar(destination, stride, above, left);
+
+ ///
+ /// SVT: highbd_h_predictor
+ ///
+ public void PredictScalar(Span destination, nuint stride, Span above, Span left)
+ {
+ Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
+ Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
+ Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
+ Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
+ ref byte leftRef = ref left[0];
+ ref byte destinationRef = ref destination[0];
+ uint width = (uint)this.blockWidth;
+ for (nuint r = 0; r < this.blockHeight; ++r)
+ {
+ Unsafe.InitBlock(ref destinationRef, Unsafe.Add(ref leftRef, r), width);
+ destinationRef = ref Unsafe.Add(ref destinationRef, stride);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PaethPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PaethPredictor.cs
new file mode 100644
index 0000000000..334868d25b
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PaethPredictor.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1PaethPredictor : IAv1Predictor
+{
+ private readonly uint blockWidth;
+ private readonly uint blockHeight;
+
+ public Av1PaethPredictor(Size blockSize)
+ {
+ this.blockWidth = (uint)blockSize.Width;
+ this.blockHeight = (uint)blockSize.Height;
+ }
+
+ public Av1PaethPredictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (uint)transformSize.GetWidth();
+ this.blockHeight = (uint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left)
+ => new Av1DcPredictor(transformSize).PredictScalar(destination, stride, above, left);
+
+ public void PredictScalar(Span destination, nuint stride, Span above, Span left)
+ {
+ Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
+ Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
+ Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
+ Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
+ ref byte leftRef = ref left[0];
+ ref byte aboveRef = ref above[0];
+ int yTopLeft = above[-1];
+ ref byte destinationRef = ref destination[0];
+ for (nuint r = 0; r < this.blockHeight; r++)
+ {
+ for (nuint c = 0; c < this.blockWidth; c++)
+ {
+ destinationRef = PredictSingle(Unsafe.Add(ref leftRef, r), Unsafe.Add(ref aboveRef, c), yTopLeft);
+ destinationRef = ref Unsafe.Add(ref destinationRef, stride);
+ }
+ }
+ }
+
+ private static byte PredictSingle(byte left, byte top, int topLeft)
+ {
+ int basis = top + left - topLeft;
+ int pLeft = Av1Math.AbsoluteDifference(basis, left);
+ int pTop = Av1Math.AbsoluteDifference(basis, top);
+ int pTopLeft = Av1Math.AbsoluteDifference(basis, topLeft);
+
+ // Return nearest to base of left, top and top_left.
+ return (byte)((pLeft <= pTop && pLeft <= pTopLeft) ? left : (pTop <= pTopLeft) ? top : topLeft);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
index 483598aa2a..0e5c4d4d49 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
@@ -868,18 +868,24 @@ internal class Av1PredictionDecoder
}
}
+ ///
+ /// SVT: svt_aom_use_intra_edge_upsample
+ ///
private static bool UseIntraEdgeUpsample(int width, int height, int delta, bool type)
{
int d = Math.Abs(delta);
- int widthHeight = width + height;
if (d is <= 0 or >= 40)
{
return false;
}
+ int widthHeight = width + height;
return type ? (widthHeight <= 8) : (widthHeight <= 16);
}
+ ///
+ /// SVT: svt_av1_filter_intra_edge_c
+ ///
private static void FilterIntraEdge(ref byte buffer, int count, int strength)
{
// TODO: Consider creating SIMD version
@@ -911,6 +917,9 @@ internal class Av1PredictionDecoder
}
}
+ ///
+ /// SVT: svt_aom_intra_edge_filter_strength
+ ///
private static int IntraEdgeFilterStrength(int width, int height, int delta, bool filterType)
{
int d = Math.Abs(delta);
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs
index 9cddf0de38..f1623a26df 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs
@@ -8,6 +8,41 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1PredictorFactory
{
+ private static readonly int[] DirectionalIntraDerivative = [
+
+ // More evenly spread out angles and limited to 10-bit
+ // Values that are 0 will never be used
+ // Approx angle
+ 0, 0, 0, // 0
+ 1023, 0, 0, // 3, ...
+ 547, 0, 0, // 6, ...
+ 372, 0, 0, 0, 0, // 9, ...
+ 273, 0, 0, // 14, ...
+ 215, 0, 0, // 17, ...
+ 178, 0, 0, // 20, ...
+ 151, 0, 0, // 23, ... (113 & 203 are base angles)
+ 132, 0, 0, // 26, ...
+ 116, 0, 0, // 29, ...
+ 102, 0, 0, 0, // 32, ...
+ 90, 0, 0, // 36, ...
+ 80, 0, 0, // 39, ...
+ 71, 0, 0, // 42, ...
+ 64, 0, 0, // 45, ... (45 & 135 are base angles)
+ 57, 0, 0, // 48, ...
+ 51, 0, 0, // 51, ...
+ 45, 0, 0, 0, // 54, ...
+ 40, 0, 0, // 58, ...
+ 35, 0, 0, // 61, ...
+ 31, 0, 0, // 64, ...
+ 27, 0, 0, // 67, ... (67 & 157 are base angles)
+ 23, 0, 0, // 70, ...
+ 19, 0, 0, // 73, ...
+ 15, 0, 0, 0, 0, // 76, ...
+ 11, 0, 0, // 81, ...
+ 7, 0, 0, // 84, ...
+ 3, 0, 0, // 87, ...
+ ];
+
internal static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, Span destination, nuint destinationStride, Span aboveRow, Span leftColumn)
{
if (hasLeft)
@@ -34,9 +69,105 @@ internal class Av1PredictorFactory
}
}
- internal static void DirectionalPredictor(Span destination, nuint destinationStride, Av1TransformSize transformSize, Span aboveRow, Span leftColumn, bool upsampleAbove, bool upsampleLeft, int angle) => throw new NotImplementedException();
+ ///
+ /// SVT: svt_aom_highbd_dr_predictor
+ ///
+ internal static void DirectionalPredictor(Span destination, nuint stride, Av1TransformSize transformSize, Span aboveRow, Span leftColumn, bool upsampleAbove, bool upsampleLeft, int angle)
+ {
+ int dx = GetDeltaX(angle);
+ int dy = GetDeltaY(angle);
+ int bw = transformSize.GetWidth();
+ int bh = transformSize.GetHeight();
+ Guard.MustBeBetweenOrEqualTo(angle, 1, 269, nameof(angle));
+
+ if (angle is > 0 and < 90)
+ {
+ Av1DirectionalZone1Predictor.PredictScalar(transformSize, destination, stride, aboveRow, upsampleAbove, dx);
+ }
+ else if (angle is > 90 and < 180)
+ {
+ Av1DirectionalZone2Predictor.PredictScalar(transformSize, destination, stride, aboveRow, leftColumn, upsampleAbove, upsampleLeft, dx, dy);
+ }
+ else if (angle is > 180 and < 270)
+ {
+ Av1DirectionalZone3Predictor.PredictScalar(transformSize, destination, stride, leftColumn, upsampleLeft, dx, dy);
+ }
+ else if (angle == 90)
+ {
+ Av1VerticalPredictor.PredictScalar(transformSize, destination, stride, aboveRow, leftColumn);
+ }
+ else if (angle == 180)
+ {
+ Av1HorizontalPredictor.PredictScalar(transformSize, destination, stride, aboveRow, leftColumn);
+ }
+ }
internal static void FilterIntraPredictor(Span destination, nuint destinationStride, Av1TransformSize transformSize, Span aboveRow, Span leftColumn, Av1FilterIntraMode filterIntraMode) => throw new NotImplementedException();
- internal static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, Span destination, nuint destinationStride, Span aboveRow, Span leftColumn) => throw new NotImplementedException();
+ internal static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, Span destination, nuint destinationStride, Span aboveRow, Span leftColumn)
+ {
+ switch (mode)
+ {
+ case Av1PredictionMode.Horizontal:
+ Av1HorizontalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
+ break;
+ case Av1PredictionMode.Vertical:
+ Av1VerticalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
+ break;
+ case Av1PredictionMode.Paeth:
+ Av1PaethPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
+ break;
+ case Av1PredictionMode.Smooth:
+ Av1SmoothPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
+ break;
+ case Av1PredictionMode.SmoothHorizontal:
+ Av1SmoothHorizontalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
+ break;
+ case Av1PredictionMode.SmoothVertical:
+ Av1SmoothVerticalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
+ break;
+ }
+ }
+
+ // Get the shift (up-scaled by 256) in Y w.r.t a unit change in X.
+ // If angle > 0 && angle < 90, dy = 1;
+ // If angle > 90 && angle < 180, dy = (int32_t)(256 * t);
+ // If angle > 180 && angle < 270, dy = -((int32_t)(256 * t));
+ private static int GetDeltaY(int angle)
+ {
+ if (angle is > 90 and < 180)
+ {
+ return DirectionalIntraDerivative[angle - 90];
+ }
+ else if (angle is > 180 and < 270)
+ {
+ return DirectionalIntraDerivative[270 - angle];
+ }
+ else
+ {
+ // In this case, we are not really going to use dy. We may return any value.
+ return 1;
+ }
+ }
+
+ // Get the shift (up-scaled by 256) in X w.r.t a unit change in Y.
+ // If angle > 0 && angle < 90, dx = -((int32_t)(256 / t));
+ // If angle > 90 && angle < 180, dx = (int32_t)(256 / t);
+ // If angle > 180 && angle < 270, dx = 1;
+ private static int GetDeltaX(int angle)
+ {
+ if (angle is > 0 and < 90)
+ {
+ return DirectionalIntraDerivative[angle];
+ }
+ else if (angle is > 90 and < 180)
+ {
+ return DirectionalIntraDerivative[180 - angle];
+ }
+ else
+ {
+ // In this case, we are not really going to use dx. We may return any value.
+ return 1;
+ }
+ }
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothHorizontalPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothHorizontalPredictor.cs
new file mode 100644
index 0000000000..29673bfd63
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothHorizontalPredictor.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1SmoothHorizontalPredictor : IAv1Predictor
+{
+ private readonly nuint blockWidth;
+ private readonly nuint blockHeight;
+
+ public Av1SmoothHorizontalPredictor(Size blockSize)
+ {
+ this.blockWidth = (nuint)blockSize.Width;
+ this.blockHeight = (nuint)blockSize.Height;
+ }
+
+ public Av1SmoothHorizontalPredictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (nuint)transformSize.GetWidth();
+ this.blockHeight = (nuint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left)
+ => new Av1SmoothHorizontalPredictor(transformSize).PredictScalar(destination, stride, above, left);
+
+ ///
+ /// SVT: highbd_smooth_h_predictor
+ ///
+ public void PredictScalar(Span destination, nuint stride, Span above, Span left)
+ {
+ Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
+ Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
+ Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
+ Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
+ ref byte leftRef = ref left[0];
+ ref byte aboveRef = ref above[0];
+ ref byte destinationRef = ref destination[0];
+ int rightPrediction = Unsafe.Add(ref aboveRef, this.blockWidth - 1); // estimated by top-right pixel
+ ref int weights = ref Av1SmoothPredictor.Weights[(int)this.blockWidth];
+
+ // scale = 2 * 2^sm_weight_log2_scale
+ int log2Scale = 1 + Av1SmoothPredictor.WeightLog2Scale;
+ int scale = 1 << Av1SmoothPredictor.WeightLog2Scale;
+
+ // sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
+ for (nuint r = 0; r < this.blockHeight; ++r)
+ {
+ for (nuint c = 0; c < this.blockWidth; ++c)
+ {
+ int columnWeight = Unsafe.Add(ref weights, c);
+ Guard.MustBeGreaterThanOrEqualTo(scale, columnWeight, nameof(scale));
+ int thisPredition = Unsafe.Add(ref leftRef, r) * columnWeight;
+ thisPredition += rightPrediction * (scale - columnWeight);
+ Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale);
+ }
+
+ destinationRef = ref Unsafe.Add(ref destinationRef, stride);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothPredictor.cs
new file mode 100644
index 0000000000..796dd3e3a7
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothPredictor.cs
@@ -0,0 +1,101 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1SmoothPredictor : IAv1Predictor
+{
+ // Weights are quadratic from '1' to '1 / BlockSize', scaled by
+ // 2^sm_weight_log2_scale.
+ internal static readonly int WeightLog2Scale = 8;
+
+ internal static readonly int[] Weights = [
+
+ // Unused, because we always offset by bs, which is at least 2.
+ 0, 0,
+
+ // bs = 2
+ 255, 128,
+
+ // bs = 4
+ 255, 149, 85, 64,
+
+ // bs = 8
+ 255, 197, 146, 105, 73, 50, 37, 32,
+
+ // bs = 16
+ 255, 225, 196, 170, 145, 123, 102, 84, 68, 54, 43, 33, 26, 20, 17, 16,
+
+ // bs = 32
+ 255, 240, 225, 210, 196, 182, 169, 157, 145, 133, 122, 111, 101, 92, 83, 74,
+ 66, 59, 52, 45, 39, 34, 29, 25, 21, 17, 14, 12, 10, 9, 8, 8,
+
+ // bs = 64
+ 255, 248, 240, 233, 225, 218, 210, 203, 196, 189, 182, 176, 169, 163, 156,
+ 150, 144, 138, 133, 127, 121, 116, 111, 106, 101, 96, 91, 86, 82, 77, 73, 69,
+ 65, 61, 57, 54, 50, 47, 44, 41, 38, 35, 32, 29, 27, 25, 22, 20, 18, 16, 15,
+ 13, 12, 10, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4,
+ ];
+
+ private readonly nuint blockWidth;
+ private readonly nuint blockHeight;
+
+ public Av1SmoothPredictor(Size blockSize)
+ {
+ this.blockWidth = (nuint)blockSize.Width;
+ this.blockHeight = (nuint)blockSize.Height;
+ }
+
+ public Av1SmoothPredictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (nuint)transformSize.GetWidth();
+ this.blockHeight = (nuint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left)
+ => new Av1SmoothPredictor(transformSize).PredictScalar(destination, stride, above, left);
+
+ ///
+ /// SVT: highbd_smooth_predictor
+ ///
+ public void PredictScalar(Span destination, nuint stride, Span above, Span left)
+ {
+ Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
+ Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
+ Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
+ Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
+ ref byte leftRef = ref left[0];
+ ref byte aboveRef = ref above[0];
+ ref byte destinationRef = ref destination[0];
+ int belowPrediction = Unsafe.Add(ref leftRef, this.blockHeight - 1); // estimated by bottom-left pixel
+ int rightPrediction = Unsafe.Add(ref aboveRef, this.blockWidth - 1); // estimated by top-right pixel
+ ref int heightWeights = ref Weights[(int)this.blockWidth];
+ ref int widthWeights = ref Weights[(int)this.blockHeight];
+
+ // scale = 2 * 2^sm_weight_log2_scale
+ int log2Scale = 1 + WeightLog2Scale;
+ int scale = 1 << WeightLog2Scale;
+
+ // sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
+ for (nuint r = 0; r < this.blockHeight; ++r)
+ {
+ int rowWeight = Unsafe.Add(ref heightWeights, r);
+ Guard.MustBeGreaterThanOrEqualTo(scale, rowWeight, nameof(scale));
+ for (nuint c = 0; c < this.blockWidth; ++c)
+ {
+ int columnWeight = Unsafe.Add(ref widthWeights, c);
+ Guard.MustBeGreaterThanOrEqualTo(scale, columnWeight, nameof(scale));
+ int thisPredition = Unsafe.Add(ref aboveRef, c) * rowWeight;
+ thisPredition += belowPrediction * (scale - rowWeight);
+ thisPredition += Unsafe.Add(ref leftRef, r) * columnWeight;
+ thisPredition += rightPrediction * (scale - columnWeight);
+ Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale);
+ }
+
+ destinationRef = ref Unsafe.Add(ref destinationRef, stride);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothVerticalPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothVerticalPredictor.cs
new file mode 100644
index 0000000000..fcafeb1bf6
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothVerticalPredictor.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1SmoothVerticalPredictor : IAv1Predictor
+{
+ private readonly nuint blockWidth;
+ private readonly nuint blockHeight;
+
+ public Av1SmoothVerticalPredictor(Size blockSize)
+ {
+ this.blockWidth = (nuint)blockSize.Width;
+ this.blockHeight = (nuint)blockSize.Height;
+ }
+
+ public Av1SmoothVerticalPredictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (nuint)transformSize.GetWidth();
+ this.blockHeight = (nuint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left)
+ => new Av1SmoothVerticalPredictor(transformSize).PredictScalar(destination, stride, above, left);
+
+ ///
+ /// SVT: highbd_smooth_v_predictor
+ ///
+ public void PredictScalar(Span destination, nuint stride, Span above, Span left)
+ {
+ Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
+ Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
+ Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
+ Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
+ ref byte leftRef = ref left[0];
+ ref byte aboveRef = ref above[0];
+ ref byte destinationRef = ref destination[0];
+ int belowPrediction = Unsafe.Add(ref leftRef, this.blockHeight - 1); // estimated by bottom-left pixel
+ ref int weights = ref Av1SmoothPredictor.Weights[(int)this.blockHeight];
+
+ // scale = 2 * 2^sm_weight_log2_scale
+ int log2Scale = 1 + Av1SmoothPredictor.WeightLog2Scale;
+ int scale = 1 << Av1SmoothPredictor.WeightLog2Scale;
+
+ // sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
+ for (nuint r = 0; r < this.blockHeight; ++r)
+ {
+ int rowWeight = Unsafe.Add(ref weights, r);
+ for (nuint c = 0; c < this.blockWidth; ++c)
+ {
+ int thisPredition = Unsafe.Add(ref aboveRef, c) * rowWeight;
+ thisPredition += belowPrediction * (scale - rowWeight);
+ Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale);
+ }
+
+ destinationRef = ref Unsafe.Add(ref destinationRef, stride);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1VerticalPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1VerticalPredictor.cs
new file mode 100644
index 0000000000..ea23c04bc6
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1VerticalPredictor.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+internal class Av1VerticalPredictor : IAv1Predictor
+{
+ private readonly nuint blockWidth;
+ private readonly nuint blockHeight;
+
+ public Av1VerticalPredictor(Size blockSize)
+ {
+ this.blockWidth = (nuint)blockSize.Width;
+ this.blockHeight = (nuint)blockSize.Height;
+ }
+
+ public Av1VerticalPredictor(Av1TransformSize transformSize)
+ {
+ this.blockWidth = (nuint)transformSize.GetWidth();
+ this.blockHeight = (nuint)transformSize.GetHeight();
+ }
+
+ public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left)
+ => new Av1VerticalPredictor(transformSize).PredictScalar(destination, stride, above, left);
+
+ ///
+ /// SVT: highbd_v_predictor
+ ///
+ public void PredictScalar(Span destination, nuint stride, Span above, Span left)
+ {
+ Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
+ Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
+ Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
+ Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
+ ref byte aboveRef = ref above[0];
+ ref byte destinationRef = ref destination[0];
+ uint width = (uint)this.blockWidth;
+ for (nuint r = 0; r < this.blockHeight; ++r)
+ {
+ Unsafe.CopyBlock(ref destinationRef, ref aboveRef, width);
+ destinationRef = ref Unsafe.Add(ref destinationRef, stride);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
index c6acbb6505..b473fc351a 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
@@ -169,11 +169,7 @@ internal class Av1TileReader : IAv1TileReader
{
int ctx = this.GetPartitionPlaneContext(modeInfoLocation, blockSize, tileInfo, superblockInfo);
partitionType = Av1PartitionType.Split;
- if (blockSize < Av1BlockSize.Block8x8)
- {
- partitionType = Av1PartitionType.None;
- }
- else if (hasRows && hasColumns)
+ if (hasRows && hasColumns)
{
partitionType = reader.ReadPartitionType(ctx);
}
@@ -376,6 +372,7 @@ internal class Av1TileReader : IAv1TileReader
///
/// 5.11.34. Residual syntax.
///
+ /// SVT: parse_residual
private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1BlockSize blockSize)
{
int maxBlocksWide = partitionInfo.GetMaxBlockWide(blockSize, false);
@@ -813,6 +810,7 @@ internal class Av1TileReader : IAv1TileReader
///
/// Section 5.11.16. Block TX size syntax.
///
+ /// SVT: read_block_tx_size
private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
{
Av1BlockSize blockSize = partitionInfo.ModeInfo.BlockSize;
@@ -1318,7 +1316,7 @@ internal class Av1TileReader : IAv1TileReader
int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2();
int above = (aboveCtx >> blockSizeLog) & 0x1;
int left = (leftCtx >> blockSizeLog) & 0x1;
- DebugGuard.IsTrue(blockSize.Get4x4WidthLog2() == blockSize.Get4x4HeightLog2(), "Blocks should be square");
+ DebugGuard.IsTrue(blockSize.Get4x4WidthLog2() == blockSize.Get4x4HeightLog2(), "Blocks should be square.");
DebugGuard.MustBeGreaterThanOrEqualTo(blockSizeLog, 0, nameof(blockSizeLog));
return ((left << 1) + above) + (blockSizeLog * Av1Constants.PartitionProbabilitySet);
}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs
index 4a4cae4564..38ba0adbc9 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs
@@ -163,19 +163,12 @@ internal static class Av1InverseTransformMath
return ClipPixelHighBitDepth(dest + trans, bitDepth);
}
- private static short ClipPixelHighBitDepth(long val, int bd)
+ private static short ClipPixelHighBitDepth(long val, int bd) => bd switch
{
- switch (bd)
- {
- case 8:
- default:
- return (short)Av1Math.Clamp(val, 0, 255);
- case 10:
- return (short)Av1Math.Clamp(val, 0, 1023);
- case 12:
- return (short)Av1Math.Clamp(val, 0, 4095);
- }
- }
+ 10 => (short)Av1Math.Clamp(val, 0, 1023),
+ 12 => (short)Av1Math.Clamp(val, 0, 4095),
+ _ => (short)Av1Math.Clamp(val, 0, 255),
+ };
public static void RoundShiftArray(Span arr, int size, int bit)
{
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraPredictionMemory.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraPredictionMemory.cs
new file mode 100644
index 0000000000..3a74f5c45c
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraPredictionMemory.cs
@@ -0,0 +1,104 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text;
+using SixLabors.ImageSharp.Formats.Heif.Av1;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
+
+internal class Av1IntraPredictionMemory
+{
+ public const int Padding = 16;
+ private const int TopPadding = 7;
+ private const int MaxBlockSizeTimes2 = 1 << Av1Constants.MaxSuperBlockSizeLog2;
+ private const int TotalPixelCount = 4096;
+ private readonly byte[] destination = new byte[TotalPixelCount];
+ private readonly byte[] referenceSource = new byte[TotalPixelCount];
+ private readonly byte[] leftMemory = new byte[MaxBlockSizeTimes2 + Padding];
+ private readonly byte[] topMemory = new byte[MaxBlockSizeTimes2 + Padding + TopPadding];
+ private readonly int bitDepth;
+
+ public Av1IntraPredictionMemory(int bitDepth) => this.bitDepth = bitDepth;
+
+ public Span Destination => this.destination;
+
+ public Span Left => this.leftMemory;
+
+ public Span Top => this.topMemory;
+
+ ///
+ /// Sets referenceSource, left and top to .
+ ///
+ public void Set(byte value)
+ {
+ for (int r = 0; r < this.referenceSource.Length; r++)
+ {
+ this.referenceSource[r] = value;
+ }
+
+ // Upsampling in the directional predictors extends left/top[-1] to [-2].
+ for (int i = Padding - 2; i < Padding + MaxBlockSizeTimes2; ++i)
+ {
+ this.leftMemory[i] = this.topMemory[i] = value;
+ }
+ }
+
+ public void Scramble(Random rnd)
+ {
+ for (int i = 0; i < this.referenceSource.Length; i++)
+ {
+ this.referenceSource[i] = this.GetRandomValue(rnd);
+ }
+
+ for (int i = Padding; i < (MaxBlockSizeTimes2 >> 1) + Padding; ++i)
+ {
+ this.leftMemory[i] = this.GetRandomValue(rnd);
+ }
+
+ for (int i = Padding - 1; i < (MaxBlockSizeTimes2 >> 1) + Padding; ++i)
+ {
+ this.topMemory[i] = this.GetRandomValue(rnd);
+ }
+
+ // Some directional predictors require top-right, bottom-left.
+ for (int i = MaxBlockSizeTimes2 >> 1; i < MaxBlockSizeTimes2; ++i)
+ {
+ this.leftMemory[Padding + i] = this.GetRandomValue(rnd);
+ this.topMemory[Padding + i] = this.GetRandomValue(rnd);
+ }
+
+ // TODO(jzern): reorder this and regenerate the digests after switching
+ // random number generators.
+ // Upsampling in the directional predictors extends left/top[-1] to [-2].
+ this.leftMemory[Padding - 1] = this.GetRandomValue(rnd);
+ this.leftMemory[Padding - 2] = this.GetRandomValue(rnd);
+ this.topMemory[Padding - 2] = this.GetRandomValue(rnd);
+ this.leftMemory.AsSpan(0, Padding - 2).Clear();
+ this.topMemory.AsSpan(0, Padding - 2).Clear();
+ this.topMemory.AsSpan(MaxBlockSizeTimes2 + Padding, TopPadding).Clear();
+ }
+
+ public void CopySourceToDestination() => this.referenceSource.CopyTo(this.destination.AsSpan());
+
+ ///
+ /// Return a hash string of a block of bytes.
+ ///
+ /// This test design is used extensively in 'libgav1' tests, some of which are ported here.
+ public static string GetDigest(byte[] input)
+ {
+ byte[] hash = MD5.HashData(input);
+ StringBuilder sb = new();
+ for (int i = 0; i < hash.Length; i++)
+ {
+ sb.Append(hash[i].ToString("X2", CultureInfo.InvariantCulture));
+ }
+
+ return sb.ToString();
+ }
+
+ public string GetDestinationDigest() => GetDigest(this.destination);
+
+ private byte GetRandomValue(Random random) => (byte)(random.Next(1 << 16) & ((1 << this.bitDepth) - 1));
+}
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs
index f72e4421a8..1527a55778 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
@@ -9,9 +10,161 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
[Trait("Format", "Avif")]
public class Av1PredictorTests
{
+ private static string[] Digests4x4 = [
+ "7b1c762e28747f885d2b7d83cb8aa75c", "73353f179207f1432d40a132809e3a50",
+ "80c9237c838b0ec0674ccb070df633d5", "1cd79116b41fda884e7fa047f5eb14df",
+ "33211425772ee539a59981a2e9dc10c1", "d6f5f65a267f0e9a2752e8151cc1dcd7",
+ "7ff8c762cb766eb0665682152102ce4b", "2276b861ae4599de15938651961907ec",
+ "766982bc69f4aaaa8e71014c2dc219bc", "04401B397D702F853B12407EBFB91027",
+ ];
+
+ private static string[] Digests4x8 = [
+ "0a0d8641ecfa0e82f541acdc894d5574", "1a40371af6cff9c278c5b0def9e4b3e7",
+ "3631a7a99569663b514f15b590523822", "646c7b592136285bd31501494e7393e7",
+ "ecbe89cc64dc2688123d3cfe865b5237", "79048e70ecbb7d43a4703f62718588c0",
+ "f3de11bf1198a00675d806d29c41d676", "32bb6cd018f6e871c342fcc21c7180cf",
+ "6f076a1e5ab3d69cf08811d62293e4be", "F94348B0D3E2B1F2FC061232831E8B9B",
+ ];
+
+ private static string[] Digests4x16 = [
+ "cb8240be98444ede5ae98ca94afc1557", "460acbcf825a1fa0d8f2aa6bf2d6a21c",
+ "7896fdbbfe538dce1dc3a5b0873d74b0", "504aea29c6b27f21555d5516b8de2d8a",
+ "c5738e7fa82b91ea0e39232120da56ea", "19abbd934c243a6d9df7585d81332dd5",
+ "9e42b7b342e45c842dfa8aedaddbdfaa", "0e9eb07a89f8bf96bc219d5d1c3d9f6d",
+ "659393c31633e0f498bae384c9df5c7b", "29241129BC91B354D04CA6C09A7CF1E1",
+ ];
+
+ private static string[] Digests8x4 = [
+ "5950744064518f77867c8e14ebd8b5d7", "46b6cbdc76efd03f4ac77870d54739f7",
+ "efe21fd1b98cb1663950e0bf49483b3b", "3c647b64760b298092cbb8e2f5c06bfd",
+ "c3595929687ffb04c59b128d56e2632f", "d89ad2ddf8a74a520fdd1d7019fd75b4",
+ "53907cb70ad597ee5885f6c58201f98b", "09d2282a29008b7fb47eb60ed6653d06",
+ "e341fc1c910d7cb2dac5dbc58b9c9af9", "93D67CD49B07CE3870526E5C12F00EBF",
+ ];
+
+ private static string[] Digests8x8 = [
+ "06fb7cb52719855a38b4883b4b241749", "2013aafd42a4303efb553e42264ab8b0",
+ "2f070511d5680c12ca73a20e47fd6e23", "9923705af63e454392625794d5459fe0",
+ "04007a0d39778621266e2208a22c4fac", "2d296c202d36b4a53f1eaddda274e4a1",
+ "c87806c220d125c7563c2928e836fbbd", "339b49710a0099087e51ab5afc8d8713",
+ "c90fbc020afd9327bf35dccae099bf77", "BAB0E5918AB2F7354D9BCA4E9A927C0F",
+ ];
+
+ private static string[] Digests8x16 = [
+ "3c5a4574d96b5bb1013429636554e761", "8cf56b17c52d25eb785685f2ab48b194",
+ "7911e2e02abfbe226f17529ac5db08fc", "064e509948982f66a14293f406d88d42",
+ "5c443aa713891406d5be3af4b3cf67c6", "5d2cb98e532822ca701110cda9ada968",
+ "3d58836e17918b8890012dd96b95bb9d", "20e8d61ddc451b9e553a294073349ffd",
+ "a9aa6cf9d0dcf1977a1853ccc264e40b", "FEDA9D1554325ED2A243FC3DD6ADD4E3",
+ ];
+
+ private static string[] Digests8x32 = [
+ "b393a2db7a76acaccc39e04d9dc3e8ac", "bbda713ee075a7ef095f0f479b5a1f82",
+ "f337dce3980f70730d6f6c2c756e3b62", "796189b05dc026e865c9e95491b255d1",
+ "ea932c21e7189eeb215c1990491320ab", "a9fffdf9455eba5e3b01317cae140289",
+ "9525dbfdbf5fba61ef9c7aa5fe887503", "8c6a7e3717ff8a459f415c79bb17341c",
+ "3761071bfaa2363a315fe07223f95a2d", "506D691319D1AEF38DF5C1E060408BDD",
+ ];
+
+ private static string[] Digests16x4 = [
+ "1c0a950b3ac500def73b165b6a38467c", "95e7f7300f19da280c6a506e40304462",
+ "28a6af15e31f76d3ff189012475d78f5", "e330d67b859bceef62b96fc9e1f49a34",
+ "36eca3b8083ce2fb5f7e6227dfc34e71", "08f567d2abaa8e83e4d9b33b3f709538",
+ "dc2d0ba13aa9369446932f03b53dc77d", "9ab342944c4b1357aa79d39d7bebdd3a",
+ "77ec278c5086c88b91d68eef561ed517", "7B8ECDFBF449908E9C96664582C05BFE",
+ ];
+
+ private static string[] Digests16x8 = [
+ "053a2bc4b5b7287fee524af4e77f077a", "619b720b13f14f32391a99ea7ff550d5",
+ "728d61c11b06baf7fe77881003a918b9", "889997b89a44c9976cb34f573e2b1eea",
+ "b43bfc31d1c770bb9ca5ca158c9beec4", "9d3fe9f762e0c6e4f114042147c50c7f",
+ "c74fdd7c9938603b01e7ecf9fdf08d61", "870c7336db1102f80f74526bd5a7cf4e",
+ "3fd5354a6190903d6a0b661fe177daf6", "EA586FE5C31B38A547D18332141665E9",
+ ];
+
+ private static string[] Digests16x16 = [
+ "1fa9e2086f6594bda60c30384fbf1635", "2098d2a030cd7c6be613edc74dc2faf8",
+ "f3c72b0c8e73f1ddca04d14f52d194d8", "6b31f2ee24cf88d3844a2fc67e1f39f3",
+ "d91a22a83575e9359c5e4871ab30ddca", "24c32a0d38b4413d2ef9bf1f842c8634",
+ "6e9e47bf9da9b2b9ae293e0bbd8ff086", "968b82804b5200b074bcdba9718140d4",
+ "4e6d7e612c5ae0bbdcc51a453cd1db3f", "D182AFFEC5DBD0C7C0BD945AF090C48D",
+ ];
+
+ private static string[] Digests16x32 = [
+ "01afd04432026ff56327d6226b720be2", "a6e7be906cc6f1e7a520151bfa7c303d",
+ "bc05c46f18d0638f0228f1de64f07cd5", "204e613e429935f721a5b29cec7d44bb",
+ "aa0a7c9a7482dfc06d9685072fc5bafd", "ffb60f090d83c624bb4f7dc3a630ac4f",
+ "36bcb9ca9bb5eac520b050409de25da5", "34d9a5dd3363668391bc3bd05b468182",
+ "1e149c28db8b234e43931c347a523794", "DD9FCEB000F2B2F1F4910AE9856BCF4C",
+ ];
+
+ private static string[] Digests16x64 = [
+ "727797ef15ccd8d325476fe8f12006a3", "f77c544ac8035e01920deae40cee7b07",
+ "12b0c69595328c465e0b25e0c9e3e9fc", "3b2a053ee8b05a8ac35ad23b0422a151",
+ "f3be77c0fe67eb5d9d515e92bec21eb7", "f1ece6409e01e9dd98b800d49628247d",
+ "efd2ec9bfbbd4fd1f6604ea369df1894", "ec703de918422b9e03197ba0ed60a199",
+ "739418efb89c07f700895deaa5d0b3e3", "AE9A0FBC3EB929B82E20E8C45001E7BE",
+ ];
+
+ private static string[] Digests32x8 = [
+ "4da55401331ed98acec0c516d7307513", "0ae6f3974701a5e6c20baccd26b4ca52",
+ "79b799f1eb77d5189535dc4e18873a0e", "90e943adf3de4f913864dce4e52b4894",
+ "5e1b9cc800a89ef45f5bdcc9e99e4e96", "3103405df20d254cbf32ac30872ead4b",
+ "648550e369b77687bff3c7d6f249b02f", "f9f73bcd8aadfc059fa260325df957a1",
+ "204cef70d741c25d4fe2b1d10d2649a5", "8CB3C74FFFF9975F3DE5178311486350",
+ ];
+
+ private static string[] Digests32x16 = [
+ "86ad1e1047abaf9959150222e8f19593", "1908cbe04eb4e5c9d35f1af7ffd7ee72",
+ "6ad3bb37ebe8374b0a4c2d18fe3ebb6a", "08d3cfe7a1148bff55eb6166da3378c6",
+ "656a722394764d17b6c42401b9e0ad3b", "4aa00c192102efeb325883737e562f0d",
+ "9881a90ca88bca4297073e60b3bb771a", "8cd74aada398a3d770fc3ace38ecd311",
+ "0a927e3f5ff8e8338984172cc0653b13", "91ABCB84EB86746DEF31AF8F96BAA0CF",
+ ];
+
+ private static string[] Digests32x32 = [
+ "1303ca680644e3d8c9ffd4185bb2835b", "2a4d9f5cc8da307d4cf7dc021df10ba9",
+ "ced60d3f4e4b011a6a0314dd8a4b1fd8", "ced60d3f4e4b011a6a0314dd8a4b1fd8",
+ "1464b01aa928e9bd82c66bad0f921693", "90deadfb13d7c3b855ba21b326c1e202",
+ "af96a74f8033dff010e53a8521bc6f63", "9f1039f2ef082aaee69fcb7d749037c2",
+ "3f82893e478e204f2d254b34222d14dc", "9B2A16A6331AA6EC41636B8F709B39C7",
+ ];
+
+ private static string[] Digests32x64 = [
+ "e1e8ed803236367821981500a3d9eebe", "0f46d124ba9f48cdd5d5290acf786d6d",
+ "4e2a2cfd8f56f15939bdfc753145b303", "0ce332b343934b34cd4417725faa85cb",
+ "1d2f8e48e3adb7c448be05d9f66f4954", "9fb2e176636a5689b26f73ca73fcc512",
+ "e720ebccae7e25e36f23da53ae5b5d6a", "86fe4364734169aaa4520d799890d530",
+ "b1870290764bb1b100d1974e2bd70f1d", "B652BBE65C345A7153A1F3E1C801633F",
+ ];
+
+ private static string[] Digests64x16 = [
+ "de1b736e9d99129609d6ef3a491507a0", "516d8f6eb054d74d150e7b444185b6b9",
+ "69e462c3338a9aaf993c3f7cfbc15649", "821b76b1494d4f84d20817840f719a1a",
+ "fd9b4276e7affe1e0e4ce4f428058994", "cd82fd361a4767ac29a9f406b480b8f3",
+ "2792c2f810157a4a6cb13c28529ff779", "1220442d90c4255ba0969d28b91e93a6",
+ "c7253e10b45f7f67dfee3256c9b94825", "F0DC6EE8E9291452AC361E08AEB53DB5",
+ ];
+
+ private static string[] Digests64x32 = [
+ "e48e1ac15e97191a8fda08d62fff343e", "80c15b303235f9bc2259027bb92dfdc4",
+ "538424b24bd0830f21788e7238ca762f", "a6c5aeb722615089efbca80b02951ceb",
+ "12604b37875533665078405ef4582e35", "0048afa17bd3e1632d68b96048836530",
+ "07a0cfcb56a5eed50c4bd6c26814336b", "529d8a070de5bc6531fa3ee8f450c233",
+ "33c50a11c7d78f72434064f634305e95", "28E560D9C16C5ED6055A66AB16EC6900",
+ ];
+
+ private static string[] Digests64x64 = [
+ "a1650dbcd56e10288c3e269eca37967d", "be91585259bc37bf4dc1651936e90b3e",
+ "afe020786b83b793c2bbd9468097ff6e", "6e1094fa7b50bc813aa2ba29f5df8755",
+ "9e5c34f3797e0cdd3cd9d4c05b0d8950", "bc87be7ac899cc6a28f399d7516c49fe",
+ "9811fd0d2dd515f06122f5d1bd18b784", "3c140e466f2c2c0d9cb7d2157ab8dc27",
+ "9543de76c925a8f6adc884cc7f98dc91", "73BBEC1CDFFF6D66318FB43628EA42C2",
+ ];
+
[Theory]
[MemberData(nameof(GetTransformSizes))]
- public void VerifyDcFill(int width, int height)
+ public void VerifyDcFill(int _, int width, int height)
{
// Assign
byte[] destination = new byte[width * height];
@@ -29,7 +182,7 @@ public class Av1PredictorTests
[Theory]
[MemberData(nameof(GetTransformSizes))]
- public void VerifyDc(int width, int height)
+ public void VerifyDc(int _, int width, int height)
{
// Assign
byte[] destination = new byte[width * height];
@@ -52,7 +205,7 @@ public class Av1PredictorTests
[Theory]
[MemberData(nameof(GetTransformSizes))]
- public void VerifyDcLeft(int width, int height)
+ public void VerifyDcLeft(int _, int width, int height)
{
// Assign
byte[] destination = new byte[width * height];
@@ -72,7 +225,7 @@ public class Av1PredictorTests
[Theory]
[MemberData(nameof(GetTransformSizes))]
- public void VerifyDcTop(int width, int height)
+ public void VerifyDcTop(int _, int width, int height)
{
// Assign
byte[] destination = new byte[width * height];
@@ -90,6 +243,25 @@ public class Av1PredictorTests
Assert.All(destination, (b) => AssertValue(expected, b));
}
+ [Theory]
+ [MemberData(nameof(GetTransformSizes))]
+ public void VerifySmooth(int index, int width, int height)
+ {
+ // Arrange
+ string expectedDigest = GetExpectedDigext((Av1TransformSize)index, Av1PredictionMode.Smooth);
+ Av1IntraPredictionMemory predictorMemory = new(8);
+ predictorMemory.Scramble(new Random(42));
+ predictorMemory.CopySourceToDestination();
+ const int stride = 1 << (Av1Constants.MaxSuperBlockSizeLog2 - 1);
+ Av1SmoothPredictor predictor = new(new Size(width, height));
+
+ // Act
+ predictor.PredictScalar(predictorMemory.Destination, stride, predictorMemory.Top, predictorMemory.Left);
+
+ // Assert
+ Assert.Equal(expectedDigest, predictorMemory.GetDestinationDigest());
+ }
+
private static void AssertValue(byte expected, byte actual)
{
Assert.NotEqual(0, actual);
@@ -107,17 +279,41 @@ public class Av1PredictorTests
return sum;
}
- public static TheoryData GetTransformSizes()
+ public static TheoryData GetTransformSizes()
{
- TheoryData combinations = [];
+ TheoryData combinations = [];
for (int s = 0; s < (int)Av1TransformSize.AllSizes; s++)
{
Av1TransformSize size = (Av1TransformSize)s;
int width = size.GetWidth();
int height = size.GetHeight();
- combinations.Add(width, height);
+ combinations.Add(s, width, height);
}
return combinations;
}
+
+ private static string GetExpectedDigext(Av1TransformSize size, Av1PredictionMode mode) => size switch
+ {
+ Av1TransformSize.Size4x4 => Digests4x4[(int)mode],
+ Av1TransformSize.Size8x4 => Digests8x4[(int)mode],
+ Av1TransformSize.Size4x8 => Digests4x8[(int)mode],
+ Av1TransformSize.Size8x8 => Digests8x8[(int)mode],
+ Av1TransformSize.Size4x16 => Digests4x16[(int)mode],
+ Av1TransformSize.Size16x4 => Digests16x4[(int)mode],
+ Av1TransformSize.Size8x16 => Digests8x16[(int)mode],
+ Av1TransformSize.Size16x8 => Digests16x8[(int)mode],
+ Av1TransformSize.Size16x16 => Digests16x16[(int)mode],
+ Av1TransformSize.Size8x32 => Digests8x32[(int)mode],
+ Av1TransformSize.Size32x8 => Digests32x8[(int)mode],
+ Av1TransformSize.Size16x32 => Digests16x32[(int)mode],
+ Av1TransformSize.Size32x16 => Digests32x16[(int)mode],
+ Av1TransformSize.Size32x32 => Digests32x32[(int)mode],
+ Av1TransformSize.Size16x64 => Digests16x64[(int)mode],
+ Av1TransformSize.Size64x16 => Digests64x16[(int)mode],
+ Av1TransformSize.Size32x64 => Digests32x64[(int)mode],
+ Av1TransformSize.Size64x32 => Digests64x32[(int)mode],
+ Av1TransformSize.Size64x64 => Digests64x64[(int)mode],
+ _ => string.Empty,
+ };
}