diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs index 78dd1efe34..eeaafe87ed 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs @@ -206,6 +206,88 @@ public int ReadChromaFromLumaAlphaV(int jointSignPlus1) return r.ReadSymbol(this.chromeForLumaAlpha[context]); } + /// + /// 5.11.39. Coefficients syntax. + /// + /// + /// The implementation is taken from SVT-AV1 library, which deviates from the code flow in the specification. + /// + public int ReadCoefficients( + Av1BlockModeInfo modeInfo, + Point blockPosition, + int[] aboveContexts, + int[] leftContexts, + int aboveOffset, + int leftOffset, + int plane, + int blocksWide, + int blocksHigh, + Av1TransformBlockContext transformBlockContext, + Av1TransformSize transformSize, + bool isLossless, + bool useReducedTransformSet, + Av1TransformInfo transformInfo, + int modeBlocksToRightEdge, + int modeBlocksToBottomEdge, + Span coefficientBuffer) + { + int width = transformSize.GetWidth(); + int height = transformSize.GetHeight(); + Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + ((int)transformSize.GetSquareUpSize() + 1)) >> 1); + Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); + int culLevel = 0; + + byte[] levelsBuffer = new byte[Av1Constants.TransformPad2d]; + Span levels = levelsBuffer.AsSpan()[(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal))..]; + + bool allZero = this.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext); + int bwl = transformSize.GetBlockWidthLog2(); + int endOfBlock; + if (allZero) + { + if (plane == 0) + { + transformInfo.Type = Av1TransformType.DctDct; + transformInfo.CodeBlockFlag = false; + } + + this.UpdateCoefficientContext(modeInfo, aboveContexts, leftContexts, blocksWide, blocksHigh, transformSize, blockPosition, aboveOffset, leftOffset, culLevel, modeBlocksToRightEdge, modeBlocksToBottomEdge); + return 0; + } + + transformInfo.Type = ComputeTransformType(planeType, modeInfo, isLossless, transformSize, transformInfo, useReducedTransformSet); + Av1TransformClass transformClass = transformInfo.Type.ToClass(); + Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformInfo.Type); + ReadOnlySpan scan = scanOrder.Scan; + + endOfBlock = this.ReadEndOfBlockPosition(transformSize, transformClass, transformSizeContext, planeType); + if (endOfBlock > 1) + { + Array.Fill(levelsBuffer, (byte)0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd); + } + + this.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, height, scan, bwl, levels, transformSizeContext, planeType); + if (endOfBlock > 1) + { + if (transformClass == Av1TransformClass.Class2D) + { + this.ReadCoefficientsReverse2d(transformSize, 1, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); + this.ReadCoefficientsReverse(transformSize, transformClass, 0, 0, scan, bwl, levels, transformSizeContext, planeType); + } + else + { + this.ReadCoefficientsReverse(transformSize, transformClass, 0, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); + } + } + + DebugGuard.MustBeGreaterThan(scan.Length, 0, nameof(scan)); + culLevel = this.ReadCoefficientsDc(coefficientBuffer, endOfBlock, scan, bwl, levels, transformBlockContext.DcSignContext, planeType); + this.UpdateCoefficientContext(modeInfo, aboveContexts, leftContexts, blocksWide, blocksHigh, transformSize, blockPosition, aboveOffset, leftOffset, culLevel, modeBlocksToRightEdge, modeBlocksToBottomEdge); + + transformInfo.CodeBlockFlag = true; + return endOfBlock; + } + public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformClass transformClass, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { int endOfBlockExtra = 0; @@ -581,6 +663,106 @@ private static void SetDcSign(ref int culLevel, int dcValue) } } + + private void UpdateCoefficientContext( + Av1BlockModeInfo modeInfo, + int[] aboveContexts, + int[] leftContexts, + int blocksWide, + int blocksHigh, + Av1TransformSize transformSize, + Point blockPosition, + int aboveOffset, + int leftOffset, + int culLevel, + int modeBlockToRightEdge, + int modeBlockToBottomEdge) + { + int transformSizeWide = transformSize.Get4x4WideCount(); + int transformSizeHigh = transformSize.Get4x4HighCount(); + + if (modeBlockToRightEdge < 0) + { + int aboveContextCount = Math.Min(transformSizeWide, blocksWide - aboveOffset); + Array.Fill(aboveContexts, culLevel, 0, aboveContextCount); + Array.Fill(aboveContexts, 0, aboveContextCount, transformSizeWide - aboveContextCount); + } + else + { + Array.Fill(aboveContexts, culLevel, 0, transformSizeWide); + } + + if (modeBlockToBottomEdge < 0) + { + int leftContextCount = Math.Min(transformSizeHigh, blocksHigh - leftOffset); + Array.Fill(leftContexts, culLevel, 0, leftContextCount); + Array.Fill(leftContexts, 0, leftContextCount, transformSizeWide - leftContextCount); + } + else + { + Array.Fill(leftContexts, culLevel, 0, transformSizeHigh); + } + } + + private static Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1BlockModeInfo modeInfo, bool isLossless, Av1TransformSize transformSize, Av1TransformInfo transformInfo, bool useReducedTransformSet) + { + Av1TransformType transformType = Av1TransformType.DctDct; + if (isLossless || transformSize.GetSquareUpSize() > Av1TransformSize.Size32x32) + { + transformType = Av1TransformType.DctDct; + } + else + { + if (planeType == Av1PlaneType.Y) + { + transformType = transformInfo.Type; + } + else + { + // In intra mode, uv planes don't share the same prediction mode as y + // plane, so the tx_type should not be shared + transformType = ConvertIntraModeToTransformType(modeInfo, Av1PlaneType.Uv); + } + } + + Av1TransformSetType transformSetType = GetExtendedTransformSetType(transformSize, useReducedTransformSet); + if (!transformType.IsExtendedSetUsed(transformSetType)) + { + transformType = Av1TransformType.DctDct; + } + + return transformType; + } + + private static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet) + { + Av1TransformSize squareUpSize = transformSize.GetSquareUpSize(); + + if (squareUpSize >= Av1TransformSize.Size32x32) + { + return Av1TransformSetType.DctOnly; + } + + if (useReducedSet) + { + return Av1TransformSetType.Dtt4Identity; + } + + Av1TransformSize squareSize = transformSize.GetSquareSize(); + return squareSize == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; + } + + private static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInfo modeInfo, Av1PlaneType planeType) + { + Av1PredictionMode mode = (planeType == Av1PlaneType.Y) ? modeInfo.YMode : modeInfo.UvMode; + if (mode == Av1PredictionMode.UvChromaFromLuma) + { + mode = Av1PredictionMode.DC; + } + + return mode.ToTransformType(); + } + internal static Av1Distribution GetSplitOrHorizontalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context) { Av1Distribution input = inputs[context]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs index 1e79671dee..417c58a5a5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -415,13 +415,13 @@ private static void InitializeLevels(Span coefficientBuffer, int width, int ref byte ls = ref levels[0]; Unsafe.InitBlock(ref levels[-Av1Constants.TransformPadTop * stride], 0, (uint)(Av1Constants.TransformPadTop * stride)); - Unsafe.InitBlock(ref levels[stride * height], 0, (uint)(Av1Constants.TransformPadBottom * stride + Av1Constants.TransformPadEnd)); + Unsafe.InitBlock(ref levels[stride * height], 0, (uint)((Av1Constants.TransformPadBottom * stride) + Av1Constants.TransformPadEnd)); - for (int i = 0; i < height; i++) + for (int y = 0; y < height; y++) { - for (int j = 0; j < width; j++) + for (int x = 0; x < width; x++) { - ls = (byte)Av1Math.Clamp(Math.Abs(coefficientBuffer[i * width + j]), 0, byte.MaxValue); + ls = (byte)Av1Math.Clamp(Math.Abs(coefficientBuffer[(y * width) + x]), 0, byte.MaxValue); ls = ref Unsafe.Add(ref ls, 1); } @@ -433,7 +433,10 @@ private static void InitializeLevels(Span coefficientBuffer, int width, int /// SVT: set_levels from EbCommonUtils.h /// private static Span SetLevels(Span levelsBuffer, int width) - => levelsBuffer.Slice(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal)); + { + int stride = width + Av1Constants.TransformPadHorizontal; + return levelsBuffer[(Av1Constants.TransformPadTop * stride)..]; + } private void WriteSkip(bool hasEndOfBlock, int context) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs index a8113a7ece..20989f3cef 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs @@ -1,30 +1,27 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1BlockModeInfoEncoder { - internal class Av1BlockModeInfoEncoder - { - public Av1BlockSize BlockSize { get; } + public Av1BlockSize BlockSize { get; } - public Av1PredictionMode PredictionMode { get; } + public Av1PredictionMode PredictionMode { get; } - public Av1PartitionType PartitionType { get; } + public Av1PartitionType PartitionType { get; } - public Av1PredictionMode UvPredictionMode { get; } + public Av1PredictionMode UvPredictionMode { get; } - public bool Skip { get; } = true; + public bool Skip { get; } = true; - public bool SkipMode { get; } = true; + public bool SkipMode { get; } = true; - public bool UseIntraBlockCopy { get; } = true; + public bool UseIntraBlockCopy { get; } = true; - public int SegmentId { get; } + public int SegmentId { get; } - public int TransformDepth { get; internal set; } - } + public int TransformDepth { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs index c3c29deae4..8ec17e55e7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs @@ -5,18 +5,15 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1Common { - internal class Av1Common - { - public int ModeInfoRowCount { get; internal set; } + public int ModeInfoRowCount { get; internal set; } - public int ModeInfoColumnCount { get; internal set; } + public int ModeInfoColumnCount { get; internal set; } - public int ModeInfoStride { get; internal set; } + public int ModeInfoStride { get; internal set; } - public required ObuFrameSize FrameSize { get; internal set; } + public required ObuFrameSize FrameSize { get; internal set; } - public required ObuTileGroupHeader TilesInfo { get; internal set; } - } + public required ObuTileGroupHeader TilesInfo { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs index 8fb96f4d2d..e1823931d9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs @@ -3,14 +3,11 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1MacroBlockModeInfo { - internal class Av1MacroBlockModeInfo - { - public required Av1BlockModeInfoEncoder Block { get; internal set; } + public required Av1BlockModeInfoEncoder Block { get; internal set; } - public required Av1PaletteLumaModeInfo Palette { get; internal set; } + public required Av1PaletteLumaModeInfo Palette { get; internal set; } - public int CdefStrength { get; internal set; } - } + public int CdefStrength { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs index 7607d1bffd..215dc2ad02 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs @@ -11,14 +11,6 @@ internal class Av1NeighborArrayUnit { public static readonly T InvalidNeighborData = T.MaxValue; - [Flags] - public enum UnitMask - { - Left = 1, - Top = 2, - TopLeft = 4, - } - private readonly T[] left; private readonly T[] top; private readonly T[] topLeft; @@ -30,6 +22,14 @@ public Av1NeighborArrayUnit(int leftSize, int topSize, int topLeftSize) this.topLeft = new T[topLeftSize]; } + [Flags] + public enum UnitMask + { + Left = 1, + Top = 2, + TopLeft = 4, + } + public Span Left => this.left; public Span Top => this.top; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs index bb40ca53a8..aa1e1e94f7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs @@ -1,11 +1,8 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1PaletteLumaModeInfo { - internal class Av1PaletteLumaModeInfo - { - } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs index 851f71dae3..ebfe36faf3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs @@ -3,20 +3,17 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1PictureControlSet { - internal class Av1PictureControlSet - { - public required Av1NeighborArrayUnit[] luma_dc_sign_level_coeff_na { get; internal set; } + public required Av1NeighborArrayUnit[] luma_dc_sign_level_coeff_na { get; internal set; } - public required Av1NeighborArrayUnit[] cr_dc_sign_level_coeff_na { get; internal set; } + public required Av1NeighborArrayUnit[] cr_dc_sign_level_coeff_na { get; internal set; } - public required Av1NeighborArrayUnit[] cb_dc_sign_level_coeff_na { get; internal set; } + public required Av1NeighborArrayUnit[] cb_dc_sign_level_coeff_na { get; internal set; } - public required Av1NeighborArrayUnit[] txfm_context_array { get; internal set; } + public required Av1NeighborArrayUnit[] txfm_context_array { get; internal set; } - public required Av1SequenceControlSet Sequence { get; internal set; } + public required Av1SequenceControlSet Sequence { get; internal set; } - public required Av1PictureParentControlSet Parent { get; internal set; } - } + public required Av1PictureParentControlSet Parent { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs index bf79f5a5c3..b1a183048d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs @@ -5,18 +5,17 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1PictureParentControlSet { - internal class Av1PictureParentControlSet - { - public required Av1Common Common { get; internal set; } + public required Av1Common Common { get; internal set; } - public required ObuFrameHeader FrameHeader { get; internal set; } + public required ObuFrameHeader FrameHeader { get; internal set; } - public required int[] PreviousQIndex { get; internal set; } + public required int[] PreviousQIndex { get; internal set; } - public int PaletteLevel { get; internal set; } - public int AlignedWidth { get; internal set; } - public int AlignedHeight { get; internal set; } - } + public int PaletteLevel { get; internal set; } + + public int AlignedWidth { get; internal set; } + + public int AlignedHeight { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs index 1ad59b31ab..39f7c2b197 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs @@ -1,14 +1,11 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1SequenceControlSet { - internal class Av1SequenceControlSet - { - public required ObuSequenceHeader SequenceHeader { get; internal set; } - } + public required ObuSequenceHeader SequenceHeader { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index b1cd2ee61d..0001ecc04a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -491,22 +491,6 @@ public static bool HasChroma(ObuSequenceHeader sequenceHeader, Point modeInfoLoc return hasChroma; } - private Av1TransformSize GetSize(int plane, object transformSize) => throw new NotImplementedException(); - - /// - /// 5.11.38. Get plane residual size function. - /// The GetPlaneResidualSize returns the size of a residual block for the specified plane. (The residual block will always - /// have width and height at least equal to 4.) - /// - private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) - { - bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; - bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; - bool subX = plane > 0 && subsamplingX; - bool subY = plane > 0 && subsamplingY; - return sizeChunk.GetSubsampled(subX, subY); - } - /// /// 5.11.35. Transform block syntax. /// @@ -563,152 +547,15 @@ private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo part int height = transformSize.GetHeight(); Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + ((int)transformSize.GetSquareUpSize() + 1)) >> 1); Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); - int culLevel = 0; - - byte[] levelsBuffer = new byte[Av1Constants.TransformPad2d]; - Span levels = levelsBuffer.AsSpan()[(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal))..]; - - bool allZero = reader.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext); - int bwl = transformSize.GetBlockWidthLog2(); - int endOfBlock; - if (allZero) - { - if (plane == 0) - { - transformInfo.Type = Av1TransformType.DctDct; - transformInfo.CodeBlockFlag = false; - } - - this.UpdateCoefficientContext(plane, partitionInfo, transformSize, blockRow, blockColumn, aboveOffset, leftOffset, culLevel); - return 0; - } - - transformInfo.Type = this.ComputeTransformType(planeType, partitionInfo, transformSize, transformInfo); - Av1TransformClass transformClass = transformInfo.Type.ToClass(); - Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformInfo.Type); - ReadOnlySpan scan = scanOrder.Scan; - - endOfBlock = reader.ReadEndOfBlockPosition(transformSize, transformClass, transformSizeContext, planeType); - if (endOfBlock > 1) - { - Array.Fill(levelsBuffer, (byte)0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd); - } - - reader.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, height, scan, bwl, levels, transformSizeContext, planeType); - if (endOfBlock > 1) - { - if (transformClass == Av1TransformClass.Class2D) - { - reader.ReadCoefficientsReverse2d(transformSize, 1, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); - reader.ReadCoefficientsReverse(transformSize, transformClass, 0, 0, scan, bwl, levels, transformSizeContext, planeType); - } - else - { - reader.ReadCoefficientsReverse(transformSize, transformClass, 0, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); - } - } - - DebugGuard.MustBeGreaterThan(scan.Length, 0, nameof(scan)); - culLevel = reader.ReadCoefficientsDc(coefficientBuffer, endOfBlock, scan, bwl, levels, transformBlockContext.DcSignContext, planeType); - this.UpdateCoefficientContext(plane, partitionInfo, transformSize, blockRow, blockColumn, aboveOffset, leftOffset, culLevel); - - transformInfo.CodeBlockFlag = true; - return endOfBlock; - } - - private void UpdateCoefficientContext(int plane, Av1PartitionInfo partitionInfo, Av1TransformSize transformSize, int blockRow, int blockColumn, int aboveOffset, int leftOffset, int culLevel) - { + Point blockPosition = new(blockColumn, blockRow); + bool isLossless = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId]; bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; - int[] aboveContexts = this.aboveNeighborContext.GetContext(plane); - int[] leftContexts = this.leftNeighborContext.GetContext(plane); - int transformSizeWide = transformSize.Get4x4WideCount(); - int transformSizeHigh = transformSize.Get4x4HighCount(); - - if (partitionInfo.ModeBlockToRightEdge < 0) - { - Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); - int blocksWide = partitionInfo.GetMaxBlockWide(planeBlockSize, subX); - int aboveContextCount = Math.Min(transformSizeWide, blocksWide - aboveOffset); - Array.Fill(aboveContexts, culLevel, 0, aboveContextCount); - Array.Fill(aboveContexts, 0, aboveContextCount, transformSizeWide - aboveContextCount); - } - else - { - Array.Fill(aboveContexts, culLevel, 0, transformSizeWide); - } - - if (partitionInfo.ModeBlockToBottomEdge < 0) - { - Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); - int blocksHigh = partitionInfo.GetMaxBlockHigh(planeBlockSize, subY); - int leftContextCount = Math.Min(transformSizeHigh, blocksHigh - leftOffset); - Array.Fill(leftContexts, culLevel, 0, leftContextCount); - Array.Fill(leftContexts, 0, leftContextCount, transformSizeWide - leftContextCount); - } - else - { - Array.Fill(leftContexts, culLevel, 0, transformSizeHigh); - } - } - - private Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1PartitionInfo partitionInfo, Av1TransformSize transformSize, Av1TransformInfo transformInfo) - { - Av1TransformType transformType = Av1TransformType.DctDct; - if (this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId] || transformSize.GetSquareUpSize() > Av1TransformSize.Size32x32) - { - transformType = Av1TransformType.DctDct; - } - else - { - if (planeType == Av1PlaneType.Y) - { - transformType = transformInfo.Type; - } - else - { - // In intra mode, uv planes don't share the same prediction mode as y - // plane, so the tx_type should not be shared - transformType = ConvertIntraModeToTransformType(partitionInfo.ModeInfo, Av1PlaneType.Uv); - } - } - - Av1TransformSetType transformSetType = GetExtendedTransformSetType(transformSize, this.FrameHeader.UseReducedTransformSet); - if (!transformType.IsExtendedSetUsed(transformSetType)) - { - transformType = Av1TransformType.DctDct; - } - - return transformType; - } - - private static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet) - { - Av1TransformSize squareUpSize = transformSize.GetSquareUpSize(); - - if (squareUpSize >= Av1TransformSize.Size32x32) - { - return Av1TransformSetType.DctOnly; - } - - if (useReducedSet) - { - return Av1TransformSetType.Dtt4Identity; - } - - Av1TransformSize squareSize = transformSize.GetSquareSize(); - return squareSize == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; - } - - private static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInfo modeInfo, Av1PlaneType planeType) - { - Av1PredictionMode mode = (planeType == Av1PlaneType.Y) ? modeInfo.YMode : modeInfo.UvMode; - if (mode == Av1PredictionMode.UvChromaFromLuma) - { - mode = Av1PredictionMode.DC; - } + Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); + int blocksWide = partitionInfo.GetMaxBlockWide(planeBlockSize, subX); + int blocksHigh = partitionInfo.GetMaxBlockHigh(planeBlockSize, subY); - return mode.ToTransformType(); + return reader.ReadCoefficients(partitionInfo.ModeInfo, blockPosition, this.aboveNeighborContext.GetContext(plane), this.leftNeighborContext.GetContext(plane), aboveOffset, leftOffset, plane, blocksWide, blocksHigh, transformBlockContext, transformSize, isLossless, this.FrameHeader.UseReducedTransformSet, transformInfo, partitionInfo.ModeBlockToRightEdge, partitionInfo.ModeBlockToBottomEdge, coefficientBuffer); } private Av1TransformBlockContext GetTransformBlockContext(Av1TransformSize transformSize, int plane, Av1BlockSize planeBlockSize, int transformBlockUnitHighCount, int transformBlockUnitWideCount, int startY, int startX) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs new file mode 100644 index 0000000000..2e1bd61914 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using Microsoft.VisualBasic; +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class Av1CoefficientsEntropyTests +{ + private const int BaseQIndex = 23; + + [Fact] + public void RoundTripZeroEndOfBlock() + { + // Assign + const short transformBlockSkipContext = 0; + const short dcSignContext = 0; + const int txbIndex = 0; + Av1BlockSize blockSize = Av1BlockSize.Block4x4; + Av1TransformSize transformSize = Av1TransformSize.Size4x4; + Av1TransformType transformType = Av1TransformType.Identity; + Av1PredictionMode intraDirection = Av1PredictionMode.DC; + Av1ComponentType componentType = Av1ComponentType.Luminance; + Av1FilterIntraMode filterIntraMode = Av1FilterIntraMode.DC; + ushort endOfBlock = 16; + Av1BlockModeInfo modeInfo = new(Av1Constants.MaxPlanes, blockSize, new Point(0, 0)); + Av1TransformInfo transformInfo = new(transformSize, 0, 0); + int[] aboveContexts = new int[1]; + int[] leftContexts = new int[1]; + Av1TransformBlockContext transformBlockContext = new(); + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); + Span coefficientsBuffer = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + Span actuals = new int[16]; + + // Act + encoder.WriteCoefficients(transformSize, transformType, txbIndex, intraDirection, coefficientsBuffer, componentType, transformBlockSkipContext, dcSignContext, endOfBlock, true, BaseQIndex, filterIntraMode); + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolReader reader = new(encoded.GetSpan()); + decoder.ReadCoefficients(modeInfo, new Point(0, 0), aboveContexts, leftContexts, 0, 0, 0, 1, 1, transformBlockContext, transformSize, false, true, transformInfo, 0, 0, actuals); + + // Assert + Assert.Equal(coefficientsBuffer, actuals); + } + +}