Browse Source

Merge 19e44be16b into ad816ed988

pull/2633/merge
Ynse Hoornenborg 3 days ago
committed by GitHub
parent
commit
630bdc8ed7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 18
      ImageSharp.sln
  2. 53
      src/ImageSharp/Common/Helpers/DisposableDictionary.cs
  3. 51
      src/ImageSharp/Common/Helpers/DisposableList.cs
  4. 3
      src/ImageSharp/Configuration.cs
  5. 11
      src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs
  6. 11
      src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs
  7. 157
      src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs
  8. 183
      src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs
  9. 79
      src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs
  10. 146
      src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs
  11. 63
      src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs
  12. 12
      src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs
  13. 205
      src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs
  14. 70
      src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
  15. 275
      src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs
  16. 169
      src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
  17. 152
      src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs
  18. 104
      src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs
  19. 11
      src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs
  20. 169
      src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs
  21. 2269
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs
  22. 129
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs
  23. 413
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs
  24. 444
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
  25. 690
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
  26. 417
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
  27. 207
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolReader.cs
  28. 216
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs
  29. 19
      src/ImageSharp/Formats/Heif/Av1/IAv1TileReader.cs
  30. 19
      src/ImageSharp/Formats/Heif/Av1/IAv1TileWriter.cs
  31. 111
      src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometry.cs
  32. 989
      src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometryFactory.cs
  33. 16
      src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1GeometryIndex.cs
  34. 22
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs
  35. 67
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs
  36. 21
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs
  37. 15
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs
  38. 29
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDecoderModelInfo.cs
  39. 13
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs
  40. 172
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs
  41. 98
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs
  42. 19
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs
  43. 12
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs
  44. 21
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs
  45. 29
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs
  46. 11
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationItem.cs
  47. 25
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs
  48. 22
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs
  49. 13
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs
  50. 29
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs
  51. 15
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs
  52. 21
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs
  53. 1821
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
  54. 10
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.cs
  55. 12
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs
  56. 16
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs
  57. 41
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs
  58. 95
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs
  59. 11
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs
  60. 11
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSkipModeParameters.cs
  61. 39
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs
  62. 33
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTimingInfo.cs
  63. 25
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs
  64. 18
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuType.cs
  65. 862
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs
  66. 171
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs
  67. 74
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs
  68. 20
      src/ImageSharp/Formats/Heif/Av1/Pipeline/IAv1FrameDecoder.cs
  69. 8
      src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs
  70. 68
      src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs
  71. 49
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs
  72. 6803
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizationLookup.cs
  73. 117
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs
  74. 190
      src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs
  75. 505
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1BottomRightTopLeftConstants.cs
  76. 41
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs
  77. 49
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs
  78. 58
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs
  79. 49
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs
  80. 79
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone1Predictor.cs
  81. 79
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone2Predictor.cs
  82. 78
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone3Predictor.cs
  83. 47
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HorizontalPredictor.cs
  84. 15
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1NeighborNeed.cs
  85. 59
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PaethPredictor.cs
  86. 1090
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
  87. 28
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs
  88. 173
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs
  89. 67
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs
  90. 63
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothHorizontalPredictor.cs
  91. 101
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothPredictor.cs
  92. 62
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothVerticalPredictor.cs
  93. 47
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1VerticalPredictor.cs
  94. 120
      src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaContext.cs
  95. 26
      src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaMath.cs
  96. 19
      src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs
  97. 73
      src/ImageSharp/Formats/Heif/Av1/Readme.md
  98. 70
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs
  99. 14
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs
  100. 31
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs

18
ImageSharp.sln

@ -37,8 +37,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{815C0625-CD3
ProjectSection(SolutionItems) = preProject
src\Directory.Build.props = src\Directory.Build.props
src\Directory.Build.targets = src\Directory.Build.targets
src\README.md = src\README.md
src\ImageSharp.ruleset = src\ImageSharp.ruleset
src\README.md = src\README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageSharp\ImageSharp.csproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}"
@ -215,6 +215,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{5C9B68
ProjectSection(SolutionItems) = preProject
tests\Images\Input\Jpg\issues\issue-1076-invalid-subsampling.jpg = tests\Images\Input\Jpg\issues\issue-1076-invalid-subsampling.jpg
tests\Images\Input\Jpg\issues\issue-1221-identify-multi-frame.jpg = tests\Images\Input\Jpg\issues\issue-1221-identify-multi-frame.jpg
tests\Images\Input\Jpg\issues\issue-2067-comment.jpg = tests\Images\Input\Jpg\issues\issue-2067-comment.jpg
tests\Images\Input\Jpg\issues\issue1006-incorrect-resize.jpg = tests\Images\Input\Jpg\issues\issue1006-incorrect-resize.jpg
tests\Images\Input\Jpg\issues\issue1049-exif-resize.jpg = tests\Images\Input\Jpg\issues\issue1049-exif-resize.jpg
tests\Images\Input\Jpg\issues\Issue159-MissingFF00-Progressive-Bedroom.jpg = tests\Images\Input\Jpg\issues\Issue159-MissingFF00-Progressive-Bedroom.jpg
@ -238,7 +239,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{5C9B68
tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg = tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg
tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg = tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg
tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg = tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg
tests\Images\Input\Jpg\issues\issue-2067-comment.jpg = tests\Images\Input\Jpg\issues\issue-2067-comment.jpg
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fuzz", "fuzz", "{516A3532-6AC2-417B-AD79-9BD5D0D378A0}"
@ -661,6 +661,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Qoi", "Qoi", "{E801B508-493
tests\Images\Input\Qoi\wikipedia_008.qoi = tests\Images\Input\Qoi\wikipedia_008.qoi
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Heif", "Heif", "{BA5D603A-C84C-43E5-B300-8BB886B02936}"
ProjectSection(SolutionItems) = preProject
tests\Images\Input\Heif\dwsample-heic-640.heic = tests\Images\Input\Heif\dwsample-heic-640.heic
tests\Images\Input\Heif\image1.heic = tests\Images\Input\Heif\image1.heic
tests\Images\Input\Heif\image2.heic = tests\Images\Input\Heif\image2.heic
tests\Images\Input\Heif\image3.heic = tests\Images\Input\Heif\image3.heic
tests\Images\Input\Heif\image4.heic = tests\Images\Input\Heif\image4.heic
tests\Images\Input\Heif\IMG-20230508-0053.hif = tests\Images\Input\Heif\IMG-20230508-0053.hif
tests\Images\Input\Heif\Irvine_CA.avif = tests\Images\Input\Heif\Irvine_CA.avif
tests\Images\Input\Heif\jpeg444_xnconvert.avif = tests\Images\Input\Heif\jpeg444_xnconvert.avif
tests\Images\Input\Heif\Orange4x4.avif = tests\Images\Input\Heif\Orange4x4.avif
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icon", "Icon", "{95E45DDE-A67D-48AD-BBA8-5FAA151B860D}"
ProjectSection(SolutionItems) = preProject
tests\Images\Input\Icon\aero_arrow.cur = tests\Images\Input\Icon\aero_arrow.cur
@ -720,6 +733,7 @@ Global
{670DD46C-82E9-499A-B2D2-00A802ED0141} = {E1C42A6F-913B-4A7B-B1A8-2BB62843B254}
{5DFC394F-136F-4B76-9BCA-3BA786515EFC} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{E801B508-4935-41CD-BA85-CF11BFF55A45} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{BA5D603A-C84C-43E5-B300-8BB886B02936} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{95E45DDE-A67D-48AD-BBA8-5FAA151B860D} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution

53
src/ImageSharp/Common/Helpers/DisposableDictionary.cs

@ -0,0 +1,53 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Common.Helpers;
/// <summary>
/// Dictionary of <see cref="IDisposable"/> objects, which is itself <see cref="IDisposable"/>.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TValue">Tye type of value, needs to implement <see cref="IDisposable"/>.</typeparam>
public sealed class DisposableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IDisposable
where TKey : notnull
where TValue : IDisposable
{
private bool disposedValue;
/// <inheritdoc />
public DisposableDictionary()
: base()
{
}
/// <inheritdoc />
public DisposableDictionary(int capacity)
: base(capacity)
{
}
/// <inheritdoc />
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
this.Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
foreach (KeyValuePair<TKey, TValue> pair in this)
{
pair.Value?.Dispose();
}
}
this.Clear();
this.disposedValue = true;
}
}
}

51
src/ImageSharp/Common/Helpers/DisposableList.cs

@ -0,0 +1,51 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Common.Helpers;
/// <summary>
/// List of <see cref="IDisposable"/> objects, which is itself <see cref="IDisposable"/>.
/// </summary>
/// <typeparam name="TValue">Tye type of value, needs to implement <see cref="IDisposable"/>.</typeparam>
public sealed class DisposableList<TValue> : List<TValue>, IDisposable
where TValue : IDisposable
{
private bool disposedValue;
/// <inheritdoc />
public DisposableList()
: base()
{
}
/// <inheritdoc />
public DisposableList(int capacity)
: base(capacity)
{
}
/// <inheritdoc />
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
this.Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
foreach (TValue item in this)
{
item?.Dispose();
}
}
this.Clear();
this.disposedValue = true;
}
}
}

3
src/ImageSharp/Configuration.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Cur;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Heif;
using SixLabors.ImageSharp.Formats.Ico;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm;
@ -213,6 +214,7 @@ public sealed class Configuration
/// <see cref="TiffConfigurationModule"/>.
/// <see cref="WebpConfigurationModule"/>.
/// <see cref="QoiConfigurationModule"/>.
/// <see cref="HeifConfigurationModule"/>.
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/>.</returns>
internal static Configuration CreateDefaultInstance() => new(
@ -225,6 +227,7 @@ public sealed class Configuration
new TiffConfigurationModule(),
new WebpConfigurationModule(),
new QoiConfigurationModule(),
new HeifConfigurationModule(),
new IcoConfigurationModule(),
new CurConfigurationModule());
}

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

@ -0,0 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal enum Av1BitDepth : int
{
EightBit = 0,
TenBit = 1,
TwelveBit = 2,
}

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

@ -0,0 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal static class Av1BitDepthExtensions
{
public static int GetBitCount(this Av1BitDepth bitDepth) => 8 + ((int)bitDepth << 1);
}

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

@ -0,0 +1,157 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal ref struct Av1BitStreamReader
{
private readonly Span<byte> data;
public Av1BitStreamReader(Span<byte> data) => this.data = data;
public int BitPosition { get; private set; } = 0;
/// <summary>
/// Gets the number of bytes in the readers buffer.
/// </summary>
public readonly int Length => this.data.Length;
public void Reset() => this.BitPosition = 0;
public void Skip(int bitCount) => this.BitPosition += bitCount;
public uint ReadLiteral(int bitCount)
{
DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount));
uint literal = 0;
for (int bit = bitCount - 1; bit >= 0; bit--)
{
literal |= this.ReadBit() << bit;
}
return literal;
}
internal uint ReadBit()
{
int byteOffset = Av1Math.DivideBy8Floor(this.BitPosition);
byte shift = (byte)(7 - Av1Math.Modulus8(this.BitPosition));
this.BitPosition++;
return (uint)((this.data[byteOffset] >> shift) & 0x01);
}
internal bool ReadBoolean() => this.ReadLiteral(1) > 0;
public ulong ReadLittleEndianBytes128(out int length)
{
// See section 4.10.5 of the AV1-Specification
DebugGuard.IsTrue((this.BitPosition & 0x07) == 0, $"Reading of Little Endian 128 value only allowed on byte alignment (offset {this.BitPosition}).");
ulong value = 0;
length = 0;
for (int i = 0; i < 56; i += 7)
{
uint leb128Byte = this.ReadLiteral(8);
value |= (leb128Byte & 0x7FUL) << i;
length++;
if ((leb128Byte & 0x80U) == 0)
{
break;
}
}
return value;
}
public uint ReadUnsignedVariableLength()
{
// See section 4.10.3 of the AV1-Specification
int leadingZerosCount = 0;
while (leadingZerosCount < 32)
{
uint bit = this.ReadLiteral(1);
if (bit == 1)
{
break;
}
leadingZerosCount++;
}
if (leadingZerosCount == 32)
{
return uint.MaxValue;
}
if (leadingZerosCount != 0)
{
uint basis = (1U << leadingZerosCount) - 1U;
uint value = this.ReadLiteral(leadingZerosCount);
return basis + value;
}
return 0;
}
public uint ReadNonSymmetric(uint n)
{
// See section 4.10.7 of the AV1-Specification
if (n <= 1)
{
return 0;
}
int w = (int)(Av1Math.FloorLog2(n) + 1);
uint m = (uint)((1 << w) - n);
uint v = this.ReadLiteral(w - 1);
if (v < m)
{
return v;
}
return (v << 1) - m + this.ReadLiteral(1);
}
public int ReadSignedFromUnsigned(int n)
{
// See section 4.10.6 of the AV1-Specification
int signedValue;
uint value = this.ReadLiteral(n);
uint signMask = 1U << (n - 1);
if ((value & signMask) == signMask)
{
// Prevent overflow by casting to long;
signedValue = (int)((long)value - (signMask << 1));
}
else
{
signedValue = (int)value;
}
return signedValue;
}
public uint ReadLittleEndian(int n)
{
// See section 4.10.4 of the AV1-Specification
DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Reading of Little Endian value only allowed on byte alignment");
uint t = 0;
for (int i = 0; i < 8 * n; i += 8)
{
t += this.ReadLiteral(8) << i;
}
return t;
}
public Span<byte> GetSymbolReader(int tileDataSize)
{
DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Symbol reading needs to start on byte boundary.");
int bytesRead = Av1Math.DivideBy8Floor(this.BitPosition);
Span<byte> span = this.data.Slice(bytesRead, tileDataSize);
this.Skip(tileDataSize << 3);
return span;
}
}

183
src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs

@ -0,0 +1,183 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal ref struct Av1BitStreamWriter
{
private const int WordSize = 8;
private readonly AutoExpandingMemory<byte> memory;
private Span<byte> span;
private int capacityTrigger;
private byte buffer = 0;
public Av1BitStreamWriter(AutoExpandingMemory<byte> memory)
{
this.memory = memory;
this.span = memory.GetEntireSpan();
this.capacityTrigger = memory.Capacity - 1;
}
public int BitPosition { get; private set; } = 0;
public readonly int Capacity => this.memory.Capacity;
public static int GetLittleEndianBytes128(uint value, Span<byte> span)
{
if (value < 0x80U)
{
span[0] = (byte)value;
return 1;
}
else if (value < 0x8000U)
{
span[0] = (byte)((value & 0x7fU) | 0x80U);
span[1] = (byte)((value >> 7) & 0xff);
return 2;
}
else if (value < 0x800000U)
{
span[0] = (byte)((value & 0x7fU) | 0x80U);
span[1] = (byte)((value >> 7) & 0xff);
span[2] = (byte)((value >> 14) & 0xff);
return 3;
}
else
{
throw new NotImplementedException("No such large values yet.");
}
}
public void Skip(int bitCount)
{
this.BitPosition += bitCount;
while (this.BitPosition >= WordSize)
{
this.BitPosition -= WordSize;
this.WriteBuffer();
}
}
public void Flush()
{
if (Av1Math.Modulus8(this.BitPosition) != 0)
{
// Flush a partial byte also.
this.WriteBuffer();
}
this.BitPosition = 0;
}
public void WriteLiteral(uint value, int bitCount)
{
for (int bit = bitCount - 1; bit >= 0; bit--)
{
this.WriteBit((byte)((value >> bit) & 0x1));
}
}
internal void WriteBoolean(bool value)
{
byte boolByte = value ? (byte)1 : (byte)0;
this.WriteBit(boolByte);
}
public void WriteSignedFromUnsigned(int signedValue, int n)
{
// See section 4.10.6 of the AV1-Specification
ulong value = (ulong)signedValue;
if (signedValue < 0)
{
value += 1UL << n;
}
this.WriteLiteral((uint)value, n);
}
public void WriteLittleEndianBytes128(uint value)
{
int bytesWritten = GetLittleEndianBytes128(value, this.span.Slice(this.BitPosition >> 3));
this.BitPosition += bytesWritten << 3;
}
internal void WriteNonSymmetric(uint value, uint numberOfSymbols)
{
// See section 4.10.7 of the AV1-Specification
if (numberOfSymbols <= 1)
{
return;
}
int w = (int)(Av1Math.FloorLog2(numberOfSymbols) + 1);
uint m = (uint)((1 << w) - numberOfSymbols);
if (value < m)
{
this.WriteLiteral(value, w - 1);
}
else
{
uint extraBit = ((value + m) >> 1) - value;
uint k = (value + m - extraBit) >> 1;
this.WriteLiteral(k, w - 1);
this.WriteLiteral(extraBit, 1);
}
}
private void WriteBit(byte value)
{
int bit = this.BitPosition & 0x07;
this.buffer = (byte)(((value << (7 - bit)) & 0xff) | this.buffer);
if (bit == 7)
{
this.WriteBuffer();
}
this.BitPosition++;
}
public void WriteLittleEndian(uint value, int n)
{
// See section 4.10.4 of the AV1-Specification
DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Writing of Little Endian value only allowed on byte alignment");
uint t = value;
for (int i = 0; i < n; i++)
{
this.WriteLiteral(t & 0xff, 8);
t >>= 8;
}
}
internal void WriteBlob(Span<byte> tileData)
{
DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Writing of Tile Data only allowed on byte alignment");
int wordPosition = this.BitPosition >> 3;
if (this.span.Length <= wordPosition + tileData.Length)
{
this.memory.GetSpan(wordPosition + tileData.Length);
this.span = this.memory.GetEntireSpan();
}
tileData.CopyTo(this.span[wordPosition..]);
this.BitPosition += tileData.Length << 3;
}
private void WriteBuffer()
{
int wordPosition = Av1Math.DivideBy8Floor(this.BitPosition);
if (wordPosition > this.capacityTrigger)
{
// Expand the memory allocation.
this.memory.GetSpan(wordPosition + 1);
this.span = this.memory.GetEntireSpan();
this.capacityTrigger = this.span.Length - 1;
}
this.span[wordPosition] = this.buffer;
this.buffer = 0;
}
}

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

@ -0,0 +1,79 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal enum Av1BlockSize : byte
{
// 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,
AllSizes = 22,
SizeS = Block4x16,
Invalid = 255,
Largest = SizeS - 1,
}

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

@ -0,0 +1,146 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
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];
// The Subsampled_Size table in the spec (Section 5.11.38. Get plane residual size function).
private static readonly Av1BlockSize[][][] SubSampled =
[
// ss_x == 0 ss_x == 0 ss_x == 1 ss_x == 1
// ss_y == 0 ss_y == 1 ss_y == 0 ss_y == 1
[[Av1BlockSize.Block4x4, Av1BlockSize.Block4x4], [Av1BlockSize.Block4x4, Av1BlockSize.Block4x4]],
[[Av1BlockSize.Block4x8, Av1BlockSize.Block4x4], [Av1BlockSize.Invalid, Av1BlockSize.Block4x4]],
[[Av1BlockSize.Block8x4, Av1BlockSize.Invalid], [Av1BlockSize.Block4x4, Av1BlockSize.Block4x4]],
[[Av1BlockSize.Block8x8, Av1BlockSize.Block8x4], [Av1BlockSize.Block4x8, Av1BlockSize.Block4x4]],
[[Av1BlockSize.Block8x16, Av1BlockSize.Block8x8], [Av1BlockSize.Invalid, Av1BlockSize.Block4x8]],
[[Av1BlockSize.Block16x8, Av1BlockSize.Invalid], [Av1BlockSize.Block8x8, Av1BlockSize.Block8x4]],
[[Av1BlockSize.Block16x16, Av1BlockSize.Block16x8], [Av1BlockSize.Block8x16, Av1BlockSize.Block8x8]],
[[Av1BlockSize.Block16x32, Av1BlockSize.Block16x16], [Av1BlockSize.Invalid, Av1BlockSize.Block8x16]],
[[Av1BlockSize.Block32x16, Av1BlockSize.Invalid], [Av1BlockSize.Block16x16, Av1BlockSize.Block16x8]],
[[Av1BlockSize.Block32x32, Av1BlockSize.Block32x16], [Av1BlockSize.Block16x32, Av1BlockSize.Block16x16]],
[[Av1BlockSize.Block32x64, Av1BlockSize.Block32x32], [Av1BlockSize.Invalid, Av1BlockSize.Block16x32]],
[[Av1BlockSize.Block64x32, Av1BlockSize.Invalid], [Av1BlockSize.Block32x32, Av1BlockSize.Block32x16]],
[[Av1BlockSize.Block64x64, Av1BlockSize.Block64x32], [Av1BlockSize.Block32x64, Av1BlockSize.Block32x32]],
[[Av1BlockSize.Block64x128, Av1BlockSize.Block64x64], [Av1BlockSize.Invalid, Av1BlockSize.Block32x64]],
[[Av1BlockSize.Block128x64, Av1BlockSize.Invalid], [Av1BlockSize.Block64x64, Av1BlockSize.Block64x32]],
[[Av1BlockSize.Block128x128, Av1BlockSize.Block128x64], [Av1BlockSize.Block64x128, Av1BlockSize.Block64x64]],
[[Av1BlockSize.Block4x16, Av1BlockSize.Block4x8], [Av1BlockSize.Invalid, Av1BlockSize.Block4x8]],
[[Av1BlockSize.Block16x4, Av1BlockSize.Invalid], [Av1BlockSize.Block8x4, Av1BlockSize.Block8x4]],
[[Av1BlockSize.Block8x32, Av1BlockSize.Block8x16], [Av1BlockSize.Invalid, Av1BlockSize.Block4x16]],
[[Av1BlockSize.Block32x8, Av1BlockSize.Invalid], [Av1BlockSize.Block16x8, Av1BlockSize.Block16x4]],
[[Av1BlockSize.Block16x64, Av1BlockSize.Block16x32], [Av1BlockSize.Invalid, Av1BlockSize.Block8x32]],
[[Av1BlockSize.Block64x16, Av1BlockSize.Invalid], [Av1BlockSize.Block32x16, Av1BlockSize.Block32x8]]
];
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
];
private static readonly int[] PelsLog2Count =
[4, 5, 5, 6, 7, 7, 8, 9, 9, 10, 11, 11, 12, 13, 13, 14, 6, 6, 8, 8, 10, 10];
private static readonly Av1BlockSize[][] HeightWidthToSize = [
[Av1BlockSize.Block4x4, Av1BlockSize.Block4x8, Av1BlockSize.Block4x16, Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid],
[Av1BlockSize.Block8x4, Av1BlockSize.Block8x8, Av1BlockSize.Block8x16, Av1BlockSize.Block8x32, Av1BlockSize.Invalid, Av1BlockSize.Invalid],
[Av1BlockSize.Block16x4, Av1BlockSize.Block16x8, Av1BlockSize.Block16x16, Av1BlockSize.Block16x32, Av1BlockSize.Block16x64, Av1BlockSize.Invalid],
[Av1BlockSize.Invalid, Av1BlockSize.Block32x8, Av1BlockSize.Block32x16, Av1BlockSize.Block32x32, Av1BlockSize.Block32x64, Av1BlockSize.Invalid],
[Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x16, Av1BlockSize.Block64x32, Av1BlockSize.Block64x64, Av1BlockSize.Block64x128],
[Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64, Av1BlockSize.Block128x128]
];
public static int Get4x4WideCount(this Av1BlockSize blockSize) => SizeWide[(int)blockSize];
public static int Get4x4HighCount(this Av1BlockSize blockSize) => SizeHigh[(int)blockSize];
/// <summary>
/// Gets the <see cref="Av1BlockSize"/> given by the Log2 of the width and height.
/// </summary>
/// <param name="widthLog2">Log2 of the width value.</param>
/// <param name="heightLog2">Log2 of the height value.</param>
/// <returns>The <see cref="Av1BlockSize"/>.</returns>
public static Av1BlockSize FromWidthAndHeight(uint widthLog2, uint heightLog2) => HeightWidthToSize[heightLog2][widthLog2];
/// <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)
=> Av1Math.Log2(Get4x4WideCount(blockSize));
/// <summary>
/// Returns base 2 logarithm of the height of the block in units of 4 samples.
/// </summary>
public static int Get4x4HeightLog2(this Av1BlockSize blockSize)
=> Av1Math.Log2(Get4x4HighCount(blockSize));
/// <summary>
/// Returns the block size of a sub sampled block.
/// </summary>
public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, bool subX, bool subY)
=> GetSubsampled(blockSize, subX ? 1 : 0, subY ? 1 : 0);
/// <summary>
/// Returns the block size of a sub sampled block.
/// </summary>
public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, int subX, int subY)
{
if (blockSize == Av1BlockSize.Invalid)
{
return Av1BlockSize.Invalid;
}
return SubSampled[(int)blockSize][subX][subY];
}
public static Av1TransformSize GetMaxUvTransformSize(this Av1BlockSize blockSize, bool subX, bool subY)
{
Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY);
Av1TransformSize uvTransformSize = Av1TransformSize.Invalid;
if (planeBlockSize < Av1BlockSize.AllSizes)
{
uvTransformSize = planeBlockSize.GetMaximumTransformSize();
}
return uvTransformSize switch
{
Av1TransformSize.Size64x64 or Av1TransformSize.Size64x32 or Av1TransformSize.Size32x64 => Av1TransformSize.Size32x32,
Av1TransformSize.Size64x16 => Av1TransformSize.Size32x16,
Av1TransformSize.Size16x64 => Av1TransformSize.Size16x32,
_ => uvTransformSize,
};
}
/// <summary>
/// Returns the 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];
public static int GetPelsLog2Count(this Av1BlockSize blockSize)
=> PelsLog2Count[(int)blockSize];
}

63
src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs

@ -0,0 +1,63 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Implementation of section 2.3.3 of AV1 Codec ISO Media File Format Binding specification v1.2.0.
/// See https://aomediacodec.github.io/av1-isobmff/v1.2.0.html#av1codecconfigurationbox-syntax.
/// </summary>
internal struct Av1CodecConfiguration
{
public Av1CodecConfiguration(Span<byte> boxBuffer)
{
Av1BitStreamReader reader = new(boxBuffer);
this.Marker = (byte)reader.ReadLiteral(1);
this.Version = (byte)reader.ReadLiteral(7);
this.SeqProfile = (byte)reader.ReadLiteral(3);
this.SeqLevelIdx0 = (byte)reader.ReadLiteral(5);
this.SeqTier0 = (byte)reader.ReadLiteral(1);
this.HighBitdepth = (byte)reader.ReadLiteral(1);
this.TwelveBit = reader.ReadLiteral(1) == 1;
this.MonoChrome = reader.ReadLiteral(1) == 1;
this.ChromaSubsamplingX = reader.ReadLiteral(1) == 1;
this.ChromaSubsamplingY = reader.ReadLiteral(1) == 1;
this.ChromaSamplePosition = (byte)reader.ReadLiteral(2);
// 3 bits are reserved.
reader.ReadLiteral(3);
this.InitialPresentationDelayPresent = reader.ReadLiteral(1) == 1;
if (this.InitialPresentationDelayPresent)
{
byte initialPresentationDelayMinusOne = (byte)reader.ReadLiteral(4);
this.InitialPresentationDelay = (byte)(initialPresentationDelayMinusOne + 1);
}
}
public byte Marker { get; }
public byte Version { get; }
public byte SeqProfile { get; }
public byte SeqLevelIdx0 { get; }
public byte SeqTier0 { get; }
public byte HighBitdepth { get; }
public bool TwelveBit { get; }
public bool MonoChrome { get; }
public bool ChromaSubsamplingX { get; }
public bool ChromaSubsamplingY { get; }
public byte ChromaSamplePosition { get; }
public bool InitialPresentationDelayPresent { get; }
public byte InitialPresentationDelay { get; }
}

12
src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs

@ -0,0 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal enum Av1ColorFormat
{
Yuv400,
Yuv420,
Yuv422,
Yuv444,
}

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

@ -0,0 +1,205 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal static class Av1Constants
{
public const ObuSequenceProfile MaxSequenceProfile = ObuSequenceProfile.Professional;
public const int LevelBits = 5;
/// <summary>
/// Number of fractional bits for computing position in upscaling.
/// </summary>
public const int SuperResolutionScaleBits = 14;
public const int ScaleNumerator = -1;
/// <summary>
/// Number of reference frames that can be used for inter prediction.
/// </summary>
public const int ReferencesPerFrame = 7;
/// <summary>
/// Maximum area of a tile in units of luma samples.
/// </summary>
public const int MaxTileArea = 4096 * 2304;
/// <summary>
/// Maximum width of a tile in units of luma samples.
/// </summary>
public const int MaxTileWidth = 4096;
/// <summary>
/// Maximum number of tile columns.
/// </summary>
public const int MaxTileColumnCount = 64;
/// <summary>
/// Maximum number of tile rows.
/// </summary>
public const int MaxTileRowCount = 64;
/// <summary>
/// Number of frames that can be stored for future reference.
/// </summary>
public const int ReferenceFrameCount = 8;
/// <summary>
/// Value of 'PrimaryReferenceFrame' indicating that there is no primary reference frame.
/// </summary>
public const uint PrimaryReferenceFrameNone = 7;
public const int PimaryReferenceBits = 3;
/// <summary>
/// Number of segments allowed in segmentation map.
/// </summary>
public const int MaxSegmentCount = 8;
/// <summary>
/// Smallest denominator for upscaling ratio.
/// </summary>
public const int SuperResolutionScaleDenominatorMinimum = 9;
/// <summary>
/// Base 2 logarithm of maximum size of a superblock in luma samples.
/// </summary>
public const int MaxSuperBlockSizeLog2 = 7;
/// <summary>
/// Base 2 logarithm of smallest size of a mode info block.
/// </summary>
public const int ModeInfoSizeLog2 = 2;
public const int MaxQ = 255;
/// <summary>
/// Number of segmentation features.
/// </summary>
public const int SegmentationLevelMax = 8;
/// <summary>
/// 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;
/// <summary>
/// Maximum number of color planes.
/// </summary>
public const int MaxPlanes = 3;
/// <summary>
/// Number of reference frame types (including intra type).
/// </summary>
public const int TotalReferencesPerFrame = 8;
/// <summary>
/// Number of values for palette_size.
/// </summary>
public const int PaletteMaxSize = 8;
/// <summary>
/// Maximum transform size categories.
/// </summary>
public const int MaxTransformCategories = 4;
public const int CoefficientContextCount = 6;
public const int BaseLevelsCount = 2;
public const int CoefficientBaseRange = 12;
public const int MaxTransformSize = 1 << 6;
public const int MaxTransformSizeUnit = MaxTransformSize >> 2;
public const int CoefficientContextBitCount = 6;
public const int CoefficientContextMask = (1 << CoefficientContextBitCount) - 1;
public const int TransformPadHorizontalLog2 = 2;
public const int TransformPadHorizontal = 1 << TransformPadHorizontalLog2;
public const int TransformPadVertical = 6;
public const int TransformPadEnd = 16;
public const int TransformPad2d = ((MaxTransformSize + TransformPadHorizontal) * (MaxTransformSize + TransformPadVertical)) + TransformPadEnd;
public const int TransformPadTop = 2;
public const int TransformPadBottom = 4;
public const int BaseRangeSizeMinus1 = 3;
public const int MaxBaseRange = 15;
/// <summary>
/// Log2 of number of values for ChromaFromLuma Alpha U and ChromaFromLuma Alpha V.
/// </summary>
public const int ChromaFromLumaAlphabetSizeLog2 = 4;
/// <summary>
/// Total number of Quantification Matrices sets stored.
/// </summary>
public const int QuantificationMatrixLevelCount = 1 << 4;
public const int AngleStep = 3;
/// <summary>
/// Maximum number of stages in a 1-dimensioanl transform function.
/// </summary>
public const int MaxTransformStageNumber = 12;
public const int PartitionProbabilitySet = 4;
// Number of transform sizes that use extended transforms.
public const int ExtendedTransformCount = 4;
public const int MaxVarTransform = 2;
/// <summary>
/// Maximum number of transform blocks per depth
/// </summary>
public const int MaxTransformBlockCount = 16;
/// <summary>
/// Number of items in the <see cref="Av1PlaneType"/> enumeration.
/// </summary>
public const int PlaneTypeCount = 2;
public const int MaxTransformUnitCount = 16;
}

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

@ -0,0 +1,70 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.PixelFormats.Utils;
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(Configuration configuration)
{
this.configuration = configuration;
this.obuReader = new();
}
public ObuFrameHeader? FrameHeader { get; private set; }
public ObuSequenceHeader? SequenceHeader { get; private set; }
public Av1FrameInfo? FrameInfo { get; private set; }
public Av1FrameBuffer<byte>? FrameBuffer { get; private set; }
public Image<TPixel> Decode<TPixel>(Span<byte> buffer)
where TPixel : unmanaged, IPixel<TPixel>
{
Av1BitStreamReader reader = new(buffer);
this.obuReader.ReadAll(ref reader, buffer.Length, () => this, false);
Guard.NotNull(this.tileReader, nameof(this.tileReader));
Guard.NotNull(this.SequenceHeader, nameof(this.SequenceHeader));
Guard.NotNull(this.FrameHeader, nameof(this.FrameHeader));
this.FrameInfo = this.tileReader.FrameInfo;
this.FrameBuffer = new(this.configuration, this.SequenceHeader, this.SequenceHeader.ColorConfig.GetColorFormat(), false);
this.frameDecoder = new(this.SequenceHeader, this.FrameHeader, this.FrameInfo, this.FrameBuffer);
this.frameDecoder.DecodeFrame();
Image<TPixel> resultImage = new(this.FrameHeader.FrameSize.FrameWidth, this.FrameHeader.FrameSize.FrameHeight);
ImageFrame<TPixel> resultFrame = resultImage.Frames.RootFrame;
Av1YuvConverter.ConvertToRgb(this.configuration, this.FrameBuffer, resultFrame);
return resultImage;
}
public void ReadTile(Span<byte> tileData, int tileNum)
{
if (this.tileReader == null)
{
this.SequenceHeader = this.obuReader.SequenceHeader;
this.FrameHeader = this.obuReader.FrameHeader;
Guard.NotNull(this.tileReader, nameof(this.tileReader));
Guard.NotNull(this.SequenceHeader, nameof(this.SequenceHeader));
Guard.NotNull(this.FrameHeader, nameof(this.FrameHeader));
this.FrameInfo = new(this.SequenceHeader);
this.FrameBuffer = new(this.configuration, this.SequenceHeader, this.SequenceHeader.ColorConfig.GetColorFormat(), false);
this.frameDecoder = new(this.SequenceHeader, this.FrameHeader, this.FrameInfo, this.FrameBuffer);
this.tileReader = new Av1TileReader(this.configuration, this.SequenceHeader, this.FrameHeader, this.frameDecoder);
}
this.tileReader.ReadTile(tileData, tileNum);
}
}

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

@ -0,0 +1,275 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Buffer for the pixels of a single frame.
/// </summary>
internal class Av1FrameBuffer<T> : IDisposable
where T : unmanaged
{
private const int DecoderPaddingValue = 72;
private const int PictureBufferYFlag = 1 << 0;
private const int PictureBufferCbFlag = 1 << 1;
private const int PictureBufferCrFlag = 1 << 2;
private const int PictureBufferLumaMask = PictureBufferYFlag;
private const int PictureBufferFullMask = PictureBufferYFlag | PictureBufferCbFlag | PictureBufferCrFlag;
public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHeader, Av1ColorFormat maxColorFormat, bool is16BitPipeline)
{
Av1ColorFormat colorFormat = sequenceHeader.ColorConfig.IsMonochrome ? Av1ColorFormat.Yuv400 : maxColorFormat;
this.MaxWidth = sequenceHeader.MaxFrameWidth;
this.MaxHeight = sequenceHeader.MaxFrameHeight;
this.BitDepth = sequenceHeader.ColorConfig.BitDepth;
int bitsPerPixel = this.BitDepth > Av1BitDepth.EightBit || is16BitPipeline ? 2 : 1;
this.ColorFormat = colorFormat;
this.BufferEnableMask = sequenceHeader.ColorConfig.IsMonochrome ? PictureBufferLumaMask : PictureBufferFullMask;
int leftPadding = DecoderPaddingValue;
int rightPadding = DecoderPaddingValue;
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;
int heightY = this.MaxHeight + topPadding + bottomPadding;
this.OriginX = leftPadding;
this.OriginY = topPadding;
this.OriginOriginY = bottomPadding;
int strideChroma = 0;
int heightChroma = 0;
switch (this.ColorFormat)
{
case Av1ColorFormat.Yuv420:
strideChroma = (strideY + 1) >> 1;
heightChroma = (heightY + 1) >> 1;
break;
case Av1ColorFormat.Yuv422:
strideChroma = (strideY + 1) >> 1;
heightChroma = heightY;
break;
case Av1ColorFormat.Yuv444:
strideChroma = strideY;
heightChroma = heightY;
break;
}
this.PackedFlag = false;
this.BufferY = null;
this.BufferCb = null;
this.BufferCr = null;
if ((this.BufferEnableMask & PictureBufferYFlag) != 0)
{
this.BufferY = configuration.MemoryAllocator.Allocate2D<T>(strideY * bitsPerPixel, heightY);
}
if ((this.BufferEnableMask & PictureBufferCbFlag) != 0)
{
this.BufferCb = configuration.MemoryAllocator.Allocate2D<T>(strideChroma * bitsPerPixel, heightChroma);
}
if ((this.BufferEnableMask & PictureBufferCrFlag) != 0)
{
this.BufferCr = configuration.MemoryAllocator.Allocate2D<T>(strideChroma * bitsPerPixel, heightChroma);
}
this.BitIncrementY = null;
this.BitIncrementCb = null;
this.BitIncrementCr = null;
this.BitIncrementY = null;
this.BitIncrementCb = null;
this.BitIncrementCr = null;
}
public Point StartPosition { get; private set; }
/// <summary>
/// Gets the Y luma buffer.
/// </summary>
public Buffer2D<T>? BufferY { get; private set; }
/// <summary>
/// Gets the U chroma buffer.
/// </summary>
public Buffer2D<T>? BufferCb { get; private set; }
/// <summary>
/// Gets the V chroma buffer.
/// </summary>
public Buffer2D<T>? BufferCr { get; private set; }
public Buffer2D<byte>? BitIncrementY { get; private set; }
public Buffer2D<byte>? BitIncrementCb { get; private set; }
public Buffer2D<byte>? BitIncrementCr { get; private set; }
/// <summary>
/// Gets or sets the horizontal padding distance.
/// </summary>
public int OriginX { get; set; }
/// <summary>
/// Gets or sets the vertical padding distance.
/// </summary>
public int OriginY { get; set; }
/// <summary>
/// Gets or sets the vertical bottom padding distance
/// </summary>
public int OriginOriginY { get; set; }
/// <summary>
/// Gets or sets the Luma picture width, which excludes the padding.
/// </summary>
public int Width { get; set; }
/// <summary>
/// Gets or sets the Luma picture height, which excludes the padding.
/// </summary>
public int Height { get; set; }
/// <summary>
/// Gets or sets the Lume picture width.
/// </summary>
public int MaxWidth { get; set; }
/// <summary>
/// Gets or sets the pixel bit depth.
/// </summary>
public Av1BitDepth BitDepth { get; set; }
/// <summary>
/// Gets or sets the chroma subsampling.
/// </summary>
public Av1ColorFormat ColorFormat { get; set; }
/// <summary>
/// Gets or sets the Luma picture height.
/// </summary>
public int MaxHeight { get; set; }
public int LumaSize { get; }
public int ChromaSize { get; }
/// <summary>
/// Gets or sets a value indicating whether the bytes of the buffers are packed.
/// </summary>
public bool PackedFlag { get; set; }
/// <summary>
/// Gets or sets a value indicating whether film grain parameters are present for this frame.
/// </summary>
public bool FilmGrainFlag { get; set; }
public int BufferEnableMask { get; set; }
public bool Is16BitPipeline { get; set; }
public void Dispose()
{
this.BufferY?.Dispose();
this.BufferY = null;
this.BufferCb?.Dispose();
this.BufferCb = null;
this.BufferCr?.Dispose();
this.BufferCr = null;
this.BitIncrementY?.Dispose();
this.BitIncrementY = null;
this.BitIncrementCb?.Dispose();
this.BitIncrementCb = null;
this.BitIncrementCr?.Dispose();
this.BitIncrementCr = null;
}
/// <summary>
/// Returns a <see cref="Span{T}"/> starting at 1 row before this blocks pixels.
/// </summary>
/// <remarks>
/// SVT: svt_aom_derive_blk_pointers
/// </remarks>
public Span<T> DeriveBlockPointer(Av1Plane plane, Point locationInPixels, int subX, int subY, out int stride)
{
Span<T> blockReconstructionBuffer;
int blockOffset;
Buffer2D<T> buffer;
switch (plane)
{
case Av1Plane.Y:
Guard.NotNull(this.BufferY);
buffer = this.BufferY;
stride = buffer.Width;
blockOffset = ((this.OriginY + locationInPixels.Y) * stride) +
(this.OriginX + locationInPixels.X);
break;
case Av1Plane.U:
Guard.NotNull(this.BufferCb);
buffer = this.BufferCb;
stride = buffer.Width;
blockOffset = (((this.OriginY >> subY) + locationInPixels.Y) * stride) +
((this.OriginX >> subX) + locationInPixels.X);
break;
case Av1Plane.V:
default:
Guard.NotNull(this.BufferCr);
buffer = this.BufferCr;
stride = buffer.Width;
blockOffset = (((this.OriginY >> subY) + locationInPixels.Y) * stride) +
((this.OriginX >> subX) + locationInPixels.X);
break;
}
// Deviation from SVT, return PREVIOUS row in Block Reconstruction Buffer.
blockOffset -= stride;
Guard.MustBeGreaterThanOrEqualTo(blockOffset, 0, nameof(blockOffset));
blockOffset = (this.BitDepth != Av1BitDepth.EightBit || this.Is16BitPipeline) ? blockOffset << 1 : blockOffset;
blockReconstructionBuffer = buffer.DangerousGetSingleSpan()[blockOffset..];
return blockReconstructionBuffer;
}
/// <summary>
/// Returns a <see cref="Buffer2DRegion{T}"/> starting at top left pixel of the block of the specified plane.
/// </summary>
/// <remarks>
/// SVT: svt_aom_derive_blk_pointers
/// </remarks>
public Buffer2DRegion<T> DeriveBlockPointer(Av1Plane plane, int subX, int subY)
{
Rectangle region;
Buffer2D<T> buffer;
switch (plane)
{
case Av1Plane.Y:
Guard.NotNull(this.BufferY);
buffer = this.BufferY;
region = new Rectangle(this.OriginX, this.OriginY, this.Width, this.Height);
break;
case Av1Plane.U:
Guard.NotNull(this.BufferCb);
buffer = this.BufferCb;
region = new Rectangle(this.OriginX >> subX, this.OriginY >> subY, this.Width >> subX, this.Height >> subY);
break;
case Av1Plane.V:
default:
Guard.NotNull(this.BufferCr);
buffer = this.BufferCr;
region = new Rectangle(this.OriginX >> subX, this.OriginY >> subY, this.Width >> subX, this.Height >> subY);
break;
}
return new Buffer2DRegion<T>(buffer, region);
}
}

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

@ -0,0 +1,169 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal static class Av1Math
{
public static int MostSignificantBit(uint value)
{
int log = 0;
int i;
Guard.IsTrue(value != 0, nameof(value), "Must have al least 1 bit set");
for (i = 4; i >= 0; --i)
{
int shift = 1 << i;
uint x = value >> shift;
if (x != 0)
{
value = x;
log += shift;
}
}
return log;
}
public static int Log2(int n)
{
int result = 0;
while ((n >>= 1) > 0)
{
result++;
}
return result;
}
/// <summary>
/// Long Log 2
/// This is a quick adaptation of a Number
/// Leading Zeros(NLZ) algorithm to get the log2f of a 32-bit number
/// </summary>
internal static uint Log2_32(uint x)
{
uint log = 0;
int i;
for (i = 4; i >= 0; --i)
{
uint shift = 1u << i;
uint n = x >> (int)shift;
if (n != 0)
{
x = n;
log += shift;
}
}
return log;
}
public static uint FloorLog2(uint value)
{
uint s = 0;
while (value != 0U)
{
value >>= 1;
s++;
}
return s - 1;
}
public static uint CeilLog2(uint value)
{
if (value < 2)
{
return 0;
}
uint i = 1;
uint p = 2;
while (p < value)
{
i++;
p <<= 1;
}
return i;
}
public static uint Clip1(uint value, int bitDepth) =>
Clip3(0, (1U << bitDepth) - 1, value);
public static uint Clip3(uint min, uint max, uint value) => Math.Max(min, Math.Min(max, value));
public static int Clip3(int min, int max, int value) => Math.Max(min, Math.Min(max, value));
public static uint Round2(uint value, int n)
{
if (n == 0)
{
return value;
}
return (uint)((value + (1 << (n - 1))) >> n);
}
public static int Round2(int value, int n)
{
if (value < 0)
{
value = -value;
}
return (int)Round2((uint)value, n);
}
internal static int AlignPowerOf2(int value, int n)
{
int mask = (1 << n) - 1;
return (value + mask) & ~mask;
}
internal static int RoundPowerOf2(int value, int n) => (value + ((1 << n) >> 1)) >> n;
internal static int Clamp(int value, int low, int high)
=> Math.Max(low, Math.Min(high, value));
internal static long Clamp(long value, long low, long high)
=> Math.Max(low, Math.Min(high, value));
internal static int DivideLog2Floor(int value, int n)
=> value >> n;
internal static int DivideLog2Ceiling(int value, int n)
=> (value + (1 << n) - 1) >> n;
internal static int DivideRound(int value, int bitCount)
=> (value + (1 << (bitCount - 1))) >> bitCount;
// Last 3 bits are the value of mod 8.
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);
internal static int RoundShift(long value, int bit)
{
DebugGuard.MustBeGreaterThanOrEqualTo(bit, 1, nameof(bit));
return (int)((value + (1L << (bit - 1))) >> bit);
}
/// <summary>
/// <paramref name="a"/> implies <paramref name="b"/>.
/// </summary>
internal static bool Implies(bool a, bool b) => !a || b;
internal static int GetBit(int value, int n)
=> (value & (1 << n)) >> n;
internal static void SetBit(ref int endOfBlockExtra, int n)
=> endOfBlockExtra |= 1 << n;
internal static int AbsoluteDifference(int a, int b) => (a > b) ? a - b : b - a;
}

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

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

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];
}

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

@ -0,0 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal enum Av1Plane : int
{
Y = 0,
U = 1,
V = 2,
}

169
src/ImageSharp/Formats/Heif/Av1/Av1YuvConverter.cs

@ -0,0 +1,169 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal class Av1YuvConverter
{
// BT.709 SPecificatiuon constants.
private const int UMax = (int)(0.436 * 255);
private const int VMax = (int)(0.615 * 255);
private const int Wr = (int)(0.2126 * 255);
private const int Wb = (int)(0.0722 * 255);
private const int Wg = 255 - Wr - Wb;
public static void ConvertToRgb<TPixel>(Configuration configuration, Av1FrameBuffer<byte> frameBuffer, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<Rgb24> rgbImage = new(image.Width, image.Height);
ImageFrame<Rgb24> rgbFrame = rgbImage.Frames.RootFrame;
// TODO: Support YUV420 and YUV420 also.
if (frameBuffer.ColorFormat != Av1ColorFormat.Yuv444)
{
throw new NotSupportedException("Only able to convert YUV444 to RGB.");
}
ConvertYuvToRgb(frameBuffer, rgbFrame, false);
image.ProcessPixelRows(rgbFrame, (resultAcc, rgbAcc) =>
{
for (int y = 0; y < rgbImage.Height; y++)
{
Span<Rgb24> rgbRow = rgbAcc.GetRowSpan(y);
Span<TPixel> resultRow = resultAcc.GetRowSpan(y);
PixelOperations<TPixel>.Instance.FromRgb24(configuration, rgbRow, resultRow);
}
});
}
public static void ConvertFromRgb<TPixel>(Configuration configuration, ImageFrame<TPixel> image, Av1FrameBuffer<byte> frameBuffer)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<Rgb24> rgbImage = new(image.Width, image.Height);
ImageFrame<Rgb24> rgbFrame = rgbImage.Frames.RootFrame;
image.ProcessPixelRows(rgbFrame, (sourceAcc, rgbAcc) =>
{
for (int y = 0; y < rgbImage.Height; y++)
{
Span<Rgb24> rgbRow = rgbAcc.GetRowSpan(y);
Span<TPixel> sourceRow = sourceAcc.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToRgb24(configuration, sourceRow, rgbRow);
}
});
// TODO: Support YUV422 and YUV420 also.
ConvertRgbToYuv444(rgbFrame, frameBuffer);
}
private static void ConvertYuvToRgb(Av1FrameBuffer<byte> buffer, ImageFrame<Rgb24> image, bool isSubsampled)
{
// Weight multiplied by 256 to exploit full byte resolution, rounded to the nearest integer.
// Using BT.709 specification
Guard.NotNull(buffer.BufferY);
Guard.NotNull(buffer.BufferCb);
Guard.NotNull(buffer.BufferCr);
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferY.Width, image.Width, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferY.Height, image.Height, nameof(buffer));
if (isSubsampled)
{
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCb.Width, image.Width >> 1, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCb.Height, image.Height >> 1, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCr.Width, image.Width >> 1, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCr.Height, image.Height >> 1, nameof(buffer));
}
else
{
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCb.Width, image.Width, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCb.Height, image.Height, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCr.Width, image.Width, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCr.Height, image.Height, nameof(buffer));
}
image.ProcessPixelRows(accessor =>
{
Span<byte> yBuffer = buffer.DeriveBlockPointer(Av1Plane.Y, default, 0, 0, out int yStride);
Span<byte> uBuffer = buffer.DeriveBlockPointer(Av1Plane.U, default, 0, 0, out int uStride);
Span<byte> vBuffer = buffer.DeriveBlockPointer(Av1Plane.V, default, 0, 0, out int vStride);
int yOffset = yStride;
int uOffset = uStride;
int vOffset = vStride;
for (int y = 0; y < image.Height; y++)
{
Span<Rgb24> rgbRow = accessor.GetRowSpan(y);
ref Rgb24 pixel = ref rgbRow[0];
ref byte yRef = ref yBuffer[yOffset];
ref byte uRef = ref uBuffer[uOffset];
ref byte vRef = ref vBuffer[vOffset];
for (int x = 0; x < image.Width; x++)
{
int u = uRef; // ((uRef - 127) * 2 * UMax) / 255;
int v = vRef; // ((vRef - 127) * 2 * VMax) / 255;
pixel.R = (byte)Av1Math.Clip3(0, 255, yRef + (v * (255 - Wr) / VMax));
pixel.G = (byte)Av1Math.Clip3(0, 255, yRef - ((u * Wb * (255 - Wb)) / (UMax * Wg)) - ((v * Wr * (255 - Wr)) / (VMax * Wg)));
pixel.B = (byte)Av1Math.Clip3(0, 255, yRef + ((u * (255 - Wb)) / UMax));
pixel = ref Unsafe.Add(ref pixel, 1);
yRef = ref Unsafe.Add(ref yRef, 1);
uRef = ref Unsafe.Add(ref uRef, 1);
vRef = ref Unsafe.Add(ref vRef, 1);
}
yOffset += yStride;
uOffset += uStride;
vOffset += vStride;
}
});
}
private static void ConvertRgbToYuv444(ImageFrame<Rgb24> image, Av1FrameBuffer<byte> buffer)
{
// Weight multiplied by 256 to exploit full byte resolution, rounded to the nearest integer.
Guard.NotNull(buffer.BufferY);
Guard.NotNull(buffer.BufferCb);
Guard.NotNull(buffer.BufferCr);
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferY.Width, image.Width, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferY.Height, image.Height, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCb.Width, image.Width, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCb.Height, image.Height, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCr.Width, image.Width, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(buffer.BufferCr.Height, image.Height, nameof(buffer));
image.ProcessPixelRows(accessor =>
{
Buffer2D<byte> yBuffer = buffer.BufferY;
Buffer2D<byte> uBuffer = buffer.BufferCb;
Buffer2D<byte> vBuffer = buffer.BufferCr;
for (int y = 0; y < image.Height; y++)
{
Span<Rgb24> rgbRow = accessor.GetRowSpan(y);
ref Rgb24 pixel = ref rgbRow[0];
Span<byte> ySpan = yBuffer.DangerousGetRowSpan(y);
ref byte yRef = ref ySpan[0];
Span<byte> uSpan = uBuffer.DangerousGetRowSpan(y);
ref byte uRef = ref uSpan[0];
Span<byte> vSpan = vBuffer.DangerousGetRowSpan(y);
ref byte vRef = ref vSpan[0];
for (int x = 0; x < image.Width; x++)
{
yRef = (byte)Av1Math.Clip3(0, 255, ((Wr * pixel.R) + (Wg * pixel.G) + (Wb * pixel.B)) / 255);
// Not normalized, where range is [-UMax, UMax] or [-VMax, VMax]
// uRef = (byte)((UMax * (pixel.B - y)) / (255 - Wb));
// vRef = (byte)((VMax * (pixel.R - y)) / (255 - Wr));
// Normalized calculations
uRef = (byte)Av1Math.Clip3(0, 255, ((UMax * (pixel.B - yRef) / (255 - Wb)) + UMax) * 255 / (2 * UMax));
vRef = (byte)Av1Math.Clip3(0, 255, ((VMax * (pixel.R - yRef) / (255 - Wr)) + VMax) * 255 / (2 * VMax));
pixel = ref Unsafe.Add(ref pixel, 1);
yRef = ref Unsafe.Add(ref yRef, 1);
uRef = ref Unsafe.Add(ref uRef, 1);
vRef = ref Unsafe.Add(ref vRef, 1);
}
}
});
}
}

2269
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs

File diff suppressed because it is too large

129
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs

@ -0,0 +1,129 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
/// <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)
: this([p0, p1, p2, p3, p4, p5, p6, p7, 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)
: this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, 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)
{
}
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, uint p13, uint p14)
: this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, 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;
}
}

413
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs

@ -0,0 +1,413 @@
// 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.Entropy;
internal static class Av1NzMap
{
// SIG_COEF_CONTEXTS_2D = 26
private const int NzMapContext0 = 26;
private const int NzMapContext5 = NzMapContext0 + 5;
private const int NzMapContext10 = NzMapContext0 + 10;
private static readonly int[] NzMapContextOffset1d = [
NzMapContext0, NzMapContext5, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10,
];
// The ctx offset table when TX is TX_CLASS_2D.
// TX col and row indices are clamped to 4
private static readonly int[] NzMapContextOffset4x4 = [
0, 1, 6, 6,
1, 6, 6, 21,
6, 6, 21, 21,
6, 21, 21, 21];
private static readonly int[] NzMapContextOffset8x8 = [
0, 1, 6, 6, 21, 21, 21, 21,
1, 6, 6, 21, 21, 21, 21, 21,
6, 6, 21, 21, 21, 21, 21, 21,
6, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
];
private static readonly int[] NzMapContextOffset16x16 = [
0, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
];
private static readonly int[] NzMapContextOffset32x32 = [
0, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
];
private static readonly int[] NzMapContextOffset8x4 = [
0, 16, 6, 6, 21, 21, 21, 21,
16, 16, 6, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21,
];
private static readonly int[] NzMapContextOffset16x8 = [
0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
];
private static readonly int[] NzMapContextOffset16x32 = [
0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
];
private static readonly int[] NzMapContextOffset32x16 = [
0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
];
private static readonly int[] NzMapContextOffset32x64 = [
0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
];
private static readonly int[] NzMapContextOffset64x32 = [
0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
];
private static readonly int[] NzMapContextOffset4x16 = [
0, 11, 11, 11,
11, 11, 11, 11,
6, 6, 21, 21,
6, 21, 21, 21,
21, 21, 21, 21,
21, 21, 21, 21,
21, 21, 21, 21,
21, 21, 21, 21,
21, 21, 21, 21,
21, 21, 21, 21,
21, 21, 21, 21,
21, 21, 21, 21,
21, 21, 21, 21,
21, 21, 21, 21,
21, 21, 21, 21,
21, 21, 21, 21,
];
private static readonly int[] NzMapContextOffset16x4 = [
0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
];
private static readonly int[] NzMapContextOffset8x32 = [
0, 11, 11, 11, 11, 11, 11, 11,
11, 11, 11, 11, 11, 11, 11, 11,
6, 6, 21, 21, 21, 21, 21, 21,
6, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21,
];
private static readonly int[] NzMapContextOffset32x8 = [
0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
];
private static readonly int[][] NzMapContextOffset = [
NzMapContextOffset4x4, // TX_4x4
NzMapContextOffset8x8, // TX_8x8
NzMapContextOffset16x16, // TX_16x16
NzMapContextOffset32x32, // TX_32x32
NzMapContextOffset32x32, // TX_32x32
NzMapContextOffset4x16, // TX_4x8
NzMapContextOffset8x4, // TX_8x4
NzMapContextOffset8x32, // TX_8x16
NzMapContextOffset16x8, // TX_16x8
NzMapContextOffset16x32, // TX_16x32
NzMapContextOffset32x16, // TX_32x16
NzMapContextOffset32x64, // TX_32x64
NzMapContextOffset64x32, // TX_64x32
NzMapContextOffset4x16, // TX_4x16
NzMapContextOffset16x4, // TX_16x4
NzMapContextOffset8x32, // TX_8x32
NzMapContextOffset32x8, // TX_32x8
NzMapContextOffset16x32, // TX_16x64
NzMapContextOffset64x32, // TX_64x16
];
/// <summary>
/// SVT: get_nz_mag
/// </summary>
public static int GetNzMagnitude(Av1LevelBuffer levels, Point position, Av1TransformClass transformClass)
{
int mag;
Span<byte> row0 = levels.GetRow(position.Y)[position.X..];
Span<byte> row1 = levels.GetRow(position.Y + 1)[position.X..];
Span<byte> row2 = levels.GetRow(position.Y + 2)[position.X..];
// Note: AOMMIN(level, 3) is useless for decoder since level < 3.
mag = ClipMax3(row0[1]); // { 0, 1 }
mag += ClipMax3(row1[0]); // { 1, 0 }
switch (transformClass)
{
case Av1TransformClass.Class2D:
mag += ClipMax3(row1[1]); // { 1, 1 }
mag += ClipMax3(row0[2]); // { 0, 2 }
mag += ClipMax3(row2[0]); // { 2, 0 }
break;
case Av1TransformClass.ClassVertical:
Span<byte> row3 = levels.GetRow(position.Y + 3)[position.X..];
Span<byte> row4 = levels.GetRow(position.Y + 4)[position.X..];
mag += ClipMax3(row2[0]); // { 2, 0 }
mag += ClipMax3(row3[0]); // { 3, 0 }
mag += ClipMax3(row4[0]); // { 4, 0 }
break;
case Av1TransformClass.ClassHorizontal:
mag += ClipMax3(row0[2]); // { 0, 2 }
mag += ClipMax3(row0[3]); // { 0, 3 }
mag += ClipMax3(row0[4]); // { 0, 4 }
break;
}
return mag;
}
public static int GetNzMapContextFromStats(int stats, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass)
{
// tx_class == 0(TX_CLASS_2D)
if (transformClass == 0 && (position.X == 0) && (position.Y == 0))
{
return 0;
}
int ctx = (stats + 1) >> 1;
ctx = Math.Min(ctx, 4);
switch (transformClass)
{
case Av1TransformClass.Class2D:
// This is the algorithm to generate eb_av1_nz_map_ctx_offset[][]
// const int width = tx_size_wide[tx_size];
// const int height = tx_size_high[tx_size];
// if (width < height) {
// if (row < 2) return 11 + ctx;
// } else if (width > height) {
// if (col < 2) return 16 + ctx;
// }
// if (row + col < 2) return ctx + 1;
// if (row + col < 4) return 5 + ctx + 1;
// return 21 + ctx;
return ctx + GetNzMapContext(transformSize, position);
case Av1TransformClass.ClassHorizontal:
return ctx + NzMapContextOffset1d[position.X];
case Av1TransformClass.ClassVertical:
return ctx + NzMapContextOffset1d[position.Y];
default:
break;
}
return 0;
}
public static int GetNzMapContext(Av1TransformSize transformSize, Point pos) => GetNzMapContext(transformSize, pos.X + (pos.Y * transformSize.GetWidth()));
public static int GetNzMapContext(Av1TransformSize transformSize, int pos) => NzMapContextOffset[(int)transformSize][pos];
private static int ClipMax3(int value) => Math.Min(value, 3);
}

444
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs

@ -0,0 +1,444 @@
// Copyright (c) Six Labors.
// 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.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
internal static class Av1SymbolContextHelper
{
public static readonly int[][] ExtendedTransformIndices = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // DCT only
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Inter set 3
[1, 3, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // Intra set 2
[1, 5, 6, 4, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0], // Intra set 1
[3, 4, 5, 8, 6, 7, 9, 10, 11, 0, 1, 2, 0, 0, 0, 0], // Inter set 2
[7, 8, 9, 12, 10, 11, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6], // All 16, inter set 1
];
// Maps tx set types to the distribution indices. INTRA values only
private static readonly int[] ExtendedTransformSetToIndex = [0, -1, 2, 1, -1, -1];
/// <summary>
/// Section 5.11.48: Transform type syntax
/// </summary>
public static readonly Av1TransformType[][] ExtendedTransformInverse = [
[Av1TransformType.DctDct], // DCT only
[], // Inter set 3
[Av1TransformType.Identity, Av1TransformType.DctDct, Av1TransformType.AdstAdst, Av1TransformType.AdstDct, Av1TransformType.DctAdst], // Intra set 2
[Av1TransformType.Identity, Av1TransformType.DctDct, Av1TransformType.VerticalDct, Av1TransformType.HorizontalDct, Av1TransformType.AdstAdst, Av1TransformType.AdstDct, Av1TransformType.DctAdst], // Intra set 1
[], // Inter set 2
[], // All 16, inter set 1
];
public static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
public static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513];
private static readonly byte[] EndOfBlockToPositionSmall = [
0, 1, 2, // 0-2
3, 3, // 3-4
4, 4, 4, 4, // 5-8
5, 5, 5, 5, 5, 5, 5, 5, // 9-16
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 // 17-32
];
private static readonly byte[] EndOfBlockToPositionLarge = [
6, // place holder
7, // 33-64
8,
8, // 65-128
9,
9,
9,
9, // 129-256
10,
10,
10,
10,
10,
10,
10,
10, // 257-512
11 // 513-
];
internal static Av1TransformSize GetTransformSizeContext(Av1TransformSize originalSize)
=> (Av1TransformSize)(((int)originalSize.GetSquareSize() + (int)originalSize.GetSquareUpSize() + 1) >> 1);
internal static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra)
{
int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint];
if (endOfBlock > 2)
{
endOfBlock += endOfBlockExtra;
}
return endOfBlock;
}
/// <summary>
/// SVT: get_lower_levels_ctx_eob
/// </summary>
internal static int GetLowerLevelContextEndOfBlock(Av1LevelBuffer levels, Point position)
{
if (position.X == 0 && position.Y == 0)
{
return 0;
}
int total = levels.Size.Height * levels.Size.Width;
int index = position.X + (position.Y * levels.Size.Width);
if (index <= total >> 3)
{
return 1;
}
if (index <= total >> 2)
{
return 2;
}
return 3;
}
/// <summary>
/// SVT: get_lower_levels_ctx_2d
/// </summary>
internal static int GetLowerLevelsContext2d(Av1LevelBuffer levelBuffer, Point position, Av1TransformSize transformSize)
{
DebugGuard.MustBeGreaterThan(position.X + position.Y, 0, nameof(position));
int mag;
Span<byte> row0 = levelBuffer.GetRow(position.Y)[position.X..];
Span<byte> row1 = levelBuffer.GetRow(position.Y + 1)[position.X..];
Span<byte> row2 = levelBuffer.GetRow(position.Y + 2)[position.X..];
mag = Math.Min((int)row0[1], 3); // { 0, 1 }
mag += Math.Min((int)row1[0], 3); // { 1, 0 }
mag += Math.Min((int)row1[1], 3); // { 1, 1 }
mag += Math.Min((int)row0[2], 3); // { 0, 2 }
mag += Math.Min((int)row2[0], 3); // { 2, 0 }
int ctx = Math.Min((mag + 1) >> 1, 4);
return ctx + Av1NzMap.GetNzMapContext(transformSize, position);
}
/// <summary>
/// Section 8.3.2 in the spec, under coeff_br. Optimized for end of block based
/// on the fact that {0, 1}, {1, 0}, {1, 1}, {0, 2} and {2, 0} will all be 0 in
/// the end of block case.
/// </summary>
internal static int GetBaseRangeContextEndOfBlock(Point pos, Av1TransformClass transformClass)
{
if (pos.X == 0 && pos.Y == 0)
{
return 0;
}
if ((transformClass == Av1TransformClass.Class2D && pos.Y < 2 && pos.X < 2) ||
(transformClass == Av1TransformClass.ClassHorizontal && pos.X == 0) ||
(transformClass == Av1TransformClass.ClassVertical && pos.Y == 0))
{
return 7;
}
return 14;
}
/// <summary>
/// SVT: get_br_ctx
/// </summary>
/// <remarks>Spec section 8.2.3, under 'coeff_br'.</remarks>
internal static int GetBaseRangeContext(Av1LevelBuffer levels, Point position, Av1TransformClass transformClass)
{
Span<byte> row0 = levels.GetRow(position.Y);
Span<byte> row1 = levels.GetRow(position.Y + 1);
int mag = row0[position.X + 1];
mag += row1[position.X];
switch (transformClass)
{
case Av1TransformClass.Class2D:
mag += row1[position.X + 1];
mag = Math.Min((mag + 1) >> 1, 6);
if ((position.X + position.Y) == 0)
{
return mag;
}
if (position.Y < 2 && position.X < 2)
{
return mag + 7;
}
break;
case Av1TransformClass.ClassHorizontal:
mag += row0[position.X + 2];
mag = Math.Min((mag + 1) >> 1, 6);
if ((position.X + position.Y) == 0)
{
return mag;
}
if (position.X == 0)
{
return mag + 7;
}
break;
case Av1TransformClass.ClassVertical:
mag += levels.GetRow(position.Y + 2)[position.X];
mag = Math.Min((mag + 1) >> 1, 6);
if ((position.X + position.Y) == 0)
{
return mag;
}
if (position.Y == 0)
{
return mag + 7;
}
break;
default:
break;
}
return mag + 14;
}
/// <summary>
/// SVT: get_br_ctx_2d
/// </summary>
internal static int GetBaseRangeContext2d(Av1LevelBuffer levels, Point position)
{
DebugGuard.MustBeGreaterThan(position.X + position.Y, 0, nameof(position));
Span<byte> row0 = levels.GetRow(position.Y);
Span<byte> row1 = levels.GetRow(position.Y + 1);
// No need to clip quantized values to COEFF_BASE_RANGE + NUM_BASE_LEVELS
// + 1, because we clip the overall output to 6 and the unclipped
// quantized values will always result in an output of greater than 6.
int mag =
row0[position.X + 1] + // {0, 1}
row1[position.X] + // {1, 0}
row1[position.X + 1]; // {1, 1}
mag = Math.Min((mag + 1) >> 1, 6);
if ((position.Y | position.X) < 2)
{
return mag + 7;
}
return mag + 14;
}
internal static int GetLowerLevelsContext(Av1LevelBuffer levels, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass)
{
int stats = Av1NzMap.GetNzMagnitude(levels, position, transformClass);
return Av1NzMap.GetNzMapContextFromStats(stats, position, transformSize, transformClass);
}
/// <summary>
/// SVT: get_ext_tx_set_type
/// </summary>
internal static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet)
{
Av1TransformSize squareUpSize = transformSize.GetSquareUpSize();
if (squareUpSize >= Av1TransformSize.Size32x32)
{
return Av1TransformSetType.DctOnly;
}
if (useReducedSet)
{
return Av1TransformSetType.IntraSet2;
}
Av1TransformSize squareSize = transformSize.GetSquareSize();
return squareSize == Av1TransformSize.Size16x16 ? Av1TransformSetType.IntraSet2 : Av1TransformSetType.IntraSet1;
}
internal static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInfo modeInfo, Av1PlaneType planeType)
{
Av1PredictionMode mode = (planeType == Av1PlaneType.Y) ? modeInfo.YMode : modeInfo.UvMode;
if (mode == Av1PredictionMode.UvChromaFromLuma)
{
mode = Av1PredictionMode.DC;
}
return mode.ToTransformType();
}
/// <summary>
/// SVT: get_nz_map_ctx
/// </summary>
internal static sbyte GetNzMapContext(
Av1LevelBuffer levels,
Point position,
bool isEndOfBlock,
Av1TransformSize transformSize,
Av1TransformClass transformClass)
{
if (isEndOfBlock)
{
return (sbyte)GetLowerLevelContextEndOfBlock(levels, position);
}
int stats = Av1NzMap.GetNzMagnitude(levels, position, transformClass);
return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, position, transformSize, transformClass);
}
/// <summary>
/// SVT: svt_av1_get_nz_map_contexts_c
/// </summary>
internal static void GetNzMapContexts(
Av1LevelBuffer levels,
ReadOnlySpan<short> scan,
ushort eob,
Av1TransformSize transformSize,
Av1TransformClass transformClass,
Span<sbyte> coefficientContexts)
{
for (int i = 0; i < eob; ++i)
{
int pos = scan[i];
Point position = levels.GetPosition(pos);
coefficientContexts[pos] = GetNzMapContext(levels, position, i == eob - 1, transformSize, transformClass);
}
}
/// <summary>
/// SVT: get_ext_tx_types
/// </summary>
internal static int GetExtendedTransformTypeCount(Av1TransformSetType setType) => ExtendedTransformInverse[(int)setType].Length;
/// <summary>
/// SVT: get_ext_tx_set
/// </summary>
internal static int GetExtendedTransformSet(Av1TransformSetType setType) => ExtendedTransformSetToIndex[(int)setType];
/// <summary>
/// SVT: set_dc_sign
/// </summary>
internal static void SetDcSign(ref int culLevel, int dcValue)
{
if (dcValue < 0)
{
culLevel |= 1 << Av1Constants.CoefficientContextBitCount;
}
else if (dcValue > 0)
{
culLevel += 2 << Av1Constants.CoefficientContextBitCount;
}
}
/// <summary>
/// SVT: get_eob_pos_token
/// </summary>
internal static short GetEndOfBlockPosition(ushort endOfBlock, out int extra)
{
short t;
if (endOfBlock < 33)
{
t = EndOfBlockToPositionSmall[endOfBlock];
}
else
{
int e = Math.Min((endOfBlock - 1) >> 5, 16);
t = EndOfBlockToPositionLarge[e];
}
extra = endOfBlock - EndOfBlockGroupStart[t];
return t;
}
public static int GetSegmentId(Av1PartitionInfo partitionInfo, ObuFrameHeader frameHeader, int[][] segmentIds, int rowIndex, int columnIndex)
{
int modeInfoOffset = (rowIndex * frameHeader.ModeInfoColumnCount) + columnIndex;
int bw4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount();
int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount();
int xMin = Math.Min(frameHeader.ModeInfoColumnCount - columnIndex, bw4);
int yMin = Math.Min(frameHeader.ModeInfoRowCount - rowIndex, bh4);
int segmentId = Av1Constants.MaxSegmentCount - 1;
for (int y = 0; y < yMin; y++)
{
for (int x = 0; x < xMin; x++)
{
segmentId = Math.Min(segmentId, segmentIds[y][x]);
}
}
return segmentId;
}
/// <summary>
/// SVT: svt_aom_get_segment_id
/// </summary>
public static int GetSegmentId(Av1EncoderCommon cm, ReadOnlySpan<byte> segment_ids, Av1BlockSize bsize, Point modeInfoPosition)
{
int mi_offset = (modeInfoPosition.Y * cm.ModeInfoColumnCount) + modeInfoPosition.X;
int bw = bsize.GetWidth();
int bh = bsize.GetHeight();
int xmis = Math.Min(cm.ModeInfoColumnCount - modeInfoPosition.X, bw);
int ymis = Math.Min(cm.ModeInfoRowCount - modeInfoPosition.Y, bh);
int segment_id = Av1Constants.MaxSegmentCount;
for (int y = 0; y < ymis; ++y)
{
int offset = mi_offset + (y * cm.ModeInfoColumnCount);
for (int x = 0; x < xmis; ++x)
{
segment_id = Math.Min(segment_id, segment_ids[offset + x]);
}
}
Guard.IsTrue(segment_id is >= 0 and < Av1Constants.MaxSegmentCount, nameof(segment_id), "Segment ID needs to be in proper range.");
return segment_id;
}
public 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);
}
}
}

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

@ -0,0 +1,690 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
internal ref struct Av1SymbolDecoder
{
private static readonly int[] IntraModeContext = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0];
private static readonly int[] AlphaVContexts = [-1, 0, 3, -1, 1, 4, -1, 2, 5];
private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes;
private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode;
private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode;
private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip;
private readonly Av1Distribution[] skipMode = Av1DefaultDistributions.SkipMode;
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 readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode;
private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra;
private readonly Av1Distribution[][] transformSize = Av1DefaultDistributions.TransformSize;
private readonly Av1Distribution[][][] endOfBlockFlag;
private readonly Av1Distribution[][][] coefficientsBase;
private readonly Av1Distribution[][][] baseEndOfBlock;
private readonly Av1Distribution[][] dcSign;
private readonly Av1Distribution[][][] coefficientsBaseRange;
private readonly Av1Distribution[][] transformBlockSkip;
private readonly Av1Distribution[][][] endOfBlockExtra;
private readonly Av1Distribution chromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign;
private readonly Av1Distribution[] chromaFromLumaAlpha = Av1DefaultDistributions.ChromaFromLumaAlpha;
private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform;
private readonly Configuration configuration;
private Av1SymbolReader reader;
private readonly int baseQIndex;
public Av1SymbolDecoder(Configuration configuration, Span<byte> tileData, int qIndex)
{
this.configuration = configuration;
this.reader = new Av1SymbolReader(tileData);
this.baseQIndex = qIndex;
this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex);
this.coefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex);
this.baseEndOfBlock = Av1DefaultDistributions.GetBaseEndOfBlock(qIndex);
this.dcSign = Av1DefaultDistributions.GetDcSign(qIndex);
this.coefficientsBaseRange = Av1DefaultDistributions.GetCoefficientsBaseRange(qIndex);
this.transformBlockSkip = Av1DefaultDistributions.GetTransformBlockSkip(qIndex);
this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex);
}
public int ReadCdfStrength(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 ReadPartitionType(int context)
{
ref Av1SymbolReader r = ref this.reader;
return (Av1PartitionType)r.ReadSymbol(this.tilePartitionTypes[context]);
}
/// <summary>
/// SVT: partition_gather_vert_alike
/// </summary>
public Av1PartitionType ReadSplitOrHorizontal(Av1BlockSize blockSize, int context)
{
Av1Distribution distribution = GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context);
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(distribution) > 0 ? Av1PartitionType.Split : Av1PartitionType.Horizontal;
}
/// <summary>
/// SVT: partition_gather_horz_alike
/// </summary>
public Av1PartitionType ReadSplitOrVertical(Av1BlockSize blockSize, int context)
{
Av1Distribution distribution = GetSplitOrVerticalDistribution(this.tilePartitionTypes, blockSize, context);
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(distribution) > 0 ? Av1PartitionType.Split : Av1PartitionType.Vertical;
}
public Av1PredictionMode ReadYMode(Av1BlockModeInfo? aboveModeInfo, Av1BlockModeInfo? leftModeInfo)
{
ref Av1SymbolReader r = ref this.reader;
Av1PredictionMode aboveMode = Av1PredictionMode.DC;
if (aboveModeInfo != null)
{
aboveMode = aboveModeInfo.YMode;
}
Av1PredictionMode leftMode = Av1PredictionMode.DC;
if (leftModeInfo != null)
{
leftMode = leftModeInfo.YMode;
}
int aboveContext = IntraModeContext[(int)aboveMode];
int leftContext = IntraModeContext[(int)leftMode];
return (Av1PredictionMode)r.ReadSymbol(this.keyFrameYMode[aboveContext][leftContext]);
}
public Av1PredictionMode ReadIntraModeUv(Av1PredictionMode mode, bool chromaFromLumaAllowed)
{
int chromaForLumaIndex = chromaFromLumaAllowed ? 1 : 0;
ref Av1SymbolReader r = ref this.reader;
return (Av1PredictionMode)r.ReadSymbol(this.uvMode[chromaForLumaIndex][(int)mode]);
}
public bool ReadSkip(int ctx)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.skip[ctx]) > 0;
}
public bool ReadSkipMode(Av1BlockSize blockSize)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.skipMode[(int)blockSize]) > 0;
}
public int ReadDeltaLoopFilter()
{
ref Av1SymbolReader r = ref this.reader;
int deltaLoopFilterAbsolute = r.ReadSymbol(this.deltaLoopFilterAbsolute);
if (deltaLoopFilterAbsolute == Av1Constants.DeltaLoopFilterSmall)
{
int deltaLoopFilterRemainingBits = r.ReadLiteral(3) + 1;
int deltaLoopFilterAbsoluteBitCount = r.ReadLiteral(deltaLoopFilterRemainingBits);
deltaLoopFilterAbsolute = deltaLoopFilterAbsoluteBitCount + (1 << deltaLoopFilterRemainingBits) + 1;
}
bool deltaLoopFilterSign = true;
if (deltaLoopFilterAbsolute != 0)
{
deltaLoopFilterSign = r.ReadLiteral(1) > 0;
}
return deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute;
}
/// <summary>
/// SVT: read_delta_qindex
/// </summary>
public int ReadDeltaQuantizerIndex()
{
ref Av1SymbolReader r = ref this.reader;
int deltaQuantizerAbsolute = r.ReadSymbol(this.deltaQuantizerAbsolute);
if (deltaQuantizerAbsolute == Av1Constants.DeltaQuantizerSmall)
{
int deltaQuantizerRemainingBits = r.ReadLiteral(3) + 1;
int deltaQuantizerAbsoluteBase = r.ReadLiteral(deltaQuantizerRemainingBits);
deltaQuantizerAbsolute = deltaQuantizerAbsoluteBase + (1 << deltaQuantizerRemainingBits) + 1;
}
bool deltaQuantizerSignBit = true;
if (deltaQuantizerAbsolute != 0)
{
deltaQuantizerSignBit = r.ReadLiteral(1) > 0;
}
return deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute;
}
public int ReadSegmentId(int context)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.segmentId[context]);
}
public int ReadAngleDelta(Av1PredictionMode mode)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.angleDelta[(int)mode - 1]);
}
public Av1FilterIntraMode ReadFilterUltraMode(Av1BlockSize blockSize)
{
ref Av1SymbolReader r = ref this.reader;
Av1FilterIntraMode filterIntraMode = Av1FilterIntraMode.AllFilterIntraModes;
bool useFilterIntra = r.ReadSymbol(this.filterIntra[(int)blockSize]) > 0;
if (useFilterIntra)
{
filterIntraMode = (Av1FilterIntraMode)r.ReadSymbol(this.filterIntraMode);
}
return filterIntraMode;
}
public Av1TransformSize ReadTransformSize(Av1BlockSize blockSize, int context)
{
ref Av1SymbolReader r = ref this.reader;
Av1TransformSize maxTransformSize = blockSize.GetMaximumTransformSize();
int depth = 0;
while (maxTransformSize != Av1TransformSize.Size4x4)
{
depth++;
maxTransformSize = maxTransformSize.GetSubSize();
DebugGuard.MustBeLessThan(depth, 10, nameof(depth));
}
DebugGuard.MustBeLessThanOrEqualTo(depth, Av1Constants.MaxTransformCategories, nameof(depth));
int category = depth - 1;
int value = r.ReadSymbol(this.transformSize[category][context]);
Av1TransformSize transformSize = blockSize.GetMaximumTransformSize();
for (int d = 0; d < value; ++d)
{
transformSize = transformSize.GetSubSize();
}
return transformSize;
}
/// <summary>
/// SVT: parse_transform_type
/// </summary>
public Av1TransformType ReadTransformType(
Av1TransformSize transformSize,
bool useReducedTransformSet,
bool useFilterIntra,
int baseQIndex,
Av1FilterIntraMode filterIntraMode,
Av1PredictionMode intraDirection)
{
Av1TransformType transformType = Av1TransformType.DctDct;
/*
// No need to read transform type if block is skipped.
if (mbmi.Skip ||
svt_aom_seg_feature_active(&parse_ctxt->frame_header->segmentation_params, mbmi->segment_id, SEG_LVL_SKIP))
return;
*/
if (baseQIndex == 0)
{
return transformType;
}
// Ignoring INTER blocks here, as these should not end up here.
// int inter_block = is_inter_block_dec(mbmi);
Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet);
if (transformSetType > Av1TransformSetType.DctOnly && baseQIndex > 0)
{
int extendedSet = Av1SymbolContextHelper.GetExtendedTransformSet(transformSetType);
Av1TransformSize squareTransformSize = transformSize.GetSquareSize();
Av1PredictionMode intraMode = useFilterIntra
? filterIntraMode.ToIntraDirection()
: intraDirection;
ref Av1SymbolReader r = ref this.reader;
int symbol = r.ReadSymbol(this.intraExtendedTransform[extendedSet][(int)squareTransformSize][(int)intraMode]);
transformType = Av1SymbolContextHelper.ExtendedTransformInverse[(int)transformSetType][symbol];
}
return transformType;
}
public bool ReadTransformBlockSkip(Av1TransformSize transformSizeContext, int skipContext)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.transformBlockSkip[(int)transformSizeContext][skipContext]) > 0;
}
public int ReadChromFromLumaSign()
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.chromaFromLumaSign);
}
public int ReadChromaFromLumaAlphaU(int jointSignPlus1)
{
ref Av1SymbolReader r = ref this.reader;
int context = jointSignPlus1 - 3;
return r.ReadSymbol(this.chromaFromLumaAlpha[context]);
}
public int ReadChromaFromLumaAlphaV(int jointSignPlus1)
{
ref Av1SymbolReader r = ref this.reader;
int context = AlphaVContexts[jointSignPlus1];
return r.ReadSymbol(this.chromaFromLumaAlpha[context]);
}
/// <summary>
/// SVT: parse_coeffs
/// </summary>
public int ReadCoefficients(
Av1BlockModeInfo modeInfo,
Point blockPosition,
int[] aboveContexts,
int[] leftContexts,
int aboveOffset,
int leftOffset,
int plane,
int blocksWide,
int blocksHigh,
Av1TransformBlockContext transformBlockContext,
Av1TransformSize transformSize,
bool isLossless,
bool useReducedTransformSet,
Av1TransformInfo transformInfo,
int modeBlocksToRightEdge,
int modeBlocksToBottomEdge,
Span<int> coefficientBuffer)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize);
Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1);
int culLevel = 0;
Av1LevelBuffer levels = new(this.configuration, new Size(width, height));
bool allZero = this.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext);
int endOfBlock;
if (allZero)
{
if (plane == 0)
{
transformInfo.Type = Av1TransformType.DctDct;
transformInfo.CodeBlockFlag = false;
}
UpdateCoefficientContext(modeInfo, aboveContexts, leftContexts, blocksWide, blocksHigh, transformSize, blockPosition, aboveOffset, leftOffset, culLevel, modeBlocksToRightEdge, modeBlocksToBottomEdge);
return 0;
}
if (plane == (int)Av1Plane.Y)
{
transformInfo.Type = this.ReadTransformType(transformSize, useReducedTransformSet, modeInfo.FilterIntraModeInfo.UseFilterIntra, this.baseQIndex, modeInfo.FilterIntraModeInfo.Mode, modeInfo.YMode);
}
transformInfo.Type = ComputeTransformType(planeType, modeInfo, isLossless, transformSize, transformInfo, useReducedTransformSet);
Av1TransformClass transformClass = transformInfo.Type.ToClass();
Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformInfo.Type);
ReadOnlySpan<short> scan = scanOrder.Scan;
endOfBlock = this.ReadEndOfBlockPosition(transformSize, transformClass, transformSizeContext, planeType);
if (endOfBlock > 1)
{
levels.Clear();
}
this.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, scan, levels, transformSizeContext, planeType);
if (endOfBlock > 1)
{
if (transformClass == Av1TransformClass.Class2D)
{
this.ReadCoefficientsReverse2d(transformSize, 1, endOfBlock - 1 - 1, scan, levels, transformSizeContext, planeType);
this.ReadCoefficientsReverse(transformSize, transformClass, 0, 0, scan, levels, transformSizeContext, planeType);
}
else
{
this.ReadCoefficientsReverse(transformSize, transformClass, 0, endOfBlock - 1 - 1, scan, levels, transformSizeContext, planeType);
}
}
DebugGuard.MustBeGreaterThan(scan.Length, 0, nameof(scan));
culLevel = this.ReadCoefficientsSign(coefficientBuffer, endOfBlock, scan, levels, transformBlockContext.DcSignContext, planeType);
UpdateCoefficientContext(modeInfo, aboveContexts, leftContexts, blocksWide, blocksHigh, transformSize, blockPosition, aboveOffset, leftOffset, culLevel, modeBlocksToRightEdge, modeBlocksToBottomEdge);
transformInfo.CodeBlockFlag = true;
return endOfBlock;
}
public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformClass transformClass, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{
ref Av1SymbolReader r = ref this.reader;
int endOfBlockExtra = 0;
int endOfBlockPoint = this.ReadEndOfBlockFlag(planeType, transformClass, transformSize);
int endOfBlockShift = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPoint];
if (endOfBlockShift > 0)
{
int endOfBlockContext = endOfBlockPoint;
bool bit = this.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext);
if (bit)
{
Av1Math.SetBit(ref endOfBlockExtra, endOfBlockShift - 1);
}
for (int j = 1; j < endOfBlockShift; j++)
{
if (r.ReadLiteral(1) != 0)
{
Av1Math.SetBit(ref endOfBlockExtra, endOfBlockShift - 1 - j);
}
}
}
return Av1SymbolContextHelper.RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra);
}
public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, ReadOnlySpan<short> scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{
int i = endOfBlock - 1;
Point position = levels.GetPosition(scan[i]);
int coefficientContext = Av1SymbolContextHelper.GetLowerLevelContextEndOfBlock(levels, position);
int level = this.ReadBaseEndOfBlock(transformSizeContext, planeType, coefficientContext) + 1;
Av1TransformSize limitedTransformSizeContext = (Av1TransformSize)Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32);
if (level > Av1Constants.BaseLevelsCount)
{
int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContextEndOfBlock(position, transformClass);
this.ReadCoefficientsBaseRangeLoop(transformSizeContext, planeType, baseRangeContext, ref level);
}
levels.GetRow(position)[position.X] = (byte)level;
}
public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan<short> scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{
Av1TransformSize limitedTransformSizeContext = (Av1TransformSize)Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32);
for (int c = endScanIndex; c >= startScanIndex; --c)
{
Point position = levels.GetPosition(scan[c]);
int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext2d(levels, position, transformSize);
int level = this.ReadCoefficientsBase(transformSizeContext, planeType, coefficientContext);
if (level > Av1Constants.BaseLevelsCount)
{
int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext2d(levels, position);
this.ReadCoefficientsBaseRangeLoop(transformSizeContext, planeType, baseRangeContext, ref level);
}
levels.GetRow(position)[position.X] = (byte)level;
}
}
public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan<short> scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{
Av1TransformSize limitedTransformSizeContext = (Av1TransformSize)Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32);
for (int c = endScanIndex; c >= startScanIndex; --c)
{
int pos = scan[c];
Point position = levels.GetPosition(pos);
int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext(levels, position, transformSize, transformClass);
int level = this.ReadCoefficientsBase(transformSizeContext, planeType, coefficientContext);
if (level > Av1Constants.BaseLevelsCount)
{
int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, position, transformClass);
this.ReadCoefficientsBaseRangeLoop(transformSizeContext, planeType, baseRangeContext, ref level);
}
levels.GetRow(position)[position.X] = (byte)level;
}
}
public int ReadCoefficientsSign(Span<int> coefficientBuffer, int endOfBlock, ReadOnlySpan<short> scan, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType)
{
ref Av1SymbolReader r = ref this.reader;
int maxScanLine = 0;
int culLevel = 0;
int dcValue = 0;
coefficientBuffer[0] = endOfBlock;
for (int c = 0; c < endOfBlock; c++)
{
int sign = 0;
Point position = levels.GetPosition(c);
int level = levels[position];
if (level != 0)
{
maxScanLine = Math.Max(maxScanLine, scan[c]);
if (c == 0)
{
sign = this.ReadDcSign(planeType, dcSignContext);
}
else
{
sign = r.ReadLiteral(1);
}
if (level >= Av1Constants.CoefficientBaseRange + Av1Constants.BaseLevelsCount + 1)
{
level += this.ReadGolomb();
}
if (c == 0)
{
dcValue = sign != 0 ? -level : level;
}
level &= 0xfffff;
culLevel += level;
}
coefficientBuffer[c + 1] = sign != 0 ? -level : level;
}
culLevel = Math.Min(Av1Constants.CoefficientContextMask, culLevel);
Av1SymbolContextHelper.SetDcSign(ref culLevel, dcValue);
return culLevel;
}
private int ReadEndOfBlockFlag(Av1PlaneType planeType, Av1TransformClass transformClass, Av1TransformSize transformSize)
{
int endOfBlockContext = transformClass == Av1TransformClass.Class2D ? 0 : 1;
int endOfBlockMultiSize = transformSize.GetLog2Minus4();
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.endOfBlockFlag[endOfBlockMultiSize][(int)planeType][endOfBlockContext]) + 1;
}
private bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.endOfBlockExtra[(int)transformSizeContext][(int)planeType][endOfBlockContext]) > 0;
}
private int ReadCoefficientsBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.coefficientsBaseRange[(int)transformSizeContext][(int)planeType][baseRangeContext]);
}
private int ReadDcSign(Av1PlaneType planeType, int dcSignContext)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.dcSign[(int)planeType][dcSignContext]);
}
private int ReadBaseEndOfBlock(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.baseEndOfBlock[(int)transformSizeContext][(int)planeType][coefficientContext]);
}
private int ReadCoefficientsBase(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext)
{
ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]);
}
private void ReadCoefficientsBaseRangeLoop(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext, ref int level)
{
ref Av1SymbolReader r = ref this.reader;
Av1TransformSize limitedTransformSizeContext = (Av1TransformSize)Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32);
Av1Distribution distribution = this.coefficientsBaseRange[(int)limitedTransformSizeContext][(int)planeType][baseRangeContext];
for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1)
{
int coefficientBaseRange = r.ReadSymbol(distribution);
level += coefficientBaseRange;
if (coefficientBaseRange < Av1Constants.BaseRangeSizeMinus1)
{
break;
}
}
}
internal int ReadGolomb()
{
ref Av1SymbolReader r = ref this.reader;
int x = 1;
int length = 0;
int i = 0;
while (i == 0)
{
i = r.ReadLiteral(1);
++length;
if (length > 20)
{
// SVT_LOG("Invalid length in read_golomb");
break;
}
}
for (i = 0; i < length - 1; ++i)
{
x <<= 1;
x += r.ReadLiteral(1);
}
return x - 1;
}
private static void UpdateCoefficientContext(
Av1BlockModeInfo modeInfo,
int[] aboveContexts,
int[] leftContexts,
int blocksWide,
int blocksHigh,
Av1TransformSize transformSize,
Point blockPosition,
int aboveOffset,
int leftOffset,
int culLevel,
int modeBlockToRightEdge,
int modeBlockToBottomEdge)
{
int transformSizeWide = transformSize.Get4x4WideCount();
int transformSizeHigh = transformSize.Get4x4HighCount();
if (modeBlockToRightEdge < 0)
{
int aboveContextCount = Math.Min(transformSizeWide, blocksWide - aboveOffset);
Array.Fill(aboveContexts, culLevel, 0, aboveContextCount);
Array.Fill(aboveContexts, 0, aboveContextCount, transformSizeWide - aboveContextCount);
}
else
{
Array.Fill(aboveContexts, culLevel, 0, transformSizeWide);
}
if (modeBlockToBottomEdge < 0)
{
int leftContextCount = Math.Min(transformSizeHigh, blocksHigh - leftOffset);
Array.Fill(leftContexts, culLevel, 0, leftContextCount);
Array.Fill(leftContexts, 0, leftContextCount, transformSizeWide - leftContextCount);
}
else
{
Array.Fill(leftContexts, culLevel, 0, transformSizeHigh);
}
}
private static Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1BlockModeInfo modeInfo, bool isLossless, Av1TransformSize transformSize, Av1TransformInfo transformInfo, bool useReducedTransformSet)
{
Av1TransformType transformType = Av1TransformType.DctDct;
if (isLossless || transformSize.GetSquareUpSize() > Av1TransformSize.Size32x32)
{
transformType = Av1TransformType.DctDct;
}
else
{
if (planeType == Av1PlaneType.Y)
{
transformType = transformInfo.Type;
}
else
{
// In intra mode, uv planes don't share the same prediction mode as y
// plane, so the tx_type should not be shared
transformType = Av1SymbolContextHelper.ConvertIntraModeToTransformType(modeInfo, Av1PlaneType.Uv);
}
}
Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet);
if (!transformType.IsExtendedSetUsed(transformSetType))
{
transformType = Av1TransformType.DctDct;
}
return transformType;
}
internal static Av1Distribution GetSplitOrHorizontalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context)
{
Av1Distribution input = inputs[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);
}
return new(Av1Distribution.ProbabilityTop - p);
}
internal static Av1Distribution GetSplitOrVerticalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context)
{
Av1Distribution input = inputs[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);
}
return new(Av1Distribution.ProbabilityTop - p);
}
private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element)
=> probability[(int)element - 1] - probability[(int)element];
}

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

@ -0,0 +1,417 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
internal class Av1SymbolEncoder : IDisposable
{
private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy;
private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes;
private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode;
private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode;
private readonly Av1Distribution[][] transformBlockSkip;
private readonly Av1Distribution[][][] endOfBlockFlag;
private readonly Av1Distribution[][][] coefficientsBaseRange;
private readonly Av1Distribution[][][] coefficientsBase;
private readonly Av1Distribution[][][] coefficientsBaseEndOfBlock;
private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra;
private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode;
private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute;
private readonly Av1Distribution[][] dcSign;
private readonly Av1Distribution[][][] endOfBlockExtra;
private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform;
private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId;
private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta;
private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip;
private readonly Av1Distribution[] skipMode = Av1DefaultDistributions.SkipMode;
private readonly Av1Distribution chromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign;
private readonly Av1Distribution[] chromaFromLumaAlpha = Av1DefaultDistributions.ChromaFromLumaAlpha;
private bool isDisposed;
private readonly Configuration configuration;
private Av1SymbolWriter writer;
private readonly int baseQIndex;
public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex)
{
this.transformBlockSkip = Av1DefaultDistributions.GetTransformBlockSkip(qIndex);
this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex);
this.coefficientsBaseRange = Av1DefaultDistributions.GetCoefficientsBaseRange(qIndex);
this.coefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex);
this.coefficientsBaseEndOfBlock = Av1DefaultDistributions.GetBaseEndOfBlock(qIndex);
this.dcSign = Av1DefaultDistributions.GetDcSign(qIndex);
this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex);
this.configuration = configuration;
this.writer = new(configuration, initialSize);
this.baseQIndex = qIndex;
}
public void WriteUseIntraBlockCopy(bool value)
{
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(value, this.tileIntraBlockCopy);
}
public void WritePartitionType(Av1PartitionType partitionType, int context)
{
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol((int)partitionType, this.tilePartitionTypes[context]);
}
public void WriteSplitOrHorizontal(Av1PartitionType partitionType, Av1BlockSize blockSize, int context)
{
Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context);
int value = partitionType == Av1PartitionType.Split ? 1 : 0;
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(value, distribution);
}
public void WriteSplitOrVertical(Av1PartitionType partitionType, Av1BlockSize blockSize, int context)
{
Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrVerticalDistribution(this.tilePartitionTypes, blockSize, context);
int value = partitionType == Av1PartitionType.Split ? 1 : 0;
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(value, distribution);
}
/// <summary>
/// SVT: av1_write_coeffs_txb_1d
/// </summary>
public int WriteCoefficients(
Av1TransformSize transformSize,
Av1TransformType transformType,
Av1PredictionMode intraDirection,
Span<int> coefficientBuffer,
Av1ComponentType componentType,
Av1TransformBlockContext transformBlockContext,
ushort endOfBlock,
bool useReducedTransformSet,
Av1FilterIntraMode filterIntraMode)
{
int c;
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
Av1TransformClass transformClass = transformType.ToClass();
Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType);
ReadOnlySpan<short> scan = scanOrder.Scan;
int blockWidthLog2 = transformSize.GetBlockWidthLog2();
Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize);
ref Av1SymbolWriter w = ref this.writer;
Av1LevelBuffer levels = new(this.configuration, new Size(width, height));
Span<sbyte> coefficientContexts = new sbyte[width * height];
Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext));
this.WriteTransformBlockSkip(endOfBlock == 0, transformSizeContext, transformBlockContext.SkipContext);
if (endOfBlock == 0)
{
return 0;
}
levels.Initialize(coefficientBuffer);
if (componentType == Av1ComponentType.Luminance)
{
this.WriteTransformType(transformType, transformSize, useReducedTransformSet, this.baseQIndex, filterIntraMode, intraDirection);
}
this.WriteEndOfBlockPosition(endOfBlock, componentType, transformClass, transformSize, transformSizeContext);
Av1SymbolContextHelper.GetNzMapContexts(levels, scan, endOfBlock, transformSize, transformClass, coefficientContexts);
int limitedTransformSizeContext = Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32);
for (c = endOfBlock - 1; c >= 0; --c)
{
short pos = scan[c];
int v = coefficientBuffer[pos];
short coeffContext = coefficientContexts[pos];
Point position = levels.GetPosition(pos);
int level = Math.Abs(v);
if (c == endOfBlock - 1)
{
w.WriteSymbol(Math.Min(level, 3) - 1, this.coefficientsBaseEndOfBlock[(int)transformSizeContext][(int)componentType][coeffContext]);
}
else
{
w.WriteSymbol(Math.Min(level, 3), this.coefficientsBase[(int)transformSizeContext][(int)componentType][coeffContext]);
}
if (level > Av1Constants.BaseLevelsCount)
{
// level is above 1.
int baseRange = level - 1 - Av1Constants.BaseLevelsCount;
int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, position, transformClass);
for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1)
{
int k = Math.Min(baseRange - idx, Av1Constants.BaseRangeSizeMinus1);
w.WriteSymbol(k, this.coefficientsBaseRange[limitedTransformSizeContext][(int)componentType][baseRangeContext]);
if (k < Av1Constants.BaseRangeSizeMinus1)
{
break;
}
}
}
}
// Loop to code all signs in the transform block,
// starting with the sign of DC (if applicable)
int cul_level = 0;
for (c = 0; c < endOfBlock; ++c)
{
short pos = scan[c];
int v = coefficientBuffer[pos];
int level = Math.Abs(v);
cul_level += level;
uint sign = v < 0 ? 1u : 0u;
if (level > 0)
{
if (c == 0)
{
w.WriteSymbol((int)sign, this.dcSign[(int)componentType][transformBlockContext.DcSignContext]);
}
else
{
w.WriteLiteral(sign, 1);
}
if (level > (Av1Constants.CoefficientBaseRange + Av1Constants.BaseLevelsCount))
{
this.WriteGolomb(level - Av1Constants.CoefficientBaseRange - 1 - Av1Constants.BaseLevelsCount);
}
}
}
cul_level = Math.Min(Av1Constants.CoefficientContextMask, cul_level);
// DC value
Av1SymbolContextHelper.SetDcSign(ref cul_level, coefficientBuffer[0]);
return cul_level;
}
internal void WriteEndOfBlockPosition(ushort endOfBlock, Av1ComponentType componentType, Av1TransformClass transformClass, Av1TransformSize transformSize, Av1TransformSize transformSizeContext)
{
short endOfBlockPosition = Av1SymbolContextHelper.GetEndOfBlockPosition(endOfBlock, out int eobExtra);
this.WriteEndOfBlockFlag(componentType, transformClass, transformSize, endOfBlockPosition);
int eobOffsetBitCount = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPosition];
if (eobOffsetBitCount > 0)
{
ref Av1SymbolWriter w = ref this.writer;
int eobShift = eobOffsetBitCount - 1;
int bit = Av1Math.GetBit(eobExtra, eobShift);
w.WriteSymbol(bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockPosition]);
for (int i = 1; i < eobOffsetBitCount; i++)
{
eobShift = eobOffsetBitCount - 1 - i;
bit = Av1Math.GetBit(eobExtra, eobShift);
w.WriteLiteral((uint)bit, 1);
}
}
}
internal void WriteTransformBlockSkip(bool skip, Av1TransformSize transformSizeContext, int skipContext)
{
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(skip, this.transformBlockSkip[(int)transformSizeContext][skipContext]);
}
public IMemoryOwner<byte> Exit()
{
ref Av1SymbolWriter w = ref this.writer;
return w.Exit();
}
public void Dispose()
{
if (!this.isDisposed)
{
this.writer.Dispose();
this.isDisposed = true;
}
}
/// <summary>
/// SVT: write_golomb
/// </summary>
internal void WriteGolomb(int level)
{
uint x = (uint)level + 1u;
int length = (int)Av1Math.Log2_32(x) + 1;
Guard.MustBeGreaterThan(length, 0, nameof(length));
ref Av1SymbolWriter w = ref this.writer;
for (int i = 0; i < length - 1; ++i)
{
w.WriteLiteral(0u, 1);
}
for (int j = length - 1; j >= 0; --j)
{
w.WriteLiteral((x >> j) & 0x01, 1);
}
}
private void WriteEndOfBlockFlag(Av1ComponentType componentType, Av1TransformClass transformClass, Av1TransformSize transformSize, int endOfBlockPosition)
{
int endOfBlockMultiSize = transformSize.GetLog2Minus4();
int endOfBlockContext = transformClass == Av1TransformClass.Class2D ? 0 : 1;
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(endOfBlockPosition - 1, this.endOfBlockFlag[endOfBlockMultiSize][(int)componentType][endOfBlockContext]);
}
/// <summary>
/// SVT: av1_write_tx_type
/// </summary>
internal void WriteTransformType(
Av1TransformType transformType,
Av1TransformSize transformSize,
bool useReducedTransformSet,
int baseQIndex,
Av1FilterIntraMode filterIntraMode,
Av1PredictionMode intraDirection)
{
// bool isInter = mbmi->block_mi.use_intrabc || is_inter_mode(mbmi->block_mi.mode);
Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet);
if (Av1SymbolContextHelper.GetExtendedTransformTypeCount(transformSetType) > 1 && baseQIndex > 0)
{
Av1TransformSize squareTransformSize = transformSize.GetSquareSize();
Guard.MustBeLessThanOrEqualTo((int)squareTransformSize, Av1Constants.ExtendedTransformCount, nameof(squareTransformSize));
int extendedSet = Av1SymbolContextHelper.GetExtendedTransformSet(transformSetType);
// eset == 0 should correspond to a set with only DCT_DCT and there
// is no need to send the tx_type
Guard.MustBeGreaterThan(extendedSet, 0, nameof(extendedSet));
// assert(av1_ext_tx_used[tx_set_type][transformType]);
Av1PredictionMode intraDirectionContext;
if (filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes)
{
intraDirectionContext = filterIntraMode.ToIntraDirection();
}
else
{
intraDirectionContext = intraDirection;
}
Guard.MustBeLessThan((int)intraDirectionContext, 13, nameof(intraDirectionContext));
Guard.MustBeLessThan((int)squareTransformSize, 4, nameof(squareTransformSize));
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(
Av1SymbolContextHelper.ExtendedTransformIndices[(int)transformSetType][(int)transformType],
this.intraExtendedTransform[extendedSet][(int)squareTransformSize][(int)intraDirectionContext]);
}
}
internal void WriteSegmentId(int segmentId, int context)
{
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(segmentId, this.segmentId[context]);
}
internal void WriteSkip(bool skip, int context)
{
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(skip, this.skip[context]);
}
internal void WriteSkipMode(bool skip, int context)
{
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(skip, this.skipMode[context]);
}
internal void WriteFilterIntraMode(Av1FilterIntraMode filterIntraMode, Av1BlockSize blockSize)
{
ref Av1SymbolWriter w = ref this.writer;
bool useFilter = filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes;
w.WriteSymbol(useFilter, this.filterIntra[(int)blockSize]);
if (useFilter)
{
w.WriteSymbol((int)filterIntraMode, this.filterIntraMode);
}
}
/// <summary>
/// SVT: av1_write_delta_q_index
/// </summary>
internal void WriteDeltaQuantizerIndex(int deltaQindex)
{
ref Av1SymbolWriter w = ref this.writer;
bool sign = deltaQindex < 0;
int abs = Math.Abs(deltaQindex);
bool smallval = abs < Av1Constants.DeltaQuantizerSmall;
w.WriteSymbol(Math.Min(abs, Av1Constants.DeltaQuantizerSmall), this.deltaQuantizerAbsolute);
if (!smallval)
{
int rem_bits = Av1Math.MostSignificantBit((uint)(abs - 1));
int threshold = (1 << rem_bits) + 1;
w.WriteLiteral((uint)(rem_bits - 1), 3);
w.WriteLiteral((uint)(abs - threshold), rem_bits);
}
if (abs > 0)
{
w.WriteLiteral(sign);
}
}
internal void WriteLumaMode(Av1PredictionMode lumaMode, byte topContext, byte leftContext)
{
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol((int)lumaMode, this.keyFrameYMode[topContext][leftContext]);
}
internal void WriteAngleDelta(int angleDelta, Av1PredictionMode context)
{
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(angleDelta, this.angleDelta[context - Av1PredictionMode.Vertical]);
}
internal void WriteCdefStrength(int cdefStrength, int bitCount)
{
ref Av1SymbolWriter w = ref this.writer;
w.WriteLiteral((uint)cdefStrength, bitCount);
}
internal void WriteChromaMode(Av1PredictionMode chromaMode, bool isChromaFromLumaAllowed, Av1PredictionMode lumaMode)
{
ref Av1SymbolWriter w = ref this.writer;
int cflAllowed = isChromaFromLumaAllowed ? 1 : 0;
w.WriteSymbol((int)chromaMode, this.uvMode[cflAllowed][(int)lumaMode]);
}
internal void WriteChromaFromLumaAlphas(int chromaFromLumaIndex, int joinedSign)
{
ref Av1SymbolWriter w = ref this.writer;
w.WriteSymbol(joinedSign, this.chromaFromLumaSign);
// Magnitudes are only signaled for nonzero codes.
int signU = ((joinedSign + 1) * 11) >> 5;
if (signU != 0)
{
int contextU = chromaFromLumaIndex - 2;
int indexU = chromaFromLumaIndex >> Av1Constants.ChromaFromLumaAlphabetSizeLog2;
w.WriteSymbol(indexU, this.chromaFromLumaAlpha[contextU]);
}
int signV = (joinedSign + 1) - (3 * signU);
if (signV != 0)
{
int contextV = (signV * 3) - signU - 3;
int indexV = chromaFromLumaIndex & ((1 << Av1Constants.ChromaFromLumaAlphabetSizeLog2) - 1);
w.WriteSymbol(indexV, this.chromaFromLumaAlpha[contextV]);
}
}
}

207
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolReader.cs

@ -0,0 +1,207 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
internal ref struct Av1SymbolReader
{
private const int DecoderWindowsSize = 32;
private const int LotsOfBits = 0x4000;
private readonly Span<byte> buffer;
private int position;
/*
* The difference between the high end of the current range, (low + rng), and
* the coded value, minus 1.
* This stores up to OD_EC_WINDOW_SIZE bits of that difference, but the
* decoder only uses the top 16 bits of the window to decode the next symbol.
* As we shift up during renormalization, if we don't have enough bits left in
* the window to fill the top 16, we'll read in more bits of the coded
* value.
*/
private uint difference;
// The number of values in the current range.
private uint range;
// The number of bits in the current value.
private int count;
public Av1SymbolReader(Span<byte> span)
{
this.buffer = span;
this.position = 0;
this.difference = (1U << (DecoderWindowsSize - 1)) - 1;
this.range = 0x8000;
this.count = -15;
this.Refill();
}
public int ReadSymbol(Av1Distribution distribution)
{
int value = this.DecodeIntegerQ15(distribution);
// UpdateCdf(probabilities, value, numberOfSymbols);
distribution.Update(value);
return value;
}
public int ReadLiteral(int bitCount)
{
const uint prob = (0x7FFFFFU - (128 << 15) + 128) >> 8;
int literal = 0;
for (int bit = bitCount - 1; bit >= 0; bit--)
{
if (this.DecodeBoolQ15(prob))
{
literal |= 1 << bit;
}
}
return literal;
}
/// <summary>
/// Decode a single binary value.
/// </summary>
/// <param name="frequency">The probability that the bit is one, scaled by 32768.</param>
private bool DecodeBoolQ15(uint frequency)
{
uint dif;
uint vw;
uint range;
uint newRange;
uint v;
bool ret;
// assert(0 < f);
// assert(f < 32768U);
dif = this.difference;
range = this.range;
// assert(dif >> (DecoderWindowsSize - 16) < r);
// assert(32768U <= r);
v = ((range >> 8) * (frequency >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift);
v += Av1Distribution.ProbabilityMinimum;
vw = v << (DecoderWindowsSize - 16);
ret = true;
newRange = v;
if (dif >= vw)
{
newRange = range - v;
dif -= vw;
ret = false;
}
this.Normalize(dif, newRange);
return ret;
}
/// <summary>
/// Decodes a symbol given an inverse cumulative distribution function(CDF) table in Q15.
/// </summary>
/// <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>
/// <returns>The decoded symbol.</returns>
private int DecodeIntegerQ15(Av1Distribution distribution)
{
uint c;
uint u;
uint v;
int ret;
uint dif = this.difference;
uint r = this.range;
int n = distribution.NumberOfSymbols - 1;
DebugGuard.MustBeLessThan(dif >> (DecoderWindowsSize - 16), r, nameof(r));
DebugGuard.IsTrue(distribution[n] == 0, "Last value in probability array needs to be zero.");
DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r));
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) * (distribution[++ret] >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift);
v += (uint)(Av1Distribution.ProbabilityMinimum * (n - ret));
}
while (c < v);
DebugGuard.MustBeLessThan(v, u, nameof(v));
DebugGuard.MustBeLessThanOrEqualTo(u, r, nameof(u));
r = u - v;
dif -= v << (DecoderWindowsSize - 16);
this.Normalize(dif, r);
return ret;
}
/// <summary>
/// Takes updated dif and range values, renormalizes them so that
/// <paramref name="rng"/> has value between 32768 and 65536 (reading more bytes from the stream into dif if
/// necessary), and stores them back in the decoder context.
/// </summary>
private void Normalize(uint dif, uint rng)
{
int d;
// assert(rng <= 65535U);
/*The number of leading zeros in the 16-bit binary representation of rng.*/
d = 15 - Av1Math.MostSignificantBit(rng);
/*d bits in dec->dif are consumed.*/
this.count -= d;
/*This is equivalent to shifting in 1's instead of 0's.*/
this.difference = ((dif + 1) << d) - 1;
this.range = rng << d;
if (this.count < 0)
{
this.Refill();
}
}
private void Refill()
{
int s;
uint dif = this.difference;
int cnt = this.count;
int position = this.position;
int end = this.buffer.Length;
s = DecoderWindowsSize - 9 - (cnt + 15);
for (; s >= 0 && position < end; s -= 8, position++)
{
/*Each time a byte is inserted into the window (dif), bptr advances and cnt
is incremented by 8, so the total number of consumed bits (the return
value of od_ec_dec_tell) does not change.*/
DebugGuard.MustBeLessThan(s, DecoderWindowsSize - 8, nameof(s));
dif ^= (uint)this.buffer[position] << s;
cnt += 8;
}
if (position >= end)
{
/*
* We've reached the end of the buffer. It is perfectly valid for us to need
* to fill the window with additional bits past the end of the buffer (and
* this happens in normal operation). These bits should all just be taken
* as zero. But we cannot increment bptr past 'end' (this is undefined
* behavior), so we start to increment dec->tell_offs. We also don't want
* to keep testing bptr against 'end', so we set cnt to OD_EC_LOTS_OF_BITS
* and adjust dec->tell_offs so that the total number of unconsumed bits in
* the window (dec->cnt - dec->tell_offs) does not change. This effectively
* puts lots of zero bits into the window, and means we won't try to refill
* it from the buffer for a very long time (at which point we'll put lots
* of zero bits into the window again).
*/
cnt = LotsOfBits;
}
this.difference = dif;
this.count = cnt;
this.position = position;
}
}

216
src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs

@ -0,0 +1,216 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy;
internal class Av1SymbolWriter : IDisposable
{
private uint low;
private uint rng = 0x8000U;
// Count is initialized to -9 so that it crosses zero after we've accumulated one byte + one carry bit.
private int cnt = -9;
private readonly Configuration configuration;
private readonly AutoExpandingMemory<ushort> memory;
private int position;
public Av1SymbolWriter(Configuration configuration, int initialSize)
{
this.configuration = configuration;
this.memory = new AutoExpandingMemory<ushort>(configuration, (initialSize + 1) >> 1);
}
public void Dispose() => this.memory.Dispose();
public void WriteSymbol(bool symbol, Av1Distribution distribution)
=> this.WriteSymbol(symbol ? 1 : 0, distribution);
public void WriteSymbol(int symbol, Av1Distribution distribution)
{
DebugGuard.MustBeGreaterThanOrEqualTo(symbol, 0, nameof(symbol));
DebugGuard.MustBeLessThan(symbol, distribution.NumberOfSymbols, nameof(symbol));
DebugGuard.IsTrue(distribution[distribution.NumberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero.");
this.EncodeIntegerQ15(symbol, distribution);
distribution.Update(symbol);
}
public void WriteLiteral(bool value) => this.WriteLiteral(value ? 1u : 0u, 1);
public void WriteLiteral(uint value, int bitCount)
{
const uint p = 0x4000U; // (0x7FFFFFU - (128 << 15) + 128) >> 8;
for (int bit = bitCount - 1; bit >= 0; bit--)
{
bool bitValue = ((value >> bit) & 0x1) > 0;
this.EncodeBoolQ15(bitValue, p);
}
}
public IMemoryOwner<byte> Exit()
{
// We output the minimum number of bits that ensures that the symbols encoded
// thus far will be decoded correctly regardless of the bits that follow.
uint l = this.low;
int c = this.cnt;
int pos = this.position;
int s = 10;
uint m = 0x3FFFU;
uint e = ((l + m) & ~m) | (m + 1);
s += c;
Span<ushort> buffer = this.memory.GetSpan(this.position + ((s + 7) >> 3));
if (s > 0)
{
uint n = (1U << (c + 16)) - 1;
do
{
buffer[pos] = (ushort)(e >> (c + 16));
pos++;
e &= n;
s -= 8;
c -= 8;
n >>= 8;
}
while (s > 0);
}
c = Math.Max((s + 7) >> 3, 0);
IMemoryOwner<byte> output = this.configuration.MemoryAllocator.Allocate<byte>(pos + c);
// Perform carry propagation.
Span<byte> outputSlice = output.GetSpan()[(output.Length() - pos)..];
c = 0;
while (pos > 0)
{
pos--;
c = buffer[pos] + c;
outputSlice[pos] = (byte)c;
c >>= 8;
}
return output;
}
/// <summary>
/// Encode a single binary value.
/// </summary>
/// <param name="val">The value to encode.</param>
/// <param name="frequency">The probability that the value is true, scaled by 32768.</param>
private void EncodeBoolQ15(bool val, uint frequency)
{
uint l;
uint r;
uint v;
DebugGuard.MustBeGreaterThan(frequency, 0U, nameof(frequency));
DebugGuard.MustBeLessThanOrEqualTo(frequency, 32768U, nameof(frequency));
l = this.low;
r = this.rng;
DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r));
v = ((r >> 8) * (frequency >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift);
v += Av1Distribution.ProbabilityMinimum;
if (val)
{
l += r - v;
r = v;
}
else
{
r -= v;
}
this.Normalize(l, r);
}
/// <summary>
/// Encodes a symbol given an inverse cumulative distribution function(CDF) table in Q15.
/// </summary>
/// <param name="symbol">The value to encode.</param>
/// <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>
private void EncodeIntegerQ15(int symbol, Av1Distribution distribution)
=> this.EncodeIntegerQ15(symbol > 0 ? distribution[symbol - 1] : Av1Distribution.ProbabilityTop, distribution[symbol], symbol, distribution.NumberOfSymbols);
private void EncodeIntegerQ15(uint lowFrequency, uint highFrequency, int symbol, int numberOfSymbols)
{
const int totalShift = 7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift;
uint l = this.low;
uint r = this.rng;
DebugGuard.MustBeLessThanOrEqualTo(32768U, r, nameof(r));
DebugGuard.MustBeLessThanOrEqualTo(highFrequency, lowFrequency, nameof(highFrequency));
DebugGuard.MustBeLessThanOrEqualTo(lowFrequency, 32768U, nameof(lowFrequency));
DebugGuard.MustBeGreaterThanOrEqualTo(totalShift, 0, nameof(totalShift));
int n = numberOfSymbols - 1;
if (lowFrequency < Av1Distribution.ProbabilityTop)
{
uint u;
uint v;
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 >> Av1Distribution.ProbabilityShift)) >> totalShift) +
(Av1Distribution.ProbabilityMinimum * (n - symbol)));
}
this.Normalize(l, r);
}
/// <summary>
/// Takes updated low and range values, renormalizes them so that <paramref name="rng"/>
/// lies between 32768 and 65536 (flushing bytes from low to the pre-carry buffer if necessary),
/// and stores them back in the encoder context.
/// </summary>
/// <param name="low">The new value of <see cref="low"/>.</param>
/// <param name="rng">The new value of <see cref="rng"/>.</param>
private void Normalize(uint low, uint rng)
{
int d;
int c;
int s;
c = this.cnt;
DebugGuard.MustBeLessThanOrEqualTo(rng, 65535U, nameof(rng));
d = 15 - Av1Math.MostSignificantBit(rng);
s = c + d;
/*TODO: Right now we flush every time we have at least one byte available.
Instead we should use an OdEcWindow and flush right before we're about to
shift bits off the end of the window.
For a 32-bit window this is about the same amount of work, but for a 64-bit
window it should be a fair win.*/
if (s >= 0)
{
uint m;
Span<ushort> buffer = this.memory.GetSpan(this.position + 2);
c += 16;
m = (1U << c) - 1;
if (s >= 8)
{
buffer[this.position] = (ushort)(low >> c);
this.position++;
low &= m;
c -= 8;
m >>= 8;
}
buffer[this.position] = (ushort)(low >> c);
this.position++;
s = c + d - 24;
low &= m;
}
this.low = low << d;
this.rng = rng << d;
this.cnt = s;
}
}

19
src/ImageSharp/Formats/Heif/Av1/IAv1TileReader.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Interface for reading of image tiles.
/// </summary>
internal interface IAv1TileReader
{
/// <summary>
/// Read the information for 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 read.</param>
void ReadTile(Span<byte> tileData, int tileNum);
}

19
src/ImageSharp/Formats/Heif/Av1/IAv1TileWriter.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
/// <summary>
/// Interface for writing of image tiles.
/// </summary>
internal interface IAv1TileWriter
{
/// <summary>
/// Write the information for a single tile.
/// </summary>
/// <param name="tileNum">The index of the tile that is to be read.</param>
/// <returns>
/// The bytes of encoded data in the bitstream dedicated to this tile.
/// </returns>
Span<byte> WriteTile(int tileNum);
}

111
src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometry.cs

@ -0,0 +1,111 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision;
internal class Av1BlockGeometry
{
private Av1BlockSize blockSize;
private Av1BlockSize blockSizeUv;
public Av1BlockGeometry()
{
this.RedunancyList = [];
this.TransformOrigin = new Point[Av1Constants.MaxVarTransform + 1][];
for (int i = 0; i < this.TransformOrigin.Length; i++)
{
this.TransformOrigin[i] = new Point[Av1Constants.MaxTransformBlockCount];
}
}
public Av1BlockSize BlockSize
{
get => this.blockSize;
internal set
{
this.blockSize = value;
this.BlockWidth = value.GetWidth();
this.BlockHeight = value.GetHeight();
}
}
public Av1BlockSize BlockSizeUv
{
get => this.blockSizeUv;
internal set
{
this.blockSizeUv = value;
this.BlockWidthUv = value.GetWidth();
this.BlockHeightUv = value.GetHeight();
}
}
/// <summary>
/// Gets or sets the Origin point from lop left of the superblock.
/// </summary>
public Point Origin { get; internal set; }
public bool HasUv { get; internal set; }
/// <summary>
/// Gets the blocks width.
/// </summary>
public int BlockWidth { get; private set; }
/// <summary>
/// Gets the blocks height.
/// </summary>
public int BlockHeight { get; private set; }
public int[] TransformBlockCount { get; } = new int[Av1Constants.MaxVarTransform + 1];
public Av1TransformSize[] TransformSize { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1];
public Av1TransformSize[] TransformSizeUv { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1];
public Point[][] TransformOrigin { get; private set; }
/// <summary>
/// Gets or sets the blocks index in the Mode Decision scan.
/// </summary>
public int ModeDecisionIndex { get; set; }
/// <summary>
/// Gets or sets the offset to the next nsq block (skip remaining d2 blocks).
/// </summary>
public int NextDepthOffset { get; set; }
/// <summary>
/// Gets or sets the offset to the next d1 sq block
/// </summary>
public int Depth1Offset { get; set; }
/// <summary>
/// Gets a value indicating whether this block is redundant to another.
/// </summary>
public bool IsRedundant => this.RedunancyList.Count > 0;
/// <summary>
/// Gets or sets the list where the block is redundant.
/// </summary>
public List<int> RedunancyList { get; internal set; }
/// <summary>
/// Gets or sets the non square index within a partition 0..totns-1
/// </summary>
public int NonSquareIndex { get; internal set; }
public int TotalNonSuareCount { get; internal set; }
public int BlockWidthUv { get; private set; }
public int BlockHeightUv { get; private set; }
public int Depth { get; internal set; }
public int SequenceSize { get; internal set; }
public bool IsLastQuadrant { get; internal set; }
}

989
src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1BlockGeometryFactory.cs

@ -0,0 +1,989 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision;
internal class Av1BlockGeometryFactory
{
private const int MaxBlocksAllocated = 4421;
private const int NotUsedValue = 0;
private static readonly int[][][] NonSkipQuarterOffMult =
[
// 9 means not used.
// | x | | y |
/*P=0*/ [[0, 9, 9, 9], [0, 9, 9, 9]],
/*P=1*/ [[0, 0, 9, 9], [0, 2, 9, 9]],
/*P=2*/ [[0, 2, 9, 9], [0, 0, 9, 9]],
/*P=7*/ [[0, 0, 0, 0], [0, 1, 2, 3]],
/*P=8*/ [[0, 1, 2, 3], [0, 0, 0, 0]],
/*P=3*/ [[0, 2, 0, 9], [0, 0, 2, 9]],
/*P=4*/ [[0, 0, 2, 9], [0, 2, 2, 9]],
/*P=5*/ [[0, 0, 2, 9], [0, 2, 0, 9]],
/*P=6*/ [[0, 2, 2, 9], [0, 0, 2, 9]]
];
private static readonly uint[][][] NonSkipSizeMult =
[
// 9 means not used.
// | h | | v |
/*P=0*/ [[4, 9, 9, 9], [4, 9, 9, 9]],
/*P=1*/ [[4, 4, 9, 9], [2, 2, 9, 9]],
/*P=2*/ [[2, 2, 9, 9], [4, 4, 9, 9]],
/*P=7*/ [[4, 4, 4, 4], [1, 1, 1, 1]],
/*P=8*/ [[1, 1, 1, 1], [4, 4, 4, 4]],
/*P=3*/ [[2, 2, 4, 9], [2, 2, 2, 9]],
/*P=4*/ [[4, 2, 2, 9], [2, 2, 2, 9]],
/*P=5*/ [[2, 2, 2, 9], [2, 2, 4, 9]],
/*P=6*/ [[2, 2, 2, 9], [4, 2, 2, 9]]
];
// gives the index of next quadrant child within a depth
private static readonly int[][] NonSkipDepthOffset =
[
[85, 21, 5, 1, NotUsedValue, NotUsedValue],
[105, 25, 5, 1, NotUsedValue, NotUsedValue],
[169, 41, 9, 1, NotUsedValue, NotUsedValue],
[425, 105, 25, 5, NotUsedValue, NotUsedValue],
[681, 169, 41, 9, 1, NotUsedValue],
[849, 209, 49, 9, 1, NotUsedValue],
[1101, 269, 61, 9, 1, NotUsedValue],
[4421, 1101, 269, 61, 9, 1],
[2377, 593, 145, 33, 5, NotUsedValue]
];
// gives the next depth block(first qudrant child) from a given parent square
private static readonly int[][] Depth1DepthOffset =
[
[1, 1, 1, 1, 1, NotUsedValue],
[5, 5, 1, 1, 1, NotUsedValue],
[5, 5, 5, 1, 1, NotUsedValue],
[5, 5, 5, 5, 1, NotUsedValue],
[5, 5, 5, 5, 1, NotUsedValue],
[13, 13, 13, 5, 1, NotUsedValue],
[25, 25, 25, 5, 1, NotUsedValue],
[17, 25, 25, 25, 5, 1],
[5, 13, 13, 13, 5, NotUsedValue]
];
private static Av1GeometryIndex geometryIndex;
private static int maxSuperblock;
private static int maxDepth;
private static int maxPart;
// private static int maxActiveBlockCount;
private readonly Av1BlockGeometry[] blockGeometryModeDecisionScan;
/// <summary>
/// Initializes a new instance of the <see cref="Av1BlockGeometryFactory"/> class.
/// </summary>
/// <remarks>SVT: md_scan_all_blks</remarks>
public Av1BlockGeometryFactory(Av1GeometryIndex geom)
{
this.blockGeometryModeDecisionScan = new Av1BlockGeometry[MaxBlocksAllocated];
int max_block_count;
geometryIndex = geom;
byte min_nsq_bsize;
if (geom == Av1GeometryIndex.Geometry0)
{
maxSuperblock = 64;
maxDepth = 4;
maxPart = 1;
max_block_count = 85;
min_nsq_bsize = 16;
}
else if (geom == Av1GeometryIndex.Geometry1)
{
maxSuperblock = 64;
maxDepth = 4;
maxPart = 3;
max_block_count = 105;
min_nsq_bsize = 16;
}
else if (geom == Av1GeometryIndex.Geometry2)
{
maxSuperblock = 64;
maxDepth = 4;
maxPart = 3;
max_block_count = 169;
min_nsq_bsize = 8;
}
else if (geom == Av1GeometryIndex.Geometry3)
{
maxSuperblock = 64;
maxDepth = 4;
maxPart = 3;
max_block_count = 425;
min_nsq_bsize = 0;
}
else if (geom == Av1GeometryIndex.Geometry4)
{
maxSuperblock = 64;
maxDepth = 5;
maxPart = 3;
max_block_count = 681;
min_nsq_bsize = 0;
}
else if (geom == Av1GeometryIndex.Geometry5)
{
maxSuperblock = 64;
maxDepth = 5;
maxPart = 5;
max_block_count = 849;
min_nsq_bsize = 0;
}
else if (geom == Av1GeometryIndex.Geometry6)
{
maxSuperblock = 64;
maxDepth = 5;
maxPart = 9;
max_block_count = 1101;
min_nsq_bsize = 0;
}
else if (geom == Av1GeometryIndex.Geometry7)
{
maxSuperblock = 128;
maxDepth = 6;
maxPart = 9;
max_block_count = 4421;
min_nsq_bsize = 0;
}
else
{
maxSuperblock = 128;
maxDepth = 5;
maxPart = 5;
max_block_count = 2377;
min_nsq_bsize = 0;
}
// (0)compute total number of blocks using the information provided
// maxActiveBlockCount = CountTotalNumberOfActiveBlocks(min_nsq_bsize);
// if (maxActiveBlockCount != max_block_count)
// SVT_LOG(" \n\n Error %i blocks\n\n ", maxActiveBlockCount);
// (2) Construct md scan blk_geom_mds: use info from dps
int idx_mds = 0;
this.ScanAllBlocks(ref idx_mds, maxSuperblock, 0, 0, false, 0, min_nsq_bsize);
LogRedundancySimilarity(max_block_count);
}
/// <summary>
/// SVT: count_total_num_of_active_blks
/// </summary>
private static int CountTotalNumberOfActiveBlocks(int min_nsq_bsize)
{
int depth_scan_idx = 0;
for (int depthIterator = 0; depthIterator < maxDepth; depthIterator++)
{
int totalSquareCount = 1 << depthIterator;
int sequenceSize = depthIterator == 0 ? maxSuperblock
: depthIterator == 1 ? maxSuperblock / 2
: depthIterator == 2 ? maxSuperblock / 4
: depthIterator == 3 ? maxSuperblock / 8
: depthIterator == 4 ? maxSuperblock / 16 : maxSuperblock / 32;
int max_part_updated = sequenceSize == 128 ? Math.Min(maxPart, maxPart < 9 && maxPart > 3 ? 3 : 7)
: sequenceSize == 8 ? Math.Min(maxPart, 3)
: sequenceSize == 4 ? 1 : maxPart;
if (sequenceSize <= min_nsq_bsize)
{
max_part_updated = 1;
}
for (int squareIteratorY = 0; squareIteratorY < totalSquareCount; squareIteratorY++)
{
for (int squareIteratorX = 0; squareIteratorX < totalSquareCount; squareIteratorX++)
{
for (int partitionIterator = 0; partitionIterator < max_part_updated; partitionIterator++)
{
int tot_num_ns_per_part = GetNonSquareCountPerPart(partitionIterator, sequenceSize);
depth_scan_idx += tot_num_ns_per_part;
}
}
}
}
return depth_scan_idx;
}
/// <summary>
/// SVT: get_num_ns_per_part
/// </summary>
private static int GetNonSquareCountPerPart(int partitionIterator, int sequenceSize)
{
int tot_num_ns_per_part = partitionIterator < 1 ? 1 : partitionIterator < 3 ? 2 : partitionIterator < 5 && sequenceSize < 128 ? 4 : 3;
return tot_num_ns_per_part;
}
/// <summary>
/// SVT: log_redundancy_similarity
/// </summary>
private static void LogRedundancySimilarity(int max_block_count)
{
for (int blockIterator = 0; blockIterator < max_block_count; blockIterator++)
{
Av1BlockGeometry cur_geom = GetBlockGeometryByModeDecisionScanIndex(blockIterator);
cur_geom.RedunancyList.Clear();
for (int searchIterator = 0; searchIterator < max_block_count; searchIterator++)
{
Av1BlockGeometry search_geom = GetBlockGeometryByModeDecisionScanIndex(searchIterator);
if (cur_geom.BlockSize == search_geom.BlockSize &&
cur_geom.Origin == search_geom.Origin &&
searchIterator != blockIterator)
{
if (cur_geom.NonSquareIndex == 0 && search_geom.NonSquareIndex == 0 && cur_geom.RedunancyList.Count < 3)
{
cur_geom.RedunancyList.Add(search_geom.ModeDecisionIndex);
}
}
}
}
}
/// <summary>
/// SVT: get_blk_geom_mds
/// </summary>
public static Av1BlockGeometry GetBlockGeometryByModeDecisionScanIndex(int modeDecisionScanIndex) => throw new NotImplementedException();
private void ScanAllBlocks(ref int index, int sequenceSize, int x, int y, bool isLastQuadrant, byte quadIterator, byte minNonSquareBlockSize)
{
// The input block is the parent square block of size sq_size located at pos (x,y)
Guard.MustBeLessThanOrEqualTo(quadIterator, (byte)3, nameof(quadIterator));
int halfsize = sequenceSize / 2;
int quartsize = sequenceSize / 4;
int max_part_updated = sequenceSize == 128 ? Math.Min(maxPart, maxPart is < 9 and > 3 ? 3 : 7)
: sequenceSize == 8 ? Math.Min(maxPart, 3)
: sequenceSize == 4 ? 1 : maxPart;
if (sequenceSize <= minNonSquareBlockSize)
{
max_part_updated = 1;
}
int sqi_mds = index;
for (int partitionIterator = 0; partitionIterator < max_part_updated; partitionIterator++)
{
int tot_num_ns_per_part = GetNonSquareCountPerPart(partitionIterator, sequenceSize);
for (int nonSquareIterator = 0; nonSquareIterator < tot_num_ns_per_part; nonSquareIterator++)
{
this.blockGeometryModeDecisionScan[index].Depth = sequenceSize == maxSuperblock / 1 ? 0
: sequenceSize == maxSuperblock / 2 ? 1
: sequenceSize == maxSuperblock / 4 ? 2
: sequenceSize == maxSuperblock / 8 ? 3
: sequenceSize == maxSuperblock / 16 ? 4 : 5;
this.blockGeometryModeDecisionScan[index].SequenceSize = sequenceSize;
this.blockGeometryModeDecisionScan[index].IsLastQuadrant = isLastQuadrant;
// part_it >= 3 for 128x128 blocks corresponds to HA/HB/VA/VB shapes since H4/V4 are not allowed
// for 128x128 blocks. Therefore, need to offset part_it by 2 to not index H4/V4 shapes.
int part_it_idx = partitionIterator >= 3 && sequenceSize == 128 ? partitionIterator + 2 : partitionIterator;
this.blockGeometryModeDecisionScan[index].Origin = new Point(
x + (quartsize * NonSkipQuarterOffMult[part_it_idx][0][nonSquareIterator]),
y + (quartsize * NonSkipQuarterOffMult[part_it_idx][1][nonSquareIterator]));
// These properties aren't used.
// this.blockGeometryModeDecisionScan[index].Shape = (Part)part_it_idx;
// this.blockGeometryModeDecisionScan[index].QuadIndex = quadIterator;
// this.blockGeometryModeDecisionScan[index].d1i = depth1Iterator++;
// this.blockGeometryModeDecisionScan[index].sqi_mds = sqi_mds;
// this.blockGeometryModeDecisionScan[index].svt_aom_geom_idx = svt_aom_geom_idx;
/*
this.blockGeometryModeDecisionScan[index].parent_depth_idx_mds = sqi_mds == 0
? 0
: (sqi_mds + (3 - quad_it) * ns_depth_offset[svt_aom_geom_idx][this.blockGeometryModeDecisionScan[index].Depth]) -
parent_depth_offset[svt_aom_geom_idx][this.blockGeometryModeDecisionScan[index].Depth];*/
this.blockGeometryModeDecisionScan[index].Depth1Offset =
Depth1DepthOffset[(int)geometryIndex][this.blockGeometryModeDecisionScan[index].Depth];
this.blockGeometryModeDecisionScan[index].NextDepthOffset =
NonSkipDepthOffset[(int)geometryIndex][this.blockGeometryModeDecisionScan[index].Depth];
this.blockGeometryModeDecisionScan[index].TotalNonSuareCount = tot_num_ns_per_part;
this.blockGeometryModeDecisionScan[index].NonSquareIndex = nonSquareIterator;
uint blockWidth = (uint)quartsize * NonSkipSizeMult[part_it_idx][0][nonSquareIterator];
uint blockHeight = (uint)quartsize * NonSkipSizeMult[part_it_idx][1][nonSquareIterator];
this.blockGeometryModeDecisionScan[index].BlockSize =
Av1BlockSizeExtensions.FromWidthAndHeight(Av1Math.Log2_32(blockWidth) - 2u, Av1Math.Log2_32(blockHeight) - 2u);
this.blockGeometryModeDecisionScan[index].BlockSizeUv = this.blockGeometryModeDecisionScan[index].BlockSize.GetSubsampled(true, true);
// this.blockGeometryModeDecisionScan[index].BlockWidthUv = Math.Max(4, this.blockGeometryModeDecisionScan[index].BlockWidth >> 1);
// this.blockGeometryModeDecisionScan[index].BlockHeightUv = Math.Max(4, this.blockGeometryModeDecisionScan[index].BlockHeight >> 1);
this.blockGeometryModeDecisionScan[index].HasUv = true;
if (this.blockGeometryModeDecisionScan[index].BlockWidth == 4 && this.blockGeometryModeDecisionScan[index].BlockHeight == 4)
{
this.blockGeometryModeDecisionScan[index].HasUv = isLastQuadrant;
}
else if ((this.blockGeometryModeDecisionScan[index].BlockWidth >> 1) < this.blockGeometryModeDecisionScan[index].BlockWidthUv ||
(this.blockGeometryModeDecisionScan[index].BlockHeight >> 1) < this.blockGeometryModeDecisionScan[index].BlockHeightUv)
{
int num_blk_same_uv = 1;
if (this.blockGeometryModeDecisionScan[index].BlockWidth >> 1 < 4)
{
num_blk_same_uv *= 2;
}
if (this.blockGeometryModeDecisionScan[index].BlockHeight >> 1 < 4)
{
num_blk_same_uv *= 2;
}
// if (this.blockGeometryModeDecisionScan[index].nsi % 2 == 0)
// if (this.blockGeometryModeDecisionScan[index].nsi != (this.blockGeometryModeDecisionScan[index].totns-1) )
if (this.blockGeometryModeDecisionScan[index].NonSquareIndex != (num_blk_same_uv - 1) &&
this.blockGeometryModeDecisionScan[index].NonSquareIndex != ((2 * num_blk_same_uv) - 1))
{
this.blockGeometryModeDecisionScan[index].HasUv = false;
}
}
// tx_depth 1 geom settings
int tx_depth = 0;
this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128
? 4
: this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block128x64 or Av1BlockSize.Block64x128
? 2
: 1;
for (int transformBlockIterator = 0; transformBlockIterator < this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth]; transformBlockIterator++)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] =
GetTransformSize(this.blockGeometryModeDecisionScan[index].BlockSize, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] =
GetTransformSize(this.blockGeometryModeDecisionScan[index].BlockSize, 1);
if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128)
{
int offsetx = (transformBlockIterator is 0 or 2) ? 0 : 64;
int offsety = (transformBlockIterator is 0 or 1) ? 0 : 64;
Size offset = new(offsetx, offsety);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x64)
{
int offsetx = (transformBlockIterator == 0) ? 0 : 64;
int offsety = 0;
Size offset = new(offsetx, offsety);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x128)
{
int offsetx = 0;
int offsety = (transformBlockIterator == 0) ? 0 : 64;
Size offset = new(offsetx, offsety);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else
{
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin;
}
/*if (this.blockGeometryModeDecisionScan[index].bsize == BLOCK_16X8)
SVT_LOG("");
this.blockGeometryModeDecisionScan[index].tx_width[tx_depth] =
tx_size_wide[this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth]];
this.blockGeometryModeDecisionScan[index].tx_height[tx_depth] =
tx_size_high[this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth]];
this.blockGeometryModeDecisionScan[index].tx_width_uv[tx_depth] =
tx_size_wide[this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth]];
this.blockGeometryModeDecisionScan[index].tx_height_uv[tx_depth] =
tx_size_high[this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth]];*/
}
// tx_depth 1 geom settings
tx_depth = 1;
this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128
? 4
: this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block128x64 or Av1BlockSize.Block64x128
? 2
: 1;
if (this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block64x64 or
Av1BlockSize.Block32x32 or
Av1BlockSize.Block16x16 or
Av1BlockSize.Block8x8)
{
this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = 4;
}
if (this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block64x32 or
Av1BlockSize.Block32x64 or
Av1BlockSize.Block32x16 or
Av1BlockSize.Block16x32 or
Av1BlockSize.Block16x8 or
Av1BlockSize.Block8x16)
{
this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = 2;
}
if (this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block64x16 or
Av1BlockSize.Block16x64 or
Av1BlockSize.Block32x8 or
Av1BlockSize.Block8x32 or
Av1BlockSize.Block16x4 or
Av1BlockSize.Block4x16)
{
this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = 2;
}
for (int transformBlockIterator = 0; transformBlockIterator < this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth]; transformBlockIterator++)
{
if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x64)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block32x32, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 32, 0, 32];
int[] offsety = [0, 0, 32, 32];
// 0 1
// 2 3
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x32)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block32x32, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 32];
int[] offsety = [0, 0];
// 0 1
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x64)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block32x32, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 0];
int[] offsety = [0, 32];
// 0 1
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x32)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 16, 0, 16];
int[] offsety = [0, 0, 16, 16];
// 0 1
// 2 3
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x16)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 16];
int[] offsety = [0, 0];
// 0 1
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x32)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 0];
int[] offsety = [0, 16];
// 0 1
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x16)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 8, 0, 8];
int[] offsety = [0, 0, 8, 8];
// 0 1
// 2 3
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x8)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 8];
int[] offsety = [0, 0];
// 0 1
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block8x16)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 0];
int[] offsety = [0, 8];
// 0 1
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block8x8)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x4, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 4, 0, 4];
int[] offsety = [0, 0, 4, 4];
// 0 1
// 2 3
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x16)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block32x16, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 32];
int[] offsety = [0, 0];
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x64)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x32, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 0];
int[] offsety = [0, 32];
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x8)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x8, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 16];
int[] offsety = [0, 0];
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block8x32)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x16, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
// 0 1 2 3
int[] offsetx = [0, 0];
int[] offsety = [0, 16];
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x4)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x4, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 8];
int[] offsety = [0, 0];
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block4x16)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x8, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx = [0, 0];
int[] offsety = [0, 8];
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else
{
if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(
this.blockGeometryModeDecisionScan[index].BlockSize, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] =
this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int offsetx = (transformBlockIterator is 0 or 2) ? 0 : 64;
int offsety = (transformBlockIterator is 0 or 1) ? 0 : 64;
Size offset = new(offsetx, offsety);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x64)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(
this.blockGeometryModeDecisionScan[index].BlockSize, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] =
this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int offsetx = (transformBlockIterator is 0) ? 0 : 64;
int offsety = 0;
Size offset = new(offsetx, offsety);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x128)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(
this.blockGeometryModeDecisionScan[index].BlockSize, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] =
this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int offsetx = 0;
int offsety = (transformBlockIterator is 0) ? 0 : 64;
Size offset = new(offsetx, offsety);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(
this.blockGeometryModeDecisionScan[index].BlockSize, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] =
this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin;
}
}
/*this.blockGeometryModeDecisionScan[index].tx_width[tx_depth] =
tx_size_wide[this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth]];
this.blockGeometryModeDecisionScan[index].tx_height[tx_depth] =
tx_size_high[this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth]];
this.blockGeometryModeDecisionScan[index].tx_width_uv[tx_depth] = this.blockGeometryModeDecisionScan[index].tx_width_uv[0];
this.blockGeometryModeDecisionScan[index].tx_height_uv[tx_depth] = this.blockGeometryModeDecisionScan[index].tx_height_uv[0];*/
}
// tx_depth 2 geom settings
tx_depth = 2;
this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128
? 4
: this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block128x64 or
Av1BlockSize.Block64x128
? 2
: 1;
if (this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block64x64 or
Av1BlockSize.Block32x32 or
Av1BlockSize.Block16x16)
{
this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = 16;
}
if (this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block64x32 or
Av1BlockSize.Block32x64 or
Av1BlockSize.Block32x16 or
Av1BlockSize.Block16x32 or
Av1BlockSize.Block16x8 or
Av1BlockSize.Block8x16)
{
this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = 8;
}
if (this.blockGeometryModeDecisionScan[index].BlockSize is Av1BlockSize.Block64x16 or
Av1BlockSize.Block16x64 or
Av1BlockSize.Block32x8 or
Av1BlockSize.Block8x32 or
Av1BlockSize.Block16x4 or
Av1BlockSize.Block4x16)
{
this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth] = 4;
}
for (int transformBlockIterator = 0; transformBlockIterator < this.blockGeometryModeDecisionScan[index].TransformBlockCount[tx_depth]; transformBlockIterator++)
{
if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x64)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx_intra = [0, 16, 32, 48, 0, 16, 32, 48, 0, 16, 32, 48, 0, 16, 32, 48];
int[] offsety_intra = [0, 0, 0, 0, 16, 16, 16, 16, 32, 32, 32, 32, 48, 48, 48, 48];
Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x32)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx_intra = [0, 16, 32, 48, 0, 16, 32, 48];
int[] offsety_intra = [0, 0, 0, 0, 16, 16, 16, 16];
Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x64)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx_intra = [0, 16, 0, 16, 0, 16, 0, 16];
int[] offsety_intra = [0, 0, 16, 16, 32, 32, 48, 48];
Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x32)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx_intra = [0, 8, 16, 24, 0, 8, 16, 24, 0, 8, 16, 24, 0, 8, 16, 24];
int[] offsety_intra = [0, 0, 0, 0, 8, 8, 8, 8, 16, 16, 16, 16, 24, 24, 24, 24];
Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x16)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx_intra = [0, 8, 16, 24, 0, 8, 16, 24];
int[] offsety_intra = [0, 0, 0, 0, 8, 8, 8, 8];
Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x32)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx_intra = [0, 8, 0, 8, 0, 8, 0, 8];
int[] offsety_intra = [0, 0, 8, 8, 16, 16, 24, 24];
Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x8)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x4, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx_intra = [0, 4, 8, 12, 0, 4, 8, 12];
int[] offsety_intra = [0, 0, 0, 0, 4, 4, 4, 4];
Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block8x16)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x4, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx_intra = [0, 4, 0, 4, 0, 4, 0, 4];
int[] offsety_intra = [0, 0, 4, 4, 8, 8, 12, 12];
Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x16)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x4, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int[] offsetx_intra = [0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12];
int[] offsety_intra = [0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12];
Size offset = new(offsetx_intra[transformBlockIterator], offsety_intra[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x16)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
// 0 1 2 3
int[] offsetx = [0, 16, 32, 48];
int[] offsety = [0, 0, 0, 0];
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x64)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block16x16, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
// 0 1 2 3
int[] offsetx = [0, 0, 0, 0];
int[] offsety = [0, 16, 32, 48];
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block32x8)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
// 0 1 2 3
int[] offsetx = [0, 8, 16, 24];
int[] offsety = [0, 0, 0, 0];
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block8x32)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block8x8, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
// 0 1 2 3
int[] offsetx = [0, 0, 0, 0];
int[] offsety = [0, 8, 16, 24];
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block16x4)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x4, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
// 0 1 2 3
int[] offsetx = [0, 4, 8, 12];
int[] offsety = [0, 0, 0, 0];
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block4x16)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(Av1BlockSize.Block4x4, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] = this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
// 0 1 2 3
int[] offsetx = [0, 0, 0, 0];
int[] offsety = [0, 4, 8, 12];
Size offset = new(offsetx[transformBlockIterator], offsety[transformBlockIterator]);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else
{
if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x128)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(
this.blockGeometryModeDecisionScan[index].BlockSize, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] =
this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int offsetx = (transformBlockIterator is 0 or 2) ? 0 : 64;
int offsety = (transformBlockIterator is 0 or 1) ? 0 : 64;
Size offset = new(offsetx, offsety);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block128x64)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(
this.blockGeometryModeDecisionScan[index].BlockSize, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] =
this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int offsetx = (transformBlockIterator is 0) ? 0 : 64;
int offsety = 0;
Size offset = new(offsetx, offsety);
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin + offset;
}
else if (this.blockGeometryModeDecisionScan[index].BlockSize == Av1BlockSize.Block64x128)
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(
this.blockGeometryModeDecisionScan[index].BlockSize, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] =
this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
int offsetx = 0;
int offsety = (transformBlockIterator is 0) ? 0 : 64;
Size offset = new(offsetx, offsety);
}
else
{
this.blockGeometryModeDecisionScan[index].TransformSize[tx_depth] = GetTransformSize(
this.blockGeometryModeDecisionScan[index].BlockSize, 0);
this.blockGeometryModeDecisionScan[index].TransformSizeUv[tx_depth] =
this.blockGeometryModeDecisionScan[index].TransformSizeUv[0];
this.blockGeometryModeDecisionScan[index].TransformOrigin[tx_depth][transformBlockIterator] =
this.blockGeometryModeDecisionScan[index].Origin;
}
}
/*this.blockGeometryModeDecisionScan[index].tx_width[tx_depth] =
tx_size_wide[this.blockGeometryModeDecisionScan[index].txsize[tx_depth]];
this.blockGeometryModeDecisionScan[index].tx_height[tx_depth] =
tx_size_high[this.blockGeometryModeDecisionScan[index].txsize[tx_depth]];
this.blockGeometryModeDecisionScan[index].tx_width_uv[tx_depth] = this.blockGeometryModeDecisionScan[index].tx_width_uv[0];
this.blockGeometryModeDecisionScan[index].tx_height_uv[tx_depth] = this.blockGeometryModeDecisionScan[index].tx_height_uv[0];*/
}
this.blockGeometryModeDecisionScan[index].ModeDecisionIndex = index;
index += 1;
}
}
}
/// <summary>
/// SVT: av1_get_tx_size
/// </summary>
private static Av1TransformSize GetTransformSize(Av1BlockSize blockSize, int plane)
{
// const MbModeInfo* mbmi = xd->mi[0];
// if (xd->lossless[mbmi->segment_id]) return TX_4X4;
if (plane == 0)
{
return blockSize.GetMaximumTransformSize();
}
// const MacroblockdPlane *pd = &xd->plane[plane];
bool subsampling_x = plane > 0;
bool subsampling_y = plane > 0;
return blockSize.GetMaxUvTransformSize(subsampling_x, subsampling_y);
}
}

16
src/ImageSharp/Formats/Heif/Av1/ModeDecision/Av1GeometryIndex.cs

@ -0,0 +1,16 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.ModeDecision;
internal enum Av1GeometryIndex
{
Geometry0,
Geometry1,
Geometry2,
Geometry3,
Geometry4,
Geometry5,
Geometry6,
Geometry7,
}

22
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs

@ -0,0 +1,22 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal enum ObuChromoSamplePosition : byte
{
/// <summary>
/// Unknown.
/// </summary>
Unknown = 0,
/// <summary>
/// Horizontally co-located with luma(0, 0) sample, between two vertical samples.
/// </summary>
Vertical = 1,
/// <summary>
/// Co-located with luma(0, 0) sample
/// </summary>
Colocated = 2,
}

67
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs

@ -0,0 +1,67 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuColorConfig
{
private bool isMonochrome;
public bool IsColorDescriptionPresent { get; set; }
/// <summary>
/// Gets the number of color channels in this image. Can have the value 1 or 3.
/// </summary>
public int PlaneCount { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether the image has a single greyscale plane, will have
/// <see cref="Av1Constants.MaxPlanes"/> color planes otherwise.
/// </summary>
public bool IsMonochrome
{
get => this.isMonochrome;
set
{
this.PlaneCount = value ? 1 : Av1Constants.MaxPlanes;
this.isMonochrome = value;
}
}
public ObuColorPrimaries ColorPrimaries { get; set; }
public ObuTransferCharacteristics TransferCharacteristics { get; set; }
public ObuMatrixCoefficients MatrixCoefficients { get; set; }
public bool ColorRange { get; set; }
public bool SubSamplingX { get; set; }
public bool SubSamplingY { get; set; }
public bool HasSeparateUvDelta { get; set; }
public ObuChromoSamplePosition ChromaSamplePosition { get; set; }
public Av1BitDepth BitDepth { get; set; }
public Av1ColorFormat GetColorFormat()
{
Av1ColorFormat format = Av1ColorFormat.Yuv400;
if (this.SubSamplingX && this.SubSamplingY)
{
format = Av1ColorFormat.Yuv420;
}
else if (this.SubSamplingX & !this.SubSamplingY)
{
format = Av1ColorFormat.Yuv422;
}
else if (!this.SubSamplingX && !this.SubSamplingY)
{
format = Av1ColorFormat.Yuv444;
}
return format;
}
}

21
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal enum ObuColorPrimaries
{
None = 0,
Bt709 = 1,
Unspecified = 2,
Bt470M = 4,
Bt470BG = 5,
Bt601 = 6,
Smpte240 = 7,
GenericFilm = 8,
Bt2020 = 9,
Xyz = 10,
Smpte431 = 11,
Smpte432 = 12,
Ebu3213 = 22,
}

15
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs

@ -0,0 +1,15 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuConstraintDirectionalEnhancementFilterParameters
{
public int BitCount { get; internal set; }
public int Damping { get; internal set; }
public int[] YStrength { get; set; } = new int[16];
public int[] UvStrength { get; set; } = new int[16];
}

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

@ -0,0 +1,29 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1;
internal class ObuDecoderModelInfo
{
/// <summary>
/// Gets or sets BufferDelayLength. Specifies the length of the decoder_buffer_delay and the encoder_buffer_delay
/// syntax elements, in bits.
/// </summary>
internal uint BufferDelayLength { get; set; }
/// <summary>
/// Gets or sets NumUnitsInDecodingTick. This is the number of time units of a decoding clock operating at the frequency time_scale Hz
/// that corresponds to one increment of a clock tick counter.
/// </summary>
internal uint NumUnitsInDecodingTick { get; set; }
/// <summary>
/// Gets or sets BufferRemovalTimeLength. Specifies the length of the buffer_removal_time syntax element, in bits.
/// </summary>
internal uint BufferRemovalTimeLength { get; set; }
/// <summary>
/// Gets or sets the FramePresentationTimeLength. Specifies the length of the frame_presentation_time syntax element, in bits.
/// </summary>
internal uint FramePresentationTimeLength { get; set; }
}

13
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs

@ -0,0 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuDeltaParameters
{
public bool IsPresent { get; internal set; }
public int Resolution { get; internal set; }
public bool IsMulti { get; internal set; }
}

172
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs

@ -0,0 +1,172 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuFilmGrainParameters
{
/// <summary>
/// Gets or sets a value indicating whether film grain should be added to this frame. A value equal to false specifies that film
/// grain should not be added.
/// </summary>
public bool ApplyGrain { get; set; }
/// <summary>
/// Gets or sets GrainSeed. This value specifies the starting value for the pseudo-random numbers used during film grain synthesis.
/// </summary>
public uint GrainSeed { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a new set of parameters should be sent. A value equal to false means that the
/// previous set of parameters should be used.
/// </summary>
public bool UpdateGrain { get; set; }
/// <summary>
/// Gets or sets FilmGrainParamsRefIdx. Indicates which reference frame contains the film grain parameters to be used for this frame.
/// It is a requirement of bitstream conformance that FilmGrainParamsRefIdx is equal to ref_frame_idx[ j ] for some value
/// of j in the range 0 to REFS_PER_FRAME - 1.
/// </summary>
public uint FilmGrainParamsRefidx { get; set; }
/// <summary>
/// Gets or sets NumYPoints. Specifies the number of points for the piece-wise linear scaling function of the luma component.
/// It is a requirement of bitstream conformance that NumYPoints is less than or equal to 14.
/// </summary>
public uint NumYPoints { get; set; }
/// <summary>
/// Gets or sets PointYValue. Represents the x (luma value) coordinate for the i-th point of the piecewise linear scaling function for
/// luma component.The values are signaled on the scale of 0..255. (In case of 10 bit video, these values correspond to
/// luma values divided by 4. In case of 12 bit video, these values correspond to luma values divided by 16.)
///
/// If i is greater than 0, it is a requirement of bitstream conformance that point_y_value[ i ] is greater than point_y_value[ i - 1] (this ensures the x coordinates are specified in increasing order).
/// </summary>
public uint[]? PointYValue { get; set; }
/// <summary>
/// Gets or sets PointYScaling. Represents the scaling (output) value for the i-th point of the piecewise linear scaling function for luma component.
/// </summary>
public uint[]? PointYScaling { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the chroma scaling is inferred from the luma scaling.
/// </summary>
public bool ChromaScalingFromLuma { get; set; }
/// <summary>
/// Gets or sets NumCbPoints. Specifies the number of points for the piece-wise linear scaling function of the cb component.
/// It is a requirement of bitstream conformance that NumCbPoints is less than or equal to 10.
/// </summary>
public uint NumCbPoints { get; set; }
/// <summary>
/// Gets or sets NumCrPoints. Specifies represents the number of points for the piece-wise linear scaling function of the cr component.
/// It is a requirement of bitstream conformance that NumCrPoints is less than or equal to 10.
/// </summary>
public uint NumCrPoints { get; set; }
/// <summary>
/// Gets or sets PointCbValue. Represents the x coordinate for the i-th point of the piece-wise linear scaling function for cb
/// component.The values are signaled on the scale of 0..255.
/// If i is greater than 0, it is a requirement of bitstream conformance that point_cb_value[ i ] is greater than point_cb_value[ i - 1 ].
/// </summary>
public uint[]? PointCbValue { get; set; }
/// <summary>
/// Gets or sets PointCbScaling. Represents the scaling (output) value for the i-th point of the piecewise linear scaling function for cb component.
/// </summary>
public uint[]? PointCbScaling { get; set; }
/// <summary>
/// Gets or sets PointCrValue. Represents the x coordinate for the i-th point of the piece-wise linear scaling function for cr component.
/// The values are signaled on the scale of 0..255.
/// If i is greater than 0, it is a requirement of bitstream conformance that point_cr_value[ i ] is greater than point_cr_value[ i - 1 ].
/// </summary>
public uint[]? PointCrValue { get; set; }
/// <summary>
/// Gets or sets PointCrScaling. Represents the scaling (output) value for the i-th point of the piecewise linear scaling function for cr component.
/// </summary>
public uint[]? PointCrScaling { get; set; }
/// <summary>
/// Gets or sets GrainScalingMinus8. represents the shift – 8 applied to the values of the chroma component. The
/// grain_scaling_minus_8 can take values of 0..3 and determines the range and quantization step of the standard deviation of film grain.
/// </summary>
public uint GrainScalingMinus8 { get; set; }
/// <summary>
/// Gets or sets ArCoeffLag. Specifies the number of auto-regressive coefficients for luma and chroma.
/// </summary>
public uint ArCoeffLag { get; set; }
/// <summary>
/// Gets or sets ArCoeffsYPlus128. Specifies auto-regressive coefficients used for the Y plane.
/// </summary>
public uint[]? ArCoeffsYPlus128 { get; set; }
/// <summary>
/// Gets or sets ArCoeffsCbPlus128. Specifies auto-regressive coefficients used for the U plane.
/// </summary>
public uint[]? ArCoeffsCbPlus128 { get; set; }
/// <summary>
/// Gets or sets ArCoeffsCrPlus128. Specifies auto-regressive coefficients used for the V plane.
/// </summary>
public uint[]? ArCoeffsCrPlus128 { get; set; }
/// <summary>
/// Gets or sets ArCoeffShiftMinus6. Specifies the range of the auto-regressive coefficients. Values of 0, 1, 2, and 3 correspond to the
/// ranges for auto-regressive coefficients of[-2, 2), [-1, 1), [-0.5, 0.5) and [-0.25, 0.25) respectively.
/// </summary>
public uint ArCoeffShiftMinus6 { get; set; }
/// <summary>
/// Gets or sets GrainScaleShift. Specifies how much the Gaussian random numbers should be scaled down during the grain synthesis process.
/// </summary>
public uint GrainScaleShift { get; set; }
/// <summary>
/// Gets or sets CbMult. Represents a multiplier for the cb component used in derivation of the input index to the cb component scaling function.
/// </summary>
public uint CbMult { get; set; }
/// <summary>
/// Gets or sets CbLumaMult. Represents a multiplier for the average luma component used in derivation of the input index to the cb component scaling function.
/// </summary>
public uint CbLumaMult { get; set; }
/// <summary>
/// Gets or sets CbOffset. Represents an offset used in derivation of the input index to the cb component scaling function.
/// </summary>
public uint CbOffset { get; set; }
/// <summary>
/// Gets or sets CrMult. Represents a multiplier for the cr component used in derivation of the input index to the cr component scaling function.
/// </summary>
public uint CrMult { get; set; }
/// <summary>
/// Gets or sets CrLumaMult. Represents a multiplier for the average luma component used in derivation of the input index to the cr component scaling function.
/// </summary>
public uint CrLumaMult { get; set; }
/// <summary>
/// Gets or sets CrOffset. Represents an offset used in derivation of the input index to the cr component scaling function.
/// </summary>
public uint CrOffset { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the overlap between film grain blocks shall be applied. OverlapFlag equal to false
/// indicates that the overlap between film grain blocks shall not be applied.
/// </summary>
public bool OverlapFlag { get; set; }
/// <summary>
/// Gets or sets a value indicating whether clipping to the restricted (studio) range shall be applied to the sample
/// values after adding the film grain(see the semantics for color_range for an explanation of studio swing).
/// ClipToRestrictedRange equal to false indicates that clipping to the full range shall be applied to the sample values after adding the film grain.
/// </summary>
public bool ClipToRestrictedRange { get; set; }
}

98
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs

@ -0,0 +1,98 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuFrameHeader
{
public bool ForceIntegerMotionVector { get; set; }
public bool AllowIntraBlockCopy { get; set; }
public bool UseReferenceFrameMotionVectors { get; set; }
public bool AllowHighPrecisionMotionVector { get; set; }
public ObuTileGroupHeader TilesInfo { get; set; } = new ObuTileGroupHeader();
public bool CodedLossless { get; set; }
public bool[] LosslessArray { get; set; } = new bool[Av1Constants.MaxSegmentCount];
public ObuQuantizationParameters QuantizationParameters { get; set; } = new ObuQuantizationParameters();
public ObuSegmentationParameters SegmentationParameters { get; set; } = new ObuSegmentationParameters();
public bool AllLossless { get; set; }
public bool AllowWarpedMotion { get; set; }
public ObuReferenceMode ReferenceMode { get; set; }
public ObuFilmGrainParameters FilmGrainParameters { get; set; } = new ObuFilmGrainParameters();
public bool UseReducedTransformSet { get; set; }
public ObuLoopFilterParameters LoopFilterParameters { get; set; } = new ObuLoopFilterParameters();
public ObuLoopRestorationParameters LoopRestorationParameters { get; set; } = new ObuLoopRestorationParameters();
public ObuConstraintDirectionalEnhancementFilterParameters CdefParameters { get; set; } = new ObuConstraintDirectionalEnhancementFilterParameters();
public int ModeInfoStride { get; set; }
public bool DisableFrameEndUpdateCdf { get; set; }
public ObuSkipModeParameters SkipModeParameters { get; set; } = new ObuSkipModeParameters();
public Av1TransformMode TransformMode { get; set; }
public ObuDeltaParameters DeltaLoopFilterParameters { get; set; } = new ObuDeltaParameters();
public ObuDeltaParameters DeltaQParameters { get; set; } = new ObuDeltaParameters();
public bool IsIntra => this.FrameType is ObuFrameType.IntraOnlyFrame or ObuFrameType.KeyFrame;
internal ObuFrameSize FrameSize { get; set; } = new ObuFrameSize();
internal int ModeInfoColumnCount { get; set; }
internal int ModeInfoRowCount { get; set; }
internal bool ShowExistingFrame { get; set; }
internal ObuFrameType FrameType { get; set; }
internal bool[] ReferenceValid { get; set; } = new bool[Av1Constants.ReferenceFrameCount];
internal bool[] ReferenceOrderHint { get; set; } = new bool[Av1Constants.ReferenceFrameCount];
internal bool ShowFrame { get; set; }
internal bool ShowableFrame { get; set; }
internal uint FrameToShowMapIdx { get; set; }
internal uint DisplayFrameId { get; set; }
internal bool ErrorResilientMode { get; set; }
internal bool AllowScreenContentTools { get; set; }
internal bool DisableCdfUpdate { get; set; }
internal uint CurrentFrameId { get; set; }
internal uint[] ReferenceFrameIndex { get; set; } = new uint[Av1Constants.ReferenceFrameCount];
internal uint OrderHint { get; set; }
internal uint PrimaryReferenceFrame { get; set; } = Av1Constants.PrimaryReferenceFrameNone;
internal uint RefreshFrameFlags { get; set; }
// 5.9.31. Temporal point info syntax
internal uint FramePresentationTime { get; set; }
}

19
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuFrameSize
{
internal int FrameWidth { get; set; }
internal int FrameHeight { get; set; }
internal int SuperResolutionDenominator { get; set; }
internal int SuperResolutionUpscaledWidth { get; set; }
internal int RenderWidth { get; set; }
internal int RenderHeight { get; set; }
}

12
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs

@ -0,0 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal enum ObuFrameType
{
KeyFrame = 0,
InterFrame = 1,
IntraOnlyFrame = 2,
SwitchFrame = 3,
}

21
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuHeader
{
public int Size { get; set; }
public ObuType Type { get; set; }
public bool HasSize { get; set; }
public bool HasExtension { get; set; }
public int TemporalId { get; set; }
public int SpatialId { get; set; }
public int PayloadSize { get; set; }
}

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

@ -0,0 +1,29 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuLoopFilterParameters
{
public ObuLoopFilterParameters()
{
this.ReferenceDeltas = [1, 0, 0, 0, 0, -1, -1, -1];
this.ModeDeltas = [0, 0];
}
public int[] FilterLevel { get; internal set; } = new int[2];
public int FilterLevelU { get; internal set; }
public int FilterLevelV { get; internal set; }
public int SharpnessLevel { get; internal set; }
public bool ReferenceDeltaModeEnabled { get; internal set; }
public bool ReferenceDeltaModeUpdate { get; internal set; }
public int[] ReferenceDeltas { get; }
public int[] ModeDeltas { get; }
}

11
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationItem.cs

@ -0,0 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuLoopRestorationItem
{
internal int Size { get; set; }
internal ObuRestorationType Type { get; set; } = ObuRestorationType.None;
}

25
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs

@ -0,0 +1,25 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuLoopRestorationParameters
{
internal ObuLoopRestorationParameters()
{
this.Items = new ObuLoopRestorationItem[3];
this.Items[0] = new();
this.Items[1] = new();
this.Items[2] = new();
}
internal bool UsesLoopRestoration { get; set; }
internal bool UsesChromaLoopRestoration { get; set; }
internal ObuLoopRestorationItem[] Items { get; }
internal int UnitShift { get; set; }
internal int UVShift { get; set; }
}

22
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs

@ -0,0 +1,22 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal enum ObuMatrixCoefficients
{
Identity = 0,
Bt407 = 1,
Unspecified = 2,
Fcc = 4,
Bt470BG = 5,
Bt601 = 6,
Smpte240 = 7,
SmpteYCgCo = 8,
Bt2020NonConstantLuminance = 9,
Bt2020ConstantLuminance = 10,
Smpte2085 = 11,
ChromaticityDerivedNonConstantLuminance = 12,
ChromaticityDerivedConstandLuminance = 13,
Bt2100ICtCp = 14,
}

13
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs

@ -0,0 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal enum ObuMetadataType
{
ItutT35,
HdrCll,
HdrMdcv,
Scalability,
Timecode
}

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

@ -0,0 +1,29 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuOperatingPoint
{
internal int OperatorIndex { get; set; }
internal int SequenceLevelIndex { get; set; }
internal int SequenceTier { get; set; }
internal bool IsDecoderModelInfoPresent { get; set; }
internal bool IsInitialDisplayDelayPresent { get; set; }
internal uint InitialDisplayDelay { get; set; }
/// <summary>
/// Gets or sets of sets the Idc bitmask. The bitmask that indicates which spatial and temporal layers should be decoded for
/// operating point i.Bit k is equal to 1 if temporal layer k should be decoded(for k between 0 and 7). Bit j+8 is equal to 1 if
/// spatial layer j should be decoded(for j between 0 and 3).
/// However, if operating_point_idc[i] is equal to 0 then the coded video sequence has no scalability information in OBU
/// extension headers and the operating point applies to the entire coded video sequence.This means that all OBUs must be decoded.
/// It is a requirement of bitstream conformance that operating_point_idc[i] is not equal to operating_point_idc[j] for j = 0..(i- 1).
/// </summary>
internal uint Idc { get; set; }
}

15
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs

@ -0,0 +1,15 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuOrderHintInfo
{
public bool EnableOrderHint { get; internal set; }
internal bool EnableJointCompound { get; set; }
internal bool EnableReferenceFrameMotionVectors { get; set; }
internal int OrderHintBits { get; set; }
}

21
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuQuantizationParameters
{
public int BaseQIndex { get; set; }
public int[] QIndex { get; set; } = new int[Av1Constants.MaxSegmentCount];
public bool IsUsingQMatrix { get; internal set; }
public int[] DeltaQDc { get; internal set; } = new int[3];
public int[] DeltaQAc { get; internal set; } = new int[3];
public int[] QMatrix { get; internal set; } = new int[3];
public bool HasSeparateUvDelta { get; internal set; }
}

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

File diff suppressed because it is too large

10
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.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 ObuReferenceMode
{
ReferenceModeSelect,
SingleReference,
}

12
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs

@ -0,0 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal enum ObuRestorationType : uint
{
None = 0,
Switchable = 1,
Weiner = 2,
SgrProj = 3,
}

16
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs

@ -0,0 +1,16 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal enum ObuSegmentationLevelFeature
{
AlternativeQuantizer,
AlternativeLoopFilterYVertical,
AlternativeLoopFilterYHorizontal,
AlternativeLoopFilterU,
AlternativeLoopFilterV,
ReferenceFrame,
Skip,
GlobalMotionVector,
}

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

@ -0,0 +1,41 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuSegmentationParameters
{
public int[][] QMLevel { get; internal set; } = new int[3][];
public bool[,] FeatureEnabled { get; internal set; } = new bool[Av1Constants.MaxSegmentCount, Av1Constants.SegmentationLevelMax];
public bool Enabled { get; internal set; }
public int[,] FeatureData { get; internal set; } = new int[Av1Constants.MaxSegmentCount, Av1Constants.SegmentationLevelMax];
public bool SegmentIdPrecedesSkip { get; internal set; }
public int LastActiveSegmentId { get; internal set; }
/// <summary>
/// Gets or sets the SegmentationUpdateMap. A value of 1 indicates that the segmentation map are updated during the decoding of this
/// frame. SegmentationUpdateMap equal to 0 means that the segmentation map from the previous frame is used.
/// </summary>
public int SegmentationUpdateMap { get; internal set; }
/// <summary>
/// Gets or sets the SegmentationTemporalUpdate. A value of 1 indicates that the updates to the segmentation map are coded relative to the
/// existing segmentation map. SegmentationTemporalUpdate equal to 0 indicates that the new segmentation map is coded
/// without reference to the existing segmentation map.
/// </summary>
public int SegmentationTemporalUpdate { get; internal set; }
/// <summary>
/// Gets or sets SegmentationUpdateData. A value of 1 indicates that new parameters are about to be specified for each segment.
/// SegmentationUpdateData equal to 0 indicates that the segmentation parameters should keep their existing values.
/// </summary>
public int SegmentationUpdateData { get; internal set; }
internal bool IsFeatureActive(int segmentId, ObuSegmentationLevelFeature feature)
=> this.FeatureEnabled[segmentId, (int)feature];
}

95
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs

@ -0,0 +1,95 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuSequenceHeader
{
private bool use128x128Superblock;
public bool EnableFilterIntra { get; set; }
public bool EnableCdef { get; set; }
public bool IsStillPicture { get; set; }
public bool IsReducedStillPictureHeader { get; set; }
public ObuSequenceProfile SequenceProfile { get; set; }
public ObuOperatingPoint[] OperatingPoint { get; set; } = new ObuOperatingPoint[1];
public ObuDecoderModelInfo? DecoderModelInfo { get; set; }
public bool InitialDisplayDelayPresentFlag { get; set; }
public bool DecoderModelInfoPresentFlag { get; set; }
public bool TimingInfoPresentFlag { get; set; }
public ObuTimingInfo? TimingInfo { get; set; }
public bool IsFrameIdNumbersPresent { get; set; }
public int FrameWidthBits { get; set; }
public int FrameHeightBits { get; set; }
public int MaxFrameWidth { get; set; }
public int MaxFrameHeight { get; set; }
public bool Use128x128Superblock
{
get => this.use128x128Superblock;
set
{
this.use128x128Superblock = value;
this.SuperblockSize = value ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64;
this.SuperblockSizeLog2 = value ? 7 : 6;
this.SuperblockModeInfoSize = 1 << (this.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2);
}
}
public Av1BlockSize SuperblockSize { get; private set; }
public int SuperblockModeInfoSize { get; private set; }
public int SuperblockSizeLog2 { get; private set; }
public int FilterIntraLevel { get; set; }
public bool EnableIntraEdgeFilter { get; set; }
public ObuOrderHintInfo OrderHintInfo { get; set; } = new ObuOrderHintInfo();
public bool EnableOrderHint { get; set; }
public bool EnableInterIntraCompound { get; set; }
public bool EnableMaskedCompound { get; set; }
public bool EnableWarpedMotion { get; set; }
public bool EnableDualFilter { get; set; }
public int ForceIntegerMotionVector { get; set; }
public int ForceScreenContentTools { get; set; }
public bool EnableSuperResolution { get; set; }
public int CdefLevel { get; set; }
public bool EnableRestoration { get; set; }
public ObuColorConfig ColorConfig { get; set; } = new ObuColorConfig();
public bool AreFilmGrainingParametersPresent { get; set; }
public int FrameIdLength { get; set; }
public int DeltaFrameIdLength { get; set; }
public uint AdditionalFrameIdLength { get; set; }
}

11
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs

@ -0,0 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal enum ObuSequenceProfile : uint
{
Main = 0,
High = 1,
Professional = 2,
}

11
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSkipModeParameters.cs

@ -0,0 +1,11 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuSkipModeParameters
{
public bool SkipModeAllowed { get; set; }
public bool SkipModeFlag { get; internal set; }
}

39
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs

@ -0,0 +1,39 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuTileGroupHeader
{
internal int MaxTileWidthSuperblock { get; set; }
internal int MaxTileHeightSuperblock { get; set; }
internal int MinLog2TileColumnCount { get; set; }
internal int MaxLog2TileColumnCount { get; set; }
internal int MaxLog2TileRowCount { get; set; }
internal int MinLog2TileCount { get; set; }
public bool HasUniformTileSpacing { get; set; }
internal int TileColumnCountLog2 { get; set; }
internal int TileColumnCount { get; set; }
internal int[] TileColumnStartModeInfo { get; set; } = new int[Av1Constants.MaxTileRowCount + 1];
internal int MinLog2TileRowCount { get; set; }
internal int TileRowCountLog2 { get; set; }
internal int[] TileRowStartModeInfo { get; set; } = new int[Av1Constants.MaxTileColumnCount + 1];
internal int TileRowCount { get; set; }
internal uint ContextUpdateTileId { get; set; }
internal int TileSizeBytes { get; set; }
}

33
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTimingInfo.cs

@ -0,0 +1,33 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuTimingInfo
{
/// <summary>
/// Gets or sets NumUnitsInDisplayTick. NumUnitsInDisplayTick is the number of time units of a clock operating at the frequency TimeScale Hz that
/// corresponds to one increment of a clock tick counter. A display clock tick, in seconds, is equal to
/// NumUnitsInDisplayTick divided by TimeScale.
/// </summary>
public uint NumUnitsInDisplayTick { get; set; }
/// <summary>
/// Gets or sets TimeScale. TimeScale is the number of time units that pass in one second.
/// It is a requirement of bitstream conformance that TimeScale is greater than 0.
/// </summary>
public uint TimeScale { get; set; }
/// <summary>
/// Gets or sets a value indicating whether that pictures should be displayed according to their output order with the
/// number of ticks between two consecutive pictures (without dropping frames) specified by NumTicksPerPicture.
/// EqualPictureInterval equal to false indicates that the interval between two consecutive pictures is not specified.
/// </summary>
public bool EqualPictureInterval { get; set; }
/// <summary>
/// Gets or sets NumTicksPerPicture. NumTicksPerPicture specifies the number of clock ticks corresponding to output time between two
/// consecutive pictures in the output order.
/// </summary>
public uint NumTicksPerPicture { get; set; }
}

25
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs

@ -0,0 +1,25 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal enum ObuTransferCharacteristics
{
Bt709 = 1,
Unspecified = 2,
Bt470M = 4,
Bt470BG = 5,
Bt601 = 6,
Smpte240 = 7,
Linear = 8,
Log100 = 9,
Log100Sqrt10 = 10,
Iec61966 = 11,
Bt1361 = 12,
Srgb = 13,
Bt202010Bit = 14,
Bt202012Bit = 15,
Smpte2084 = 16,
Smpte248 = 17,
Hlg = 18,
}

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

@ -0,0 +1,18 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal enum ObuType
{
None = 0,
SequenceHeader = 1,
TemporalDelimiter = 2,
FrameHeader = 3,
RedundantFrameHeader = 7,
TileGroup = 4,
Metadata = 5,
Frame = 6,
TileList = 8,
Padding = 15,
}

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

@ -0,0 +1,862 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
internal class ObuWriter
{
private int[] previousQIndex = [];
private int[] previousDeltaLoopFilter = [];
/// <summary>
/// Encode a single frame into OBU's.
/// </summary>
public void WriteAll(Configuration configuration, Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, IAv1TileWriter tileWriter)
{
// TODO: Determine inital size dynamically
int initialBufferSize = 2000;
AutoExpandingMemory<byte> buffer = new(configuration, initialBufferSize);
Av1BitStreamWriter writer = new(buffer);
WriteObuHeaderAndSize(stream, ObuType.TemporalDelimiter, []);
if (sequenceHeader != null)
{
WriteSequenceHeader(ref writer, sequenceHeader);
int bytesWritten = (writer.BitPosition + 7) >> 3;
writer.Flush();
WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, buffer.GetSpan(bytesWritten));
}
if (frameHeader != null && sequenceHeader != null)
{
this.WriteFrameHeader(ref writer, sequenceHeader, frameHeader, false);
if (frameHeader.TilesInfo != null)
{
WriteTileGroup(ref writer, frameHeader.TilesInfo, tileWriter);
}
int bytesWritten = (writer.BitPosition + 7) >> 3;
writer.Flush();
WriteObuHeaderAndSize(stream, ObuType.Frame, buffer.GetSpan(bytesWritten));
}
}
private static void WriteObuHeader(ref Av1BitStreamWriter writer, ObuType type)
{
writer.WriteBoolean(false); // Forbidden bit
writer.WriteLiteral((uint)type, 4);
writer.WriteBoolean(false); // Extension
writer.WriteBoolean(true); // HasSize
writer.WriteBoolean(false); // Reserved
}
private static byte WriteObuHeader(ObuType type) =>
// 0: Forbidden bit
// 1: Type, 4
// 5: Extension (false)
// 6: HasSize (true)
// 7: Reserved (false)
(byte)(((byte)type << 3) | 0x02);
/// <summary>
/// Read OBU header and size.
/// </summary>
private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span<byte> payload)
{
stream.WriteByte(WriteObuHeader(type));
Span<byte> lengthBytes = stackalloc byte[3];
int lengthLength = Av1BitStreamWriter.GetLittleEndianBytes128((uint)payload.Length, lengthBytes);
stream.Write(lengthBytes, 0, lengthLength);
stream.Write(payload);
}
/// <summary>
/// Write trsainling bits to end on a byte boundary, these trailing bits start with a 1 and end with 0s.
/// </summary>
/// <remarks>Write an additional byte, if already byte aligned before.</remarks>
private static void WriteTrailingBits(ref Av1BitStreamWriter writer)
{
int bitsBeforeAlignment = 8 - (writer.BitPosition & 0x7);
if (bitsBeforeAlignment != 8)
{
writer.WriteLiteral(1U << (bitsBeforeAlignment - 1), bitsBeforeAlignment);
}
}
private static void AlignToByteBoundary(ref Av1BitStreamWriter writer)
{
while ((writer.BitPosition & 0x7) > 0)
{
writer.WriteBoolean(false);
}
}
private static bool IsValidObuType(ObuType type) => type switch
{
ObuType.SequenceHeader or ObuType.TemporalDelimiter or ObuType.FrameHeader or
ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrameHeader or
ObuType.TileList or ObuType.Padding => true,
_ => false,
};
private static void WriteSequenceHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader)
{
writer.WriteLiteral((uint)sequenceHeader.SequenceProfile, 3);
writer.WriteBoolean(true); // IsStillPicture
writer.WriteBoolean(true); // IsReducedStillPicture
writer.WriteLiteral((uint)sequenceHeader.OperatingPoint[0].SequenceLevelIndex, Av1Constants.LevelBits);
// Frame width and Height
writer.WriteLiteral((uint)sequenceHeader.FrameWidthBits - 1, 4);
writer.WriteLiteral((uint)sequenceHeader.FrameHeightBits - 1, 4);
writer.WriteLiteral((uint)sequenceHeader.MaxFrameWidth - 1, sequenceHeader.FrameWidthBits);
writer.WriteLiteral((uint)sequenceHeader.MaxFrameHeight - 1, sequenceHeader.FrameHeightBits);
// Video related flags removed
writer.WriteBoolean(sequenceHeader.Use128x128Superblock);
writer.WriteBoolean(sequenceHeader.EnableFilterIntra);
writer.WriteBoolean(sequenceHeader.EnableIntraEdgeFilter);
// Video related flags removed
writer.WriteBoolean(sequenceHeader.EnableSuperResolution);
writer.WriteBoolean(sequenceHeader.EnableCdef);
writer.WriteBoolean(sequenceHeader.EnableRestoration);
WriteColorConfig(ref writer, sequenceHeader);
writer.WriteBoolean(sequenceHeader.AreFilmGrainingParametersPresent);
WriteTrailingBits(ref writer);
}
private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader)
{
ObuColorConfig colorConfig = sequenceHeader.ColorConfig;
WriteBitDepth(ref writer, colorConfig, sequenceHeader);
if (sequenceHeader.SequenceProfile != ObuSequenceProfile.High)
{
writer.WriteBoolean(colorConfig.IsMonochrome);
}
writer.WriteBoolean(colorConfig.IsColorDescriptionPresent);
if (colorConfig.IsColorDescriptionPresent)
{
writer.WriteLiteral((uint)colorConfig.ColorPrimaries, 8);
writer.WriteLiteral((uint)colorConfig.TransferCharacteristics, 8);
writer.WriteLiteral((uint)colorConfig.MatrixCoefficients, 8);
}
if (colorConfig.IsMonochrome)
{
writer.WriteBoolean(colorConfig.ColorRange);
return;
}
else if (
colorConfig.ColorPrimaries == ObuColorPrimaries.Bt709 &&
colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb &&
colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity)
{
colorConfig.ColorRange = true;
colorConfig.SubSamplingX = false;
colorConfig.SubSamplingY = false;
}
else
{
writer.WriteBoolean(colorConfig.ColorRange);
if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && colorConfig.BitDepth == Av1BitDepth.TwelveBit)
{
writer.WriteBoolean(colorConfig.SubSamplingX);
if (colorConfig.SubSamplingX)
{
writer.WriteBoolean(colorConfig.SubSamplingY);
}
}
if (colorConfig.SubSamplingX && colorConfig.SubSamplingY)
{
writer.WriteLiteral((uint)colorConfig.ChromaSamplePosition, 2);
}
}
writer.WriteBoolean(colorConfig.HasSeparateUvDelta);
}
private static void WriteBitDepth(ref Av1BitStreamWriter writer, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader)
{
bool hasHighBitDepth = colorConfig.BitDepth > Av1BitDepth.EightBit;
writer.WriteBoolean(hasHighBitDepth);
if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && hasHighBitDepth)
{
writer.WriteBoolean(colorConfig.BitDepth == Av1BitDepth.TwelveBit);
}
}
private static void WriteSuperResolutionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
bool useSuperResolution = false;
if (sequenceHeader.EnableSuperResolution)
{
writer.WriteBoolean(useSuperResolution);
}
if (useSuperResolution)
{
writer.WriteLiteral((uint)frameHeader.FrameSize.SuperResolutionDenominator - Av1Constants.SuperResolutionScaleDenominatorMinimum, Av1Constants.SuperResolutionScaleBits);
}
}
private static void WriteRenderSize(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
bool renderSizeAndFrameSizeDifferent = false;
writer.WriteBoolean(false);
if (renderSizeAndFrameSizeDifferent)
{
writer.WriteLiteral((uint)frameHeader.FrameSize.RenderWidth - 1, 16);
writer.WriteLiteral((uint)frameHeader.FrameSize.RenderHeight - 1, 16);
}
}
private static void WriteFrameSizeWithReferences(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool frameSizeOverrideFlag)
{
bool foundReference = false;
for (int i = 0; i < Av1Constants.ReferencesPerFrame; i++)
{
writer.WriteBoolean(foundReference);
if (foundReference)
{
// Take values over from reference frame
break;
}
}
if (!foundReference)
{
WriteFrameSize(ref writer, sequenceHeader, frameHeader, frameSizeOverrideFlag);
WriteRenderSize(ref writer, frameHeader);
}
else
{
WriteSuperResolutionParameters(ref writer, sequenceHeader, frameHeader);
}
}
private static void WriteFrameSize(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool frameSizeOverrideFlag)
{
if (frameSizeOverrideFlag)
{
writer.WriteLiteral((uint)frameHeader.FrameSize.FrameWidth - 1, sequenceHeader.FrameWidthBits + 1);
writer.WriteLiteral((uint)frameHeader.FrameSize.FrameHeight - 1, sequenceHeader.FrameHeightBits + 1);
}
WriteSuperResolutionParameters(ref writer, sequenceHeader, frameHeader);
}
private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
ObuTileGroupHeader tileInfo = frameHeader.TilesInfo;
int superblockColumnCount;
int superblockRowCount;
int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2;
int superblockShift = superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2;
superblockColumnCount = (frameHeader.ModeInfoColumnCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift;
superblockRowCount = (frameHeader.ModeInfoRowCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift;
int superBlockSize = superblockShift + 2;
int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (2 * superBlockSize);
tileInfo.MaxTileWidthSuperblock = Av1Constants.MaxTileWidth >> superBlockSize;
tileInfo.MaxTileHeightSuperblock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superBlockSize;
tileInfo.MinLog2TileColumnCount = ObuReader.TileLog2(tileInfo.MaxTileWidthSuperblock, superblockColumnCount);
tileInfo.MaxLog2TileColumnCount = ObuReader.TileLog2(1, Math.Min(superblockColumnCount, Av1Constants.MaxTileColumnCount));
tileInfo.MaxLog2TileRowCount = ObuReader.TileLog2(1, Math.Min(superblockRowCount, Av1Constants.MaxTileRowCount));
tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, ObuReader.TileLog2(maxTileAreaOfSuperBlock, superblockColumnCount * superblockRowCount));
int log2TileColumnCount = Av1Math.Log2(tileInfo.TileColumnCount);
int log2TileRowCount = Av1Math.Log2(tileInfo.TileRowCount);
writer.WriteBoolean(tileInfo.HasUniformTileSpacing);
if (tileInfo.HasUniformTileSpacing)
{
// Uniform spaced tiles with power-of-two number of rows and columns
// tile columns
int ones = log2TileColumnCount - tileInfo.MinLog2TileColumnCount;
while (ones-- > 0)
{
writer.WriteBoolean(true);
}
if (log2TileColumnCount < tileInfo.MaxLog2TileColumnCount)
{
writer.WriteBoolean(false);
}
// rows
tileInfo.MinLog2TileRowCount = Math.Min(tileInfo.MinLog2TileCount - log2TileColumnCount, 0);
ones = log2TileRowCount - tileInfo.MinLog2TileRowCount;
while (ones-- > 0)
{
writer.WriteBoolean(true);
}
if (log2TileRowCount < tileInfo.MaxLog2TileRowCount)
{
writer.WriteBoolean(false);
}
}
else
{
int startSuperBlock = 0;
int i = 0;
for (; startSuperBlock < superblockColumnCount; i++)
{
uint widthInSuperBlocks = (uint)((tileInfo.TileColumnStartModeInfo[i] >> superblockShift) - startSuperBlock);
uint maxWidth = (uint)Math.Min(superblockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperblock);
writer.WriteNonSymmetric(widthInSuperBlocks - 1, maxWidth);
startSuperBlock += (int)widthInSuperBlocks;
}
if (startSuperBlock != superblockColumnCount)
{
throw new ImageFormatException("Super block tiles width does not add up to total width.");
}
startSuperBlock = 0;
for (i = 0; startSuperBlock < superblockRowCount; i++)
{
uint heightInSuperBlocks = (uint)((tileInfo.TileRowStartModeInfo[i] >> superblockShift) - startSuperBlock);
uint maxHeight = (uint)Math.Min(superblockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperblock);
writer.WriteNonSymmetric(heightInSuperBlocks - 1, maxHeight);
startSuperBlock += (int)heightInSuperBlocks;
}
if (startSuperBlock != superblockRowCount)
{
throw new ImageFormatException("Super block tiles height does not add up to total height.");
}
}
if (tileInfo.TileColumnCountLog2 > 0 || tileInfo.TileRowCountLog2 > 0)
{
writer.WriteLiteral(tileInfo.ContextUpdateTileId, tileInfo.TileRowCountLog2 + tileInfo.TileColumnCountLog2);
writer.WriteLiteral((uint)tileInfo.TileSizeBytes - 1, 2);
}
frameHeader.TilesInfo = tileInfo;
}
private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
// TODO: Make tile count configurable.
int tileCount = 1;
int planesCount = sequenceHeader.ColorConfig.PlaneCount;
writer.WriteBoolean(frameHeader.DisableCdfUpdate);
if (sequenceHeader.ForceScreenContentTools == 2)
{
writer.WriteBoolean(frameHeader.AllowScreenContentTools);
}
else
{
// Guard.IsTrue(frameHeader.AllowScreenContentTools == sequenceHeader.ForceScreenContentTools);
}
if (frameHeader.AllowScreenContentTools)
{
if (sequenceHeader.ForceIntegerMotionVector == 2)
{
writer.WriteBoolean(frameHeader.ForceIntegerMotionVector);
}
else
{
// Guard.IsTrue(frameHeader.ForceIntegerMotionVector == sequenceHeader.ForceIntegerMotionVector, nameof(frameHeader.ForceIntegerMotionVector), "Frame and sequence must be in sync");
}
}
if (frameHeader.FrameType == ObuFrameType.KeyFrame)
{
if (!frameHeader.ShowFrame)
{
throw new NotImplementedException("No support for hidden frames.");
}
}
else if (frameHeader.FrameType == ObuFrameType.IntraOnlyFrame)
{
throw new NotImplementedException("No IntraOnly frames supported.");
}
if (frameHeader.FrameType == ObuFrameType.KeyFrame)
{
WriteFrameSize(ref writer, sequenceHeader, frameHeader, false);
WriteRenderSize(ref writer, frameHeader);
if (frameHeader.AllowScreenContentTools)
{
writer.WriteBoolean(frameHeader.AllowIntraBlockCopy);
}
}
else if (frameHeader.FrameType == ObuFrameType.IntraOnlyFrame)
{
WriteFrameSize(ref writer, sequenceHeader, frameHeader, false);
WriteRenderSize(ref writer, frameHeader);
if (frameHeader.AllowScreenContentTools)
{
writer.WriteBoolean(frameHeader.AllowIntraBlockCopy);
}
}
else
{
throw new NotImplementedException("Inter frames not applicable for AVIF.");
}
WriteTileInfo(ref writer, sequenceHeader, frameHeader);
WriteQuantizationParameters(ref writer, sequenceHeader, frameHeader);
WriteSegmentationParameters(ref writer, sequenceHeader, frameHeader);
if (frameHeader.QuantizationParameters.BaseQIndex > 0)
{
writer.WriteBoolean(frameHeader.DeltaQParameters.IsPresent);
if (frameHeader.DeltaQParameters.IsPresent)
{
writer.WriteLiteral((uint)frameHeader.DeltaQParameters.Resolution - 1, 2);
this.previousQIndex = new int[tileCount];
for (int tileIndex = 0; tileIndex < tileCount; tileIndex++)
{
this.previousQIndex[tileIndex] = frameHeader.QuantizationParameters.BaseQIndex;
}
if (frameHeader.AllowIntraBlockCopy)
{
Guard.IsFalse(
frameHeader.DeltaLoopFilterParameters.IsPresent,
nameof(frameHeader.DeltaLoopFilterParameters.IsPresent),
"Allow INTRA block copy required Loop Filter.");
}
else
{
writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsPresent);
}
if (frameHeader.DeltaLoopFilterParameters.IsPresent)
{
writer.WriteLiteral((uint)(1 + Av1Math.MostSignificantBit((uint)frameHeader.DeltaLoopFilterParameters.Resolution) - 1), 2);
writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti);
int frameLoopFilterCount = sequenceHeader.ColorConfig.IsMonochrome ? Av1Constants.FrameLoopFilterCount - 2 : Av1Constants.FrameLoopFilterCount;
this.previousDeltaLoopFilter = new int[frameLoopFilterCount];
for (int loopFilterId = 0; loopFilterId < frameLoopFilterCount; loopFilterId++)
{
this.previousDeltaLoopFilter[loopFilterId] = 0;
}
}
}
}
if (frameHeader.AllLossless)
{
throw new NotImplementedException("No entire lossless supported.");
}
else
{
if (!frameHeader.CodedLossless)
{
WriteLoopFilterParameters(ref writer, sequenceHeader, frameHeader);
if (sequenceHeader.CdefLevel > 0)
{
WriteCdefParameters(ref writer, sequenceHeader, frameHeader);
}
}
if (sequenceHeader.EnableRestoration)
{
WriteLoopRestorationParameters(ref writer, sequenceHeader, frameHeader);
}
}
// No Frame Reference mode selection for AVIF
WriteTransformMode(ref writer, frameHeader);
// No compound INTER-INTER for AVIF.
WriteFrameReferenceMode(ref writer, frameHeader);
WriteSkipModeParameters(ref writer, frameHeader);
// No warp motion for AVIF.
writer.WriteBoolean(frameHeader.UseReducedTransformSet);
WriteGlobalMotionParameters(ref writer, frameHeader);
WriteFilmGrainFilterParameters(ref writer, sequenceHeader, frameHeader);
}
private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature)
=> segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature];
private int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool writeTrailingBits)
{
int startBitPosition = writer.BitPosition;
this.WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameHeader);
if (writeTrailingBits)
{
WriteTrailingBits(ref writer);
}
int endPosition = writer.BitPosition;
int headerBytes = (endPosition - startBitPosition) / 8;
return headerBytes;
}
/// <summary>
/// 5.11.1. General tile group OBU syntax.
/// </summary>
private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter)
{
int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
int startBitPosition = writer.BitPosition;
bool tileStartAndEndPresentFlag = tileCount > 1;
writer.WriteBoolean(tileStartAndEndPresentFlag);
uint tileGroupStart = 0U;
uint tileGroupEnd = (uint)tileCount - 1U;
if (tileCount != 1)
{
int tileBits = Av1Math.Log2(tileInfo.TileColumnCount) + Av1Math.Log2(tileInfo.TileRowCount);
writer.WriteLiteral(tileGroupStart, tileBits);
writer.WriteLiteral(tileGroupEnd, tileBits);
}
AlignToByteBoundary(ref writer);
WriteTileData(ref writer, tileInfo, tileWriter);
int endBitPosition = writer.BitPosition;
int headerBytes = (endBitPosition - startBitPosition) / 8;
return headerBytes;
}
private static void WriteTileData(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter)
{
int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount;
for (int tileNum = 0; tileNum < tileCount; tileNum++)
{
Span<byte> tileData = tileWriter.WriteTile(tileNum);
if (tileNum != tileCount - 1 && tileCount > 1)
{
writer.WriteLittleEndian((uint)tileData.Length - 1U, tileInfo.TileSizeBytes);
}
writer.WriteBlob(tileData);
}
}
private static int WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ)
{
bool isCoded = deltaQ != 0;
writer.WriteBoolean(isCoded);
if (isCoded)
{
writer.WriteSignedFromUnsigned(deltaQ, 7);
}
return deltaQ;
}
private static void WriteFrameDeltaQParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
if (frameHeader.QuantizationParameters.BaseQIndex > 0)
{
writer.WriteBoolean(frameHeader.DeltaQParameters.IsPresent);
}
if (frameHeader.DeltaQParameters.IsPresent)
{
writer.WriteLiteral((uint)frameHeader.DeltaQParameters.Resolution, 2);
}
}
private static void WriteFrameDeltaLoopFilterParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
if (frameHeader.DeltaQParameters.IsPresent)
{
if (!frameHeader.AllowIntraBlockCopy)
{
writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsPresent);
}
if (frameHeader.DeltaLoopFilterParameters.IsPresent)
{
writer.WriteLiteral((uint)frameHeader.DeltaLoopFilterParameters.Resolution, 2);
writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti);
}
}
}
/// <summary>
/// See section 5.9.12.
/// </summary>
private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
ObuQuantizationParameters quantParams = frameHeader.QuantizationParameters;
writer.WriteLiteral((uint)quantParams.BaseQIndex, 8);
WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.Y]);
if (sequenceHeader.ColorConfig.PlaneCount > 1)
{
if (sequenceHeader.ColorConfig.HasSeparateUvDelta)
{
writer.WriteBoolean(quantParams.HasSeparateUvDelta);
}
WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.U]);
WriteDeltaQ(ref writer, quantParams.DeltaQAc[(int)Av1Plane.U]);
if (quantParams.HasSeparateUvDelta)
{
WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.V]);
WriteDeltaQ(ref writer, quantParams.DeltaQAc[(int)Av1Plane.V]);
}
}
writer.WriteBoolean(quantParams.IsUsingQMatrix);
if (quantParams.IsUsingQMatrix)
{
writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.Y], 4);
writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.U], 4);
if (sequenceHeader.ColorConfig.HasSeparateUvDelta)
{
writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.V], 4);
}
}
}
private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
_ = sequenceHeader;
Guard.IsFalse(frameHeader.SegmentationParameters.Enabled, nameof(frameHeader.SegmentationParameters.Enabled), "Segmentation not supported yet.");
writer.WriteBoolean(false);
}
/// <summary>
/// 5.9.11. Loop filter params syntax
/// </summary>
private static void WriteLoopFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy)
{
return;
}
writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.FilterLevel[0], 6);
writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.FilterLevel[1], 6);
if (sequenceHeader.ColorConfig.PlaneCount > 1)
{
if (frameHeader.LoopFilterParameters.FilterLevel[0] > 0 || frameHeader.LoopFilterParameters.FilterLevel[1] > 0)
{
writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.FilterLevelU, 6);
writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.FilterLevelV, 6);
}
}
writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.SharpnessLevel, 3);
writer.WriteBoolean(frameHeader.LoopFilterParameters.ReferenceDeltaModeEnabled);
if (frameHeader.LoopFilterParameters.ReferenceDeltaModeEnabled)
{
writer.WriteBoolean(frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate);
if (frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate)
{
throw new NotImplementedException("Reference update of loop filter not supported yet.");
}
}
}
/// <summary>
/// 5.9.21. TX mode syntax.
/// </summary>
private static void WriteTransformMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
if (!frameHeader.CodedLossless)
{
writer.WriteBoolean(frameHeader.TransformMode == Av1TransformMode.Select);
}
}
private static void WriteLoopRestorationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration)
{
return;
}
int planesCount = sequenceHeader.ColorConfig.PlaneCount;
for (int i = 0; i < planesCount; i++)
{
writer.WriteLiteral((uint)frameHeader.LoopRestorationParameters.Items[i].Type, 2);
}
if (frameHeader.LoopRestorationParameters.UsesLoopRestoration)
{
uint unitShift = (uint)frameHeader.LoopRestorationParameters.UnitShift;
if (sequenceHeader.Use128x128Superblock)
{
writer.WriteLiteral(unitShift - 1, 1);
}
else
{
writer.WriteLiteral(unitShift & 0x01, 1);
if (unitShift > 0)
{
writer.WriteLiteral(unitShift - 1, 1);
}
}
if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && frameHeader.LoopRestorationParameters.UsesChromaLoopRestoration)
{
writer.WriteLiteral((uint)frameHeader.LoopRestorationParameters.UVShift, 1);
}
}
}
private static void WriteCdefParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
_ = writer;
_ = sequenceHeader;
if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableCdef)
{
return;
}
throw new NotImplementedException("Didn't implement writing CDEF yet.");
}
private static void WriteGlobalMotionParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
_ = writer;
if (frameHeader.IsIntra)
{
// Nothing to be written for INTRA frames.
return;
}
throw new InvalidImageContentException("AVIF files can only contain INTRA frames.");
}
private static void WriteFrameReferenceMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
_ = writer;
if (frameHeader.IsIntra)
{
// Nothing to be written for INTRA frames.
return;
}
throw new InvalidImageContentException("AVIF files can only contain INTRA frames.");
}
private static void WriteSkipModeParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader)
{
if (frameHeader.SkipModeParameters.SkipModeAllowed)
{
writer.WriteBoolean(frameHeader.SkipModeParameters.SkipModeFlag);
}
}
private static void WriteFilmGrainFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
ObuFilmGrainParameters grainParams = frameHeader.FilmGrainParameters;
if (!sequenceHeader.AreFilmGrainingParametersPresent || (!frameHeader.ShowFrame && !frameHeader.ShowableFrame))
{
return;
}
writer.WriteBoolean(grainParams.ApplyGrain);
if (!grainParams.ApplyGrain)
{
return;
}
writer.WriteLiteral(grainParams.GrainSeed, 16);
writer.WriteLiteral(grainParams.NumYPoints, 4);
Guard.NotNull(grainParams.PointYValue);
Guard.NotNull(grainParams.PointYScaling);
for (int i = 0; i < grainParams.NumYPoints; i++)
{
writer.WriteLiteral(grainParams.PointYValue[i], 8);
writer.WriteLiteral(grainParams.PointYScaling[i], 8);
}
if (!sequenceHeader.ColorConfig.IsMonochrome)
{
writer.WriteBoolean(grainParams.ChromaScalingFromLuma);
}
if (!sequenceHeader.ColorConfig.IsMonochrome &&
!grainParams.ChromaScalingFromLuma &&
(!sequenceHeader.ColorConfig.SubSamplingX || !sequenceHeader.ColorConfig.SubSamplingY || grainParams.NumYPoints != 0))
{
writer.WriteLiteral(grainParams.NumCbPoints, 4);
Guard.NotNull(grainParams.PointCbValue);
Guard.NotNull(grainParams.PointCbScaling);
for (int i = 0; i < grainParams.NumCbPoints; i++)
{
writer.WriteLiteral(grainParams.PointCbValue[i], 8);
writer.WriteLiteral(grainParams.PointCbScaling[i], 8);
}
writer.WriteLiteral(grainParams.NumCrPoints, 4);
Guard.NotNull(grainParams.PointCrValue);
Guard.NotNull(grainParams.PointCrScaling);
for (int i = 0; i < grainParams.NumCbPoints; i++)
{
writer.WriteLiteral(grainParams.PointCrValue[i], 8);
writer.WriteLiteral(grainParams.PointCrScaling[i], 8);
}
}
writer.WriteLiteral(grainParams.GrainScalingMinus8, 2);
writer.WriteLiteral(grainParams.ArCoeffLag, 2);
uint numPosLuma = 2 * grainParams.ArCoeffLag * (grainParams.ArCoeffLag + 1);
uint numPosChroma = 0;
if (grainParams.NumYPoints != 0)
{
numPosChroma = numPosLuma + 1;
Guard.NotNull(grainParams.ArCoeffsYPlus128);
for (int i = 0; i < numPosLuma; i++)
{
writer.WriteLiteral(grainParams.ArCoeffsYPlus128[i], 8);
}
}
if (grainParams.ChromaScalingFromLuma || grainParams.NumCbPoints != 0)
{
Guard.NotNull(grainParams.ArCoeffsCbPlus128);
for (int i = 0; i < numPosChroma; i++)
{
writer.WriteLiteral(grainParams.ArCoeffsCbPlus128[i], 8);
}
}
if (grainParams.ChromaScalingFromLuma || grainParams.NumCrPoints != 0)
{
Guard.NotNull(grainParams.ArCoeffsCrPlus128);
for (int i = 0; i < numPosChroma; i++)
{
writer.WriteLiteral(grainParams.ArCoeffsCrPlus128[i], 8);
}
}
writer.WriteLiteral(grainParams.ArCoeffShiftMinus6, 2);
writer.WriteLiteral(grainParams.GrainScaleShift, 2);
if (grainParams.NumCbPoints != 0)
{
writer.WriteLiteral(grainParams.CbMult, 8);
writer.WriteLiteral(grainParams.CbLumaMult, 8);
writer.WriteLiteral(grainParams.CbOffset, 9);
}
if (grainParams.NumCrPoints != 0)
{
writer.WriteLiteral(grainParams.CrMult, 8);
writer.WriteLiteral(grainParams.CrLumaMult, 8);
writer.WriteLiteral(grainParams.CrOffset, 9);
}
writer.WriteBoolean(grainParams.OverlapFlag);
writer.WriteBoolean(grainParams.ClipToRestrictedRange);
}
}

171
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs

@ -0,0 +1,171 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline;
internal class Av1FrameDecoder : IAv1FrameDecoder
{
private readonly ObuSequenceHeader sequenceHeader;
private readonly ObuFrameHeader frameHeader;
private readonly Av1FrameInfo frameInfo;
private readonly Av1FrameBuffer<byte> frameBuffer;
private readonly Av1InverseQuantizer inverseQuantizer;
private readonly Av1DeQuantizationContext deQuants;
private readonly Av1BlockDecoder blockDecoder;
public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer<byte> frameBuffer)
{
this.sequenceHeader = sequenceHeader;
this.frameHeader = frameHeader;
this.frameInfo = frameInfo;
this.frameBuffer = frameBuffer;
this.inverseQuantizer = new(sequenceHeader, frameHeader);
this.deQuants = new(sequenceHeader, frameHeader);
this.blockDecoder = new(this.sequenceHeader, this.frameHeader, this.frameInfo, this.frameBuffer);
}
public void DecodeFrame()
{
for (int column = 0; column < this.frameHeader.TilesInfo.TileColumnCount; column++)
{
this.DecodeFrameTiles(column);
}
bool doLoopFilterFlag = false;
bool doLoopRestoration = false;
bool doUpscale = false;
if (doLoopFilterFlag)
{
this.DecodeLoopFilterForFrame();
}
if (doLoopRestoration)
{
// LoopRestorationSaveBoundaryLines(false);
}
// DecodeCdef();
// SuperResolutionUpscaling(doUpscale);
if (doLoopRestoration && doUpscale)
{
// LoopRestorationSaveBoundaryLines(true);
}
// DecodeLoopRestoration(doLoopRestoration);
// PadPicture();
}
/// <summary>
/// SVT: decode_tile
/// </summary>
private void DecodeFrameTiles(int tileColumn)
{
int tileRowCount = this.frameHeader.TilesInfo.TileRowCount;
int tileCount = tileRowCount * this.frameHeader.TilesInfo.TileColumnCount;
for (int row = 0; row < tileRowCount; row++)
{
int superblockRowTileStart = this.frameHeader.TilesInfo.TileRowStartModeInfo[row] << Av1Constants.ModeInfoSizeLog2 >>
this.sequenceHeader.SuperblockSizeLog2;
int superblockRow = row + superblockRowTileStart;
int modeInfoRow = superblockRow << this.sequenceHeader.SuperblockSizeLog2 >> Av1Constants.ModeInfoSizeLog2;
// EbColorConfig* color_config = &dec_mod_ctxt->seq_header->color_config;
// svt_cfl_init(&dec_mod_ctxt->cfl_ctx, color_config);
this.DecodeTileRow(row, tileColumn, modeInfoRow, superblockRow);
}
}
/// <summary>
/// SVT: decode_tile_row
/// </summary>
private void DecodeTileRow(int tileRow, int tileColumn, int modeInfoRow, int superblockRow)
{
int superblockModeInfoSizeLog2 = this.sequenceHeader.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2;
int superblockRowTileStart = this.frameHeader.TilesInfo.TileRowStartModeInfo[tileRow] << Av1Constants.ModeInfoSizeLog2 >>
this.sequenceHeader.SuperblockSizeLog2;
int superblockRowInTile = superblockRow - superblockRowTileStart;
ObuTileGroupHeader tileInfo = this.frameHeader.TilesInfo;
for (int modeInfoColumn = tileInfo.TileColumnStartModeInfo[tileColumn]; modeInfoColumn < tileInfo.TileColumnStartModeInfo[tileColumn + 1];
modeInfoColumn += this.sequenceHeader.SuperblockModeInfoSize)
{
int superblockColumn = modeInfoColumn << Av1Constants.ModeInfoSizeLog2 >> this.sequenceHeader.SuperblockSizeLog2;
Av1SuperblockInfo superblockInfo = this.frameInfo.GetSuperblock(new Point(superblockColumn, superblockRow));
Point modeInfoPosition = new(modeInfoColumn, modeInfoRow);
this.DecodeSuperblock(modeInfoPosition, superblockInfo, new Av1TileInfo(tileRow, tileColumn, this.frameHeader));
}
}
/// <summary>
/// SVT: svt_aom_decode_super_block
/// </summary>
public void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
{
this.blockDecoder.UpdateSuperblock(superblockInfo);
this.inverseQuantizer.UpdateDequant(this.deQuants, superblockInfo);
this.DecodePartition(modeInfoPosition, superblockInfo, tileInfo);
}
/// <summary>
/// SVT: decode_partition
/// </summary>
private void DecodePartition(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
{
Av1BlockModeInfo modeInfo = superblockInfo.GetModeInfo(modeInfoPosition);
for (int i = 0; i < superblockInfo.BlockCount; i++)
{
Point subPosition = modeInfo.PositionInSuperblock;
Av1BlockSize subSize = modeInfo.BlockSize;
Point globalPosition = new(modeInfoPosition.X, modeInfoPosition.Y);
globalPosition.Offset(subPosition);
this.blockDecoder.DecodeBlock(modeInfo, globalPosition, subSize, superblockInfo, tileInfo);
}
}
private void DecodeLoopFilterForFrame()
{
int superblockSizeLog2 = this.sequenceHeader.SuperblockSizeLog2;
int pictureWidthInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameWidth, this.sequenceHeader.SuperblockSizeLog2);
int pictureHeightInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameHeight, this.sequenceHeader.SuperblockSizeLog2);
// Loop over a frame : tregger dec_loop_filter_sb for each SB
for (int superblockIndexY = 0; superblockIndexY < pictureHeightInSuperblocks; ++superblockIndexY)
{
for (int superblockIndexX = 0; superblockIndexX < pictureWidthInSuperblocks; ++superblockIndexX)
{
int superblockOriginX = superblockIndexX << superblockSizeLog2;
int superblockOriginY = superblockIndexY << superblockSizeLog2;
bool endOfRowFlag = superblockIndexX == pictureWidthInSuperblocks - 1;
Point superblockPoint = new(superblockOriginX, superblockOriginY);
Av1SuperblockInfo superblockInfo = this.frameInfo.GetSuperblock(superblockPoint);
// LF function for a SB
/*
DecodeLoopFilterForSuperblock(
superblockInfo,
this.frameHeader,
this.sequenceHeader,
reconstructionFrameBuffer,
loopFilterContext,
superblockOriginY >> 2,
superblockOriginX >> 2,
Av1Plane.Y,
3,
endOfRowFlag,
superblockInfo.SuperblockDeltaLoopFilter);
*/
}
}
}
}

74
src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs

@ -0,0 +1,74 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline;
internal class Av1FrameEncoder
{
private readonly Av1FrameBuffer<byte> frameBuffer;
public Av1FrameEncoder(Av1FrameBuffer<byte> frameBuffer)
{
this.frameBuffer = frameBuffer;
}
/// <summary>
/// SVT: svt_av1_enc_init
/// </summary>
public static void Encode()
{
/************************************
* Thread Handles
************************************/
// Resource Coordination
// Single thread calling svt_aom_resource_coordination_kernel with context enc_handle_ptr->resource_coordination_context_ptr
// Multiple threads calling svt_aom_picture_analysis_kernel, with context enc_handle_ptr->picture_analysis_context_ptr_array
// Picture Decision
// Single thread calling svt_aom_picture_decision_kernel with context enc_handle_ptr->picture_decision_context_ptr
// Motion Estimation
// Multiple threads calling svt_aom_motion_estimation_kernel with context enc_handle_ptr->motion_estimation_context_ptr_array
// Initial Rate Control
// Single thread calling svt_aom_initial_rate_control_kernel with context enc_handle_ptr->initial_rate_control_context_ptr
// Source Based Oprations
// <Multiple threads calling svt_aom_source_based_operations_kernel with context enc_handle_ptr->source_based_operations_context_ptr_array
// TPL dispenser
// Multiple threads calling svt_aom_tpl_disp_kernel with context enc_handle_ptr->tpl_disp_context_ptr_array
// Picture Manager
// Single thread calling svt_aom_picture_manager_kernel with context enc_handle_ptr->picture_manager_context_ptr
// Rate Control
// Single thread calling svt_aom_rate_control_kernel with context enc_handle_ptr->rate_control_context_ptr
// Mode Decision Configuration Process
// Multiple threads calling svt_aom_mode_decision_configuration_kernel with context enc_handle_ptr->mode_decision_configuration_context_ptr_array
// EncDec Process
// Multiple threads calling svt_aom_mode_decision_kernel enc_handle_ptr->enc_dec_context_ptr_array
// Dlf Process
// Multiple threads calling svt_aom_dlf_kernel with context enc_handle_ptr->dlf_context_ptr_array
// Cdef Process
// Multiple threads calling svt_aom_cdef_kernel enc_handle_ptr->cdef_context_ptr_array
// Rest Process
// Multiple threads calling svt_aom_rest_kernel enc_handle_ptr->rest_context_ptr_array
// Entropy Coding Process
// Multiple threads calling svt_aom_entropy_coding_kernel enc_handle_ptr->entropy_coding_context_ptr_array
// Packetization
// Single thread calling svt_aom_packetization_kernel with context enc_handle_ptr->packetization_context_ptr
// svt_print_memory_usage();
}
}

20
src/ImageSharp/Formats/Heif/Av1/Pipeline/IAv1FrameDecoder.cs

@ -0,0 +1,20 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline;
/// <summary>
/// Interface for decoder of a single frame.
/// </summary>
internal interface IAv1FrameDecoder
{
/// <summary>
/// Decode a single superblock.
/// </summary>
/// <param name="modeInfoPosition">The top left position of the superblock, in mode info units.</param>
/// <param name="superblockInfo">The superblock to decode</param>
/// <param name="tileInfo">The tile in whcih the superblock is positioned.</param>
void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo);
}

8
src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs

@ -0,0 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter;
internal class Av1LoopFilterContext
{
}

68
src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs

@ -0,0 +1,68 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter;
internal class Av1LoopFilterDecoder
{
private readonly ObuSequenceHeader sequenceHeader;
private readonly ObuFrameHeader frameHeader;
private readonly Av1FrameInfo frameInfo;
private readonly Av1FrameBuffer<byte> frameBuffer;
private readonly Av1LoopFilterContext loopFilterContext;
public Av1LoopFilterDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer<byte> frameBuffer)
{
this.sequenceHeader = sequenceHeader;
this.frameHeader = frameHeader;
this.frameInfo = frameInfo;
this.frameBuffer = frameBuffer;
this.loopFilterContext = new();
}
public void DecodeFrame(bool doLoopFilterFlag)
{
Guard.NotNull(this.sequenceHeader);
Guard.NotNull(this.frameHeader);
Guard.NotNull(this.frameInfo);
if (!doLoopFilterFlag)
{
return;
}
int superblockSizeLog2 = this.sequenceHeader.SuperblockSizeLog2;
int frameWidthInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameWidth, this.sequenceHeader.SuperblockSizeLog2);
int frameHeightInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameHeight, this.sequenceHeader.SuperblockSizeLog2);
// Loop over a frame : tregger dec_loop_filter_sb for each SB
for (int superblockIndexY = 0; superblockIndexY < frameHeightInSuperblocks; ++superblockIndexY)
{
for (int superblockIndexX = 0; superblockIndexX < frameWidthInSuperblocks; ++superblockIndexX)
{
int superblockOriginX = superblockIndexX << superblockSizeLog2;
int superblockOriginY = superblockIndexY << superblockSizeLog2;
bool endOfRowFlag = superblockIndexX == frameWidthInSuperblocks - 1;
Point superblockPoint = new(superblockOriginX, superblockOriginY);
Av1SuperblockInfo superblockInfo = this.frameInfo.GetSuperblock(superblockPoint);
Point superblockOriginInModeInfo = new(superblockOriginX >> 2, superblockOriginY >> 2);
// LF function for a SB
this.DecodeForSuperblock(
superblockInfo,
superblockOriginInModeInfo,
Av1Plane.Y,
3,
endOfRowFlag,
superblockInfo.SuperblockDeltaLoopFilter);
}
}
}
private void DecodeForSuperblock(Av1SuperblockInfo superblockInfo, Point modeInfoLocation, Av1Plane startPlane, int endPlane, bool endOfRowFlag, Span<int> superblockDeltaLoopFilter)
=> throw new NotImplementedException();
}

49
src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs

@ -0,0 +1,49 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
internal class Av1DeQuantizationContext
{
private readonly short[][] dcContent;
private readonly short[][] acContent;
/// <remarks>
/// SVT: svt_aom_setup_segmentation_dequant
/// </remarks>
public Av1DeQuantizationContext(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
Av1BitDepth bitDepth = sequenceHeader.ColorConfig.BitDepth;
this.dcContent = new short[Av1Constants.MaxSegmentCount][];
this.acContent = new short[Av1Constants.MaxSegmentCount][];
for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++)
{
this.dcContent[segmentId] = new short[Av1Constants.MaxPlanes];
this.acContent[segmentId] = new short[Av1Constants.MaxPlanes];
int qindex = Av1QuantizationLookup.GetQIndex(frameHeader.SegmentationParameters, segmentId, frameHeader.QuantizationParameters.BaseQIndex);
for (int plane = 0; plane < Av1Constants.MaxPlanes; plane++)
{
int dc_delta_q = frameHeader.QuantizationParameters.DeltaQDc[plane];
int ac_delta_q = frameHeader.QuantizationParameters.DeltaQAc[plane];
this.dcContent[segmentId][plane] = Av1QuantizationLookup.GetDcQuant(qindex, dc_delta_q, bitDepth);
this.acContent[segmentId][plane] = Av1QuantizationLookup.GetAcQuant(qindex, ac_delta_q, bitDepth);
}
}
}
public short GetDc(int segmentId, Av1Plane plane)
=> this.dcContent[segmentId][(int)plane];
public short GetAc(int segmentId, Av1Plane plane)
=> this.acContent[segmentId][(int)plane];
public void SetAc(int segmentId, Av1Plane plane, short value)
=> this.dcContent[segmentId][(int)plane] = value;
public void SetDc(int segmentId, Av1Plane plane, short value)
=> this.dcContent[segmentId][(int)plane] = value;
}

6803
src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizationLookup.cs

File diff suppressed because it is too large

117
src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs

@ -0,0 +1,117 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
internal class Av1InverseQuantizer
{
private readonly ObuSequenceHeader sequenceHeader;
private readonly ObuFrameHeader frameHeader;
private Av1DeQuantizationContext deQuantsDeltaQ;
public Av1InverseQuantizer(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
{
this.sequenceHeader = sequenceHeader;
this.frameHeader = frameHeader;
this.deQuantsDeltaQ = new(sequenceHeader, frameHeader);
}
public void UpdateDequant(Av1DeQuantizationContext deQuants, Av1SuperblockInfo superblockInfo)
{
Av1BitDepth bitDepth = this.sequenceHeader.ColorConfig.BitDepth;
Guard.NotNull(deQuants, nameof(deQuants));
this.deQuantsDeltaQ = deQuants;
if (this.frameHeader.DeltaQParameters.IsPresent)
{
for (int i = 0; i < Av1Constants.MaxSegmentCount; i++)
{
int currentQIndex = Av1QuantizationLookup.GetQIndex(this.frameHeader.SegmentationParameters, i, superblockInfo.SuperblockDeltaQ);
for (Av1Plane plane = 0; (int)plane < Av1Constants.MaxPlanes; plane++)
{
int dcDeltaQ = this.frameHeader.QuantizationParameters.DeltaQDc[(int)plane];
int acDeltaQ = this.frameHeader.QuantizationParameters.DeltaQAc[(int)plane];
this.deQuantsDeltaQ.SetDc(i, plane, Av1QuantizationLookup.GetDcQuant(currentQIndex, dcDeltaQ, bitDepth));
this.deQuantsDeltaQ.SetAc(i, plane, Av1QuantizationLookup.GetAcQuant(currentQIndex, acDeltaQ, bitDepth));
}
}
}
}
/// <summary>
/// SVT: svt_aom_inverse_quantize
/// </summary>
public int InverseQuantize(Av1BlockModeInfo mode, Span<int> level, Span<int> qCoefficients, Av1TransformType transformType, Av1TransformSize transformSize, Av1Plane plane)
{
Guard.NotNull(this.deQuantsDeltaQ);
Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType);
ReadOnlySpan<short> scanIndices = scanOrder.Scan;
int maxValue = (1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount())) - 1;
int minValue = -(1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount()));
Av1TransformSize qmTransformSize = transformSize.GetAdjusted();
bool usingQuantizationMatrix = this.frameHeader.QuantizationParameters.IsUsingQMatrix;
bool lossless = this.frameHeader.LosslessArray[mode.SegmentId];
short dequantDc = this.deQuantsDeltaQ.GetDc(mode.SegmentId, plane);
short dequantAc = this.deQuantsDeltaQ.GetAc(mode.SegmentId, plane);
int qmLevel = lossless || !usingQuantizationMatrix ? Av1ScanOrderConstants.QuantizationMatrixLevelCount - 1 : this.frameHeader.QuantizationParameters.QMatrix[(int)plane];
ReadOnlySpan<int> iqMatrix = (transformType.ToClass() == Av1TransformClass.Class2D) ?
Av1InverseQuantizationLookup.GetQuantizationMatrix(qmLevel, plane, qmTransformSize)
: Av1InverseQuantizationLookup.GetQuantizationMatrix(Av1Constants.QuantificationMatrixLevelCount - 1, Av1Plane.Y, qmTransformSize);
int shift = transformSize.GetScale();
int coefficientCount = level[0];
level = level[1..];
int lev = level[0];
int qCoefficient;
if (lev != 0)
{
int pos = scanIndices[0];
qCoefficient = (int)(((long)Math.Abs(lev) * GetDeQuantizedValue(dequantDc, pos, iqMatrix)) & 0xffffff);
qCoefficient >>= shift;
if (lev < 0)
{
qCoefficient = -qCoefficient;
}
qCoefficients[0] = Av1Math.Clamp(qCoefficient, minValue, maxValue);
}
for (int i = 1; i < coefficientCount; i++)
{
lev = level[i];
if (lev != 0)
{
int pos = scanIndices[i];
qCoefficient = (int)(((long)Math.Abs(lev) * GetDeQuantizedValue(dequantAc, pos, iqMatrix)) & 0xffffff);
qCoefficient >>= shift;
if (lev < 0)
{
qCoefficient = -qCoefficient;
}
qCoefficients[pos] = Av1Math.Clamp(qCoefficient, minValue, maxValue);
}
}
return coefficientCount;
}
/// <summary>
/// SVT: get_dqv
/// </summary>
private static int GetDeQuantizedValue(short dequant, int coefficientIndex, ReadOnlySpan<int> iqMatrix)
{
const int bias = 1 << (Av1ScanOrderConstants.QuantizationMatrixLevelBitCount - 1);
int deQuantifiedValue = dequant;
deQuantifiedValue = ((iqMatrix[coefficientIndex] * deQuantifiedValue) + bias) >> Av1ScanOrderConstants.QuantizationMatrixLevelBitCount;
return deQuantifiedValue;
}
}

190
src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs

@ -0,0 +1,190 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification;
internal class Av1QuantizationLookup
{
// Coefficient scaling and quantization with AV1 TX are tailored to
// the AV1 TX transforms. Regardless of the bit-depth of the input,
// the transform stages scale the coefficient values up by a factor of
// 8 (3 bits) over the scale of the pixel values. Thus, for 8-bit
// input, the coefficients have effectively 11 bits of scale depth
// (8+3), 10-bit input pixels result in 13-bit coefficient depth
// (10+3) and 12-bit pixels yield 15-bit (12+3) coefficient depth.
// All quantizers are built using this invariant of x8, 3-bit scaling,
// thus the Q3 suffix.
// A partial exception to this rule is large transforms; to avoid
// overflow, TX blocks with > 256 pels (>16x16) are scaled only
// 4-times unity (2 bits) over the pixel depth, and TX blocks with
// over 1024 pixels (>32x32) are scaled up only 2x unity (1 bit).
// This descaling is found via av1_tx_get_scale(). Thus, 16x32, 32x16
// and 32x32 transforms actually return Q2 coefficients, and 32x64,
// 64x32 and 64x64 transforms return Q1 coefficients. However, the
// quantizers are de-scaled down on-the-fly by the same amount
// (av1_tx_get_scale()) during quantization, and as such the
// dequantized/decoded coefficients, even for large TX blocks, are always
// effectively Q3. Meanwhile, quantized/coded coefficients are Q0
// because Qn quantizers are applied to Qn tx coefficients.
// Note that encoder decision making (which uses the quantizer to
// generate several bespoke lamdas for RDO and other heuristics)
// expects quantizers to be larger for higher-bitdepth input. In
// addition, the minimum allowable quantizer is 4; smaller values will
// underflow to 0 in the actual quantization routines.
private static readonly short[] AcQlookup8 = [
4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101,
102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138,
140, 142, 144, 146, 148, 150, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, 182, 185, 188,
191, 194, 197, 200, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, 260,
265, 270, 275, 280, 285, 290, 295, 300, 305, 311, 317, 323, 329, 335, 341, 347, 353, 359, 366,
373, 380, 387, 394, 401, 408, 416, 424, 432, 440, 448, 456, 465, 474, 483, 492, 501, 510, 520,
530, 540, 550, 560, 571, 582, 593, 604, 615, 627, 639, 651, 663, 676, 689, 702, 715, 729, 743,
757, 771, 786, 801, 816, 832, 848, 864, 881, 898, 915, 933, 951, 969, 988, 1007, 1026, 1046, 1066,
1087, 1108, 1129, 1151, 1173, 1196, 1219, 1243, 1267, 1292, 1317, 1343, 1369, 1396, 1423, 1451, 1479, 1508, 1537,
1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828,
];
private static readonly short[] AcQlookup10 = [
4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63,
67, 71, 75, 79, 83, 88, 92, 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, 140, 145,
149, 154, 158, 163, 168, 172, 177, 181, 186, 190, 195, 199, 204, 208, 213, 217, 222, 226, 231,
235, 240, 244, 249, 253, 258, 262, 267, 271, 275, 280, 284, 289, 293, 297, 302, 306, 311, 315,
319, 324, 328, 332, 337, 341, 345, 349, 354, 358, 362, 367, 371, 375, 379, 384, 388, 392, 396,
401, 409, 417, 425, 433, 441, 449, 458, 466, 474, 482, 490, 498, 506, 514, 523, 531, 539, 547,
555, 563, 571, 579, 588, 596, 604, 616, 628, 640, 652, 664, 676, 688, 700, 713, 725, 737, 749,
761, 773, 785, 797, 809, 825, 841, 857, 873, 889, 905, 922, 938, 954, 970, 986, 1002, 1018, 1038,
1058, 1078, 1098, 1118, 1138, 1158, 1178, 1198, 1218, 1242, 1266, 1290, 1314, 1338, 1362, 1386, 1411, 1435, 1463,
1491, 1519, 1547, 1575, 1603, 1631, 1663, 1695, 1727, 1759, 1791, 1823, 1859, 1895, 1931, 1967, 2003, 2039, 2079,
2119, 2159, 2199, 2239, 2283, 2327, 2371, 2415, 2459, 2507, 2555, 2603, 2651, 2703, 2755, 2807, 2859, 2915, 2971,
3027, 3083, 3143, 3203, 3263, 3327, 3391, 3455, 3523, 3591, 3659, 3731, 3803, 3876, 3952, 4028, 4104, 4184, 4264,
4348, 4432, 4516, 4604, 4692, 4784, 4876, 4972, 5068, 5168, 5268, 5372, 5476, 5584, 5692, 5804, 5916, 6032, 6148,
6268, 6388, 6512, 6640, 6768, 6900, 7036, 7172, 7312,
];
private static readonly short[] AcQlookup12 = [
4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168,
183, 199, 214, 230, 247, 263, 280, 297, 314, 331, 349, 366, 384, 402, 420, 438,
456, 475, 493, 511, 530, 548, 567, 586, 604, 623, 642, 660, 679, 698, 716, 735,
753, 772, 791, 809, 828, 846, 865, 884, 902, 920, 939, 957, 976, 994, 1012, 1030,
1049, 1067, 1085, 1103, 1121, 1139, 1157, 1175, 1193, 1211, 1229, 1246, 1264, 1282, 1299, 1317,
1335, 1352, 1370, 1387, 1405, 1422, 1440, 1457, 1474, 1491, 1509, 1526, 1543, 1560, 1577, 1595,
1627, 1660, 1693, 1725, 1758, 1791, 1824, 1856, 1889, 1922, 1954, 1987, 2020, 2052, 2085, 2118,
2150, 2183, 2216, 2248, 2281, 2313, 2346, 2378, 2411, 2459, 2508, 2556, 2605, 2653, 2701, 2750,
2798, 2847, 2895, 2943, 2992, 3040, 3088, 3137, 3185, 3234, 3298, 3362, 3426, 3491, 3555, 3619,
3684, 3748, 3812, 3876, 3941, 4005, 4069, 4149, 4230, 4310, 4390, 4470, 4550, 4631, 4711, 4791,
4871, 4967, 5064, 5160, 5256, 5352, 5448, 5544, 5641, 5737, 5849, 5961, 6073, 6185, 6297, 6410,
6522, 6650, 6778, 6906, 7034, 7162, 7290, 7435, 7579, 7723, 7867, 8011, 8155, 8315, 8475, 8635,
8795, 8956, 9132, 9308, 9484, 9660, 9836, 10028, 10220, 10412, 10604, 10812, 11020, 11228, 11437, 11661,
11885, 12109, 12333, 12573, 12813, 13053, 13309, 13565, 13821, 14093, 14365, 14637, 14925, 15213, 15502, 15806,
16110, 16414, 16734, 17054, 17390, 17726, 18062, 18414, 18766, 19134, 19502, 19886, 20270, 20670, 21070, 21486,
21902, 22334, 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, 28143, 28687, 29247,
];
private static readonly short[] DcQlookup8 = [
4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23,
24, 25, 26, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, 40,
41, 42, 43, 43, 44, 45, 46, 47, 48, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57,
57, 58, 59, 60, 61, 62, 62, 63, 64, 65, 66, 66, 67, 68, 69, 70, 70, 71, 72, 73,
74, 74, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 85, 85, 87, 88, 90, 92,
93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, 113, 114, 116, 117, 118, 120, 121,
123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 161, 164,
166, 169, 172, 174, 177, 180, 182, 185, 187, 190, 192, 195, 199, 202, 205, 208, 211, 214, 217, 220,
223, 226, 230, 233, 237, 240, 243, 247, 250, 253, 257, 261, 265, 269, 272, 276, 280, 284, 288, 292,
296, 300, 304, 309, 313, 317, 322, 326, 330, 335, 340, 344, 349, 354, 359, 364, 369, 374, 379, 384,
389, 395, 400, 406, 411, 417, 423, 429, 435, 441, 447, 454, 461, 467, 475, 482, 489, 497, 505, 513,
522, 530, 539, 549, 559, 569, 579, 590, 602, 614, 626, 640, 654, 668, 684, 700, 717, 736, 755, 775,
796, 819, 843, 869, 896, 925, 955, 988, 1022, 1058, 1098, 1139, 1184, 1232, 1282, 1336,
];
private static readonly short[] DcQlookup10 = [
4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57,
60, 64, 68, 71, 75, 78, 82, 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, 124, 128,
132, 136, 140, 143, 147, 151, 155, 159, 163, 166, 170, 174, 178, 182, 185, 189, 193, 197, 200,
204, 208, 212, 215, 219, 223, 226, 230, 233, 237, 241, 244, 248, 251, 255, 259, 262, 266, 269,
273, 276, 280, 283, 287, 290, 293, 297, 300, 304, 307, 310, 314, 317, 321, 324, 327, 331, 334,
337, 343, 350, 356, 362, 369, 375, 381, 387, 394, 400, 406, 412, 418, 424, 430, 436, 442, 448,
454, 460, 466, 472, 478, 484, 490, 499, 507, 516, 525, 533, 542, 550, 559, 567, 576, 584, 592,
601, 609, 617, 625, 634, 644, 655, 666, 676, 687, 698, 708, 718, 729, 739, 749, 759, 770, 782,
795, 807, 819, 831, 844, 856, 868, 880, 891, 906, 920, 933, 947, 961, 975, 988, 1001, 1015, 1030,
1045, 1061, 1076, 1090, 1105, 1120, 1137, 1153, 1170, 1186, 1202, 1218, 1236, 1253, 1271, 1288, 1306, 1323, 1342,
1361, 1379, 1398, 1416, 1436, 1456, 1476, 1496, 1516, 1537, 1559, 1580, 1601, 1624, 1647, 1670, 1692, 1717, 1741,
1766, 1791, 1817, 1844, 1871, 1900, 1929, 1958, 1990, 2021, 2054, 2088, 2123, 2159, 2197, 2236, 2276, 2319, 2363,
2410, 2458, 2508, 2561, 2616, 2675, 2737, 2802, 2871, 2944, 3020, 3102, 3188, 3280, 3375, 3478, 3586, 3702, 3823,
3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130, 5347,
];
private static readonly short[] DcQlookup12 = [
4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153,
166, 180, 194, 208, 222, 237, 251, 266, 281, 296, 312, 327, 343, 358, 374, 390,
405, 421, 437, 453, 469, 484, 500, 516, 532, 548, 564, 580, 596, 611, 627, 643,
659, 674, 690, 706, 721, 737, 752, 768, 783, 798, 814, 829, 844, 859, 874, 889,
904, 919, 934, 949, 964, 978, 993, 1008, 1022, 1037, 1051, 1065, 1080, 1094, 1108, 1122,
1136, 1151, 1165, 1179, 1192, 1206, 1220, 1234, 1248, 1261, 1275, 1288, 1302, 1315, 1329, 1342,
1368, 1393, 1419, 1444, 1469, 1494, 1519, 1544, 1569, 1594, 1618, 1643, 1668, 1692, 1717, 1741,
1765, 1789, 1814, 1838, 1862, 1885, 1909, 1933, 1957, 1992, 2027, 2061, 2096, 2130, 2165, 2199,
2233, 2267, 2300, 2334, 2367, 2400, 2434, 2467, 2499, 2532, 2575, 2618, 2661, 2704, 2746, 2788,
2830, 2872, 2913, 2954, 2995, 3036, 3076, 3127, 3177, 3226, 3275, 3324, 3373, 3421, 3469, 3517,
3565, 3621, 3677, 3733, 3788, 3843, 3897, 3951, 4005, 4058, 4119, 4181, 4241, 4301, 4361, 4420,
4479, 4546, 4612, 4677, 4742, 4807, 4871, 4942, 5013, 5083, 5153, 5222, 5291, 5367, 5442, 5517,
5591, 5665, 5745, 5825, 5905, 5984, 6063, 6149, 6234, 6319, 6404, 6495, 6587, 6678, 6769, 6867,
6966, 7064, 7163, 7269, 7376, 7483, 7599, 7715, 7832, 7958, 8085, 8214, 8352, 8492, 8635, 8788,
8945, 9104, 9275, 9450, 9639, 9832, 10031, 10245, 10465, 10702, 10946, 11210, 11482, 11776, 12081, 12409,
12750, 13118, 13501, 13913, 14343, 14807, 15290, 15812, 16356, 16943, 17575, 18237, 18949, 19718, 20521, 21387,
];
public static short GetDcQuant(int qIndex, int dcDeltaQ, Av1BitDepth bitDepth)
{
int qClamped = Av1Math.Clamp(qIndex + dcDeltaQ, 0, Av1Constants.MaxQ);
switch (bitDepth)
{
case Av1BitDepth.EightBit:
return DcQlookup8[qClamped];
case Av1BitDepth.TenBit:
return DcQlookup10[qClamped];
case Av1BitDepth.TwelveBit:
return DcQlookup12[qClamped];
default:
Guard.IsFalse(true, nameof(bitDepth), "bit_depth should be EB_EIGHT_BIT, EB_TEN_BIT or EB_TWELVE_BIT");
return -1;
}
}
public static short GetAcQuant(int qIndex, int dcDeltaQ, Av1BitDepth bitDepth)
{
int qClamped = Av1Math.Clamp(qIndex + dcDeltaQ, 0, Av1Constants.MaxQ);
switch (bitDepth)
{
case Av1BitDepth.EightBit:
return AcQlookup8[qClamped];
case Av1BitDepth.TenBit:
return AcQlookup10[qClamped];
case Av1BitDepth.TwelveBit:
return AcQlookup12[qClamped];
default:
Guard.IsFalse(true, nameof(bitDepth), "bit_depth should be EB_EIGHT_BIT, EB_TEN_BIT or EB_TWELVE_BIT");
return -1;
}
}
public static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex)
{
if (segmentationParameters.IsFeatureActive(segmentId, ObuSegmentationLevelFeature.AlternativeQuantizer))
{
int data = segmentationParameters.FeatureData[segmentId, (int)ObuSegmentationLevelFeature.AlternativeQuantizer];
int qIndex = baseQIndex + data;
return Av1Math.Clamp(qIndex, 0, Av1Constants.MaxQ);
}
else
{
return baseQIndex;
}
}
}

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;
}
}

41
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs

@ -0,0 +1,41 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1DcFillPredictor : IAv1Predictor
{
private readonly uint blockWidth;
private readonly uint blockHeight;
public Av1DcFillPredictor(Size blockSize)
{
this.blockWidth = (uint)blockSize.Width;
this.blockHeight = (uint)blockSize.Height;
}
public Av1DcFillPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (uint)transformSize.GetWidth();
this.blockHeight = (uint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1DcFillPredictor(transformSize).PredictScalar(destination, stride, above, left);
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
const byte expectedDc = 0x80;
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
ref byte destinationRef = ref destination[0];
for (uint r = 0; r < this.blockHeight; r++)
{
Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

49
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs

@ -0,0 +1,49 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1DcLeftPredictor : IAv1Predictor
{
private readonly uint blockWidth;
private readonly uint blockHeight;
public Av1DcLeftPredictor(Size blockSize)
{
this.blockWidth = (uint)blockSize.Width;
this.blockHeight = (uint)blockSize.Height;
}
public Av1DcLeftPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (uint)transformSize.GetWidth();
this.blockHeight = (uint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1DcLeftPredictor(transformSize).PredictScalar(destination, stride, above, left);
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
int sum = 0;
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
ref byte leftRef = ref left[0];
ref byte destinationRef = ref destination[0];
for (uint i = 0; i < this.blockHeight; i++)
{
sum += Unsafe.Add(ref leftRef, i);
}
byte expectedDc = (byte)((sum + (this.blockHeight >> 1)) / this.blockHeight);
for (uint r = 0; r < this.blockHeight; r++)
{
Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

58
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs

@ -0,0 +1,58 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1DcPredictor : IAv1Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1DcPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1DcPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1DcPredictor(transformSize).PredictScalar(destination, stride, above, left);
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
int sum = 0;
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
ref byte leftRef = ref left[0];
ref byte aboveRef = ref above[0];
ref byte destinationRef = ref destination[0];
uint count = (uint)(this.blockWidth + this.blockHeight);
uint width = (uint)this.blockWidth;
for (nuint i = 0; i < this.blockWidth; i++)
{
sum += Unsafe.Add(ref aboveRef, i);
}
for (nuint i = 0; i < this.blockHeight; i++)
{
sum += Unsafe.Add(ref leftRef, i);
}
byte expectedDc = (byte)((sum + (count >> 1)) / count);
for (nuint r = 0; r < this.blockHeight; r++)
{
Unsafe.InitBlock(ref destinationRef, expectedDc, width);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

49
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs

@ -0,0 +1,49 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1DcTopPredictor : IAv1Predictor
{
private readonly uint blockWidth;
private readonly uint blockHeight;
public Av1DcTopPredictor(Size blockSize)
{
this.blockWidth = (uint)blockSize.Width;
this.blockHeight = (uint)blockSize.Height;
}
public Av1DcTopPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (uint)transformSize.GetWidth();
this.blockHeight = (uint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1DcTopPredictor(transformSize).PredictScalar(destination, stride, above, left);
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
int sum = 0;
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
ref byte aboveRef = ref above[0];
ref byte destinationRef = ref destination[0];
for (uint i = 0; i < this.blockWidth; i++)
{
sum += Unsafe.Add(ref aboveRef, i);
}
byte expectedDc = (byte)((sum + (this.blockWidth >> 1)) / this.blockWidth);
for (uint r = 0; r < this.blockHeight; r++)
{
Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

79
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone1Predictor.cs

@ -0,0 +1,79 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1DirectionalZone1Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1DirectionalZone1Predictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1DirectionalZone1Predictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, bool upsampleAbove, int dx)
=> new Av1DirectionalZone1Predictor(transformSize).PredictScalar(destination, stride, above, upsampleAbove, dx);
/// <summary>
/// SVT: svt_av1_dr_prediction_z1_c
/// </summary>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, bool upsample, int dx)
{
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
int upsampleAbove = upsample ? 1 : 0;
ref byte aboveRef = ref above[0];
ref byte destinationRef = ref destination[0];
int maxBasisX = (((int)this.blockWidth + (int)this.blockHeight) - 1) << upsampleAbove;
int fractionBitCount = 6 - upsampleAbove;
int basisIncrement = 1 << upsampleAbove;
int x = dx;
for (nuint r = 0; r < this.blockHeight; ++r)
{
int basis = x >> fractionBitCount, shift = ((x << upsampleAbove) & 0x3F) >> 1;
if (basis >= maxBasisX)
{
for (nuint i = r; i < this.blockHeight; ++i)
{
Unsafe.InitBlock(ref destinationRef, Unsafe.Add(ref aboveRef, maxBasisX), (uint)this.blockWidth);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
return;
}
for (nuint c = 0; c < this.blockWidth; ++c)
{
if (basis < maxBasisX)
{
int val;
val = (Unsafe.Add(ref aboveRef, basis) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basis + 1) * shift);
val = Av1Math.RoundPowerOf2(val, 5);
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.Clamp(val, 0, 255);
}
else
{
Unsafe.Add(ref destinationRef, c) = Unsafe.Add(ref aboveRef, maxBasisX);
}
basis += basisIncrement;
}
x += dx;
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

79
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone2Predictor.cs

@ -0,0 +1,79 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1DirectionalZone2Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1DirectionalZone2Predictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1DirectionalZone2Predictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy)
=> new Av1DirectionalZone2Predictor(transformSize).PredictScalar(destination, stride, above, left, upsampleAbove, upsampleAbove, dx, dy);
/// <summary>
/// SVT: svt_av1_dr_prediction_z1_c
/// </summary>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left, bool doUpsampleAbove, bool doUpsampleLeft, int dx, int dy)
{
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
int upsampleAbove = doUpsampleAbove ? 1 : 0;
int upsampleLeft = doUpsampleLeft ? 1 : 0;
ref byte aboveRef = ref above[0];
ref byte leftRef = ref left[0];
ref byte destinationRef = ref destination[0];
int minBasisX = -(1 << upsampleAbove);
int fractionBitCountX = 6 - upsampleAbove;
int fractionBitCountY = 6 - upsampleLeft;
int basisIncrementX = 1 << upsampleAbove;
int x = -dx;
for (nuint r = 0; r < this.blockHeight; ++r)
{
int val;
int base1 = x >> fractionBitCountX;
int y = ((int)r << 6) - dy;
for (nuint c = 0; c < this.blockWidth; ++c, base1 += basisIncrementX, y -= dy)
{
if (base1 >= minBasisX)
{
int shift1 = ((x * (1 << upsampleAbove)) & 0x3F) >> 1;
val = (Unsafe.Add(ref aboveRef, base1) * (32 - shift1)) + (Unsafe.Add(ref aboveRef, base1 + 1) * shift1);
val = Av1Math.RoundPowerOf2(val, 5);
}
else
{
int base2 = y >> fractionBitCountY;
Guard.MustBeGreaterThanOrEqualTo(base2, -(1 << upsampleLeft), nameof(base2));
int shift2 = ((y * (1 << upsampleLeft)) & 0x3F) >> 1;
val = (Unsafe.Add(ref leftRef, base2) * (32 - shift2)) + (Unsafe.Add(ref leftRef, base2 + 1) * shift2);
val = Av1Math.RoundPowerOf2(val, 5);
}
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.Clamp(val, 0, 255);
}
x -= dx;
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

78
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone3Predictor.cs

@ -0,0 +1,78 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1DirectionalZone3Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1DirectionalZone3Predictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1DirectionalZone3Predictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> left, bool upsampleAbove, int dx, int dy)
=> new Av1DirectionalZone3Predictor(transformSize).PredictScalar(destination, stride, left, upsampleAbove, dx, dy);
/// <summary>
/// SVT: svt_av1_dr_prediction_z3_c
/// </summary>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> left, bool upsample, int dx, int dy)
{
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
int upsampleLeft = upsample ? 1 : 0;
ref byte leftRef = ref left[0];
ref byte destinationRef = ref destination[0];
Guard.IsTrue(dx == 1, nameof(dx), "Dx expected to be always equal to 1 for directional Zone 3 prediction.");
Guard.MustBeGreaterThan(dy, 0, nameof(dy));
int maxBasisY = ((int)this.blockWidth + (int)this.blockHeight - 1) << upsampleLeft;
int fractionBitCount = 6 - upsampleLeft;
int basisIncrement = 1 << upsampleLeft;
int y = dy;
for (nuint c = 0; c < this.blockWidth; ++c)
{
int basis = y >> fractionBitCount;
int shift = ((y << upsampleLeft) & 0x3F) >> 1;
for (nuint r = 0; r < this.blockHeight; ++r)
{
if (basis < maxBasisY)
{
int val;
val = (Unsafe.Add(ref leftRef, basis) * (32 - shift)) + (Unsafe.Add(ref leftRef, basis + 1) * shift);
val = Av1Math.RoundPowerOf2(val, 5);
Unsafe.Add(ref destinationRef, (r * stride) + c) = (byte)Av1Math.Clamp(val, 0, 255);
}
else
{
for (; r < this.blockHeight; ++r)
{
Unsafe.Add(ref destinationRef, (r * stride) + c) = left[maxBasisY];
}
break;
}
basis += basisIncrement;
}
y += dy;
}
}
}

47
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HorizontalPredictor.cs

@ -0,0 +1,47 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1HorizontalPredictor : IAv1Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1HorizontalPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1HorizontalPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1HorizontalPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <summary>
/// SVT: highbd_h_predictor
/// </summary>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
ref byte leftRef = ref left[0];
ref byte destinationRef = ref destination[0];
uint width = (uint)this.blockWidth;
for (nuint r = 0; r < this.blockHeight; ++r)
{
Unsafe.InitBlock(ref destinationRef, Unsafe.Add(ref leftRef, r), width);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

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,
}

59
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PaethPredictor.cs

@ -0,0 +1,59 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1PaethPredictor : IAv1Predictor
{
private readonly uint blockWidth;
private readonly uint blockHeight;
public Av1PaethPredictor(Size blockSize)
{
this.blockWidth = (uint)blockSize.Width;
this.blockHeight = (uint)blockSize.Height;
}
public Av1PaethPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (uint)transformSize.GetWidth();
this.blockHeight = (uint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1DcPredictor(transformSize).PredictScalar(destination, stride, above, left);
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
ref byte leftRef = ref left[0];
ref byte aboveRef = ref above[0];
int yTopLeft = above[-1];
ref byte destinationRef = ref destination[0];
for (nuint r = 0; r < this.blockHeight; r++)
{
for (nuint c = 0; c < this.blockWidth; c++)
{
destinationRef = PredictSingle(Unsafe.Add(ref leftRef, r), Unsafe.Add(ref aboveRef, c), yTopLeft);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}
private static byte PredictSingle(byte left, byte top, int topLeft)
{
int basis = top + left - topLeft;
int pLeft = Av1Math.AbsoluteDifference(basis, left);
int pTop = Av1Math.AbsoluteDifference(basis, top);
int pTopLeft = Av1Math.AbsoluteDifference(basis, topLeft);
// Return nearest to base of left, top and top_left.
return (byte)((pLeft <= pTop && pLeft <= pTopLeft) ? left : (pTop <= pTopLeft) ? top : topLeft);
}
}

1090
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs

File diff suppressed because it is too large

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

@ -0,0 +1,28 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
// Inter modes are not defined here, as they do not apply to pictures.
internal enum Av1PredictionMode
{
DC,
Vertical,
Horizontal,
Directional45Degrees,
Directional135Degrees,
Directional113Degrees,
Directional157Degrees,
Directional203Degrees,
Directional67Degrees,
Smooth,
SmoothVertical,
SmoothHorizontal,
Paeth,
UvChromaFromLuma,
IntraModeStart = DC,
IntraModeEnd = Paeth + 1,
IntraModes = Paeth,
UvIntraModes = UvChromaFromLuma + 1,
IntraInvalid = 25,
}

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

@ -0,0 +1,173 @@
// 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
{
private static readonly int[] DirectionalIntraDerivative = [
// More evenly spread out angles and limited to 10-bit
// Values that are 0 will never be used
// Approx angle
0, 0, 0, // 0
1023, 0, 0, // 3, ...
547, 0, 0, // 6, ...
372, 0, 0, 0, 0, // 9, ...
273, 0, 0, // 14, ...
215, 0, 0, // 17, ...
178, 0, 0, // 20, ...
151, 0, 0, // 23, ... (113 & 203 are base angles)
132, 0, 0, // 26, ...
116, 0, 0, // 29, ...
102, 0, 0, 0, // 32, ...
90, 0, 0, // 36, ...
80, 0, 0, // 39, ...
71, 0, 0, // 42, ...
64, 0, 0, // 45, ... (45 & 135 are base angles)
57, 0, 0, // 48, ...
51, 0, 0, // 51, ...
45, 0, 0, 0, // 54, ...
40, 0, 0, // 58, ...
35, 0, 0, // 61, ...
31, 0, 0, // 64, ...
27, 0, 0, // 67, ... (67 & 157 are base angles)
23, 0, 0, // 70, ...
19, 0, 0, // 73, ...
15, 0, 0, 0, 0, // 76, ...
11, 0, 0, // 81, ...
7, 0, 0, // 84, ...
3, 0, 0, // 87, ...
];
internal static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, Span<byte> destination, nuint destinationStride, Span<byte> aboveRow, Span<byte> leftColumn)
{
if (hasLeft)
{
if (hasAbove)
{
Av1DcPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
}
else
{
Av1DcLeftPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
}
}
else
{
if (hasAbove)
{
Av1DcTopPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
}
else
{
Av1DcFillPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
}
}
}
/// <summary>
/// SVT: svt_aom_highbd_dr_predictor
/// </summary>
internal static void DirectionalPredictor(Span<byte> destination, nuint stride, Av1TransformSize transformSize, Span<byte> aboveRow, Span<byte> leftColumn, bool upsampleAbove, bool upsampleLeft, int angle)
{
int dx = GetDeltaX(angle);
int dy = GetDeltaY(angle);
int bw = transformSize.GetWidth();
int bh = transformSize.GetHeight();
Guard.MustBeBetweenOrEqualTo(angle, 1, 269, nameof(angle));
if (angle is > 0 and < 90)
{
Av1DirectionalZone1Predictor.PredictScalar(transformSize, destination, stride, aboveRow, upsampleAbove, dx);
}
else if (angle is > 90 and < 180)
{
Av1DirectionalZone2Predictor.PredictScalar(transformSize, destination, stride, aboveRow, leftColumn, upsampleAbove, upsampleLeft, dx, dy);
}
else if (angle is > 180 and < 270)
{
Av1DirectionalZone3Predictor.PredictScalar(transformSize, destination, stride, leftColumn, upsampleLeft, dx, dy);
}
else if (angle == 90)
{
Av1VerticalPredictor.PredictScalar(transformSize, destination, stride, aboveRow, leftColumn);
}
else if (angle == 180)
{
Av1HorizontalPredictor.PredictScalar(transformSize, destination, stride, aboveRow, leftColumn);
}
}
internal static void FilterIntraPredictor(Span<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, Span<byte> destination, nuint destinationStride, Span<byte> aboveRow, Span<byte> leftColumn)
{
switch (mode)
{
case Av1PredictionMode.Horizontal:
Av1HorizontalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
break;
case Av1PredictionMode.Vertical:
Av1VerticalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
break;
case Av1PredictionMode.Paeth:
Av1PaethPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
break;
case Av1PredictionMode.Smooth:
Av1SmoothPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
break;
case Av1PredictionMode.SmoothHorizontal:
Av1SmoothHorizontalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
break;
case Av1PredictionMode.SmoothVertical:
Av1SmoothVerticalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
break;
}
}
// Get the shift (up-scaled by 256) in Y w.r.t a unit change in X.
// If angle > 0 && angle < 90, dy = 1;
// If angle > 90 && angle < 180, dy = (int32_t)(256 * t);
// If angle > 180 && angle < 270, dy = -((int32_t)(256 * t));
private static int GetDeltaY(int angle)
{
if (angle is > 90 and < 180)
{
return DirectionalIntraDerivative[angle - 90];
}
else if (angle is > 180 and < 270)
{
return DirectionalIntraDerivative[270 - angle];
}
else
{
// In this case, we are not really going to use dy. We may return any value.
return 1;
}
}
// Get the shift (up-scaled by 256) in X w.r.t a unit change in Y.
// If angle > 0 && angle < 90, dx = -((int32_t)(256 / t));
// If angle > 90 && angle < 180, dx = (int32_t)(256 / t);
// If angle > 180 && angle < 270, dx = 1;
private static int GetDeltaX(int angle)
{
if (angle is > 0 and < 90)
{
return DirectionalIntraDerivative[angle];
}
else if (angle is > 90 and < 180)
{
return DirectionalIntraDerivative[180 - angle];
}
else
{
// In this case, we are not really going to use dx. We may return any value.
return 1;
}
}
}

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

@ -0,0 +1,67 @@
// 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;
internal static class Av1PreditionModeExtensions
{
private static readonly Av1TransformType[] IntraPreditionMode2TransformType = [
Av1TransformType.DctDct, // DC
Av1TransformType.AdstDct, // V
Av1TransformType.DctAdst, // H
Av1TransformType.DctDct, // D45
Av1TransformType.AdstAdst, // D135
Av1TransformType.AdstDct, // D117
Av1TransformType.DctAdst, // D153
Av1TransformType.DctAdst, // D207
Av1TransformType.AdstDct, // D63
Av1TransformType.AdstAdst, // SMOOTH
Av1TransformType.AdstDct, // SMOOTH_V
Av1TransformType.DctAdst, // SMOOTH_H
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];
}

63
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothHorizontalPredictor.cs

@ -0,0 +1,63 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1SmoothHorizontalPredictor : IAv1Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1SmoothHorizontalPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1SmoothHorizontalPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1SmoothHorizontalPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <summary>
/// SVT: highbd_smooth_h_predictor
/// </summary>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
ref byte leftRef = ref left[0];
ref byte aboveRef = ref above[0];
ref byte destinationRef = ref destination[0];
int rightPrediction = Unsafe.Add(ref aboveRef, this.blockWidth - 1); // estimated by top-right pixel
ref int weights = ref Av1SmoothPredictor.Weights[(int)this.blockWidth];
// scale = 2 * 2^sm_weight_log2_scale
int log2Scale = 1 + Av1SmoothPredictor.WeightLog2Scale;
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale;
// sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
for (nuint r = 0; r < this.blockHeight; ++r)
{
for (nuint c = 0; c < this.blockWidth; ++c)
{
int columnWeight = Unsafe.Add(ref weights, c);
Guard.MustBeGreaterThanOrEqualTo(scale, columnWeight, nameof(scale));
int thisPredition = Unsafe.Add(ref leftRef, r) * columnWeight;
thisPredition += rightPrediction * (scale - columnWeight);
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale);
}
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

101
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothPredictor.cs

@ -0,0 +1,101 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1SmoothPredictor : IAv1Predictor
{
// Weights are quadratic from '1' to '1 / BlockSize', scaled by
// 2^sm_weight_log2_scale.
internal static readonly int WeightLog2Scale = 8;
internal static readonly int[] Weights = [
// Unused, because we always offset by bs, which is at least 2.
0, 0,
// bs = 2
255, 128,
// bs = 4
255, 149, 85, 64,
// bs = 8
255, 197, 146, 105, 73, 50, 37, 32,
// bs = 16
255, 225, 196, 170, 145, 123, 102, 84, 68, 54, 43, 33, 26, 20, 17, 16,
// bs = 32
255, 240, 225, 210, 196, 182, 169, 157, 145, 133, 122, 111, 101, 92, 83, 74,
66, 59, 52, 45, 39, 34, 29, 25, 21, 17, 14, 12, 10, 9, 8, 8,
// bs = 64
255, 248, 240, 233, 225, 218, 210, 203, 196, 189, 182, 176, 169, 163, 156,
150, 144, 138, 133, 127, 121, 116, 111, 106, 101, 96, 91, 86, 82, 77, 73, 69,
65, 61, 57, 54, 50, 47, 44, 41, 38, 35, 32, 29, 27, 25, 22, 20, 18, 16, 15,
13, 12, 10, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4,
];
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1SmoothPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1SmoothPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1SmoothPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <summary>
/// SVT: highbd_smooth_predictor
/// </summary>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
ref byte leftRef = ref left[0];
ref byte aboveRef = ref above[0];
ref byte destinationRef = ref destination[0];
int belowPrediction = Unsafe.Add(ref leftRef, this.blockHeight - 1); // estimated by bottom-left pixel
int rightPrediction = Unsafe.Add(ref aboveRef, this.blockWidth - 1); // estimated by top-right pixel
ref int heightWeights = ref Weights[(int)this.blockWidth];
ref int widthWeights = ref Weights[(int)this.blockHeight];
// scale = 2 * 2^sm_weight_log2_scale
int log2Scale = 1 + WeightLog2Scale;
int scale = 1 << WeightLog2Scale;
// sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
for (nuint r = 0; r < this.blockHeight; ++r)
{
int rowWeight = Unsafe.Add(ref heightWeights, r);
Guard.MustBeGreaterThanOrEqualTo(scale, rowWeight, nameof(scale));
for (nuint c = 0; c < this.blockWidth; ++c)
{
int columnWeight = Unsafe.Add(ref widthWeights, c);
Guard.MustBeGreaterThanOrEqualTo(scale, columnWeight, nameof(scale));
int thisPredition = Unsafe.Add(ref aboveRef, c) * rowWeight;
thisPredition += belowPrediction * (scale - rowWeight);
thisPredition += Unsafe.Add(ref leftRef, r) * columnWeight;
thisPredition += rightPrediction * (scale - columnWeight);
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale);
}
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

62
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothVerticalPredictor.cs

@ -0,0 +1,62 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1SmoothVerticalPredictor : IAv1Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1SmoothVerticalPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1SmoothVerticalPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1SmoothVerticalPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <summary>
/// SVT: highbd_smooth_v_predictor
/// </summary>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
ref byte leftRef = ref left[0];
ref byte aboveRef = ref above[0];
ref byte destinationRef = ref destination[0];
int belowPrediction = Unsafe.Add(ref leftRef, this.blockHeight - 1); // estimated by bottom-left pixel
ref int weights = ref Av1SmoothPredictor.Weights[(int)this.blockHeight];
// scale = 2 * 2^sm_weight_log2_scale
int log2Scale = 1 + Av1SmoothPredictor.WeightLog2Scale;
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale;
// sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
for (nuint r = 0; r < this.blockHeight; ++r)
{
int rowWeight = Unsafe.Add(ref weights, r);
for (nuint c = 0; c < this.blockWidth; ++c)
{
int thisPredition = Unsafe.Add(ref aboveRef, c) * rowWeight;
thisPredition += belowPrediction * (scale - rowWeight);
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale);
}
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

47
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1VerticalPredictor.cs

@ -0,0 +1,47 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1VerticalPredictor : IAv1Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1VerticalPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1VerticalPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1VerticalPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <summary>
/// SVT: highbd_v_predictor
/// </summary>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
ref byte aboveRef = ref above[0];
ref byte destinationRef = ref destination[0];
uint width = (uint)this.blockWidth;
for (nuint r = 0; r < this.blockHeight; ++r)
{
Unsafe.CopyBlock(ref destinationRef, ref aboveRef, width);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

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;
}

19
src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Interface for predictor implementations.
/// </summary>
internal interface IAv1Predictor
{
/// <summary>
/// Predict using scalar logic within the 8-bit pipeline.
/// </summary>
/// <param name="destination">The destination to write to.</param>
/// <param name="stride">The stride of the destination buffer.</param>
/// <param name="above">Pointer to the first element of the block above.</param>
/// <param name="left">Pointer to the first element of the block to the left.</param>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left);
}

73
src/ImageSharp/Formats/Heif/Av1/Readme.md

@ -0,0 +1,73 @@
# Open Bitstream Unit
An OBU unit is a unit of parameters encoded in a bitstream format. In AVIF, it contains a single frame.
This frame is coded using no other frame as reference, it is a so called INTRA frame. AV1 movie encoding also defines INTER frames,
which are predictions of one or more other frames. INTER frames are not used in AVIF and therefore this coded ignores INTER frames.
An OBU section for AVIF consists of the following headers:
## Temporal delimiter
In AV1 movies this is a time point. Although irrelevant for AVIF, most implementtions write one such delimiter at the start of the section.
## Sequence header
Common herader for a list (or sequence) of frames. For AVIF, this is exaclty 1 frame. For AVIF, this header can be reduced in size when its `ReducedStillPictureHerader` parameter is true.
This setting is recommended, as all the extra parameters are not applicable for AVIF.
## Frame header
Can be 3 different OBU types, which define a single INTRA frame in AVIF files.
## Tile group
Defines the tiling parameters and contains the parameters its tile using a different coding.
# Tiling
In AV1 a frame is made up of 1 or more tiles. The parameters for each tile are entropy encoded using the context aware symbol coding.
These parameters are contained in an OBU tile group header.
## Superblock
A tile consists of one or more superblocks. Superblocks can be either 64x64 or 128x128 pixels in size.
This choice is made per frame, and is specified in the `ObuFrameHeader`.
A superblock contains one or more partitions, to further devide the area.
## Partition
A superblock contains one or more Partitions. The partition Type determines the number of partitions it is further split in.
Paritions can contain other partitions and blocks.
## Block
## Transform Block
A Transform Block is the smallest area of the image, which has the same transformation parameters. A block contains ore or more ModeInfos.
## ModeInfo
The smallest unit in the frame. It determines the parameters for an area of 4 by 4 pixels.
# References
[AV1 embedded in HEIF](https://aomediacodec.github.io/av1-isobmff)
[AV1 specification](https://aomediacodec.github.io/av1-spec/av1-spec.pdf)
[AVIF specification](https://aomediacodec.github.io/av1-avif)
[AV1/AVIF reference implementation](http://gitlab.com/AOMediaCodec/SVT-AV1)
[AOM's original development implementation](https://github.com/AOMediaCodec/libavif)
[Paper describing the techniques used in AV1](https://arxiv.org/pdf/2008.06091)
# Test images
[Netflix image repository](http://download.opencontent.netflix.com/?prefix=AV1/)
[AVIF sample images](https://github.com/link-u/avif-sample-images)

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

@ -0,0 +1,70 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
internal class Av1BlockModeInfo
{
private int[] paletteSize;
public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point positionInSuperblock)
{
this.BlockSize = blockSize;
this.PositionInSuperblock = positionInSuperblock;
this.AngleDelta = new int[numPlanes - 1];
this.paletteSize = new int[numPlanes - 1];
this.FilterIntraModeInfo = new();
this.FirstTransformLocation = new int[numPlanes - 1];
this.TransformUnitsCount = new int[numPlanes - 1];
}
public Av1BlockSize BlockSize { get; }
/// <summary>
/// Gets or sets the <see cref="Av1PredictionMode"/> for the luminance channel.
/// </summary>
public Av1PredictionMode YMode { get; set; }
public bool Skip { get; set; }
public Av1PartitionType PartitionType { get; set; }
public bool SkipMode { get; set; }
public int SegmentId { get; set; }
/// <summary>
/// Gets or sets the <see cref="Av1PredictionMode"/> for the chroma channels.
/// </summary>
public Av1PredictionMode UvMode { get; set; }
public bool UseUltraBlockCopy { get; set; }
public int ChromaFromLumaAlphaIndex { get; set; }
public int ChromaFromLumaAlphaSign { get; set; }
public int[] AngleDelta { get; set; }
/// <summary>
/// Gets the position relative to the Superblock, counted in mode info (4x4 pixels).
/// </summary>
public Point PositionInSuperblock { get; }
public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; }
/// <summary>
/// Gets the index of the first <see cref="Av1TransformInfo"/> of this Mode Info in the <see cref="Av1FrameInfo"/>.
/// </summary>
public int[] FirstTransformLocation { get; }
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];
}

14
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs

@ -0,0 +1,14 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
internal enum Av1ComponentType
{
Luminance = 0, // luma
Chroma = 1, // chroma (Cb+Cr)
ChromaCb = 2, // chroma Cb
ChromaCr = 3, // chroma Cr
All = 4, // Y+Cb+Cr
None = 15
}

31
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockModeInfo.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
internal class Av1EncoderBlockModeInfo
{
public Av1BlockSize BlockSize { get; }
public Av1PredictionMode PredictionMode { get; }
public Av1PartitionType PartitionType { get; }
public Av1PredictionMode UvPredictionMode { get; }
public bool Skip { get; } = true;
public bool SkipMode { get; } = true;
public bool UseIntraBlockCopy { get; } = true;
public int SegmentId { get; }
public int TransformDepth { get; internal set; }
public Av1PredictionMode Mode { get; internal set; }
public Av1PredictionMode UvMode { get; internal set; }
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save