Browse Source

Prediction decoding

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
c9baaf019b
  1. 2
      src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
  2. 9
      src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
  3. 4
      src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs
  4. 3
      src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
  5. 505
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1BottomRightTopLeftConstants.cs
  6. 15
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1NeighborNeed.cs
  7. 18
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs
  8. 40
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs
  9. 120
      src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaContext.cs
  10. 26
      src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaMath.cs
  11. 1094
      src/ImageSharp/Formats/Heif/Av1/Prediction/PredictionDecoder.cs
  12. 2
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs
  13. 3
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs
  14. 63
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs
  15. 18
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
  16. 2
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs

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

@ -173,4 +173,6 @@ internal static class Av1Constants
/// Total number of Quantification Matrices sets stored.
/// </summary>
public const int QuantificationMatrixLevelCount = 4;
public const int AngleStep = 3;
}

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

@ -10,10 +10,15 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal class Av1Decoder : IAv1TileReader
{
private readonly ObuReader obuReader;
private readonly Configuration configuration;
private Av1TileReader? tileReader;
private Av1FrameDecoder? frameDecoder;
public Av1Decoder() => this.obuReader = new();
public Av1Decoder(Configuration configuration)
{
this.configuration = configuration;
this.obuReader = new();
}
public ObuFrameHeader? FrameHeader { get; private set; }
@ -40,7 +45,7 @@ internal class Av1Decoder : IAv1TileReader
{
this.SequenceHeader = this.obuReader.SequenceHeader;
this.FrameHeader = this.obuReader.FrameHeader;
this.tileReader = new Av1TileReader(this.SequenceHeader!, this.FrameHeader!);
this.tileReader = new Av1TileReader(this.configuration, this.SequenceHeader!, this.FrameHeader!);
}
this.tileReader.ReadTile(tileData, tileNum);

4
src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs

@ -33,6 +33,8 @@ internal class Av1FrameBuffer : IDisposable
int topPadding = DecoderPaddingValue;
int bottomPadding = DecoderPaddingValue;
this.StartPosition = new Point(leftPadding, topPadding);
this.Width = this.MaxWidth;
this.Height = this.MaxHeight;
int strideY = this.MaxWidth + leftPadding + rightPadding;
@ -86,6 +88,8 @@ internal class Av1FrameBuffer : IDisposable
this.BitIncrementCr = null;
}
public Point StartPosition { get; private set; }
/// <summary>
/// Gets the Y luma buffer.
/// </summary>

3
src/ImageSharp/Formats/Heif/Av1/Av1Math.cs

@ -152,4 +152,7 @@ internal static class Av1Math
internal static int Modulus8(int value) => value & 0x07;
internal static int DivideBy8Floor(int value) => value >> 3;
internal static int RoundPowerOf2Signed(int value, int n)
=> (value < 0) ? -RoundPowerOf2(-value, n) : RoundPowerOf2(value, n);
}

505
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1BottomRightTopLeftConstants.cs

@ -0,0 +1,505 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1BottomRightTopLeftConstants
{
// Tables to store if the top-right reference pixels are available. The flags
// are represented with bits, packed into 8-bit integers. E.g., for the 32x32
// blocks in a 128x128 superblock, the index of the "o" block is 10 (in raster
// order), so its flag is stored at the 3rd bit of the 2nd entry in the table,
// i.e. (table[10 / 8] >> (10 % 8)) & 1.
// . . . .
// . . . .
// . . o .
// . . . .
private static readonly byte[] HasTopRight4x4 = [
255, 255, 255, 255, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 127, 127, 127, 127, 85, 85,
85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 255, 127, 255, 127, 85, 85, 85, 85, 119, 119, 119, 119,
85, 85, 85, 85, 127, 127, 127, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 255, 255,
255, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 127, 127, 127, 127, 85, 85, 85, 85,
119, 119, 119, 119, 85, 85, 85, 85, 255, 127, 255, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85,
85, 85, 127, 127, 127, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85,
];
private static readonly byte[] HasTopRight4x8 = [
255, 255, 255, 255, 119, 119, 119, 119, 127, 127, 127, 127, 119, 119, 119, 119, 255, 127, 255, 127, 119, 119,
119, 119, 127, 127, 127, 127, 119, 119, 119, 119, 255, 255, 255, 127, 119, 119, 119, 119, 127, 127, 127, 127,
119, 119, 119, 119, 255, 127, 255, 127, 119, 119, 119, 119, 127, 127, 127, 127, 119, 119, 119, 119,
];
private static readonly byte[] HasTopRight8x4 = [
255, 255, 0, 0, 85, 85, 0, 0, 119, 119, 0, 0, 85, 85, 0, 0, 127, 127, 0, 0, 85, 85,
0, 0, 119, 119, 0, 0, 85, 85, 0, 0, 255, 127, 0, 0, 85, 85, 0, 0, 119, 119, 0, 0,
85, 85, 0, 0, 127, 127, 0, 0, 85, 85, 0, 0, 119, 119, 0, 0, 85, 85, 0, 0,
];
private static readonly byte[] HasTopRight8x8 = [
255, 255, 85, 85, 119, 119, 85, 85, 127, 127, 85, 85, 119, 119, 85, 85,
255, 127, 85, 85, 119, 119, 85, 85, 127, 127, 85, 85, 119, 119, 85, 85,
];
private static readonly byte[] HasTopRight8x16 = [
255,
255,
119,
119,
127,
127,
119,
119,
255,
127,
119,
119,
127,
127,
119,
119,
];
private static readonly byte[] HasTopRight16x8 = [
255,
0,
85,
0,
119,
0,
85,
0,
127,
0,
85,
0,
119,
0,
85,
0,
];
private static readonly byte[] HasTopRight16x16 = [
255,
85,
119,
85,
127,
85,
119,
85,
];
private static readonly byte[] HasTopRight16x32 = [255, 119, 127, 119];
private static readonly byte[] HasTopRight32x16 = [15, 5, 7, 5];
private static readonly byte[] HasTopRight32x32 = [95, 87];
private static readonly byte[] HasTopRight32x64 = [127];
private static readonly byte[] HasTopRight64x32 = [19];
private static readonly byte[] HasTopRight64x64 = [7];
private static readonly byte[] HasTopRight64x128 = [3];
private static readonly byte[] HasTopRight128x64 = [1];
private static readonly byte[] HasTopRight128x128 = [1];
private static readonly byte[] HasTopRight4x16 = [
255, 255, 255, 255, 127, 127, 127, 127, 255, 127, 255, 127, 127, 127, 127, 127,
255, 255, 255, 127, 127, 127, 127, 127, 255, 127, 255, 127, 127, 127, 127, 127,
];
private static readonly byte[] HasTopRight16x4 = [
255, 0, 0, 0, 85, 0, 0, 0, 119, 0, 0, 0, 85, 0, 0, 0, 127, 0, 0, 0, 85, 0, 0, 0, 119, 0, 0, 0, 85, 0, 0, 0,
];
private static readonly byte[] HasTopRight8x32 = [
255,
255,
127,
127,
255,
127,
127,
127,
];
private static readonly byte[] HasTopRight32x8 = [
15,
0,
5,
0,
7,
0,
5,
0,
];
private static readonly byte[] HasTopRight16x64 = [255, 127];
private static readonly byte[] HasTopRight64x16 = [3, 1];
private static readonly byte[][] HasTopRightTables = [
// 4X4
HasTopRight4x4,
// 4X8, 8X4, 8X8
HasTopRight4x8,
HasTopRight8x4,
HasTopRight8x8,
// 8X16, 16X8, 16X16
HasTopRight8x16,
HasTopRight16x8,
HasTopRight16x16,
// 16X32, 32X16, 32X32
HasTopRight16x32,
HasTopRight32x16,
HasTopRight32x32,
// 32X64, 64X32, 64X64
HasTopRight32x64,
HasTopRight64x32,
HasTopRight64x64,
// 64x128, 128x64, 128x128
HasTopRight64x128,
HasTopRight128x64,
HasTopRight128x128,
// 4x16, 16x4, 8x32
HasTopRight4x16,
HasTopRight16x4,
HasTopRight8x32,
// 32x8, 16x64, 64x16
HasTopRight32x8,
HasTopRight16x64,
HasTopRight64x16
];
private static readonly byte[] HasTopRightVertical8x8 = [
255, 255, 0, 0, 119, 119, 0, 0, 127, 127, 0, 0, 119, 119, 0, 0,
255, 127, 0, 0, 119, 119, 0, 0, 127, 127, 0, 0, 119, 119, 0, 0,
];
private static readonly byte[] HasTopRightVertical16x16 = [
255,
0,
119,
0,
127,
0,
119,
0,
];
private static readonly byte[] HasTopRightVertical32x32 = [15, 7];
private static readonly byte[] HasTopRightVertical64x64 = [3];
// The _vert_* tables are like the ordinary tables above, but describe the
// order we visit square blocks when doing a PARTITION_VERT_A or
// PARTITION_VERT_B. This is the same order as normal except for on the last
// split where we go vertically (TL, BL, TR, BR). We treat the rectangular block
// as a pair of squares, which means that these tables work correctly for both
// mixed vertical partition types.
//
// There are tables for each of the square sizes. Vertical rectangles (like
// BLOCK_16X32) use their respective "non-vert" table
private static readonly byte[]?[] HasTopRightVerticalTables = [
// 4X4
null,
// 4X8, 8X4, 8X8
HasTopRight4x8,
null,
HasTopRightVertical8x8,
// 8X16, 16X8, 16X16
HasTopRight8x16,
null,
HasTopRightVertical16x16,
// 16X32, 32X16, 32X32
HasTopRight16x32,
null,
HasTopRightVertical32x32,
// 32X64, 64X32, 64X64
HasTopRight32x64,
null,
HasTopRightVertical64x64,
// 64x128, 128x64, 128x128
HasTopRight64x128,
null,
HasTopRight128x128
];
// Similar to the has_tr_* tables, but store if the bottom-left reference
// pixels are available.
private static readonly byte[] HasBottomLeft4x4 = [
84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 1, 1, 1, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85,
85, 85, 0, 0, 1, 0, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 1, 1, 1, 84, 85, 85, 85,
16, 17, 17, 17, 84, 85, 85, 85, 0, 0, 0, 0, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 1,
1, 1, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 0, 1, 0, 84, 85, 85, 85, 16, 17, 17, 17,
84, 85, 85, 85, 0, 1, 1, 1, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 0, 0, 0,
];
private static readonly byte[] HasBottomLeft4x8 = [
16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 1, 0, 16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 0, 0,
16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 1, 0, 16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 0, 0,
];
private static readonly byte[] HasBottomLeft8x4 = [
254, 255, 84, 85, 254, 255, 16, 17, 254, 255, 84, 85, 254, 255, 0, 1, 254, 255, 84, 85, 254, 255,
16, 17, 254, 255, 84, 85, 254, 255, 0, 0, 254, 255, 84, 85, 254, 255, 16, 17, 254, 255, 84, 85,
254, 255, 0, 1, 254, 255, 84, 85, 254, 255, 16, 17, 254, 255, 84, 85, 254, 255, 0, 0,
];
private static readonly byte[] HasBottomLeft8x8 = [
84, 85, 16, 17, 84, 85, 0, 1, 84, 85, 16, 17, 84, 85, 0, 0,
84, 85, 16, 17, 84, 85, 0, 1, 84, 85, 16, 17, 84, 85, 0, 0,
];
private static readonly byte[] HasBottomLeft8x16 = [
16,
17,
0,
1,
16,
17,
0,
0,
16,
17,
0,
1,
16,
17,
0,
0,
];
private static readonly byte[] HasBottomLeft16x8 = [
254,
84,
254,
16,
254,
84,
254,
0,
254,
84,
254,
16,
254,
84,
254,
0,
];
private static readonly byte[] HasBottomLeft16x16 = [
84,
16,
84,
0,
84,
16,
84,
0,
];
private static readonly byte[] HasBottomLeft16x32 = [16, 0, 16, 0];
private static readonly byte[] HasBottomLeft32x16 = [78, 14, 78, 14];
private static readonly byte[] HasBottomLeft32x32 = [4, 4];
private static readonly byte[] HasBottomLeft32x64 = [0];
private static readonly byte[] HasBottomLeft64x32 = [34];
private static readonly byte[] HasBottomLeft64x64 = [0];
private static readonly byte[] HasBottomLeft64x128 = [0];
private static readonly byte[] HasBottomLeft128x64 = [0];
private static readonly byte[] HasBottomLeft128x128 = [0];
private static readonly byte[] HasBottomLeft4x16 = [
0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0,
];
private static readonly byte[] HasBottomLeft16x4 = [
254, 254, 254, 84, 254, 254, 254, 16, 254, 254, 254, 84, 254, 254, 254, 0,
254, 254, 254, 84, 254, 254, 254, 16, 254, 254, 254, 84, 254, 254, 254, 0,
];
private static readonly byte[] HasBottomLeft8x32 = [
0,
1,
0,
0,
0,
1,
0,
0,
];
private static readonly byte[] HasBottomLeft32x8 = [
238,
78,
238,
14,
238,
78,
238,
14,
];
private static readonly byte[] HasBottomLeft16x64 = [0, 0];
private static readonly byte[] HasBottomLeft64x16 = [42, 42];
private static readonly byte[][] HasBottomLeftTables = [
// 4X4
HasBottomLeft4x4,
// 4X8, 8X4, 8X8
HasBottomLeft4x8,
HasBottomLeft8x4,
HasBottomLeft8x8,
// 8X16, 16X8, 16X16
HasBottomLeft8x16,
HasBottomLeft16x8,
HasBottomLeft16x16,
// 16X32, 32X16, 32X32
HasBottomLeft16x32,
HasBottomLeft32x16,
HasBottomLeft32x32,
// 32X64, 64X32, 64X64
HasBottomLeft32x64,
HasBottomLeft64x32,
HasBottomLeft64x64,
// 64x128, 128x64, 128x128
HasBottomLeft64x128,
HasBottomLeft128x64,
HasBottomLeft128x128,
// 4x16, 16x4, 8x32
HasBottomLeft4x16,
HasBottomLeft16x4,
HasBottomLeft8x32,
// 32x8, 16x64, 64x16
HasBottomLeft32x8,
HasBottomLeft16x64,
HasBottomLeft64x16
];
private static readonly byte[] HasBottomLeftVertical8x8 = [
254, 255, 16, 17, 254, 255, 0, 1, 254, 255, 16, 17, 254, 255, 0, 0,
254, 255, 16, 17, 254, 255, 0, 1, 254, 255, 16, 17, 254, 255, 0, 0,
];
private static readonly byte[] HasBottomLeftVertical16x16 = [
254,
16,
254,
0,
254,
16,
254,
0,
];
private static readonly byte[] HasBottomLeftVertical32x32 = [14, 14];
private static readonly byte[] HasBottomLeftVertical64x64 = [2];
// The _vert_* tables are like the ordinary tables above, but describe the
// order we visit square blocks when doing a PARTITION_VERT_A or
// PARTITION_VERT_B. This is the same order as normal except for on the last
// split where we go vertically (TL, BL, TR, BR). We treat the rectangular block
// as a pair of squares, which means that these tables work correctly for both
// mixed vertical partition types.
//
// There are tables for each of the square sizes. Vertical rectangles (like
// BLOCK_16X32) use their respective "non-vert" table
private static readonly byte[]?[] HasBottomLeftVerticalTables = [
// 4X4
null,
// 4X8, 8X4, 8X8
HasBottomLeft4x8,
null,
HasBottomLeftVertical8x8,
// 8X16, 16X8, 16X16
HasBottomLeft8x16,
null,
HasBottomLeftVertical16x16,
// 16X32, 32X16, 32X32
HasBottomLeft16x32,
null,
HasBottomLeftVertical32x32,
// 32X64, 64X32, 64X64
HasBottomLeft32x64,
null,
HasBottomLeftVertical64x64,
// 64x128, 128x64, 128x128
HasBottomLeft64x128,
null,
HasBottomLeft128x128];
public static bool HasTopRight(Av1PartitionType partitionType, Av1BlockSize blockSize, int blockIndex)
{
int index1 = blockIndex / 8;
int index2 = blockIndex % 8;
Span<byte> hasBottomLeftTable = GetHasTopRightTable(partitionType, blockSize);
return ((hasBottomLeftTable[index1] >> index2) & 1) > 0;
}
public static bool HasBottomLeft(Av1PartitionType partitionType, Av1BlockSize blockSize, int blockIndex)
{
int index1 = blockIndex / 8;
int index2 = blockIndex % 8;
Span<byte> hasBottomLeftTable = GetHasBottomLeftTable(partitionType, blockSize);
return ((hasBottomLeftTable[index1] >> index2) & 1) > 0;
}
private static Span<byte> GetHasTopRightTable(Av1PartitionType partition, Av1BlockSize blockSize)
{
byte[]? ret;
// If this is a mixed vertical partition, look up block size in vertical order.
if (partition is Av1PartitionType.VerticalA or Av1PartitionType.VerticalB)
{
DebugGuard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.SizeS, nameof(blockSize));
ret = HasTopRightVerticalTables[(int)blockSize];
}
else
{
ret = HasTopRightTables[(int)blockSize];
}
DebugGuard.NotNull(ret, nameof(ret));
return ret;
}
private static Span<byte> GetHasBottomLeftTable(Av1PartitionType partition, Av1BlockSize blockSize)
{
byte[]? ret;
// If this is a mixed vertical partition, look up block size in vertical order.
if (partition is Av1PartitionType.VerticalA or Av1PartitionType.VerticalB)
{
DebugGuard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.SizeS, nameof(blockSize));
ret = HasBottomLeftVerticalTables[(int)blockSize];
}
else
{
ret = HasBottomLeftTables[(int)blockSize];
}
DebugGuard.NotNull(ret, nameof(ret));
return ret;
}
}

15
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1NeighborNeed.cs

@ -0,0 +1,15 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
[Flags]
internal enum Av1NeighborNeed
{
Nothing = 0,
Left = 2,
Above = 4,
AboveRight = 8,
AboveLeft = 16,
BottomLeft = 32,
}

18
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs

@ -0,0 +1,18 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1PredictorFactory
{
internal static void DcPredictor(bool v1, bool v2, Av1TransformSize transformSize, ref byte destination, nuint destinationStride, Span<byte> aboveRow, Span<byte> leftColumn) => throw new NotImplementedException();
internal static void DirectionalPredictor(ref byte destination, nuint destinationStride, Av1TransformSize transformSize, Span<byte> aboveRow, Span<byte> leftColumn, bool upsampleAbove, bool upsampleLeft, int angle) => throw new NotImplementedException();
internal static void FilterIntraPredictor(ref byte destination, nuint destinationStride, Av1TransformSize transformSize, Span<byte> aboveRow, Span<byte> leftColumn, Av1FilterIntraMode filterIntraMode) => throw new NotImplementedException();
internal static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, ref byte destination, nuint destinationStride, Span<byte> aboveRow, Span<byte> leftColumn) => throw new NotImplementedException();
}

40
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
@ -23,5 +24,44 @@ internal static class Av1PreditionModeExtensions
Av1TransformType.AdstAdst, // PAETH
];
private static readonly Av1NeighborNeed[] NeedsMap = [
Av1NeighborNeed.Above | Av1NeighborNeed.Left, // DC
Av1NeighborNeed.Above, // V
Av1NeighborNeed.Left, // H
Av1NeighborNeed.Above | Av1NeighborNeed.AboveRight, // D45
Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // D135
Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // D113
Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // D157
Av1NeighborNeed.Left | Av1NeighborNeed.BottomLeft, // D203
Av1NeighborNeed.Above | Av1NeighborNeed.AboveRight, // D67
Av1NeighborNeed.Left | Av1NeighborNeed.Above, // SMOOTH
Av1NeighborNeed.Left | Av1NeighborNeed.Above, // SMOOTH_V
Av1NeighborNeed.Left | Av1NeighborNeed.Above, // SMOOTH_H
Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // PAETH
];
private static readonly int[] AngleMap = [
0,
90,
180,
45,
135,
113,
157,
203,
67,
0,
0,
0,
0,
];
public static Av1TransformType ToTransformType(this Av1PredictionMode mode) => IntraPreditionMode2TransformType[(int)mode];
public static bool IsDirectional(this Av1PredictionMode mode)
=> mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees;
public static Av1NeighborNeed GetNeighborNeed(this Av1PredictionMode mode) => NeedsMap[(int)mode];
public static int ToAngle(this Av1PredictionMode mode) => AngleMap[(int)mode];
}

120
src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaContext.cs

@ -0,0 +1,120 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma;
internal class Av1ChromaFromLumaContext
{
private const int BufferLine = 32;
private int bufferHeight;
private int bufferWidth;
private readonly bool subX;
private readonly bool subY;
public Av1ChromaFromLumaContext(Configuration configuration, ObuColorConfig colorConfig)
{
this.subX = colorConfig.SubSamplingX;
this.subY = colorConfig.SubSamplingY;
this.Q3Buffer = configuration.MemoryAllocator.Allocate2D<short>(new Size(32, 32), AllocationOptions.Clean);
}
public Buffer2D<short> Q3Buffer { get; private set; }
public bool AreParametersComputed { get; private set; }
public void ComputeParameters(Av1TransformSize transformSize)
{
Guard.IsFalse(this.AreParametersComputed, nameof(this.AreParametersComputed), "Do not call cfl_compute_parameters multiple time on the same values.");
this.Pad(transformSize.GetWidth(), transformSize.GetHeight());
SubtractAverage(ref this.Q3Buffer[0, 0], transformSize);
this.AreParametersComputed = true;
}
private void Pad(int width, int height)
{
int diff_width = width - this.bufferWidth;
int diff_height = height - this.bufferHeight;
if (diff_width > 0)
{
int min_height = height - diff_height;
ref short recon_buf_q3 = ref this.Q3Buffer[width - diff_width, 0];
for (int j = 0; j < min_height; j++)
{
short last_pixel = Unsafe.Subtract(ref recon_buf_q3, 1);
Guard.IsTrue(Unsafe.IsAddressLessThan(ref Unsafe.Add(ref recon_buf_q3, diff_width), ref this.Q3Buffer[BufferLine, BufferLine]), nameof(recon_buf_q3), "Shall stay within bounds.");
for (int i = 0; i < diff_width; i++)
{
Unsafe.Add(ref recon_buf_q3, i) = last_pixel;
}
recon_buf_q3 += BufferLine;
}
this.bufferWidth = width;
}
if (diff_height > 0)
{
ref short recon_buf_q3 = ref this.Q3Buffer[0, height - diff_height];
for (int j = 0; j < diff_height; j++)
{
ref short last_row_q3 = ref Unsafe.Subtract(ref recon_buf_q3, BufferLine);
Guard.IsTrue(Unsafe.IsAddressLessThan(ref Unsafe.Add(ref recon_buf_q3, diff_width), ref this.Q3Buffer[BufferLine, BufferLine]), nameof(recon_buf_q3), "Shall stay within bounds.");
for (int i = 0; i < width; i++)
{
Unsafe.Add(ref recon_buf_q3, i) = Unsafe.Add(ref last_row_q3, i);
}
recon_buf_q3 += BufferLine;
}
this.bufferHeight = height;
}
}
/************************************************************************************************
* svt_subtract_average_c
* Calculate the DC value by averaging over all sample. Subtract DC value to get AC values In C
************************************************************************************************/
private static void SubtractAverage(ref short pred_buf_q3, Av1TransformSize transformSize)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
int roundOffset = (width * height) >> 1;
int pelCountLog2 = transformSize.GetBlockWidthLog2() + transformSize.GetBlockHeightLog2();
int sum_q3 = 0;
ref short pred_buf = ref pred_buf_q3;
for (int j = 0; j < height; j++)
{
// assert(pred_buf_q3 + tx_width <= cfl->pred_buf_q3 + CFL_BUF_SQUARE);
for (int i = 0; i < width; i++)
{
sum_q3 += Unsafe.Add(ref pred_buf, i);
}
pred_buf += BufferLine;
}
int avg_q3 = (sum_q3 + roundOffset) >> pelCountLog2;
// Loss is never more than 1/2 (in Q3)
// assert(abs((avg_q3 * (1 << num_pel_log2)) - sum_q3) <= 1 << num_pel_log2 >>
// 1);
for (int j = 0; j < height; j++)
{
for (int i = 0; i < width; i++)
{
Unsafe.Add(ref pred_buf_q3, i) -= (short)avg_q3;
}
pred_buf_q3 += BufferLine;
}
}
}

26
src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaMath.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma;
internal static class Av1ChromaFromLumaMath
{
private const int Signs = 3;
private const int AlphabetSizeLog2 = 4;
public const int SignZero = 0;
public const int SignNegative = 1;
public const int SignPositive = 2;
public static int SignU(int jointSign) => ((jointSign + 1) * 11) >> 5;
public static int SignV(int jointSign) => (jointSign + 1) - (Signs * SignU(jointSign));
public static int IndexU(int index) => index >> AlphabetSizeLog2;
public static int IndexV(int index) => index & (AlphabetSizeLog2 - 1);
public static int ContextU(int jointSign) => jointSign + 1 - Signs;
public static int ContextV(int jointSign) => (SignV(jointSign) * Signs) + SignU(jointSign) - Signs;
}

1094
src/ImageSharp/Formats/Heif/Av1/Prediction/PredictionDecoder.cs

File diff suppressed because it is too large

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

@ -62,6 +62,8 @@ internal class Av1BlockModeInfo
public int[] TransformUnitsCount { get; internal set; }
public int GetPaletteSize(Av1Plane plane) => this.paletteSize[Math.Min(1, (int)plane)];
public int GetPaletteSize(Av1PlaneType planeType) => this.paletteSize[(int)planeType];
public void SetPaletteSizes(int ySize, int uvSize) => this.paletteSize = [ySize, uvSize];

3
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs

@ -9,5 +9,6 @@ internal enum Av1FilterIntraMode
Vertical,
Horizontal,
Directional157,
Paeth
Paeth,
FilterIntraModes,
}

63
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs

@ -2,16 +2,12 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
internal class Av1PartitionInfo
{
private int modeBlockToLeftEdge;
private int modeBlockToRightEdge;
private int modeBlockToTopEdge;
private int modeBlockToBottomEdge;
public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo, bool isChroma, Av1PartitionType partitionType)
{
this.ModeInfo = modeInfo;
@ -20,6 +16,8 @@ internal class Av1PartitionInfo
this.Type = partitionType;
this.CdefStrength = [];
this.ReferenceFrame = [-1, -1];
this.WidthInPixels = new int[3];
this.HeightInPixels = new int[3];
}
public Av1BlockModeInfo ModeInfo { get; }
@ -67,37 +65,70 @@ internal class Av1PartitionInfo
public Av1BlockModeInfo? LeftModeInfo { get; set; }
public Av1BlockModeInfo? AboveModeInfoForChroma { get; set; }
public Av1BlockModeInfo? LeftModeInfoForChroma { get; set; }
public int[][] CdefStrength { get; set; }
public int[] ReferenceFrame { get; set; }
public int ModeBlockToRightEdge => this.modeBlockToRightEdge;
public int ModeBlockToLeftEdge { get; private set; }
public int ModeBlockToRightEdge { get; private set; }
public int ModeBlockToTopEdge { get; private set; }
public int ModeBlockToBottomEdge => this.modeBlockToBottomEdge;
public int ModeBlockToBottomEdge { get; private set; }
public void ComputeBoundaryOffsets(ObuFrameHeader frameHeader, Av1TileInfo tileInfo)
public int[] WidthInPixels { get; private set; }
public int[] HeightInPixels { get; private set; }
public Av1ChromaFromLumaContext? ChromaFromLumaContext { get; internal set; }
public void ComputeBoundaryOffsets(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1TileInfo tileInfo)
{
Av1BlockSize blockSize = this.ModeInfo.BlockSize;
int bw4 = blockSize.Get4x4WideCount();
int bh4 = blockSize.Get4x4HighCount();
int subX = sequenceHeader.ColorConfig.SubSamplingX ? 1 : 0;
int subY = sequenceHeader.ColorConfig.SubSamplingY ? 1 : 0;
this.AvailableAbove = this.RowIndex > tileInfo.ModeInfoRowStart;
this.AvailableLeft = this.ColumnIndex > tileInfo.ModeInfoColumnStart;
this.AvailableAboveForChroma = this.AvailableAbove;
this.AvailableLeftForChroma = this.AvailableLeft;
int shift = Av1Constants.ModeInfoSizeLog2 + 3;
this.modeBlockToLeftEdge = -this.ColumnIndex << shift;
this.modeBlockToRightEdge = (frameHeader.ModeInfoColumnCount - bw4 - this.ColumnIndex) << shift;
this.modeBlockToTopEdge = -this.RowIndex << shift;
this.modeBlockToBottomEdge = (frameHeader.ModeInfoRowCount - bh4 - this.RowIndex) << shift;
this.ModeBlockToLeftEdge = -this.ColumnIndex << shift;
this.ModeBlockToRightEdge = (frameHeader.ModeInfoColumnCount - bw4 - this.ColumnIndex) << shift;
this.ModeBlockToTopEdge = -this.RowIndex << shift;
this.ModeBlockToBottomEdge = (frameHeader.ModeInfoRowCount - bh4 - this.RowIndex) << shift;
// Block Size width & height in pixels.
// For Luma bock
const int modeInfoSize = 1 << Av1Constants.ModeInfoSizeLog2;
this.WidthInPixels[0] = bw4 * modeInfoSize;
this.HeightInPixels[0] = bh4 * modeInfoSize;
// For U plane chroma bock
this.WidthInPixels[1] = Math.Max(1, bw4 >> subX) * modeInfoSize;
this.HeightInPixels[1] = Math.Max(1, bh4 >> subY) * modeInfoSize;
// For V plane chroma bock
this.WidthInPixels[2] = Math.Max(1, bw4 >> subX) * modeInfoSize;
this.HeightInPixels[2] = Math.Max(1, bh4 >> subY) * modeInfoSize;
this.ChromaFromLumaContext = new Av1ChromaFromLumaContext(configuration, sequenceHeader.ColorConfig);
}
public int GetMaxBlockWide(Av1BlockSize blockSize, bool subX)
{
int maxBlockWide = blockSize.GetWidth();
if (this.modeBlockToRightEdge < 0)
if (this.ModeBlockToRightEdge < 0)
{
int shift = subX ? 4 : 3;
maxBlockWide += this.modeBlockToRightEdge >> shift;
maxBlockWide += this.ModeBlockToRightEdge >> shift;
}
return maxBlockWide >> 2;
@ -106,10 +137,10 @@ internal class Av1PartitionInfo
public int GetMaxBlockHigh(Av1BlockSize blockSize, bool subY)
{
int maxBlockHigh = blockSize.GetHeight();
if (this.modeBlockToBottomEdge < 0)
if (this.ModeBlockToBottomEdge < 0)
{
int shift = subY ? 4 : 3;
maxBlockHigh += this.modeBlockToBottomEdge >> shift;
maxBlockHigh += this.ModeBlockToBottomEdge >> shift;
}
return maxBlockHigh >> 2;

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

@ -34,10 +34,12 @@ internal class Av1TileReader : IAv1TileReader
private readonly int[][] transformUnitCount;
private readonly int[] firstTransformOffset = new int[2];
private readonly int[] coefficientIndex = [];
private readonly Configuration configuration;
public Av1TileReader(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
this.FrameHeader = frameHeader;
this.configuration = configuration;
this.SequenceHeader = sequenceHeader;
// init_main_frame_ctxt
@ -284,6 +286,8 @@ internal class Av1TileReader : IAv1TileReader
int block4x4Width = blockSize.Get4x4WideCount();
int block4x4Height = blockSize.Get4x4HighCount();
int planesCount = this.SequenceHeader.ColorConfig.PlaneCount;
int subX = this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0;
int subY = this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0;
Point superblockLocation = superblockInfo.Position * this.SequenceHeader.SuperblockModeInfoSize;
Point locationInSuperblock = new Point(modeInfoLocation.X - superblockLocation.X, modeInfoLocation.Y - superblockLocation.Y);
Av1BlockModeInfo blockModeInfo = new(planesCount, blockSize, locationInSuperblock);
@ -295,7 +299,7 @@ internal class Av1TileReader : IAv1TileReader
partitionInfo.ColumnIndex = columnIndex;
partitionInfo.RowIndex = rowIndex;
superblockInfo.BlockCount++;
partitionInfo.ComputeBoundaryOffsets(this.FrameHeader, tileInfo);
partitionInfo.ComputeBoundaryOffsets(this.configuration, this.SequenceHeader, this.FrameHeader, tileInfo);
if (hasChroma)
{
if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1)
@ -319,6 +323,16 @@ internal class Av1TileReader : IAv1TileReader
partitionInfo.LeftModeInfo = superblockInfo.GetModeInfo(new Point(rowIndex, columnIndex - 1));
}
if (partitionInfo.AvailableAboveForChroma)
{
partitionInfo.AboveModeInfoForChroma = superblockInfo.GetModeInfo(new Point(rowIndex & ~subY, columnIndex | subX));
}
if (partitionInfo.AvailableLeftForChroma)
{
partitionInfo.LeftModeInfoForChroma = superblockInfo.GetModeInfo(new Point(rowIndex | subY, columnIndex & ~subX));
}
this.ReadModeInfo(ref reader, partitionInfo);
ReadPaletteTokens(ref reader, partitionInfo);
this.ReadBlockTransformSize(ref reader, modeInfoLocation, partitionInfo, superblockInfo, tileInfo);

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

@ -24,7 +24,7 @@ public class Av1TilingTests
IAv1TileReader stub = new Av1TileDecoderStub();
ObuReader obuReader = new();
obuReader.ReadAll(ref bitStreamReader, dataSize, stub);
Av1TileReader tileReader = new(obuReader.SequenceHeader, obuReader.FrameHeader);
Av1TileReader tileReader = new(Configuration.Default, obuReader.SequenceHeader, obuReader.FrameHeader);
// Act
tileReader.ReadTile(tileSpan, 0);

Loading…
Cancel
Save