mirror of https://github.com/SixLabors/ImageSharp
8 changed files with 526 additions and 537 deletions
@ -0,0 +1,382 @@ |
|||
// 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 static class Av1SymbolContextHelper |
|||
{ |
|||
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 int[] TransformCountInSet = [1, 2, 5, 7, 12, 16]; |
|||
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-
|
|||
]; |
|||
|
|||
// Maps tx set types to the indices. INTRA values only
|
|||
private static readonly int[] ExtendedTransformSetToIndex = [0, -1, 2, 1, -1, -1]; |
|||
|
|||
internal static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra) |
|||
{ |
|||
int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint]; |
|||
if (endOfBlock > 2) |
|||
{ |
|||
endOfBlock += endOfBlockExtra; |
|||
} |
|||
|
|||
return endOfBlock; |
|||
} |
|||
|
|||
internal static int GetBaseRangeContextEndOfBlock(int index, int blockWidthLog2, Av1TransformClass transformClass) |
|||
{ |
|||
int row = index >> blockWidthLog2; |
|||
int col = index - (row << blockWidthLog2); |
|||
if (index == 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
if ((transformClass == Av1TransformClass.Class2D && row < 2 && col < 2) || |
|||
(transformClass == Av1TransformClass.ClassHorizontal && col == 0) || |
|||
(transformClass == Av1TransformClass.ClassVertical && row == 0)) |
|||
{ |
|||
return 7; |
|||
} |
|||
|
|||
return 14; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// SVT: get_lower_levels_ctx_eob
|
|||
/// </summary>
|
|||
internal static int GetLowerLevelContextEndOfBlock(int blockWidthLog2, int height, int scanIndex) |
|||
{ |
|||
if (scanIndex == 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
if (scanIndex <= height << blockWidthLog2 >> 3) |
|||
{ |
|||
return 1; |
|||
} |
|||
|
|||
if (scanIndex <= height << blockWidthLog2 >> 2) |
|||
{ |
|||
return 2; |
|||
} |
|||
|
|||
return 3; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// SVT: get_br_ctx_2d
|
|||
/// </summary>
|
|||
internal static int GetBaseRangeContext2d(Av1LevelBuffer levels, int index, int blockWidthLog2) |
|||
{ |
|||
DebugGuard.MustBeGreaterThan(index, 0, nameof(index)); |
|||
int y = index >> blockWidthLog2; |
|||
int x = index - (y << blockWidthLog2); |
|||
int stride = (1 << blockWidthLog2) + Av1Constants.TransformPadHorizontal; |
|||
int pos = (y * stride) + x; |
|||
Span<byte> row0 = levels.GetRow(y); |
|||
Span<byte> row1 = levels.GetRow(y + 1); |
|||
int mag = |
|||
Math.Min((int)row0[1], Av1Constants.MaxBaseRange) + |
|||
Math.Min((int)row1[0], Av1Constants.MaxBaseRange) + |
|||
Math.Min((int)row1[1], Av1Constants.MaxBaseRange); |
|||
mag = Math.Min((mag + 1) >> 1, 6); |
|||
if ((y | x) < 2) |
|||
{ |
|||
return mag + 7; |
|||
} |
|||
|
|||
return mag + 14; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// SVT: get_lower_levels_ctx_2d
|
|||
/// </summary>
|
|||
internal static int GetLowerLevelsContext2d(Av1LevelBuffer levelBuffer, int index, int blockWidthLog2, Av1TransformSize transformSize) |
|||
{ |
|||
DebugGuard.MustBeGreaterThan(index, 0, nameof(index)); |
|||
int y = index >> blockWidthLog2; |
|||
int x = index - (y << blockWidthLog2); |
|||
int mag; |
|||
Span<byte> row0 = levelBuffer.GetRow(y); |
|||
Span<byte> row1 = levelBuffer.GetRow(y + 1); |
|||
Span<byte> row2 = levelBuffer.GetRow(y + 2); |
|||
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, index); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// SVT: get_br_ctx
|
|||
/// </summary>
|
|||
internal static int GetBaseRangeContext(Av1LevelBuffer levels, int index, int blockWidthLog2, Av1TransformClass transformClass) |
|||
{ |
|||
int y = index >> blockWidthLog2; |
|||
int x = index - (y << blockWidthLog2); |
|||
Span<byte> row0 = levels.GetRow(y); |
|||
Span<byte> row1 = levels.GetRow(y + 1); |
|||
int mag = row0[x + 1]; |
|||
mag += row1[x]; |
|||
switch (transformClass) |
|||
{ |
|||
case Av1TransformClass.Class2D: |
|||
mag += row1[x + 1]; |
|||
mag = Math.Min((mag + 1) >> 1, 6); |
|||
if (index == 0) |
|||
{ |
|||
return mag; |
|||
} |
|||
|
|||
if (y < 2 && x < 2) |
|||
{ |
|||
return mag + 7; |
|||
} |
|||
|
|||
break; |
|||
case Av1TransformClass.ClassHorizontal: |
|||
mag += row0[2]; |
|||
mag = Math.Min((mag + 1) >> 1, 6); |
|||
if (index == 0) |
|||
{ |
|||
return mag; |
|||
} |
|||
|
|||
if (x == 0) |
|||
{ |
|||
return mag + 7; |
|||
} |
|||
|
|||
break; |
|||
case Av1TransformClass.ClassVertical: |
|||
mag += levels.GetRow(y + 2)[0]; |
|||
mag = Math.Min((mag + 1) >> 1, 6); |
|||
if (index == 0) |
|||
{ |
|||
return mag; |
|||
} |
|||
|
|||
if (y == 0) |
|||
{ |
|||
return mag + 7; |
|||
} |
|||
|
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
|
|||
return mag + 14; |
|||
} |
|||
|
|||
internal static int GetLowerLevelsContext(Av1LevelBuffer levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) |
|||
{ |
|||
int stats = Av1NzMap.GetNzMagnitude(levels, pos >> bwl, transformClass); |
|||
return Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); |
|||
} |
|||
|
|||
internal static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet) |
|||
{ |
|||
Av1TransformSize squareUpSize = transformSize.GetSquareUpSize(); |
|||
|
|||
if (squareUpSize >= Av1TransformSize.Size32x32) |
|||
{ |
|||
return Av1TransformSetType.DctOnly; |
|||
} |
|||
|
|||
if (useReducedSet) |
|||
{ |
|||
return Av1TransformSetType.Dtt4Identity; |
|||
} |
|||
|
|||
Av1TransformSize squareSize = transformSize.GetSquareSize(); |
|||
return squareSize == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; |
|||
} |
|||
|
|||
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, |
|||
int index, |
|||
int blockWidthLog2, |
|||
int height, |
|||
int scan_idx, |
|||
bool is_eob, |
|||
Av1TransformSize transformSize, |
|||
Av1TransformClass transformClass) |
|||
{ |
|||
if (is_eob) |
|||
{ |
|||
if (scan_idx == 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
if (scan_idx <= (height << blockWidthLog2) / 8) |
|||
{ |
|||
return 1; |
|||
} |
|||
|
|||
if (scan_idx <= (height << blockWidthLog2) / 4) |
|||
{ |
|||
return 2; |
|||
} |
|||
|
|||
return 3; |
|||
} |
|||
|
|||
int stats = Av1NzMap.GetNzMagnitude(levels, index, blockWidthLog2, transformClass); |
|||
return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, index, blockWidthLog2, 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) |
|||
{ |
|||
int blockWidthLog2 = transformSize.GetBlockWidthLog2(); |
|||
int height = transformSize.GetHeight(); |
|||
for (int i = 0; i < eob; ++i) |
|||
{ |
|||
int pos = scan[i]; |
|||
coefficientContexts[pos] = GetNzMapContext(levels, pos, blockWidthLog2, height, i, i == eob - 1, transformSize, transformClass); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// SVT: get_ext_tx_set_type
|
|||
/// </summary>
|
|||
internal static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet) |
|||
{ |
|||
Av1TransformSize transformSizeSquareUp = transformSize.GetSquareUpSize(); |
|||
|
|||
if (transformSizeSquareUp > Av1TransformSize.Size32x32) |
|||
{ |
|||
return Av1TransformSetType.DctOnly; |
|||
} |
|||
|
|||
if (transformSizeSquareUp == Av1TransformSize.Size32x32) |
|||
{ |
|||
return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.DctOnly; |
|||
} |
|||
|
|||
if (useReducedTransformSet) |
|||
{ |
|||
return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.Dtt4Identity; |
|||
} |
|||
|
|||
Av1TransformSize transformSizeSquare = transformSize.GetSquareSize(); |
|||
if (isInter) |
|||
{ |
|||
return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt9Identity1dDct : Av1TransformSetType.All16; |
|||
} |
|||
else |
|||
{ |
|||
return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; |
|||
} |
|||
} |
|||
|
|||
internal static int GetExtendedTransformTypeCount(Av1TransformSize transformSize, bool useReducedTransformSet) |
|||
{ |
|||
int setType = (int)GetExtendedTransformSetType(transformSize, useReducedTransformSet); |
|||
return TransformCountInSet[setType]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// SVT: get_ext_tx_set
|
|||
/// </summary>
|
|||
internal static int GetExtendedTransformSet(Av1TransformSize transformSize, bool useReducedTransformSet) |
|||
{ |
|||
int setType = (int)GetExtendedTransformSetType(transformSize, useReducedTransformSet); |
|||
return ExtendedTransformSetToIndex[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; |
|||
} |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
internal sealed class Av1LevelBuffer : IDisposable |
|||
{ |
|||
private IMemoryOwner<byte>? memory; |
|||
|
|||
public Av1LevelBuffer(Configuration configuration) |
|||
: this(configuration, new Size(Av1Constants.MaxTransformSize, Av1Constants.MaxTransformSize)) |
|||
{ |
|||
} |
|||
|
|||
public Av1LevelBuffer(Configuration configuration, Size size) |
|||
{ |
|||
this.Size = size; |
|||
int totalHeight = Av1Constants.TransformPadTop + size.Height + Av1Constants.TransformPadBottom; |
|||
this.Stride = Av1Constants.TransformPadHorizontal + size.Width; |
|||
this.memory = configuration.MemoryAllocator.Allocate<byte>(this.Stride * totalHeight, AllocationOptions.Clean); |
|||
} |
|||
|
|||
public Size Size { get; } |
|||
|
|||
public int Stride { get; } |
|||
|
|||
public void Initialize(Span<int> coefficientBuffer) |
|||
{ |
|||
ObjectDisposedException.ThrowIf(this.memory == null, this); |
|||
ArgumentOutOfRangeException.ThrowIfLessThan(coefficientBuffer.Length, this.Size.Width * this.Size.Height, nameof(coefficientBuffer)); |
|||
for (int y = 0; y < this.Size.Height; y++) |
|||
{ |
|||
ref byte destRef = ref this.GetRow(y)[0]; |
|||
ref int sourceRef = ref coefficientBuffer[y * this.Size.Width]; |
|||
for (int x = 0; x < this.Size.Width; x++) |
|||
{ |
|||
destRef = (byte)Av1Math.Clamp(sourceRef, 0, byte.MaxValue); |
|||
destRef = ref Unsafe.Add(ref destRef, 1); |
|||
sourceRef = ref Unsafe.Add(ref sourceRef, 1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public Span<byte> GetRow(int y) |
|||
{ |
|||
ObjectDisposedException.ThrowIf(this.memory == null, this); |
|||
ArgumentOutOfRangeException.ThrowIfLessThan(y, -Av1Constants.TransformPadTop); |
|||
int row = y + Av1Constants.TransformPadTop; |
|||
return this.memory.Memory.Span.Slice(row * this.Stride, this.Size.Width); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
this.memory?.Dispose(); |
|||
this.memory = null; |
|||
} |
|||
|
|||
internal void Clear() |
|||
{ |
|||
ObjectDisposedException.ThrowIf(this.memory == null, this); |
|||
this.memory.Memory.Span.Clear(); |
|||
} |
|||
|
|||
internal Span<byte> GetPaddedRow(int index, int blockWidthLog2) |
|||
{ |
|||
int y = index >> blockWidthLog2; |
|||
return this.GetRow(y); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue