Browse Source

Fix AV1 intra reference extents and partition context

pull/2633/head
James Jackson-South 4 weeks ago
parent
commit
578ec34d9c
  1. 163
      HEIF_IMPLEMENTATION_PLAN.md
  2. 75
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaModeDecision.cs
  3. 133
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs
  4. 92
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs
  5. 89
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs

163
HEIF_IMPLEMENTATION_PLAN.md

File diff suppressed because one or more lines are too long

75
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ChromaModeDecision.cs

@ -116,6 +116,7 @@ internal static partial class Av1IntraSuperblockEncoder
bool rightAvailable = modeInfoColumn + (transformSize.Get4x4WideCount() << subsamplingX) < macroBlock.Tile.ModeInfoColumnEnd;
bool bottomAvailable = modeInfoRow + (transformSize.Get4x4HighCount() << subsamplingY) < macroBlock.Tile.ModeInfoRowEnd;
Av1PartitionType partitionType = modeInfo.Block.PartitionType;
bool hasTopRight = Av1IntraReferenceAvailability.HasTopRight(
this.picture.Sequence.SequenceHeader.SuperblockSize,
blockSize,
@ -123,7 +124,7 @@ internal static partial class Av1IntraSuperblockEncoder
modeInfoColumn,
hasAbove,
rightAvailable,
Av1PartitionType.None,
partitionType,
transformSize,
0,
0,
@ -137,7 +138,7 @@ internal static partial class Av1IntraSuperblockEncoder
modeInfoColumn,
bottomAvailable,
hasLeft,
Av1PartitionType.None,
partitionType,
transformSize,
0,
0,
@ -152,7 +153,7 @@ internal static partial class Av1IntraSuperblockEncoder
Span<TSample> blueLeftStorage = workspace.GetReferenceSamples(1);
Span<TSample> redAboveStorage = workspace.GetReferenceSamples(2);
Span<TSample> redLeftStorage = workspace.GetReferenceSamples(3);
this.PrepareReferenceSamples(
PrepareReferenceSamples(
blueReconstruction,
chromaOrigin,
width,
@ -161,10 +162,11 @@ internal static partial class Av1IntraSuperblockEncoder
hasAbove,
hasTopRight,
hasBottomLeft,
this.bitDepth,
blueAboveStorage,
blueLeftStorage);
this.PrepareReferenceSamples(
PrepareReferenceSamples(
redReconstruction,
chromaOrigin,
width,
@ -173,13 +175,14 @@ internal static partial class Av1IntraSuperblockEncoder
hasAbove,
hasTopRight,
hasBottomLeft,
this.bitDepth,
redAboveStorage,
redLeftStorage);
ReadOnlySpan<TSample> blueAbove = blueAboveStorage.Slice(1, width * 2);
ReadOnlySpan<TSample> blueLeft = blueLeftStorage.Slice(1, height * 2);
ReadOnlySpan<TSample> redAbove = redAboveStorage.Slice(1, width * 2);
ReadOnlySpan<TSample> redLeft = redLeftStorage.Slice(1, height * 2);
ReadOnlySpan<TSample> blueAbove = blueAboveStorage.Slice(1, width + height);
ReadOnlySpan<TSample> blueLeft = blueLeftStorage.Slice(1, width + height);
ReadOnlySpan<TSample> redAbove = redAboveStorage.Slice(1, width + height);
ReadOnlySpan<TSample> redLeft = redLeftStorage.Slice(1, width + height);
Av1TransformBlockContext blueContext = Av1TileWriter.GetTransformBlockContexts(
Av1ComponentType.Chroma,
this.picture.CbDcSignLevelCoefficientNeighbors[tileIndex],
@ -873,8 +876,8 @@ internal static partial class Av1IntraSuperblockEncoder
source,
transformOrigin,
prediction,
aboveStorage.Slice(1, transformWidth * 2),
leftStorage.Slice(1, transformHeight * 2),
aboveStorage.Slice(1, transformWidth + transformHeight),
leftStorage.Slice(1, transformWidth + transformHeight),
hasLeft,
hasAbove,
predictionMode,
@ -1150,7 +1153,21 @@ internal static partial class Av1IntraSuperblockEncoder
return Av1RateDistortion.GetCost(this.rateMultiplier, rate, distortion);
}
private void PrepareReferenceSamples(
/// <summary>
/// Prepares the shared corner and extended top and left edges for intra prediction.
/// </summary>
/// <param name="reconstructionPlane">The previously reconstructed plane.</param>
/// <param name="blockOrigin">The prediction block's origin in plane samples.</param>
/// <param name="width">The transform width in samples.</param>
/// <param name="height">The transform height in samples.</param>
/// <param name="hasLeft">Whether the left edge is available.</param>
/// <param name="hasAbove">Whether the top edge is available.</param>
/// <param name="hasTopRight">Whether the adjacent top-right block is reconstructed.</param>
/// <param name="hasBottomLeft">Whether the adjacent bottom-left block is reconstructed.</param>
/// <param name="bitDepth">The sample precision used for unavailable edges.</param>
/// <param name="aboveStorage">The corner followed by at least width plus height top-edge samples.</param>
/// <param name="leftStorage">The corner followed by at least width plus height left-edge samples.</param>
public static void PrepareReferenceSamples(
Buffer2DRegion<TSample> reconstructionPlane,
Point blockOrigin,
int width,
@ -1159,11 +1176,14 @@ internal static partial class Av1IntraSuperblockEncoder
bool hasAbove,
bool hasTopRight,
bool hasBottomLeft,
Av1BitDepth bitDepth,
Span<TSample> aboveStorage,
Span<TSample> leftStorage)
{
Span<TSample> above = aboveStorage.Slice(1, width * 2);
Span<TSample> left = leftStorage.Slice(1, height * 2);
// A directional ray can reach width + height - 1 on either edge, including on rectangles.
// Only one adjacent block supplies extension samples; the rest repeat its final sample.
Span<TSample> above = aboveStorage.Slice(1, width + height);
Span<TSample> left = leftStorage.Slice(1, width + height);
if (hasAbove)
{
reconstructionPlane.DangerousGetRowSpan(blockOrigin.Y - 1).Slice(blockOrigin.X, width).CopyTo(above[..width]);
@ -1177,7 +1197,7 @@ internal static partial class Av1IntraSuperblockEncoder
}
}
int midpoint = 128 << (this.bitDepth.GetBitCount() - 8);
int midpoint = 128 << (bitDepth.GetBitCount() - 8);
if (!hasAbove)
{
above[..width].Fill(hasLeft ? left[0] : TOperator.CreateSample(midpoint - 1));
@ -1188,27 +1208,26 @@ internal static partial class Av1IntraSuperblockEncoder
left[..height].Fill(hasAbove ? above[0] : TOperator.CreateSample(midpoint + 1));
}
int topRightCount = hasTopRight ? Math.Min(width, height) : 0;
if (hasTopRight)
{
reconstructionPlane.DangerousGetRowSpan(blockOrigin.Y - 1).Slice(blockOrigin.X + width, width).CopyTo(above[width..]);
}
else
{
above[width..].Fill(above[width - 1]);
reconstructionPlane.DangerousGetRowSpan(blockOrigin.Y - 1)
.Slice(blockOrigin.X + width, topRightCount)
.CopyTo(above[width..]);
}
if (hasBottomLeft)
{
for (int row = height; row < height * 2; row++)
{
left[row] = reconstructionPlane.DangerousGetRowSpan(blockOrigin.Y + row)[blockOrigin.X - 1];
}
}
else
int topCount = width + topRightCount;
above[topCount..].Fill(above[topCount - 1]);
int bottomLeftCount = hasBottomLeft ? Math.Min(height, width) : 0;
for (int row = height; row < height + bottomLeftCount; row++)
{
left[height..].Fill(left[height - 1]);
left[row] = reconstructionPlane.DangerousGetRowSpan(blockOrigin.Y + row)[blockOrigin.X - 1];
}
int leftCount = height + bottomLeftCount;
left[leftCount..].Fill(left[leftCount - 1]);
// Zone-two projection and Paeth address the common corner immediately before both edges.
// Missing edges derive it from the closest coded sample or the bit-depth midpoint.
TSample corner = hasAbove && hasLeft

133
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ModeDecision.cs

@ -376,6 +376,7 @@ internal static partial class Av1IntraSuperblockEncoder
leafOrigin,
tileIndex,
leafSize,
partitionType == Av1PartitionType.Split ? Av1PartitionType.None : partitionType,
publishContexts);
}
@ -412,7 +413,13 @@ internal static partial class Av1IntraSuperblockEncoder
if (this.IsBlockOriginInsideFrame(leafOrigin))
{
this.SetBlockGeometry(leafOrigin, leafSize, Av1PartitionType.None);
// Mixed vertical partitions reconstruct square leaves in a different order.
// Retain the parent decision so prediction uses the same edge availability as the decoder.
// Split children own another partition node; a terminal 4x4 child implicitly owns NONE.
this.SetBlockGeometry(
leafOrigin,
leafSize,
partitionType == Av1PartitionType.Split ? Av1PartitionType.None : partitionType);
}
}
}
@ -861,9 +868,11 @@ internal static partial class Av1IntraSuperblockEncoder
Point blockOrigin,
ushort tileIndex,
Av1BlockSize blockSize,
Av1PartitionType partitionType,
bool publishContexts)
{
this.SetBlockGeometry(blockOrigin, blockSize, Av1PartitionType.None);
// Trial leaves must use the same reconstruction order as final leaves of this partition.
this.SetBlockGeometry(blockOrigin, blockSize, partitionType);
Point modeInfoPosition = blockOrigin >> Av1Constants.ModeInfoSizeLog2;
Av1TileWriter.SetModeInfoRowAndColumn(
this.picture,
@ -1435,6 +1444,7 @@ internal static partial class Av1IntraSuperblockEncoder
int modeInfoColumn = blockOrigin.X >> Av1Constants.ModeInfoSizeLog2;
bool rightAvailable = modeInfoColumn + transformSize.Get4x4WideCount() < macroBlock.Tile.ModeInfoColumnEnd;
bool bottomAvailable = modeInfoRow + transformSize.Get4x4HighCount() < macroBlock.Tile.ModeInfoRowEnd;
Av1PartitionType partitionType = macroBlock.GetRelativeModeInfo(0).Block.PartitionType;
bool hasTopRight = Av1IntraReferenceAvailability.HasTopRight(
this.picture.Sequence.SequenceHeader.SuperblockSize,
blockSize,
@ -1442,7 +1452,7 @@ internal static partial class Av1IntraSuperblockEncoder
modeInfoColumn,
hasAbove,
rightAvailable,
Av1PartitionType.None,
partitionType,
transformSize,
0,
0,
@ -1456,7 +1466,7 @@ internal static partial class Av1IntraSuperblockEncoder
modeInfoColumn,
bottomAvailable,
hasLeft,
Av1PartitionType.None,
partitionType,
transformSize,
0,
0,
@ -1464,74 +1474,22 @@ internal static partial class Av1IntraSuperblockEncoder
0);
Span<TSample> aboveStorage = workspace.GetReferenceSamples(0);
Span<TSample> above = aboveStorage[1..];
Span<TSample> leftStorage = workspace.GetReferenceSamples(1);
Span<TSample> left = leftStorage[1..];
if (hasAbove)
{
reconstructionPlane.DangerousGetRowSpan(blockOrigin.Y - 1)
.Slice(blockOrigin.X, blockWidth)
.CopyTo(above[..blockWidth]);
}
if (hasLeft)
{
for (int row = 0; row < blockHeight; row++)
{
left[row] = reconstructionPlane.DangerousGetRowSpan(blockOrigin.Y + row)[blockOrigin.X - 1];
}
}
int midpoint = 128 << (this.bitDepth.GetBitCount() - 8);
// A missing edge repeats the closest perpendicular sample. Only a block with neither edge
// available uses the asymmetric midpoint offsets that distinguish top from left.
if (!hasAbove)
{
above[..blockWidth].Fill(hasLeft ? left[0] : TOperator.CreateSample(midpoint - 1));
}
if (!hasLeft)
{
left[..blockHeight].Fill(hasAbove ? above[0] : TOperator.CreateSample(midpoint + 1));
}
if (hasTopRight)
{
reconstructionPlane.DangerousGetRowSpan(blockOrigin.Y - 1)
.Slice(blockOrigin.X + blockWidth, blockWidth)
.CopyTo(above.Slice(blockWidth, blockWidth));
}
else
{
above.Slice(blockWidth, blockWidth).Fill(above[blockWidth - 1]);
}
if (hasBottomLeft)
{
for (int row = blockHeight; row < blockHeight * 2; row++)
{
left[row] = reconstructionPlane.DangerousGetRowSpan(blockOrigin.Y + row)[blockOrigin.X - 1];
}
}
else
{
left.Slice(blockHeight, blockHeight).Fill(left[blockHeight - 1]);
}
// Zone-two projection and Paeth address the common corner immediately before both prepared edges.
// When an edge is unavailable AV1 derives that corner from the closest coded edge.
TSample corner = hasAbove && hasLeft
? reconstructionPlane.DangerousGetRowSpan(blockOrigin.Y - 1)[blockOrigin.X - 1]
: hasAbove
? above[0]
: hasLeft
? left[0]
: TOperator.CreateSample(midpoint);
PrepareReferenceSamples(
reconstructionPlane,
blockOrigin,
blockWidth,
blockHeight,
hasLeft,
hasAbove,
hasTopRight,
hasBottomLeft,
this.bitDepth,
aboveStorage,
leftStorage);
aboveStorage[0] = corner;
leftStorage[0] = corner;
ReadOnlySpan<TSample> above = aboveStorage.Slice(1, blockWidth + blockHeight);
ReadOnlySpan<TSample> left = leftStorage.Slice(1, blockWidth + blockHeight);
Av1TransformBlockContext blockContext = Av1TileWriter.GetTransformBlockContexts(
Av1ComponentType.Luminance,
@ -2536,6 +2494,7 @@ internal static partial class Av1IntraSuperblockEncoder
((transformRow4x4 + transformSize.Get4x4HighCount()) << subsamplingY) <
macroBlock.Tile.ModeInfoRowEnd;
Av1PartitionType partitionType = macroBlock.GetRelativeModeInfo(0).Block.PartitionType;
bool hasTopRight = Av1IntraReferenceAvailability.HasTopRight(
this.picture.Sequence.SequenceHeader.SuperblockSize,
blockSize,
@ -2543,7 +2502,7 @@ internal static partial class Av1IntraSuperblockEncoder
modeInfoColumn,
hasAbove,
rightAvailable,
Av1PartitionType.None,
partitionType,
transformSize,
transformRow4x4,
transformColumn4x4,
@ -2557,15 +2516,17 @@ internal static partial class Av1IntraSuperblockEncoder
modeInfoColumn,
bottomAvailable,
hasLeft,
Av1PartitionType.None,
partitionType,
transformSize,
transformRow4x4,
transformColumn4x4,
subsamplingX,
subsamplingY);
Span<TSample> above = aboveStorage.Slice(1, transformWidth * 2);
Span<TSample> left = leftStorage.Slice(1, transformHeight * 2);
// Rectangular transforms project as far as width + height - 1 on either edge.
// The existing reference storage already holds this maximum; no additional scratch is needed.
Span<TSample> above = aboveStorage.Slice(1, transformWidth + transformHeight);
Span<TSample> left = leftStorage.Slice(1, transformWidth + transformHeight);
if (hasAbove)
{
if (transformRow > 0)
@ -2613,6 +2574,7 @@ internal static partial class Av1IntraSuperblockEncoder
left[..transformHeight].Fill(hasAbove ? above[0] : TOperator.CreateSample(midpoint + 1));
}
int topRightCount = hasTopRight ? Math.Min(transformWidth, transformHeight) : 0;
if (hasTopRight)
{
if (transformRow > 0)
@ -2620,26 +2582,26 @@ internal static partial class Av1IntraSuperblockEncoder
candidateReconstruction
.Slice(
((rowOffset - 1) * planeBlockWidth) + columnOffset + transformWidth,
transformWidth)
topRightCount)
.CopyTo(above[transformWidth..]);
}
else
{
reconstructionPlane.DangerousGetRowSpan(planeBlockOrigin.Y - 1)
.Slice(planeBlockOrigin.X + columnOffset + transformWidth, transformWidth)
.Slice(planeBlockOrigin.X + columnOffset + transformWidth, topRightCount)
.CopyTo(above[transformWidth..]);
}
}
else
{
above[transformWidth..].Fill(above[transformWidth - 1]);
}
int topCount = transformWidth + topRightCount;
above[topCount..].Fill(above[topCount - 1]);
int bottomLeftCount = hasBottomLeft ? Math.Min(transformHeight, transformWidth) : 0;
if (hasBottomLeft)
{
if (transformColumn > 0)
{
for (int row = transformHeight; row < transformHeight * 2; row++)
for (int row = transformHeight; row < transformHeight + bottomLeftCount; row++)
{
left[row] = candidateReconstruction[
((rowOffset + row) * planeBlockWidth) + columnOffset - 1];
@ -2647,17 +2609,16 @@ internal static partial class Av1IntraSuperblockEncoder
}
else
{
for (int row = transformHeight; row < transformHeight * 2; row++)
for (int row = transformHeight; row < transformHeight + bottomLeftCount; row++)
{
left[row] = reconstructionPlane
.DangerousGetRowSpan(planeBlockOrigin.Y + rowOffset + row)[planeBlockOrigin.X - 1];
}
}
}
else
{
left[transformHeight..].Fill(left[transformHeight - 1]);
}
int leftCount = transformHeight + bottomLeftCount;
left[leftCount..].Fill(left[leftCount - 1]);
// Only an interior transform corner belongs to decision scratch. Boundary corners continue
// to read the already reconstructed neighboring block so candidate trials remain isolated.

92
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs

@ -29,6 +29,98 @@ public class Av1EncoderFrameTests
private const int Yuv422 = (int)Av1ColorFormat.Yuv422;
private const int Yuv444 = (int)Av1ColorFormat.Yuv444;
[Theory]
[InlineData(EightBit, false, false)]
[InlineData(EightBit, false, true)]
[InlineData(EightBit, true, false)]
[InlineData(EightBit, true, true)]
[InlineData(TenBit, false, false)]
[InlineData(TenBit, false, true)]
[InlineData(TenBit, true, false)]
[InlineData(TenBit, true, true)]
[InlineData(TwelveBit, false, false)]
[InlineData(TwelveBit, false, true)]
[InlineData(TwelveBit, true, false)]
[InlineData(TwelveBit, true, true)]
public void RectangularIntraReferencesExtendTheLastAvailableSample(int bitDepthValue, bool transpose, bool extensionAvailable)
{
Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue;
if (bitDepth == Av1BitDepth.EightBit)
{
AssertRectangularIntraReferences<byte, Av1IntraSuperblockEncoder.ByteOperator>(bitDepth, transpose, extensionAvailable);
}
else
{
AssertRectangularIntraReferences<ushort, Av1IntraSuperblockEncoder.UInt16Operator>(bitDepth, transpose, extensionAvailable);
}
}
private static void AssertRectangularIntraReferences<TSample, TOperator>(
Av1BitDepth bitDepth,
bool transpose,
bool extensionAvailable)
where TSample : unmanaged
where TOperator : struct, Av1IntraSuperblockEncoder.IBlockEncodingOperator<TSample>
{
int width = transpose ? 16 : 4;
int height = transpose ? 4 : 16;
int scale = 1 << (bitDepth.GetBitCount() - 8);
using Buffer2D<TSample> plane = Configuration.Default.MemoryAllocator.Allocate2D<TSample>(33, 33);
for (int i = 0; i < 32; i++)
{
plane.DangerousGetRowSpan(0)[i + 1] = TOperator.CreateSample((10 + i) * scale);
plane.DangerousGetRowSpan(i + 1)[0] = TOperator.CreateSample((50 + i) * scale);
}
plane.DangerousGetRowSpan(0)[0] = TOperator.CreateSample(100 * scale);
// Native reconintra.c extends a four-sample edge through its four-sample neighbor, then
// repeats sample seven to cover the twenty samples required by a 4x16 directional ray.
// These explicit offsets also distinguish unavailable neighbors from available extension.
int[] shortEdge = extensionAvailable
? [0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
: [0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3];
int[] longEdge = extensionAvailable
? [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15];
int[] expectedAbove = transpose ? longEdge : shortEdge;
int[] expectedLeft = transpose ? shortEdge : longEdge;
TSample poison = TOperator.CreateSample((1 << bitDepth.GetBitCount()) - 1);
TSample[] above = new TSample[23];
TSample[] left = new TSample[23];
above.AsSpan().Fill(poison);
left.AsSpan().Fill(poison);
// The exact-sized interior includes the corner and twenty projected samples. Sentinel samples
// on either side detect writes outside the reference view, including the former 2*long-edge span.
Av1IntraSuperblockEncoder.ModeDecision<TSample, TOperator>.PrepareReferenceSamples(
plane.GetRegion(),
new Point(1, 1),
width,
height,
true,
true,
extensionAvailable,
extensionAvailable,
bitDepth,
above.AsSpan(1, 21),
left.AsSpan(1, 21));
Assert.Equal(poison, above[0]);
Assert.Equal(poison, left[0]);
Assert.Equal(poison, above[^1]);
Assert.Equal(poison, left[^1]);
Assert.Equal(TOperator.CreateSample(100 * scale), above[1]);
Assert.Equal(TOperator.CreateSample(100 * scale), left[1]);
for (int i = 0; i < 20; i++)
{
Assert.Equal(TOperator.CreateSample((10 + expectedAbove[i]) * scale), above[i + 2]);
Assert.Equal(TOperator.CreateSample((50 + expectedLeft[i]) * scale), left[i + 2]);
}
}
[Fact]
public void EncodeUsesMultipleTilesWhenSingleTileWidthLimitIsExceeded()
{

89
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs

@ -2911,6 +2911,95 @@ public class Av1IntraSuperblockEncoderTests
Assert.NotEqual(0, tileWriter.GetTileData(0).Length);
}
/// <summary>
/// Verifies that mixed partition trials and final writing retain the decoder's reconstruction order.
/// </summary>
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ProductionMixedPartitionsPreserveReconstructionOrder(bool transpose)
{
const int Size = 32;
const int QIndex = 4;
ObuColorConfig colorConfig = new()
{
IsMonochrome = true,
SubSamplingX = true,
SubSamplingY = true,
BitDepth = Av1BitDepth.EightBit
};
using Av1EncoderFrameBuffer<byte> source = new(Configuration.Default, Size, Size, 8, Av1ColorFormat.Yuv400, 0, 0);
using Av1EncoderFrameBuffer<byte> reconstruction = new(Configuration.Default, Size, Size, 8, Av1ColorFormat.Yuv400, 0, 0);
Buffer2DRegion<byte> sourcePlane = source.Frame.CodedView.GetPlane(Av1Plane.Y);
for (int y = 0; y < Size; y++)
{
for (int x = 0; x < Size; x++)
{
// The lower-right quadrant contains two different square surfaces beside one vertical
// surface. Transposition exercises the corresponding horizontal reconstruction order.
int value = x < 16 && y < 16 ? 128
: y < 16 ? 16 + ((x - 16) * 12)
: x < 16 ? 16 + ((y - 16) * 12)
: x >= 24 ? 16 + ((x - 16) * 12)
: y < 24 ? 16 + ((x + y - 31) * 12)
: 16 + ((x - 8) * 12);
sourcePlane.DangerousGetRowSpan(transpose ? x : y)[transpose ? y : x] = (byte)value;
}
}
ClearPlane(reconstruction.Luma);
using Av1EncoderModeInfoBuffer modeInfo = new(Configuration.Default, Size, Size, disallow4x4AllFrames: false);
Av1PictureControlSet template = CreatePicture(modeInfo, colorConfig, use128x128Superblock: false, QIndex);
using Av1EncoderPictureBuffer picture = new(
Configuration.Default, template.Sequence.SequenceHeader, template.Parent.FrameHeader, Size, Size, disallow4x4AllFrames: false);
using Av1EncoderCoefficientBuffer coefficients = new(Configuration.Default, template.Sequence.SequenceHeader, Size, Size);
using Av1EncoderSuperblockWorkspace superblockWorkspace = new(Configuration.Default);
using Av1EncoderBlockWorkspace blockWorkspace = new(Configuration.Default);
using Av1SymbolEncoder symbolEncoder = CreateTileSymbolEncoder(picture.Picture, 8192);
Av1TileEncoder tileWriter = new(
symbolEncoder, source.Frame, reconstruction.Frame, picture.Picture, coefficients, superblockWorkspace, blockWorkspace, effort: 9);
byte[] payload = WriteCompleteTileObu(picture.Picture, tileWriter, Size, Size);
using Av1Decoder decoder = new(Configuration.Default);
decoder.DecodeSequenceReference(payload, null, null);
Av1FrameInfo decodedInfo = Assert.IsType<Av1FrameInfo>(decoder.FrameInfo);
Av1FrameBuffer<byte> decodedFrame = Assert.IsType<Av1FrameBuffer<byte>>(decoder.FrameBuffer);
Buffer2DRegion<byte> decodedPlane = decodedFrame.DeriveBlockPointer(Av1Plane.Y, 0, 0);
Buffer2DRegion<byte> retainedPlane = reconstruction.Frame.CodedView.GetPlane(Av1Plane.Y);
bool hasMixedPartition = false;
for (int y = 0; y < Size; y++)
{
Assert.Equal(retainedPlane.DangerousGetRowSpan(y).ToArray(), decodedPlane.DangerousGetRowSpan(y).ToArray());
for (int x = 0; x < Size; x += 4)
{
Point position = new(x >> 2, y >> 2);
Av1PartitionType partition = decodedInfo.GetModeInfoAt(position).PartitionType;
// Interior 4x4 entries alias the block origin through the live grid; unused allocation
// slots may still contain rejected trial data and are not retained block state.
int allocationIndex = picture.Picture.ModeInfoGrid.Span[(position.Y * picture.Picture.ModeInfoStride) + position.X];
Assert.Equal(partition, picture.Picture.ModeInfoAllocation.Span[allocationIndex].Block.PartitionType);
hasMixedPartition |= partition is Av1PartitionType.HorizontalA or Av1PartitionType.HorizontalB
or Av1PartitionType.VerticalA or Av1PartitionType.VerticalB;
}
}
Assert.True(hasMixedPartition);
string directory = Path.Combine(
TestEnvironment.ActualOutputDirectoryFullPath, "Heif", "Av1", nameof(this.ProductionMixedPartitionsPreserveReconstructionOrder));
Directory.CreateDirectory(directory);
File.WriteAllBytes(Path.Combine(directory, $"{transpose}.obu"), payload);
using FileStream raw = File.Create(Path.Combine(directory, $"{transpose}.retained.yuv"));
for (int y = 0; y < Size; y++)
{
raw.Write(retainedPlane.DangerousGetRowSpan(y));
}
}
private static byte[] WriteCompleteTileObu(
Av1PictureControlSet pictureTemplate,
IAv1TileWriter tileWriter,

Loading…
Cancel
Save