diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs index b66d092ba5..412f6f80e0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; @@ -282,31 +283,45 @@ internal static class Av1NzMap NzMapContextOffset64x32, // TX_64x16 ]; - public static int GetNzMagnitude(ReadOnlySpan levels, int bwl, Av1TransformClass transformClass) + /// + /// SVT: get_nz_mag + /// + public static int GetNzMagnitude(Av1LevelBuffer levels, int index, int blockWidthLog2, Av1TransformClass transformClass) + => GetNzMagnitude(levels, index >> blockWidthLog2, transformClass); + + /// + /// SVT: get_nz_mag + /// + public static int GetNzMagnitude(Av1LevelBuffer levels, int y, Av1TransformClass transformClass) { int mag; + Span row0 = levels.GetRow(y); + Span row1 = levels.GetRow(y + 1); + Span row2 = levels.GetRow(y + 2); // Note: AOMMIN(level, 3) is useless for decoder since level < 3. - mag = ClipMax3[levels[1]]; // { 0, 1 } - mag += ClipMax3[levels[(1 << bwl) + Av1Constants.TransformPadHorizontal]]; // { 1, 0 } + mag = ClipMax3[row0[1]]; // { 0, 1 } + mag += ClipMax3[row1[0]]; // { 1, 0 } switch (transformClass) { case Av1TransformClass.Class2D: - mag += ClipMax3[levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1]]; // { 1, 1 } - mag += ClipMax3[levels[2]]; // { 0, 2 } - mag += ClipMax3[levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)]]; // { 2, 0 } + mag += ClipMax3[row1[1]]; // { 1, 1 } + mag += ClipMax3[row0[2]]; // { 0, 2 } + mag += ClipMax3[row2[0]]; // { 2, 0 } break; case Av1TransformClass.ClassVertical: - mag += ClipMax3[levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)]]; // { 2, 0 } - mag += ClipMax3[levels[(3 << bwl) + (3 << Av1Constants.TransformPadHorizontalLog2)]]; // { 3, 0 } - mag += ClipMax3[levels[(4 << bwl) + (4 << Av1Constants.TransformPadHorizontalLog2)]]; // { 4, 0 } + Span row3 = levels.GetRow(y + 3); + Span row4 = levels.GetRow(y + 4); + mag += ClipMax3[row2[0]]; // { 2, 0 } + mag += ClipMax3[row3[0]]; // { 3, 0 } + mag += ClipMax3[row4[0]]; // { 4, 0 } break; case Av1TransformClass.ClassHorizontal: - mag += ClipMax3[levels[2]]; // { 0, 2 } - mag += ClipMax3[levels[3]]; // { 0, 3 } - mag += ClipMax3[levels[4]]; // { 0, 4 } + mag += ClipMax3[row0[2]]; // { 0, 2 } + mag += ClipMax3[row0[3]]; // { 0, 3 } + mag += ClipMax3[row0[4]]; // { 0, 4 } break; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs new file mode 100644 index 0000000000..ea321205f3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs @@ -0,0 +1,382 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; + +internal static class Av1SymbolContextHelper +{ + public static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + public static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513]; + private static readonly int[] TransformCountInSet = [1, 2, 5, 7, 12, 16]; + private static readonly byte[] EndOfBlockToPositionSmall = [ + 0, 1, 2, // 0-2 + 3, 3, // 3-4 + 4, 4, 4, 4, // 5-8 + 5, 5, 5, 5, 5, 5, 5, 5, // 9-16 + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 // 17-32 + ]; + + private static readonly byte[] EndOfBlockToPositionLarge = [ + 6, // place holder + 7, // 33-64 + 8, + 8, // 65-128 + 9, + 9, + 9, + 9, // 129-256 + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, // 257-512 + 11 // 513- + ]; + + // Maps tx set types to the indices. INTRA values only + private static readonly int[] ExtendedTransformSetToIndex = [0, -1, 2, 1, -1, -1]; + + internal static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra) + { + int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint]; + if (endOfBlock > 2) + { + endOfBlock += endOfBlockExtra; + } + + return endOfBlock; + } + + internal static int GetBaseRangeContextEndOfBlock(int index, int blockWidthLog2, Av1TransformClass transformClass) + { + int row = index >> blockWidthLog2; + int col = index - (row << blockWidthLog2); + if (index == 0) + { + return 0; + } + + if ((transformClass == Av1TransformClass.Class2D && row < 2 && col < 2) || + (transformClass == Av1TransformClass.ClassHorizontal && col == 0) || + (transformClass == Av1TransformClass.ClassVertical && row == 0)) + { + return 7; + } + + return 14; + } + + /// + /// SVT: get_lower_levels_ctx_eob + /// + internal static int GetLowerLevelContextEndOfBlock(int blockWidthLog2, int height, int scanIndex) + { + if (scanIndex == 0) + { + return 0; + } + + if (scanIndex <= height << blockWidthLog2 >> 3) + { + return 1; + } + + if (scanIndex <= height << blockWidthLog2 >> 2) + { + return 2; + } + + return 3; + } + + /// + /// SVT: get_br_ctx_2d + /// + internal static int GetBaseRangeContext2d(Av1LevelBuffer levels, int index, int blockWidthLog2) + { + DebugGuard.MustBeGreaterThan(index, 0, nameof(index)); + int y = index >> blockWidthLog2; + int x = index - (y << blockWidthLog2); + int stride = (1 << blockWidthLog2) + Av1Constants.TransformPadHorizontal; + int pos = (y * stride) + x; + Span row0 = levels.GetRow(y); + Span row1 = levels.GetRow(y + 1); + int mag = + Math.Min((int)row0[1], Av1Constants.MaxBaseRange) + + Math.Min((int)row1[0], Av1Constants.MaxBaseRange) + + Math.Min((int)row1[1], Av1Constants.MaxBaseRange); + mag = Math.Min((mag + 1) >> 1, 6); + if ((y | x) < 2) + { + return mag + 7; + } + + return mag + 14; + } + + /// + /// SVT: get_lower_levels_ctx_2d + /// + internal static int GetLowerLevelsContext2d(Av1LevelBuffer levelBuffer, int index, int blockWidthLog2, Av1TransformSize transformSize) + { + DebugGuard.MustBeGreaterThan(index, 0, nameof(index)); + int y = index >> blockWidthLog2; + int x = index - (y << blockWidthLog2); + int mag; + Span row0 = levelBuffer.GetRow(y); + Span row1 = levelBuffer.GetRow(y + 1); + Span row2 = levelBuffer.GetRow(y + 2); + mag = Math.Min((int)row0[1], 3); // { 0, 1 } + mag += Math.Min((int)row1[0], 3); // { 1, 0 } + mag += Math.Min((int)row1[1], 3); // { 1, 1 } + mag += Math.Min((int)row0[2], 3); // { 0, 2 } + mag += Math.Min((int)row2[0], 3); // { 2, 0 } + + int ctx = Math.Min((mag + 1) >> 1, 4); + return ctx + Av1NzMap.GetNzMapContext(transformSize, index); + } + + /// + /// SVT: get_br_ctx + /// + internal static int GetBaseRangeContext(Av1LevelBuffer levels, int index, int blockWidthLog2, Av1TransformClass transformClass) + { + int y = index >> blockWidthLog2; + int x = index - (y << blockWidthLog2); + Span row0 = levels.GetRow(y); + Span row1 = levels.GetRow(y + 1); + int mag = row0[x + 1]; + mag += row1[x]; + switch (transformClass) + { + case Av1TransformClass.Class2D: + mag += row1[x + 1]; + mag = Math.Min((mag + 1) >> 1, 6); + if (index == 0) + { + return mag; + } + + if (y < 2 && x < 2) + { + return mag + 7; + } + + break; + case Av1TransformClass.ClassHorizontal: + mag += row0[2]; + mag = Math.Min((mag + 1) >> 1, 6); + if (index == 0) + { + return mag; + } + + if (x == 0) + { + return mag + 7; + } + + break; + case Av1TransformClass.ClassVertical: + mag += levels.GetRow(y + 2)[0]; + mag = Math.Min((mag + 1) >> 1, 6); + if (index == 0) + { + return mag; + } + + if (y == 0) + { + return mag + 7; + } + + break; + default: + break; + } + + return mag + 14; + } + + internal static int GetLowerLevelsContext(Av1LevelBuffer levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) + { + int stats = Av1NzMap.GetNzMagnitude(levels, pos >> bwl, transformClass); + return Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); + } + + internal 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; + } + + internal 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(); + } + + /// + /// SVT: get_nz_map_ctx + /// + internal static sbyte GetNzMapContext( + Av1LevelBuffer levels, + int index, + int blockWidthLog2, + int height, + int scan_idx, + bool is_eob, + Av1TransformSize transformSize, + Av1TransformClass transformClass) + { + if (is_eob) + { + if (scan_idx == 0) + { + return 0; + } + + if (scan_idx <= (height << blockWidthLog2) / 8) + { + return 1; + } + + if (scan_idx <= (height << blockWidthLog2) / 4) + { + return 2; + } + + return 3; + } + + int stats = Av1NzMap.GetNzMagnitude(levels, index, blockWidthLog2, transformClass); + return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, index, blockWidthLog2, transformSize, transformClass); + } + + /// + /// SVT: svt_av1_get_nz_map_contexts_c + /// + internal static void GetNzMapContexts( + Av1LevelBuffer levels, + ReadOnlySpan scan, + ushort eob, + Av1TransformSize transformSize, + Av1TransformClass transformClass, + Span coefficientContexts) + { + int blockWidthLog2 = transformSize.GetBlockWidthLog2(); + int height = transformSize.GetHeight(); + for (int i = 0; i < eob; ++i) + { + int pos = scan[i]; + coefficientContexts[pos] = GetNzMapContext(levels, pos, blockWidthLog2, height, i, i == eob - 1, transformSize, transformClass); + } + } + + /// + /// SVT: get_ext_tx_set_type + /// + internal static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet) + { + Av1TransformSize transformSizeSquareUp = transformSize.GetSquareUpSize(); + + if (transformSizeSquareUp > Av1TransformSize.Size32x32) + { + return Av1TransformSetType.DctOnly; + } + + if (transformSizeSquareUp == Av1TransformSize.Size32x32) + { + return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.DctOnly; + } + + if (useReducedTransformSet) + { + return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.Dtt4Identity; + } + + Av1TransformSize transformSizeSquare = transformSize.GetSquareSize(); + if (isInter) + { + return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt9Identity1dDct : Av1TransformSetType.All16; + } + else + { + return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; + } + } + + internal static int GetExtendedTransformTypeCount(Av1TransformSize transformSize, bool useReducedTransformSet) + { + int setType = (int)GetExtendedTransformSetType(transformSize, useReducedTransformSet); + return TransformCountInSet[setType]; + } + + /// + /// SVT: get_ext_tx_set + /// + internal static int GetExtendedTransformSet(Av1TransformSize transformSize, bool useReducedTransformSet) + { + int setType = (int)GetExtendedTransformSetType(transformSize, useReducedTransformSet); + return ExtendedTransformSetToIndex[setType]; + } + + /// + /// SVT: set_dc_sign + /// + internal static void SetDcSign(ref int culLevel, int dcValue) + { + if (dcValue < 0) + { + culLevel |= 1 << Av1Constants.CoefficientContextBitCount; + } + else if (dcValue > 0) + { + culLevel += 2 << Av1Constants.CoefficientContextBitCount; + } + } + + /// + /// SVT: get_eob_pos_token + /// + internal static short GetEndOfBlockPosition(ushort endOfBlock, out int extra) + { + short t; + if (endOfBlock < 33) + { + t = EndOfBlockToPositionSmall[endOfBlock]; + } + else + { + int e = Math.Min((endOfBlock - 1) >> 5, 16); + t = EndOfBlockToPositionLarge[e]; + } + + extra = endOfBlock - EndOfBlockGroupStart[t]; + return t; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs index eeaafe87ed..544e8915b3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs @@ -11,8 +11,6 @@ internal ref struct Av1SymbolDecoder { private static readonly int[] IntraModeContext = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; private static readonly int[] AlphaVContexts = [-1, 0, 3, -1, 1, 4, -1, 2, 5]; - public static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - public static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513]; private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; @@ -35,10 +33,12 @@ internal ref struct Av1SymbolDecoder private readonly Av1Distribution[][][] endOfBlockExtra; private readonly Av1Distribution chromeForLumaSign = Av1DefaultDistributions.ChromeForLumaSign; private readonly Av1Distribution[] chromeForLumaAlpha = Av1DefaultDistributions.ChromeForLumaAlpha; + private Configuration configuration; private Av1SymbolReader reader; - public Av1SymbolDecoder(Span tileData, int qIndex) + public Av1SymbolDecoder(Configuration configuration, Span tileData, int qIndex) { + this.configuration = configuration; this.reader = new Av1SymbolReader(tileData); this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex); this.coefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex); @@ -237,8 +237,7 @@ internal ref struct Av1SymbolDecoder 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))..]; + Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); bool allZero = this.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext); int bwl = transformSize.GetBlockWidthLog2(); @@ -263,7 +262,7 @@ internal ref struct Av1SymbolDecoder endOfBlock = this.ReadEndOfBlockPosition(transformSize, transformClass, transformSizeContext, planeType); if (endOfBlock > 1) { - Array.Fill(levelsBuffer, (byte)0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd); + levels.Clear(); } this.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, height, scan, bwl, levels, transformSizeContext, planeType); @@ -292,7 +291,7 @@ internal ref struct Av1SymbolDecoder { int endOfBlockExtra = 0; int endOfBlockPoint = this.ReadEndOfBlockFlag(planeType, transformClass, transformSize); - int endOfBlockShift = EndOfBlockOffsetBits[endOfBlockPoint]; + int endOfBlockShift = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPoint]; if (endOfBlockShift > 0) { int endOfBlockContext = endOfBlockPoint; @@ -313,18 +312,18 @@ internal ref struct Av1SymbolDecoder } } - return RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra); + return Av1SymbolContextHelper.RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra); } - public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { int i = endOfBlock - 1; int pos = scan[i]; - int coefficientContext = GetLowerLevelContextEndOfBlock(bwl, height, i); + int coefficientContext = Av1SymbolContextHelper.GetLowerLevelContextEndOfBlock(blockWidthLog2, height, i); int level = this.ReadBaseEndOfBlock(transformSizeContext, planeType, coefficientContext); if (level > Av1Constants.BaseLevelsCount) { - int baseRangeContext = GetBaseRangeContextEndOfBlock(pos, bwl, transformClass); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContextEndOfBlock(pos, blockWidthLog2, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange / Av1Constants.BaseRangeSizeMinus1; idx++) { int coefficinetBaseRange = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); @@ -336,19 +335,19 @@ internal ref struct Av1SymbolDecoder } } - levels[GetPaddedIndex(pos, bwl)] = (byte)level; + levels.GetPaddedRow(pos, blockWidthLog2)[0] = (byte)level; } - public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { for (int c = endScanIndex; c >= startScanIndex; --c) { int pos = scan[c]; - int coefficientContext = GetLowerLevelsContext2d(levels, pos, bwl, transformSize); + int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext2d(levels, pos, blockWidthLog2, transformSize); int level = this.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); if (level > Av1Constants.BaseLevelsCount) { - int baseRangeContext = GetBaseRangeContext2d(levels, pos, bwl); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext2d(levels, pos, blockWidthLog2); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) { int k = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); @@ -360,20 +359,20 @@ internal ref struct Av1SymbolDecoder } } - levels[GetPaddedIndex(pos, bwl)] = (byte)level; + levels.GetPaddedRow(pos, blockWidthLog2)[0] = (byte)level; } } - public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { for (int c = endScanIndex; c >= startScanIndex; --c) { int pos = scan[c]; - int coefficientContext = GetLowerLevelsContext(levels, pos, bwl, transformSize, transformClass); + int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext(levels, pos, blockWidthLog2, transformSize, transformClass); int level = this.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); if (level > Av1Constants.BaseLevelsCount) { - int baseRangeContext = GetBaseRangeContext(levels, pos, bwl, transformClass); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, pos, blockWidthLog2, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) { int k = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); @@ -385,11 +384,11 @@ internal ref struct Av1SymbolDecoder } } - levels[GetPaddedIndex(pos, bwl)] = (byte)level; + levels.GetPaddedRow(pos, blockWidthLog2)[0] = (byte)level; } } - public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, int bwl, Span levels, int dcSignContext, Av1PlaneType planeType) + public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType) { int maxScanLine = 0; int culLevel = 0; @@ -398,7 +397,7 @@ internal ref struct Av1SymbolDecoder for (int c = 0; c < endOfBlock; c++) { int sign = 0; - int level = levels[GetPaddedIndex(scan[c], bwl)]; + int level = levels.GetPaddedRow(scan[c], blockWidthLog2)[0]; if (level != 0) { maxScanLine = Math.Max(maxScanLine, scan[c]); @@ -429,7 +428,7 @@ internal ref struct Av1SymbolDecoder } culLevel = Math.Min(Av1Constants.CoefficientContextMask, culLevel); - SetDcSign(ref culLevel, dcValue); + Av1SymbolContextHelper.SetDcSign(ref culLevel, dcValue); return culLevel; } @@ -472,159 +471,6 @@ internal ref struct Av1SymbolDecoder return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]); } - private static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra) - { - int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint]; - if (endOfBlock > 2) - { - endOfBlock += endOfBlockExtra; - } - - return endOfBlock; - } - - private static int GetBaseRangeContextEndOfBlock(int pos, int bwl, Av1TransformClass transformClass) - { - int row = pos >> bwl; - int col = pos - (row << bwl); - if (pos == 0) - { - return 0; - } - - if (transformClass == Av1TransformClass.Class2D && row < 2 && col < 2 || - transformClass == Av1TransformClass.ClassHorizontal && col == 0 || - transformClass == Av1TransformClass.ClassVertical && row == 0) - { - return 7; - } - - return 14; - } - - private static int GetLowerLevelContextEndOfBlock(int bwl, int height, int scanIndex) - { - if (scanIndex == 0) - { - return 0; - } - - if (scanIndex <= height << bwl >> 3) - { - return 1; - } - - if (scanIndex <= height << bwl >> 2) - { - return 2; - } - - return 3; - } - - private static int GetBaseRangeContext2d(Span levels, int c, int bwl) - { - DebugGuard.MustBeGreaterThan(c, 0, nameof(c)); - int row = c >> bwl; - int col = c - (row << bwl); - int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; - int pos = row * stride + col; - int mag = - Math.Min((int)levels[pos + 1], Av1Constants.MaxBaseRange) + - Math.Min((int)levels[pos + stride], Av1Constants.MaxBaseRange) + - Math.Min((int)levels[pos + 1 + stride], Av1Constants.MaxBaseRange); - mag = Math.Min(mag + 1 >> 1, 6); - if ((row | col) < 2) - { - return mag + 7; - } - - return mag + 14; - } - - private static int GetLowerLevelsContext2d(Span levels, int pos, int bwl, Av1TransformSize transformSize) - { - DebugGuard.MustBeGreaterThan(pos, 0, nameof(pos)); - int mag; - levels = levels[GetPaddedIndex(pos, bwl)..]; - mag = Math.Min((int)levels[1], 3); // { 0, 1 } - mag += Math.Min((int)levels[(1 << bwl) + Av1Constants.TransformPadHorizontal], 3); // { 1, 0 } - mag += Math.Min((int)levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1], 3); // { 1, 1 } - mag += Math.Min((int)levels[2], 3); // { 0, 2 } - mag += Math.Min((int)levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)], 3); // { 2, 0 } - - int ctx = Math.Min(mag + 1 >> 1, 4); - return ctx + Av1NzMap.GetNzMapContext(transformSize, pos); - } - - private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1TransformClass transformClass) - { - int row = c >> bwl; - int col = c - (row << bwl); - int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; - int pos = row * stride + col; - int mag = levels[pos + 1]; - mag += levels[pos + stride]; - switch (transformClass) - { - case Av1TransformClass.Class2D: - mag += levels[pos + stride + 1]; - mag = Math.Min(mag + 1 >> 1, 6); - if (c == 0) - { - return mag; - } - - if (row < 2 && col < 2) - { - return mag + 7; - } - - break; - case Av1TransformClass.ClassHorizontal: - mag += levels[pos + 2]; - mag = Math.Min(mag + 1 >> 1, 6); - if (c == 0) - { - return mag; - } - - if (col == 0) - { - return mag + 7; - } - - break; - case Av1TransformClass.ClassVertical: - mag += levels[pos + (stride << 1)]; - mag = Math.Min(mag + 1 >> 1, 6); - if (c == 0) - { - return mag; - } - - if (row == 0) - { - return mag + 7; - } - - break; - default: - break; - } - - return mag + 14; - } - - private static int GetLowerLevelsContext(ReadOnlySpan levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) - { - int stats = Av1NzMap.GetNzMagnitude(levels[GetPaddedIndex(pos, bwl)..], bwl, transformClass); - return Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); - } - - public static int GetPaddedIndex(int scanIndex, int bwl) - => scanIndex + (scanIndex >> bwl << Av1Constants.TransformPadHorizontalLog2); - private int ReadGolomb() { int x = 1; @@ -651,19 +497,6 @@ internal ref struct Av1SymbolDecoder return x - 1; } - private static void SetDcSign(ref int culLevel, int dcValue) - { - if (dcValue < 0) - { - culLevel |= 1 << Av1Constants.CoefficientContextBitCount; - } - else if (dcValue > 0) - { - culLevel += 2 << Av1Constants.CoefficientContextBitCount; - } - } - - private void UpdateCoefficientContext( Av1BlockModeInfo modeInfo, int[] aboveContexts, @@ -721,11 +554,11 @@ internal ref struct Av1SymbolDecoder { // 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); + transformType = Av1SymbolContextHelper.ConvertIntraModeToTransformType(modeInfo, Av1PlaneType.Uv); } } - Av1TransformSetType transformSetType = GetExtendedTransformSetType(transformSize, useReducedTransformSet); + Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet); if (!transformType.IsExtendedSetUsed(transformSetType)) { transformType = Av1TransformType.DctDct; @@ -734,35 +567,6 @@ internal ref struct Av1SymbolDecoder 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 417c58a5a5..0822489d3d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -1,11 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Drawing; -using System.Formats.Asn1; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; @@ -24,46 +20,6 @@ internal class Av1SymbolEncoder : IDisposable [7, 8, 9, 12, 10, 11, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6], ]; - private static readonly byte[] EndOfBlockToPositionSmall = [ - 0, 1, 2, // 0-2 - 3, 3, // 3-4 - 4, 4, 4, 4, // 5-8 - 5, 5, 5, 5, 5, 5, 5, 5, // 9-16 - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 // 17-32 - ]; - - private static readonly byte[] EndOfBlockToPositionLarge = [ - 6, // place holder - 7, // 33-64 - 8, - 8, // 65-128 - 9, - 9, - 9, - 9, // 129-256 - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 10, // 257-512 - 11 // 513- - ]; - - private static readonly int[] TransformCountInSet = [1, 2, 5, 7, 12, 16]; - - // Maps tx set types to the indices. - private static readonly int[][] ExtendedTransformSetToIndex = [ - - // Intra - [0, -1, 2, 1, -1, -1], - - // Inter - [0, 3, -1, -1, 2, 1] - ]; - private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; @@ -75,6 +31,7 @@ internal class Av1SymbolEncoder : IDisposable private readonly Av1Distribution[][][] endOfBlockExtra; private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform; private bool isDisposed; + private readonly Configuration configuration; private Av1SymbolWriter writer; public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex) @@ -85,6 +42,7 @@ internal class Av1SymbolEncoder : IDisposable this.coefficientsBaseEndOfBlock = Av1DefaultDistributions.GetBaseEndOfBlock(qIndex); this.dcSign = Av1DefaultDistributions.GetDcSign(qIndex); this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex); + this.configuration = configuration; this.writer = new(configuration, initialSize); } @@ -140,12 +98,11 @@ internal class Av1SymbolEncoder : IDisposable Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); ReadOnlySpan scan = scanOrder.Scan; int bwl = transformSize.GetBlockWidthLog2(); - Av1TransformSize transformSizeContext = (Av1TransformSize)((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1 >> 1); + Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1) >> 1); ref Av1SymbolWriter w = ref this.writer; - Span levels_buf = new byte[Av1Constants.TransformPad2d]; - Span levels = SetLevels(levels_buf, width); + Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); Span coeff_contexts = new sbyte[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize]; Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext)); @@ -158,16 +115,16 @@ internal class Av1SymbolEncoder : IDisposable return 0; } - InitializeLevels(coeffBuffer, width, height, levels); + levels.Initialize(coeffBuffer); if (componentType == Av1ComponentType.Luminance) { this.WriteTransformType(transformType, transformSize, useReducedTransformSet, baseQIndex, filterIntraMode, intraDirection); } - short endOfBlockPosition = GetEndOfBlockPosition(eob, out int eob_extra); + short endOfBlockPosition = Av1SymbolContextHelper.GetEndOfBlockPosition(eob, out int eob_extra); this.WriteEndOfBlockFlag(componentType, transformClass, transformSize, endOfBlockPosition); - int eob_offset_bits = Av1SymbolDecoder.EndOfBlockOffsetBits[endOfBlockPosition]; + int eob_offset_bits = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPosition]; if (eob_offset_bits > 0) { int eob_shift = eob_offset_bits - 1; @@ -181,7 +138,7 @@ internal class Av1SymbolEncoder : IDisposable } } - GetNzMapContexts(levels, scan, eob, transformSize, transformClass, coeff_contexts); + Av1SymbolContextHelper.GetNzMapContexts(levels, scan, eob, transformSize, transformClass, coeff_contexts); int limitedTransformSizeContext = Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32); for (c = eob - 1; c >= 0; --c) { @@ -203,7 +160,7 @@ internal class Av1SymbolEncoder : IDisposable { // level is above 1. int baseRange = level - 1 - Av1Constants.BaseLevelsCount; - int baseRangeContext = GetBaseRangeContext(levels, pos, bwl, transformClass); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, pos, bwl, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) { int k = Math.Min(baseRange - idx, Av1Constants.BaseRangeSizeMinus1); @@ -248,196 +205,10 @@ internal class Av1SymbolEncoder : IDisposable cul_level = Math.Min(Av1Constants.CoefficientContextMask, cul_level); // DC value - SetDcSign(ref cul_level, coeffBuffer[0]); + Av1SymbolContextHelper.SetDcSign(ref cul_level, coeffBuffer[0]); return cul_level; } - /// - /// SVT: set_dc_sign - /// - private static void SetDcSign(ref int culLevel, int dcValue) - { - if (dcValue < 0) - { - culLevel |= 1 << Av1Constants.CoefficientContextBitCount; - } - else if (dcValue > 0) - { - culLevel += 2 << Av1Constants.CoefficientContextBitCount; - } - } - - /// - /// SVT: get_br_ctx - /// - private static int GetBaseRangeContext(Span levels, short c, int bwl, Av1TransformClass transformClass) - { - int row = c >> bwl; - int col = c - (row << bwl); - int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; - int pos = row * stride + col; - int mag = levels[pos + 1]; - mag += levels[pos + stride]; - switch (transformClass) - { - case Av1TransformClass.Class2D: - mag += levels[pos + stride + 1]; - mag = Math.Min(mag + 1 >> 1, 6); - if (c == 0) - { - return mag; - } - - if (row < 2 && col < 2) - { - return mag + 7; - } - - break; - case Av1TransformClass.ClassHorizontal: - mag += levels[pos + 2]; - mag = Math.Min(mag + 1 >> 1, 6); - if (c == 0) - { - return mag; - } - - if (col == 0) - { - return mag + 7; - } - - break; - case Av1TransformClass.ClassVertical: - mag += levels[pos + (stride << 1)]; - mag = Math.Min(mag + 1 >> 1, 6); - if (c == 0) - { - return mag; - } - - if (row == 0) - { - return mag + 7; - } - - break; - default: - break; - } - - return mag + 14; - } - - /// - /// SVT: get_eob_pos_token - /// - private static short GetEndOfBlockPosition(ushort endOfBlock, out int extra) - { - short t; - if (endOfBlock < 33) - { - t = EndOfBlockToPositionSmall[endOfBlock]; - } - else - { - int e = Math.Min(endOfBlock - 1 >> 5, 16); - t = EndOfBlockToPositionLarge[e]; - } - - extra = endOfBlock - Av1SymbolDecoder.EndOfBlockGroupStart[t]; - return t; - } - - /// - /// SVT: get_nz_map_ctx - /// - private static sbyte GetNzMapContext( - ReadOnlySpan levels, - int pos, - int bwl, - int height, - int scan_idx, - bool is_eob, - Av1TransformSize transformSize, - Av1TransformClass transformClass) - { - if (is_eob) - { - if (scan_idx == 0) - { - return 0; - } - - if (scan_idx <= (height << bwl) / 8) - { - return 1; - } - - if (scan_idx <= (height << bwl) / 4) - { - return 2; - } - - return 3; - } - - int stats = Av1NzMap.GetNzMagnitude(levels[Av1SymbolDecoder.GetPaddedIndex(pos, bwl)..], bwl, transformClass); - return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); - } - - /// - /// SVT: svt_av1_get_nz_map_contexts_c - /// - private static void GetNzMapContexts( - ReadOnlySpan levels, - ReadOnlySpan scan, - ushort eob, - Av1TransformSize tx_size, - Av1TransformClass tx_class, - Span coeff_contexts) - { - int bwl = tx_size.GetBlockWidthLog2(); - int height = tx_size.GetHeight(); - for (int i = 0; i < eob; ++i) - { - int pos = scan[i]; - coeff_contexts[pos] = GetNzMapContext(levels, pos, bwl, height, i, i == eob - 1, tx_size, tx_class); - } - } - - /// - /// SVT: svt_av1_txb_init_levels_c - /// - private static void InitializeLevels(Span coefficientBuffer, int width, int height, Span levels) - { - int stride = width + Av1Constants.TransformPadHorizontal; - 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)); - - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - ls = (byte)Av1Math.Clamp(Math.Abs(coefficientBuffer[(y * width) + x]), 0, byte.MaxValue); - ls = ref Unsafe.Add(ref ls, 1); - } - - Unsafe.InitBlock(ref ls, 0, Av1Constants.TransformPadHorizontal); - } - } - - /// - /// SVT: set_levels from EbCommonUtils.h - /// - private static Span SetLevels(Span levelsBuffer, int width) - { - int stride = width + Av1Constants.TransformPadHorizontal; - return levelsBuffer[(Av1Constants.TransformPadTop * stride)..]; - } - private void WriteSkip(bool hasEndOfBlock, int context) { // Has EOB, means we won't skip, negating the logic. @@ -503,28 +274,20 @@ internal class Av1SymbolEncoder : IDisposable Av1PredictionMode intraDirection) { ref Av1SymbolWriter w = ref this.writer; - bool isInter = false; // mbmi->block_mi.use_intrabc || is_inter_mode(mbmi->block_mi.mode); - if (GetExtendedTransformTypeCount(transformSize, isInter, useReducedTransformSet) > 1 && baseQIndex > 0) + // bool isInter = mbmi->block_mi.use_intrabc || is_inter_mode(mbmi->block_mi.mode); + if (Av1SymbolContextHelper.GetExtendedTransformTypeCount(transformSize, useReducedTransformSet) > 1 && baseQIndex > 0) { Av1TransformSize square_tx_size = transformSize.GetSquareSize(); Guard.MustBeLessThanOrEqualTo((int)square_tx_size, Av1Constants.ExtendedTransformCount, nameof(square_tx_size)); - Av1TransformSetType tx_set_type = GetExtendedTransformSetType(transformSize, isInter, useReducedTransformSet); - int eset = GetExtendedTransformSet(transformSize, isInter, useReducedTransformSet); + Av1TransformSetType tx_set_type = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet); + int eset = Av1SymbolContextHelper.GetExtendedTransformSet(transformSize, useReducedTransformSet); // eset == 0 should correspond to a set with only DCT_DCT and there // is no need to send the tx_type Guard.MustBeGreaterThan(eset, 0, nameof(eset)); // assert(av1_ext_tx_used[tx_set_type][transformType]); - if (isInter) - { - /* - w.WriteSymbol( - av1_ext_tx_ind[tx_set_type][transformType], - this.inter_ext_tx_cdf[eset][square_tx_size]);*/ - } - else { Av1PredictionMode intra_dir; if (filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes) @@ -544,52 +307,4 @@ internal class Av1SymbolEncoder : IDisposable } } } - - /// - /// SVT: get_ext_tx_set - /// - private static int GetExtendedTransformSet(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet) - { - int set_type = (int)GetExtendedTransformSetType(transformSize, isInter, useReducedTransformSet); - return ExtendedTransformSetToIndex[isInter ? 1 : 0][set_type]; - } - - /// - /// SVT: get_ext_tx_set_type - /// - private static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet) - { - Av1TransformSize transformSizeSquareUp = transformSize.GetSquareUpSize(); - - if (transformSizeSquareUp > Av1TransformSize.Size32x32) - { - return Av1TransformSetType.DctOnly; - } - - if (transformSizeSquareUp == Av1TransformSize.Size32x32) - { - return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.DctOnly; - } - - if (useReducedTransformSet) - { - return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.Dtt4Identity; - } - - Av1TransformSize transformSizeSquare = transformSize.GetSquareSize(); - if (isInter) - { - return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt9Identity1dDct : Av1TransformSetType.All16; - } - else - { - return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; - } - } - - private static int GetExtendedTransformTypeCount(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet) - { - int set_type = (int)GetExtendedTransformSetType(transformSize, isInter, useReducedTransformSet); - return TransformCountInSet[set_type]; - } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs new file mode 100644 index 0000000000..d3dd37836e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal sealed class Av1LevelBuffer : IDisposable +{ + private IMemoryOwner? memory; + + public Av1LevelBuffer(Configuration configuration) + : this(configuration, new Size(Av1Constants.MaxTransformSize, Av1Constants.MaxTransformSize)) + { + } + + public Av1LevelBuffer(Configuration configuration, Size size) + { + this.Size = size; + int totalHeight = Av1Constants.TransformPadTop + size.Height + Av1Constants.TransformPadBottom; + this.Stride = Av1Constants.TransformPadHorizontal + size.Width; + this.memory = configuration.MemoryAllocator.Allocate(this.Stride * totalHeight, AllocationOptions.Clean); + } + + public Size Size { get; } + + public int Stride { get; } + + public void Initialize(Span coefficientBuffer) + { + ObjectDisposedException.ThrowIf(this.memory == null, this); + ArgumentOutOfRangeException.ThrowIfLessThan(coefficientBuffer.Length, this.Size.Width * this.Size.Height, nameof(coefficientBuffer)); + for (int y = 0; y < this.Size.Height; y++) + { + ref byte destRef = ref this.GetRow(y)[0]; + ref int sourceRef = ref coefficientBuffer[y * this.Size.Width]; + for (int x = 0; x < this.Size.Width; x++) + { + destRef = (byte)Av1Math.Clamp(sourceRef, 0, byte.MaxValue); + destRef = ref Unsafe.Add(ref destRef, 1); + sourceRef = ref Unsafe.Add(ref sourceRef, 1); + } + } + } + + public Span GetRow(int y) + { + ObjectDisposedException.ThrowIf(this.memory == null, this); + ArgumentOutOfRangeException.ThrowIfLessThan(y, -Av1Constants.TransformPadTop); + int row = y + Av1Constants.TransformPadTop; + return this.memory.Memory.Span.Slice(row * this.Stride, this.Size.Width); + } + + public void Dispose() + { + this.memory?.Dispose(); + this.memory = null; + } + + internal void Clear() + { + ObjectDisposedException.ThrowIf(this.memory == null, this); + this.memory.Memory.Span.Clear(); + } + + internal Span GetPaddedRow(int index, int blockWidthLog2) + { + int y = index >> blockWidthLog2; + return this.GetRow(y); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 0001ecc04a..63827c5e98 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -77,7 +77,7 @@ internal class Av1TileReader : IAv1TileReader /// public void ReadTile(Span tileData, int tileNum) { - Av1SymbolDecoder reader = new(tileData, this.FrameHeader.QuantizationParameters.BaseQIndex); + Av1SymbolDecoder reader = new(this.configuration, tileData, this.FrameHeader.QuantizationParameters.BaseQIndex); int tileColumnIndex = tileNum % this.FrameHeader.TilesInfo.TileColumnCount; int tileRowIndex = tileNum / this.FrameHeader.TilesInfo.TileColumnCount; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs index 2e1bd61914..e877ef7130 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs @@ -46,7 +46,7 @@ public class Av1CoefficientsEntropyTests using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolDecoder decoder = new(Configuration.Default, 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); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs index 37c4aa4a61..a23acfc046 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs @@ -208,7 +208,7 @@ public class Av1EntropyTests using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { @@ -245,7 +245,7 @@ public class Av1EntropyTests using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { @@ -282,7 +282,7 @@ public class Av1EntropyTests using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { @@ -310,7 +310,7 @@ public class Av1EntropyTests using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); for (int i = 0; i < values.Length; i++) { actuals[i] = decoder.ReadUseIntraBlockCopy();