diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index fc4915e537..6539012e5b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -180,4 +180,6 @@ internal static class Av1Constants /// Maximum number of stages in a 1-dimensioanl transform function. /// public const int MaxTransformStageNumber = 12; + + public const int PartitionProbabilitySet = 4; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index 59da84eeea..62d6d63c08 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -166,4 +166,9 @@ internal static class Av1Math DebugGuard.MustBeGreaterThanOrEqualTo(bit, 1, nameof(bit)); return (int)((value + (1L << (bit - 1))) >> bit); } + + /// + /// implies . + /// + internal static bool Implies(bool a, bool b) => !a || b; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index 62a3894d45..d33b5f6599 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -64,42 +64,24 @@ internal ref struct Av1SymbolDecoder return (Av1PartitionType)r.ReadSymbol(this.tilePartitionTypes[context]); } - public bool ReadSplitOrHorizontal(Av1BlockSize blockSize, int context) + /// + /// SVT: partition_gather_vert_alike + /// + public Av1PartitionType ReadSplitOrHorizontal(Av1BlockSize blockSize, int context) { - Av1Distribution input = this.tilePartitionTypes[context]; - uint p = Av1Distribution.ProbabilityTop; - p -= GetElementProbability(input, Av1PartitionType.Horizontal); - p -= GetElementProbability(input, Av1PartitionType.Split); - p -= GetElementProbability(input, Av1PartitionType.HorizontalA); - p -= GetElementProbability(input, Av1PartitionType.HorizontalB); - p -= GetElementProbability(input, Av1PartitionType.VerticalA); - if (blockSize != Av1BlockSize.Block128x128) - { - p -= GetElementProbability(input, Av1PartitionType.Horizontal4); - } - - Av1Distribution distribution = new(Av1Distribution.ProbabilityTop - p); + Av1Distribution distribution = GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context); ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(distribution) > 0; + return r.ReadSymbol(distribution) > 0 ? Av1PartitionType.Split : Av1PartitionType.Horizontal; } - public bool ReadSplitOrVertical(Av1BlockSize blockSize, int context) + /// + /// SVT: partition_gather_horz_alike + /// + public Av1PartitionType ReadSplitOrVertical(Av1BlockSize blockSize, int context) { - Av1Distribution input = this.tilePartitionTypes[context]; - uint p = Av1Distribution.ProbabilityTop; - p -= GetElementProbability(input, Av1PartitionType.Vertical); - p -= GetElementProbability(input, Av1PartitionType.Split); - p -= GetElementProbability(input, Av1PartitionType.HorizontalA); - p -= GetElementProbability(input, Av1PartitionType.VerticalA); - p -= GetElementProbability(input, Av1PartitionType.VerticalB); - if (blockSize != Av1BlockSize.Block128x128) - { - p -= GetElementProbability(input, Av1PartitionType.Vertical4); - } - - Av1Distribution distribution = new(Av1Distribution.ProbabilityTop - p); + Av1Distribution distribution = GetSplitOrVerticalDistribution(this.tilePartitionTypes, blockSize, context); ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(distribution) > 0; + return r.ReadSymbol(distribution) > 0 ? Av1PartitionType.Split : Av1PartitionType.Vertical; } public Av1PredictionMode ReadYMode(Av1BlockModeInfo? aboveModeInfo, Av1BlockModeInfo? leftModeInfo) @@ -259,6 +241,40 @@ internal ref struct Av1SymbolDecoder return r.ReadSymbol(this.chromeForLumaAlpha[context]); } + internal static Av1Distribution GetSplitOrHorizontalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context) + { + Av1Distribution input = inputs[context]; + uint p = Av1Distribution.ProbabilityTop; + p -= GetElementProbability(input, Av1PartitionType.Horizontal); + p -= GetElementProbability(input, Av1PartitionType.Split); + p -= GetElementProbability(input, Av1PartitionType.HorizontalA); + p -= GetElementProbability(input, Av1PartitionType.HorizontalB); + p -= GetElementProbability(input, Av1PartitionType.VerticalA); + if (blockSize != Av1BlockSize.Block128x128) + { + p -= GetElementProbability(input, Av1PartitionType.Horizontal4); + } + + return new(Av1Distribution.ProbabilityTop - p); + } + + internal static Av1Distribution GetSplitOrVerticalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context) + { + Av1Distribution input = inputs[context]; + uint p = Av1Distribution.ProbabilityTop; + p -= GetElementProbability(input, Av1PartitionType.Vertical); + p -= GetElementProbability(input, Av1PartitionType.Split); + p -= GetElementProbability(input, Av1PartitionType.HorizontalA); + p -= GetElementProbability(input, Av1PartitionType.VerticalA); + p -= GetElementProbability(input, Av1PartitionType.VerticalB); + if (blockSize != Av1BlockSize.Block128x128) + { + p -= GetElementProbability(input, Av1PartitionType.Vertical4); + } + + return new(Av1Distribution.ProbabilityTop - p); + } + private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) => probability[(int)element - 1] - probability[(int)element]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs index 3da3237c26..78bc2eec49 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs @@ -10,22 +10,52 @@ internal class Av1SymbolEncoder : IDisposable private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; - private Av1SymbolWriter? writer; + private bool isDisposed; + private Av1SymbolWriter writer; public Av1SymbolEncoder(Configuration configuration, int initialSize) => this.writer = new(configuration, initialSize); public void WriteUseIntraBlockCopy(bool value) - => this.writer!.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy); + { + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy); + } + + public void WritePartitionType(Av1PartitionType partitionType, int context) + { + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol((int)partitionType, this.tilePartitionTypes[context]); + } + + public void WriteSplitOrHorizontal(Av1PartitionType partitionType, Av1BlockSize blockSize, int context) + { + Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context); + int value = (partitionType == Av1PartitionType.Split) ? 1 : 0; + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol(value, distribution); + } - public void WritePartitionType(Av1PartitionType value, int context) - => this.writer!.WriteSymbol((int)value, this.tilePartitionTypes[context]); + public void WriteSplitOrVertical(Av1PartitionType partitionType, Av1BlockSize blockSize, int context) + { + Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrVerticalDistribution(this.tilePartitionTypes, blockSize, context); + int value = (partitionType == Av1PartitionType.Split) ? 1 : 0; + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol(value, distribution); + } - public IMemoryOwner Exit() => this.writer!.Exit(); + public IMemoryOwner Exit() + { + ref Av1SymbolWriter w = ref this.writer; + return w.Exit(); + } public void Dispose() { - this.writer?.Dispose(); - this.writer = null; + if (!this.isDisposed) + { + this.writer.Dispose(); + this.isDisposed = true; + } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 6a4dcbde81..d4355fc016 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -14,7 +14,6 @@ internal class Av1TileReader : IAv1TileReader { private static readonly int[] SgrprojXqdMid = [-32, 31]; private static readonly int[] WienerTapsMid = [3, -7, 15]; - private const int PartitionProbabilitySet = 4; private static readonly int[] Signs = [0, -1, 1]; private static readonly int[] DcSignContexts = [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, @@ -182,13 +181,11 @@ internal class Av1TileReader : IAv1TileReader } else if (hasColumns) { - bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx); - partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Horizontal; + partitionType = reader.ReadSplitOrHorizontal(blockSize, ctx); } else if (hasRows) { - bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx); - partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Vertical; + partitionType = reader.ReadSplitOrVertical(blockSize, ctx); } } @@ -1886,7 +1883,7 @@ internal class Av1TileReader : IAv1TileReader int left = (leftCtx >> blockSizeLog) & 0x1; DebugGuard.IsTrue(blockSize.Get4x4WidthLog2() == blockSize.Get4x4HeightLog2(), "Blocks should be square"); DebugGuard.MustBeGreaterThanOrEqualTo(blockSizeLog, 0, nameof(blockSizeLog)); - return ((left << 1) + above) + (blockSizeLog * PartitionProbabilitySet); + return ((left << 1) + above) + (blockSizeLog * Av1Constants.PartitionProbabilitySet); } private void UpdatePartitionContext(Point modeInfoLocation, Av1TileInfo tileLoc, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize, Av1PartitionType partition) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs index 756319adaf..81f2d84776 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs @@ -2,9 +2,11 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Reflection; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Memory; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -217,6 +219,80 @@ public class SymbolTests Assert.Equal(values, actuals); } + [Theory] + [InlineData((int)Av1BlockSize.Block4x4, 7)] + [InlineData((int)Av1BlockSize.Block4x4, 5)] + [InlineData((int)Av1BlockSize.Block8x4, 7)] + [InlineData((int)Av1BlockSize.Block4x8, 7)] + [InlineData((int)Av1BlockSize.Block32x64, 7)] + [InlineData((int)Av1BlockSize.Block64x32, 7)] + [InlineData((int)Av1BlockSize.Block64x64, 7)] + public void RoundTripSplitOrHorizontalPartitionType(int blockSize, int context) + { + // Assign + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8); + Av1PartitionType[] values = [ + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Horizontal, + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Horizontal, Av1PartitionType.Horizontal]; + Av1PartitionType[] actuals = new Av1PartitionType[values.Length]; + + // Act + foreach (Av1PartitionType value in values) + { + encoder.WriteSplitOrHorizontal(value, (Av1BlockSize)blockSize, context); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolReader reader = new(encoded.GetSpan()); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadSplitOrHorizontal((Av1BlockSize)blockSize, context); + } + + // Assert + Assert.Equal(values, actuals); + } + + [Theory] + [InlineData((int)Av1BlockSize.Block4x4, 7)] + [InlineData((int)Av1BlockSize.Block4x4, 5)] + [InlineData((int)Av1BlockSize.Block8x4, 7)] + [InlineData((int)Av1BlockSize.Block4x8, 7)] + [InlineData((int)Av1BlockSize.Block32x64, 7)] + [InlineData((int)Av1BlockSize.Block64x32, 7)] + [InlineData((int)Av1BlockSize.Block64x64, 7)] + public void RoundTripSplitOrVerticalPartitionType(int blockSize, int context) + { + // Assign + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8); + Av1PartitionType[] values = [ + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Vertical, + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Vertical, Av1PartitionType.Vertical]; + Av1PartitionType[] actuals = new Av1PartitionType[values.Length]; + + // Act + foreach (Av1PartitionType value in values) + { + encoder.WriteSplitOrVertical(value, (Av1BlockSize)blockSize, context); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolReader reader = new(encoded.GetSpan()); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadSplitOrVertical((Av1BlockSize)blockSize, context); + } + + // Assert + Assert.Equal(values, actuals); + } + [Fact] public void RoundTripUseIntraBlockCopy() { @@ -235,7 +311,6 @@ public class SymbolTests using IMemoryOwner encoded = encoder.Exit(); Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); - Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { actuals[i] = decoder.ReadUseIntraBlockCopy();