Browse Source

Test case for coefficient round trip

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
fa886a4d25
  1. 182
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
  2. 13
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
  3. 25
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs
  4. 15
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs
  5. 11
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs
  6. 16
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs
  7. 7
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs
  8. 17
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs
  9. 19
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs
  10. 9
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs
  11. 165
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
  12. 57
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs

182
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs

@ -206,6 +206,88 @@ internal ref struct Av1SymbolDecoder
return r.ReadSymbol(this.chromeForLumaAlpha[context]); return r.ReadSymbol(this.chromeForLumaAlpha[context]);
} }
/// <summary>
/// 5.11.39. Coefficients syntax.
/// </summary>
/// <remarks>
/// The implementation is taken from SVT-AV1 library, which deviates from the code flow in the specification.
/// </remarks>
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<int> 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<byte> 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<short> 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) public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformClass transformClass, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{ {
int endOfBlockExtra = 0; int endOfBlockExtra = 0;
@ -581,6 +663,106 @@ internal ref struct Av1SymbolDecoder
} }
} }
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) internal static Av1Distribution GetSplitOrHorizontalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context)
{ {
Av1Distribution input = inputs[context]; Av1Distribution input = inputs[context];

13
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs

@ -415,13 +415,13 @@ internal class Av1SymbolEncoder : IDisposable
ref byte ls = ref levels[0]; ref byte ls = ref levels[0];
Unsafe.InitBlock(ref levels[-Av1Constants.TransformPadTop * stride], 0, (uint)(Av1Constants.TransformPadTop * stride)); 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); ls = ref Unsafe.Add(ref ls, 1);
} }
@ -433,7 +433,10 @@ internal class Av1SymbolEncoder : IDisposable
/// SVT: set_levels from EbCommonUtils.h /// SVT: set_levels from EbCommonUtils.h
/// </summary> /// </summary>
private static Span<byte> SetLevels(Span<byte> levelsBuffer, int width) private static Span<byte> SetLevels(Span<byte> 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) private void WriteSkip(bool hasEndOfBlock, int context)
{ {

25
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. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; 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; }
}
} }

15
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs

@ -5,18 +5,15 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; 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; }
}
} }

11
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs

@ -3,14 +3,11 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; 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; }
}
} }

16
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs

@ -11,14 +11,6 @@ internal class Av1NeighborArrayUnit<T>
{ {
public static readonly T InvalidNeighborData = T.MaxValue; public static readonly T InvalidNeighborData = T.MaxValue;
[Flags]
public enum UnitMask
{
Left = 1,
Top = 2,
TopLeft = 4,
}
private readonly T[] left; private readonly T[] left;
private readonly T[] top; private readonly T[] top;
private readonly T[] topLeft; private readonly T[] topLeft;
@ -30,6 +22,14 @@ internal class Av1NeighborArrayUnit<T>
this.topLeft = new T[topLeftSize]; this.topLeft = new T[topLeftSize];
} }
[Flags]
public enum UnitMask
{
Left = 1,
Top = 2,
TopLeft = 4,
}
public Span<T> Left => this.left; public Span<T> Left => this.left;
public Span<T> Top => this.top; public Span<T> Top => this.top;

7
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. // Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
internal partial class Av1TileWriter internal class Av1PaletteLumaModeInfo
{ {
internal class Av1PaletteLumaModeInfo
{
}
} }

17
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs

@ -3,20 +3,17 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
internal partial class Av1TileWriter internal class Av1PictureControlSet
{ {
internal class Av1PictureControlSet public required Av1NeighborArrayUnit<byte>[] 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<byte>[] cr_dc_sign_level_coeff_na { get; internal set; }
public required Av1NeighborArrayUnit[] cb_dc_sign_level_coeff_na { get; internal set; } public required Av1NeighborArrayUnit<byte>[] cb_dc_sign_level_coeff_na { get; internal set; }
public required Av1NeighborArrayUnit[] txfm_context_array { get; internal set; } public required Av1NeighborArrayUnit<byte>[] 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; }
}
} }

19
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs

@ -5,18 +5,17 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; 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 PaletteLevel { get; internal set; }
public int AlignedWidth { get; internal set; }
public int AlignedHeight { get; internal set; } public int AlignedWidth { get; internal set; }
}
public int AlignedHeight { get; internal set; }
} }

9
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. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; 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; }
}
} }

165
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs

@ -491,22 +491,6 @@ internal class Av1TileReader : IAv1TileReader
return hasChroma; return hasChroma;
} }
private Av1TransformSize GetSize(int plane, object transformSize) => throw new NotImplementedException();
/// <summary>
/// 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.)
/// </summary>
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);
}
/// <summary> /// <summary>
/// 5.11.35. Transform block syntax. /// 5.11.35. Transform block syntax.
/// </summary> /// </summary>
@ -563,152 +547,15 @@ internal class Av1TileReader : IAv1TileReader
int height = transformSize.GetHeight(); int height = transformSize.GetHeight();
Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + ((int)transformSize.GetSquareUpSize() + 1)) >> 1); Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + ((int)transformSize.GetSquareUpSize() + 1)) >> 1);
Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1);
int culLevel = 0; Point blockPosition = new(blockColumn, blockRow);
bool isLossless = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId];
byte[] levelsBuffer = new byte[Av1Constants.TransformPad2d];
Span<byte> 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<short> 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)
{
bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; bool subX = this.SequenceHeader.ColorConfig.SubSamplingX;
bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; bool subY = this.SequenceHeader.ColorConfig.SubSamplingY;
int[] aboveContexts = this.aboveNeighborContext.GetContext(plane); Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY);
int[] leftContexts = this.leftNeighborContext.GetContext(plane); int blocksWide = partitionInfo.GetMaxBlockWide(planeBlockSize, subX);
int transformSizeWide = transformSize.Get4x4WideCount(); int blocksHigh = partitionInfo.GetMaxBlockHigh(planeBlockSize, subY);
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;
}
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) private Av1TransformBlockContext GetTransformBlockContext(Av1TransformSize transformSize, int plane, Av1BlockSize planeBlockSize, int transformBlockUnitHighCount, int transformBlockUnitWideCount, int startY, int startX)

57
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<int> coefficientsBuffer = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
Span<int> actuals = new int[16];
// Act
encoder.WriteCoefficients(transformSize, transformType, txbIndex, intraDirection, coefficientsBuffer, componentType, transformBlockSkipContext, dcSignContext, endOfBlock, true, BaseQIndex, filterIntraMode);
using IMemoryOwner<byte> 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);
}
}
Loading…
Cancel
Save