mirror of https://github.com/SixLabors/ImageSharp
5 changed files with 912 additions and 797 deletions
@ -0,0 +1,862 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; |
||||
|
|
||||
|
internal class Av1TileDecoder : 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 int paletteSizeY; |
||||
|
private int paletteSizeUv; |
||||
|
private object[][] aboveLevelContext = []; |
||||
|
private object[][] aboveDcContext = []; |
||||
|
private object[][] leftLevelContext = []; |
||||
|
private object[][] leftDcContext = []; |
||||
|
private Av1TransformSize transformSize = Av1TransformSize.Size4x4; |
||||
|
private object filterUltraMode = -1; |
||||
|
private int angleDeltaY; |
||||
|
private int angleDeltaUv; |
||||
|
private bool lossless; |
||||
|
private int[][] segmentIds = []; |
||||
|
private int maxLumaWidth; |
||||
|
private int maxLumaHeight; |
||||
|
private int segmentId; |
||||
|
private int[][] cdefIndex = []; |
||||
|
private int deltaLoopFilterResolution = -1; |
||||
|
private int deltaQuantizerResolution = -1; |
||||
|
|
||||
|
public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo) |
||||
|
{ |
||||
|
this.FrameInfo = frameInfo; |
||||
|
this.SequenceHeader = sequenceHeader; |
||||
|
this.TileInfo = tileInfo; |
||||
|
} |
||||
|
|
||||
|
public bool SequenceHeaderDone { get; set; } |
||||
|
|
||||
|
public bool ShowExistingFrame { get; set; } |
||||
|
|
||||
|
public bool SeenFrameHeader { get; set; } |
||||
|
|
||||
|
public ObuFrameHeader FrameInfo { get; } |
||||
|
|
||||
|
public ObuSequenceHeader SequenceHeader { get; } |
||||
|
|
||||
|
public ObuTileInfo TileInfo { get; } |
||||
|
|
||||
|
public void DecodeTile(Span<byte> tileData, int tileNum) |
||||
|
{ |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int lastIndex = this.blockDecoded[plane][(superBlock4x4Size >> subY) - 1].Length - 1; |
||||
|
this.blockDecoded[plane][(superBlock4x4Size >> subY) - 1][lastIndex] = 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) |
||||
|
{ |
||||
|
// TODO: Implement
|
||||
|
} |
||||
|
|
||||
|
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.ReadPartitionType(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); |
||||
|
ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); |
||||
|
if (this.skip) |
||||
|
{ |
||||
|
this.ResetBlockContext(rowIndex, columnIndex, blockSize); |
||||
|
} |
||||
|
|
||||
|
// 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]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ComputePrediction(); |
||||
|
this.Residual(rowIndex, columnIndex, blockSize); |
||||
|
} |
||||
|
|
||||
|
private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize) |
||||
|
{ |
||||
|
bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; |
||||
|
bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; |
||||
|
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(rowIndex, columnIndex, blockSize) ? 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 && subsamplingX ? 1 : 0; |
||||
|
int subY = plane > 0 && 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 bool HasChroma(int rowIndex, int columnIndex, Av1BlockSize blockSize) |
||||
|
{ |
||||
|
int bw = blockSize.Get4x4WideCount(); |
||||
|
int bh = blockSize.Get4x4HighCount(); |
||||
|
bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; |
||||
|
bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; |
||||
|
bool hasChroma = ((rowIndex & 0x01) != 0 || (bh & 0x01) == 0 || !subY) && |
||||
|
((columnIndex & 0x01) != 0 || (bw & 0x01) == 0 || !subX); |
||||
|
return hasChroma; |
||||
|
} |
||||
|
|
||||
|
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; |
||||
|
bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; |
||||
|
bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; |
||||
|
int subX = plane > 0 && subsamplingX ? 1 : 0; |
||||
|
int subY = plane > 0 && 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 static void ComputePrediction() |
||||
|
{ |
||||
|
// Not applicable for INTRA frames.
|
||||
|
} |
||||
|
|
||||
|
private void ResetBlockContext(int rowIndex, int columnIndex, Av1BlockSize blockSize) |
||||
|
{ |
||||
|
int block4x4Width = blockSize.Get4x4WideCount(); |
||||
|
int block4x4Height = blockSize.Get4x4HighCount(); |
||||
|
bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; |
||||
|
bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; |
||||
|
int endPlane = this.HasChroma(rowIndex, columnIndex, blockSize) ? 3 : 1; |
||||
|
for (int plane = 0; plane < endPlane; plane++) |
||||
|
{ |
||||
|
int subX = plane > 0 && subsamplingX ? 1 : 0; |
||||
|
int subY = plane > 0 && subsamplingY ? 1 : 0; |
||||
|
for (int i = columnIndex >> subX; i < (columnIndex + block4x4Width) >> subX; i++) |
||||
|
{ |
||||
|
this.aboveLevelContext[plane][i] = 0; |
||||
|
this.aboveDcContext[plane][i] = 0; |
||||
|
} |
||||
|
|
||||
|
for (int i = rowIndex >> subY; i < (rowIndex + block4x4Height) >> subY; i++) |
||||
|
{ |
||||
|
this.leftLevelContext[plane][i] = 0; |
||||
|
this.leftDcContext[plane][i] = 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static 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.
|
||||
|
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) |
||||
|
{ |
||||
|
reader.ReadLiteral(-1); |
||||
|
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(rowIndex, columnIndex, blockSize)) |
||||
|
{ |
||||
|
this.uvMode = reader.ReadUvMode(blockSize, this.IsChromaForLumaAllowed(blockSize)); |
||||
|
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 bool IsChromaForLumaAllowed(Av1BlockSize blockSize) |
||||
|
{ |
||||
|
if (this.lossless) |
||||
|
{ |
||||
|
// In lossless, CfL is available when the partition size is equal to the
|
||||
|
// transform size.
|
||||
|
bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; |
||||
|
bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; |
||||
|
Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY); |
||||
|
return planeBlockSize == Av1BlockSize.Block4x4; |
||||
|
} |
||||
|
|
||||
|
// Spec: CfL is available to luma partitions lesser than or equal to 32x32
|
||||
|
return blockSize.Get4x4WideCount() <= 32 && blockSize.Get4x4HighCount() <= 32; |
||||
|
} |
||||
|
|
||||
|
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(-1, 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); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue