mirror of https://github.com/SixLabors/ImageSharp
12 changed files with 1362 additions and 87 deletions
@ -0,0 +1,343 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the immutable entropy-coding parameters for one HEVC transform block.
|
|||
/// </summary>
|
|||
internal readonly struct HevcCoefficientCodingParameters |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcCoefficientCodingParameters"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="plane">The reconstructed component.</param>
|
|||
/// <param name="scanType">The coefficient scan selected for the block.</param>
|
|||
/// <param name="useSingleSignificanceContext">Whether transform skip or transquant bypass selects the single significance context.</param>
|
|||
/// <param name="signDataHidingEnabled">Whether the first coefficient sign in an eligible group is inferred.</param>
|
|||
/// <param name="persistentRiceAdaptationEnabled">Whether Rice parameters adapt across transform blocks.</param>
|
|||
/// <param name="cabacBypassAlignmentEnabled">Whether coefficient bypass data is byte aligned when escape data is present.</param>
|
|||
/// <param name="extendedPrecisionProcessingEnabled">Whether coefficient remainders use the bounded extended-precision prefix.</param>
|
|||
/// <param name="maximumLog2TransformDynamicRange">The component transform dynamic range excluding its sign bit.</param>
|
|||
/// <param name="riceStatisticsIndex">The luma/chroma and transformed/non-transformed Rice statistics selector.</param>
|
|||
public HevcCoefficientCodingParameters( |
|||
int width, |
|||
int height, |
|||
HevcPlane plane, |
|||
HevcCoefficientScanType scanType, |
|||
bool useSingleSignificanceContext, |
|||
bool signDataHidingEnabled, |
|||
bool persistentRiceAdaptationEnabled, |
|||
bool cabacBypassAlignmentEnabled, |
|||
bool extendedPrecisionProcessingEnabled, |
|||
int maximumLog2TransformDynamicRange, |
|||
int riceStatisticsIndex) |
|||
{ |
|||
this.Width = width; |
|||
this.Height = height; |
|||
this.Plane = plane; |
|||
this.ScanType = scanType; |
|||
this.FirstSignificanceMapContext = GetFirstSignificanceMapContext(width, height, plane != HevcPlane.Y, scanType, useSingleSignificanceContext); |
|||
this.SignDataHidingEnabled = signDataHidingEnabled; |
|||
this.PersistentRiceAdaptationEnabled = persistentRiceAdaptationEnabled; |
|||
this.CabacBypassAlignmentEnabled = cabacBypassAlignmentEnabled; |
|||
this.ExtendedPrecisionProcessingEnabled = extendedPrecisionProcessingEnabled; |
|||
this.MaximumLog2TransformDynamicRange = maximumLog2TransformDynamicRange; |
|||
this.RiceStatisticsIndex = riceStatisticsIndex; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform-block width.
|
|||
/// </summary>
|
|||
public int Width { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform-block height.
|
|||
/// </summary>
|
|||
public int Height { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the reconstructed component.
|
|||
/// </summary>
|
|||
public HevcPlane Plane { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coefficient scan selected for the block.
|
|||
/// </summary>
|
|||
public HevcCoefficientScanType ScanType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the first significant-coefficient context within the component context set.
|
|||
/// </summary>
|
|||
public int FirstSignificanceMapContext { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether an eligible first coefficient sign is inferred from the group parity.
|
|||
/// </summary>
|
|||
public bool SignDataHidingEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether Rice parameters adapt across transform blocks.
|
|||
/// </summary>
|
|||
public bool PersistentRiceAdaptationEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether coefficient bypass data is byte aligned when escape data is present.
|
|||
/// </summary>
|
|||
public bool CabacBypassAlignmentEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether coefficient remainders use the bounded extended-precision prefix.
|
|||
/// </summary>
|
|||
public bool ExtendedPrecisionProcessingEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the component transform dynamic range excluding its sign bit.
|
|||
/// </summary>
|
|||
public int MaximumLog2TransformDynamicRange { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma/chroma and transformed/non-transformed Rice statistics selector.
|
|||
/// </summary>
|
|||
public int RiceStatisticsIndex { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the raster-position context mapping for a 4 by 4 transform block.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> SignificanceContexts4x4 => |
|||
[ |
|||
0, 1, 4, 5, |
|||
2, 3, 4, 5, |
|||
6, 6, 8, 8, |
|||
7, 7, 8, 8, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Creates the coefficient parameters selected by the active sequence, picture, and transform-unit state.
|
|||
/// </summary>
|
|||
/// <param name="pictureParameterSet">The active picture parameters.</param>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="plane">The reconstructed component.</param>
|
|||
/// <param name="isIntra">Whether the containing coding unit uses intra prediction.</param>
|
|||
/// <param name="intraPredictionMode">The effective intra prediction mode, or a value ignored for inter prediction.</param>
|
|||
/// <param name="transformSkip">Whether the transform block bypasses the inverse transform.</param>
|
|||
/// <param name="transquantBypass">Whether the coding unit bypasses inverse quantization and inverse transform.</param>
|
|||
/// <param name="residualDpcmMode">The residual differential-pulse-code-modulation mode selected for the block.</param>
|
|||
/// <returns>The coefficient entropy-coding parameters for the transform block.</returns>
|
|||
public static HevcCoefficientCodingParameters Create( |
|||
HevcPictureParameterSet pictureParameterSet, |
|||
int width, |
|||
int height, |
|||
HevcPlane plane, |
|||
bool isIntra, |
|||
int intraPredictionMode, |
|||
bool transformSkip, |
|||
bool transquantBypass, |
|||
HevcResidualDpcmMode residualDpcmMode) |
|||
{ |
|||
HevcSequenceParameterSet sequenceParameterSet = pictureParameterSet.SequenceParameterSet; |
|||
bool isChroma = plane != HevcPlane.Y; |
|||
bool nonTransformed = transformSkip || transquantBypass; |
|||
HevcCoefficientScanType scanType = SelectScanType( |
|||
width, |
|||
height, |
|||
plane, |
|||
isIntra, |
|||
intraPredictionMode, |
|||
sequenceParameterSet.ChromaFormat, |
|||
sequenceParameterSet.SeparateColorPlaneFlag); |
|||
|
|||
return new HevcCoefficientCodingParameters( |
|||
width, |
|||
height, |
|||
plane, |
|||
scanType, |
|||
sequenceParameterSet.TransformSkipContextEnabled && nonTransformed, |
|||
pictureParameterSet.SignDataHidingEnabled && !transquantBypass && residualDpcmMode == HevcResidualDpcmMode.None, |
|||
sequenceParameterSet.PersistentRiceAdaptationEnabled, |
|||
sequenceParameterSet.CabacBypassAlignmentEnabled, |
|||
sequenceParameterSet.ExtendedPrecisionProcessingEnabled, |
|||
sequenceParameterSet.GetMaxTransformDynamicRange(plane), |
|||
(isChroma ? 2 : 0) + (nonTransformed ? 1 : 0)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects the scan direction from transform geometry and the effective intra prediction direction.
|
|||
/// </summary>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="plane">The reconstructed component.</param>
|
|||
/// <param name="isIntra">Whether the containing coding unit uses intra prediction.</param>
|
|||
/// <param name="intraPredictionMode">The effective intra prediction mode.</param>
|
|||
/// <param name="chromaFormat">The sequence chroma-format identifier.</param>
|
|||
/// <param name="separateColorPlane">Whether each 4:4:4 component is coded as an independent color plane.</param>
|
|||
/// <returns>The selected coefficient scan.</returns>
|
|||
public static HevcCoefficientScanType SelectScanType( |
|||
int width, |
|||
int height, |
|||
HevcPlane plane, |
|||
bool isIntra, |
|||
int intraPredictionMode, |
|||
byte chromaFormat, |
|||
bool separateColorPlane) |
|||
{ |
|||
if (!isIntra) |
|||
{ |
|||
return HevcCoefficientScanType.Diagonal; |
|||
} |
|||
|
|||
bool isSubsampledChroma = plane != HevcPlane.Y && !separateColorPlane; |
|||
int subsamplingX = isSubsampledChroma && chromaFormat is 1 or 2 ? 1 : 0; |
|||
int subsamplingY = isSubsampledChroma && chromaFormat == 1 ? 1 : 0; |
|||
if (width > (8 >> subsamplingX) || height > (8 >> subsamplingY)) |
|||
{ |
|||
return HevcCoefficientScanType.Diagonal; |
|||
} |
|||
|
|||
int mode = plane != HevcPlane.Y && chromaFormat == 2 && !separateColorPlane |
|||
? HevcIntraPredictionMode.RemapChroma422(intraPredictionMode) |
|||
: intraPredictionMode; |
|||
|
|||
// Modes close to vertical place correlated residuals along rows, while modes close to horizontal use the
|
|||
// transposed column scan. All other modes retain the diagonal scan.
|
|||
if (Math.Abs(mode - HevcIntraPredictionMode.Vertical) <= 4) |
|||
{ |
|||
return HevcCoefficientScanType.Horizontal; |
|||
} |
|||
|
|||
return Math.Abs(mode - HevcIntraPredictionMode.Horizontal) <= 4 |
|||
? HevcCoefficientScanType.Vertical |
|||
: HevcCoefficientScanType.Diagonal; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Derives the coded-sub-block significance context from already decoded right and lower groups.
|
|||
/// </summary>
|
|||
/// <param name="groupFlags">The raster-ordered significant-group flags.</param>
|
|||
/// <param name="groupX">The current group horizontal coordinate.</param>
|
|||
/// <param name="groupY">The current group vertical coordinate.</param>
|
|||
/// <returns>Zero when neither neighbor is significant; otherwise, one.</returns>
|
|||
public int GetSignificantGroupContext(ReadOnlySpan<int> groupFlags, int groupX, int groupY) |
|||
{ |
|||
int widthInGroups = this.Width / 4; |
|||
int heightInGroups = this.Height / 4; |
|||
bool rightSignificant = groupX < widthInGroups - 1 && groupFlags[(groupY * widthInGroups) + groupX + 1] != 0; |
|||
bool lowerSignificant = groupY < heightInGroups - 1 && groupFlags[((groupY + 1) * widthInGroups) + groupX] != 0; |
|||
return rightSignificant || lowerSignificant ? 1 : 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Derives the two-bit right-and-lower significance pattern for coefficient contexts.
|
|||
/// </summary>
|
|||
/// <param name="groupFlags">The raster-ordered significant-group flags.</param>
|
|||
/// <param name="groupX">The current group horizontal coordinate.</param>
|
|||
/// <param name="groupY">The current group vertical coordinate.</param>
|
|||
/// <returns>The right flag in bit zero and the lower flag in bit one.</returns>
|
|||
public int GetSignificancePattern(ReadOnlySpan<int> groupFlags, int groupX, int groupY) |
|||
{ |
|||
int widthInGroups = this.Width / 4; |
|||
int heightInGroups = this.Height / 4; |
|||
int right = groupX < widthInGroups - 1 && groupFlags[(groupY * widthInGroups) + groupX + 1] != 0 ? 1 : 0; |
|||
int lower = groupY < heightInGroups - 1 && groupFlags[((groupY + 1) * widthInGroups) + groupX] != 0 ? 1 : 0; |
|||
return right + (lower << 1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Derives the significant-coefficient context from its position and neighboring coefficient groups.
|
|||
/// </summary>
|
|||
/// <param name="rasterPosition">The coefficient raster position.</param>
|
|||
/// <param name="significancePattern">The right-and-lower significant-group pattern.</param>
|
|||
/// <returns>The context index within the component significance-map context set.</returns>
|
|||
public int GetSignificantCoefficientContext(int rasterPosition, int significancePattern) |
|||
{ |
|||
bool isChroma = this.Plane != HevcPlane.Y; |
|||
if (this.FirstSignificanceMapContext == (isChroma ? 15 : 27)) |
|||
{ |
|||
return this.FirstSignificanceMapContext; |
|||
} |
|||
|
|||
int y = rasterPosition / this.Width; |
|||
int x = rasterPosition - (y * this.Width); |
|||
if (x + y == 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
if (this.Width == 4 && this.Height == 4) |
|||
{ |
|||
return SignificanceContexts4x4[(y * 4) + x]; |
|||
} |
|||
|
|||
int context; |
|||
switch (significancePattern) |
|||
{ |
|||
case 0: |
|||
int positionInGroup = (x & 3) + (y & 3); |
|||
context = positionInGroup >= 3 ? 0 : positionInGroup >= 1 ? 1 : 2; |
|||
break; |
|||
case 1: |
|||
int yInGroup = y & 3; |
|||
context = yInGroup >= 2 ? 0 : yInGroup >= 1 ? 1 : 2; |
|||
break; |
|||
case 2: |
|||
int xInGroup = x & 3; |
|||
context = xInGroup >= 2 ? 0 : xInGroup >= 1 ? 1 : 2; |
|||
break; |
|||
default: |
|||
context = 2; |
|||
break; |
|||
} |
|||
|
|||
bool isBeyondFirstGroup = (x >> 2) + (y >> 2) > 0; |
|||
return this.FirstSignificanceMapContext + (isBeyondFirstGroup && !isChroma ? 3 : 0) + context; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects the greater-than-one and greater-than-two context set for one coefficient group.
|
|||
/// </summary>
|
|||
/// <param name="subset">The coefficient-group scan index.</param>
|
|||
/// <param name="foundGreaterThanOne">Whether the preceding group ended after finding a coefficient greater than one.</param>
|
|||
/// <returns>The zero-based context set within the component context range.</returns>
|
|||
public int GetLevelContextSet(int subset, bool foundGreaterThanOne) |
|||
{ |
|||
int nonFirstSubsetOffset = this.Plane == HevcPlane.Y && subset > 0 ? 2 : 0; |
|||
return nonFirstSubsetOffset + (foundGreaterThanOne ? 1 : 0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Derives the first significant-coefficient context within one component context set.
|
|||
/// </summary>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="isChroma">Whether the transform block belongs to a chroma channel.</param>
|
|||
/// <param name="scanType">The selected coefficient scan.</param>
|
|||
/// <param name="useSingleSignificanceContext">Whether Range Extensions selects the single-context mode.</param>
|
|||
/// <returns>The first significant-coefficient context index.</returns>
|
|||
private static int GetFirstSignificanceMapContext( |
|||
int width, |
|||
int height, |
|||
bool isChroma, |
|||
HevcCoefficientScanType scanType, |
|||
bool useSingleSignificanceContext) |
|||
{ |
|||
if (useSingleSignificanceContext) |
|||
{ |
|||
return isChroma ? 15 : 27; |
|||
} |
|||
|
|||
if (width == 4 && height == 4) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
if (width == 8 && height == 8) |
|||
{ |
|||
return isChroma ? 9 : scanType == HevcCoefficientScanType.Diagonal ? 9 : 15; |
|||
} |
|||
|
|||
return isChroma ? 12 : 21; |
|||
} |
|||
} |
|||
@ -0,0 +1,389 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Decodes HEVC transform coefficients while retaining entropy-substream Rice state and reusable scratch storage.
|
|||
/// </summary>
|
|||
internal sealed class HevcCoefficientDecoder : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The maximum coefficient count in a 32 by 32 transform block.
|
|||
/// </summary>
|
|||
private const int MaximumCoefficientCount = 32 * 32; |
|||
|
|||
/// <summary>
|
|||
/// The maximum number of 4 by 4 coefficient groups in a transform block.
|
|||
/// </summary>
|
|||
private const int MaximumCoefficientGroupCount = MaximumCoefficientCount / 16; |
|||
|
|||
/// <summary>
|
|||
/// The maximum number of significant coefficients in one coefficient group.
|
|||
/// </summary>
|
|||
private const int CoefficientsPerGroup = 16; |
|||
|
|||
/// <summary>
|
|||
/// The maximum number of greater-than-one flags coded in one coefficient group.
|
|||
/// </summary>
|
|||
private const int GreaterThanOneFlagCount = 8; |
|||
|
|||
/// <summary>
|
|||
/// The minimum scan-position separation that enables sign-data hiding.
|
|||
/// </summary>
|
|||
private const int SignDataHidingThreshold = 4; |
|||
|
|||
/// <summary>
|
|||
/// The divisor that converts a persistent adaptation statistic to its Rice parameter.
|
|||
/// </summary>
|
|||
private const int RiceAdaptationDivisor = 4; |
|||
|
|||
/// <summary>
|
|||
/// The first scratch index occupied by coefficient-group significance flags.
|
|||
/// </summary>
|
|||
private const int CoefficientGroupFlagsOffset = MaximumCoefficientCount; |
|||
|
|||
/// <summary>
|
|||
/// The first scratch index occupied by significant coefficient raster positions.
|
|||
/// </summary>
|
|||
private const int CoefficientPositionsOffset = CoefficientGroupFlagsOffset + MaximumCoefficientGroupCount; |
|||
|
|||
/// <summary>
|
|||
/// The first scratch index occupied by absolute coefficient levels.
|
|||
/// </summary>
|
|||
private const int AbsoluteLevelsOffset = CoefficientPositionsOffset + CoefficientsPerGroup; |
|||
|
|||
/// <summary>
|
|||
/// The total number of pooled integers used by coefficient decoding.
|
|||
/// </summary>
|
|||
private const int ScratchLength = AbsoluteLevelsOffset + CoefficientsPerGroup; |
|||
|
|||
/// <summary>
|
|||
/// The allocator-owned scan and coefficient-group working storage reused for every transform block.
|
|||
/// </summary>
|
|||
private readonly IMemoryOwner<int> scratchOwner; |
|||
|
|||
/// <summary>
|
|||
/// The persistent Rice statistics for transformed and non-transformed luma and chroma blocks.
|
|||
/// </summary>
|
|||
private InlineArray4<int> riceAdaptationStatistics; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcCoefficientDecoder"/> class for one entropy substream.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration providing pooled codec memory.</param>
|
|||
public HevcCoefficientDecoder(Configuration configuration) |
|||
{ |
|||
this.scratchOwner = configuration.MemoryAllocator.Allocate<int>(ScratchLength); |
|||
this.riceAdaptationStatistics = default; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the minimum coordinate represented by each last-significant prefix.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> MinimumCoordinateInGroup => [0, 1, 2, 3, 4, 6, 8, 12, 16, 24]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the last-significant prefix selected by each transform coordinate.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> CoordinateGroupIndex => |
|||
[ |
|||
0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, |
|||
8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Decodes one transform block into raster-ordered signed coefficient levels.
|
|||
/// </summary>
|
|||
/// <param name="reader">The current entropy-substream syntax reader.</param>
|
|||
/// <param name="coefficients">The destination coefficient block.</param>
|
|||
/// <param name="parameters">The transform-block coefficient coding parameters.</param>
|
|||
/// <returns>The number of nonzero coefficients decoded into <paramref name="coefficients"/>.</returns>
|
|||
public int Decode(ref HevcCabacSyntaxReader reader, Span<int> coefficients, in HevcCoefficientCodingParameters parameters) |
|||
{ |
|||
int width = parameters.Width; |
|||
int height = parameters.Height; |
|||
int coefficientCount = width * height; |
|||
bool isChroma = parameters.Plane != HevcPlane.Y; |
|||
coefficients[..coefficientCount].Clear(); |
|||
|
|||
ReadLastSignificantPosition(ref reader, in parameters, out int lastX, out int lastY); |
|||
int lastRasterPosition = (lastY * width) + lastX; |
|||
Span<int> scratch = this.scratchOwner.Memory.Span; |
|||
Span<int> scan = scratch[..coefficientCount]; |
|||
int lastScanPosition = HevcCoefficientScanOrder.Write(scan, width, height, parameters.ScanType, lastRasterPosition); |
|||
int groupCount = coefficientCount / CoefficientsPerGroup; |
|||
Span<int> significantGroupFlags = scratch.Slice(CoefficientGroupFlagsOffset, groupCount); |
|||
Span<int> positions = scratch.Slice(CoefficientPositionsOffset, CoefficientsPerGroup); |
|||
Span<int> absoluteLevels = scratch.Slice(AbsoluteLevelsOffset, CoefficientsPerGroup); |
|||
significantGroupFlags.Clear(); |
|||
|
|||
int widthInGroups = width / 4; |
|||
int lastSubset = lastScanPosition / CoefficientsPerGroup; |
|||
int significantScanPosition = lastScanPosition; |
|||
int c1 = 1; |
|||
int totalNonZero = 0; |
|||
ref int currentRiceStatistic = ref this.riceAdaptationStatistics[parameters.RiceStatisticsIndex]; |
|||
|
|||
// Coefficient groups are decoded from the last significant position toward DC. This direction makes the
|
|||
// already decoded right and lower groups available to the significance-context derivation below.
|
|||
for (int subset = lastSubset; subset >= 0; subset--) |
|||
{ |
|||
int subsetStart = subset * CoefficientsPerGroup; |
|||
int riceParameter = currentRiceStatistic / RiceAdaptationDivisor; |
|||
bool updateRiceStatistic = parameters.PersistentRiceAdaptationEnabled; |
|||
int nonZeroCount = 0; |
|||
int lastNonZeroScanPosition = -1; |
|||
int firstNonZeroScanPosition = CoefficientsPerGroup; |
|||
bool escapeDataPresent = false; |
|||
|
|||
if (significantScanPosition == lastScanPosition) |
|||
{ |
|||
lastNonZeroScanPosition = significantScanPosition; |
|||
firstNonZeroScanPosition = significantScanPosition; |
|||
significantScanPosition--; |
|||
positions[0] = lastRasterPosition; |
|||
nonZeroCount = 1; |
|||
} |
|||
|
|||
int groupRasterPosition = scan[subsetStart]; |
|||
int groupY = (groupRasterPosition / width) / 4; |
|||
int groupX = (groupRasterPosition % width) / 4; |
|||
int groupIndex = (groupY * widthInGroups) + groupX; |
|||
if (subset == lastSubset || subset == 0) |
|||
{ |
|||
significantGroupFlags[groupIndex] = 1; |
|||
} |
|||
else |
|||
{ |
|||
int groupContext = parameters.GetSignificantGroupContext(significantGroupFlags, groupX, groupY); |
|||
significantGroupFlags[groupIndex] = reader.ReadSignificantCoefficientGroup(isChroma, groupContext) ? 1 : 0; |
|||
} |
|||
|
|||
int significancePattern = parameters.GetSignificancePattern(significantGroupFlags, groupX, groupY); |
|||
for (; significantScanPosition >= subsetStart; significantScanPosition--) |
|||
{ |
|||
int rasterPosition = scan[significantScanPosition]; |
|||
bool isSignificant = false; |
|||
if (significantGroupFlags[groupIndex] != 0) |
|||
{ |
|||
if (significantScanPosition > subsetStart || subset == 0 || nonZeroCount != 0) |
|||
{ |
|||
int contextIndex = parameters.GetSignificantCoefficientContext(rasterPosition, significancePattern); |
|||
isSignificant = reader.ReadSignificantCoefficient(isChroma, contextIndex); |
|||
} |
|||
else |
|||
{ |
|||
// A coded significant group must contain at least one coefficient. When every later flag is
|
|||
// zero, the first scan position is therefore inferred rather than consuming another CABAC bin.
|
|||
isSignificant = true; |
|||
} |
|||
} |
|||
|
|||
if (isSignificant) |
|||
{ |
|||
positions[nonZeroCount++] = rasterPosition; |
|||
if (lastNonZeroScanPosition < 0) |
|||
{ |
|||
lastNonZeroScanPosition = significantScanPosition; |
|||
} |
|||
|
|||
firstNonZeroScanPosition = significantScanPosition; |
|||
} |
|||
} |
|||
|
|||
if (nonZeroCount == 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
bool hideSign = lastNonZeroScanPosition - firstNonZeroScanPosition >= SignDataHidingThreshold; |
|||
int contextSet = parameters.GetLevelContextSet(subset, c1 == 0); |
|||
c1 = 1; |
|||
absoluteLevels[..nonZeroCount].Fill(1); |
|||
int greaterThanOneCount = Math.Min(nonZeroCount, GreaterThanOneFlagCount); |
|||
int firstGreaterThanOneIndex = -1; |
|||
|
|||
for (int index = 0; index < greaterThanOneCount; index++) |
|||
{ |
|||
bool greaterThanOne = reader.ReadCoefficientGreaterThanOne(isChroma, (contextSet * 4) + c1); |
|||
if (greaterThanOne) |
|||
{ |
|||
c1 = 0; |
|||
if (firstGreaterThanOneIndex < 0) |
|||
{ |
|||
firstGreaterThanOneIndex = index; |
|||
} |
|||
else |
|||
{ |
|||
escapeDataPresent = true; |
|||
} |
|||
} |
|||
else if (c1 is > 0 and < 3) |
|||
{ |
|||
c1++; |
|||
} |
|||
|
|||
absoluteLevels[index] = greaterThanOne ? 2 : 1; |
|||
} |
|||
|
|||
if (c1 == 0 && firstGreaterThanOneIndex >= 0) |
|||
{ |
|||
bool greaterThanTwo = reader.ReadCoefficientGreaterThanTwo(isChroma, contextSet); |
|||
absoluteLevels[firstGreaterThanOneIndex] = greaterThanTwo ? 3 : 2; |
|||
escapeDataPresent |= greaterThanTwo; |
|||
} |
|||
|
|||
escapeDataPresent |= nonZeroCount > GreaterThanOneFlagCount; |
|||
if (escapeDataPresent && parameters.CabacBypassAlignmentEnabled) |
|||
{ |
|||
reader.AlignBypass(); |
|||
} |
|||
|
|||
int signCount = hideSign && parameters.SignDataHidingEnabled ? nonZeroCount - 1 : nonZeroCount; |
|||
uint coefficientSigns = reader.ReadBypassBits(signCount); |
|||
int nextSignBit = signCount - 1; |
|||
int firstCoefficientAtLeastTwo = 1; |
|||
if (escapeDataPresent) |
|||
{ |
|||
for (int index = 0; index < nonZeroCount; index++) |
|||
{ |
|||
int baseLevel = index < GreaterThanOneFlagCount ? 2 + firstCoefficientAtLeastTwo : 1; |
|||
if (absoluteLevels[index] == baseLevel) |
|||
{ |
|||
uint remainder = reader.ReadCoefficientRemaining( |
|||
riceParameter, |
|||
parameters.ExtendedPrecisionProcessingEnabled, |
|||
parameters.MaximumLog2TransformDynamicRange); |
|||
|
|||
ulong decodedLevel = (ulong)remainder + (uint)baseLevel; |
|||
if (decodedLevel > int.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC transform coefficient level is too large."); |
|||
} |
|||
|
|||
absoluteLevels[index] = (int)decodedLevel; |
|||
if (decodedLevel > (3UL << riceParameter)) |
|||
{ |
|||
riceParameter = parameters.PersistentRiceAdaptationEnabled ? riceParameter + 1 : Math.Min(riceParameter + 1, 4); |
|||
} |
|||
|
|||
if (updateRiceStatistic) |
|||
{ |
|||
int initialRiceParameter = currentRiceStatistic / RiceAdaptationDivisor; |
|||
if (remainder >= (3UL << initialRiceParameter)) |
|||
{ |
|||
currentRiceStatistic++; |
|||
} |
|||
else if (((ulong)remainder * 2) < (1UL << initialRiceParameter) && currentRiceStatistic > 0) |
|||
{ |
|||
currentRiceStatistic--; |
|||
} |
|||
|
|||
// Only the first escape value in a coefficient group updates persistent state.
|
|||
updateRiceStatistic = false; |
|||
} |
|||
} |
|||
|
|||
if (absoluteLevels[index] >= 2) |
|||
{ |
|||
firstCoefficientAtLeastTwo = 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
int absoluteSum = 0; |
|||
for (int index = 0; index < nonZeroCount; index++) |
|||
{ |
|||
int rasterPosition = positions[index]; |
|||
int level = absoluteLevels[index]; |
|||
absoluteSum += level; |
|||
if (index == nonZeroCount - 1 && hideSign && parameters.SignDataHidingEnabled) |
|||
{ |
|||
level = (absoluteSum & 1) == 0 ? level : -level; |
|||
} |
|||
else if (((coefficientSigns >> nextSignBit--) & 1U) != 0) |
|||
{ |
|||
level = -level; |
|||
} |
|||
|
|||
coefficients[rasterPosition] = level; |
|||
} |
|||
|
|||
totalNonZero += nonZeroCount; |
|||
} |
|||
|
|||
return totalNonZero; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Releases the allocator-owned coefficient scratch storage.
|
|||
/// </summary>
|
|||
public void Dispose() => this.scratchOwner.Dispose(); |
|||
|
|||
/// <summary>
|
|||
/// Decodes the raster coordinates of the final significant coefficient.
|
|||
/// </summary>
|
|||
/// <param name="reader">The current entropy-substream syntax reader.</param>
|
|||
/// <param name="parameters">The transform-block coefficient coding parameters.</param>
|
|||
/// <param name="x">The decoded horizontal coordinate.</param>
|
|||
/// <param name="y">The decoded vertical coordinate.</param>
|
|||
private static void ReadLastSignificantPosition( |
|||
ref HevcCabacSyntaxReader reader, |
|||
in HevcCoefficientCodingParameters parameters, |
|||
out int x, |
|||
out int y) |
|||
{ |
|||
bool verticalScan = parameters.ScanType == HevcCoefficientScanType.Vertical; |
|||
int syntaxWidth = verticalScan ? parameters.Height : parameters.Width; |
|||
int syntaxHeight = verticalScan ? parameters.Width : parameters.Height; |
|||
bool isChroma = parameters.Plane != HevcPlane.Y; |
|||
x = ReadLastSignificantCoordinate(ref reader, isChroma, syntaxWidth, true); |
|||
y = ReadLastSignificantCoordinate(ref reader, isChroma, syntaxHeight, false); |
|||
if (verticalScan) |
|||
{ |
|||
(x, y) = (y, x); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes one last-significant coefficient coordinate from its context prefix and bypass suffix.
|
|||
/// </summary>
|
|||
/// <param name="reader">The current entropy-substream syntax reader.</param>
|
|||
/// <param name="isChroma">Whether the coordinate belongs to a chroma transform block.</param>
|
|||
/// <param name="size">The transform-block extent along the coded axis.</param>
|
|||
/// <param name="horizontal">Whether to use the horizontal rather than vertical context set.</param>
|
|||
/// <returns>The decoded zero-based coefficient coordinate.</returns>
|
|||
private static int ReadLastSignificantCoordinate(ref HevcCabacSyntaxReader reader, bool isChroma, int size, bool horizontal) |
|||
{ |
|||
int convertedSize = BitOperations.Log2((uint)size) - 2; |
|||
int contextOffset = isChroma ? 0 : (convertedSize * 3) + ((convertedSize + 1) >> 2); |
|||
int contextShift = isChroma ? convertedSize : (convertedSize + 3) >> 2; |
|||
int maximumPrefix = CoordinateGroupIndex[size - 1]; |
|||
int prefix; |
|||
for (prefix = 0; prefix < maximumPrefix; prefix++) |
|||
{ |
|||
int contextIndex = contextOffset + (prefix >> contextShift); |
|||
bool prefixContinues = horizontal |
|||
? reader.ReadLastSignificantX(isChroma, contextIndex) |
|||
: reader.ReadLastSignificantY(isChroma, contextIndex); |
|||
|
|||
if (!prefixContinues) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (prefix <= 3) |
|||
{ |
|||
return prefix; |
|||
} |
|||
|
|||
int suffixLength = (prefix - 2) >> 1; |
|||
return MinimumCoordinateInGroup[prefix] + (int)reader.ReadBypassBits(suffixLength); |
|||
} |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Writes the grouped coefficient scan used by HEVC residual entropy coding.
|
|||
/// </summary>
|
|||
internal static class HevcCoefficientScanOrder |
|||
{ |
|||
/// <summary>
|
|||
/// The width and height of one coefficient group.
|
|||
/// </summary>
|
|||
private const int CoefficientGroupSize = 4; |
|||
|
|||
/// <summary>
|
|||
/// Writes the grouped scan for one transform block into caller-owned storage.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination receiving raster coefficient indices in scan order.</param>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="scanType">The scan direction selected for the transform block.</param>
|
|||
/// <param name="lastRasterPosition">The raster index of the last significant coefficient.</param>
|
|||
/// <returns>The scan position of <paramref name="lastRasterPosition"/>.</returns>
|
|||
public static int Write(Span<int> destination, int width, int height, HevcCoefficientScanType scanType, int lastRasterPosition) |
|||
{ |
|||
int widthInGroups = width / CoefficientGroupSize; |
|||
int heightInGroups = height / CoefficientGroupSize; |
|||
int groupCount = widthInGroups * heightInGroups; |
|||
int lastScanPosition = -1; |
|||
ScanGenerator groupScan = new(widthInGroups, heightInGroups, scanType); |
|||
|
|||
// H.265 scans the 4x4 groups first, then applies the same direction inside each group. Keeping this grouped
|
|||
// layout contiguous lets coefficient decoding walk every 16-entry subset without lookup-table allocations.
|
|||
for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) |
|||
{ |
|||
int groupOffsetX = groupScan.X * CoefficientGroupSize; |
|||
int groupOffsetY = groupScan.Y * CoefficientGroupSize; |
|||
int groupScanOffset = groupIndex * CoefficientGroupSize * CoefficientGroupSize; |
|||
ScanGenerator coefficientScan = new(CoefficientGroupSize, CoefficientGroupSize, scanType); |
|||
|
|||
for (int coefficientIndex = 0; coefficientIndex < CoefficientGroupSize * CoefficientGroupSize; coefficientIndex++) |
|||
{ |
|||
int rasterPosition = ((groupOffsetY + coefficientScan.Y) * width) + groupOffsetX + coefficientScan.X; |
|||
int scanPosition = groupScanOffset + coefficientIndex; |
|||
destination[scanPosition] = rasterPosition; |
|||
if (rasterPosition == lastRasterPosition) |
|||
{ |
|||
lastScanPosition = scanPosition; |
|||
} |
|||
|
|||
coefficientScan.MoveNext(); |
|||
} |
|||
|
|||
groupScan.MoveNext(); |
|||
} |
|||
|
|||
return lastScanPosition; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Advances through one rectangular scan without retaining a heap-backed lookup table.
|
|||
/// </summary>
|
|||
private struct ScanGenerator |
|||
{ |
|||
/// <summary>
|
|||
/// The scan width.
|
|||
/// </summary>
|
|||
private readonly int width; |
|||
|
|||
/// <summary>
|
|||
/// The scan height.
|
|||
/// </summary>
|
|||
private readonly int height; |
|||
|
|||
/// <summary>
|
|||
/// The selected scan direction.
|
|||
/// </summary>
|
|||
private readonly HevcCoefficientScanType scanType; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ScanGenerator"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="width">The scan width.</param>
|
|||
/// <param name="height">The scan height.</param>
|
|||
/// <param name="scanType">The scan direction.</param>
|
|||
public ScanGenerator(int width, int height, HevcCoefficientScanType scanType) |
|||
{ |
|||
this.width = width; |
|||
this.height = height; |
|||
this.scanType = scanType; |
|||
this.X = 0; |
|||
this.Y = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current horizontal coordinate.
|
|||
/// </summary>
|
|||
public int X { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the current vertical coordinate.
|
|||
/// </summary>
|
|||
public int Y { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Advances to the next coordinate in the selected scan direction.
|
|||
/// </summary>
|
|||
public void MoveNext() |
|||
{ |
|||
switch (this.scanType) |
|||
{ |
|||
case HevcCoefficientScanType.Diagonal: |
|||
if (this.X == this.width - 1 || this.Y == 0) |
|||
{ |
|||
this.Y += this.X + 1; |
|||
this.X = 0; |
|||
if (this.Y >= this.height) |
|||
{ |
|||
this.X += this.Y - (this.height - 1); |
|||
this.Y = this.height - 1; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this.X++; |
|||
this.Y--; |
|||
} |
|||
|
|||
break; |
|||
case HevcCoefficientScanType.Horizontal: |
|||
if (++this.X == this.width) |
|||
{ |
|||
this.X = 0; |
|||
this.Y++; |
|||
} |
|||
|
|||
break; |
|||
default: |
|||
if (++this.Y == this.height) |
|||
{ |
|||
this.Y = 0; |
|||
this.X++; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the coefficient scan used by one HEVC transform block.
|
|||
/// </summary>
|
|||
internal enum HevcCoefficientScanType |
|||
{ |
|||
/// <summary>
|
|||
/// The up-right diagonal scan.
|
|||
/// </summary>
|
|||
Diagonal, |
|||
|
|||
/// <summary>
|
|||
/// The row-major horizontal scan.
|
|||
/// </summary>
|
|||
Horizontal, |
|||
|
|||
/// <summary>
|
|||
/// The column-major vertical scan.
|
|||
/// </summary>
|
|||
Vertical, |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Provides the HEVC intra-mode values and chroma-format mapping shared by entropy decoding and reconstruction.
|
|||
/// </summary>
|
|||
internal static class HevcIntraPredictionMode |
|||
{ |
|||
/// <summary>
|
|||
/// The horizontal angular prediction mode.
|
|||
/// </summary>
|
|||
public const int Horizontal = 10; |
|||
|
|||
/// <summary>
|
|||
/// The vertical angular prediction mode.
|
|||
/// </summary>
|
|||
public const int Vertical = 26; |
|||
|
|||
/// <summary>
|
|||
/// Gets the 4:2:2 chroma intra-angle remapping defined by H.265 Table 8-4.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> Chroma422AngleMap => |
|||
[ |
|||
0, 1, 2, 2, 2, 2, 3, 5, 7, 8, 10, 12, 13, 15, 17, 18, 19, 20, 21, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 28, 29, 29, 30, 31, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Maps a coded chroma intra mode to the angular mode used by a 4:2:2 chroma block.
|
|||
/// </summary>
|
|||
/// <param name="mode">The effective coded chroma intra mode.</param>
|
|||
/// <returns>The prediction angle used by the rectangular chroma block.</returns>
|
|||
public static int RemapChroma422(int mode) => Chroma422AngleMap[mode]; |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif; |
|||
|
|||
/// <summary>
|
|||
/// Measures full-frame HEVC grouped coefficient-scan generation into reusable transform scratch.
|
|||
/// </summary>
|
|||
[MemoryDiagnoser(displayGenColumns: false)] |
|||
public class HevcCoefficientScanBenchmarks |
|||
{ |
|||
/// <summary>
|
|||
/// The coded frame width, which is an exact multiple of the maximum transform-block side.
|
|||
/// </summary>
|
|||
private const int Width = 1920; |
|||
|
|||
/// <summary>
|
|||
/// The coded frame height including the final padded coding-tree row for a 1080-line presentation.
|
|||
/// </summary>
|
|||
private const int Height = 1088; |
|||
|
|||
/// <summary>
|
|||
/// The maximum transform-block side.
|
|||
/// </summary>
|
|||
private const int BlockSize = 32; |
|||
|
|||
/// <summary>
|
|||
/// The reusable maximum-size grouped scan destination.
|
|||
/// </summary>
|
|||
private readonly int[] scan = new int[BlockSize * BlockSize]; |
|||
|
|||
/// <summary>
|
|||
/// Measures diagonal scan generation for every maximum-size transform block in one coded full-HD frame.
|
|||
/// </summary>
|
|||
/// <returns>The final last-significant scan position, keeping the generated scan observable.</returns>
|
|||
[Benchmark] |
|||
public int WriteDiagonalFrame() |
|||
{ |
|||
int lastScanPosition = 0; |
|||
for (int y = 0; y < Height; y += BlockSize) |
|||
{ |
|||
for (int x = 0; x < Width; x += BlockSize) |
|||
{ |
|||
lastScanPosition = HevcCoefficientScanOrder.Write( |
|||
this.scan, |
|||
BlockSize, |
|||
BlockSize, |
|||
HevcCoefficientScanType.Diagonal, |
|||
(BlockSize * BlockSize) - 1); |
|||
} |
|||
} |
|||
|
|||
return lastScanPosition; |
|||
} |
|||
} |
|||
@ -0,0 +1,287 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
using SixLabors.ImageSharp.Tests.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Verifies HEVC coefficient scan selection, grouped ordering, and CABAC level reconstruction.
|
|||
/// </summary>
|
|||
public class HevcCoefficientDecoderTests |
|||
{ |
|||
/// <summary>
|
|||
/// Verifies the three normative four-by-four coefficient scans.
|
|||
/// </summary>
|
|||
/// <param name="scanType">The scan direction under test.</param>
|
|||
/// <param name="expected">The expected raster indices in scan order.</param>
|
|||
[Theory] |
|||
[MemberData(nameof(GetFourByFourScans))] |
|||
public void WritesFourByFourScan(int scanType, int[] expected) |
|||
{ |
|||
int[] actual = new int[16]; |
|||
|
|||
int lastScanPosition = HevcCoefficientScanOrder.Write(actual, 4, 4, (HevcCoefficientScanType)scanType, expected[^1]); |
|||
|
|||
Assert.Equal(expected, actual); |
|||
Assert.Equal(15, lastScanPosition); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies that grouped scans visit each coefficient exactly once for every supported transform geometry.
|
|||
/// </summary>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="scanType">The scan direction under test.</param>
|
|||
[Theory] |
|||
[InlineData(4, 4, HevcCoefficientScanType.Diagonal)] |
|||
[InlineData(8, 8, HevcCoefficientScanType.Diagonal)] |
|||
[InlineData(8, 4, HevcCoefficientScanType.Horizontal)] |
|||
[InlineData(4, 8, HevcCoefficientScanType.Vertical)] |
|||
[InlineData(16, 32, HevcCoefficientScanType.Diagonal)] |
|||
[InlineData(32, 16, HevcCoefficientScanType.Horizontal)] |
|||
[InlineData(32, 32, HevcCoefficientScanType.Vertical)] |
|||
public void GroupedScanVisitsEveryCoefficient(int width, int height, int scanType) |
|||
{ |
|||
int coefficientCount = width * height; |
|||
int[] scan = new int[coefficientCount]; |
|||
bool[] visited = new bool[coefficientCount]; |
|||
|
|||
int lastScanPosition = HevcCoefficientScanOrder.Write(scan, width, height, (HevcCoefficientScanType)scanType, coefficientCount - 1); |
|||
|
|||
Assert.InRange(lastScanPosition, 0, coefficientCount - 1); |
|||
foreach (int rasterPosition in scan) |
|||
{ |
|||
Assert.InRange(rasterPosition, 0, coefficientCount - 1); |
|||
Assert.False(visited[rasterPosition]); |
|||
visited[rasterPosition] = true; |
|||
} |
|||
|
|||
Assert.All(visited, Assert.True); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies that an eight-by-eight diagonal scan groups coefficients in the normative group order.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void DiagonalEightByEightScanUsesGroupedOrder() |
|||
{ |
|||
int[] scan = new int[64]; |
|||
|
|||
HevcCoefficientScanOrder.Write(scan, 8, 8, HevcCoefficientScanType.Diagonal, 63); |
|||
|
|||
Assert.Equal(0, scan[0]); |
|||
Assert.Equal(32, scan[16]); |
|||
Assert.Equal(4, scan[32]); |
|||
Assert.Equal(36, scan[48]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies transform geometry and intra direction select horizontal, vertical, or diagonal scans.
|
|||
/// </summary>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="plane">The reconstructed component.</param>
|
|||
/// <param name="isIntra">Whether the containing coding unit uses intra prediction.</param>
|
|||
/// <param name="mode">The effective intra mode.</param>
|
|||
/// <param name="chromaFormat">The sequence chroma format.</param>
|
|||
/// <param name="expected">The expected coefficient scan.</param>
|
|||
[Theory] |
|||
[InlineData(8, 8, HevcPlane.Y, false, 26, 3, HevcCoefficientScanType.Diagonal)] |
|||
[InlineData(8, 8, HevcPlane.Y, true, 26, 3, HevcCoefficientScanType.Horizontal)] |
|||
[InlineData(8, 8, HevcPlane.Y, true, 10, 3, HevcCoefficientScanType.Vertical)] |
|||
[InlineData(8, 8, HevcPlane.Y, true, 18, 3, HevcCoefficientScanType.Diagonal)] |
|||
[InlineData(16, 16, HevcPlane.Y, true, 26, 3, HevcCoefficientScanType.Diagonal)] |
|||
[InlineData(8, 8, HevcPlane.Cb, true, 26, 1, HevcCoefficientScanType.Diagonal)] |
|||
[InlineData(4, 4, HevcPlane.Cb, true, 26, 1, HevcCoefficientScanType.Horizontal)] |
|||
public void SelectsScanType( |
|||
int width, |
|||
int height, |
|||
int plane, |
|||
bool isIntra, |
|||
int mode, |
|||
byte chromaFormat, |
|||
int expected) |
|||
{ |
|||
HevcCoefficientScanType actual = HevcCoefficientCodingParameters.SelectScanType( |
|||
width, |
|||
height, |
|||
(HevcPlane)plane, |
|||
isIntra, |
|||
mode, |
|||
chromaFormat, |
|||
false); |
|||
|
|||
Assert.Equal((HevcCoefficientScanType)expected, actual); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies the significance-map context bases selected by transform size, scan, channel, and Range Extensions.
|
|||
/// </summary>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="plane">The reconstructed component.</param>
|
|||
/// <param name="scanType">The selected coefficient scan.</param>
|
|||
/// <param name="singleContext">Whether the Range Extensions single-context mode applies.</param>
|
|||
/// <param name="expected">The expected first significance-map context.</param>
|
|||
[Theory] |
|||
[InlineData(4, 4, HevcPlane.Y, HevcCoefficientScanType.Diagonal, false, 0)] |
|||
[InlineData(8, 8, HevcPlane.Y, HevcCoefficientScanType.Diagonal, false, 9)] |
|||
[InlineData(8, 8, HevcPlane.Y, HevcCoefficientScanType.Horizontal, false, 15)] |
|||
[InlineData(16, 16, HevcPlane.Y, HevcCoefficientScanType.Diagonal, false, 21)] |
|||
[InlineData(8, 8, HevcPlane.Cb, HevcCoefficientScanType.Vertical, false, 9)] |
|||
[InlineData(16, 16, HevcPlane.Cr, HevcCoefficientScanType.Diagonal, false, 12)] |
|||
[InlineData(4, 4, HevcPlane.Y, HevcCoefficientScanType.Diagonal, true, 27)] |
|||
[InlineData(4, 4, HevcPlane.Cb, HevcCoefficientScanType.Diagonal, true, 15)] |
|||
public void SelectsFirstSignificanceContext( |
|||
int width, |
|||
int height, |
|||
int plane, |
|||
int scanType, |
|||
bool singleContext, |
|||
int expected) |
|||
{ |
|||
HevcCoefficientCodingParameters parameters = CreateParameters( |
|||
width, |
|||
height, |
|||
(HevcPlane)plane, |
|||
(HevcCoefficientScanType)scanType, |
|||
singleContext); |
|||
|
|||
Assert.Equal(expected, parameters.FirstSignificanceMapContext); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies the complete four-by-four raster-position context mapping.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void FourByFourSignificanceContextsMatchNormativeMap() |
|||
{ |
|||
int[] expected = [0, 1, 4, 5, 2, 3, 4, 5, 6, 6, 8, 8, 7, 7, 8, 8]; |
|||
HevcCoefficientCodingParameters parameters = CreateParameters(4, 4, HevcPlane.Y, HevcCoefficientScanType.Diagonal, false); |
|||
|
|||
for (int rasterPosition = 0; rasterPosition < expected.Length; rasterPosition++) |
|||
{ |
|||
Assert.Equal(expected[rasterPosition], parameters.GetSignificantCoefficientContext(rasterPosition, 0)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies significant-group and coefficient contexts use already decoded right and lower groups.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void SignificanceContextsUseRightAndLowerGroups() |
|||
{ |
|||
int[] groupFlags = [0, 1, 1, 0]; |
|||
HevcCoefficientCodingParameters luma = CreateParameters(8, 8, HevcPlane.Y, HevcCoefficientScanType.Diagonal, false); |
|||
HevcCoefficientCodingParameters chroma = CreateParameters(8, 8, HevcPlane.Cb, HevcCoefficientScanType.Diagonal, false); |
|||
|
|||
Assert.Equal(1, luma.GetSignificantGroupContext(groupFlags, 0, 0)); |
|||
Assert.Equal(3, luma.GetSignificancePattern(groupFlags, 0, 0)); |
|||
Assert.Equal(0, luma.GetSignificantGroupContext(groupFlags, 1, 0)); |
|||
Assert.Equal(0, luma.GetSignificancePattern(groupFlags, 1, 0)); |
|||
Assert.Equal(0, luma.GetSignificantCoefficientContext(0, 3)); |
|||
Assert.Equal(11, luma.GetSignificantCoefficientContext(1, 3)); |
|||
Assert.Equal(14, luma.GetSignificantCoefficientContext(4, 0)); |
|||
Assert.Equal(11, chroma.GetSignificantCoefficientContext(4, 0)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies luma and chroma level-context sets track subset position and preceding greater-than-one state.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void SelectsLevelContextSets() |
|||
{ |
|||
HevcCoefficientCodingParameters luma = CreateParameters(8, 8, HevcPlane.Y, HevcCoefficientScanType.Diagonal, false); |
|||
HevcCoefficientCodingParameters chroma = CreateParameters(8, 8, HevcPlane.Cb, HevcCoefficientScanType.Diagonal, false); |
|||
|
|||
Assert.Equal(0, luma.GetLevelContextSet(0, false)); |
|||
Assert.Equal(1, luma.GetLevelContextSet(0, true)); |
|||
Assert.Equal(2, luma.GetLevelContextSet(1, false)); |
|||
Assert.Equal(3, luma.GetLevelContextSet(1, true)); |
|||
Assert.Equal(0, chroma.GetLevelContextSet(0, false)); |
|||
Assert.Equal(1, chroma.GetLevelContextSet(3, true)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies a fixed CABAC substream reconstructs one positive DC coefficient without allocating per block.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void DecodesPositiveDcCoefficientWithReusableScratch() |
|||
{ |
|||
TestMemoryAllocator allocator = new(); |
|||
allocator.EnableNonThreadSafeLogging(); |
|||
Configuration configuration = new() { MemoryAllocator = allocator }; |
|||
using HevcCoefficientDecoder decoder = new(configuration); |
|||
int[] coefficients = new int[16]; |
|||
HevcCoefficientCodingParameters parameters = CreateParameters(4, 4, HevcPlane.Y, HevcCoefficientScanType.Diagonal, false); |
|||
|
|||
HevcCabacSyntaxReader firstReader = new([0xEE, 0x48], 22); |
|||
int firstNonZeroCount = decoder.Decode(ref firstReader, coefficients, in parameters); |
|||
HevcCabacSyntaxReader secondReader = new([0xEE, 0x48], 22); |
|||
int secondNonZeroCount = decoder.Decode(ref secondReader, coefficients, in parameters); |
|||
|
|||
Assert.Equal(1, firstNonZeroCount); |
|||
Assert.Equal(1, secondNonZeroCount); |
|||
Assert.Equal(1, coefficients[0]); |
|||
Assert.All(coefficients[1..], value => Assert.Equal(0, value)); |
|||
Assert.Single(allocator.AllocationLog); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies the bypass-coded sign is applied to a fixed single-coefficient CABAC substream.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void DecodesNegativeDcCoefficient() |
|||
{ |
|||
using HevcCoefficientDecoder decoder = new(Configuration.Default); |
|||
HevcCabacSyntaxReader reader = new([0xF4, 0x24], 22); |
|||
int[] coefficients = new int[16]; |
|||
HevcCoefficientCodingParameters parameters = CreateParameters(4, 4, HevcPlane.Y, HevcCoefficientScanType.Diagonal, false); |
|||
|
|||
int nonZeroCount = decoder.Decode(ref reader, coefficients, in parameters); |
|||
|
|||
Assert.Equal(1, nonZeroCount); |
|||
Assert.Equal(-1, coefficients[0]); |
|||
Assert.All(coefficients[1..], value => Assert.Equal(0, value)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the exact raster order for each four-by-four scan direction.
|
|||
/// </summary>
|
|||
/// <returns>The scan direction and expected raster positions.</returns>
|
|||
public static TheoryData<int, int[]> GetFourByFourScans() => |
|||
new() |
|||
{ |
|||
{ |
|||
(int)HevcCoefficientScanType.Diagonal, |
|||
[0, 4, 1, 8, 5, 2, 12, 9, 6, 3, 13, 10, 7, 14, 11, 15] |
|||
}, |
|||
{ |
|||
(int)HevcCoefficientScanType.Horizontal, |
|||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] |
|||
}, |
|||
{ |
|||
(int)HevcCoefficientScanType.Vertical, |
|||
[0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15] |
|||
}, |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Creates explicit coefficient parameters for scan and entropy tests without requiring a parsed parameter set.
|
|||
/// </summary>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="plane">The reconstructed component.</param>
|
|||
/// <param name="scanType">The coefficient scan.</param>
|
|||
/// <param name="singleContext">Whether the Range Extensions single significance context applies.</param>
|
|||
/// <returns>The coefficient coding parameters.</returns>
|
|||
private static HevcCoefficientCodingParameters CreateParameters( |
|||
int width, |
|||
int height, |
|||
HevcPlane plane, |
|||
HevcCoefficientScanType scanType, |
|||
bool singleContext) |
|||
=> new(width, height, plane, scanType, singleContext, false, false, false, false, 15, plane == HevcPlane.Y ? 0 : 2); |
|||
} |
|||
Loading…
Reference in new issue