Browse Source

Initial symbol decoding

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
fec0413a1a
  1. 11
      src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs
  2. 2
      src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs
  3. 96
      src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs
  4. 46
      src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs
  5. 792
      src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
  6. 22
      src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs
  7. 104
      src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs
  8. 5
      src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs
  9. 29
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs
  10. 18
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
  11. 10
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs
  12. 8
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs
  13. 4
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
  14. 1
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs
  15. 122
      src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs
  16. 114
      src/ImageSharp/Formats/Heif/Av1/Symbol/Av1Distribution.cs
  17. 11
      src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs
  18. 11
      src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs
  19. 124
      src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs
  20. 23
      src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs
  21. 64
      src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs
  22. 34
      src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs
  23. 8
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs
  24. 2
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs
  25. 38
      tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs

11
src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
@ -171,4 +172,14 @@ internal ref struct Av1BitStreamReader
uint temp = this.data[this.wordPosition];
this.nextWord = (temp << 24) | ((temp & 0x0000ff00U) << 8) | ((temp & 0x00ff0000U) >> 8) | (temp >> 24);
}
public Span<byte> GetSymbolReader(int tileDataSize)
{
DebugGuard.IsTrue((this.bitOffset & (WordSize - 1)) == 0, "Symbol reading needs to start on byte boundary.");
// TODO: Pass exact byte iso Word start.
Span<uint> span = this.data.Slice(this.bitOffset >> WordSize, tileDataSize);
this.bitOffset += tileDataSize << 8;
return MemoryMarshal.Cast<uint, byte>(span);
}
}

2
src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs

@ -11,7 +11,7 @@ internal class Av1BlockModeInfo
public Av1PredictionMode PredictionMode { get; }
public Av1PartitionType Partition { get; }
public Av1PartitionType Partition { get; set; }
public int SegmentId { get; }
}

96
src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs

@ -5,30 +5,74 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal enum Av1BlockSize
{
Block4x4,
Block4x8,
Block8x4,
Block8x8,
Block8x16,
Block16x8,
Block16x16,
Block16x32,
Block32x16,
Block32x32,
Block32x64,
Block64x32,
Block64x64,
Block64x128,
Block128x64,
Block128x128,
Block4x16,
Block16x4,
Block8x32,
Block32x8,
Block16x64,
Block64x16,
BlockSizeSAll,
BlockSizeS = Block4x16,
BlockInvalid = 255,
BlockLargest = BlockSizeS - 1,
// See sction 6.10.4 of the Av1 Specification.
/// <summary>A block of samples, 4 samples wide and 4 samples high.</summary>
Block4x4 = 0,
/// <summary>A block of samples, 4 samples wide and 8 samples high.</summary>
Block4x8 = 1,
/// <summary>A block of samples, 8 samples wide and 4 samples high.</summary>
Block8x4 = 2,
/// <summary>A block of samples, 8 samples wide and 8 samples high.</summary>
Block8x8 = 3,
/// <summary>A block of samples, 8 samples wide and 16 samples high.</summary>
Block8x16 = 4,
/// <summary>A block of samples, 16 samples wide and 8 samples high.</summary>
Block16x8 = 5,
/// <summary>A block of samples, 16 samples wide and 16 samples high.</summary>
Block16x16 = 6,
/// <summary>A block of samples, 16 samples wide and 32 samples high.</summary>
Block16x32 = 7,
/// <summary>A block of samples, 32 samples wide and 16 samples high.</summary>
Block32x16 = 8,
/// <summary>A block of samples, 32 samples wide and 32 samples high.</summary>
Block32x32 = 9,
/// <summary>A block of samples, 32 samples wide and 64 samples high.</summary>
Block32x64 = 10,
/// <summary>A block of samples, 64 samples wide and 32 samples high.</summary>
Block64x32 = 11,
/// <summary>A block of samples, 64 samples wide and 64 samples high.</summary>
Block64x64 = 12,
/// <summary>A block of samples, 64 samples wide and 128 samples high.</summary>
Block64x128 = 13,
/// <summary>A block of samples, 128 samples wide and 64 samples high.</summary>
Block128x64 = 14,
/// <summary>A block of samples, 128 samples wide and 128 samples high.</summary>
Block128x128 = 15,
/// <summary>A block of samples, 4 samples wide and 16 samples high.</summary>
Block4x16 = 16,
/// <summary>A block of samples, 16 samples wide and 4 samples high.</summary>
Block16x4 = 17,
/// <summary>A block of samples, 8 samples wide and 32 samples high.</summary>
Block8x32 = 18,
/// <summary>A block of samples, 32 samples wide and 8 samples high.</summary>
Block32x8 = 19,
/// <summary>A block of samples, 16 samples wide and 64 samples high.</summary>
Block16x64 = 20,
/// <summary>A block of samples, 64 samples wide and 16 samples high.</summary>
Block64x16 = 21,
Invalid = 22,
SizeS = Block4x16,
Largest = SizeS - 1,
}

46
src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs

@ -1,14 +1,56 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal static class Av1BlockSizeExtensions
{
private static readonly int[] SizeWide = { 1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16 };
private static readonly int[] SizeHigh = { 1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4 };
private static readonly int[] SizeWide = [1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16];
private static readonly int[] SizeHigh = [1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4];
private static readonly Av1TransformSize[] MaxTransformSize = [
Av1TransformSize.Size4x4, Av1TransformSize.Size4x8, Av1TransformSize.Size8x4, Av1TransformSize.Size8x8,
Av1TransformSize.Size8x16, Av1TransformSize.Size16x8, Av1TransformSize.Size16x16, Av1TransformSize.Size16x32,
Av1TransformSize.Size32x16, Av1TransformSize.Size32x32, Av1TransformSize.Size32x64, Av1TransformSize.Size64x32,
Av1TransformSize.Size64x64, Av1TransformSize.Size64x64, Av1TransformSize.Size64x64, Av1TransformSize.Size64x64,
Av1TransformSize.Size4x16, Av1TransformSize.Size16x4, Av1TransformSize.Size8x32, Av1TransformSize.Size32x8,
Av1TransformSize.Size16x64, Av1TransformSize.Size64x16
];
public static int Get4x4WideCount(this Av1BlockSize blockSize) => SizeWide[(int)blockSize];
public static int Get4x4HighCount(this Av1BlockSize blockSize) => SizeHigh[(int)blockSize];
/// <summary>
/// Returns the width of the block in samples.
/// </summary>
public static int GetWidth(this Av1BlockSize blockSize)
=> Get4x4WideCount(blockSize) << 2;
/// <summary>
/// Returns of the height of the block in 4 samples.
/// </summary>
public static int GetHeight(this Av1BlockSize blockSize)
=> Get4x4HighCount(blockSize) << 2;
/// <summary>
/// Returns base 2 logarithm of the width of the block in units of 4 samples.
/// </summary>
public static int Get4x4WidthLog2(this Av1BlockSize blockSize)
=> Get4x4WideCount(blockSize) << 2;
/// <summary>
/// Returns base 2 logarithm of the height of the block in units of 4 samples.
/// </summary>
public static int Get4x4HeightLog2(this Av1BlockSize blockSize)
=> Get4x4HighCount(blockSize) << 2;
/// <summary>
/// Returns th largest transform size that can be used for blocks of given size.
/// The can be either a square or rectangular block.
/// </summary>
public static Av1TransformSize GetMaximumTransformSize(this Av1BlockSize blockSize)
=> MaxTransformSize[(int)blockSize];
}

792
src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs

@ -2,16 +2,69 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization;
using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal class Av1Decoder : IAv1TileDecoder
{
private static readonly int[] SgrprojXqdMid = [-32, 31];
private static readonly int[] WienerTapsMid = [3, -7, 15];
private const int PartitionProbabilitySet = 4;
private int[] deltaLoopFilter = [];
private bool[][][] blockDecoded = [];
private int[][] referenceSgrXqd = [];
private int[][][] referenceLrWiener = [];
private bool availableUp;
private bool availableLeft;
private bool availableUpForChroma;
private bool availableLeftForChroma;
private Av1ParseAboveContext aboveContext = new();
private Av1ParseLeftContext leftContext = new();
private bool skip;
private bool readDeltas;
private int currentQuantizerIndex;
private Av1PredictionMode[][] YModes = [];
private Av1PredictionMode YMode = Av1PredictionMode.DC;
private Av1PredictionMode UvMode = Av1PredictionMode.DC;
private Av1PredictionMode[][] UvModes = [];
private object[] ReferenceFrame = [];
private object[][][] ReferenceFrames = [];
private bool HasChroma;
private bool SubsamplingX;
private bool SubsamplingY;
private int PaletteSizeY;
private int PaletteSizeUv;
private object[][] aboveLevelContext = [];
private object[][] aboveDcContext = [];
private object[][] leftLevelContext = [];
private object[][] leftDcContext = [];
private Av1TransformSize TransformSize = Av1TransformSize.Size4x4;
private bool skipMode;
private bool IsChromaForLumaAllowed;
private object FilterUltraMode = -1;
private int AngleDeltaY;
private int AngleDeltaUv;
private bool Lossless;
private int[][] SegmentIds = [];
private int MaxLumaWidth;
private int MaxLumaHeight;
private int segmentId;
private int[][] cdefIndex = [];
private int deltaLoopFilterResolution;
private int deltaQuantizerResolution;
public Av1Decoder()
{
this.FrameInfo = new ObuFrameHeader();
this.SequenceHeader = new ObuSequenceHeader();
this.TileInfo = new ObuTileInfo();
this.SeenFrameHeader = false;
this.currentQuantizerIndex = -1;
}
public bool SequenceHeaderDone { get; set; }
@ -32,9 +85,96 @@ internal class Av1Decoder : IAv1TileDecoder
ObuReader.Read(ref reader, buffer.Length, this, false);
}
public void DecodeTile(int tileNum)
public void DecodeTile(Span<byte> tileData, int tileNum)
{
// TODO: Implement
Av1SymbolDecoder reader = new(tileData);
int tileRowIndex = tileNum / this.TileInfo.TileColumnCount;
int tileColumnIndex = tileNum % this.TileInfo.TileColumnCount;
this.aboveContext.Clear();
this.ClearLoopFilterDelta();
int planesCount = this.SequenceHeader.ColorConfig.ChannelCount;
this.referenceSgrXqd = new int[planesCount][];
this.referenceLrWiener = new int[planesCount][][];
for (int plane = 0; plane < planesCount; plane++)
{
this.referenceSgrXqd[plane] = new int[2];
Array.Copy(SgrprojXqdMid, this.referenceSgrXqd[plane], SgrprojXqdMid.Length);
this.referenceLrWiener[plane] = new int[2][];
for (int pass = 0; pass < 2; pass++)
{
this.referenceLrWiener[plane][pass] = new int[ObuConstants.WienerCoefficientCount];
Array.Copy(WienerTapsMid, this.referenceLrWiener[plane][pass], WienerTapsMid.Length);
}
}
Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
int superBlock4x4Size = superBlockSize.Get4x4WideCount();
for (int row = this.TileInfo.TileRowStartModeInfo[tileRowIndex]; row < this.TileInfo.TileRowStartModeInfo[tileRowIndex + 1]; row += this.SequenceHeader.ModeInfoSize)
{
int superBlockRow = (row << ObuConstants.ModeInfoSizeLog2) >> this.SequenceHeader.SuperBlockSizeLog2;
this.leftContext.Clear();
for (int column = this.TileInfo.TileColumnStartModeInfo[tileColumnIndex]; column < this.TileInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; column += this.SequenceHeader.ModeInfoSize)
{
int superBlockColumn = (column << ObuConstants.ModeInfoSizeLog2) >> this.SequenceHeader.SuperBlockSizeLog2;
bool subSamplingX = this.SequenceHeader.ColorConfig.SubSamplingX;
bool subSamplingY = this.SequenceHeader.ColorConfig.SubSamplingY;
bool ReadDeltas = this.FrameInfo.DeltaQParameters.IsPresent;
// Nothing to do for CDEF
// this.ClearCdef(row, column);
this.ClearBlockDecodedFlags(row, column, superBlock4x4Size);
this.ReadLoopRestoration(row, column, superBlockSize);
this.DecodePartition(ref reader, row, column, superBlockSize);
}
}
}
private void ClearLoopFilterDelta() => this.deltaLoopFilter = new int[4];
private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size)
{
int planesCount = this.SequenceHeader.ColorConfig.ChannelCount;
for (int plane = 0; plane < planesCount; plane++)
{
int subX = (plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX) ? 1 : 0;
int subY = (plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY) ? 1 : 0;
int superBlock4x4Width = (this.FrameInfo.ModeInfoColumnCount - column) >> subX;
int superBlock4x4Height = (this.FrameInfo.ModeInfoRowCount - row) >> subY;
for (int y = -1; y <= (superBlock4x4Size >> subY); y++)
{
for (int x = -1; x <= (superBlock4x4Size >> subX); x++)
{
if (y < 0 && x < superBlock4x4Width)
{
this.blockDecoded[plane][y][x] = true;
}
else if (x < 0 && y < superBlock4x4Height)
{
this.blockDecoded[plane][y][x] = true;
}
else
{
this.blockDecoded[plane][y][x] = false;
}
}
}
this.blockDecoded[plane][superBlock4x4Size >> subY][-1] = false;
}
}
private void ReadLoopRestoration(int row, int column, Av1BlockSize superBlockSize)
{
int planesCount = this.SequenceHeader.ColorConfig.ChannelCount;
for (int plane = 0; plane < planesCount; plane++)
{
if (this.FrameInfo.LoopRestorationParameters[plane].Type != ObuRestorationType.None)
{
// TODO: Implement.
throw new NotImplementedException("No loop restoration filter support.");
}
}
}
public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration)
@ -42,9 +182,655 @@ internal class Av1Decoder : IAv1TileDecoder
// TODO: Implement
}
private static void DecodeBlock(Av1BlockModeInfo blockMode, int rowIndex, int columnIndex)
private void DecodePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
{
if (rowIndex >= this.TileInfo.TileRowStartModeInfo[rowIndex] ||
columnIndex >= this.TileInfo.TileColumnStartModeInfo[columnIndex])
{
return;
}
this.availableUp = this.IsInside(rowIndex - 1, columnIndex);
this.availableLeft = this.IsInside(rowIndex, columnIndex - 1);
int block4x4Size = blockSize.Get4x4WideCount();
int halfBlock4x4Size = block4x4Size >> 1;
int quarterBlock4x4Size = halfBlock4x4Size >> 2;
bool hasRows = (rowIndex + halfBlock4x4Size) < this.TileInfo.TileRowCount;
bool hasColumns = (columnIndex + halfBlock4x4Size) < this.TileInfo.TileColumnCount;
Av1PartitionType partitionType = Av1PartitionType.Split;
if (blockSize < Av1BlockSize.Block8x8)
{
partitionType = Av1PartitionType.None;
}
else if (hasRows && hasColumns)
{
int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize);
partitionType = reader.ReadPartitionSymbol(ctx);
}
else if (hasColumns)
{
int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize);
bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx);
partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Horizontal;
}
else if (hasRows)
{
int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize);
bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx);
partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Vertical;
}
Av1BlockSize subSize = partitionType.GetBlockSubSize(blockSize);
Av1BlockSize splitSize = Av1PartitionType.Split.GetBlockSubSize(blockSize);
switch (partitionType)
{
case Av1PartitionType.Split:
this.DecodePartition(ref reader, rowIndex, columnIndex, subSize);
this.DecodePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize);
this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize);
this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize);
break;
case Av1PartitionType.None:
this.DecodeBlock(ref reader, rowIndex, columnIndex, subSize);
break;
default:
throw new NotImplementedException($"Partition type: {partitionType} is not supported.");
}
}
private void DecodeBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
{
int block4x4Width = blockSize.Get4x4WideCount();
int block4x4Height = blockSize.Get4x4HighCount();
int planesCount = this.SequenceHeader.ColorConfig.ChannelCount;
bool hasChroma = planesCount > 1;
if (block4x4Height == 1 && this.SequenceHeader.ColorConfig.SubSamplingY && (rowIndex & 0x1) == 0)
{
hasChroma = false;
}
if (block4x4Width == 1 && this.SequenceHeader.ColorConfig.SubSamplingX && (columnIndex & 0x1) == 0)
{
hasChroma = false;
}
this.availableUp = this.IsInside(rowIndex - 1, columnIndex);
this.availableLeft = this.IsInside(rowIndex, columnIndex - 1);
this.availableUpForChroma = this.availableUp;
this.availableLeftForChroma = this.availableLeft;
if (hasChroma)
{
if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1)
{
this.availableUpForChroma = this.IsInside(rowIndex - 2, columnIndex);
}
if (this.SequenceHeader.ColorConfig.SubSamplingX && block4x4Width == 1)
{
this.availableLeftForChroma = this.IsInside(rowIndex, columnIndex - 2);
}
}
this.ReadModeInfo(ref reader, rowIndex, columnIndex, blockSize);
this.ReadPaletteTokens(ref reader);
this.ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize);
if (this.skip)
{
this.ResetBlockContext(rowIndex, columnIndex, block4x4Width, block4x4Height);
}
bool isCompound = false;
for (int y = 0; y < block4x4Height; y++)
{
for (int x = 0; x < block4x4Width; x++)
{
this.YModes[rowIndex + y][columnIndex + x] = this.YMode;
if (this.ReferenceFrame[0] == (object)ObuFrameType.IntraOnlyFrame && hasChroma)
{
this.UvModes[rowIndex + y][columnIndex + x] = this.UvMode;
}
for (int refList = 0; refList < 2; refList++)
{
this.ReferenceFrames[rowIndex + y][columnIndex + x][refList] = this.ReferenceFrame[refList];
}
}
}
this.ComputePrediction();
this.Residual(rowIndex, columnIndex, blockSize);
}
private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize)
{
int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15;
int widthChunks = Math.Max(1, blockSize.Get4x4WideCount() >> 6);
int heightChunks = Math.Max(1, blockSize.Get4x4HighCount() >> 6);
Av1BlockSize sizeChunk = (widthChunks > 1 || heightChunks > 1) ? Av1BlockSize.Block64x64 : blockSize;
for (int chunkY = 0; chunkY < heightChunks; chunkY++)
{
for (int chunkX = 0; chunkX < widthChunks; chunkX++)
{
int rowChunk = rowIndex + (chunkY << 4);
int columnChunk = columnIndex + (chunkX << 4);
int subBlockRow = rowChunk & superBlockMask;
int subBlockColumn = columnChunk & superBlockMask;
for (int plane = 0; plane < 1 + (this.HasChroma ? 2 : 0); plane++)
{
Av1TransformSize transformSize = this.FrameInfo.CodedLossless ? Av1TransformSize.Size4x4 : this.GetSize(plane, this.TransformSize);
int stepX = transformSize.GetWidth() >> 2;
int stepY = transformSize.GetHeight() >> 2;
Av1BlockSize planeSize = this.GetPlaneResidualSize(sizeChunk, plane);
int num4x4Width = planeSize.Get4x4WideCount();
int num4x4Height = planeSize.Get4x4HighCount();
int subX = (plane > 0 && this.SubsamplingX) ? 1 : 0;
int subY = (plane > 0 && this.SubsamplingY) ? 1 : 0;
int baseX = (columnChunk >> subX) * (1 << ObuConstants.ModeInfoSizeLog2);
int baseY = (rowChunk >> subY) * (1 << ObuConstants.ModeInfoSizeLog2);
int baseXBlock = (columnIndex >> subX) * (1 << ObuConstants.ModeInfoSizeLog2);
int baseYBlock = (rowIndex >> subY) * (1 << ObuConstants.ModeInfoSizeLog2);
for (int y = 0; y < num4x4Height; y += stepY)
{
for (int x = 0; x < num4x4Width; x += stepX)
{
this.TransformBlock(plane, baseXBlock, baseYBlock, transformSize, x + ((chunkX << 4) >> subX), y + ((chunkY << 4) >> subY));
}
}
}
}
}
}
private Av1TransformSize GetSize(int plane, object transformSize) => throw new NotImplementedException();
private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) => throw new NotImplementedException();
private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y)
{
int startX = baseX + (4 * x);
int startY = baseY + (4 * y);
int subX = (plane > 0 && this.SubsamplingX) ? 1 : 0;
int subY = (plane > 0 && this.SubsamplingY) ? 1 : 0;
int columnIndex = (startX << subX) >> ObuConstants.ModeInfoSizeLog2;
int rowIndex = (startY << subY) >> ObuConstants.ModeInfoSizeLog2;
int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15;
int subBlockColumn = columnIndex & superBlockMask;
int subBlockRow = rowIndex & superBlockMask;
int stepX = transformSize.GetWidth() >> ObuConstants.ModeInfoSizeLog2;
int stepY = transformSize.GetHeight() >> ObuConstants.ModeInfoSizeLog2;
int maxX = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subX;
int maxY = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subY;
if (startX >= maxX || startY >= maxY)
{
return;
}
if ((plane == 0 && this.PaletteSizeY > 0) ||
(plane != 0 && this.PaletteSizeUv > 0))
{
this.PredictPalette(plane, startX, startY, x, y, transformSize);
}
else
{
bool isChromaFromLuma = plane > 0 && this.UvMode == Av1PredictionMode.UvChromaFromLuma;
Av1PredictionMode mode;
if (plane == 0)
{
mode = this.YMode;
}
else
{
mode = isChromaFromLuma ? Av1PredictionMode.DC : this.UvMode;
}
int log2Width = transformSize.GetWidthLog2();
int log2Height = transformSize.GetHeightLog2();
bool leftAvailable = (x > 0) || plane == 0 ? this.availableLeft : this.availableLeftForChroma;
bool upAvailable = (y > 0) || plane == 0 ? this.availableUp : this.availableUpForChroma;
bool haveAboveRight = this.blockDecoded[plane][(subBlockRow >> subY) - 1][(subBlockColumn >> subX) + stepX];
bool haveBelowLeft = this.blockDecoded[plane][(subBlockRow >> subY) + stepY][(subBlockColumn >> subX) - 1];
this.PredictIntra(plane, startX, startY, leftAvailable, upAvailable, haveAboveRight, haveBelowLeft, mode, log2Width, log2Height);
if (isChromaFromLuma)
{
this.PredictChromaFromLuma(plane, startX, startY, transformSize);
}
}
if (plane == 0)
{
this.MaxLumaWidth = startX + (stepX * 4);
this.MaxLumaHeight = startY + (stepY * 4);
}
if (!this.skip)
{
int eob = this.Coefficients(plane, startX, startY, transformSize);
if (eob > 0)
{
this.Reconstruct(plane, startX, startY, transformSize);
}
}
for (int i = 0; i < stepY; i++)
{
for (int j = 0; j < stepX; j++)
{
// Ignore loop filter.
this.blockDecoded[plane][(subBlockRow >> subY) + i][(subBlockColumn >> subX) + j] = true;
}
}
}
private void Reconstruct(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException();
private int Coefficients(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException();
private void PredictChromaFromLuma(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException();
private void PredictIntra(int plane, int startX, int startY, bool leftAvailable, bool upAvailable, bool haveAboveRight, bool haveBelowLeft, Av1PredictionMode mode, int log2Width, int log2Height) => throw new NotImplementedException();
private void PredictPalette(int plane, int startX, int startY, int x, int y, Av1TransformSize transformSize) => throw new NotImplementedException();
private void ComputePrediction()
{
// Not applicable for INTRA frames.
}
private void ResetBlockContext(int rowIndex, int columnIndex, int block4x4Width, int block4x4Height)
{
for (int plane = 0; plane < 1 + (this.HasChroma ? 2 : 0); plane++)
{
int subX = (plane > 0 && this.SubsamplingX) ? 1 : 0;
int subY = (plane > 0 && this.SubsamplingY) ? 1 : 0;
for (int i = columnIndex >> subX; i < ((columnIndex + block4x4Width) >> subX); i++)
{
this.aboveLevelContext[plane][i] = 0;
this.aboveDcContext[plane][i] = 0;
}
for (int i = rowIndex >> subY; i < ((rowIndex + block4x4Height) >> subY); i++)
{
this.leftLevelContext[plane][i] = 0;
this.leftDcContext[plane][i] = 0;
}
}
}
private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
{
int block4x4Width = blockSize.Get4x4WideCount();
int block4x4Height = blockSize.Get4x4HighCount();
// First condition in spec is for INTER frames, implemented only the INTRA condition.
this.ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize);
/*for (int row = rowIndex; row < rowIndex + block4x4Height; row++)
{
for (int column = columnIndex; column < columnIndex + block4x4Width; column++)
{
this.InterTransformSizes[row][column] = this.TransformSize;
}
}*/
}
private void ReadPaletteTokens(ref Av1SymbolDecoder reader)
{
if (this.PaletteSizeY != 0)
{
// Todo: Implement.
throw new NotImplementedException();
}
if (this.PaletteSizeUv != 0)
{
// Todo: Implement.
throw new NotImplementedException();
}
}
private void ReadModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
=> this.ReadIntraFrameModeInfo(ref reader, rowIndex, columnIndex, blockSize);
private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
{
this.skip = false;
if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip)
{
this.ReadIntraSegmentId(ref reader);
}
this.skipMode = false;
this.ReadSkip(ref reader);
if (!this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip)
{
this.IntraSegmentId(ref reader, rowIndex, columnIndex);
}
this.ReadCdef(ref reader, rowIndex, columnIndex, blockSize);
this.ReadDeltaQuantizerIndex(ref reader, blockSize);
this.ReadDeltaLoopFilter(ref reader, blockSize);
this.readDeltas = false;
this.ReferenceFrame[0] = -1; // IntraFrame;
this.ReferenceFrame[1] = -1; // None;
bool useIntraBlockCopy = false;
if (this.FrameInfo.AllowIntraBlockCopy)
{
useIntraBlockCopy = reader.ReadUseIntraBlockCopy();
}
if (useIntraBlockCopy)
{
// TODO: Implement
}
else
{
// this.IsInter = false;
this.YMode = reader.ReadIntraFrameYMode(blockSize);
this.IntraAngleInfoY(ref reader, blockSize);
if (this.HasChroma)
{
this.UvMode = reader.ReadUvMode(blockSize, this.IsChromaForLumaAllowed);
if (this.UvMode == Av1PredictionMode.UvChromaFromLuma)
{
this.ReadChromaFromLumaAlphas(ref reader);
}
this.IntraAngleInfoUv(ref reader, blockSize);
}
this.PaletteSizeY = 0;
this.PaletteSizeUv = 0;
if (this.SequenceHeader.ModeInfoSize >= (int)Av1BlockSize.Block8x8 &&
((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4WideCount() <= 64 &&
((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4HighCount() <= 64 &&
this.FrameInfo.AllowScreenContentTools)
{
this.PaletteModeInfo(ref reader);
}
this.FilterIntraModeInfo(ref reader, blockSize);
}
}
private void ReadIntraSegmentId(ref Av1SymbolDecoder reader) => throw new NotImplementedException();
private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
{
bool useFilterIntra = false;
if (this.SequenceHeader.EnableFilterIntra &&
this.YMode == Av1PredictionMode.DC && this.PaletteSizeY == 0 &&
Math.Max(blockSize.GetWidth(), blockSize.GetHeight()) <= 32)
{
useFilterIntra = reader.ReadUseFilterUltra();
if (useFilterIntra)
{
this.FilterUltraMode = reader.ReadFilterUltraMode();
}
}
}
private void PaletteModeInfo(ref Av1SymbolDecoder reader) =>
// TODO: Implement.
throw new NotImplementedException();
private void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader) =>
// TODO: Implement.
throw new NotImplementedException();
private void IntraAngleInfoY(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
{
this.AngleDeltaY = 0;
if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.YMode))
{
int angleDeltaY = reader.ReadAngleDelta(this.YMode);
this.AngleDeltaY = angleDeltaY - ObuConstants.MaxAngleDelta;
}
}
private void IntraAngleInfoUv(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
{
this.AngleDeltaUv = 0;
if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.UvMode))
{
int angleDeltaUv = reader.ReadAngleDelta(this.UvMode);
this.AngleDeltaUv = angleDeltaUv - ObuConstants.MaxAngleDelta;
}
}
private static bool IsDirectionalMode(Av1PredictionMode mode)
=> mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees;
private void IntraSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex)
{
if (this.FrameInfo.SegmentationParameters.Enabled)
{
this.ReadSegmentId(ref reader, rowIndex, columnIndex);
}
else
{
this.segmentId = 0;
}
this.Lossless = this.FrameInfo.LosslessArray[this.segmentId];
}
private void ReadSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex)
{
int pred;
int prevUL = -1;
int prevU = -1;
int prevL = -1;
if (this.availableUp && this.availableLeft)
{
prevUL = this.SegmentIds[rowIndex - 1][columnIndex - 1];
}
if (this.availableUp)
{
prevU = this.SegmentIds[rowIndex - 1][columnIndex];
}
if (this.availableLeft)
{
prevU = this.SegmentIds[rowIndex][columnIndex - 1];
}
if (prevU == -1)
{
pred = (prevL == -1) ? 0 : prevL;
}
else if (prevL == -1)
{
pred = prevU;
}
else
{
pred = (prevU == prevUL) ? prevU : prevL;
}
if (this.skip)
{
this.segmentId = 0;
}
else
{
int lastActiveSegmentId = this.FrameInfo.SegmentationParameters.LastActiveSegmentId;
this.segmentId = NegativeDeinterleave(reader.ReadSegmentId(-1), pred, lastActiveSegmentId + 1);
}
}
private static int NegativeDeinterleave(int diff, int reference, int max)
{
if (reference == 0)
{
return diff;
}
if (reference >= (max - 1))
{
return max - diff - 1;
}
if (2 * reference < max)
{
if (diff <= 2 * reference)
{
if ((diff & 1) > 0)
{
return reference + ((diff + 1) >> 1);
}
else
{
return reference - (diff >> 1);
}
}
return diff;
}
else
{
if (diff <= 2 * (max - reference - 1))
{
if ((diff & 1) > 0)
{
return reference + ((diff + 1) >> 1);
}
else
{
return reference - (diff >> 1);
}
}
return max - (diff + 1);
}
}
private void ReadCdef(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize)
{
if (this.skip || this.FrameInfo.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameInfo.AllowIntraBlockCopy)
{
return;
}
int cdefSize4 = Av1BlockSize.Block64x64.Get4x4WideCount();
int cdefMask4 = ~(cdefSize4 - 1);
int r = rowIndex & cdefMask4;
int c = columnIndex & cdefMask4;
if (this.cdefIndex[r][c] == -1)
{
this.cdefIndex[r][c] = reader.ReadLiteral(this.FrameInfo.CdefParameters.BitCount);
int w4 = blockSize.Get4x4WideCount();
int h4 = blockSize.Get4x4HighCount();
for (int i = r; i < r + h4; i += cdefSize4)
{
for (int j = c; j < c + w4; j += cdefSize4)
{
this.cdefIndex[i][j] = this.cdefIndex[r][c];
}
}
}
}
private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
{
Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
if (blockSize == superBlockSize && this.skip)
{
return;
}
if (this.readDeltas && this.FrameInfo.DeltaLoopFilterParameters.IsPresent)
{
int frameLoopFilterCount = 1;
if (this.FrameInfo.DeltaLoopFilterParameters.Multi)
{
frameLoopFilterCount = (this.SequenceHeader.ColorConfig.ChannelCount > 1) ? ObuConstants.FrameLoopFilterCount : ObuConstants.FrameLoopFilterCount - 2;
}
for (int i = 0; i < frameLoopFilterCount; i++)
{
int deltaLoopFilterAbsolute = reader.ReadDeltaLoopFilterAbsolute();
if (deltaLoopFilterAbsolute == ObuConstants.DeltaLoopFilterSmall)
{
int deltaLoopFilterRemainingBits = reader.ReadLiteral(3) + 1;
int deltaLoopFilterAbsoluteBitCount = reader.ReadLiteral(deltaLoopFilterRemainingBits);
deltaLoopFilterAbsolute = deltaLoopFilterAbsoluteBitCount + (1 << deltaLoopFilterRemainingBits) + 1;
}
if (deltaLoopFilterAbsolute != 0)
{
bool deltaLoopFilterSign = reader.ReadLiteral(1) > 0;
int reducedDeltaLoopFilterLevel = deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute;
this.deltaLoopFilter[i] = Av1Math.Clip3(-ObuConstants.MaxLoopFilter, ObuConstants.MaxLoopFilter, this.deltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << this.deltaLoopFilterResolution));
}
}
}
}
private void ReadSkip(ref Av1SymbolDecoder reader)
{
if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip && this.FrameInfo.SegmentationParameters.IsFeatureActive(ObuSegmentationFeature.LevelSkip))
{
this.skip = true;
}
else
{
this.skip = reader.ReadSkip(-1);
}
}
private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1BlockSize blockSize)
{
Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
if (blockSize == superBlockSize && this.skip)
{
return;
}
if (this.readDeltas)
{
int deltaQuantizerAbsolute = reader.ReadDeltaQuantizerAbsolute();
if (deltaQuantizerAbsolute == ObuConstants.DeltaQuantizerSmall)
{
int deltaQuantizerRemainingBits = reader.ReadLiteral(3) + 1;
int deltaQuantizerAbsoluteBitCount = reader.ReadLiteral(deltaQuantizerRemainingBits);
deltaQuantizerAbsolute = deltaQuantizerRemainingBits + (1 << deltaQuantizerRemainingBits) + 1;
}
if (deltaQuantizerAbsolute != 0)
{
bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0;
int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute;
this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << this.deltaQuantizerResolution));
}
}
}
private bool IsInside(int rowIndex, int columnIndex) =>
columnIndex >= this.TileInfo.TileColumnCount &&
columnIndex < this.TileInfo.TileColumnCount &&
rowIndex >= this.TileInfo.TileRowCount &&
rowIndex < this.TileInfo.TileRowCount;
private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blockMode, bool subSamplingX, bool subSamplingY)
{
int block4x4Width = blockMode.BlockSize.Get4x4WideCount();
int block4x4Height = blockMode.BlockSize.Get4x4HighCount();
bool xPos = (columnIndex & 0x1) > 0 || (block4x4Width & 0x1) > 0 || !subSamplingX;
bool yPos = (rowIndex & 0x1) > 0 || (block4x4Height & 0x1) > 0 || !subSamplingY;
return xPos && yPos;
}
private int GetPartitionContext(int rowIndex, int columnIndex, Av1BlockSize blockSize)
{
// Maximum partition point is 8x8. Offset the log value occordingly.
int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2();
int aboveCtx = this.aboveContext.PartitionWidth + columnIndex - this.TileInfo.TileColumnStartModeInfo[columnIndex];
int leftCtx = this.leftContext.PartitionHeight + rowIndex - this.TileInfo.TileRowStartModeInfo[rowIndex];
int above = (aboveCtx >> blockSizeLog) & 0x1;
int left = (leftCtx >> blockSizeLog) & 0x1;
return ((left * 2) + above) + (blockSizeLog * PartitionProbabilitySet);
}
}

22
src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs

@ -5,55 +5,57 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal enum Av1PartitionType
{
// See section 6.10.4 of Avi Spcification
/// <summary>
/// Not partitioned any further.
/// </summary>
None,
None = 0,
/// <summary>
/// Horizontally split in 2 partitions.
/// </summary>
Horizontal,
Horizontal = 1,
/// <summary>
/// Vertically split in 2 partitions.
/// </summary>
Vertical,
Vertical = 2,
/// <summary>
/// 4 equally sized partitions.
/// </summary>
Split,
Split = 3,
/// <summary>
/// Horizontal split and the top partition is split again.
/// </summary>
HorizontalA,
HorizontalA = 4,
/// <summary>
/// Horizontal split and the bottom partition is split again.
/// </summary>
HorizontalB,
HorizontalB = 5,
/// <summary>
/// Vertical split and the left partition is split again.
/// </summary>
VerticalA,
VerticalA = 6,
/// <summary>
/// Vertical split and the right partitino is split again.
/// </summary>
VerticalB,
VerticalB = 7,
/// <summary>
/// 4:1 horizontal partition.
/// </summary>
Horizontal4,
Horizontal4 = 8,
/// <summary>
/// 4:1 vertical partition.
/// </summary>
Vertical4,
Vertical4 = 9,
/// <summary>
/// Invalid value.

104
src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs

@ -0,0 +1,104 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal static class Av1PartitionTypeExtensions
{
private static readonly Av1BlockSize[][] PartitionSubSize = [
[
Av1BlockSize.Block4x4,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x8,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x16,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x32,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x64,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x128,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
], [
Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x4,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x8,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x16,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x32,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
], [
Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x8,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x16,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x32,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x64,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x128,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
], [
Av1BlockSize.Block4x4,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x4,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x8,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x16,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x32,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x64,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
], [
Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x4,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x8,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x16,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x32,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
], [
Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x4,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x8,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x16,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x32,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
], [
Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x8,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x16,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x32,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x64,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x128,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
], [
Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x8,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x16,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x32,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x64,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x128,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid
], [
Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x4,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x8,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x16,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
], [
Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x16,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x32,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x64,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid,
]
];
public static Av1BlockSize GetBlockSubSize(this Av1PartitionType partition, Av1BlockSize blockSize)
=> PartitionSubSize[(int)partition][(int)blockSize];
}

5
src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs

@ -43,8 +43,11 @@ internal interface IAv1TileDecoder
/// <summary>
/// Decode a single tile.
/// </summary>
/// <param name="tileData">
/// The bytes of encoded data in the bitstream dedicated to this tile.
/// </param>
/// <param name="tileNum">The index of the tile that is to be decoded.</param>
void DecodeTile(int tileNum);
void DecodeTile(Span<byte> tileData, int tileNum);
/// <summary>
/// Finshed decoding all tiles of a frame.

29
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal static class ObuConstants
@ -84,4 +86,31 @@ internal static class ObuConstants
/// Maximum size of a loop restoration tile.
/// </summary>
public const int RestorationMaxTileSize = 256;
/// <summary>
/// Number of Wiener coefficients to read.
/// </summary>
public const int WienerCoefficientCount = 3;
public const int FrameLoopFilterCount = 4;
/// <summary>
/// Value indicating alternative encoding of quantizer index delta values.
/// </summary>
public const int DeltaQuantizerSmall = 3;
/// <summary>
/// Value indicating alternative encoding of loop filter delta values.
/// </summary>
public const int DeltaLoopFilterSmall = 3;
/// <summary>
/// Maximum value used for loop filtering.
/// </summary>
public const int MaxLoopFilter = 63;
/// <summary>
/// Maximum magnitude of AngleDeltaY and AngleDeltaUV.
/// </summary>
public const int MaxAngleDelta = 3;
}

18
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs

@ -899,7 +899,7 @@ internal class ObuReader
}
private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature)
=> segmentationParameters.SegmentationEnabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature];
=> segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature];
private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex)
{
@ -980,18 +980,16 @@ internal class ObuReader
for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++)
{
int tileRow = tileNum / tileInfo.TileColumnCount;
int tileColumn = tileNum % tileInfo.TileColumnCount;
bool isLastTile = tileNum == tileGroupEnd;
int tileSize = header.PayloadSize;
int tileDataSize = header.PayloadSize;
if (!isLastTile)
{
tileSize = (int)reader.ReadLittleEndian(tileInfo.TileSizeBytes) + 1;
header.PayloadSize -= tileSize + tileInfo.TileSizeBytes;
tileDataSize = (int)reader.ReadLittleEndian(tileInfo.TileSizeBytes) + 1;
header.PayloadSize -= tileDataSize + tileInfo.TileSizeBytes;
}
// TODO: Pass more info to the decoder.
decoder.DecodeTile(tileNum);
Span<byte> tileData = reader.GetSymbolReader(tileDataSize);
decoder.DecodeTile(tileData, tileNum);
}
if (tileGroupEnd != tileCount - 1)
@ -1101,8 +1099,8 @@ internal class ObuReader
private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount)
{
frameInfo.SegmentationParameters.SegmentationEnabled = reader.ReadBoolean();
Guard.IsFalse(frameInfo.SegmentationParameters.SegmentationEnabled, nameof(frameInfo.SegmentationParameters.SegmentationEnabled), "Segmentation not supported yet.");
frameInfo.SegmentationParameters.Enabled = reader.ReadBoolean();
Guard.IsFalse(frameInfo.SegmentationParameters.Enabled, nameof(frameInfo.SegmentationParameters.Enabled), "Segmentation not supported yet.");
// TODO: Parse more stuff.
}

10
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs

@ -0,0 +1,10 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal enum ObuSegmentationFeature
{
None = 0,
LevelSkip,
}

8
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs

@ -9,7 +9,13 @@ internal class ObuSegmentationParameters
public bool[,] FeatureEnabled { get; internal set; } = new bool[ObuConstants.MaxSegmentCount, ObuConstants.SegmentationLevelMax];
public bool SegmentationEnabled { get; internal set; }
public bool Enabled { get; internal set; }
public int[,] FeatureData { get; internal set; } = new int[ObuConstants.MaxSegmentCount, ObuConstants.SegmentationLevelMax];
public bool SegmentIdPrecedesSkip { get; internal set; }
public int LastActiveSegmentId { get; internal set; }
internal bool IsFeatureActive(ObuSegmentationFeature feature) => false;
}

4
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs

@ -428,7 +428,7 @@ internal class ObuWriter
}
private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature)
=> segmentationParameters.SegmentationEnabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature];
=> segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature];
private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex)
{
@ -560,7 +560,7 @@ internal class ObuWriter
private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount)
{
Guard.IsFalse(frameInfo.SegmentationParameters.SegmentationEnabled, nameof(frameInfo.SegmentationParameters.SegmentationEnabled), "Segmentatino not supported yet.");
Guard.IsFalse(frameInfo.SegmentationParameters.Enabled, nameof(frameInfo.SegmentationParameters.Enabled), "Segmentatino not supported yet.");
writer.WriteBoolean(false);
}

1
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs

@ -19,6 +19,7 @@ internal enum Av1PredictionMode
SmoothVertical,
SmoothHorizontal,
Paeth,
UvChromaFromLuma,
IntraModeStart = DC,
IntraModeEnd = Paeth + 1,
IntraModes = Paeth,

122
src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs

@ -5,23 +5,125 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol;
internal static class Av1DefaultDistributions
{
public static uint[] YMode => [Av1SymbolReader.CdfProbabilityTop, 0];
public static Av1Distribution[] FrameYMode =>
[
new(22801, 23489, 24293, 24756, 25601, 26123, 26606, 27418, 27945, 29228, 29685, 30349),
new(18673, 19845, 22631, 23318, 23950, 24649, 25527, 27364, 28152, 29701, 29984, 30852),
new(19770, 20979, 23396, 23939, 24241, 24654, 25136, 27073, 27830, 29360, 29730, 30659),
new(20155, 21301, 22838, 23178, 23261, 23533, 23703, 24804, 25352, 26575, 27016, 28049)
];
public static uint[] UvModeCflNotAllowed => [Av1SymbolReader.CdfProbabilityTop, 0];
public static Av1Distribution[][] FilterYMode =>
[
[
new(15588, 17027, 19338, 20218, 20682, 21110, 21825, 23244, 24189, 28165, 29093, 30466),
new(12016, 18066, 19516, 20303, 20719, 21444, 21888, 23032, 24434, 28658, 30172, 31409),
new(10052, 10771, 22296, 22788, 23055, 23239, 24133, 25620, 26160, 29336, 29929, 31567),
new(14091, 15406, 16442, 18808, 19136, 19546, 19998, 22096, 24746, 29585, 30958, 32462),
new(12122, 13265, 15603, 16501, 18609, 20033, 22391, 25583, 26437, 30261, 31073, 32475)
], [
new(10023, 19585, 20848, 21440, 21832, 22760, 23089, 24023, 25381, 29014, 30482, 31436),
new(5983, 24099, 24560, 24886, 25066, 25795, 25913, 26423, 27610, 29905, 31276, 31794),
new(7444, 12781, 20177, 20728, 21077, 21607, 22170, 23405, 24469, 27915, 29090, 30492),
new(8537, 14689, 15432, 17087, 17408, 18172, 18408, 19825, 24649, 29153, 31096, 32210),
new(7543, 14231, 15496, 16195, 17905, 20717, 21984, 24516, 26001, 29675, 30981, 31994)
], [
new(12613, 13591, 21383, 22004, 22312, 22577, 23401, 25055, 25729, 29538, 30305, 32077),
new(9687, 13470, 18506, 19230, 19604, 20147, 20695, 22062, 23219, 27743, 29211, 30907),
new(6183, 6505, 26024, 26252, 26366, 26434, 27082, 28354, 28555, 30467, 30794, 32086),
new(10718, 11734, 14954, 17224, 17565, 17924, 18561, 21523, 23878, 28975, 30287, 32252),
new(9194, 9858, 16501, 17263, 18424, 19171, 21563, 25961, 26561, 30072, 30737, 32463)
], [
new(12602, 14399, 15488, 18381, 18778, 19315, 19724, 21419, 25060, 29696, 30917, 32409),
new(8203, 13821, 14524, 17105, 17439, 18131, 18404, 19468, 25225, 29485, 31158, 32342),
new(8451, 9731, 15004, 17643, 18012, 18425, 19070, 21538, 24605, 29118, 30078, 32018),
new(7714, 9048, 9516, 16667, 16817, 16994, 17153, 18767, 26743, 30389, 31536, 32528),
new(8843, 10280, 11496, 15317, 16652, 17943, 19108, 22718, 25769, 29953, 30983, 32485)
], [
new(12578, 13671, 15979, 16834, 19075, 20913, 22989, 25449, 26219, 30214, 31150, 32477),
new(9563, 13626, 15080, 15892, 17756, 20863, 22207, 24236, 25380, 29653, 31143, 32277),
new(8356, 8901, 17616, 18256, 19350, 20106, 22598, 25947, 26466, 29900, 30523, 32261),
new(10835, 11815, 13124, 16042, 17018, 18039, 18947, 22753, 24615, 29489, 30883, 32482),
new(7618, 8288, 9859, 10509, 15386, 18657, 22903, 28776, 29180, 31355, 31802, 32593)
]
];
public static uint[] UvModeCflAllowed => [Av1SymbolReader.CdfProbabilityTop, 0];
public static Av1Distribution[][] UvMode =>
[
[
new(22631, 24152, 25378, 25661, 25986, 26520, 27055, 27923, 28244, 30059, 30941, 31961),
new(9513, 26881, 26973, 27046, 27118, 27664, 27739, 27824, 28359, 29505, 29800, 31796),
new(9845, 9915, 28663, 28704, 28757, 28780, 29198, 29822, 29854, 30764, 31777, 32029),
new(13639, 13897, 14171, 25331, 25606, 25727, 25953, 27148, 28577, 30612, 31355, 32493),
new(9764, 9835, 9930, 9954, 25386, 27053, 27958, 28148, 28243, 31101, 31744, 32363),
new(11825, 13589, 13677, 13720, 15048, 29213, 29301, 29458, 29711, 31161, 31441, 32550),
new(14175, 14399, 16608, 16821, 17718, 17775, 28551, 30200, 30245, 31837, 32342, 32667),
new(12885, 13038, 14978, 15590, 15673, 15748, 16176, 29128, 29267, 30643, 31961, 32461),
new(12026, 13661, 13874, 15305, 15490, 15726, 15995, 16273, 28443, 30388, 30767, 32416),
new(19052, 19840, 20579, 20916, 21150, 21467, 21885, 22719, 23174, 28861, 30379, 32175),
new(18627, 19649, 20974, 21219, 21492, 21816, 22199, 23119, 23527, 27053, 31397, 32148),
new(17026, 19004, 19997, 20339, 20586, 21103, 21349, 21907, 22482, 25896, 26541, 31819),
new(12124, 13759, 14959, 14992, 15007, 15051, 15078, 15166, 15255, 15753, 16039, 16606)
], [
new(10407, 11208, 12900, 13181, 13823, 14175, 14899, 15656, 15986, 20086, 20995, 22455, 24212),
new(4532, 19780, 20057, 20215, 20428, 21071, 21199, 21451, 22099, 24228, 24693, 27032, 29472),
new(5273, 5379, 20177, 20270, 20385, 20439, 20949, 21695, 21774, 23138, 24256, 24703, 26679),
new(6740, 7167, 7662, 14152, 14536, 14785, 15034, 16741, 18371, 21520, 22206, 23389, 24182),
new(4987, 5368, 5928, 6068, 19114, 20315, 21857, 22253, 22411, 24911, 25380, 26027, 26376),
new(5370, 6889, 7247, 7393, 9498, 21114, 21402, 21753, 21981, 24780, 25386, 26517, 27176),
new(4816, 4961, 7204, 7326, 8765, 8930, 20169, 20682, 20803, 23188, 23763, 24455, 24940),
new(6608, 6740, 8529, 9049, 9257, 9356, 9735, 18827, 19059, 22336, 23204, 23964, 24793),
new(5998, 7419, 7781, 8933, 9255, 9549, 9753, 10417, 18898, 22494, 23139, 24764, 25989),
new(10660, 11298, 12550, 12957, 13322, 13624, 14040, 15004, 15534, 20714, 21789, 23443, 24861),
new(10522, 11530, 12552, 12963, 13378, 13779, 14245, 15235, 15902, 20102, 22696, 23774, 25838),
new(10099, 10691, 12639, 13049, 13386, 13665, 14125, 15163, 15636, 19676, 20474, 23519, 25208),
new(3144, 5087, 7382, 7504, 7593, 7690, 7801, 8064, 8232, 9248, 9875, 10521, 29048)
]
];
public static uint[] AngleDelta => [Av1SymbolReader.CdfProbabilityTop, 0];
public static Av1Distribution[] AngleDelta =>
[
new(2180, 5032, 7567, 22776, 26989, 30217),
new(2301, 5608, 8801, 23487, 26974, 30330),
new(3780, 11018, 13699, 19354, 23083, 31286),
new(4581, 11226, 15147, 17138, 21834, 28397),
new(1737, 10927, 14509, 19588, 22745, 28823),
new(2664, 10176, 12485, 17650, 21600, 30495),
new(2240, 11096, 15453, 20341, 22561, 28917),
new(3605, 10428, 12459, 17676, 21244, 30655)
];
public static uint[] IntraBlockCopy => [30531, 0, 0];
public static Av1Distribution IntraBlockCopy => new(30531);
public static uint[] PartitionWidth8 => [Av1SymbolReader.CdfProbabilityTop, 0];
public static Av1Distribution[] PartitionTypes =>
[
new(19132, 25510, 30392),
new(13928, 19855, 28540),
new(12522, 23679, 28629),
new(9896, 18783, 25853),
new(15597, 20929, 24571, 26706, 27664, 28821, 29601, 30571, 31902),
new(7925, 11043, 16785, 22470, 23971, 25043, 26651, 28701, 29834),
new(5414, 13269, 15111, 20488, 22360, 24500, 25537, 26336, 32117),
new(2662, 6362, 8614, 20860, 23053, 24778, 26436, 27829, 31171),
new(18462, 20920, 23124, 27647, 28227, 29049, 29519, 30178, 31544),
new(7689, 9060, 12056, 24992, 25660, 26182, 26951, 28041, 29052),
new(6015, 9009, 10062, 24544, 25409, 26545, 27071, 27526, 32047),
new(1394, 2208, 2796, 28614, 29061, 29466, 29840, 30185, 31899),
new(20137, 21547, 23078, 29566, 29837, 30261, 30524, 30892, 31724),
new(6732, 7490, 9497, 27944, 28250, 28515, 28969, 29630, 30104),
new(5945, 7663, 8348, 28683, 29117, 29749, 30064, 30298, 32238),
new(870, 1212, 1487, 31198, 31394, 31574, 31743, 31881, 32332),
new(27899, 28219, 28529, 32484, 32539, 32619, 32639),
new(6607, 6990, 8268, 32060, 32219, 32338, 32371),
new(5429, 6676, 7122, 32027, 32227, 32531, 32582),
new(711, 966, 1172, 32448, 32538, 32617, 32664)
];
public static uint[] PartitionWidth16 => [Av1SymbolReader.CdfProbabilityTop, 0];
public static Av1Distribution[] Skip => [new(31671), new(16515), new(4576)];
public static uint[] PartitionWidth32 => [Av1SymbolReader.CdfProbabilityTop, 0];
public static Av1Distribution DeltaLoopFilterAbsolute => new(28160, 32120, 32677);
public static uint[] PartitionWidth64 => [Av1SymbolReader.CdfProbabilityTop, 0];
public static Av1Distribution DeltaQuantizerAbsolute => new(28160, 32120, 32677);
public static uint[] SegmentId => [Av1SymbolReader.CdfProbabilityTop, 0];
public static Av1Distribution[] SegmentId => [new(128 * 128), new(128 * 128), new(128 * 128)];
}

114
src/ImageSharp/Formats/Heif/Av1/Symbol/Av1Distribution.cs

@ -0,0 +1,114 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol;
/// <summary>
/// Class representing the probability distribution used for symbol coding.
/// </summary>
internal class Av1Distribution
{
internal const int ProbabilityTop = 1 << ProbabilityBitCount;
internal const int ProbabilityMinimum = 4;
internal const int CdfShift = 15 - ProbabilityBitCount;
internal const int ProbabilityShift = 6;
private const int ProbabilityBitCount = 15;
private readonly uint[] probabilities;
private readonly int speed;
private int updateCount;
public Av1Distribution(uint p0)
: this([p0, 0], 1)
{
}
public Av1Distribution(uint p0, uint p1)
: this([p0, p1, 0], 1)
{
}
public Av1Distribution(uint p0, uint p1, uint p2)
: this([p0, p1, p2, 0], 2)
{
}
public Av1Distribution(uint p0, uint p1, uint p2, uint p3)
: this([p0, p1, p2, p3, 0], 2)
{
}
public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4)
: this([p0, p1, p2, p3, p4, 0], 2)
{
}
public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5)
: this([p0, p1, p2, p3, p4, p5, 0], 2)
{
}
public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6)
: this([p0, p1, p2, p3, p4, p5, p6, 0], 2)
{
}
public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8)
: this([p0, p1, p2, p3, p4, p5, p6, p7, p8, 0], 2)
{
}
public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9, uint p10, uint p11)
: this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, 0], 2)
{
}
public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9, uint p10, uint p11, uint p12)
: this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, 0], 2)
{
}
private Av1Distribution(uint[] props, int speed)
{
// this.probabilities = props;
this.probabilities = new uint[props.Length];
for (int i = 0; i < props.Length - 1; i++)
{
this.probabilities[i] = ProbabilityTop - props[i];
}
this.NumberOfSymbols = props.Length;
this.speed = speed;
}
public int NumberOfSymbols { get; }
public uint this[int index] => this.probabilities[index];
public void Update(int value)
{
int rate15 = this.updateCount > 15 ? 1 : 0;
int rate31 = this.updateCount > 31 ? 1 : 0;
int rate = 3 + rate15 + rate31 + this.speed; // + get_msb(nsymbs);
int tmp = ProbabilityTop;
// Single loop (faster)
for (int i = 0; i < this.NumberOfSymbols - 1; i++)
{
tmp = (i == value) ? 0 : tmp;
uint p = this.probabilities[i];
if (tmp < p)
{
this.probabilities[i] -= (ushort)((p - tmp) >> rate);
}
else
{
this.probabilities[i] += (ushort)((tmp - p) >> rate);
}
}
int rate32 = this.updateCount < 32 ? 1 : 0;
this.updateCount += rate32;
}
}

11
src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs

@ -0,0 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol;
internal class Av1ParseAboveContext
{
public int PartitionWidth { get; internal set; }
internal void Clear() => throw new NotImplementedException();
}

11
src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs

@ -0,0 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol;
internal class Av1ParseLeftContext
{
public int PartitionHeight { get; internal set; }
internal void Clear() => throw new NotImplementedException();
}

124
src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs

@ -1,12 +1,128 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol;
internal class Av1SymbolDecoder
internal ref struct Av1SymbolDecoder
{
private readonly uint[] tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes;
private readonly Av1Distribution[] frameYMode = Av1DefaultDistributions.FrameYMode;
private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode;
private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip;
private readonly Av1Distribution deltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute;
private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute;
private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId;
private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta;
private Av1SymbolReader reader;
public Av1SymbolDecoder(Span<byte> tileData) => this.reader = new Av1SymbolReader(tileData);
public int ReadLiteral(int bitCount)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadLiteral(bitCount);
}
public bool ReadUseIntraBlockCopy()
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.tileIntraBlockCopy) > 0;
}
public Av1PartitionType ReadPartitionSymbol(int context)
{
ref Av1SymbolReader r = ref this.reader;
return (Av1PartitionType)r.ReadSymbol(this.tilePartitionTypes[context]);
}
public bool ReadSplitOrHorizontal(Av1BlockSize blockSize, int context)
{
Av1Distribution input = this.tilePartitionTypes[context];
uint p = Av1Distribution.ProbabilityTop;
p -= GetElementProbability(input, Av1PartitionType.Horizontal);
p -= GetElementProbability(input, Av1PartitionType.Split);
p -= GetElementProbability(input, Av1PartitionType.HorizontalA);
p -= GetElementProbability(input, Av1PartitionType.HorizontalB);
p -= GetElementProbability(input, Av1PartitionType.VerticalA);
if (blockSize != Av1BlockSize.Block128x128)
{
p -= GetElementProbability(input, Av1PartitionType.Horizontal4);
}
Av1Distribution distribution = new(Av1Distribution.ProbabilityTop - p);
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(distribution) > 0;
}
public bool ReadSplitOrVertical(Av1BlockSize blockSize, int context)
{
Av1Distribution input = this.tilePartitionTypes[context];
uint p = Av1Distribution.ProbabilityTop;
p -= GetElementProbability(input, Av1PartitionType.Vertical);
p -= GetElementProbability(input, Av1PartitionType.Split);
p -= GetElementProbability(input, Av1PartitionType.HorizontalA);
p -= GetElementProbability(input, Av1PartitionType.VerticalA);
p -= GetElementProbability(input, Av1PartitionType.VerticalB);
if (blockSize != Av1BlockSize.Block128x128)
{
p -= GetElementProbability(input, Av1PartitionType.Vertical4);
}
Av1Distribution distribution = new(Av1Distribution.ProbabilityTop - p);
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(distribution) > 0;
}
public Av1PredictionMode ReadIntraFrameYMode(Av1BlockSize blockSize)
{
ref Av1SymbolReader r = ref this.reader;
return (Av1PredictionMode)r.ReadSymbol(this.frameYMode[(int)blockSize]);
}
public Av1PredictionMode ReadUvMode(Av1BlockSize blockSize, bool chromaFromLumaAllowed)
{
int chromaForLumaIndex = chromaFromLumaAllowed ? 1 : 0;
ref Av1SymbolReader r = ref this.reader;
return (Av1PredictionMode)r.ReadSymbol(this.uvMode[chromaForLumaIndex][(int)blockSize]);
}
public bool ReadSkip(int ctx)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.skip[ctx]) > 0;
}
public int ReadDeltaLoopFilterAbsolute()
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.deltaLoopFilterAbsolute);
}
public int ReadDeltaQuantizerAbsolute()
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.deltaQuantizerAbsolute);
}
public int ReadSegmentId(int ctx)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.segmentId[ctx]);
}
public int ReadAngleDelta(Av1PredictionMode mode)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.angleDelta[((int)mode) - 1]);
}
public bool ReadUseFilterUltra() => throw new NotImplementedException();
public object ReadFilterUltraMode() => throw new NotImplementedException();
public bool ReadUseIntraBlockCopySymbol(ref Av1SymbolReader reader)
=> reader.ReadSymbol(this.tileIntraBlockCopy, 2) > 0;
private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element)
=> probability[(int)element - 1] - probability[(int)element];
}

23
src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs

@ -1,12 +1,27 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol;
internal class Av1SymbolEncoder
internal class Av1SymbolEncoder : IDisposable
{
private readonly uint[] tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
private Av1SymbolWriter? writer;
public Av1SymbolEncoder(Configuration configuration, int initialSize)
=> this.writer = new(configuration, initialSize);
public void WriteUseIntraBlockCopySymbol(bool value)
=> this.writer!.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy, 2);
public IMemoryOwner<byte> Exit() => this.writer!.Exit();
public void WriteUseIntraBlockCopySymbol(Av1SymbolWriter writer, bool value)
=> writer.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy, 2);
public void Dispose()
{
this.writer?.Dispose();
this.writer = null;
}
}

64
src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs

@ -5,13 +5,6 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol;
internal ref struct Av1SymbolReader
{
internal const int CdfProbabilityTop = 1 << CdfProbabilityBitCount;
internal const int ProbabilityMinimum = 4;
internal const int CdfShift = 15 - CdfProbabilityBitCount;
internal const int ProbabilityShift = 6;
internal static readonly int[] NumberOfSymbols2Speed = [0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
private const int CdfProbabilityBitCount = 15;
private const int DecoderWindowsSize = 32;
private const int LotsOfBits = 0x4000;
@ -45,10 +38,12 @@ internal ref struct Av1SymbolReader
this.Refill();
}
public int ReadSymbol(uint[] probabilities, int numberOfSymbols)
public int ReadSymbol(Av1Distribution distribution)
{
int value = this.DecodeIntegerQ15(probabilities, numberOfSymbols);
UpdateCdf(probabilities, value, numberOfSymbols);
int value = this.DecodeIntegerQ15(distribution);
// UpdateCdf(probabilities, value, numberOfSymbols);
distribution.Update(value);
return value;
}
@ -87,8 +82,8 @@ internal ref struct Av1SymbolReader
// assert(dif >> (DecoderWindowsSize - 16) < r);
// assert(32768U <= r);
v = ((range >> 8) * (frequency >> ProbabilityShift)) >> (7 - ProbabilityShift);
v += ProbabilityMinimum;
v = ((range >> 8) * (frequency >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift);
v += Av1Distribution.ProbabilityMinimum;
vw = v << (DecoderWindowsSize - 16);
ret = true;
newRange = v;
@ -106,17 +101,13 @@ internal ref struct Av1SymbolReader
/// <summary>
/// Decodes a symbol given an inverse cumulative distribution function(CDF) table in Q15.
/// </summary>
/// <param name="probabilities">
/// <param name="distribution">
/// CDF_PROB_TOP minus the CDF, such that symbol s falls in the range
/// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]).
/// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0.
/// </param>
/// <param name="numberOfSymbols">
/// The number of symbols in the alphabet.
/// This should be at most 16.
/// </param>
/// <returns>The decoded symbol.</returns>
private int DecodeIntegerQ15(uint[] probabilities, int numberOfSymbols)
private int DecodeIntegerQ15(Av1Distribution distribution)
{
uint c;
uint u;
@ -125,20 +116,20 @@ internal ref struct Av1SymbolReader
uint dif = this.difference;
uint r = this.range;
int n = numberOfSymbols - 1;
int n = distribution.NumberOfSymbols - 1;
DebugGuard.MustBeLessThan(dif >> (DecoderWindowsSize - 16), r, nameof(r));
DebugGuard.IsTrue(probabilities[numberOfSymbols - 1] == 0, "Last value in probability array needs to be zero.");
DebugGuard.IsTrue(distribution[n] == 0, "Last value in probability array needs to be zero.");
DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r));
DebugGuard.MustBeGreaterThanOrEqualTo(7 - ProbabilityShift - CdfShift, 0, nameof(CdfShift));
DebugGuard.MustBeGreaterThanOrEqualTo(7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift, 0, nameof(Av1Distribution.CdfShift));
c = dif >> (DecoderWindowsSize - 16);
v = r;
ret = -1;
do
{
u = v;
v = ((r >> 8) * (probabilities[++ret] >> ProbabilityShift)) >> (7 - ProbabilityShift - CdfShift);
v += (uint)(ProbabilityMinimum * (n - ret));
v = ((r >> 8) * (distribution[++ret] >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift);
v += (uint)(Av1Distribution.ProbabilityMinimum * (n - ret));
}
while (c < v);
@ -213,31 +204,4 @@ internal ref struct Av1SymbolReader
this.count = cnt;
this.position = position;
}
internal static void UpdateCdf(uint[] probabilities, int value, int numberOfSymbols)
{
DebugGuard.MustBeLessThan(numberOfSymbols, 17, nameof(numberOfSymbols));
int rate15 = probabilities[numberOfSymbols] > 15 ? 1 : 0;
int rate31 = probabilities[numberOfSymbols] > 31 ? 1 : 0;
int rate = 3 + rate15 + rate31 + NumberOfSymbols2Speed[numberOfSymbols]; // + get_msb(nsymbs);
int tmp = CdfProbabilityTop;
// Single loop (faster)
for (int i = 0; i < numberOfSymbols - 1; i++)
{
tmp = (i == value) ? 0 : tmp;
uint p = probabilities[i];
if (tmp < p)
{
probabilities[i] -= (ushort)((p - tmp) >> rate);
}
else
{
probabilities[i] += (ushort)((tmp - p) >> rate);
}
}
uint rate32 = probabilities[numberOfSymbols] < 32 ? 1U : 0U;
probabilities[numberOfSymbols] += rate32;
}
}

34
src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs

@ -25,14 +25,14 @@ internal class Av1SymbolWriter : IDisposable
public void Dispose() => this.memory.Dispose();
public void WriteSymbol(int symbol, uint[] probabilities, int numberOfSymbols)
public void WriteSymbol(int symbol, Av1Distribution distribution, int numberOfSymbols)
{
DebugGuard.MustBeGreaterThanOrEqualTo(symbol, 0, nameof(symbol));
DebugGuard.MustBeLessThan(symbol, numberOfSymbols, nameof(symbol));
DebugGuard.IsTrue(probabilities[numberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero.");
DebugGuard.IsTrue(distribution[numberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero.");
this.EncodeIntegerQ15(symbol, probabilities, numberOfSymbols);
Av1SymbolReader.UpdateCdf(probabilities, symbol, numberOfSymbols);
this.EncodeIntegerQ15(symbol, distribution, numberOfSymbols);
distribution.Update(symbol);
}
public void WriteLiteral(uint value, int bitCount)
@ -104,8 +104,8 @@ internal class Av1SymbolWriter : IDisposable
l = this.low;
r = this.rng;
DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r));
v = ((r >> 8) * (frequency >> Av1SymbolReader.ProbabilityShift)) >> (7 - Av1SymbolReader.ProbabilityShift);
v += Av1SymbolReader.ProbabilityMinimum;
v = ((r >> 8) * (frequency >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift);
v += Av1Distribution.ProbabilityMinimum;
if (val)
{
l += r - v;
@ -123,7 +123,7 @@ internal class Av1SymbolWriter : IDisposable
/// Encodes a symbol given an inverse cumulative distribution function(CDF) table in Q15.
/// </summary>
/// <param name="symbol">The value to encode.</param>
/// <param name="probabilities">
/// <param name="distribution">
/// CDF_PROB_TOP minus the CDF, such that symbol s falls in the range
/// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]).
/// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0.
@ -132,12 +132,12 @@ internal class Av1SymbolWriter : IDisposable
/// The number of symbols in the alphabet.
/// This should be at most 16.
/// </param>
private void EncodeIntegerQ15(int symbol, uint[] probabilities, int numberOfSymbols)
=> this.EncodeIntegerQ15(symbol > 0 ? probabilities[symbol - 1] : Av1SymbolReader.CdfProbabilityTop, probabilities[symbol], symbol, numberOfSymbols);
private void EncodeIntegerQ15(int symbol, Av1Distribution distribution, int numberOfSymbols)
=> this.EncodeIntegerQ15(symbol > 0 ? distribution[symbol - 1] : Av1Distribution.ProbabilityTop, distribution[symbol], symbol, numberOfSymbols);
private void EncodeIntegerQ15(uint lowFrequency, uint highFrequency, int symbol, int numberOfSymbols)
{
const int totalShift = 7 - Av1SymbolReader.ProbabilityShift - Av1SymbolReader.CdfShift;
const int totalShift = 7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift;
uint l = this.low;
uint r = this.rng;
DebugGuard.MustBeLessThanOrEqualTo(32768U, r, nameof(r));
@ -145,21 +145,21 @@ internal class Av1SymbolWriter : IDisposable
DebugGuard.MustBeLessThanOrEqualTo(lowFrequency, 32768U, nameof(lowFrequency));
DebugGuard.MustBeGreaterThanOrEqualTo(totalShift, 0, nameof(totalShift));
int n = numberOfSymbols - 1;
if (lowFrequency < Av1SymbolReader.CdfProbabilityTop)
if (lowFrequency < Av1Distribution.ProbabilityTop)
{
uint u;
uint v;
u = (uint)((((r >> 8) * (lowFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) +
(Av1SymbolReader.ProbabilityMinimum * (n - (symbol - 1))));
v = (uint)((((r >> 8) * (highFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) +
(Av1SymbolReader.ProbabilityMinimum * (n - symbol)));
u = (uint)((((r >> 8) * (lowFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) +
(Av1Distribution.ProbabilityMinimum * (n - (symbol - 1))));
v = (uint)((((r >> 8) * (highFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) +
(Av1Distribution.ProbabilityMinimum * (n - symbol)));
l += r - u;
r = u - v;
}
else
{
r -= (uint)((((r >> 8) * (highFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) +
(Av1SymbolReader.ProbabilityMinimum * (n - symbol)));
r -= (uint)((((r >> 8) * (highFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) +
(Av1Distribution.ProbabilityMinimum * (n - symbol)));
}
this.Normalize(l, r);

8
src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs

@ -13,4 +13,12 @@ internal static class Av1TransformSizeExtensions
int pels = Size2d[(int)size];
return (pels > 1024) ? 2 : (pels > 256) ? 1 : 0;
}
public static int GetWidth(this Av1TransformSize size) => (int)size;
public static int GetHeight(this Av1TransformSize size) => (int)size;
public static int GetWidthLog2(this Av1TransformSize size) => (int)size;
public static int GetHeightLog2(this Av1TransformSize size) => (int)size;
}

2
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs

@ -20,7 +20,7 @@ internal class Av1TileDecoderStub : IAv1TileDecoder
public ObuTileInfo TileInfo { get; } = new ObuTileInfo();
public void DecodeTile(int tileNum)
public void DecodeTile(Span<byte> tileData, int tileNum)
{
}

38
tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Buffers;
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol;
using SixLabors.ImageSharp.Memory;
@ -185,30 +186,53 @@ public class SymbolTest
Assert.Equal(expectedValues, values);
}
[Theory]
[InlineData(0, 255, 255, 255, 158)]
// [InlineData(1, 255, 255, 255, 158)]
// [InlineData(4, 255, 207, 254, 18)]
public void ReadPartitionTypeSymbols(int ctx, byte b0, byte b1, byte b2, byte b3)
{
// Assign
byte[] values = [b0, b1, b2, b3, 0];
Av1SymbolDecoder decoder = new(values);
Av1PartitionType[] expected = [
Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None,
Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, Av1PartitionType.None ];
Av1PartitionType[] actuals = new Av1PartitionType[expected.Length];
// Act
for (int i = 0; i < expected.Length; i++)
{
actuals[i] = decoder.ReadPartitionSymbol(ctx);
}
// Assert
Assert.Equal(expected, actuals);
}
[Fact]
public void RoundTripUseIntraBlockCopy()
{
// Assign
bool[] values = [true, true, false, true, false, false, false];
MemoryStream output = new(100);
Configuration configuration = Configuration.Default;
using Av1SymbolWriter writer = new(configuration, 100 / 8);
Av1SymbolEncoder encoder = new();
Av1SymbolDecoder decoder = new();
Av1SymbolEncoder encoder = new(configuration, 100 / 8);
bool[] actuals = new bool[values.Length];
// Act
foreach (bool value in values)
{
encoder.WriteUseIntraBlockCopySymbol(writer, value);
encoder.WriteUseIntraBlockCopySymbol(value);
}
using IMemoryOwner<byte> encoded = writer.Exit();
using IMemoryOwner<byte> encoded = encoder.Exit();
Av1SymbolDecoder decoder = new(encoded.GetSpan());
Av1SymbolReader reader = new(encoded.GetSpan());
for (int i = 0; i < values.Length; i++)
{
actuals[i] = decoder.ReadUseIntraBlockCopySymbol(ref reader);
actuals[i] = decoder.ReadUseIntraBlockCopy();
}
// Assert

Loading…
Cancel
Save