mirror of https://github.com/SixLabors/ImageSharp
27 changed files with 2440 additions and 14 deletions
File diff suppressed because it is too large
@ -0,0 +1,169 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using BenchmarkDotNet.Columns; |
||||
|
using BenchmarkDotNet.Configs; |
||||
|
using BenchmarkDotNet.Jobs; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopRestoration; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures normative AV1 self-guided restoration across a full-HD-equivalent luma workload.
|
||||
|
/// </summary>
|
||||
|
[Config(typeof(Configuration))] |
||||
|
[MemoryDiagnoser(displayGenColumns: false)] |
||||
|
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] |
||||
|
[CategoriesColumn] |
||||
|
public class Av1LoopRestorationBenchmarks |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The width of one normative self-guided processing unit.
|
||||
|
/// </summary>
|
||||
|
private const int Width = 64; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The height of one normative self-guided processing unit.
|
||||
|
/// </summary>
|
||||
|
private const int Height = 64; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The three source samples required on each side of a processing unit.
|
||||
|
/// </summary>
|
||||
|
private const int Border = 3; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The number of processing units covering a 1920 by 1080 luma plane.
|
||||
|
/// </summary>
|
||||
|
private const int ProcessingUnitCount = 30 * 17; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The bordered source-row stride.
|
||||
|
/// </summary>
|
||||
|
private const int SourceStride = Width + (Border * 2); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The self-guided parameter set activating both radius-two and radius-one filtering.
|
||||
|
/// </summary>
|
||||
|
private const int ParameterSetIndex = 0; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The deterministic bordered eight-bit source block.
|
||||
|
/// </summary>
|
||||
|
private readonly ushort[] source8 = new ushort[SourceStride * (Height + (Border * 2))]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The deterministic bordered twelve-bit source block.
|
||||
|
/// </summary>
|
||||
|
private readonly ushort[] source12 = new ushort[SourceStride * (Height + (Border * 2))]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The restored processing-unit destination.
|
||||
|
/// </summary>
|
||||
|
private readonly ushort[] destination = new ushort[Width * Height]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The caller-owned self-guided work storage.
|
||||
|
/// </summary>
|
||||
|
private readonly int[] scratch = new int[Av1SelfGuidedFilter.GetScratchLength(Width, Height)]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the two transmitted projection coefficients used by the measured parameter set.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<int> ProjectionCoefficients => [31, -7]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Populates deterministic bordered source blocks outside the measured traversal.
|
||||
|
/// </summary>
|
||||
|
[GlobalSetup] |
||||
|
public void Setup() |
||||
|
{ |
||||
|
for (int row = 0; row < Height + (Border * 2); row++) |
||||
|
{ |
||||
|
for (int column = 0; column < SourceStride; column++) |
||||
|
{ |
||||
|
int sample = ((row * 4051) + (column * 7919) + 127) & byte.MaxValue; |
||||
|
int offset = (row * SourceStride) + column; |
||||
|
this.source8[offset] = (ushort)sample; |
||||
|
this.source12[offset] = (ushort)(sample << 4); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures eight-bit self-guided restoration for a full-HD-equivalent luma plane.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final restored sample, keeping the output observable.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("8Bit")] |
||||
|
public ushort Restore8BitPlane() |
||||
|
{ |
||||
|
for (int unit = 0; unit < ProcessingUnitCount; unit++) |
||||
|
{ |
||||
|
Av1SelfGuidedFilter.FilterBlock( |
||||
|
this.source8, |
||||
|
SourceStride, |
||||
|
this.destination, |
||||
|
Width, |
||||
|
Width, |
||||
|
Height, |
||||
|
8, |
||||
|
ParameterSetIndex, |
||||
|
ProjectionCoefficients, |
||||
|
this.scratch); |
||||
|
} |
||||
|
|
||||
|
return this.destination[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Measures twelve-bit self-guided restoration for a full-HD-equivalent luma plane.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The final restored sample, keeping the output observable.</returns>
|
||||
|
[Benchmark] |
||||
|
[BenchmarkCategory("12Bit")] |
||||
|
public ushort Restore12BitPlane() |
||||
|
{ |
||||
|
for (int unit = 0; unit < ProcessingUnitCount; unit++) |
||||
|
{ |
||||
|
Av1SelfGuidedFilter.FilterBlock( |
||||
|
this.source12, |
||||
|
SourceStride, |
||||
|
this.destination, |
||||
|
Width, |
||||
|
Width, |
||||
|
Height, |
||||
|
12, |
||||
|
ParameterSetIndex, |
||||
|
ProjectionCoefficients, |
||||
|
this.scratch); |
||||
|
} |
||||
|
|
||||
|
return this.destination[^1]; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Configures production-process measurements for hardware, 128-bit, and scalar filtering.
|
||||
|
/// </summary>
|
||||
|
public sealed class Configuration : ManualConfig |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Configuration"/> class.
|
||||
|
/// </summary>
|
||||
|
public Configuration() |
||||
|
{ |
||||
|
this.AddJob(Job.ShortRun.WithId("Hardware").AsBaseline()); |
||||
|
|
||||
|
this.AddJob( |
||||
|
Job.ShortRun |
||||
|
.WithId("Vector128") |
||||
|
.WithEnvironmentVariable("DOTNET_EnableAVX", "0")); |
||||
|
|
||||
|
this.AddJob( |
||||
|
Job.ShortRun |
||||
|
.WithId("Scalar") |
||||
|
.WithEnvironmentVariable("DOTNET_EnableHWIntrinsic", "0")); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,426 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopRestoration; |
||||
|
using SixLabors.ImageSharp.Tests.TestUtilities; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies AV1 self-guided restoration against a direct-window definition across the supported hardware-intrinsic configurations.
|
||||
|
/// </summary>
|
||||
|
[Trait("Format", "Heif")] |
||||
|
public class Av1SelfGuidedFilterTests |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The number of source samples required on every side of a filtered processing unit.
|
||||
|
/// </summary>
|
||||
|
private const int Border = 3; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The number of fractional bits retained by each self-guided filter result.
|
||||
|
/// </summary>
|
||||
|
private const int RestorationBits = 4; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The number of fractional bits used by the projection coefficients.
|
||||
|
/// </summary>
|
||||
|
private const int ProjectionBits = 7; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The hardware configurations required to exercise the AVX2 path, portable 128-bit path, and scalar fallback.
|
||||
|
/// </summary>
|
||||
|
private const HwIntrinsics Configurations = |
||||
|
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the radii selected by each of the sixteen normative parameter sets.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<int> ParameterRadii => |
||||
|
[ |
||||
|
2, 1, 2, 1, 2, 1, 2, 1, |
||||
|
2, 1, 2, 1, 2, 1, 2, 1, |
||||
|
2, 1, 2, 1, 0, 1, 0, 1, |
||||
|
0, 1, 0, 1, 2, 0, 2, 0, |
||||
|
]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the variance scales selected by each of the sixteen normative parameter sets.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<int> ParameterScales => |
||||
|
[ |
||||
|
140, 3236, 112, 2158, 93, 1618, 80, 1438, |
||||
|
70, 1295, 58, 1177, 47, 1079, 37, 996, |
||||
|
30, 925, 25, 863, -1, 2589, -1, 1618, |
||||
|
-1, 1177, -1, 925, 56, -1, 22, -1, |
||||
|
]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets processing-unit dimensions covering narrow chroma units, odd frame edges, and both vector remainder widths.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<int> ProcessingUnitDimensions => |
||||
|
[ |
||||
|
1, 1, |
||||
|
3, 5, |
||||
|
7, 4, |
||||
|
13, 9, |
||||
|
29, 6, |
||||
|
]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies every normative parameter set, sample precision, and processing-unit tail against the direct-window definition.
|
||||
|
/// </summary>
|
||||
|
[Fact] |
||||
|
public void FilterMatchesDirectWindowDefinitionAcrossIntrinsicWidths() |
||||
|
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateFilters, Configurations); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Validates the complete self-guided parameter matrix in the active hardware-intrinsic configuration.
|
||||
|
/// </summary>
|
||||
|
private static void ValidateFilters() |
||||
|
{ |
||||
|
foreach (int bitDepth in new[] { 8, 10, 12 }) |
||||
|
{ |
||||
|
int maximumSample = (1 << bitDepth) - 1; |
||||
|
ReadOnlySpan<int> processingUnitDimensions = ProcessingUnitDimensions; |
||||
|
for (int dimensionIndex = 0; dimensionIndex < processingUnitDimensions.Length; dimensionIndex += 2) |
||||
|
{ |
||||
|
int width = processingUnitDimensions[dimensionIndex]; |
||||
|
int height = processingUnitDimensions[dimensionIndex + 1]; |
||||
|
int sourceStride = width + (Border * 2) + 5; |
||||
|
int destinationStride = width + 7; |
||||
|
ushort[] source = new ushort[sourceStride * (height + (Border * 2))]; |
||||
|
ushort[] expected = new ushort[destinationStride * height]; |
||||
|
ushort[] actual = new ushort[destinationStride * height]; |
||||
|
int[] scratch = new int[Av1SelfGuidedFilter.GetScratchLength(width, height)]; |
||||
|
int[] projectionCoefficients = new int[2]; |
||||
|
|
||||
|
FillSource(source, sourceStride, maximumSample); |
||||
|
|
||||
|
for (int parameterSetIndex = 0; parameterSetIndex < 16; parameterSetIndex++) |
||||
|
{ |
||||
|
expected.AsSpan().Fill(ushort.MaxValue); |
||||
|
actual.AsSpan().Fill(ushort.MaxValue); |
||||
|
projectionCoefficients[0] = -96 + ((parameterSetIndex * 17) & 127); |
||||
|
projectionCoefficients[1] = -32 + ((parameterSetIndex * 29) & 127); |
||||
|
|
||||
|
FilterReference( |
||||
|
source, |
||||
|
sourceStride, |
||||
|
expected, |
||||
|
destinationStride, |
||||
|
width, |
||||
|
height, |
||||
|
bitDepth, |
||||
|
parameterSetIndex, |
||||
|
projectionCoefficients); |
||||
|
|
||||
|
Av1SelfGuidedFilter.FilterBlock( |
||||
|
source, |
||||
|
sourceStride, |
||||
|
actual, |
||||
|
destinationStride, |
||||
|
width, |
||||
|
height, |
||||
|
bitDepth, |
||||
|
parameterSetIndex, |
||||
|
projectionCoefficients, |
||||
|
scratch); |
||||
|
|
||||
|
AssertBlockEqual(expected, actual, destinationStride, width, height, bitDepth, parameterSetIndex); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Populates the bordered source with deterministic values spanning the selected sample precision.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The complete bordered source storage.</param>
|
||||
|
/// <param name="sourceStride">The number of samples between source rows.</param>
|
||||
|
/// <param name="maximumSample">The largest encoded sample value.</param>
|
||||
|
private static void FillSource(Span<ushort> source, int sourceStride, int maximumSample) |
||||
|
{ |
||||
|
int rowCount = source.Length / sourceStride; |
||||
|
for (int row = 0; row < rowCount; row++) |
||||
|
{ |
||||
|
for (int column = 0; column < sourceStride; column++) |
||||
|
{ |
||||
|
int value = (row * 239) + (column * 101) + (row * column * 17) + (((row + column) & 3) * (maximumSample / 3)); |
||||
|
source[(row * sourceStride) + column] = (ushort)(value & maximumSample); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Exact endpoints make clipping and the full local-variance range observable without depending on random input.
|
||||
|
source[0] = 0; |
||||
|
source[^1] = (ushort)maximumSample; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the normative projection to direct-window self-guided results.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source rectangle beginning three samples above and left of the processing unit.</param>
|
||||
|
/// <param name="sourceStride">The number of samples between source rows.</param>
|
||||
|
/// <param name="destination">The destination storage beginning at the restored processing-unit origin.</param>
|
||||
|
/// <param name="destinationStride">The number of samples between destination rows.</param>
|
||||
|
/// <param name="width">The processing-unit width.</param>
|
||||
|
/// <param name="height">The processing-unit height.</param>
|
||||
|
/// <param name="bitDepth">The encoded sample bit depth.</param>
|
||||
|
/// <param name="parameterSetIndex">The self-guided parameter-set index.</param>
|
||||
|
/// <param name="projectionCoefficients">The two transmitted projection coefficients.</param>
|
||||
|
private static void FilterReference( |
||||
|
ReadOnlySpan<ushort> source, |
||||
|
int sourceStride, |
||||
|
Span<ushort> destination, |
||||
|
int destinationStride, |
||||
|
int width, |
||||
|
int height, |
||||
|
int bitDepth, |
||||
|
int parameterSetIndex, |
||||
|
ReadOnlySpan<int> projectionCoefficients) |
||||
|
{ |
||||
|
int parameterOffset = parameterSetIndex * 2; |
||||
|
int radius0 = ParameterRadii[parameterOffset]; |
||||
|
int radius1 = ParameterRadii[parameterOffset + 1]; |
||||
|
int scale0 = ParameterScales[parameterOffset]; |
||||
|
int scale1 = ParameterScales[parameterOffset + 1]; |
||||
|
int projection0; |
||||
|
int projection1; |
||||
|
|
||||
|
if (radius0 == 0) |
||||
|
{ |
||||
|
projection0 = 0; |
||||
|
projection1 = (1 << ProjectionBits) - projectionCoefficients[1]; |
||||
|
} |
||||
|
else if (radius1 == 0) |
||||
|
{ |
||||
|
projection0 = projectionCoefficients[0]; |
||||
|
projection1 = 0; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
projection0 = projectionCoefficients[0]; |
||||
|
projection1 = (1 << ProjectionBits) - projection0 - projectionCoefficients[1]; |
||||
|
} |
||||
|
|
||||
|
int maximumSample = (1 << bitDepth) - 1; |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
int sourceRowOffset = (row + Border) * sourceStride; |
||||
|
int destinationRowOffset = row * destinationStride; |
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
int unfiltered = source[sourceRowOffset + column + Border] << RestorationBits; |
||||
|
int projected = unfiltered << ProjectionBits; |
||||
|
|
||||
|
if (radius0 > 0) |
||||
|
{ |
||||
|
int filtered0 = CalculateFilteredSample(source, sourceStride, column, row, bitDepth, radius0, scale0); |
||||
|
projected += projection0 * (filtered0 - unfiltered); |
||||
|
} |
||||
|
|
||||
|
if (radius1 > 0) |
||||
|
{ |
||||
|
int filtered1 = CalculateFilteredSample(source, sourceStride, column, row, bitDepth, radius1, scale1); |
||||
|
projected += projection1 * (filtered1 - unfiltered); |
||||
|
} |
||||
|
|
||||
|
destination[destinationRowOffset + column] = |
||||
|
(ushort)Math.Clamp(RoundPowerOfTwo(projected, ProjectionBits + RestorationBits), 0, maximumSample); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates one fixed-point filtered sample directly from its local coefficient windows.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The bordered source rectangle.</param>
|
||||
|
/// <param name="sourceStride">The number of samples between source rows.</param>
|
||||
|
/// <param name="column">The processing-unit column.</param>
|
||||
|
/// <param name="row">The processing-unit row.</param>
|
||||
|
/// <param name="bitDepth">The encoded sample bit depth.</param>
|
||||
|
/// <param name="radius">The selected filter radius.</param>
|
||||
|
/// <param name="scale">The selected variance scale.</param>
|
||||
|
/// <returns>The filtered sample with four fractional bits.</returns>
|
||||
|
private static int CalculateFilteredSample( |
||||
|
ReadOnlySpan<ushort> source, |
||||
|
int sourceStride, |
||||
|
int column, |
||||
|
int row, |
||||
|
int bitDepth, |
||||
|
int radius, |
||||
|
int scale) |
||||
|
{ |
||||
|
int blendFactor = 0; |
||||
|
int localMean = 0; |
||||
|
int roundingBits; |
||||
|
|
||||
|
if (radius == 2 && (row & 1) == 0) |
||||
|
{ |
||||
|
for (int coefficientRow = row - 1; coefficientRow <= row + 1; coefficientRow += 2) |
||||
|
{ |
||||
|
for (int coefficientColumn = column - 1; coefficientColumn <= column + 1; coefficientColumn++) |
||||
|
{ |
||||
|
int weight = coefficientColumn == column ? 6 : 5; |
||||
|
(int localBlendFactor, int localMeanValue) = |
||||
|
CalculateCoefficient(source, sourceStride, coefficientColumn, coefficientRow, bitDepth, radius, scale); |
||||
|
|
||||
|
blendFactor += weight * localBlendFactor; |
||||
|
localMean += weight * localMeanValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
roundingBits = 9; |
||||
|
} |
||||
|
else if (radius == 2) |
||||
|
{ |
||||
|
for (int coefficientColumn = column - 1; coefficientColumn <= column + 1; coefficientColumn++) |
||||
|
{ |
||||
|
int weight = coefficientColumn == column ? 6 : 5; |
||||
|
(int localBlendFactor, int localMeanValue) = |
||||
|
CalculateCoefficient(source, sourceStride, coefficientColumn, row, bitDepth, radius, scale); |
||||
|
|
||||
|
blendFactor += weight * localBlendFactor; |
||||
|
localMean += weight * localMeanValue; |
||||
|
} |
||||
|
|
||||
|
roundingBits = 8; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
for (int coefficientRow = row - 1; coefficientRow <= row + 1; coefficientRow++) |
||||
|
{ |
||||
|
for (int coefficientColumn = column - 1; coefficientColumn <= column + 1; coefficientColumn++) |
||||
|
{ |
||||
|
int weight = coefficientRow == row || coefficientColumn == column ? 4 : 3; |
||||
|
(int localBlendFactor, int localMeanValue) = |
||||
|
CalculateCoefficient(source, sourceStride, coefficientColumn, coefficientRow, bitDepth, radius, scale); |
||||
|
|
||||
|
blendFactor += weight * localBlendFactor; |
||||
|
localMean += weight * localMeanValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
roundingBits = 9; |
||||
|
} |
||||
|
|
||||
|
int sample = source[((row + Border) * sourceStride) + column + Border]; |
||||
|
return RoundPowerOfTwo((blendFactor * sample) + localMean, roundingBits); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates the blend factor and scaled local mean for one coefficient location by visiting every window sample.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The bordered source rectangle.</param>
|
||||
|
/// <param name="sourceStride">The number of samples between source rows.</param>
|
||||
|
/// <param name="column">The coefficient column relative to the processing unit.</param>
|
||||
|
/// <param name="row">The coefficient row relative to the processing unit.</param>
|
||||
|
/// <param name="bitDepth">The encoded sample bit depth.</param>
|
||||
|
/// <param name="radius">The square-window radius.</param>
|
||||
|
/// <param name="scale">The variance scale.</param>
|
||||
|
/// <returns>The local blend factor and scaled mean.</returns>
|
||||
|
private static (int BlendFactor, int LocalMean) CalculateCoefficient( |
||||
|
ReadOnlySpan<ushort> source, |
||||
|
int sourceStride, |
||||
|
int column, |
||||
|
int row, |
||||
|
int bitDepth, |
||||
|
int radius, |
||||
|
int scale) |
||||
|
{ |
||||
|
int centerX = column + Border; |
||||
|
int centerY = row + Border; |
||||
|
int sum = 0; |
||||
|
int squareSum = 0; |
||||
|
|
||||
|
for (int windowY = centerY - radius; windowY <= centerY + radius; windowY++) |
||||
|
{ |
||||
|
int sourceRowOffset = windowY * sourceStride; |
||||
|
for (int windowX = centerX - radius; windowX <= centerX + radius; windowX++) |
||||
|
{ |
||||
|
int sample = source[sourceRowOffset + windowX]; |
||||
|
sum += sample; |
||||
|
squareSum += sample * sample; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int diameter = (radius * 2) + 1; |
||||
|
int windowArea = diameter * diameter; |
||||
|
int normalizedSquareSum = RoundPowerOfTwo(squareSum, 2 * (bitDepth - 8)); |
||||
|
int normalizedSum = RoundPowerOfTwo(sum, bitDepth - 8); |
||||
|
uint squareOfSum = (uint)normalizedSum * (uint)normalizedSum; |
||||
|
uint scaledSquareSum = (uint)normalizedSquareSum * (uint)windowArea; |
||||
|
uint variance = scaledSquareSum < squareOfSum ? 0 : scaledSquareSum - squareOfSum; |
||||
|
uint varianceIndex = Math.Min(RoundPowerOfTwo(variance * (uint)scale, 20), 255U); |
||||
|
|
||||
|
// The endpoint exceptions are part of the normative table. The middle values are the rounded x / (x + 1) ratio in Q8.
|
||||
|
int blendFactor = varianceIndex switch |
||||
|
{ |
||||
|
0 => 1, |
||||
|
255 => 256, |
||||
|
_ => (int)(((varianceIndex << 8) + ((varianceIndex + 1) >> 1)) / (varianceIndex + 1)), |
||||
|
}; |
||||
|
|
||||
|
uint reciprocal = radius == 1 ? 455U : 164U; |
||||
|
uint meanProduct = (uint)(256 - blendFactor) * (uint)sum * reciprocal; |
||||
|
int localMean = (int)RoundPowerOfTwo(meanProduct, 12); |
||||
|
return (blendFactor, localMean); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Verifies visible samples and confirms that the filter does not overwrite destination-row padding.
|
||||
|
/// </summary>
|
||||
|
/// <param name="expected">The direct-window output.</param>
|
||||
|
/// <param name="actual">The production output.</param>
|
||||
|
/// <param name="stride">The number of samples between destination rows.</param>
|
||||
|
/// <param name="width">The processing-unit width.</param>
|
||||
|
/// <param name="height">The processing-unit height.</param>
|
||||
|
/// <param name="bitDepth">The encoded sample bit depth.</param>
|
||||
|
/// <param name="parameterSetIndex">The self-guided parameter-set index.</param>
|
||||
|
private static void AssertBlockEqual( |
||||
|
ReadOnlySpan<ushort> expected, |
||||
|
ReadOnlySpan<ushort> actual, |
||||
|
int stride, |
||||
|
int width, |
||||
|
int height, |
||||
|
int bitDepth, |
||||
|
int parameterSetIndex) |
||||
|
{ |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
int rowOffset = row * stride; |
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
if (expected[rowOffset + column] != actual[rowOffset + column]) |
||||
|
{ |
||||
|
Assert.Fail( |
||||
|
$"Self-guided parameter {parameterSetIndex}, {bitDepth}-bit block differs at ({column}, {row}): " |
||||
|
+ $"expected {expected[rowOffset + column]}, actual {actual[rowOffset + column]}."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (int column = width; column < stride; column++) |
||||
|
{ |
||||
|
Assert.Equal(ushort.MaxValue, actual[rowOffset + column]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Rounds a signed fixed-point value to the requested lower precision.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The signed fixed-point value.</param>
|
||||
|
/// <param name="bitCount">The number of low bits to discard.</param>
|
||||
|
/// <returns>The rounded signed value.</returns>
|
||||
|
private static int RoundPowerOfTwo(int value, int bitCount) |
||||
|
=> bitCount == 0 ? value : (value + (1 << (bitCount - 1))) >> bitCount; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Rounds an unsigned fixed-point value to the requested lower precision.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The unsigned fixed-point value.</param>
|
||||
|
/// <param name="bitCount">The number of low bits to discard.</param>
|
||||
|
/// <returns>The rounded unsigned value.</returns>
|
||||
|
private static uint RoundPowerOfTwo(uint value, int bitCount) |
||||
|
=> bitCount == 0 ? value : (value + (1U << (bitCount - 1))) >> bitCount; |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:609d2dbdee3660b466b846b2269a8c061665ae7d52737bcf6c0004094c88dc80 |
||||
|
size 2629632 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:cbdd03657e8e21ae26769a1e1e59920dba779c47cd1d03b18425148310f10a0f |
||||
|
size 26746 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:61971cbe96f1cfc86b42b0608b91bebc7cc67bf758acf7cfdf7e17cd36f66beb |
||||
|
size 2629632 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:7444a3f87497ed68cad2f5af2363b80381523f976a982de0fa1f58db8533b23b |
||||
|
size 23182 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:6305858ad4204af4df42ac4bb207e94e1a7f155d97241e2cb335cb46db8d6925 |
||||
|
size 589824 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:0e605f0783daf8f6c530fe73572468b20f9834e7a8699a3e4e0f846741306711 |
||||
|
size 16460 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:86771fc87add9f179f327c0c9d44ae9ee0b373f977c23585e8bd0c322a95a215 |
||||
|
size 524288 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:f3c3d6ed68a69a0a689b333ac71f016b5f60cbd48941faf0dcec707f8e2d6c9c |
||||
|
size 1131 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:84666f72dd19ae549b7c5b77b07063e3a52c6a7e7d8798be126c0cdfcc8af5e4 |
||||
|
size 2629632 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:bed0130a163d9ad7c3068f6964c3944871d66da5efd2f37e435467183dba2402 |
||||
|
size 16125 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:5c6e3642a399f59b0aea75261a69f0f31e6fae07998631831b17b7338efa3d9e |
||||
|
size 589824 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:f7b5ec685eb17237354dd7c7d08dddbe41bf408e5eb1c0fb697ceb4a0dec81e4 |
||||
|
size 12322 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:d3215c84d33f1cb37f9c8998ed4c1982837b9207c7a3018cac0df1659691ed8b |
||||
|
size 27021 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:24ff2fe2ab28f044945df7b35d4ed9213ae7e054e33b45fb92e4924566f3e944 |
||||
|
size 454939 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:fc446537fb7954656379384232857a23c5eab25187e63352097d276d8034869e |
||||
|
size 23453 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:4835eabb73ca6e51db7f6779a76499a9f2d07274116b816487681a51883f074f |
||||
|
size 436999 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:fb86fc23e29c19566600fa8e5230038598c37836971feff35976b3f720fd5df0 |
||||
|
size 16735 |
||||
@ -0,0 +1,3 @@ |
|||||
|
version https://git-lfs.github.com/spec/v1 |
||||
|
oid sha256:2098b78efb22b27473802e4e9e074d4948eda6d6bb06510069c9497e63f37bf5 |
||||
|
size 383051 |
||||
Loading…
Reference in new issue