Browse Source

Refactor position inside transform block

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
41a11fd339
  1. 32
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs
  2. 131
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs
  3. 57
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs
  4. 7
      src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs
  5. 17
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs
  6. 2
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
  7. 40
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs
  8. 6
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs
  9. 91
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1LevelBufferTests.cs

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

@ -286,18 +286,12 @@ internal static class Av1NzMap
/// <summary> /// <summary>
/// SVT: get_nz_mag /// SVT: get_nz_mag
/// </summary> /// </summary>
public static int GetNzMagnitude(Av1LevelBuffer levels, int index, int blockWidthLog2, Av1TransformClass transformClass) public static int GetNzMagnitude(Av1LevelBuffer levels, Point position, Av1TransformClass transformClass)
=> GetNzMagnitude(levels, index >> blockWidthLog2, transformClass);
/// <summary>
/// SVT: get_nz_mag
/// </summary>
public static int GetNzMagnitude(Av1LevelBuffer levels, int y, Av1TransformClass transformClass)
{ {
int mag; int mag;
Span<byte> row0 = levels.GetRow(y); Span<byte> row0 = levels.GetRow(position.Y);
Span<byte> row1 = levels.GetRow(y + 1); Span<byte> row1 = levels.GetRow(position.Y + 1);
Span<byte> row2 = levels.GetRow(y + 2); Span<byte> row2 = levels.GetRow(position.Y + 2);
// Note: AOMMIN(level, 3) is useless for decoder since level < 3. // Note: AOMMIN(level, 3) is useless for decoder since level < 3.
mag = ClipMax3[row0[1]]; // { 0, 1 } mag = ClipMax3[row0[1]]; // { 0, 1 }
@ -312,8 +306,8 @@ internal static class Av1NzMap
break; break;
case Av1TransformClass.ClassVertical: case Av1TransformClass.ClassVertical:
Span<byte> row3 = levels.GetRow(y + 3); Span<byte> row3 = levels.GetRow(position.Y + 3);
Span<byte> row4 = levels.GetRow(y + 4); Span<byte> row4 = levels.GetRow(position.Y + 4);
mag += ClipMax3[row2[0]]; // { 2, 0 } mag += ClipMax3[row2[0]]; // { 2, 0 }
mag += ClipMax3[row3[0]]; // { 3, 0 } mag += ClipMax3[row3[0]]; // { 3, 0 }
mag += ClipMax3[row4[0]]; // { 4, 0 } mag += ClipMax3[row4[0]]; // { 4, 0 }
@ -328,10 +322,10 @@ internal static class Av1NzMap
return mag; return mag;
} }
public static int GetNzMapContextFromStats(int stats, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) public static int GetNzMapContextFromStats(int stats, Av1LevelBuffer levels, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass)
{ {
// tx_class == 0(TX_CLASS_2D) // tx_class == 0(TX_CLASS_2D)
if (((int)transformClass | pos) == 0) if (position.Y == 0 && ((int)transformClass | position.X) == 0)
{ {
return 0; return 0;
} }
@ -352,14 +346,12 @@ internal static class Av1NzMap
// if (row + col < 2) return ctx + 1; // if (row + col < 2) return ctx + 1;
// if (row + col < 4) return 5 + ctx + 1; // if (row + col < 4) return 5 + ctx + 1;
// return 21 + ctx; // return 21 + ctx;
return ctx + NzMapContextOffset[(int)transformSize][pos]; int index = position.X + (levels.Size.Width * position.Y);
return ctx + NzMapContextOffset[(int)transformSize][index];
case Av1TransformClass.ClassHorizontal: case Av1TransformClass.ClassHorizontal:
int row = pos >> bwl; return ctx + NzMapContextOffset1d[position.X];
int col = pos - (row << bwl);
return ctx + NzMapContextOffset1d[col];
case Av1TransformClass.ClassVertical: case Av1TransformClass.ClassVertical:
int row2 = pos >> bwl; return ctx + NzMapContextOffset1d[position.Y];
return ctx + NzMapContextOffset1d[row2];
default: default:
break; break;
} }

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

@ -52,6 +52,9 @@ internal static class Av1SymbolContextHelper
// Maps tx set types to the indices. INTRA values only // Maps tx set types to the indices. INTRA values only
private static readonly int[] ExtendedTransformSetToIndex = [0, -1, 2, 1, -1, -1]; private static readonly int[] ExtendedTransformSetToIndex = [0, -1, 2, 1, -1, -1];
internal static Av1TransformSize GetTransformSizeContext(Av1TransformSize originalSize)
=> (Av1TransformSize)(((int)originalSize.GetSquareSize() + (int)originalSize.GetSquareUpSize() + 1) >> 1);
internal static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra) internal static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra)
{ {
int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint]; int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint];
@ -63,18 +66,16 @@ internal static class Av1SymbolContextHelper
return endOfBlock; return endOfBlock;
} }
internal static int GetBaseRangeContextEndOfBlock(int index, int blockWidthLog2, Av1TransformClass transformClass) internal static int GetBaseRangeContextEndOfBlock(Point pos, Av1TransformClass transformClass)
{ {
int row = index >> blockWidthLog2; if (pos.X == 0 && pos.Y == 0)
int col = index - (row << blockWidthLog2);
if (index == 0)
{ {
return 0; return 0;
} }
if ((transformClass == Av1TransformClass.Class2D && row < 2 && col < 2) || if ((transformClass == Av1TransformClass.Class2D && pos.Y < 2 && pos.X < 2) ||
(transformClass == Av1TransformClass.ClassHorizontal && col == 0) || (transformClass == Av1TransformClass.ClassHorizontal && pos.X == 0) ||
(transformClass == Av1TransformClass.ClassVertical && row == 0)) (transformClass == Av1TransformClass.ClassVertical && pos.Y == 0))
{ {
return 7; return 7;
} }
@ -82,6 +83,31 @@ internal static class Av1SymbolContextHelper
return 14; return 14;
} }
/// <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> /// <summary>
/// SVT: get_lower_levels_ctx_eob /// SVT: get_lower_levels_ctx_eob
/// </summary> /// </summary>
@ -108,21 +134,17 @@ internal static class Av1SymbolContextHelper
/// <summary> /// <summary>
/// SVT: get_br_ctx_2d /// SVT: get_br_ctx_2d
/// </summary> /// </summary>
internal static int GetBaseRangeContext2d(Av1LevelBuffer levels, int index, int blockWidthLog2) internal static int GetBaseRangeContext2d(Av1LevelBuffer levels, Point position)
{ {
DebugGuard.MustBeGreaterThan(index, 0, nameof(index)); DebugGuard.MustBeGreaterThan(position.X + position.Y, 0, nameof(position));
int y = index >> blockWidthLog2; Span<byte> row0 = levels.GetRow(position.Y);
int x = index - (y << blockWidthLog2); Span<byte> row1 = levels.GetRow(position.Y + 1);
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 = int mag =
Math.Min((int)row0[1], Av1Constants.MaxBaseRange) + Math.Min((int)row0[1], Av1Constants.MaxBaseRange) +
Math.Min((int)row1[0], Av1Constants.MaxBaseRange) + Math.Min((int)row1[0], Av1Constants.MaxBaseRange) +
Math.Min((int)row1[1], Av1Constants.MaxBaseRange); Math.Min((int)row1[1], Av1Constants.MaxBaseRange);
mag = Math.Min((mag + 1) >> 1, 6); mag = Math.Min((mag + 1) >> 1, 6);
if ((y | x) < 2) if ((position.Y | position.X) < 2)
{ {
return mag + 7; return mag + 7;
} }
@ -133,15 +155,13 @@ internal static class Av1SymbolContextHelper
/// <summary> /// <summary>
/// SVT: get_lower_levels_ctx_2d /// SVT: get_lower_levels_ctx_2d
/// </summary> /// </summary>
internal static int GetLowerLevelsContext2d(Av1LevelBuffer levelBuffer, int index, int blockWidthLog2, Av1TransformSize transformSize) internal static int GetLowerLevelsContext2d(Av1LevelBuffer levelBuffer, Point pos, Av1TransformSize transformSize)
{ {
DebugGuard.MustBeGreaterThan(index, 0, nameof(index)); DebugGuard.MustBeGreaterThan(pos.X + pos.Y, 0, nameof(pos));
int y = index >> blockWidthLog2;
int x = index - (y << blockWidthLog2);
int mag; int mag;
Span<byte> row0 = levelBuffer.GetRow(y); Span<byte> row0 = levelBuffer.GetRow(pos.Y);
Span<byte> row1 = levelBuffer.GetRow(y + 1); Span<byte> row1 = levelBuffer.GetRow(pos.Y + 1);
Span<byte> row2 = levelBuffer.GetRow(y + 2); Span<byte> row2 = levelBuffer.GetRow(pos.Y + 2);
mag = Math.Min((int)row0[1], 3); // { 0, 1 } mag = Math.Min((int)row0[1], 3); // { 0, 1 }
mag += Math.Min((int)row1[0], 3); // { 1, 0 } mag += Math.Min((int)row1[0], 3); // { 1, 0 }
mag += Math.Min((int)row1[1], 3); // { 1, 1 } mag += Math.Min((int)row1[1], 3); // { 1, 1 }
@ -149,31 +169,30 @@ internal static class Av1SymbolContextHelper
mag += Math.Min((int)row2[0], 3); // { 2, 0 } mag += Math.Min((int)row2[0], 3); // { 2, 0 }
int ctx = Math.Min((mag + 1) >> 1, 4); int ctx = Math.Min((mag + 1) >> 1, 4);
int index = pos.X + (pos.Y * levelBuffer.Size.Width);
return ctx + Av1NzMap.GetNzMapContext(transformSize, index); return ctx + Av1NzMap.GetNzMapContext(transformSize, index);
} }
/// <summary> /// <summary>
/// SVT: get_br_ctx /// SVT: get_br_ctx
/// </summary> /// </summary>
internal static int GetBaseRangeContext(Av1LevelBuffer levels, int index, int blockWidthLog2, Av1TransformClass transformClass) internal static int GetBaseRangeContext(Av1LevelBuffer levels, Point position, Av1TransformClass transformClass)
{ {
int y = index >> blockWidthLog2; Span<byte> row0 = levels.GetRow(position.Y);
int x = index - (y << blockWidthLog2); Span<byte> row1 = levels.GetRow(position.Y + 1);
Span<byte> row0 = levels.GetRow(y); int mag = row0[position.X + 1];
Span<byte> row1 = levels.GetRow(y + 1); mag += row1[position.X];
int mag = row0[x + 1];
mag += row1[x];
switch (transformClass) switch (transformClass)
{ {
case Av1TransformClass.Class2D: case Av1TransformClass.Class2D:
mag += row1[x + 1]; mag += row1[position.X + 1];
mag = Math.Min((mag + 1) >> 1, 6); mag = Math.Min((mag + 1) >> 1, 6);
if (index == 0) if ((position.X + position.Y) == 0)
{ {
return mag; return mag;
} }
if (y < 2 && x < 2) if (position.Y < 2 && position.X < 2)
{ {
return mag + 7; return mag + 7;
} }
@ -182,26 +201,26 @@ internal static class Av1SymbolContextHelper
case Av1TransformClass.ClassHorizontal: case Av1TransformClass.ClassHorizontal:
mag += row0[2]; mag += row0[2];
mag = Math.Min((mag + 1) >> 1, 6); mag = Math.Min((mag + 1) >> 1, 6);
if (index == 0) if ((position.X + position.Y) == 0)
{ {
return mag; return mag;
} }
if (x == 0) if (position.X == 0)
{ {
return mag + 7; return mag + 7;
} }
break; break;
case Av1TransformClass.ClassVertical: case Av1TransformClass.ClassVertical:
mag += levels.GetRow(y + 2)[0]; mag += levels.GetRow(position.Y + 2)[0];
mag = Math.Min((mag + 1) >> 1, 6); mag = Math.Min((mag + 1) >> 1, 6);
if (index == 0) if ((position.X + position.Y) == 0)
{ {
return mag; return mag;
} }
if (y == 0) if (position.Y == 0)
{ {
return mag + 7; return mag + 7;
} }
@ -214,10 +233,10 @@ internal static class Av1SymbolContextHelper
return mag + 14; return mag + 14;
} }
internal static int GetLowerLevelsContext(Av1LevelBuffer levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) internal static int GetLowerLevelsContext(Av1LevelBuffer levels, Point position, Av1TransformSize transformSize, Av1TransformClass transformClass)
{ {
int stats = Av1NzMap.GetNzMagnitude(levels, pos >> bwl, transformClass); int stats = Av1NzMap.GetNzMagnitude(levels, position, transformClass);
return Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); return Av1NzMap.GetNzMapContextFromStats(stats, levels, position, transformSize, transformClass);
} }
internal static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet) internal static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet)
@ -254,9 +273,7 @@ internal static class Av1SymbolContextHelper
/// </summary> /// </summary>
internal static sbyte GetNzMapContext( internal static sbyte GetNzMapContext(
Av1LevelBuffer levels, Av1LevelBuffer levels,
int index, Point position,
int blockWidthLog2,
int height,
int scan_idx, int scan_idx,
bool is_eob, bool is_eob,
Av1TransformSize transformSize, Av1TransformSize transformSize,
@ -264,26 +281,11 @@ internal static class Av1SymbolContextHelper
{ {
if (is_eob) if (is_eob)
{ {
if (scan_idx == 0) return (sbyte)GetLowerLevelContextEndOfBlock(levels, position);
{
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); int stats = Av1NzMap.GetNzMagnitude(levels, position, transformClass);
return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, index, blockWidthLog2, transformSize, transformClass); return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, levels, position, transformSize, transformClass);
} }
/// <summary> /// <summary>
@ -297,12 +299,11 @@ internal static class Av1SymbolContextHelper
Av1TransformClass transformClass, Av1TransformClass transformClass,
Span<sbyte> coefficientContexts) Span<sbyte> coefficientContexts)
{ {
int blockWidthLog2 = transformSize.GetBlockWidthLog2();
int height = transformSize.GetHeight();
for (int i = 0; i < eob; ++i) for (int i = 0; i < eob; ++i)
{ {
int pos = scan[i]; int pos = scan[i];
coefficientContexts[pos] = GetNzMapContext(levels, pos, blockWidthLog2, height, i, i == eob - 1, transformSize, transformClass); Point position = levels.GetPosition(pos);
coefficientContexts[pos] = GetNzMapContext(levels, position, i, i == eob - 1, transformSize, transformClass);
} }
} }

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

@ -259,11 +259,8 @@ internal ref struct Av1SymbolDecoder
} }
/// <summary> /// <summary>
/// 5.11.39. Coefficients syntax. /// SVT: parse_coeffs
/// </summary> /// </summary>
/// <remarks>
/// The implementation is taken from SVT-AV1 library, which deviates from the code flow in the specification.
/// </remarks>
public int ReadCoefficients( public int ReadCoefficients(
Av1BlockModeInfo modeInfo, Av1BlockModeInfo modeInfo,
Point blockPosition, Point blockPosition,
@ -285,14 +282,13 @@ internal ref struct Av1SymbolDecoder
{ {
int width = transformSize.GetWidth(); int width = transformSize.GetWidth();
int height = transformSize.GetHeight(); int height = transformSize.GetHeight();
Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1) >> 1); Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize);
Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1);
int culLevel = 0; int culLevel = 0;
Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); Av1LevelBuffer levels = new(this.configuration, new Size(width, height));
bool allZero = this.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext); bool allZero = this.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext);
int bwl = transformSize.GetBlockWidthLog2();
int endOfBlock; int endOfBlock;
if (allZero) if (allZero)
{ {
@ -322,22 +318,24 @@ internal ref struct Av1SymbolDecoder
levels.Clear(); levels.Clear();
} }
this.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, height, scan, bwl, levels, transformSizeContext, planeType); this.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, scan, levels, transformSizeContext, planeType);
if (endOfBlock > 1) if (endOfBlock > 1)
{ {
if (transformClass == Av1TransformClass.Class2D) if (transformClass == Av1TransformClass.Class2D)
{ {
this.ReadCoefficientsReverse2d(transformSize, 1, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); this.ReadCoefficientsReverse2d(transformSize, 1, endOfBlock - 1 - 1, scan, levels, transformSizeContext, planeType);
this.ReadCoefficientsReverse(transformSize, transformClass, 0, 0, scan, bwl, levels, transformSizeContext, planeType); this.ReadCoefficientsReverse(transformSize, transformClass, 0, 0, scan, levels, transformSizeContext, planeType);
} }
else else
{ {
this.ReadCoefficientsReverse(transformSize, transformClass, 0, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); this.ReadCoefficientsReverse(transformSize, transformClass, 0, endOfBlock - 1 - 1, scan, levels, transformSizeContext, planeType);
} }
} }
coefficientBuffer[0] = endOfBlock;
DebugGuard.MustBeGreaterThan(scan.Length, 0, nameof(scan)); DebugGuard.MustBeGreaterThan(scan.Length, 0, nameof(scan));
culLevel = this.ReadCoefficientsSign(coefficientBuffer, endOfBlock, scan, bwl, levels, transformBlockContext.DcSignContext, planeType); culLevel = this.ReadCoefficientsSign(coefficientBuffer, endOfBlock, scan, levels, transformBlockContext.DcSignContext, planeType);
UpdateCoefficientContext(modeInfo, aboveContexts, leftContexts, blocksWide, blocksHigh, transformSize, blockPosition, aboveOffset, leftOffset, culLevel, modeBlocksToRightEdge, modeBlocksToBottomEdge); UpdateCoefficientContext(modeInfo, aboveContexts, leftContexts, blocksWide, blocksHigh, transformSize, blockPosition, aboveOffset, leftOffset, culLevel, modeBlocksToRightEdge, modeBlocksToBottomEdge);
transformInfo.CodeBlockFlag = true; transformInfo.CodeBlockFlag = true;
@ -372,15 +370,15 @@ internal ref struct Av1SymbolDecoder
return Av1SymbolContextHelper.RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra); return Av1SymbolContextHelper.RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra);
} }
public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, ReadOnlySpan<short> scan, int blockWidthLog2, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, ReadOnlySpan<short> scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{ {
int i = endOfBlock - 1; int i = endOfBlock - 1;
int pos = scan[i]; Point position = levels.GetPosition(scan[i]);
int coefficientContext = Av1SymbolContextHelper.GetLowerLevelContextEndOfBlock(blockWidthLog2, height, i); int coefficientContext = Av1SymbolContextHelper.GetLowerLevelContextEndOfBlock(levels, position);
int level = this.ReadBaseEndOfBlock(transformSizeContext, planeType, coefficientContext); int level = this.ReadBaseEndOfBlock(transformSizeContext, planeType, coefficientContext);
if (level > Av1Constants.BaseLevelsCount) if (level > Av1Constants.BaseLevelsCount)
{ {
int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContextEndOfBlock(pos, blockWidthLog2, transformClass); int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContextEndOfBlock(position, transformClass);
for (int idx = 0; idx < Av1Constants.CoefficientBaseRange / Av1Constants.BaseRangeSizeMinus1; idx++) for (int idx = 0; idx < Av1Constants.CoefficientBaseRange / Av1Constants.BaseRangeSizeMinus1; idx++)
{ {
int coefficinetBaseRange = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); int coefficinetBaseRange = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext);
@ -392,19 +390,19 @@ internal ref struct Av1SymbolDecoder
} }
} }
levels.GetPaddedRow(pos, blockWidthLog2)[0] = (byte)level; levels.GetRow(position)[0] = (byte)level;
} }
public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan<short> scan, int blockWidthLog2, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan<short> scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{ {
for (int c = endScanIndex; c >= startScanIndex; --c) for (int c = endScanIndex; c >= startScanIndex; --c)
{ {
int pos = scan[c]; Point position = levels.GetPosition(scan[c]);
int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext2d(levels, pos, blockWidthLog2, transformSize); int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext2d(levels, position, transformSize);
int level = this.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); int level = this.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType);
if (level > Av1Constants.BaseLevelsCount) if (level > Av1Constants.BaseLevelsCount)
{ {
int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext2d(levels, pos, blockWidthLog2); int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext2d(levels, position);
for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1)
{ {
int k = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); int k = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext);
@ -416,20 +414,21 @@ internal ref struct Av1SymbolDecoder
} }
} }
levels.GetPaddedRow(pos, blockWidthLog2)[0] = (byte)level; levels.GetRow(position)[0] = (byte)level;
} }
} }
public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan<short> scan, int blockWidthLog2, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan<short> scan, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType)
{ {
for (int c = endScanIndex; c >= startScanIndex; --c) for (int c = endScanIndex; c >= startScanIndex; --c)
{ {
int pos = scan[c]; int pos = scan[c];
int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext(levels, pos, blockWidthLog2, transformSize, transformClass); Point position = levels.GetPosition(pos);
int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext(levels, position, transformSize, transformClass);
int level = this.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); int level = this.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType);
if (level > Av1Constants.BaseLevelsCount) if (level > Av1Constants.BaseLevelsCount)
{ {
int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, pos, blockWidthLog2, transformClass); int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, position, transformClass);
for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1)
{ {
int k = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); int k = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext);
@ -441,11 +440,11 @@ internal ref struct Av1SymbolDecoder
} }
} }
levels.GetPaddedRow(pos, blockWidthLog2)[0] = (byte)level; levels.GetRow(position)[0] = (byte)level;
} }
} }
public int ReadCoefficientsSign(Span<int> coefficientBuffer, int endOfBlock, ReadOnlySpan<short> scan, int blockWidthLog2, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType) public int ReadCoefficientsSign(Span<int> coefficientBuffer, int endOfBlock, ReadOnlySpan<short> scan, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType)
{ {
int maxScanLine = 0; int maxScanLine = 0;
int culLevel = 0; int culLevel = 0;
@ -454,7 +453,8 @@ internal ref struct Av1SymbolDecoder
for (int c = 0; c < endOfBlock; c++) for (int c = 0; c < endOfBlock; c++)
{ {
int sign = 0; int sign = 0;
int level = levels.GetPaddedRow(scan[c], blockWidthLog2)[0]; Point position = levels.GetPosition(scan[c]);
int level = levels.GetRow(position)[0];
if (level != 0) if (level != 0)
{ {
maxScanLine = Math.Max(maxScanLine, scan[c]); maxScanLine = Math.Max(maxScanLine, scan[c]);
@ -507,7 +507,8 @@ internal ref struct Av1SymbolDecoder
private int ReadCoefficientsBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext) private int ReadCoefficientsBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext)
{ {
ref Av1SymbolReader r = ref this.reader; ref Av1SymbolReader r = ref this.reader;
return r.ReadSymbol(this.coefficientsBaseRange[(int)transformSizeContext][(int)planeType][baseRangeContext]); int transformContext = Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32);
return r.ReadSymbol(this.coefficientsBaseRange[transformContext][(int)planeType][baseRangeContext]);
} }
private int ReadDcSign(Av1PlaneType planeType, int dcSignContext) private int ReadDcSign(Av1PlaneType planeType, int dcSignContext)

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

@ -98,12 +98,12 @@ internal class Av1SymbolEncoder : IDisposable
Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType);
ReadOnlySpan<short> scan = scanOrder.Scan; ReadOnlySpan<short> scan = scanOrder.Scan;
int blockWidthLog2 = transformSize.GetBlockWidthLog2(); int blockWidthLog2 = transformSize.GetBlockWidthLog2();
Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1) >> 1); Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize);
ref Av1SymbolWriter w = ref this.writer; ref Av1SymbolWriter w = ref this.writer;
Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); Av1LevelBuffer levels = new(this.configuration, new Size(width, height));
Span<sbyte> coefficientContexts = new sbyte[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize]; Span<sbyte> coefficientContexts = new sbyte[width * height];
Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext)); Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext));
@ -129,6 +129,7 @@ internal class Av1SymbolEncoder : IDisposable
short pos = scan[c]; short pos = scan[c];
int v = coefficientBuffer[pos]; int v = coefficientBuffer[pos];
short coeffContext = coefficientContexts[pos]; short coeffContext = coefficientContexts[pos];
Point position = levels.GetPosition(pos);
int level = Math.Abs(v); int level = Math.Abs(v);
if (c == endOfBlock - 1) if (c == endOfBlock - 1)
@ -144,7 +145,7 @@ internal class Av1SymbolEncoder : IDisposable
{ {
// level is above 1. // level is above 1.
int baseRange = level - 1 - Av1Constants.BaseLevelsCount; int baseRange = level - 1 - Av1Constants.BaseLevelsCount;
int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, pos, blockWidthLog2, transformClass); int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, position, transformClass);
for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1)
{ {
int k = Math.Min(baseRange - idx, Av1Constants.BaseRangeSizeMinus1); int k = Math.Min(baseRange - idx, Av1Constants.BaseRangeSizeMinus1);

17
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -45,6 +46,16 @@ internal sealed class Av1LevelBuffer : IDisposable
} }
} }
public Point GetPosition(int index)
{
int x = index % this.Size.Width;
int y = index / this.Size.Width;
return new Point(x, y);
}
public Span<byte> GetRow(Point pos)
=> this.GetRow(pos.Y);
public Span<byte> GetRow(int y) public Span<byte> GetRow(int y)
{ {
ObjectDisposedException.ThrowIf(this.memory == null, this); ObjectDisposedException.ThrowIf(this.memory == null, this);
@ -64,10 +75,4 @@ internal sealed class Av1LevelBuffer : IDisposable
ObjectDisposedException.ThrowIf(this.memory == null, this); ObjectDisposedException.ThrowIf(this.memory == null, this);
this.memory.Memory.Span.Clear(); this.memory.Memory.Span.Clear();
} }
internal Span<byte> GetPaddedRow(int index, int blockWidthLog2)
{
int y = index >> blockWidthLog2;
return this.GetRow(y);
}
} }

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

@ -545,7 +545,7 @@ internal class Av1TileReader : IAv1TileReader
Span<int> coefficientBuffer = this.FrameInfo.GetCoefficients(plane); Span<int> coefficientBuffer = this.FrameInfo.GetCoefficients(plane);
int width = transformSize.GetWidth(); int width = transformSize.GetWidth();
int height = transformSize.GetHeight(); int height = transformSize.GetHeight();
Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + ((int)transformSize.GetSquareUpSize() + 1)) >> 1); Av1TransformSize transformSizeContext = Av1SymbolContextHelper.GetTransformSizeContext(transformSize);
Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1);
Point blockPosition = new(blockColumn, blockRow); Point blockPosition = new(blockColumn, blockRow);
bool isLossless = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId]; bool isLossless = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId];

40
src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs

@ -5,26 +5,26 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
internal enum Av1TransformSize : byte internal enum Av1TransformSize : byte
{ {
Size4x4, Size4x4 = 0,
Size8x8, Size8x8 = 1,
Size16x16, Size16x16 = 2,
Size32x32, Size32x32 = 3,
Size64x64, Size64x64 = 4,
Size4x8, Size4x8 = 5,
Size8x4, Size8x4 = 6,
Size8x16, Size8x16 = 7,
Size16x8, Size16x8 = 8,
Size16x32, Size16x32 = 9,
Size32x16, Size32x16 = 10,
Size32x64, Size32x64 = 11,
Size64x32, Size64x32 = 12,
Size4x16, Size4x16 = 13,
Size16x4, Size16x4 = 14,
Size8x32, Size8x32 = 15,
Size32x8, Size32x8 = 16,
Size16x64, Size16x64 = 17,
Size64x16, Size64x16 = 18,
AllSizes, AllSizes = 19,
SquareSizes = Size4x8, SquareSizes = Size4x8,
Invalid = 255, Invalid = 255,
} }

6
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs

@ -48,6 +48,7 @@ public class Av1CoefficientsEntropyTests
decoder.ReadCoefficients(modeInfo, new Point(0, 0), aboveContexts, leftContexts, 0, 0, 0, 1, 1, transformBlockContext, transformSize, false, true, transformInfo, 0, 0, actuals); decoder.ReadCoefficients(modeInfo, new Point(0, 0), aboveContexts, leftContexts, 0, 0, 0, 1, 1, transformBlockContext, transformSize, false, true, transformInfo, 0, 0, actuals);
// Assert // Assert
Assert.Equal(endOfBlock, actuals[0]);
Assert.Equal(expected, actuals); Assert.Equal(expected, actuals);
} }
@ -70,7 +71,7 @@ public class Av1CoefficientsEntropyTests
Configuration configuration = Configuration.Default; Configuration configuration = Configuration.Default;
Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex);
Span<int> coefficientsBuffer = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; Span<int> coefficientsBuffer = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
Span<int> actuals = new int[16]; Span<int> actuals = new int[16 + 1];
// Act // Act
encoder.WriteCoefficients(transformSize, transformType, intraDirection, coefficientsBuffer, componentType, transformBlockContext, endOfBlock, true, filterIntraMode); encoder.WriteCoefficients(transformSize, transformType, intraDirection, coefficientsBuffer, componentType, transformBlockContext, endOfBlock, true, filterIntraMode);
@ -83,6 +84,7 @@ public class Av1CoefficientsEntropyTests
decoder.ReadCoefficients(modeInfo, new Point(0, 0), aboveContexts, leftContexts, 0, 0, plane, 1, 1, transformBlockContext, transformSize, false, true, transformInfo, 0, 0, actuals); decoder.ReadCoefficients(modeInfo, new Point(0, 0), aboveContexts, leftContexts, 0, 0, plane, 1, 1, transformBlockContext, transformSize, false, true, transformInfo, 0, 0, actuals);
// Assert // Assert
Assert.Equal(coefficientsBuffer, actuals); Assert.Equal(endOfBlock, actuals[0]);
Assert.Equal(coefficientsBuffer, actuals[1..]);
} }
} }

91
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1LevelBufferTests.cs

@ -0,0 +1,91 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
[Trait("Format", "Avif")]
public class Av1LevelBufferTests
{
[Theory]
[InlineData(4, 4, 4, 1)]
[InlineData(4, 4, 5, 1)]
[InlineData(4, 4, 6, 1)]
[InlineData(4, 4, 7, 1)]
[InlineData(8, 4, 7, 0)]
[InlineData(8, 8, 16, 2)]
[InlineData(8, 4, 16, 2)]
public void TestGetPaddedRow(int width, int height, int index, byte expected)
{
// Arrange
Size size = new(width, height);
Av1LevelBuffer levels = new(Configuration.Default, size);
for (byte i = 0; i < 4; i++)
{
levels.GetRow(i).Fill(i);
}
// Act
Point pos = levels.GetPosition(index);
// Assert
Assert.Equal(expected, pos.Y);
Assert.Equal(expected, levels.GetRow(pos)[0]);
}
[Theory]
[InlineData(4, 4)]
[InlineData(8, 4)]
[InlineData(8, 8)]
[InlineData(16, 4)]
public void TestGetRow(int width, int height)
{
// Arrange
Size size = new(width, height);
Av1LevelBuffer levels = new(Configuration.Default, size);
for (byte i = 0; i < height; i++)
{
levels.GetRow(i).Fill(i);
}
for (int j = 0; j < height; j++)
{
// Act
Span<byte> actual = levels.GetRow(j);
// Assert
Assert.Equal(j, actual[0]);
Assert.True(actual.Length >= width);
}
}
[Theory]
[InlineData(4, 4)]
[InlineData(8, 4)]
[InlineData(8, 8)]
[InlineData(16, 4)]
public void TestClear(int width, int height)
{
// Arrange
Size size = new(width, height);
Av1LevelBuffer levels = new(Configuration.Default, size);
for (byte i = 0; i < height; i++)
{
levels.GetRow(i).Fill(i);
}
// Act
levels.Clear();
// Assert
for (int j = 0; j < height; j++)
{
Span<byte> rowSpan = levels.GetRow(j);
for (int k = 0; k < width; k++)
{
Assert.Equal(0, rowSpan[k]);
}
}
}
}
Loading…
Cancel
Save