mirror of https://github.com/SixLabors/ImageSharp
9 changed files with 429 additions and 147 deletions
@ -1,11 +1,90 @@ |
|||||
// Copyright (c) Six Labors.
|
// Copyright (c) Six Labors.
|
||||
// Licensed under the Six Labors Split License.
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter; |
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Defines the per-frame state boundary used while applying the AV1 deblocking loop filter.
|
/// Stores the transform-size map consumed by the AV1 deblocking loop filter.
|
||||
/// </summary>
|
/// </summary>
|
||||
internal class Av1LoopFilterContext |
internal class Av1LoopFilterContext |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Stores luma and shared-chroma transform sizes at plane-relative 4x4 granularity.
|
||||
|
/// </summary>
|
||||
|
private readonly Av1TransformSize[][] transformSizes = new Av1TransformSize[2][]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores the row stride of each transform-size map.
|
||||
|
/// </summary>
|
||||
|
private readonly int[] transformSizeStrides = new int[2]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Av1LoopFilterContext"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="sequenceHeader">The sequence header defining aligned frame and chroma dimensions.</param>
|
||||
|
public Av1LoopFilterContext(ObuSequenceHeader sequenceHeader) |
||||
|
{ |
||||
|
int alignedModeInfoWidth = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, sequenceHeader.SuperblockSizeLog2) >> |
||||
|
Av1Constants.ModeInfoSizeLog2; |
||||
|
|
||||
|
int alignedModeInfoHeight = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameHeight, sequenceHeader.SuperblockSizeLog2) >> |
||||
|
Av1Constants.ModeInfoSizeLog2; |
||||
|
|
||||
|
this.transformSizeStrides[(int)Av1PlaneType.Y] = alignedModeInfoWidth; |
||||
|
this.transformSizes[(int)Av1PlaneType.Y] = new Av1TransformSize[alignedModeInfoWidth * alignedModeInfoHeight]; |
||||
|
|
||||
|
if (!sequenceHeader.ColorConfig.IsMonochrome) |
||||
|
{ |
||||
|
int subX = sequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; |
||||
|
int subY = sequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; |
||||
|
int chromaWidth = Av1Math.DivideLog2Ceiling(alignedModeInfoWidth, subX); |
||||
|
int chromaHeight = Av1Math.DivideLog2Ceiling(alignedModeInfoHeight, subY); |
||||
|
|
||||
|
this.transformSizeStrides[(int)Av1PlaneType.Uv] = chromaWidth; |
||||
|
this.transformSizes[(int)Av1PlaneType.Uv] = new Av1TransformSize[chromaWidth * chromaHeight]; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.transformSizes[(int)Av1PlaneType.Uv] = []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores a transform size across every 4x4 position covered by one transform block.
|
||||
|
/// </summary>
|
||||
|
/// <param name="plane">The luma or chroma plane.</param>
|
||||
|
/// <param name="position">The transform origin in plane-relative 4x4 units.</param>
|
||||
|
/// <param name="transformSize">The transform size.</param>
|
||||
|
public void SetTransformSize(Av1Plane plane, Point position, Av1TransformSize transformSize) |
||||
|
{ |
||||
|
int planeType = Math.Min((int)plane, (int)Av1PlaneType.Uv); |
||||
|
Span<Av1TransformSize> transformSizeMap = this.transformSizes[planeType]; |
||||
|
int stride = this.transformSizeStrides[planeType]; |
||||
|
int width = transformSize.Get4x4WideCount(); |
||||
|
int height = transformSize.Get4x4HighCount(); |
||||
|
|
||||
|
// Loop filtering addresses every covered 4x4 position, not only the transform origin. Replication keeps
|
||||
|
// edge lookup independent of the transform traversal order used while reconstructing the coded block.
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
transformSizeMap.Slice(((position.Y + y) * stride) + position.X, width).Fill(transformSize); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the transform size covering a plane-relative 4x4 position.
|
||||
|
/// </summary>
|
||||
|
/// <param name="plane">The luma or chroma plane.</param>
|
||||
|
/// <param name="position">The position in plane-relative 4x4 units.</param>
|
||||
|
/// <returns>The transform size covering the position.</returns>
|
||||
|
public Av1TransformSize GetTransformSize(Av1Plane plane, Point position) |
||||
|
{ |
||||
|
int planeType = Math.Min((int)plane, (int)Av1PlaneType.Uv); |
||||
|
int stride = this.transformSizeStrides[planeType]; |
||||
|
return this.transformSizes[planeType][(position.Y * stride) + position.X]; |
||||
|
} |
||||
} |
} |
||||
|
|||||
@ -1,114 +1,329 @@ |
|||||
// Copyright (c) Six Labors.
|
// Copyright (c) Six Labors.
|
||||
// Licensed under the Six Labors Split License.
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.InteropServices; |
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter; |
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Provides frame traversal for the AV1 in-loop deblocking stage.
|
/// Applies the AV1 in-loop deblocking stage to a reconstructed still-image frame.
|
||||
/// </summary>
|
/// </summary>
|
||||
internal class Av1LoopFilterDecoder |
internal class Av1LoopFilterDecoder |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// The sequence-level superblock configuration.
|
/// The sequence-level superblock and color configuration.
|
||||
/// </summary>
|
/// </summary>
|
||||
private readonly ObuSequenceHeader sequenceHeader; |
private readonly ObuSequenceHeader sequenceHeader; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The frame dimensions and loop-filter parameters.
|
/// The frame dimensions, segmentation state, and loop-filter parameters.
|
||||
/// </summary>
|
/// </summary>
|
||||
private readonly ObuFrameHeader frameHeader; |
private readonly ObuFrameHeader frameHeader; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The decoded block-mode information addressed by superblock origin.
|
/// The decoded mode and superblock delta information.
|
||||
/// </summary>
|
/// </summary>
|
||||
private readonly Av1FrameInfo frameInfo; |
private readonly Av1FrameInfo frameInfo; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The reconstructed plane samples supplied to in-loop filtering.
|
/// The reconstructed plane samples modified by deblocking.
|
||||
/// </summary>
|
/// </summary>
|
||||
private readonly Av1FrameBuffer<byte> frameBuffer; |
private readonly Av1FrameBuffer<byte> frameBuffer; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The per-frame filter context retained across superblocks.
|
/// The per-plane transform-size map populated during reconstruction.
|
||||
/// </summary>
|
/// </summary>
|
||||
private readonly Av1LoopFilterContext loopFilterContext; |
private readonly Av1LoopFilterContext loopFilterContext; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Av1LoopFilterDecoder"/> class.
|
/// Initializes a new instance of the <see cref="Av1LoopFilterDecoder"/> class.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="sequenceHeader">The sequence header that supplies the superblock size.</param>
|
/// <param name="sequenceHeader">The sequence header defining superblock size and color layout.</param>
|
||||
/// <param name="frameHeader">The frame header that supplies dimensions and filter parameters.</param>
|
/// <param name="frameHeader">The frame header defining dimensions and filter parameters.</param>
|
||||
/// <param name="frameInfo">The decoded block-mode information for the frame.</param>
|
/// <param name="frameInfo">The decoded block-mode and superblock delta information.</param>
|
||||
/// <param name="frameBuffer">The reconstructed frame samples to filter.</param>
|
/// <param name="frameBuffer">The reconstructed frame samples to filter.</param>
|
||||
public Av1LoopFilterDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer<byte> frameBuffer) |
/// <param name="loopFilterContext">The transform-size map populated during reconstruction.</param>
|
||||
|
public Av1LoopFilterDecoder( |
||||
|
ObuSequenceHeader sequenceHeader, |
||||
|
ObuFrameHeader frameHeader, |
||||
|
Av1FrameInfo frameInfo, |
||||
|
Av1FrameBuffer<byte> frameBuffer, |
||||
|
Av1LoopFilterContext loopFilterContext) |
||||
{ |
{ |
||||
this.sequenceHeader = sequenceHeader; |
this.sequenceHeader = sequenceHeader; |
||||
this.frameHeader = frameHeader; |
this.frameHeader = frameHeader; |
||||
this.frameInfo = frameInfo; |
this.frameInfo = frameInfo; |
||||
this.frameBuffer = frameBuffer; |
this.frameBuffer = frameBuffer; |
||||
this.loopFilterContext = new(); |
this.loopFilterContext = loopFilterContext; |
||||
} |
} |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Traverses each superblock in raster order when loop filtering is enabled for the decode pass.
|
/// Filters every enabled plane, processing all vertical boundaries before horizontal boundaries.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="doLoopFilterFlag">Whether the frame should run the deblocking stage.</param>
|
public void DecodeFrame() |
||||
/// <exception cref="NotImplementedException">The superblock filtering operation has not been implemented.</exception>
|
|
||||
public void DecodeFrame(bool doLoopFilterFlag) |
|
||||
{ |
{ |
||||
Guard.NotNull(this.sequenceHeader); |
ObuLoopFilterParameters filterParameters = this.frameHeader.LoopFilterParameters; |
||||
Guard.NotNull(this.frameHeader); |
if (filterParameters.FilterLevel[0] == 0 && filterParameters.FilterLevel[1] == 0) |
||||
Guard.NotNull(this.frameInfo); |
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
ObuColorConfig colorConfig = this.sequenceHeader.ColorConfig; |
||||
|
for (int planeIndex = 0; planeIndex < colorConfig.PlaneCount; planeIndex++) |
||||
|
{ |
||||
|
Av1Plane plane = (Av1Plane)planeIndex; |
||||
|
int planeFilterLevel = plane switch |
||||
|
{ |
||||
|
Av1Plane.U => filterParameters.FilterLevelU, |
||||
|
Av1Plane.V => filterParameters.FilterLevelV, |
||||
|
_ => Math.Max(filterParameters.FilterLevel[0], filterParameters.FilterLevel[1]) |
||||
|
}; |
||||
|
|
||||
|
if (planeFilterLevel == 0) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
int subX = plane != Av1Plane.Y && colorConfig.SubSamplingX ? 1 : 0; |
||||
|
int subY = plane != Av1Plane.Y && colorConfig.SubSamplingY ? 1 : 0; |
||||
|
Span<byte> lowBitDepthSamples = default; |
||||
|
Span<ushort> highBitDepthSamples = default; |
||||
|
int stride; |
||||
|
|
||||
|
if (this.frameBuffer.BytesPerSample == 2) |
||||
|
{ |
||||
|
Span<short> signedSamples = this.frameBuffer.DeriveBlockPointer16(plane, Point.Empty, subX, subY, out stride); |
||||
|
highBitDepthSamples = MemoryMarshal.Cast<short, ushort>(signedSamples); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
lowBitDepthSamples = this.frameBuffer.DeriveBlockPointer(plane, Point.Empty, subX, subY, out stride); |
||||
|
} |
||||
|
|
||||
|
this.FilterPlane(plane, subX, subY, stride, lowBitDepthSamples, highBitDepthSamples); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Filters one plane in the AV1 vertical-then-horizontal boundary order.
|
||||
|
/// </summary>
|
||||
|
/// <param name="plane">The color plane to filter.</param>
|
||||
|
/// <param name="subX">The horizontal chroma subsampling shift.</param>
|
||||
|
/// <param name="subY">The vertical chroma subsampling shift.</param>
|
||||
|
/// <param name="stride">The plane stride in logical samples.</param>
|
||||
|
/// <param name="lowBitDepthSamples">The low-bit-depth plane storage, when active.</param>
|
||||
|
/// <param name="highBitDepthSamples">The high-bit-depth plane storage, when active.</param>
|
||||
|
private void FilterPlane( |
||||
|
Av1Plane plane, |
||||
|
int subX, |
||||
|
int subY, |
||||
|
int stride, |
||||
|
Span<byte> lowBitDepthSamples, |
||||
|
Span<ushort> highBitDepthSamples) |
||||
|
{ |
||||
|
int rowStep = 1 << subY; |
||||
|
int columnStep = 1 << subX; |
||||
|
|
||||
|
// The AV1 result is independent of ordering within a pass, but vertical filtering must finish before any
|
||||
|
// horizontal filtering begins because the two directions modify intersecting sample neighborhoods.
|
||||
|
for (int pass = 0; pass < 2; pass++) |
||||
|
{ |
||||
|
for (int row = 0; row < this.frameHeader.ModeInfoRowCount; row += rowStep) |
||||
|
{ |
||||
|
for (int column = 0; column < this.frameHeader.ModeInfoColumnCount; column += columnStep) |
||||
|
{ |
||||
|
this.FilterEdge( |
||||
|
plane, |
||||
|
pass, |
||||
|
row, |
||||
|
column, |
||||
|
subX, |
||||
|
subY, |
||||
|
stride, |
||||
|
lowBitDepthSamples, |
||||
|
highBitDepthSamples); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Derives and applies the filter for one 4x4 luma-grid boundary.
|
||||
|
/// </summary>
|
||||
|
/// <param name="plane">The color plane to filter.</param>
|
||||
|
/// <param name="pass">Zero for a vertical boundary; one for a horizontal boundary.</param>
|
||||
|
/// <param name="row">The boundary row in luma 4x4 units.</param>
|
||||
|
/// <param name="column">The boundary column in luma 4x4 units.</param>
|
||||
|
/// <param name="subX">The horizontal chroma subsampling shift.</param>
|
||||
|
/// <param name="subY">The vertical chroma subsampling shift.</param>
|
||||
|
/// <param name="stride">The plane stride in logical samples.</param>
|
||||
|
/// <param name="lowBitDepthSamples">The low-bit-depth plane storage, when active.</param>
|
||||
|
/// <param name="highBitDepthSamples">The high-bit-depth plane storage, when active.</param>
|
||||
|
private void FilterEdge( |
||||
|
Av1Plane plane, |
||||
|
int pass, |
||||
|
int row, |
||||
|
int column, |
||||
|
int subX, |
||||
|
int subY, |
||||
|
int stride, |
||||
|
Span<byte> lowBitDepthSamples, |
||||
|
Span<ushort> highBitDepthSamples) |
||||
|
{ |
||||
|
int x = column << Av1Constants.ModeInfoSizeLog2; |
||||
|
int y = row << Av1Constants.ModeInfoSizeLog2; |
||||
|
bool verticalBoundary = pass == 0; |
||||
|
if (x >= this.frameHeader.FrameSize.FrameWidth || |
||||
|
y >= this.frameHeader.FrameSize.FrameHeight || |
||||
|
(verticalBoundary ? x == 0 : y == 0)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
int adjustedRow = row | subY; |
||||
|
int adjustedColumn = column | subX; |
||||
|
int previousRow = adjustedRow - (verticalBoundary ? 0 : 1 << subY); |
||||
|
int previousColumn = adjustedColumn - (verticalBoundary ? 1 << subX : 0); |
||||
|
Point modeInfoPosition = new(adjustedColumn, adjustedRow); |
||||
|
Point previousModeInfoPosition = new(previousColumn, previousRow); |
||||
|
Point planeTransformPosition = new(adjustedColumn >> subX, adjustedRow >> subY); |
||||
|
Point previousPlaneTransformPosition = new(previousColumn >> subX, previousRow >> subY); |
||||
|
Av1BlockModeInfo modeInfo = this.frameInfo.GetModeInfoAt(modeInfoPosition); |
||||
|
Av1TransformSize transformSize = this.loopFilterContext.GetTransformSize(plane, planeTransformPosition); |
||||
|
Av1TransformSize previousTransformSize = this.loopFilterContext.GetTransformSize(plane, previousPlaneTransformPosition); |
||||
|
Av1BlockSize planeBlockSize = modeInfo.BlockSize.GetSubsampled(subX, subY); |
||||
|
int planeX = x >> subX; |
||||
|
int planeY = y >> subY; |
||||
|
bool isBlockEdge = verticalBoundary |
||||
|
? planeX % planeBlockSize.GetWidth() == 0 |
||||
|
: planeY % planeBlockSize.GetHeight() == 0; |
||||
|
|
||||
|
bool isTransformEdge = verticalBoundary |
||||
|
? planeX % transformSize.GetWidth() == 0 |
||||
|
: planeY % transformSize.GetHeight() == 0; |
||||
|
|
||||
if (!doLoopFilterFlag) |
// The still-image decoder accepts key and intra-only frames, so every decoded block satisfies the AV1
|
||||
|
// isIntra condition. Retaining the other predicates mirrors the normative edge decision without inter state.
|
||||
|
bool applyFilter = isTransformEdge && (isBlockEdge || !modeInfo.Skip || this.frameHeader.IsIntra); |
||||
|
if (!applyFilter) |
||||
{ |
{ |
||||
return; |
return; |
||||
} |
} |
||||
|
|
||||
int superblockSizeLog2 = this.sequenceHeader.SuperblockSizeLog2; |
int currentLevel = this.GetFilterLevel(modeInfo, modeInfoPosition, plane, pass); |
||||
int frameWidthInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameWidth, this.sequenceHeader.SuperblockSizeLog2); |
int filterLevel = currentLevel != 0 |
||||
int frameHeightInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameHeight, this.sequenceHeader.SuperblockSizeLog2); |
? currentLevel |
||||
|
: this.GetFilterLevel(this.frameInfo.GetModeInfoAt(previousModeInfoPosition), previousModeInfoPosition, plane, pass); |
||||
|
|
||||
// Filtering proceeds in raster order because vertical and horizontal edges depend on already reconstructed
|
if (filterLevel == 0) |
||||
// neighboring blocks, while the final superblock in each row requires distinct delayed-edge handling.
|
|
||||
for (int superblockIndexY = 0; superblockIndexY < frameHeightInSuperblocks; ++superblockIndexY) |
|
||||
{ |
{ |
||||
for (int superblockIndexX = 0; superblockIndexX < frameWidthInSuperblocks; ++superblockIndexX) |
return; |
||||
|
} |
||||
|
|
||||
|
int baseFilterSize = verticalBoundary |
||||
|
? Math.Min(transformSize.GetWidth(), previousTransformSize.GetWidth()) |
||||
|
: Math.Min(transformSize.GetHeight(), previousTransformSize.GetHeight()); |
||||
|
|
||||
|
int maximumFilterSize = plane == Av1Plane.Y ? 16 : 8; |
||||
|
int filterSize = Math.Min(maximumFilterSize, baseFilterSize); |
||||
|
int kernelLength = plane == Av1Plane.Y |
||||
|
? filterSize switch |
||||
{ |
{ |
||||
int superblockOriginX = superblockIndexX << superblockSizeLog2; |
4 => 4, |
||||
int superblockOriginY = superblockIndexY << superblockSizeLog2; |
8 => 8, |
||||
bool endOfRowFlag = superblockIndexX == frameWidthInSuperblocks - 1; |
_ => 14 |
||||
|
|
||||
Point superblockPoint = new(superblockOriginX, superblockOriginY); |
|
||||
Av1SuperblockInfo superblockInfo = this.frameInfo.GetSuperblock(superblockPoint); |
|
||||
|
|
||||
// Mode-info coordinates are measured in 4x4 units, whereas the frame and superblock origins are pixels.
|
|
||||
Point superblockOriginInModeInfo = new(superblockOriginX >> 2, superblockOriginY >> 2); |
|
||||
|
|
||||
this.DecodeForSuperblock( |
|
||||
superblockInfo, |
|
||||
superblockOriginInModeInfo, |
|
||||
Av1Plane.Y, |
|
||||
3, |
|
||||
endOfRowFlag, |
|
||||
superblockInfo.SuperblockDeltaLoopFilter); |
|
||||
} |
} |
||||
|
: filterSize == 4 ? 4 : 6; |
||||
|
|
||||
|
int sharpness = this.frameHeader.LoopFilterParameters.SharpnessLevel; |
||||
|
int shift = sharpness > 4 ? 2 : sharpness > 0 ? 1 : 0; |
||||
|
int limit = sharpness > 0 |
||||
|
? Av1Math.Clip3(1, 9 - sharpness, filterLevel >> shift) |
||||
|
: Math.Max(1, filterLevel >> shift); |
||||
|
|
||||
|
int boundaryLimit = (2 * (filterLevel + 2)) + limit; |
||||
|
int highEdgeVarianceThreshold = filterLevel >> 4; |
||||
|
int q0Offset = stride + (planeY * stride) + planeX; |
||||
|
int pixelStep = verticalBoundary ? 1 : stride; |
||||
|
int lineStep = verticalBoundary ? stride : 1; |
||||
|
|
||||
|
if (this.frameBuffer.BytesPerSample == 2) |
||||
|
{ |
||||
|
Av1LoopFilterKernels.FilterHighBitDepthEdge( |
||||
|
highBitDepthSamples, |
||||
|
q0Offset, |
||||
|
pixelStep, |
||||
|
lineStep, |
||||
|
kernelLength, |
||||
|
limit, |
||||
|
boundaryLimit, |
||||
|
highEdgeVarianceThreshold, |
||||
|
this.frameBuffer.BitDepth.GetBitCount()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Av1LoopFilterKernels.FilterLowBitDepthEdge( |
||||
|
lowBitDepthSamples, |
||||
|
q0Offset, |
||||
|
pixelStep, |
||||
|
lineStep, |
||||
|
kernelLength, |
||||
|
limit, |
||||
|
boundaryLimit, |
||||
|
highEdgeVarianceThreshold); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Represents the not-yet-implemented deblocking operation for one superblock and plane range.
|
/// Derives the adaptive filter level for one block, plane, and boundary direction.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="superblockInfo">The decoded modes and delta values for the superblock.</param>
|
/// <param name="modeInfo">The decoded mode and segment information.</param>
|
||||
/// <param name="modeInfoLocation">The superblock origin in 4x4 mode-info units.</param>
|
/// <param name="modeInfoPosition">The frame-relative position in luma 4x4 units.</param>
|
||||
/// <param name="startPlane">The first color plane to filter.</param>
|
/// <param name="plane">The color plane.</param>
|
||||
/// <param name="endPlane">The exclusive color-plane index at which filtering stops.</param>
|
/// <param name="pass">Zero for a vertical boundary; one for a horizontal boundary.</param>
|
||||
/// <param name="endOfRowFlag">Whether the superblock is the final block in its raster row.</param>
|
/// <returns>The filter level in the AV1 zero-to-63 domain.</returns>
|
||||
/// <param name="superblockDeltaLoopFilter">The per-superblock loop-filter strength adjustments.</param>
|
private int GetFilterLevel(Av1BlockModeInfo modeInfo, Point modeInfoPosition, Av1Plane plane, int pass) |
||||
/// <exception cref="NotImplementedException">Always thrown because superblock deblocking has not been implemented.</exception>
|
{ |
||||
private void DecodeForSuperblock(Av1SuperblockInfo superblockInfo, Point modeInfoLocation, Av1Plane startPlane, int endPlane, bool endOfRowFlag, Span<int> superblockDeltaLoopFilter) |
int filterIndex = plane == Av1Plane.Y ? pass : (int)plane + 1; |
||||
=> throw new NotImplementedException(); |
ObuLoopFilterParameters parameters = this.frameHeader.LoopFilterParameters; |
||||
|
int baseLevel = filterIndex switch |
||||
|
{ |
||||
|
0 => parameters.FilterLevel[0], |
||||
|
1 => parameters.FilterLevel[1], |
||||
|
2 => parameters.FilterLevelU, |
||||
|
_ => parameters.FilterLevelV |
||||
|
}; |
||||
|
|
||||
|
int superblockShift = this.sequenceHeader.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2; |
||||
|
Point superblockPosition = new(modeInfoPosition.X >> superblockShift, modeInfoPosition.Y >> superblockShift); |
||||
|
Span<int> deltaLoopFilter = this.frameInfo.GetSuperblock(superblockPosition).SuperblockDeltaLoopFilter; |
||||
|
int delta = this.frameHeader.DeltaLoopFilterParameters.IsMulti ? deltaLoopFilter[filterIndex] : deltaLoopFilter[0]; |
||||
|
int level = Av1Math.Clip3(0, Av1Constants.MaxLoopFilter, baseLevel + delta); |
||||
|
ObuSegmentationLevelFeature feature = (ObuSegmentationLevelFeature)((int)ObuSegmentationLevelFeature.AlternativeLoopFilterYVertical + filterIndex); |
||||
|
ObuSegmentationParameters segmentation = this.frameHeader.SegmentationParameters; |
||||
|
|
||||
|
if (segmentation.IsFeatureActive(modeInfo.SegmentId, feature)) |
||||
|
{ |
||||
|
level = Av1Math.Clip3( |
||||
|
0, |
||||
|
Av1Constants.MaxLoopFilter, |
||||
|
level + segmentation.FeatureData[modeInfo.SegmentId, (int)feature]); |
||||
|
} |
||||
|
|
||||
|
if (parameters.ReferenceDeltaModeEnabled) |
||||
|
{ |
||||
|
// Every supported AVIF still-picture block uses INTRA_FRAME, whose reference delta is index zero and
|
||||
|
// whose prediction mode does not consume either inter mode delta.
|
||||
|
int referenceScale = 1 << (level >> 5); |
||||
|
level = Av1Math.Clip3( |
||||
|
0, |
||||
|
Av1Constants.MaxLoopFilter, |
||||
|
level + (parameters.ReferenceDeltas[0] * referenceScale)); |
||||
|
} |
||||
|
|
||||
|
return level; |
||||
|
} |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue