From d748a0d8040735c3fec5c12eb2e419aaa67b6d22 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 20 Aug 2024 20:16:31 +0200 Subject: [PATCH] Introduce DC predictors --- .../Heif/Av1/Prediction/Av1DcFillPredictor.cs | 28 ++++ .../Heif/Av1/Prediction/Av1DcLeftPredictor.cs | 34 +++++ .../Heif/Av1/Prediction/Av1DcPredictor.cs | 40 ++++++ .../Heif/Av1/Prediction/Av1DcTopPredictor.cs | 34 +++++ .../Heif/Av1/Prediction/IAv1Predictor.cs | 19 +++ .../Formats/Heif/Av1/PredictorTests.cs | 123 ++++++++++++++++++ .../Av1/{SymbolTest.cs => SymbolTests.cs} | 2 +- 7 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/PredictorTests.cs rename tests/ImageSharp.Tests/Formats/Heif/Av1/{SymbolTest.cs => SymbolTests.cs} (99%) diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs new file mode 100644 index 0000000000..d0bc59f1c0 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +internal class Av1DcFillPredictor : IAv1Predictor +{ + private readonly uint blockWidth; + private readonly uint blockHeight; + + public Av1DcFillPredictor(Size blockSize) + { + this.blockWidth = (uint)blockSize.Width; + this.blockHeight = (uint)blockSize.Height; + } + + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) + { + const byte expectedDc = 0x80; + for (uint r = 0; r < this.blockHeight; r++) + { + Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth); + destination = ref Unsafe.Add(ref destination, stride); + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs new file mode 100644 index 0000000000..4aea10bdf4 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +internal class Av1DcLeftPredictor : IAv1Predictor +{ + private readonly uint blockWidth; + private readonly uint blockHeight; + + public Av1DcLeftPredictor(Size blockSize) + { + this.blockWidth = (uint)blockSize.Width; + this.blockHeight = (uint)blockSize.Height; + } + + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) + { + int sum = 0; + for (uint i = 0; i < this.blockHeight; i++) + { + sum += Unsafe.Add(ref left, i); + } + + byte expectedDc = (byte)((sum + (this.blockHeight >> 1)) / this.blockHeight); + for (uint r = 0; r < this.blockHeight; r++) + { + Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth); + destination = ref Unsafe.Add(ref destination, stride); + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs new file mode 100644 index 0000000000..f3a3cf4ae6 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +internal class Av1DcPredictor : IAv1Predictor +{ + private readonly uint blockWidth; + private readonly uint blockHeight; + + public Av1DcPredictor(Size blockSize) + { + this.blockWidth = (uint)blockSize.Width; + this.blockHeight = (uint)blockSize.Height; + } + + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) + { + int sum = 0; + uint count = this.blockWidth + this.blockHeight; + for (uint i = 0; i < this.blockWidth; i++) + { + sum += Unsafe.Add(ref above, i); + } + + for (uint i = 0; i < this.blockHeight; i++) + { + sum += Unsafe.Add(ref left, i); + } + + byte expectedDc = (byte)((sum + (count >> 1)) / count); + for (uint r = 0; r < this.blockHeight; r++) + { + Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth); + destination = ref Unsafe.Add(ref destination, stride); + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs new file mode 100644 index 0000000000..a79a473938 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +internal class Av1DcTopPredictor : IAv1Predictor +{ + private readonly uint blockWidth; + private readonly uint blockHeight; + + public Av1DcTopPredictor(Size blockSize) + { + this.blockWidth = (uint)blockSize.Width; + this.blockHeight = (uint)blockSize.Height; + } + + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) + { + int sum = 0; + for (uint i = 0; i < this.blockWidth; i++) + { + sum += Unsafe.Add(ref above, i); + } + + byte expectedDc = (byte)((sum + (this.blockWidth >> 1)) / this.blockWidth); + for (uint r = 0; r < this.blockHeight; r++) + { + Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth); + destination = ref Unsafe.Add(ref destination, stride); + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs new file mode 100644 index 0000000000..3bf8907789 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +/// +/// Interface for predictor implementations. +/// +internal interface IAv1Predictor +{ + /// + /// Predict using scalar logic within the 8-bit pipeline. + /// + /// The destination to write to. + /// The stride of the destination buffer. + /// Pointer to the first element of the block above. + /// Pointer to the first element of the block to the left. + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left); +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/PredictorTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/PredictorTests.cs new file mode 100644 index 0000000000..42dfaa568a --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/PredictorTests.cs @@ -0,0 +1,123 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class PredictorTests +{ + [Theory] + [MemberData(nameof(GetTransformSizes))] + public void VerifyDcFill(int width, int height) + { + // Assign + byte[] destination = new byte[width * height]; + byte[] left = new byte[1]; + byte[] above = new byte[1]; + byte expected = 0x80; + + // Act + Av1DcFillPredictor predictor = new(new Size(width, height)); + predictor.PredictScalar(ref destination[0], (nuint)width, ref above[0], ref left[0]); + + // Assert + Assert.All(destination, (b) => AssertValue(expected, b)); + } + + [Theory] + [MemberData(nameof(GetTransformSizes))] + public void VerifyDc(int width, int height) + { + // Assign + byte[] destination = new byte[width * height]; + byte[] left = new byte[width * height]; + byte[] above = new byte[width * height]; + Array.Fill(left, (byte)5); + Array.Fill(above, (byte)28); + int count = width + height; + int sum = Sum(left, height) + Sum(above, width); + byte expected = (byte)((sum + (count >> 1)) / count); + + // Act + Av1DcPredictor predictor = new(new Size(width, height)); + predictor.PredictScalar(ref destination[0], (nuint)width, ref above[0], ref left[0]); + + // Assert + Assert.Equal((5 * height) + (28 * width), sum); + Assert.All(destination, (b) => AssertValue(expected, b)); + } + + [Theory] + [MemberData(nameof(GetTransformSizes))] + public void VerifyDcLeft(int width, int height) + { + // Assign + byte[] destination = new byte[width * height]; + byte[] left = new byte[width * height]; + byte[] above = new byte[width * height]; + Array.Fill(left, (byte)5); + Array.Fill(above, (byte)28); + byte expected = left[0]; + + // Act + Av1DcLeftPredictor predictor = new(new Size(width, height)); + predictor.PredictScalar(ref destination[0], (nuint)width, ref above[0], ref left[0]); + + // Assert + Assert.All(destination, (b) => AssertValue(expected, b)); + } + + [Theory] + [MemberData(nameof(GetTransformSizes))] + public void VerifyDcTop(int width, int height) + { + // Assign + byte[] destination = new byte[width * height]; + byte[] left = new byte[width * height]; + byte[] above = new byte[width * height]; + Array.Fill(left, (byte)5); + Array.Fill(above, (byte)28); + byte expected = above[0]; + + // Act + Av1DcTopPredictor predictor = new(new Size(width, height)); + predictor.PredictScalar(ref destination[0], (nuint)width, ref above[0], ref left[0]); + + // Assert + Assert.All(destination, (b) => AssertValue(expected, b)); + } + + private static void AssertValue(byte expected, byte actual) + { + Assert.NotEqual(0, actual); + Assert.Equal(expected, actual); + } + + private static int Sum(Span values, int length) + { + int sum = 0; + for (int i = 0; i < length; i++) + { + sum += values[i]; + } + + return sum; + } + + public static TheoryData GetTransformSizes() + { + 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); + } + + return combinations; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs similarity index 99% rename from tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs rename to tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs index 33a47cd807..756319adaf 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs @@ -9,7 +9,7 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] -public class SymbolTest +public class SymbolTests { [Fact] public void ReadRandomLiteral()