diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs index c70c189a55..7ad04f6bba 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs @@ -9,7 +9,7 @@ internal class ObuColorConfig public int ChannelCount { get; set; } - public bool Monochrome { get; set; } + public bool IsMonochrome { get; set; } public ObuColorPrimaries ColorPrimaries { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs index 2742264498..d3d2194a2e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs @@ -113,4 +113,9 @@ internal static class ObuConstants /// Maximum magnitude of AngleDeltaY and AngleDeltaUV. /// public const int MaxAngleDelta = 3; + + /// + /// Number of segments allowed in segmentation map. + /// + public const int MaxSegments = 8; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 756f71f1f9..fcae54d647 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -263,8 +263,8 @@ internal class ObuReader sequenceHeader.EnableDualFilter = false; sequenceHeader.OrderHintInfo.EnableJointCompound = false; sequenceHeader.OrderHintInfo.EnableReferenceFrameMotionVectors = false; - sequenceHeader.SequenceForceScreenContentTools = 2; - sequenceHeader.SequenceForceIntegerMotionVector = 2; + sequenceHeader.ForceScreenContentTools = 2; + sequenceHeader.ForceIntegerMotionVector = 2; sequenceHeader.OrderHintInfo.OrderHintBits = 0; // Video related flags removed @@ -280,13 +280,13 @@ internal class ObuReader { ObuColorConfig colorConfig = new(); ReadBitDepth(ref reader, colorConfig, sequenceHeader); - colorConfig.Monochrome = false; + colorConfig.IsMonochrome = false; if (sequenceHeader.SequenceProfile != ObuSequenceProfile.High) { - colorConfig.Monochrome = reader.ReadBoolean(); + colorConfig.IsMonochrome = reader.ReadBoolean(); } - colorConfig.ChannelCount = colorConfig.Monochrome ? 1 : 3; + colorConfig.ChannelCount = colorConfig.IsMonochrome ? 1 : 3; colorConfig.IsColorDescriptionPresent = reader.ReadBoolean(); colorConfig.ColorPrimaries = ObuColorPrimaries.Unspecified; colorConfig.TransferCharacteristics = ObuTransferCharacteristics.Unspecified; @@ -303,7 +303,7 @@ internal class ObuReader colorConfig.SubSamplingY = false; colorConfig.ChromaSamplePosition = ObuChromoSamplePosition.Unknown; colorConfig.HasSeparateUvDelta = false; - if (colorConfig.Monochrome) + if (colorConfig.IsMonochrome) { colorConfig.ColorRange = reader.ReadBoolean(); colorConfig.SubSamplingX = true; @@ -669,7 +669,7 @@ internal class ObuReader } frameInfo.DisableCdfUpdate = reader.ReadBoolean(); - frameInfo.AllowScreenContentTools = sequenceHeader.SequenceForceScreenContentTools == 1; + frameInfo.AllowScreenContentTools = sequenceHeader.ForceScreenContentTools == 1; if (frameInfo.AllowScreenContentTools) { frameInfo.AllowScreenContentTools = reader.ReadBoolean(); @@ -677,13 +677,13 @@ internal class ObuReader if (frameInfo.AllowScreenContentTools) { - if (sequenceHeader.SequenceForceIntegerMotionVector == 1) + if (sequenceHeader.ForceIntegerMotionVector == 1) { frameInfo.ForceIntegerMotionVector = reader.ReadBoolean(); } else { - frameInfo.ForceIntegerMotionVector = sequenceHeader.SequenceForceIntegerMotionVector != 0; + frameInfo.ForceIntegerMotionVector = sequenceHeader.ForceIntegerMotionVector != 0; } } else @@ -899,7 +899,7 @@ internal class ObuReader } private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) - => segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; + => segmentationParameters.Enabled && segmentationParameters.IsFeatureActive(segmentId, feature); private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) { @@ -919,7 +919,7 @@ internal class ObuReader { ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; ObuFrameHeader frameInfo = decoder.FrameInfo; - int planeCount = sequenceHeader.ColorConfig.Monochrome ? 1 : 3; + int planeCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; int startBitPosition = reader.BitPosition; ReadUncompressedFrameHeader(ref reader, decoder, header, planeCount); if (trailingBit) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs deleted file mode 100644 index 1cfade4063..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; - -internal enum ObuSegmentationFeature -{ - None = 0, - LevelSkip, -} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs index be428efc32..c74a698b2f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs @@ -17,6 +17,6 @@ internal class ObuSegmentationParameters public int LastActiveSegmentId { get; internal set; } - internal bool IsFeatureActive(int segmentId, ObuSegmentationFeature feature) + internal bool IsFeatureActive(int segmentId, ObuSegmentationLevelFeature feature) => this.FeatureEnabled[segmentId, (int)feature]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs index 8bb6a11215..0b970091af 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs @@ -55,9 +55,9 @@ internal class ObuSequenceHeader public bool EnableDualFilter { get; set; } - public int SequenceForceIntegerMotionVector { get; set; } + public int ForceIntegerMotionVector { get; set; } - public int SequenceForceScreenContentTools { get; set; } + public int ForceScreenContentTools { get; set; } public bool EnableSuperResolution { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 80b0675850..45a05653d8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -109,11 +109,11 @@ internal class ObuWriter WriteBitDepth(ref writer, colorConfig, sequenceHeader); if (sequenceHeader.SequenceProfile != ObuSequenceProfile.High) { - writer.WriteBoolean(colorConfig.Monochrome); + writer.WriteBoolean(colorConfig.IsMonochrome); } writer.WriteBoolean(false); // colorConfig.IsColorDescriptionPresent - if (colorConfig.Monochrome) + if (colorConfig.IsMonochrome) { writer.WriteBoolean(colorConfig.ColorRange); return; @@ -321,7 +321,7 @@ internal class ObuWriter if (frameInfo.AllowScreenContentTools) { - if (sequenceHeader.SequenceForceIntegerMotionVector == 1) + if (sequenceHeader.ForceIntegerMotionVector == 1) { writer.WriteBoolean(frameInfo.ForceIntegerMotionVector); } @@ -448,7 +448,7 @@ internal class ObuWriter { ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; ObuFrameHeader frameInfo = decoder.FrameInfo; - int planeCount = sequenceHeader.ColorConfig.Monochrome ? 1 : 3; + int planeCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; int startBitPosition = writer.BitPosition; WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameInfo, planeCount); if (writeTrailingBits) diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1BlockModeInfo.cs new file mode 100644 index 0000000000..27ba271ab3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1BlockModeInfo.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1BlockModeInfo +{ + private int[] paletteSize; + + public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize) + { + this.BlockSize = blockSize; + this.AngleDelta = new int[numPlanes]; + this.paletteSize = new int[numPlanes - 1]; + this.FilterIntraModeInfo = new(); + } + + public Av1BlockSize BlockSize { get; } + + public Av1PredictionMode YMode { get; set; } + + public bool Skip { get; set; } + + public Av1PartitionType PartitionType { get; } + + public bool SkipMode { get; set; } + + public int SegmentId { get; set; } + + public Av1PredictionMode UvMode { get; set; } + + public bool UseUltraBlockCopy { get; set; } + + public int ChromaFromLumaAlphaIndex { get; set; } + + public int ChromaFromLumaAlphaSign { get; set; } + + public int[] AngleDelta { get; set; } + + public Size IndexInSuperblock { get; set; } + + public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; } + + public int GetPaletteSize(Av1PlaneType planeType) => this.paletteSize[(int)planeType]; + + public void SetPaletteSizes(int ySize, int uvSize) => this.paletteSize = [ySize, uvSize]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs index 3b9f555ef5..cb65bfafe4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs @@ -126,4 +126,49 @@ 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)]; + + public static Av1Distribution[][] KeyFrameYMode => + [ + [ + new(15588, 17027, 19338, 20218, 20682, 21110, 21825, 23244, 24189, 28165, 29093, 30466), + new(12016, 18066, 19516, 20303, 20719, 21444, 21888, 23032, 24434, 28658, 30172, 31409), + new(10052, 10771, 22296, 22788, 23055, 23239, 24133, 25620, 26160, 29336, 29929, 31567), + new(14091, 15406, 16442, 18808, 19136, 19546, 19998, 22096, 24746, 29585, 30958, 32462), + new(12122, 13265, 15603, 16501, 18609, 20033, 22391, 25583, 26437, 30261, 31073, 32475), + ], [ + new(10023, 19585, 20848, 21440, 21832, 22760, 23089, 24023, 25381, 29014, 30482, 31436), + new(5983, 24099, 24560, 24886, 25066, 25795, 25913, 26423, 27610, 29905, 31276, 31794), + new(7444, 12781, 20177, 20728, 21077, 21607, 22170, 23405, 24469, 27915, 29090, 30492), + new(8537, 14689, 15432, 17087, 17408, 18172, 18408, 19825, 24649, 29153, 31096, 32210), + new(7543, 14231, 15496, 16195, 17905, 20717, 21984, 24516, 26001, 29675, 30981, 31994) + ], [ + new(12613, 13591, 21383, 22004, 22312, 22577, 23401, 25055, 25729, 29538, 30305, 32077), + new(9687, 13470, 18506, 19230, 19604, 20147, 20695, 22062, 23219, 27743, 29211, 30907), + new(6183, 6505, 26024, 26252, 26366, 26434, 27082, 28354, 28555, 30467, 30794, 32086), + new(10718, 11734, 14954, 17224, 17565, 17924, 18561, 21523, 23878, 28975, 30287, 32252), + new(9194, 9858, 16501, 17263, 18424, 19171, 21563, 25961, 26561, 30072, 30737, 32463) + ], [ + new(12602, 14399, 15488, 18381, 18778, 19315, 19724, 21419, 25060, 29696, 30917, 32409), + new(8203, 13821, 14524, 17105, 17439, 18131, 18404, 19468, 25225, 29485, 31158, 32342), + new(8451, 9731, 15004, 17643, 18012, 18425, 19070, 21538, 24605, 29118, 30078, 32018), + new(7714, 9048, 9516, 16667, 16817, 16994, 17153, 18767, 26743, 30389, 31536, 32528), + new(8843, 10280, 11496, 15317, 16652, 17943, 19108, 22718, 25769, 29953, 30983, 32485) + ], [ + new(12578, 13671, 15979, 16834, 19075, 20913, 22989, 25449, 26219, 30214, 31150, 32477), + new(9563, 13626, 15080, 15892, 17756, 20863, 22207, 24236, 25380, 29653, 31143, 32277), + new(8356, 8901, 17616, 18256, 19350, 20106, 22598, 25947, 26466, 29900, 30523, 32261), + new(10835, 11815, 13124, 16042, 17018, 18039, 18947, 22753, 24615, 29489, 30883, 32482), + new(7618, 8288, 9859, 10509, 15386, 18657, 22903, 28776, 29180, 31355, 31802, 32593) + ] + ]; + + public static Av1Distribution FilterIntraMode => new(8949, 12776, 17211, 29558); + + public static Av1Distribution[] FilterIntra => + [ + new(4621), new(6743), new(5893), new(7866), new(12551), new(9394), + new(12408), new(14301), new(12756), new(22343), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(12770), new(10368), + new(20229), new(18101), new(16384), new(16384) + ]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1FilterIntraMode.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1FilterIntraMode.cs new file mode 100644 index 0000000000..b93b8522df --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1FilterIntraMode.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal enum Av1FilterIntraMode +{ + DC, + Vertical, + Horizontal, + Directional157, + Paeth +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1IntraFilterModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1IntraFilterModeInfo.cs new file mode 100644 index 0000000000..6adb43dabd --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1IntraFilterModeInfo.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1IntraFilterModeInfo +{ + public bool UseFilterIntra { get; set; } + + public Av1FilterIntraMode Mode { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs index f783c9cdda..e213005dff 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs @@ -5,7 +5,13 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1ParseAboveContext { - public int PartitionWidth { get; internal set; } + public int PartitionWidth { get; set; } - internal void Clear() => throw new NotImplementedException(); + public int[][] AboveContext { get; set; } = []; + + internal void Clear(int startColumnIndex, int endColumnIndex) + { + this.PartitionWidth = -1; + this.AboveContext = []; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs index 7f521c273e..01219dcfcb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs @@ -5,7 +5,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1ParseLeftContext { - public int PartitionHeight { get; internal set; } + public int PartitionHeight { get; set; } + + public int[][] LeftContext { get; set; } = []; internal void Clear() => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PartitionInfo.cs new file mode 100644 index 0000000000..d6dc9ffa65 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PartitionInfo.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1PartitionInfo +{ + public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo, bool isChroma, Av1PartitionType partitionType) + { + this.ModeInfo = modeInfo; + this.SuperblockInfo = superblockInfo; + this.IsChroma = isChroma; + this.PartitionType = partitionType; + this.CdefStrength = []; + this.ReferenceFrame = [-1, -1]; + } + + public Av1BlockModeInfo ModeInfo { get; } + + public Av1SuperblockInfo SuperblockInfo { get; } + + public bool IsChroma { get; } + + public Av1PartitionType PartitionType { get; } + + public bool AvailableUp { get; set; } + + public bool AvailableLeft { get; set; } + + public bool AvailableUpForChroma { get; set; } + + public bool AvailableLeftForChroma { get; set; } + + public int ColumnIndex { get; set; } + + public int RowIndex { get; set; } + + public Av1BlockModeInfo? AboveModeInfo { get; set; } + + public Av1BlockModeInfo? LeftModeInfo { get; set; } + + public int[][] CdefStrength { get; set; } + + public int[] ReferenceFrame { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PlaneType.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PlaneType.cs new file mode 100644 index 0000000000..d41e4e6e10 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PlaneType.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal enum Av1PlaneType : int +{ + Y, + Uv +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs new file mode 100644 index 0000000000..fccc6f6079 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1SuperblockInfo +{ + public int[] SuperblockDeltaQ { get; internal set; } = []; + + public Av1BlockModeInfo GetModeInfo(int rowIndex, int columnIndex) => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs index faec9c8214..3d76994391 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs @@ -7,15 +7,19 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal ref struct Av1SymbolDecoder { + private static readonly int[] IntraModeContext = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; + private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; - private readonly Av1Distribution[] frameYMode = Av1DefaultDistributions.FrameYMode; + private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode; private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode; private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; private readonly Av1Distribution deltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute; private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute; private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId; private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta; + private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode; + private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra; private Av1SymbolReader reader; public Av1SymbolDecoder(Span tileData) => this.reader = new Av1SymbolReader(tileData); @@ -76,17 +80,31 @@ internal ref struct Av1SymbolDecoder return r.ReadSymbol(distribution) > 0; } - public Av1PredictionMode ReadIntraFrameYMode(Av1BlockSize blockSize) + public Av1PredictionMode ReadYMode(Av1BlockModeInfo? aboveModeInfo, Av1BlockModeInfo? leftModeInfo) { ref Av1SymbolReader r = ref this.reader; - return (Av1PredictionMode)r.ReadSymbol(this.frameYMode[(int)blockSize]); + Av1PredictionMode aboveMode = Av1PredictionMode.DC; + if (aboveModeInfo != null) + { + aboveMode = aboveModeInfo.YMode; + } + + Av1PredictionMode leftMode = Av1PredictionMode.DC; + if (leftModeInfo != null) + { + leftMode = leftModeInfo.YMode; + } + + int aboveContext = IntraModeContext[(int)aboveMode]; + int leftContext = IntraModeContext[(int)leftMode]; + return (Av1PredictionMode)r.ReadSymbol(this.keyFrameYMode[aboveContext][leftContext]); } - public Av1PredictionMode ReadUvMode(Av1BlockSize blockSize, bool chromaFromLumaAllowed) + public Av1PredictionMode ReadIntraModeUv(Av1PredictionMode mode, bool chromaFromLumaAllowed) { int chromaForLumaIndex = chromaFromLumaAllowed ? 1 : 0; ref Av1SymbolReader r = ref this.reader; - return (Av1PredictionMode)r.ReadSymbol(this.uvMode[chromaForLumaIndex][(int)blockSize]); + return (Av1PredictionMode)r.ReadSymbol(this.uvMode[chromaForLumaIndex][(int)mode]); } public bool ReadSkip(int ctx) @@ -119,9 +137,17 @@ internal ref struct Av1SymbolDecoder return r.ReadSymbol(this.angleDelta[((int)mode) - 1]); } - public bool ReadUseFilterUltra() => throw new NotImplementedException(); + public bool ReadUseFilterUltra(Av1BlockSize blockSize) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.filterIntra[(int)blockSize]) > 0; + } - public object ReadFilterUltraMode() => throw new NotImplementedException(); + public Av1FilterIntraMode ReadFilterUltraMode() + { + ref Av1SymbolReader r = ref this.reader; + return (Av1FilterIntraMode)r.ReadSymbol(this.filterIntraMode); + } private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) => probability[(int)element - 1] - probability[(int)element]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs index 6c8a6bbff2..00812e521b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs @@ -18,37 +18,16 @@ internal class Av1TileDecoder : IAv1TileDecoder private bool[][][] blockDecoded = []; private int[][] referenceSgrXqd = []; private int[][][] referenceLrWiener = []; - private bool availableUp; - private bool availableLeft; - private bool availableUpForChroma; - private bool availableLeftForChroma; private Av1ParseAboveContext aboveContext = new(); private Av1ParseLeftContext leftContext = new(); - private bool skip; - private bool readDeltas; private int currentQuantizerIndex; - private Av1PredictionMode[][] yModes = []; - private Av1PredictionMode yMode = Av1PredictionMode.DC; - private Av1PredictionMode uvMode = Av1PredictionMode.DC; - private Av1PredictionMode[][] uvModes = []; - private object[] referenceFrame = []; - private object[][][] referenceFrames = []; - private int paletteSizeY; - private int paletteSizeUv; - private object[][] aboveLevelContext = []; - private object[][] aboveDcContext = []; - private object[][] leftLevelContext = []; - private object[][] leftDcContext = []; - private Av1TransformSize transformSize = Av1TransformSize.Size4x4; - private object filterUltraMode = -1; - private int angleDeltaY; - private int angleDeltaUv; - private bool lossless; + private int[][] aboveLevelContext = []; + private int[][] aboveDcContext = []; + private int[][] leftLevelContext = []; + private int[][] leftDcContext = []; private int[][] segmentIds = []; private int maxLumaWidth; private int maxLumaHeight; - private int segmentId; - private int[][] cdefIndex = []; private int deltaLoopFilterResolution = -1; private int deltaQuantizerResolution = -1; @@ -74,9 +53,10 @@ internal class Av1TileDecoder : IAv1TileDecoder public void DecodeTile(Span tileData, int tileNum) { Av1SymbolDecoder reader = new(tileData); - int tileRowIndex = tileNum / this.TileInfo.TileColumnCount; int tileColumnIndex = tileNum % this.TileInfo.TileColumnCount; - this.aboveContext.Clear(); + int tileRowIndex = tileNum / this.TileInfo.TileColumnCount; + + this.aboveContext.Clear(this.TileInfo.TileColumnStartModeInfo[tileColumnIndex], this.TileInfo.TileColumnStartModeInfo[tileColumnIndex - 1]); this.ClearLoopFilterDelta(); int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; this.referenceSgrXqd = new int[planesCount][]; @@ -111,12 +91,13 @@ internal class Av1TileDecoder : IAv1TileDecoder // this.ClearCdef(row, column); this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); this.ReadLoopRestoration(row, column, superBlockSize); - this.DecodePartition(ref reader, row, column, superBlockSize); + this.ParsePartition(ref reader, row, column, superBlockSize); } } } - private void ClearLoopFilterDelta() => this.deltaLoopFilter = new int[4]; + private void ClearLoopFilterDelta() + => this.deltaLoopFilter = new int[4]; private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) { @@ -169,16 +150,17 @@ internal class Av1TileDecoder : IAv1TileDecoder // TODO: Implement } - private void DecodePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + private void ParsePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) { + Av1SuperblockInfo superblockInfo = new(); if (rowIndex >= this.TileInfo.TileRowStartModeInfo[rowIndex] || columnIndex >= this.TileInfo.TileColumnStartModeInfo[columnIndex]) { return; } - this.availableUp = this.IsInside(rowIndex - 1, columnIndex); - this.availableLeft = this.IsInside(rowIndex, columnIndex - 1); + bool availableUp = this.IsInside(rowIndex - 1, columnIndex); + bool availableLeft = this.IsInside(rowIndex, columnIndex - 1); int block4x4Size = blockSize.Get4x4WideCount(); int halfBlock4x4Size = block4x4Size >> 1; int quarterBlock4x4Size = halfBlock4x4Size >> 2; @@ -212,82 +194,86 @@ internal class Av1TileDecoder : IAv1TileDecoder switch (partitionType) { case Av1PartitionType.Split: - this.DecodePartition(ref reader, rowIndex, columnIndex, subSize); - this.DecodePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize); - this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize); - this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize); + this.ParsePartition(ref reader, rowIndex, columnIndex, subSize); + this.ParsePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize); + this.ParsePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize); + this.ParsePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize); break; case Av1PartitionType.None: - this.DecodeBlock(ref reader, rowIndex, columnIndex, subSize); + this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.None); break; default: throw new NotImplementedException($"Partition type: {partitionType} is not supported."); } } - private void DecodeBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + private void ParseBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1PartitionType partitionType) { int block4x4Width = blockSize.Get4x4WideCount(); int block4x4Height = blockSize.Get4x4HighCount(); int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; - bool hasChroma = planesCount > 1; - if (block4x4Height == 1 && this.SequenceHeader.ColorConfig.SubSamplingY && (rowIndex & 0x1) == 0) - { - hasChroma = false; - } - - if (block4x4Width == 1 && this.SequenceHeader.ColorConfig.SubSamplingX && (columnIndex & 0x1) == 0) - { - hasChroma = false; - } - - this.availableUp = this.IsInside(rowIndex - 1, columnIndex); - this.availableLeft = this.IsInside(rowIndex, columnIndex - 1); - this.availableUpForChroma = this.availableUp; - this.availableLeftForChroma = this.availableLeft; + Av1BlockModeInfo blockModeInfo = new(planesCount, blockSize); + bool hasChroma = this.HasChroma(rowIndex, columnIndex, blockSize); + Av1PartitionInfo partitionInfo = new(blockModeInfo, superblockInfo, hasChroma, partitionType); + partitionInfo.ColumnIndex = columnIndex; + partitionInfo.RowIndex = rowIndex; + partitionInfo.AvailableUp = this.IsInside(rowIndex - 1, columnIndex); + partitionInfo.AvailableLeft = this.IsInside(rowIndex, columnIndex - 1); + partitionInfo.AvailableUpForChroma = partitionInfo.AvailableUp; + partitionInfo.AvailableLeftForChroma = partitionInfo.AvailableLeft; if (hasChroma) { if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1) { - this.availableUpForChroma = this.IsInside(rowIndex - 2, columnIndex); + partitionInfo.AvailableUpForChroma = this.IsInside(rowIndex - 2, columnIndex); } if (this.SequenceHeader.ColorConfig.SubSamplingX && block4x4Width == 1) { - this.availableLeftForChroma = this.IsInside(rowIndex, columnIndex - 2); + partitionInfo.AvailableLeftForChroma = this.IsInside(rowIndex, columnIndex - 2); } } - this.ReadModeInfo(ref reader, rowIndex, columnIndex, blockSize); - this.ReadPaletteTokens(ref reader); - ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); - if (this.skip) + if (partitionInfo.AvailableUp) { - this.ResetBlockContext(rowIndex, columnIndex, blockSize); + partitionInfo.AboveModeInfo = superblockInfo.GetModeInfo(rowIndex - 1, columnIndex); } - // bool isCompound = false; - for (int y = 0; y < block4x4Height; y++) + if (partitionInfo.AvailableLeft) { - for (int x = 0; x < block4x4Width; x++) - { - this.yModes[rowIndex + y][columnIndex + x] = this.yMode; - if (this.referenceFrame[0] == (object)ObuFrameType.IntraOnlyFrame && hasChroma) - { - this.uvModes[rowIndex + y][columnIndex + x] = this.uvMode; - } + partitionInfo.LeftModeInfo = superblockInfo.GetModeInfo(rowIndex, columnIndex - 1); + } - for (int refList = 0; refList < 2; refList++) - { - this.referenceFrames[rowIndex + y][columnIndex + x][refList] = this.referenceFrame[refList]; - } - } + this.ReadModeInfo(ref reader, partitionInfo); + ReadPaletteTokens(ref reader, partitionInfo); + ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); + if (partitionInfo.ModeInfo.Skip) + { + this.ResetSkipContext(partitionInfo); } - ComputePrediction(); this.Residual(rowIndex, columnIndex, blockSize); } + private void ResetSkipContext(Av1PartitionInfo partitionInfo) + { + int planesCount = this.SequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; + for (int i = 0; i < planesCount; i++) + { + bool subX = i > 0 && this.SequenceHeader.ColorConfig.SubSamplingX; + bool subY = i > 0 && this.SequenceHeader.ColorConfig.SubSamplingY; + Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); + int txsWide = planeBlockSize.GetWidth() >> 2; + int txsHigh = planeBlockSize.GetHeight() >> 2; + int aboveOffset = (partitionInfo.ColumnIndex - this.TileInfo.TileColumnStartModeInfo[partitionInfo.ColumnIndex]) >> (subX ? 1 : 0); + int leftOffset = (partitionInfo.RowIndex - this.TileInfo.TileRowStartModeInfo[partitionInfo.RowIndex]) >> (subY ? 1 : 0); + int[] aboveContext = this.aboveContext.AboveContext[i + aboveOffset]; + int[] leftContext = this.leftContext.LeftContext[i + leftOffset]; + Array.Fill(aboveContext, 0); + Array.Fill(leftContext, 0); + } + } + private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize) { bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; @@ -307,7 +293,7 @@ internal class Av1TileDecoder : IAv1TileDecoder int subBlockColumn = columnChunk & superBlockMask; for (int plane = 0; plane < 1 + (this.HasChroma(rowIndex, columnIndex, blockSize) ? 2 : 0); plane++) { - Av1TransformSize transformSize = this.FrameInfo.CodedLossless ? Av1TransformSize.Size4x4 : this.GetSize(plane, this.transformSize); + Av1TransformSize transformSize = this.FrameInfo.CodedLossless ? Av1TransformSize.Size4x4 : this.GetSize(plane, -1); int stepX = transformSize.GetWidth() >> 2; int stepY = transformSize.GetHeight() >> 2; Av1BlockSize planeSize = this.GetPlaneResidualSize(sizeChunk, plane); @@ -348,6 +334,7 @@ internal class Av1TileDecoder : IAv1TileDecoder private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y) { + Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid), new(), false, Av1PartitionType.None); int startX = (baseX + 4) * x; int startY = (baseY + 4) * y; bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; @@ -368,28 +355,28 @@ internal class Av1TileDecoder : IAv1TileDecoder return; } - if ((plane == 0 && this.paletteSizeY > 0) || - (plane != 0 && this.paletteSizeUv > 0)) + if ((plane == 0 && partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) > 0) || + (plane != 0 && partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Uv) > 0)) { this.PredictPalette(plane, startX, startY, x, y, transformSize); } else { - bool isChromaFromLuma = plane > 0 && this.uvMode == Av1PredictionMode.UvChromaFromLuma; + bool isChromaFromLuma = plane > 0 && partitionInfo.ModeInfo.UvMode == Av1PredictionMode.UvChromaFromLuma; Av1PredictionMode mode; if (plane == 0) { - mode = this.yMode; + mode = partitionInfo.ModeInfo.YMode; } else { - mode = isChromaFromLuma ? Av1PredictionMode.DC : this.uvMode; + mode = isChromaFromLuma ? Av1PredictionMode.DC : partitionInfo.ModeInfo.UvMode; } int log2Width = transformSize.GetWidthLog2(); int log2Height = transformSize.GetHeightLog2(); - bool leftAvailable = x > 0 || plane == 0 ? this.availableLeft : this.availableLeftForChroma; - bool upAvailable = y > 0 || plane == 0 ? this.availableUp : this.availableUpForChroma; + bool leftAvailable = x > 0 || plane == 0 ? partitionInfo.AvailableLeft : partitionInfo.AvailableLeftForChroma; + bool upAvailable = y > 0 || plane == 0 ? partitionInfo.AvailableUp : partitionInfo.AvailableUpForChroma; bool haveAboveRight = this.blockDecoded[plane][(subBlockRow >> subY) - 1][(subBlockColumn >> subX) + stepX]; bool haveBelowLeft = this.blockDecoded[plane][(subBlockRow >> subY) + stepY][(subBlockColumn >> subX) - 1]; this.PredictIntra(plane, startX, startY, leftAvailable, upAvailable, haveAboveRight, haveBelowLeft, mode, log2Width, log2Height); @@ -405,7 +392,7 @@ internal class Av1TileDecoder : IAv1TileDecoder this.maxLumaHeight = startY + (stepY * 4); } - if (!this.skip) + if (!partitionInfo.ModeInfo.Skip) { int eob = this.Coefficients(plane, startX, startY, transformSize); if (eob > 0) @@ -434,11 +421,6 @@ internal class Av1TileDecoder : IAv1TileDecoder private void PredictPalette(int plane, int startX, int startY, int x, int y, Av1TransformSize transformSize) => throw new NotImplementedException(); - private static void ComputePrediction() - { - // Not applicable for INTRA frames. - } - private void ResetBlockContext(int rowIndex, int columnIndex, Av1BlockSize blockSize) { int block4x4Width = blockSize.Get4x4WideCount(); @@ -446,21 +428,22 @@ internal class Av1TileDecoder : IAv1TileDecoder bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; int endPlane = this.HasChroma(rowIndex, columnIndex, blockSize) ? 3 : 1; + this.aboveLevelContext = new int[3][]; + this.aboveDcContext = new int[3][]; + this.leftLevelContext = new int[3][]; + this.leftDcContext = new int[3][]; for (int plane = 0; plane < endPlane; plane++) { int subX = plane > 0 && subsamplingX ? 1 : 0; int subY = plane > 0 && subsamplingY ? 1 : 0; - for (int i = columnIndex >> subX; i < (columnIndex + block4x4Width) >> subX; i++) - { - this.aboveLevelContext[plane][i] = 0; - this.aboveDcContext[plane][i] = 0; - } - - for (int i = rowIndex >> subY; i < (rowIndex + block4x4Height) >> subY; i++) - { - this.leftLevelContext[plane][i] = 0; - this.leftDcContext[plane][i] = 0; - } + this.aboveLevelContext[plane] = new int[(columnIndex + block4x4Width) >> subX]; + this.aboveDcContext[plane] = new int[(columnIndex + block4x4Width) >> subX]; + this.leftLevelContext[plane] = new int[(rowIndex + block4x4Height) >> subY]; + this.leftDcContext[plane] = new int[(rowIndex + block4x4Height) >> subY]; + Array.Fill(this.aboveLevelContext[plane], 0); + Array.Fill(this.aboveDcContext[plane], 0); + Array.Fill(this.leftLevelContext[plane], 0); + Array.Fill(this.leftDcContext[plane], 0); } } @@ -480,209 +463,250 @@ internal class Av1TileDecoder : IAv1TileDecoder }*/ } - private void ReadPaletteTokens(ref Av1SymbolDecoder reader) + private static void ReadPaletteTokens(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { reader.ReadLiteral(-1); - if (this.paletteSizeY != 0) + if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) != 0) { // Todo: Implement. throw new NotImplementedException(); } - if (this.paletteSizeUv != 0) + if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Uv) != 0) { // Todo: Implement. throw new NotImplementedException(); } } - private void ReadModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) - => this.ReadIntraFrameModeInfo(ref reader, rowIndex, columnIndex, blockSize); + private void ReadModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) + { + DebugGuard.IsTrue(this.FrameInfo.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame, "Only INTRA frames supported."); + this.ReadIntraFrameModeInfo(ref reader, partitionInfo); + } - private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - this.skip = false; if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) { - this.ReadIntraSegmentId(ref reader); + this.IntraSegmentId(ref reader, partitionInfo); } // this.skipMode = false; - this.ReadSkip(ref reader); + partitionInfo.ModeInfo.Skip = this.ReadSkip(ref reader, partitionInfo); if (!this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) { - this.IntraSegmentId(ref reader, rowIndex, columnIndex); + this.IntraSegmentId(ref reader, partitionInfo); } - this.ReadCdef(ref reader, rowIndex, columnIndex, blockSize); - this.ReadDeltaQuantizerIndex(ref reader, blockSize); - this.ReadDeltaLoopFilter(ref reader, blockSize); - this.readDeltas = false; - this.referenceFrame[0] = -1; // IntraFrame; - this.referenceFrame[1] = -1; // None; + this.ReadCdef(ref reader, partitionInfo); + + bool readDeltas = false; + if (readDeltas) + { + this.ReadDeltaQuantizerIndex(ref reader, partitionInfo); + this.ReadDeltaLoopFilter(ref reader, partitionInfo); + } + + partitionInfo.ReferenceFrame[0] = 0; // IntraFrame; + partitionInfo.ReferenceFrame[1] = -1; // None; + partitionInfo.ModeInfo.SetPaletteSizes(0, 0); bool useIntraBlockCopy = false; - if (this.FrameInfo.AllowIntraBlockCopy) + if (this.AllowIntraBlockCopy()) { useIntraBlockCopy = reader.ReadUseIntraBlockCopy(); } if (useIntraBlockCopy) { - // TODO: Implement + partitionInfo.ModeInfo.YMode = Av1PredictionMode.DC; + partitionInfo.ModeInfo.UvMode = Av1PredictionMode.DC; } else { // this.IsInter = false; - this.yMode = reader.ReadIntraFrameYMode(blockSize); - this.IntraAngleInfoY(ref reader, blockSize); - if (this.HasChroma(rowIndex, columnIndex, blockSize)) + partitionInfo.ModeInfo.YMode = reader.ReadYMode(partitionInfo.AboveModeInfo, partitionInfo.LeftModeInfo); + partitionInfo.ModeInfo.AngleDelta[(int)Av1PlaneType.Y] = IntraAngleInfo(ref reader, partitionInfo.ModeInfo.YMode, partitionInfo.ModeInfo.BlockSize); + if (partitionInfo.IsChroma && !this.SequenceHeader.ColorConfig.IsMonochrome) { - this.uvMode = reader.ReadUvMode(blockSize, this.IsChromaForLumaAllowed(blockSize)); - if (this.uvMode == Av1PredictionMode.UvChromaFromLuma) + partitionInfo.ModeInfo.UvMode = reader.ReadIntraModeUv(partitionInfo.ModeInfo.YMode, this.IsChromaForLumaAllowed(partitionInfo)); + if (partitionInfo.ModeInfo.UvMode == Av1PredictionMode.UvChromaFromLuma) { - this.ReadChromaFromLumaAlphas(ref reader); + this.ReadChromaFromLumaAlphas(ref reader, partitionInfo); } - this.IntraAngleInfoUv(ref reader, blockSize); + partitionInfo.ModeInfo.AngleDelta[(int)Av1PlaneType.Uv] = IntraAngleInfo(ref reader, partitionInfo.ModeInfo.UvMode, partitionInfo.ModeInfo.BlockSize); + } + else + { + partitionInfo.ModeInfo.UvMode = Av1PredictionMode.DC; } - this.paletteSizeY = 0; - this.paletteSizeUv = 0; - if (this.SequenceHeader.ModeInfoSize >= (int)Av1BlockSize.Block8x8 && - ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4WideCount() <= 64 && - ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4HighCount() <= 64 && + if (partitionInfo.ModeInfo.BlockSize >= Av1BlockSize.Block8x8 && + partitionInfo.ModeInfo.BlockSize.GetWidth() <= 64 && + partitionInfo.ModeInfo.BlockSize.GetHeight() <= 64 && this.FrameInfo.AllowScreenContentTools) { - this.PaletteModeInfo(ref reader); + this.PaletteModeInfo(ref reader, partitionInfo); } - this.FilterIntraModeInfo(ref reader, blockSize); + this.FilterIntraModeInfo(ref reader, partitionInfo); } } - private bool IsChromaForLumaAllowed(Av1BlockSize blockSize) + private bool AllowIntraBlockCopy() + => (this.FrameInfo.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame) && + (this.SequenceHeader.ForceScreenContentTools > 0) && + this.FrameInfo.AllowIntraBlockCopy; + + private bool IsChromaForLumaAllowed(Av1PartitionInfo partitionInfo) { - if (this.lossless) + if (this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId]) { // In lossless, CfL is available when the partition size is equal to the // transform size. bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; - Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY); + Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); return planeBlockSize == Av1BlockSize.Block4x4; } // Spec: CfL is available to luma partitions lesser than or equal to 32x32 - return blockSize.Get4x4WideCount() <= 32 && blockSize.Get4x4HighCount() <= 32; + return partitionInfo.ModeInfo.BlockSize.GetWidth() <= 32 && partitionInfo.ModeInfo.BlockSize.GetHeight() <= 32; } - private void ReadIntraSegmentId(ref Av1SymbolDecoder reader) => throw new NotImplementedException(); - - private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - bool useFilterIntra = false; if (this.SequenceHeader.EnableFilterIntra && - this.yMode == Av1PredictionMode.DC && this.paletteSizeY == 0 && - Math.Max(blockSize.GetWidth(), blockSize.GetHeight()) <= 32) + partitionInfo.ModeInfo.YMode == Av1PredictionMode.DC && + partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) == 0 && + Math.Max(partitionInfo.ModeInfo.BlockSize.GetWidth(), partitionInfo.ModeInfo.BlockSize.GetHeight()) <= 32) { - useFilterIntra = reader.ReadUseFilterUltra(); - if (useFilterIntra) + partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = reader.ReadUseFilterUltra(partitionInfo.ModeInfo.BlockSize); + if (partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra) { - this.filterUltraMode = reader.ReadFilterUltraMode(); + partitionInfo.ModeInfo.FilterIntraModeInfo.Mode = reader.ReadFilterUltraMode(); } } + else + { + partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = false; + } } - private void PaletteModeInfo(ref Av1SymbolDecoder reader) => + private void PaletteModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) => // TODO: Implement. throw new NotImplementedException(); - private void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader) => + private void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) => // TODO: Implement. throw new NotImplementedException(); - private void IntraAngleInfoY(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + private static int IntraAngleInfo(ref Av1SymbolDecoder reader, Av1PredictionMode mode, Av1BlockSize blockSize) { - this.angleDeltaY = 0; - if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.yMode)) + int angleDelta = 0; + if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(mode)) { - int angleDeltaY = reader.ReadAngleDelta(this.yMode); - this.angleDeltaY = angleDeltaY - ObuConstants.MaxAngleDelta; + int symbol = reader.ReadAngleDelta(mode); + angleDelta = symbol - ObuConstants.MaxAngleDelta; } - } - private void IntraAngleInfoUv(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) - { - this.angleDeltaUv = 0; - if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.uvMode)) - { - int angleDeltaUv = reader.ReadAngleDelta(this.uvMode); - this.angleDeltaUv = angleDeltaUv - ObuConstants.MaxAngleDelta; - } + return angleDelta; } private static bool IsDirectionalMode(Av1PredictionMode mode) => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees; - private void IntraSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex) + private void IntraSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { if (this.FrameInfo.SegmentationParameters.Enabled) { - this.ReadSegmentId(ref reader, rowIndex, columnIndex); + this.ReadSegmentId(ref reader, partitionInfo); } - else + + int bw4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); + int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); + int x_mis = Math.Min(this.FrameInfo.ModeInfoColumnCount - partitionInfo.ColumnIndex, bw4); + int y_mis = Math.Min(this.FrameInfo.ModeInfoRowCount - partitionInfo.RowIndex, bh4); + + for (int y = 0; y < y_mis; y++) { - this.segmentId = 0; + for (int x = 0; x < x_mis; x++) + { + this.segmentIds[partitionInfo.RowIndex + y][partitionInfo.ColumnIndex + x] = partitionInfo.ModeInfo.SegmentId; + } } - - this.lossless = this.FrameInfo.LosslessArray[this.segmentId]; } - private void ReadSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex) + private void ReadSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - int pred; + int predictor; int prevUL = -1; int prevU = -1; int prevL = -1; - if (this.availableUp && this.availableLeft) + int columnIndex = partitionInfo.ColumnIndex; + int rowIndex = partitionInfo.RowIndex; + if (partitionInfo.AvailableUp && partitionInfo.AvailableLeft) { - prevUL = this.segmentIds[rowIndex - 1][columnIndex - 1]; + prevUL = this.GetSegmentId(partitionInfo, rowIndex - 1, columnIndex - 1); } - if (this.availableUp) + if (partitionInfo.AvailableUp) { - prevU = this.segmentIds[rowIndex - 1][columnIndex]; + prevU = this.GetSegmentId(partitionInfo, rowIndex - 1, columnIndex); } - if (this.availableLeft) + if (partitionInfo.AvailableLeft) { - prevU = this.segmentIds[rowIndex][columnIndex - 1]; + prevU = this.GetSegmentId(partitionInfo, rowIndex, columnIndex - 1); } if (prevU == -1) { - pred = prevL == -1 ? 0 : prevL; + predictor = prevL == -1 ? 0 : prevL; } else if (prevL == -1) { - pred = prevU; + predictor = prevU; } else { - pred = prevU == prevUL ? prevU : prevL; + predictor = prevU == prevUL ? prevU : prevL; } - if (this.skip) + if (partitionInfo.ModeInfo.Skip) { - this.segmentId = 0; + partitionInfo.ModeInfo.SegmentId = predictor; } else { + int ctx = prevUL < 0 ? 0 /* Edge cases */ + : prevUL == prevU && prevUL == prevL ? 2 + : prevUL == prevU || prevUL == prevL || prevU == prevL ? 1 : 0; int lastActiveSegmentId = this.FrameInfo.SegmentationParameters.LastActiveSegmentId; - this.segmentId = NegativeDeinterleave(reader.ReadSegmentId(-1), pred, lastActiveSegmentId + 1); + partitionInfo.ModeInfo.SegmentId = NegativeDeinterleave(reader.ReadSegmentId(ctx), predictor, lastActiveSegmentId + 1); + } + } + + private int GetSegmentId(Av1PartitionInfo partitionInfo, int rowIndex, int columnIndex) + { + int modeInfoOffset = (rowIndex * this.FrameInfo.ModeInfoColumnCount) + columnIndex; + int bw4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); + int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); + int xMin = Math.Min(this.FrameInfo.ModeInfoColumnCount - columnIndex, bw4); + int yMin = Math.Min(this.FrameInfo.ModeInfoRowCount - rowIndex, bh4); + int segmentId = ObuConstants.MaxSegments - 1; + for (int y = 0; y < yMin; y++) + { + for (int x = 0; x < xMin; x++) + { + segmentId = Math.Min(segmentId, this.segmentIds[y][x]); + } } + + return segmentId; } private static int NegativeDeinterleave(int diff, int reference, int max) @@ -731,41 +755,45 @@ internal class Av1TileDecoder : IAv1TileDecoder } } - private void ReadCdef(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + private void ReadCdef(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - if (this.skip || this.FrameInfo.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameInfo.AllowIntraBlockCopy) + if (partitionInfo.ModeInfo.Skip || this.FrameInfo.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameInfo.AllowIntraBlockCopy) { return; } int cdefSize4 = Av1BlockSize.Block64x64.Get4x4WideCount(); int cdefMask4 = ~(cdefSize4 - 1); - int r = rowIndex & cdefMask4; - int c = columnIndex & cdefMask4; - if (this.cdefIndex[r][c] == -1) - { - this.cdefIndex[r][c] = reader.ReadLiteral(this.FrameInfo.CdefParameters.BitCount); - int w4 = blockSize.Get4x4WideCount(); - int h4 = blockSize.Get4x4HighCount(); - for (int i = r; i < r + h4; i += cdefSize4) + int r = partitionInfo.RowIndex & cdefMask4; + int c = partitionInfo.ColumnIndex & cdefMask4; + if (partitionInfo.CdefStrength[r][c] == -1) + { + partitionInfo.CdefStrength[r][c] = reader.ReadLiteral(this.FrameInfo.CdefParameters.BitCount); + if (this.SequenceHeader.SuperBlockSize == Av1BlockSize.Block128x128) { - for (int j = c; j < c + w4; j += cdefSize4) + int w4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); + int h4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); + for (int i = r; i < r + h4; i += cdefSize4) { - this.cdefIndex[i][j] = this.cdefIndex[r][c]; + for (int j = c; j < c + w4; j += cdefSize4) + { + partitionInfo.CdefStrength[i & cdefMask4][j & cdefMask4] = partitionInfo.CdefStrength[r][c]; + } } } } } - private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; - if (blockSize == superBlockSize && this.skip) + if (this.FrameInfo.DeltaLoopFilterParameters.IsPresent || + (partitionInfo.ModeInfo.BlockSize == superBlockSize && partitionInfo.ModeInfo.Skip)) { return; } - if (this.readDeltas && this.FrameInfo.DeltaLoopFilterParameters.IsPresent) + if (this.FrameInfo.DeltaLoopFilterParameters.IsPresent) { int frameLoopFilterCount = 1; if (this.FrameInfo.DeltaLoopFilterParameters.Multi) @@ -793,28 +821,32 @@ internal class Av1TileDecoder : IAv1TileDecoder } } - private void ReadSkip(ref Av1SymbolDecoder reader) + private bool ReadSkip(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { + int segmentId = partitionInfo.ModeInfo.SegmentId; if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip && - this.FrameInfo.SegmentationParameters.IsFeatureActive(-1, ObuSegmentationFeature.LevelSkip)) + this.FrameInfo.SegmentationParameters.IsFeatureActive(segmentId, ObuSegmentationLevelFeature.Skip)) { - this.skip = true; + return true; } else { - this.skip = reader.ReadSkip(-1); + int aboveSkip = partitionInfo.AboveModeInfo != null && partitionInfo.AboveModeInfo.Skip ? 1 : 0; + int leftSkip = partitionInfo.LeftModeInfo != null && partitionInfo.LeftModeInfo.Skip ? 1 : 0; + return reader.ReadSkip(aboveSkip + leftSkip); } } - private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; - if (blockSize == superBlockSize && this.skip) + if (!this.FrameInfo.DeltaQParameters.IsPresent || + (partitionInfo.ModeInfo.BlockSize == superBlockSize && partitionInfo.ModeInfo.Skip)) { return; } - if (this.readDeltas) + if (partitionInfo.ModeInfo.BlockSize != this.SequenceHeader.SuperBlockSize || !partitionInfo.ModeInfo.Skip) { int deltaQuantizerAbsolute = reader.ReadDeltaQuantizerAbsolute(); if (deltaQuantizerAbsolute == ObuConstants.DeltaQuantizerSmall) @@ -829,6 +861,7 @@ internal class Av1TileDecoder : IAv1TileDecoder bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0; int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute; this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << this.deltaQuantizerResolution)); + partitionInfo.SuperblockInfo.SuperblockDeltaQ[0] = this.currentQuantizerIndex; } } }