diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs index cc08556fd..09efc4ba9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs @@ -135,7 +135,16 @@ internal static class Av1DefaultDistributions public static Av1Distribution DeltaQuantizerAbsolute => new(28160, 32120, 32677); - public static Av1Distribution[] SegmentId => [new(128 * 128), new(128 * 128), new(128 * 128)]; + /// + /// Gets the Segment identifier . + /// + /// SVT: default_spatial_pred_seg_tree_cdf + public static Av1Distribution[] SegmentId => + [ + new(5622, 7893, 16093, 18233, 27809, 28373, 32533), + new(14274, 18230, 22557, 24935, 29980, 30851, 32344), + new(27527, 28487, 28723, 28890, 32397, 32647, 32679), + ]; public static Av1Distribution[][] KeyFrameYMode => [ diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs index f265156a7..ca46def22 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs @@ -53,7 +53,7 @@ internal ref struct Av1SymbolDecoder this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex); } - public int ReadLiteral(int bitCount) + public int ReadCdfStrength(int bitCount) { ref Av1SymbolReader r = ref this.reader; return r.ReadLiteral(bitCount); @@ -130,22 +130,53 @@ internal ref struct Av1SymbolDecoder return r.ReadSymbol(this.skipMode[(int)blockSize]) > 0; } - public int ReadDeltaLoopFilterAbsolute() + public int ReadDeltaLoopFilter() { ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(this.deltaLoopFilterAbsolute); + int deltaLoopFilterAbsolute = r.ReadSymbol(this.deltaLoopFilterAbsolute); + if (deltaLoopFilterAbsolute == Av1Constants.DeltaLoopFilterSmall) + { + int deltaLoopFilterRemainingBits = r.ReadLiteral(3) + 1; + int deltaLoopFilterAbsoluteBitCount = r.ReadLiteral(deltaLoopFilterRemainingBits); + deltaLoopFilterAbsolute = deltaLoopFilterAbsoluteBitCount + (1 << deltaLoopFilterRemainingBits) + 1; + } + + bool deltaLoopFilterSign = true; + if (deltaLoopFilterAbsolute != 0) + { + deltaLoopFilterSign = r.ReadLiteral(1) > 0; + } + + return deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute; } - public int ReadDeltaQuantizerAbsolute() + /// + /// SVT: read_delta_qindex + /// + public int ReadDeltaQuantizerIndex() { ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(this.deltaQuantizerAbsolute); + int deltaQuantizerAbsolute = r.ReadSymbol(this.deltaQuantizerAbsolute); + if (deltaQuantizerAbsolute == Av1Constants.DeltaQuantizerSmall) + { + int deltaQuantizerRemainingBits = r.ReadLiteral(3) + 1; + int deltaQuantizerAbsoluteBase = r.ReadLiteral(deltaQuantizerRemainingBits); + deltaQuantizerAbsolute = deltaQuantizerAbsoluteBase + (1 << deltaQuantizerRemainingBits) + 1; + } + + bool deltaQuantizerSignBit = true; + if (deltaQuantizerAbsolute != 0) + { + deltaQuantizerSignBit = r.ReadLiteral(1) > 0; + } + + return deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute; } - public int ReadSegmentId(int ctx) + public int ReadSegmentId(int context) { ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(this.segmentId[ctx]); + return r.ReadSymbol(this.segmentId[context]); } public int ReadAngleDelta(Av1PredictionMode mode) @@ -154,16 +185,17 @@ internal ref struct Av1SymbolDecoder return r.ReadSymbol(this.angleDelta[(int)mode - 1]); } - public bool ReadUseFilterUltra(Av1BlockSize blockSize) + public Av1FilterIntraMode ReadFilterUltraMode(Av1BlockSize blockSize) { ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(this.filterIntra[(int)blockSize]) > 0; - } + Av1FilterIntraMode filterIntraMode = Av1FilterIntraMode.AllFilterIntraModes; + bool useFilterIntra = r.ReadSymbol(this.filterIntra[(int)blockSize]) > 0; + if (useFilterIntra) + { + filterIntraMode = (Av1FilterIntraMode)r.ReadSymbol(this.filterIntraMode); + } - public Av1FilterIntraMode ReadFilterUltraMode() - { - ref Av1SymbolReader r = ref this.reader; - return (Av1FilterIntraMode)r.ReadSymbol(this.filterIntraMode); + return filterIntraMode; } public Av1TransformSize ReadTransformSize(Av1BlockSize blockSize, int context) @@ -348,6 +380,7 @@ internal ref struct Av1SymbolDecoder public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformClass transformClass, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { + ref Av1SymbolReader r = ref this.reader; int endOfBlockExtra = 0; int endOfBlockPoint = this.ReadEndOfBlockFlag(planeType, transformClass, transformSize); int endOfBlockShift = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPoint]; @@ -362,7 +395,7 @@ internal ref struct Av1SymbolDecoder for (int j = 1; j < endOfBlockShift; j++) { - if (this.ReadLiteral(1) != 0) + if (r.ReadLiteral(1) != 0) { Av1Math.SetBit(ref endOfBlockExtra, endOfBlockShift - 1 - j); } @@ -451,6 +484,7 @@ internal ref struct Av1SymbolDecoder public int ReadCoefficientsSign(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType) { + ref Av1SymbolReader r = ref this.reader; int maxScanLine = 0; int culLevel = 0; int dcValue = 0; @@ -469,7 +503,7 @@ internal ref struct Av1SymbolDecoder } else { - sign = this.ReadLiteral(1); + sign = r.ReadLiteral(1); } if (level >= Av1Constants.CoefficientBaseRange + Av1Constants.BaseLevelsCount + 1) @@ -535,13 +569,14 @@ internal ref struct Av1SymbolDecoder internal int ReadGolomb() { + ref Av1SymbolReader r = ref this.reader; int x = 1; int length = 0; int i = 0; while (i == 0) { - i = this.ReadLiteral(1); + i = r.ReadLiteral(1); ++length; if (length > 20) { @@ -553,7 +588,7 @@ internal ref struct Av1SymbolDecoder for (i = 0; i < length - 1; ++i) { x <<= 1; - x += this.ReadLiteral(1); + x += r.ReadLiteral(1); } return x - 1; diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs index 1c9ef131c..4f98f446f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -330,19 +330,21 @@ internal class Av1SymbolEncoder : IDisposable w.WriteSymbol(skip, this.skipMode[context]); } - internal void WriteFilterIntra(Av1FilterIntraMode filterIntraMode, Av1BlockSize blockSize) + internal void WriteFilterIntraMode(Av1FilterIntraMode filterIntraMode, Av1BlockSize blockSize) { ref Av1SymbolWriter w = ref this.writer; - w.WriteSymbol(filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes, this.filterIntra[(int)blockSize]); - } - - internal void WriteFilterIntraMode(Av1FilterIntraMode filterIntraMode) - { - ref Av1SymbolWriter w = ref this.writer; - w.WriteSymbol((int)filterIntraMode, this.filterIntraMode); + bool useFilter = filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes; + w.WriteSymbol(useFilter, this.filterIntra[(int)blockSize]); + if (useFilter) + { + w.WriteSymbol((int)filterIntraMode, this.filterIntraMode); + } } - internal void WriteDeltaQIndex(int deltaQindex) + /// + /// SVT: av1_write_delta_q_index + /// + internal void WriteDeltaQuantizerIndex(int deltaQindex) { ref Av1SymbolWriter w = ref this.writer; bool sign = deltaQindex < 0; @@ -353,7 +355,7 @@ internal class Av1SymbolEncoder : IDisposable if (!smallval) { - int rem_bits = Av1Math.MostSignificantBit((uint)(abs - 1)) - 1; + int rem_bits = Av1Math.MostSignificantBit((uint)(abs - 1)); int threshold = (1 << rem_bits) + 1; w.WriteLiteral((uint)(rem_bits - 1), 3); w.WriteLiteral((uint)(abs - threshold), rem_bits); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index e4cf2f5fe..f6aad1019 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -1051,21 +1051,19 @@ internal class Av1TileReader : IAv1TileReader private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { + partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = false; if (this.SequenceHeader.EnableFilterIntra && partitionInfo.ModeInfo.YMode == Av1PredictionMode.DC && partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) == 0 && Math.Max(partitionInfo.ModeInfo.BlockSize.GetWidth(), partitionInfo.ModeInfo.BlockSize.GetHeight()) <= 32) { - partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = reader.ReadUseFilterUltra(partitionInfo.ModeInfo.BlockSize); - if (partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra) + Av1FilterIntraMode filterIntraMode = reader.ReadFilterUltraMode(partitionInfo.ModeInfo.BlockSize); + if (filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes) { - partitionInfo.ModeInfo.FilterIntraModeInfo.Mode = reader.ReadFilterUltraMode(); + partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = true; + partitionInfo.ModeInfo.FilterIntraModeInfo.Mode = filterIntraMode; } } - else - { - partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = false; - } } /// @@ -1209,7 +1207,8 @@ internal class Av1TileReader : IAv1TileReader int c = partitionInfo.ColumnIndex & cdefMask4; if (partitionInfo.CdefStrength[r][c] == -1) { - partitionInfo.CdefStrength[r][c] = reader.ReadLiteral(this.FrameHeader.CdefParameters.BitCount); + int cdfStrength = reader.ReadCdfStrength(this.FrameHeader.CdefParameters.BitCount); + partitionInfo.CdefStrength[r][c] = cdfStrength; if (this.SequenceHeader.SuperblockSize == Av1BlockSize.Block128x128) { int w4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); @@ -1218,7 +1217,7 @@ internal class Av1TileReader : IAv1TileReader { for (int j = c; j < c + w4; j += cdefSize4) { - partitionInfo.CdefStrength[i & cdefMask4][j & cdefMask4] = partitionInfo.CdefStrength[r][c]; + partitionInfo.CdefStrength[i & cdefMask4][j & cdefMask4] = cdfStrength; } } } @@ -1245,21 +1244,9 @@ internal class Av1TileReader : IAv1TileReader Span currentDeltaLoopFilter = partitionInfo.SuperblockInfo.SuperblockDeltaLoopFilter; for (int i = 0; i < frameLoopFilterCount; i++) { - int deltaLoopFilterAbsolute = reader.ReadDeltaLoopFilterAbsolute(); - if (deltaLoopFilterAbsolute == Av1Constants.DeltaLoopFilterSmall) - { - int deltaLoopFilterRemainingBits = reader.ReadLiteral(3) + 1; - int deltaLoopFilterAbsoluteBitCount = reader.ReadLiteral(deltaLoopFilterRemainingBits); - deltaLoopFilterAbsolute = deltaLoopFilterAbsoluteBitCount + (1 << deltaLoopFilterRemainingBits) + 1; - } - - if (deltaLoopFilterAbsolute != 0) - { - bool deltaLoopFilterSign = reader.ReadLiteral(1) > 0; - int reducedDeltaLoopFilterLevel = deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute; - int deltaLoopFilterResolution = this.FrameHeader.DeltaLoopFilterParameters.Resolution; - currentDeltaLoopFilter[i] = Av1Math.Clip3(-Av1Constants.MaxLoopFilter, Av1Constants.MaxLoopFilter, currentDeltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << deltaLoopFilterResolution)); - } + int reducedDeltaLoopFilterLevel = reader.ReadDeltaLoopFilter(); + int deltaLoopFilterResolution = this.FrameHeader.DeltaLoopFilterParameters.Resolution; + currentDeltaLoopFilter[i] = Av1Math.Clip3(-Av1Constants.MaxLoopFilter, Av1Constants.MaxLoopFilter, currentDeltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << deltaLoopFilterResolution)); } } } @@ -1280,6 +1267,9 @@ internal class Av1TileReader : IAv1TileReader } } + /// + /// SVT: read_delta_qindex + /// private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; @@ -1291,22 +1281,10 @@ internal class Av1TileReader : IAv1TileReader if (partitionInfo.ModeInfo.BlockSize != this.SequenceHeader.SuperblockSize || !partitionInfo.ModeInfo.Skip) { - int deltaQuantizerAbsolute = reader.ReadDeltaQuantizerAbsolute(); - if (deltaQuantizerAbsolute == Av1Constants.DeltaQuantizerSmall) - { - int deltaQuantizerRemainingBits = reader.ReadLiteral(3) + 1; - int deltaQuantizerAbsoluteBitCount = reader.ReadLiteral(deltaQuantizerRemainingBits); - deltaQuantizerAbsolute = deltaQuantizerRemainingBits + (1 << deltaQuantizerRemainingBits) + 1; - } - - if (deltaQuantizerAbsolute != 0) - { - bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0; - int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute; - int deltaQuantizerResolution = this.FrameHeader.DeltaQParameters.Resolution; - this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << deltaQuantizerResolution)); - partitionInfo.SuperblockInfo.SuperblockDeltaQ = this.currentQuantizerIndex; - } + int reducedDeltaQuantizerIndex = reader.ReadDeltaQuantizerIndex(); + int deltaQuantizerResolution = this.FrameHeader.DeltaQParameters.Resolution; + this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << deltaQuantizerResolution)); + partitionInfo.SuperblockInfo.SuperblockDeltaQ = this.currentQuantizerIndex; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs index c405cbd05..2ee25e9bd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs @@ -449,7 +449,7 @@ internal partial class Av1TileWriter int reduced_delta_qindex = (current_q_index - pcs.Parent.PreviousQIndex[tile_idx]) / frm_hdr.DeltaQParameters.Resolution; - writer.WriteDeltaQIndex(reduced_delta_qindex); + writer.WriteDeltaQuantizerIndex(reduced_delta_qindex); pcs.Parent.PreviousQIndex[tile_idx] = current_q_index; } } @@ -495,11 +495,7 @@ internal partial class Av1TileWriter if (!macroBlockModeInfo.Block.UseIntraBlockCopy && IsFilterIntraAllowed(scs.SequenceHeader.FilterIntraLevel > 0, blockSize, blk_ptr.PaletteSize[0], intra_luma_mode)) { - writer.WriteFilterIntra(blk_ptr.FilterIntraMode, blockSize); - if (blk_ptr.FilterIntraMode != Av1FilterIntraMode.AllFilterIntraModes) - { - writer.WriteFilterIntraMode(blk_ptr.FilterIntraMode); - } + writer.WriteFilterIntraMode(blk_ptr.FilterIntraMode, blockSize); } if (!macroBlockModeInfo.Block.UseIntraBlockCopy) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs index cabcc7ae4..cfe14009d 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs @@ -191,11 +191,11 @@ public class Av1EntropyTests Assert.Equal(expectedValues, values); } - [Fact] - public void RoundTripPartitionType() + [Theory] + [MemberData(nameof(GetRangeData), 20)] + public void RoundTripPartitionType(int context) { // Assign - int ctx = 7; Configuration configuration = Configuration.Default; Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); Av1PartitionType[] values = [ @@ -206,7 +206,7 @@ public class Av1EntropyTests // Act foreach (Av1PartitionType value in values) { - encoder.WritePartitionType(value, 7); + encoder.WritePartitionType(value, context); } using IMemoryOwner encoded = encoder.Exit(); @@ -214,7 +214,7 @@ public class Av1EntropyTests Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); for (int i = 0; i < values.Length; i++) { - actuals[i] = decoder.ReadPartitionType(ctx); + actuals[i] = decoder.ReadPartitionType(context); } // Assert @@ -222,16 +222,11 @@ public class Av1EntropyTests } [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) + [MemberData(nameof(GetSplitPartitionTypeData))] + public void RoundTripSplitOrHorizontalPartitionType(int size, int context) { // Assign + Av1BlockSize blockSize = (Av1BlockSize)size; Configuration configuration = Configuration.Default; Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); Av1PartitionType[] values = [ @@ -242,7 +237,7 @@ public class Av1EntropyTests // Act foreach (Av1PartitionType value in values) { - encoder.WriteSplitOrHorizontal(value, (Av1BlockSize)blockSize, context); + encoder.WriteSplitOrHorizontal(value, blockSize, context); } using IMemoryOwner encoded = encoder.Exit(); @@ -250,7 +245,7 @@ public class Av1EntropyTests Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); for (int i = 0; i < values.Length; i++) { - actuals[i] = decoder.ReadSplitOrHorizontal((Av1BlockSize)blockSize, context); + actuals[i] = decoder.ReadSplitOrHorizontal(blockSize, context); } // Assert @@ -258,16 +253,11 @@ public class Av1EntropyTests } [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) + [MemberData(nameof(GetSplitPartitionTypeData))] + public void RoundTripSplitOrVerticalPartitionType(int size, int context) { // Assign + Av1BlockSize blockSize = (Av1BlockSize)size; Configuration configuration = Configuration.Default; Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); Av1PartitionType[] values = [ @@ -278,7 +268,7 @@ public class Av1EntropyTests // Act foreach (Av1PartitionType value in values) { - encoder.WriteSplitOrVertical(value, (Av1BlockSize)blockSize, context); + encoder.WriteSplitOrVertical(value, blockSize, context); } using IMemoryOwner encoded = encoder.Exit(); @@ -286,19 +276,49 @@ public class Av1EntropyTests Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); for (int i = 0; i < values.Length; i++) { - actuals[i] = decoder.ReadSplitOrVertical((Av1BlockSize)blockSize, context); + actuals[i] = decoder.ReadSplitOrVertical(blockSize, context); } // Assert Assert.Equal(values, actuals); } - [Fact] - public void RoundTripTransformBlockSkip() + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void RoundTripSkip(int context) { // Assign - const Av1TransformSize transformSizeContext = Av1TransformSize.Size4x4; - const int skipContext = 0; + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); + bool[] values = [true, true, false, false, false, false, false, false, true]; + bool[] actuals = new bool[values.Length]; + + // Act + foreach (bool value in values) + { + encoder.WriteSkip(value, context); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadSkip(context); + } + + // Assert + Assert.Equal(values, actuals); + } + + [Theory] + [MemberData(nameof(GetTransformBlockSkipData))] + internal void RoundTripTransformBlockSkip(int transformContext, int skipContext) + { + // Assign + Av1TransformSize transformSizeContext = (Av1TransformSize)transformContext; Configuration configuration = Configuration.Default; Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); bool[] values = [true, true, false, false, false, false, false, false, true]; @@ -322,13 +342,14 @@ public class Av1EntropyTests Assert.Equal(values, actuals); } - [Fact] - public void RoundTripTransformType() + // [Theory] + [MemberData(nameof(GetTransformTypeData))] + public void RoundTripTransformType(int txSizeContext, int intraMode, int intraDir) { // Assign - const Av1TransformSize transformSizeContext = Av1TransformSize.Size4x4; - const Av1FilterIntraMode filterIntraMode = Av1FilterIntraMode.DC; - const Av1PredictionMode intraDirection = Av1PredictionMode.DC; + Av1TransformSize transformSizeContext = (Av1TransformSize)txSizeContext; + Av1FilterIntraMode filterIntraMode = (Av1FilterIntraMode)intraMode; + Av1PredictionMode intraDirection = (Av1PredictionMode)intraDir; Configuration configuration = Configuration.Default; Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); @@ -336,7 +357,7 @@ public class Av1EntropyTests Av1TransformType[] values = [ Av1TransformType.DctDct, Av1TransformType.DctDct, Av1TransformType.Identity, Av1TransformType.AdstDct, Av1TransformType.DctDct, Av1TransformType.AdstAdst, Av1TransformType.Identity, Av1TransformType.DctAdst - ]; + ]; Av1TransformType[] actuals = new Av1TransformType[values.Length]; // Act @@ -357,25 +378,26 @@ public class Av1EntropyTests Assert.Equal(values, actuals); } - [Fact] - public void RoundTripEndOfBlockPosition() + [Theory] + [MemberData(nameof(GetEndOfBlockPositionData))] + public void RoundTripEndOfBlockPosition(int txSize, int txSizeContext, int plane, int txClass) { // Assign - const Av1TransformSize transformSize = Av1TransformSize.Size4x4; - const Av1TransformSize transformSizeContext = Av1TransformSize.Size4x4; - const Av1ComponentType componentType = Av1ComponentType.Luminance; - const Av1PlaneType planeType = Av1PlaneType.Y; - const Av1TransformClass transformClass = Av1TransformClass.Class2D; + Av1TransformSize transformSize = (Av1TransformSize)txSize; + Av1TransformSize transformSizeContext = (Av1TransformSize)txSizeContext; + Av1ComponentType componentType = (Av1ComponentType)plane; + Av1PlaneType planeType = (Av1PlaneType)plane; + Av1TransformClass transformClass = (Av1TransformClass)txClass; Configuration configuration = Configuration.Default; Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); - ushort[] values = [1, 2, 3, 4, 5]; + int[] values = [1, 2, 3, 4, 5]; int[] actuals = new int[values.Length]; // Act - foreach (ushort value in values) + foreach (int value in values) { - encoder.WriteEndOfBlockPosition(value, componentType, transformClass, transformSize, transformSizeContext); + encoder.WriteEndOfBlockPosition((ushort)value, componentType, transformClass, transformSize, transformSizeContext); } using IMemoryOwner encoded = encoder.Exit(); @@ -387,7 +409,7 @@ public class Av1EntropyTests } // Assert - Assert.Equal(values.Select(x => (int)x).ToArray(), actuals); + Assert.Equal(values, actuals); } [Fact] @@ -418,6 +440,94 @@ public class Av1EntropyTests Assert.Equal(values, actuals); } + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void RoundTripSegmentId(int context) + { + // Assign + int[] values = [3, 6, 7, 0, 2, 0, 2, 1, 1]; + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); + int[] actuals = new int[values.Length]; + + // Act + foreach (int value in values) + { + encoder.WriteSegmentId(value, context); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadSegmentId(context); + } + + // Assert + Assert.Equal(values, actuals); + } + + [Fact] + public void RoundTripDeltaQuantizerIndex() + { + // Assign + int[] values = [3, 6, -7, -8, -2, 0, 2, 1, -1]; + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); + int[] actuals = new int[values.Length]; + + // Act + foreach (int value in values) + { + encoder.WriteDeltaQuantizerIndex(value); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadDeltaQuantizerIndex(); + } + + // Assert + Assert.Equal(values, actuals); + } + + [Theory] + [MemberData(nameof(GetRangeData), (int)Av1BlockSize.AllSizes)] + public void RoundTripFilterIntraMode(int bSize) + { + // Assign + Av1BlockSize blockSize = (Av1BlockSize)bSize; + Av1FilterIntraMode[] values = [ + Av1FilterIntraMode.DC, Av1FilterIntraMode.Vertical, Av1FilterIntraMode.DC, Av1FilterIntraMode.Paeth, + Av1FilterIntraMode.AllFilterIntraModes, Av1FilterIntraMode.Directional157, Av1FilterIntraMode.DC, Av1FilterIntraMode.Directional157]; + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); + Av1FilterIntraMode[] actuals = new Av1FilterIntraMode[values.Length]; + + // Act + foreach (Av1FilterIntraMode value in values) + { + encoder.WriteFilterIntraMode(value, blockSize); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), BaseQIndex); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadFilterUltraMode(blockSize); + } + + // Assert + Assert.Equal(values, actuals); + } + [Fact] public void RoundTripUseIntraBlockCopy() { @@ -444,4 +554,80 @@ public class Av1EntropyTests // Assert Assert.Equal(values, actuals); } + + public static TheoryData GetRangeData(int count) + { + TheoryData result = []; + for (int i = 0; i < count; i++) + { + result.Add(i); + } + + return result; + } + + public static TheoryData GetTransformBlockSkipData() + { + TheoryData result = []; + for (Av1TransformSize transformSizeContext = Av1TransformSize.Size4x4; transformSizeContext <= Av1TransformSize.Size64x64; transformSizeContext++) + { + for (int skipContext = 0; skipContext < 5; skipContext++) + { + result.Add((int)transformSizeContext, skipContext); + } + } + + return result; + } + + public static TheoryData GetSplitPartitionTypeData() + { + TheoryData result = []; + for (Av1BlockSize blockSize = Av1BlockSize.Block4x4; blockSize < Av1BlockSize.AllSizes; blockSize++) + { + for (int context = 4; context < 16; context++) + { + result.Add((int)blockSize, context); + } + } + + return result; + } + + public static TheoryData GetTransformTypeData() + { + TheoryData result = []; + for (Av1TransformSize transformSize = Av1TransformSize.Size4x4; transformSize < Av1TransformSize.AllSizes; transformSize++) + { + for (Av1FilterIntraMode filterIntraMode = Av1FilterIntraMode.DC; filterIntraMode <= Av1FilterIntraMode.AllFilterIntraModes; filterIntraMode++) + { + for (Av1PredictionMode intraDirection = Av1PredictionMode.IntraModeStart; intraDirection < Av1PredictionMode.IntraModeEnd; intraDirection++) + { + result.Add((int)transformSize, (int)filterIntraMode, (int)intraDirection); + } + } + } + + return result; + } + + public static TheoryData GetEndOfBlockPositionData() + { + TheoryData result = []; + for (Av1TransformSize transformSize = Av1TransformSize.Size4x4; transformSize < Av1TransformSize.AllSizes; transformSize++) + { + for (Av1TransformSize transformSizeContext = Av1TransformSize.Size4x4; transformSizeContext <= Av1TransformSize.Size64x64; transformSizeContext++) + { + for (int componentType = 0; componentType < 2; componentType++) + { + for (Av1TransformClass transformClass = Av1TransformClass.Class2D; transformClass <= Av1TransformClass.ClassVertical; transformClass++) + { + result.Add((int)transformSize, (int)transformSizeContext, componentType, (int)transformClass); + } + } + } + } + + return result; + } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SymbolContextTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SymbolContextTests.cs index f21aa5715..2a7bf4e68 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SymbolContextTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1SymbolContextTests.cs @@ -69,9 +69,9 @@ public class Av1SymbolContextTests for (Av1TransformSetType setType = Av1TransformSetType.DctOnly; setType <= Av1TransformSetType.All16; setType++) { int count = Av1SymbolContextHelper.GetExtendedTransformTypeCount(setType); - for (int type = 1; type < count; type++) + for (int index = 1; index < count; index++) { - result.Add((int)setType, type); + result.Add((int)setType, index); } }