mirror of https://github.com/SixLabors/ImageSharp
297 changed files with 281 additions and 26058 deletions
File diff suppressed because it is too large
@ -1,151 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Components; |
|||
|
|||
/// <content>
|
|||
/// Provides pinned-libheif high-bit-depth coefficient conversion at the source RGB precision.
|
|||
/// </content>
|
|||
internal static partial class HeifYuvToRgb16Converter |
|||
{ |
|||
/// <summary>
|
|||
/// Implements pinned-libheif coefficient conversion for scalar and SIMD lanes.
|
|||
/// </summary>
|
|||
private readonly struct LibheifCoefficientOperator : IHeifYuvToRgb16Operator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
Vector512<int> y, |
|||
Vector512<int> cb, |
|||
Vector512<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector512<int> r, |
|||
out Vector512<int> g, |
|||
out Vector512<int> b) |
|||
{ |
|||
Vector512Parameters values = parameters.SixteenLane; |
|||
Vector512<float> luma = (Vector512.ConvertToSingle(y) - values.LumaOffset) * values.LumaScale; |
|||
Vector512<float> blueDifference = (Vector512.ConvertToSingle(cb) - values.ChromaMidpoint) * values.ChromaScale; |
|||
Vector512<float> redDifference = (Vector512.ConvertToSingle(cr) - values.ChromaMidpoint) * values.ChromaScale; |
|||
Vector512<float> half = Vector512.Create(0.5F); |
|||
|
|||
// libheif evaluates these as distinct float32 multiplies and adds; FMA changes some 12-bit results by one.
|
|||
Vector512<float> redValue = Vector512.Multiply(values.RedCr, redDifference); |
|||
redValue = Vector512.Add(luma, redValue); |
|||
Vector512<float> greenValue = Vector512.Multiply(values.GreenCb, blueDifference); |
|||
greenValue = Vector512.Add(luma, greenValue); |
|||
Vector512<float> greenRedValue = Vector512.Multiply(values.GreenCr, redDifference); |
|||
greenValue = Vector512.Add(greenValue, greenRedValue); |
|||
Vector512<float> blueValue = Vector512.Multiply(values.BlueCb, blueDifference); |
|||
blueValue = Vector512.Add(luma, blueValue); |
|||
Vector512<int> red = Vector512.ConvertToInt32(Vector512.Truncate(Vector512.Add(redValue, half))); |
|||
Vector512<int> green = Vector512.ConvertToInt32(Vector512.Truncate(Vector512.Add(greenValue, half))); |
|||
Vector512<int> blue = Vector512.ConvertToInt32(Vector512.Truncate(Vector512.Add(blueValue, half))); |
|||
|
|||
r = Vector512.Clamp(red, default, values.Maximum); |
|||
g = Vector512.Clamp(green, default, values.Maximum); |
|||
b = Vector512.Clamp(blue, default, values.Maximum); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
Vector256<int> y, |
|||
Vector256<int> cb, |
|||
Vector256<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector256<int> r, |
|||
out Vector256<int> g, |
|||
out Vector256<int> b) |
|||
{ |
|||
Vector256Parameters values = parameters.EightLane; |
|||
Vector256<float> luma = (Vector256.ConvertToSingle(y) - values.LumaOffset) * values.LumaScale; |
|||
Vector256<float> blueDifference = (Vector256.ConvertToSingle(cb) - values.ChromaMidpoint) * values.ChromaScale; |
|||
Vector256<float> redDifference = (Vector256.ConvertToSingle(cr) - values.ChromaMidpoint) * values.ChromaScale; |
|||
Vector256<float> half = Vector256.Create(0.5F); |
|||
Vector256<float> redValue = Vector256.Multiply(values.RedCr, redDifference); |
|||
redValue = Vector256.Add(luma, redValue); |
|||
Vector256<float> greenValue = Vector256.Multiply(values.GreenCb, blueDifference); |
|||
greenValue = Vector256.Add(luma, greenValue); |
|||
Vector256<float> greenRedValue = Vector256.Multiply(values.GreenCr, redDifference); |
|||
greenValue = Vector256.Add(greenValue, greenRedValue); |
|||
Vector256<float> blueValue = Vector256.Multiply(values.BlueCb, blueDifference); |
|||
blueValue = Vector256.Add(luma, blueValue); |
|||
Vector256<int> red = Vector256.ConvertToInt32(Vector256.Truncate(Vector256.Add(redValue, half))); |
|||
Vector256<int> green = Vector256.ConvertToInt32(Vector256.Truncate(Vector256.Add(greenValue, half))); |
|||
Vector256<int> blue = Vector256.ConvertToInt32(Vector256.Truncate(Vector256.Add(blueValue, half))); |
|||
|
|||
r = Vector256.Clamp(red, default, values.Maximum); |
|||
g = Vector256.Clamp(green, default, values.Maximum); |
|||
b = Vector256.Clamp(blue, default, values.Maximum); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
Vector128<int> y, |
|||
Vector128<int> cb, |
|||
Vector128<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector128<int> r, |
|||
out Vector128<int> g, |
|||
out Vector128<int> b) |
|||
{ |
|||
Vector128Parameters values = parameters.FourLane; |
|||
Vector128<float> luma = (Vector128.ConvertToSingle(y) - values.LumaOffset) * values.LumaScale; |
|||
Vector128<float> blueDifference = (Vector128.ConvertToSingle(cb) - values.ChromaMidpoint) * values.ChromaScale; |
|||
Vector128<float> redDifference = (Vector128.ConvertToSingle(cr) - values.ChromaMidpoint) * values.ChromaScale; |
|||
Vector128<float> half = Vector128.Create(0.5F); |
|||
Vector128<float> redValue = Vector128.Multiply(values.RedCr, redDifference); |
|||
redValue = Vector128.Add(luma, redValue); |
|||
Vector128<float> greenValue = Vector128.Multiply(values.GreenCb, blueDifference); |
|||
greenValue = Vector128.Add(luma, greenValue); |
|||
Vector128<float> greenRedValue = Vector128.Multiply(values.GreenCr, redDifference); |
|||
greenValue = Vector128.Add(greenValue, greenRedValue); |
|||
Vector128<float> blueValue = Vector128.Multiply(values.BlueCb, blueDifference); |
|||
blueValue = Vector128.Add(luma, blueValue); |
|||
Vector128<int> red = Vector128.ConvertToInt32(Vector128.Truncate(Vector128.Add(redValue, half))); |
|||
Vector128<int> green = Vector128.ConvertToInt32(Vector128.Truncate(Vector128.Add(greenValue, half))); |
|||
Vector128<int> blue = Vector128.ConvertToInt32(Vector128.Truncate(Vector128.Add(blueValue, half))); |
|||
|
|||
r = Vector128.Clamp(red, default, values.Maximum); |
|||
g = Vector128.Clamp(green, default, values.Maximum); |
|||
b = Vector128.Clamp(blue, default, values.Maximum); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
ushort y, |
|||
ushort cb, |
|||
ushort cr, |
|||
in ConversionParameters parameters, |
|||
out int r, |
|||
out int g, |
|||
out int b) |
|||
{ |
|||
ScalarParameters values = parameters.Scalar; |
|||
float luma = (y - values.LumaOffset) * values.LumaScale; |
|||
float blueDifference = (cb - values.ChromaMidpoint) * values.ChromaScale; |
|||
float redDifference = (cr - values.ChromaMidpoint) * values.ChromaScale; |
|||
|
|||
// Keep each assignment separate so the JIT cannot fuse the reference float32 operations.
|
|||
float redValue = values.RedCr * redDifference; |
|||
redValue = luma + redValue; |
|||
float greenValue = values.GreenCb * blueDifference; |
|||
greenValue = luma + greenValue; |
|||
float greenRedValue = values.GreenCr * redDifference; |
|||
greenValue += greenRedValue; |
|||
float blueValue = values.BlueCb * blueDifference; |
|||
blueValue = luma + blueValue; |
|||
|
|||
r = Numerics.Clamp((int)(redValue + 0.5F), 0, values.Maximum); |
|||
g = Numerics.Clamp((int)(greenValue + 0.5F), 0, values.Maximum); |
|||
b = Numerics.Clamp((int)(blueValue + 0.5F), 0, values.Maximum); |
|||
} |
|||
} |
|||
} |
|||
@ -1,83 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Components; |
|||
|
|||
/// <content>
|
|||
/// Provides pinned-libheif high-bit-depth monochrome presentation without luma-range expansion.
|
|||
/// </content>
|
|||
internal static partial class HeifYuvToRgb16Converter |
|||
{ |
|||
/// <summary>
|
|||
/// Copies source-precision luma into each source-precision RGB component.
|
|||
/// </summary>
|
|||
private readonly struct LibheifMonochromeOperator : IHeifYuvToRgb16Operator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
Vector512<int> y, |
|||
Vector512<int> cb, |
|||
Vector512<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector512<int> r, |
|||
out Vector512<int> g, |
|||
out Vector512<int> b) |
|||
{ |
|||
r = y; |
|||
g = y; |
|||
b = y; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
Vector256<int> y, |
|||
Vector256<int> cb, |
|||
Vector256<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector256<int> r, |
|||
out Vector256<int> g, |
|||
out Vector256<int> b) |
|||
{ |
|||
r = y; |
|||
g = y; |
|||
b = y; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
Vector128<int> y, |
|||
Vector128<int> cb, |
|||
Vector128<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector128<int> r, |
|||
out Vector128<int> g, |
|||
out Vector128<int> b) |
|||
{ |
|||
r = y; |
|||
g = y; |
|||
b = y; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
ushort y, |
|||
ushort cb, |
|||
ushort cr, |
|||
in ConversionParameters parameters, |
|||
out int r, |
|||
out int g, |
|||
out int b) |
|||
{ |
|||
r = y; |
|||
g = y; |
|||
b = y; |
|||
} |
|||
} |
|||
} |
|||
@ -1,293 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Components; |
|||
|
|||
/// <content>
|
|||
/// Defines closed high-bit-depth color operators and nearest-sample row traversal.
|
|||
/// </content>
|
|||
internal static partial class HeifYuvToRgb16Converter |
|||
{ |
|||
/// <summary>
|
|||
/// Defines source-precision RGB arithmetic for scalar and SIMD lanes.
|
|||
/// </summary>
|
|||
private interface IHeifYuvToRgb16Operator |
|||
{ |
|||
/// <summary>
|
|||
/// Converts sixteen YCbCr samples to source-precision RGB lanes.
|
|||
/// </summary>
|
|||
/// <param name="y">The luma lanes.</param>
|
|||
/// <param name="cb">The blue-difference lanes.</param>
|
|||
/// <param name="cr">The red-difference lanes.</param>
|
|||
/// <param name="parameters">The image conversion parameters.</param>
|
|||
/// <param name="r">The converted red lanes.</param>
|
|||
/// <param name="g">The converted green lanes.</param>
|
|||
/// <param name="b">The converted blue lanes.</param>
|
|||
public static abstract void Convert( |
|||
Vector512<int> y, |
|||
Vector512<int> cb, |
|||
Vector512<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector512<int> r, |
|||
out Vector512<int> g, |
|||
out Vector512<int> b); |
|||
|
|||
/// <summary>
|
|||
/// Converts eight YCbCr samples to source-precision RGB lanes.
|
|||
/// </summary>
|
|||
/// <param name="y">The luma lanes.</param>
|
|||
/// <param name="cb">The blue-difference lanes.</param>
|
|||
/// <param name="cr">The red-difference lanes.</param>
|
|||
/// <param name="parameters">The image conversion parameters.</param>
|
|||
/// <param name="r">The converted red lanes.</param>
|
|||
/// <param name="g">The converted green lanes.</param>
|
|||
/// <param name="b">The converted blue lanes.</param>
|
|||
public static abstract void Convert( |
|||
Vector256<int> y, |
|||
Vector256<int> cb, |
|||
Vector256<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector256<int> r, |
|||
out Vector256<int> g, |
|||
out Vector256<int> b); |
|||
|
|||
/// <summary>
|
|||
/// Converts four YCbCr samples to source-precision RGB lanes.
|
|||
/// </summary>
|
|||
/// <param name="y">The luma lanes.</param>
|
|||
/// <param name="cb">The blue-difference lanes.</param>
|
|||
/// <param name="cr">The red-difference lanes.</param>
|
|||
/// <param name="parameters">The image conversion parameters.</param>
|
|||
/// <param name="r">The converted red lanes.</param>
|
|||
/// <param name="g">The converted green lanes.</param>
|
|||
/// <param name="b">The converted blue lanes.</param>
|
|||
public static abstract void Convert( |
|||
Vector128<int> y, |
|||
Vector128<int> cb, |
|||
Vector128<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector128<int> r, |
|||
out Vector128<int> g, |
|||
out Vector128<int> b); |
|||
|
|||
/// <summary>
|
|||
/// Converts one YCbCr sample to source-precision RGB.
|
|||
/// </summary>
|
|||
/// <param name="y">The luma sample.</param>
|
|||
/// <param name="cb">The blue-difference sample.</param>
|
|||
/// <param name="cr">The red-difference sample.</param>
|
|||
/// <param name="parameters">The image conversion parameters.</param>
|
|||
/// <param name="r">The converted red sample.</param>
|
|||
/// <param name="g">The converted green sample.</param>
|
|||
/// <param name="b">The converted blue sample.</param>
|
|||
public static abstract void Convert( |
|||
ushort y, |
|||
ushort cb, |
|||
ushort cr, |
|||
in ConversionParameters parameters, |
|||
out int r, |
|||
out int g, |
|||
out int b); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts one luma row and its nearest native chroma row to planar 16-bit RGB storage.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The source-precision color arithmetic selected for the row.</typeparam>
|
|||
/// <param name="luma">The full-resolution luma samples.</param>
|
|||
/// <param name="chromaBlue">The native blue-difference samples.</param>
|
|||
/// <param name="chromaRed">The native red-difference samples.</param>
|
|||
/// <param name="red">The destination red samples.</param>
|
|||
/// <param name="green">The destination green samples.</param>
|
|||
/// <param name="blue">The destination blue samples.</param>
|
|||
/// <param name="subsamplingX">The horizontal chroma subsampling shift.</param>
|
|||
/// <param name="parameters">The image conversion parameters.</param>
|
|||
private static void ConvertRow<TOperator>( |
|||
ReadOnlySpan<ushort> luma, |
|||
ReadOnlySpan<ushort> chromaBlue, |
|||
ReadOnlySpan<ushort> chromaRed, |
|||
Span<ushort> red, |
|||
Span<ushort> green, |
|||
Span<ushort> blue, |
|||
int subsamplingX, |
|||
in ConversionParameters parameters) |
|||
where TOperator : struct, IHeifYuvToRgb16Operator |
|||
{ |
|||
ref ushort lumaBase = ref MemoryMarshal.GetReference(luma); |
|||
ref ushort chromaBlueBase = ref MemoryMarshal.GetReference(chromaBlue); |
|||
ref ushort chromaRedBase = ref MemoryMarshal.GetReference(chromaRed); |
|||
ref ushort redBase = ref MemoryMarshal.GetReference(red); |
|||
ref ushort greenBase = ref MemoryMarshal.GetReference(green); |
|||
ref ushort blueBase = ref MemoryMarshal.GetReference(blue); |
|||
int outputLeftShift = parameters.Scalar.OutputLeftShift; |
|||
int x = 0; |
|||
|
|||
// Each operator produces code values at the source precision. The traversal then left-aligns those values in
|
|||
// UInt16 storage, matching libheif's high-bit-depth RGB output without discarding low source bits.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = luma.Length - Vector512<int>.Count; |
|||
|
|||
for (; x <= oneVectorFromEnd; x += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> y = LoadVector512(ref Unsafe.Add(ref lumaBase, x)); |
|||
Vector512<int> cb = subsamplingX == 0 |
|||
? LoadVector512(ref Unsafe.Add(ref chromaBlueBase, x)) |
|||
: LoadRepeatedVector512(ref Unsafe.Add(ref chromaBlueBase, x >> 1)); |
|||
|
|||
Vector512<int> cr = subsamplingX == 0 |
|||
? LoadVector512(ref Unsafe.Add(ref chromaRedBase, x)) |
|||
: LoadRepeatedVector512(ref Unsafe.Add(ref chromaRedBase, x >> 1)); |
|||
|
|||
TOperator.Convert(y, cb, cr, in parameters, out Vector512<int> r, out Vector512<int> g, out Vector512<int> b); |
|||
HeifUShortSampleConverter.Store(r << outputLeftShift, ref Unsafe.Add(ref redBase, x)); |
|||
HeifUShortSampleConverter.Store(g << outputLeftShift, ref Unsafe.Add(ref greenBase, x)); |
|||
HeifUShortSampleConverter.Store(b << outputLeftShift, ref Unsafe.Add(ref blueBase, x)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = luma.Length - Vector256<int>.Count; |
|||
|
|||
for (; x <= oneVectorFromEnd; x += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> y = LoadVector256(ref Unsafe.Add(ref lumaBase, x)); |
|||
Vector256<int> cb = subsamplingX == 0 |
|||
? LoadVector256(ref Unsafe.Add(ref chromaBlueBase, x)) |
|||
: LoadRepeatedVector256(ref Unsafe.Add(ref chromaBlueBase, x >> 1)); |
|||
|
|||
Vector256<int> cr = subsamplingX == 0 |
|||
? LoadVector256(ref Unsafe.Add(ref chromaRedBase, x)) |
|||
: LoadRepeatedVector256(ref Unsafe.Add(ref chromaRedBase, x >> 1)); |
|||
|
|||
TOperator.Convert(y, cb, cr, in parameters, out Vector256<int> r, out Vector256<int> g, out Vector256<int> b); |
|||
HeifUShortSampleConverter.Store(r << outputLeftShift, ref Unsafe.Add(ref redBase, x)); |
|||
HeifUShortSampleConverter.Store(g << outputLeftShift, ref Unsafe.Add(ref greenBase, x)); |
|||
HeifUShortSampleConverter.Store(b << outputLeftShift, ref Unsafe.Add(ref blueBase, x)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = luma.Length - Vector128<int>.Count; |
|||
|
|||
for (; x <= oneVectorFromEnd; x += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> y = LoadVector128(ref Unsafe.Add(ref lumaBase, x)); |
|||
Vector128<int> cb = subsamplingX == 0 |
|||
? LoadVector128(ref Unsafe.Add(ref chromaBlueBase, x)) |
|||
: LoadRepeatedVector128(ref Unsafe.Add(ref chromaBlueBase, x >> 1)); |
|||
|
|||
Vector128<int> cr = subsamplingX == 0 |
|||
? LoadVector128(ref Unsafe.Add(ref chromaRedBase, x)) |
|||
: LoadRepeatedVector128(ref Unsafe.Add(ref chromaRedBase, x >> 1)); |
|||
|
|||
TOperator.Convert(y, cb, cr, in parameters, out Vector128<int> r, out Vector128<int> g, out Vector128<int> b); |
|||
HeifUShortSampleConverter.Store(r << outputLeftShift, ref Unsafe.Add(ref redBase, x)); |
|||
HeifUShortSampleConverter.Store(g << outputLeftShift, ref Unsafe.Add(ref greenBase, x)); |
|||
HeifUShortSampleConverter.Store(b << outputLeftShift, ref Unsafe.Add(ref blueBase, x)); |
|||
} |
|||
} |
|||
|
|||
for (; x < luma.Length; x++) |
|||
{ |
|||
TOperator.Convert( |
|||
Unsafe.Add(ref lumaBase, x), |
|||
Unsafe.Add(ref chromaBlueBase, x >> subsamplingX), |
|||
Unsafe.Add(ref chromaRedBase, x >> subsamplingX), |
|||
in parameters, |
|||
out int r, |
|||
out int g, |
|||
out int b); |
|||
|
|||
Unsafe.Add(ref redBase, x) = (ushort)(r << outputLeftShift); |
|||
Unsafe.Add(ref greenBase, x) = (ushort)(g << outputLeftShift); |
|||
Unsafe.Add(ref blueBase, x) = (ushort)(b << outputLeftShift); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads sixteen native samples as signed 32-bit SIMD lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first native sample.</param>
|
|||
/// <returns>The widened sample lanes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> LoadVector512(ref ushort source) |
|||
{ |
|||
(Vector256<uint> lower, Vector256<uint> upper) = Vector256.Widen(Vector256.LoadUnsafe(ref source)); |
|||
return Vector512.Create(lower, upper).AsInt32(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads eight native samples as signed 32-bit SIMD lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first native sample.</param>
|
|||
/// <returns>The widened sample lanes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> LoadVector256(ref ushort source) |
|||
{ |
|||
Vector128<ushort> samples = Vector128.LoadUnsafe(ref source); |
|||
return Vector256.Create(Vector128.WidenLower(samples), Vector128.WidenUpper(samples)).AsInt32(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads four native samples as signed 32-bit SIMD lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first native sample.</param>
|
|||
/// <returns>The widened sample lanes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> LoadVector128(ref ushort source) |
|||
{ |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<ushort, byte>(ref source)); |
|||
return Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsUInt16()).AsInt32(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads eight chroma samples and repeats each sample into two of sixteen 32-bit SIMD lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first native chroma sample.</param>
|
|||
/// <returns>The horizontally replicated chroma lanes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> LoadRepeatedVector512(ref ushort source) |
|||
{ |
|||
Vector128<ushort> samples = Vector128.LoadUnsafe(ref source); |
|||
Vector128<ushort> lower = Vector128_.UnpackLow(samples.AsInt16(), samples.AsInt16()).AsUInt16(); |
|||
Vector128<ushort> upper = Vector128_.UnpackHigh(samples.AsInt16(), samples.AsInt16()).AsUInt16(); |
|||
(Vector256<uint> widenedLower, Vector256<uint> widenedUpper) = Vector256.Widen(Vector256.Create(lower, upper)); |
|||
return Vector512.Create(widenedLower, widenedUpper).AsInt32(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads four chroma samples and repeats each sample into two of eight 32-bit SIMD lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first native chroma sample.</param>
|
|||
/// <returns>The horizontally replicated chroma lanes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> LoadRepeatedVector256(ref ushort source) |
|||
{ |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<ushort, byte>(ref source)); |
|||
Vector128<ushort> samples = Vector128.CreateScalarUnsafe(packed).AsUInt16(); |
|||
Vector128<ushort> repeated = Vector128_.UnpackLow(samples.AsInt16(), samples.AsInt16()).AsUInt16(); |
|||
return Vector256.Create(Vector128.WidenLower(repeated), Vector128.WidenUpper(repeated)).AsInt32(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads two chroma samples and repeats each sample into two of four 32-bit SIMD lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first native chroma sample.</param>
|
|||
/// <returns>The horizontally replicated chroma lanes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> LoadRepeatedVector128(ref ushort source) |
|||
{ |
|||
uint packed = Unsafe.ReadUnaligned<uint>(ref Unsafe.As<ushort, byte>(ref source)); |
|||
Vector128<ushort> samples = Vector128.CreateScalarUnsafe(packed).AsUInt16(); |
|||
Vector128<ushort> repeated = Vector128_.UnpackLow(samples.AsInt16(), samples.AsInt16()).AsUInt16(); |
|||
return Vector128.WidenLower(repeated).AsInt32(); |
|||
} |
|||
} |
|||
@ -1,347 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Cicp; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Components; |
|||
|
|||
/// <content>
|
|||
/// Provides scalar and SIMD parameter storage for high-bit-depth pinned-libheif conversion.
|
|||
/// </content>
|
|||
internal static partial class HeifYuvToRgb16Converter |
|||
{ |
|||
/// <summary>
|
|||
/// Stores every scalar and SIMD coefficient representation resolved once for an image.
|
|||
/// </summary>
|
|||
private readonly struct ConversionParameters |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ConversionParameters"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="parameters">The resolved H.273 matrix and range values.</param>
|
|||
/// <param name="bitDepth">The common source component precision.</param>
|
|||
public ConversionParameters(in HeifColorConversionParameters parameters, int bitDepth) |
|||
{ |
|||
ScalarParameters scalar = new(in parameters, bitDepth); |
|||
this.Scalar = scalar; |
|||
this.SixteenLane = new(in scalar); |
|||
this.EightLane = new(in scalar); |
|||
this.FourLane = new(in scalar); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the scalar conversion parameters.
|
|||
/// </summary>
|
|||
public ScalarParameters Scalar { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the sixteen-lane conversion parameters.
|
|||
/// </summary>
|
|||
public Vector512Parameters SixteenLane { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the eight-lane conversion parameters.
|
|||
/// </summary>
|
|||
public Vector256Parameters EightLane { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the four-lane conversion parameters.
|
|||
/// </summary>
|
|||
public Vector128Parameters FourLane { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores the scalar arithmetic and output scaling used by pinned libheif.
|
|||
/// </summary>
|
|||
private readonly struct ScalarParameters |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ScalarParameters"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="parameters">The resolved H.273 matrix and range values.</param>
|
|||
/// <param name="bitDepth">The common source component precision.</param>
|
|||
public ScalarParameters(in HeifColorConversionParameters parameters, int bitDepth) |
|||
{ |
|||
this.LumaOffset = parameters.IsFullRange ? 0F : parameters.LumaBias; |
|||
this.LumaScale = parameters.IsFullRange ? 1F : 1.1689F; |
|||
this.ChromaMidpoint = parameters.ChromaBias; |
|||
this.ChromaScale = parameters.IsFullRange ? 1F : 1.1429F; |
|||
if (parameters.MatrixCoefficients == CicpMatrixCoefficients.Unspecified) |
|||
{ |
|||
// libheif falls back to these literal Rec.601 coefficients when no matrix is signaled. Deriving them
|
|||
// from Kr and Kb produces different float32 values and can move high-bit-depth green by one code value.
|
|||
this.RedCr = 1.402F; |
|||
this.GreenCb = -0.344136F; |
|||
this.GreenCr = -0.714136F; |
|||
this.BlueCb = 1.772F; |
|||
} |
|||
else |
|||
{ |
|||
float kr = parameters.Kr; |
|||
float kb = parameters.Kb; |
|||
this.RedCr = 2F * (-kr + 1F); |
|||
this.GreenCb = 2F * kb * (-kb + 1F) / (kb + kr - 1F); |
|||
this.GreenCr = 2F * kr * (-kr + 1F) / (kb + kr - 1F); |
|||
this.BlueCb = 2F * (-kb + 1F); |
|||
} |
|||
|
|||
this.Maximum = (1 << bitDepth) - 1; |
|||
this.OutputLeftShift = 16 - bitDepth; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma code-value offset removed before limited-range expansion.
|
|||
/// </summary>
|
|||
public float LumaOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma range-expansion factor.
|
|||
/// </summary>
|
|||
public float LumaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the neutral chroma code value.
|
|||
/// </summary>
|
|||
public float ChromaMidpoint { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma range-expansion factor.
|
|||
/// </summary>
|
|||
public float ChromaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the red contribution from Cr.
|
|||
/// </summary>
|
|||
public float RedCr { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the green contribution from Cb.
|
|||
/// </summary>
|
|||
public float GreenCb { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the green contribution from Cr.
|
|||
/// </summary>
|
|||
public float GreenCr { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue contribution from Cb.
|
|||
/// </summary>
|
|||
public float BlueCb { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the largest source-precision RGB code value.
|
|||
/// </summary>
|
|||
public int Maximum { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the left shift mapping source-precision RGB into 16-bit pixel storage.
|
|||
/// </summary>
|
|||
public int OutputLeftShift { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Broadcasts pinned-libheif coefficients for sixteen-lane conversion.
|
|||
/// </summary>
|
|||
private readonly struct Vector512Parameters |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vector512Parameters"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="parameters">The scalar pinned-libheif coefficients.</param>
|
|||
public Vector512Parameters(in ScalarParameters parameters) |
|||
{ |
|||
this.LumaOffset = Vector512.Create(parameters.LumaOffset); |
|||
this.LumaScale = Vector512.Create(parameters.LumaScale); |
|||
this.ChromaMidpoint = Vector512.Create(parameters.ChromaMidpoint); |
|||
this.ChromaScale = Vector512.Create(parameters.ChromaScale); |
|||
this.RedCr = Vector512.Create(parameters.RedCr); |
|||
this.GreenCb = Vector512.Create(parameters.GreenCb); |
|||
this.GreenCr = Vector512.Create(parameters.GreenCr); |
|||
this.BlueCb = Vector512.Create(parameters.BlueCb); |
|||
this.Maximum = Vector512.Create(parameters.Maximum); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma offset lanes.
|
|||
/// </summary>
|
|||
public Vector512<float> LumaOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma scale lanes.
|
|||
/// </summary>
|
|||
public Vector512<float> LumaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma-midpoint lanes.
|
|||
/// </summary>
|
|||
public Vector512<float> ChromaMidpoint { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma-scale lanes.
|
|||
/// </summary>
|
|||
public Vector512<float> ChromaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the red Cr coefficient lanes.
|
|||
/// </summary>
|
|||
public Vector512<float> RedCr { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the green Cb coefficient lanes.
|
|||
/// </summary>
|
|||
public Vector512<float> GreenCb { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the green Cr coefficient lanes.
|
|||
/// </summary>
|
|||
public Vector512<float> GreenCr { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue Cb coefficient lanes.
|
|||
/// </summary>
|
|||
public Vector512<float> BlueCb { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum source-precision RGB lanes.
|
|||
/// </summary>
|
|||
public Vector512<int> Maximum { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Broadcasts pinned-libheif coefficients for eight-lane conversion.
|
|||
/// </summary>
|
|||
private readonly struct Vector256Parameters |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vector256Parameters"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="parameters">The scalar pinned-libheif coefficients.</param>
|
|||
public Vector256Parameters(in ScalarParameters parameters) |
|||
{ |
|||
this.LumaOffset = Vector256.Create(parameters.LumaOffset); |
|||
this.LumaScale = Vector256.Create(parameters.LumaScale); |
|||
this.ChromaMidpoint = Vector256.Create(parameters.ChromaMidpoint); |
|||
this.ChromaScale = Vector256.Create(parameters.ChromaScale); |
|||
this.RedCr = Vector256.Create(parameters.RedCr); |
|||
this.GreenCb = Vector256.Create(parameters.GreenCb); |
|||
this.GreenCr = Vector256.Create(parameters.GreenCr); |
|||
this.BlueCb = Vector256.Create(parameters.BlueCb); |
|||
this.Maximum = Vector256.Create(parameters.Maximum); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma offset lanes.
|
|||
/// </summary>
|
|||
public Vector256<float> LumaOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma scale lanes.
|
|||
/// </summary>
|
|||
public Vector256<float> LumaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma-midpoint lanes.
|
|||
/// </summary>
|
|||
public Vector256<float> ChromaMidpoint { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma-scale lanes.
|
|||
/// </summary>
|
|||
public Vector256<float> ChromaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the red Cr coefficient lanes.
|
|||
/// </summary>
|
|||
public Vector256<float> RedCr { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the green Cb coefficient lanes.
|
|||
/// </summary>
|
|||
public Vector256<float> GreenCb { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the green Cr coefficient lanes.
|
|||
/// </summary>
|
|||
public Vector256<float> GreenCr { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue Cb coefficient lanes.
|
|||
/// </summary>
|
|||
public Vector256<float> BlueCb { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum source-precision RGB lanes.
|
|||
/// </summary>
|
|||
public Vector256<int> Maximum { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Broadcasts pinned-libheif coefficients for four-lane conversion.
|
|||
/// </summary>
|
|||
private readonly struct Vector128Parameters |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Vector128Parameters"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="parameters">The scalar pinned-libheif coefficients.</param>
|
|||
public Vector128Parameters(in ScalarParameters parameters) |
|||
{ |
|||
this.LumaOffset = Vector128.Create(parameters.LumaOffset); |
|||
this.LumaScale = Vector128.Create(parameters.LumaScale); |
|||
this.ChromaMidpoint = Vector128.Create(parameters.ChromaMidpoint); |
|||
this.ChromaScale = Vector128.Create(parameters.ChromaScale); |
|||
this.RedCr = Vector128.Create(parameters.RedCr); |
|||
this.GreenCb = Vector128.Create(parameters.GreenCb); |
|||
this.GreenCr = Vector128.Create(parameters.GreenCr); |
|||
this.BlueCb = Vector128.Create(parameters.BlueCb); |
|||
this.Maximum = Vector128.Create(parameters.Maximum); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma offset lanes.
|
|||
/// </summary>
|
|||
public Vector128<float> LumaOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma scale lanes.
|
|||
/// </summary>
|
|||
public Vector128<float> LumaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma-midpoint lanes.
|
|||
/// </summary>
|
|||
public Vector128<float> ChromaMidpoint { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma-scale lanes.
|
|||
/// </summary>
|
|||
public Vector128<float> ChromaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the red Cr coefficient lanes.
|
|||
/// </summary>
|
|||
public Vector128<float> RedCr { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the green Cb coefficient lanes.
|
|||
/// </summary>
|
|||
public Vector128<float> GreenCb { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the green Cr coefficient lanes.
|
|||
/// </summary>
|
|||
public Vector128<float> GreenCr { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue Cb coefficient lanes.
|
|||
/// </summary>
|
|||
public Vector128<float> BlueCb { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum source-precision RGB lanes.
|
|||
/// </summary>
|
|||
public Vector128<int> Maximum { get; } |
|||
} |
|||
} |
|||
@ -1,114 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Components; |
|||
|
|||
/// <summary>
|
|||
/// Converts high-bit-depth HEIF YUV planes to packed pixels through opaque 16-bit RGB.
|
|||
/// </summary>
|
|||
internal static partial class HeifYuvToRgb16Converter |
|||
{ |
|||
/// <summary>
|
|||
/// Determines whether the pinned libheif-compatible high-bit-depth conversion supports the supplied planes.
|
|||
/// </summary>
|
|||
/// <param name="subsamplingX">The horizontal chroma subsampling shift.</param>
|
|||
/// <param name="subsamplingY">The vertical chroma subsampling shift.</param>
|
|||
/// <param name="lumaBitDepth">The luma sample precision in bits.</param>
|
|||
/// <param name="chromaBitDepth">The chroma sample precision in bits.</param>
|
|||
/// <param name="isMonochrome">Whether the image contains only luma samples.</param>
|
|||
/// <param name="mode">The resolved H.273 conversion operation.</param>
|
|||
/// <returns><see langword="true"/> when the planes can use this converter; otherwise, <see langword="false"/>.</returns>
|
|||
public static bool SupportsLibheifConversion( |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
int lumaBitDepth, |
|||
int chromaBitDepth, |
|||
bool isMonochrome, |
|||
HeifColorConversionMode mode) |
|||
=> (isMonochrome || (subsamplingX is 0 or 1 && subsamplingY is 0 or 1)) |
|||
&& lumaBitDepth is > 8 and <= 16 |
|||
&& (isMonochrome || chromaBitDepth == lumaBitDepth) |
|||
&& mode == HeifColorConversionMode.Coefficients; |
|||
|
|||
/// <summary>
|
|||
/// Converts supported high-bit-depth HEVC planes using pinned libheif arithmetic and nearest chroma sampling.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
|
|||
/// <typeparam name="TBuffer">The codec adapter that exposes reconstructed component rows.</typeparam>
|
|||
/// <param name="configuration">The configuration used for allocation and pixel conversion.</param>
|
|||
/// <param name="buffer">The reconstructed component-plane buffer.</param>
|
|||
/// <param name="image">The destination image frame.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
/// <param name="sourceX">The horizontal luma-sample offset of the output window.</param>
|
|||
/// <param name="sourceY">The vertical luma-sample offset of the output window.</param>
|
|||
public static void Convert<TPixel, TBuffer>( |
|||
Configuration configuration, |
|||
TBuffer buffer, |
|||
ImageFrame<TPixel> image, |
|||
in HeifColorConversionParameters parameters, |
|||
int sourceX, |
|||
int sourceY) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
where TBuffer : struct, IHeifPlanarSampleBuffer<ushort> |
|||
{ |
|||
ConversionParameters conversionParameters = new(in parameters, buffer.LumaBitDepth); |
|||
|
|||
// Three planar rows and one packed Rgba64 row share a single image-lifetime allocation. The latter occupies
|
|||
// four UInt16 values per pixel, so the complete scratch requirement is seven samples per output pixel.
|
|||
using IMemoryOwner<ushort> rowOwner = configuration.MemoryAllocator.Allocate<ushort>(image.Width * 7); |
|||
Span<ushort> storage = rowOwner.GetSpan(); |
|||
Span<ushort> red = storage[..image.Width]; |
|||
Span<ushort> green = storage.Slice(image.Width, image.Width); |
|||
Span<ushort> blue = storage.Slice(image.Width * 2, image.Width); |
|||
Span<Rgba64> packed = MemoryMarshal.Cast<ushort, Rgba64>(storage[(image.Width * 3)..]); |
|||
|
|||
for (int y = 0; y < image.Height; y++) |
|||
{ |
|||
int lumaY = sourceY + y; |
|||
ReadOnlySpan<ushort> luma = buffer.GetLumaRowSpan(lumaY).Slice(sourceX, image.Width); |
|||
if (buffer.IsMonochrome) |
|||
{ |
|||
// Pinned libheif copies the reconstructed luma code value directly to RGB for monochrome images.
|
|||
// Scaling to the 16-bit pixel domain happens after that copy, without limited-range expansion.
|
|||
ConvertRow<LibheifMonochromeOperator>( |
|||
luma, |
|||
luma, |
|||
luma, |
|||
red, |
|||
green, |
|||
blue, |
|||
0, |
|||
in conversionParameters); |
|||
} |
|||
else |
|||
{ |
|||
int subsamplingX = buffer.ChromaSubsamplingX; |
|||
int chromaY = lumaY >> buffer.ChromaSubsamplingY; |
|||
ReadOnlySpan<ushort> chromaBlue = buffer.GetChromaBlueRowSpan(chromaY).Slice(sourceX >> subsamplingX); |
|||
ReadOnlySpan<ushort> chromaRed = buffer.GetChromaRedRowSpan(chromaY).Slice(sourceX >> subsamplingX); |
|||
|
|||
// libheif's selected direct conversion addresses the native chroma sample at x >> subsamplingX.
|
|||
// The HEIF crop boundary already keeps sourceX aligned to complete chroma samples.
|
|||
ConvertRow<LibheifCoefficientOperator>( |
|||
luma, |
|||
chromaBlue, |
|||
chromaRed, |
|||
red, |
|||
green, |
|||
blue, |
|||
subsamplingX, |
|||
in conversionParameters); |
|||
} |
|||
|
|||
HeifSampleConversion.PackRgba64(red, green, blue, packed); |
|||
Span<TPixel> destination = image.PixelBuffer.DangerousGetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.FromRgba64(configuration, packed, destination); |
|||
} |
|||
} |
|||
} |
|||
@ -1,125 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Components; |
|||
|
|||
/// <content>
|
|||
/// Provides the coefficient conversion executed by pinned libheif 1.23.1. Each SIMD lane carries one output pixel.
|
|||
/// Range expansion and matrix arithmetic remain in single precision, RGB is rounded and clipped at the coded
|
|||
/// precision, and the final integer shift reproduces libheif's separate high-bit-depth-to-eight-bit operation.
|
|||
/// </content>
|
|||
internal static partial class HeifYuvToRgb8Converter |
|||
{ |
|||
/// <summary>
|
|||
/// Implements pinned-libheif coefficient conversion for scalar and SIMD lanes.
|
|||
/// </summary>
|
|||
private readonly struct LibheifCoefficientOperator : IHeifYuvToRgb8Operator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
Vector512<int> y, |
|||
Vector512<int> cb, |
|||
Vector512<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector512<int> r, |
|||
out Vector512<int> g, |
|||
out Vector512<int> b) |
|||
{ |
|||
LibheifVector512Parameters values = parameters.LibheifSixteenLane; |
|||
Vector512<float> luma = (Vector512.ConvertToSingle(y) - values.LumaOffset) * values.LumaScale; |
|||
Vector512<float> blueDifference = (Vector512.ConvertToSingle(cb) - values.ChromaMidpoint) * values.ChromaScale; |
|||
Vector512<float> redDifference = (Vector512.ConvertToSingle(cr) - values.ChromaMidpoint) * values.ChromaScale; |
|||
Vector512<float> half = Vector512.Create(0.5F); |
|||
|
|||
// Sixteen independent samples use the same float32 ordering as the narrower paths. The closed operator
|
|||
// keeps this compatibility arithmetic outside the row dispatch while allowing an exact scalar fallback.
|
|||
Vector512<int> red = Vector512.ConvertToInt32(Vector512.Truncate(luma + (values.RedCr * redDifference) + half)); |
|||
Vector512<int> green = Vector512.ConvertToInt32(Vector512.Truncate(luma + (values.GreenCb * blueDifference) + (values.GreenCr * redDifference) + half)); |
|||
Vector512<int> blue = Vector512.ConvertToInt32(Vector512.Truncate(luma + (values.BlueCb * blueDifference) + half)); |
|||
|
|||
r = Vector512.Clamp(red, default, values.Maximum) >> values.OutputShift; |
|||
g = Vector512.Clamp(green, default, values.Maximum) >> values.OutputShift; |
|||
b = Vector512.Clamp(blue, default, values.Maximum) >> values.OutputShift; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
Vector256<int> y, |
|||
Vector256<int> cb, |
|||
Vector256<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector256<int> r, |
|||
out Vector256<int> g, |
|||
out Vector256<int> b) |
|||
{ |
|||
LibheifVector256Parameters values = parameters.LibheifEightLane; |
|||
Vector256<float> luma = (Vector256.ConvertToSingle(y) - values.LumaOffset) * values.LumaScale; |
|||
Vector256<float> blueDifference = (Vector256.ConvertToSingle(cb) - values.ChromaMidpoint) * values.ChromaScale; |
|||
Vector256<float> redDifference = (Vector256.ConvertToSingle(cr) - values.ChromaMidpoint) * values.ChromaScale; |
|||
Vector256<float> half = Vector256.Create(0.5F); |
|||
|
|||
// Eight YUV tuples remain planar across the YMM arithmetic. The expression association matches the
|
|||
// pinned scalar source, including the two successive green additions before truncation.
|
|||
Vector256<int> red = Vector256.ConvertToInt32(Vector256.Truncate(luma + (values.RedCr * redDifference) + half)); |
|||
Vector256<int> green = Vector256.ConvertToInt32(Vector256.Truncate(luma + (values.GreenCb * blueDifference) + (values.GreenCr * redDifference) + half)); |
|||
Vector256<int> blue = Vector256.ConvertToInt32(Vector256.Truncate(luma + (values.BlueCb * blueDifference) + half)); |
|||
|
|||
r = Vector256.Clamp(red, default, values.Maximum) >> values.OutputShift; |
|||
g = Vector256.Clamp(green, default, values.Maximum) >> values.OutputShift; |
|||
b = Vector256.Clamp(blue, default, values.Maximum) >> values.OutputShift; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
Vector128<int> y, |
|||
Vector128<int> cb, |
|||
Vector128<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector128<int> r, |
|||
out Vector128<int> g, |
|||
out Vector128<int> b) |
|||
{ |
|||
LibheifVector128Parameters values = parameters.LibheifFourLane; |
|||
Vector128<float> luma = (Vector128.ConvertToSingle(y) - values.LumaOffset) * values.LumaScale; |
|||
Vector128<float> blueDifference = (Vector128.ConvertToSingle(cb) - values.ChromaMidpoint) * values.ChromaScale; |
|||
Vector128<float> redDifference = (Vector128.ConvertToSingle(cr) - values.ChromaMidpoint) * values.ChromaScale; |
|||
Vector128<float> half = Vector128.Create(0.5F); |
|||
|
|||
// Truncate after the explicit half-unit bias to mirror C++ float-to-int conversion. Clipping in integer
|
|||
// lanes then preserves the source-precision boundary before the common eight-bit reduction shift.
|
|||
Vector128<int> red = Vector128.ConvertToInt32(Vector128.Truncate(luma + (values.RedCr * redDifference) + half)); |
|||
Vector128<int> green = Vector128.ConvertToInt32(Vector128.Truncate(luma + (values.GreenCb * blueDifference) + (values.GreenCr * redDifference) + half)); |
|||
Vector128<int> blue = Vector128.ConvertToInt32(Vector128.Truncate(luma + (values.BlueCb * blueDifference) + half)); |
|||
|
|||
r = Vector128.Clamp(red, default, values.Maximum) >> values.OutputShift; |
|||
g = Vector128.Clamp(green, default, values.Maximum) >> values.OutputShift; |
|||
b = Vector128.Clamp(blue, default, values.Maximum) >> values.OutputShift; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert(ushort y, ushort cb, ushort cr, in ConversionParameters parameters, out byte r, out byte g, out byte b) |
|||
{ |
|||
LibheifParameters values = parameters.LibheifScalar; |
|||
float luma = (y - values.LumaOffset) * values.LumaScale; |
|||
float blueDifference = (cb - values.ChromaMidpoint) * values.ChromaScale; |
|||
float redDifference = (cr - values.ChromaMidpoint) * values.ChromaScale; |
|||
|
|||
// libheif's clip_f_u16 adds one half, truncates toward zero, and then clips. RGB is rounded before
|
|||
// the high-bit-depth plane is reduced, so moving the shift into the floating-point scale changes bytes.
|
|||
int red = (int)(luma + (values.RedCr * redDifference) + 0.5F); |
|||
int green = (int)(luma + (values.GreenCb * blueDifference) + (values.GreenCr * redDifference) + 0.5F); |
|||
int blue = (int)(luma + (values.BlueCb * blueDifference) + 0.5F); |
|||
|
|||
r = (byte)(Numerics.Clamp(red, 0, values.Maximum) >> values.OutputShift); |
|||
g = (byte)(Numerics.Clamp(green, 0, values.Maximum) >> values.OutputShift); |
|||
b = (byte)(Numerics.Clamp(blue, 0, values.Maximum) >> values.OutputShift); |
|||
} |
|||
} |
|||
} |
|||
@ -1,82 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Components; |
|||
|
|||
/// <content>
|
|||
/// Provides pinned-libheif monochrome presentation. The luma code value is reduced directly to eight bits and copied
|
|||
/// to all RGB components; signaled luma-range expansion is intentionally absent because libheif's direct monochrome
|
|||
/// operation does not apply it.
|
|||
/// </content>
|
|||
internal static partial class HeifYuvToRgb8Converter |
|||
{ |
|||
/// <summary>
|
|||
/// Implements pinned-libheif monochrome conversion for scalar and SIMD lanes.
|
|||
/// </summary>
|
|||
private readonly struct LibheifMonochromeOperator : IHeifYuvToRgb8Operator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
Vector512<int> y, |
|||
Vector512<int> cb, |
|||
Vector512<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector512<int> r, |
|||
out Vector512<int> g, |
|||
out Vector512<int> b) |
|||
{ |
|||
Vector512<int> value = y >> parameters.LibheifSixteenLane.OutputShift; |
|||
r = value; |
|||
g = value; |
|||
b = value; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
Vector256<int> y, |
|||
Vector256<int> cb, |
|||
Vector256<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector256<int> r, |
|||
out Vector256<int> g, |
|||
out Vector256<int> b) |
|||
{ |
|||
Vector256<int> value = y >> parameters.LibheifEightLane.OutputShift; |
|||
r = value; |
|||
g = value; |
|||
b = value; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert( |
|||
Vector128<int> y, |
|||
Vector128<int> cb, |
|||
Vector128<int> cr, |
|||
in ConversionParameters parameters, |
|||
out Vector128<int> r, |
|||
out Vector128<int> g, |
|||
out Vector128<int> b) |
|||
{ |
|||
Vector128<int> value = y >> parameters.LibheifFourLane.OutputShift; |
|||
r = value; |
|||
g = value; |
|||
b = value; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Convert(ushort y, ushort cb, ushort cr, in ConversionParameters parameters, out byte r, out byte g, out byte b) |
|||
{ |
|||
byte value = (byte)(y >> parameters.LibheifScalar.OutputShift); |
|||
r = value; |
|||
g = value; |
|||
b = value; |
|||
} |
|||
} |
|||
} |
|||
@ -1,81 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Components; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc.Color; |
|||
|
|||
/// <summary>
|
|||
/// Adapts reconstructed HEVC planes to the shared HEIF planar color pipeline.
|
|||
/// </summary>
|
|||
internal struct HevcPlanarSampleBuffer : IHeifPlanarSampleBuffer<ushort> |
|||
{ |
|||
/// <summary>
|
|||
/// The reconstructed HEVC picture containing the component planes.
|
|||
/// </summary>
|
|||
private readonly HevcPictureBuffer picture; |
|||
|
|||
/// <summary>
|
|||
/// The progressive-frame 4:2:0 chroma sample location.
|
|||
/// </summary>
|
|||
private readonly HevcChromaSampleLocation chromaSampleLocation; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcPlanarSampleBuffer"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="picture">The reconstructed HEVC picture.</param>
|
|||
/// <param name="chromaSampleLocation">The progressive-frame 4:2:0 chroma sample location.</param>
|
|||
public HevcPlanarSampleBuffer(HevcPictureBuffer picture, HevcChromaSampleLocation chromaSampleLocation) |
|||
{ |
|||
this.picture = picture; |
|||
this.chromaSampleLocation = chromaSampleLocation; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the horizontal offset in half-luma-sample units for each HEVC 4:2:0 chroma-location code.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> ChromaLocationX => [0, 1, 0, 1, 0, 1]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the vertical offset in half-luma-sample units for each HEVC 4:2:0 chroma-location code.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> ChromaLocationY => [1, 1, 0, 0, 2, 2]; |
|||
|
|||
/// <inheritdoc/>
|
|||
public readonly int Width => this.picture.Width; |
|||
|
|||
/// <inheritdoc/>
|
|||
public readonly int Height => this.picture.Height; |
|||
|
|||
/// <inheritdoc/>
|
|||
public readonly int LumaBitDepth => this.picture.BitDepthLuma; |
|||
|
|||
/// <inheritdoc/>
|
|||
public readonly int ChromaBitDepth => this.picture.BitDepthChroma; |
|||
|
|||
/// <inheritdoc/>
|
|||
public readonly bool IsMonochrome => this.picture.ChromaFormat == 0; |
|||
|
|||
/// <inheritdoc/>
|
|||
public readonly int ChromaSubsamplingX => this.picture.GetSubsamplingX(HevcPlane.Cb); |
|||
|
|||
/// <inheritdoc/>
|
|||
public readonly int ChromaSubsamplingY => this.picture.GetSubsamplingY(HevcPlane.Cb); |
|||
|
|||
/// <inheritdoc/>
|
|||
public readonly int ChromaPositionX |
|||
=> this.picture.ChromaFormat == 1 && !this.picture.SeparateColorPlane ? ChromaLocationX[(int)this.chromaSampleLocation] : 0; |
|||
|
|||
/// <inheritdoc/>
|
|||
public readonly int ChromaPositionY |
|||
=> this.picture.ChromaFormat == 1 && !this.picture.SeparateColorPlane ? ChromaLocationY[(int)this.chromaSampleLocation] : 0; |
|||
|
|||
/// <inheritdoc/>
|
|||
public Span<ushort> GetLumaRowSpan(int row) => this.picture.GetRowSpan(HevcPlane.Y, row); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Span<ushort> GetChromaBlueRowSpan(int row) => this.picture.GetRowSpan(HevcPlane.Cb, row); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Span<ushort> GetChromaRedRowSpan(int row) => this.picture.GetRowSpan(HevcPlane.Cr, row); |
|||
} |
|||
@ -1,179 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Components; |
|||
using SixLabors.ImageSharp.Formats.Heif.Components.Alpha; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Cicp; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc.Color; |
|||
|
|||
/// <summary>
|
|||
/// Adapts HEVC color signaling and reconstructed planes to the shared HEIF color pipeline.
|
|||
/// </summary>
|
|||
internal static class HevcYuvConverter |
|||
{ |
|||
/// <summary>
|
|||
/// Converts reconstructed HEVC component planes to packed pixels.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
|
|||
/// <param name="configuration">The configuration used for allocation and pixel conversion.</param>
|
|||
/// <param name="picture">The reconstructed HEVC picture.</param>
|
|||
/// <param name="image">The destination image frame.</param>
|
|||
/// <param name="colorProfile">The effective H.273 color description.</param>
|
|||
/// <param name="chromaSampleLocation">The progressive-frame 4:2:0 chroma sample location.</param>
|
|||
/// <param name="sourceX">The horizontal luma-sample offset of the first converted pixel.</param>
|
|||
/// <param name="sourceY">The vertical luma-sample offset of the first converted pixel.</param>
|
|||
public static void ConvertToRgb<TPixel>( |
|||
Configuration configuration, |
|||
HevcPictureBuffer picture, |
|||
ImageFrame<TPixel> image, |
|||
CicpProfile colorProfile, |
|||
HevcChromaSampleLocation chromaSampleLocation, |
|||
int sourceX = 0, |
|||
int sourceY = 0) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
HeifColorConversionParameters parameters = GetConversionParameters(picture, colorProfile, out HeifColorConversionMode mode); |
|||
HevcPlanarSampleBuffer buffer = new(picture, chromaSampleLocation); |
|||
if (HeifYuvToRgb8Converter.SupportsLibheifConversion( |
|||
buffer.ChromaSubsamplingX, |
|||
buffer.ChromaSubsamplingY, |
|||
buffer.LumaBitDepth, |
|||
buffer.ChromaBitDepth, |
|||
buffer.IsMonochrome, |
|||
mode)) |
|||
{ |
|||
// libheif 1.23.1 is the pinned HEIC presentation implementation. Its pipeline search selects the
|
|||
// lower-cost direct YCbCr operation when preferred-only upsampling is disabled, so subsampled chroma is
|
|||
// nearest-replicated and high-bit-depth RGB is rounded before a separate shift to eight bits.
|
|||
HeifYuvToRgb8Converter.ConvertLibheif( |
|||
configuration, |
|||
buffer, |
|||
image, |
|||
in parameters, |
|||
sourceX, |
|||
sourceY); |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (HeifYuvToRgb16Converter.SupportsLibheifConversion( |
|||
buffer.ChromaSubsamplingX, |
|||
buffer.ChromaSubsamplingY, |
|||
buffer.LumaBitDepth, |
|||
buffer.ChromaBitDepth, |
|||
buffer.IsMonochrome, |
|||
mode)) |
|||
{ |
|||
// High-bit-depth conversion retains every rounded source-precision RGB bit in UInt16 storage before
|
|||
// PixelOperations performs the requested TPixel conversion. This prevents an Rgba32 test from concealing
|
|||
// precision loss in Rgba64, Rgb48, or floating-point decoder output.
|
|||
HeifYuvToRgb16Converter.Convert( |
|||
configuration, |
|||
buffer, |
|||
image, |
|||
in parameters, |
|||
sourceX, |
|||
sourceY); |
|||
|
|||
return; |
|||
} |
|||
|
|||
HeifPlanarColorConverter.ConvertToRgb<TPixel, HevcPlanarSampleBuffer>( |
|||
configuration, |
|||
buffer, |
|||
image, |
|||
in parameters, |
|||
mode, |
|||
sourceX, |
|||
sourceY); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Composes a visible HEVC luma rectangle into a packed color frame as auxiliary alpha.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The destination color pixel type.</typeparam>
|
|||
/// <param name="configuration">The configuration used for allocation and pixel conversion.</param>
|
|||
/// <param name="picture">The reconstructed HEVC picture containing the alpha luma plane.</param>
|
|||
/// <param name="destination">The packed color frame receiving alpha values.</param>
|
|||
/// <param name="colorProfile">The effective H.273 color description defining the luma range.</param>
|
|||
/// <param name="chromaSampleLocation">The progressive-frame 4:2:0 chroma sample location.</param>
|
|||
/// <param name="sourceRectangle">The visible luma rectangle within the coded picture.</param>
|
|||
/// <param name="outputSize">The complete presented size of the auxiliary image or grid tile.</param>
|
|||
/// <param name="destinationRectangle">The destination region receiving the top-left portion of the presented alpha image.</param>
|
|||
/// <param name="premultiplied">Whether stored color samples must be converted to unassociated alpha.</param>
|
|||
public static void ComposeAlpha<TPixel>( |
|||
Configuration configuration, |
|||
HevcPictureBuffer picture, |
|||
ImageFrame<TPixel> destination, |
|||
CicpProfile colorProfile, |
|||
HevcChromaSampleLocation chromaSampleLocation, |
|||
Rectangle sourceRectangle, |
|||
Size outputSize, |
|||
Rectangle destinationRectangle, |
|||
bool premultiplied) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
HeifColorConversionParameters parameters = GetConversionParameters(picture, colorProfile, out _); |
|||
HevcPlanarSampleBuffer buffer = new(picture, chromaSampleLocation); |
|||
HeifPlanarAlphaCompositor.Compose<TPixel, HevcPlanarSampleBuffer, ushort, HeifUShortSampleConverter>( |
|||
configuration, |
|||
buffer, |
|||
destination, |
|||
in parameters, |
|||
sourceRectangle, |
|||
outputSize, |
|||
destinationRectangle, |
|||
premultiplied); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts packed pixels to the configured HEVC component planes.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The source pixel type.</typeparam>
|
|||
/// <param name="configuration">The configuration used for allocation and pixel conversion.</param>
|
|||
/// <param name="image">The source image frame.</param>
|
|||
/// <param name="picture">The destination HEVC picture.</param>
|
|||
/// <param name="colorProfile">The H.273 color description to encode.</param>
|
|||
/// <param name="chromaSampleLocation">The progressive-frame 4:2:0 chroma sample location.</param>
|
|||
public static void ConvertFromRgb<TPixel>( |
|||
Configuration configuration, |
|||
ImageFrame<TPixel> image, |
|||
HevcPictureBuffer picture, |
|||
CicpProfile colorProfile, |
|||
HevcChromaSampleLocation chromaSampleLocation) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
HeifColorConversionParameters parameters = GetConversionParameters(picture, colorProfile, out HeifColorConversionMode mode); |
|||
HevcPlanarSampleBuffer buffer = new(picture, chromaSampleLocation); |
|||
HeifPlanarColorConverter.ConvertFromRgb<TPixel, HevcPlanarSampleBuffer, ushort, HeifUShortSampleConverter>( |
|||
configuration, |
|||
image, |
|||
buffer, |
|||
in parameters, |
|||
mode); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resolves the shared H.273 conversion parameters for an HEVC picture.
|
|||
/// </summary>
|
|||
/// <param name="picture">The picture defining component precision and sampling.</param>
|
|||
/// <param name="colorProfile">The effective H.273 color description.</param>
|
|||
/// <param name="mode">The resolved color conversion operation.</param>
|
|||
/// <returns>The immutable scalar and SIMD conversion parameters.</returns>
|
|||
private static HeifColorConversionParameters GetConversionParameters( |
|||
HevcPictureBuffer picture, |
|||
CicpProfile colorProfile, |
|||
out HeifColorConversionMode mode) |
|||
=> HeifColorConversionParameters.Create( |
|||
colorProfile.ColorPrimaries, |
|||
colorProfile.TransferCharacteristics, |
|||
colorProfile.MatrixCoefficients, |
|||
colorProfile.FullRange, |
|||
picture.BitDepthLuma, |
|||
picture.ChromaFormat == 0 ? picture.BitDepthLuma : picture.BitDepthChroma, |
|||
picture.ChromaFormat == 0, |
|||
picture.ChromaFormat == 3, |
|||
out mode); |
|||
} |
|||
@ -1,230 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Reads fixed-width and Exp-Golomb HEVC syntax from a most-significant-bit-first byte span.
|
|||
/// </summary>
|
|||
internal ref struct HevcBitReader |
|||
{ |
|||
/// <summary>
|
|||
/// The complete raw byte sequence buffer.
|
|||
/// </summary>
|
|||
private readonly ReadOnlySpan<byte> data; |
|||
|
|||
/// <summary>
|
|||
/// The zero-based position of the next bit to read.
|
|||
/// </summary>
|
|||
private int bitPosition; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcBitReader"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="data">The bounded HEVC syntax bytes.</param>
|
|||
public HevcBitReader(ReadOnlySpan<byte> data) |
|||
{ |
|||
this.data = data; |
|||
this.bitPosition = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the zero-based position of the next bit to read.
|
|||
/// </summary>
|
|||
public readonly int BitPosition => this.bitPosition; |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of unread bits in the bounded byte span.
|
|||
/// </summary>
|
|||
public readonly int BitsRemaining => (this.data.Length * 8) - this.bitPosition; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the next bit begins a byte.
|
|||
/// </summary>
|
|||
public readonly bool IsByteAligned => (this.bitPosition & 7) == 0; |
|||
|
|||
/// <summary>
|
|||
/// Reads an unsigned fixed-width value in most-significant-bit-first order.
|
|||
/// </summary>
|
|||
/// <param name="bitCount">The number of bits to read.</param>
|
|||
/// <returns>The decoded unsigned value.</returns>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The requested value extends beyond the bounded HEVC syntax.
|
|||
/// </exception>
|
|||
public uint ReadBits(int bitCount) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount)); |
|||
if (bitCount > this.BitsRemaining) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC bitstream is truncated."); |
|||
} |
|||
|
|||
uint value = 0; |
|||
int remaining = bitCount; |
|||
while (remaining > 0) |
|||
{ |
|||
// HEVC fixed-width syntax is MSB-first. Reading only the available portion of each byte keeps the
|
|||
// same operation valid for both aligned parameter fields and fields that straddle byte boundaries.
|
|||
int byteOffset = this.bitPosition >> 3; |
|||
int bitOffset = this.bitPosition & 7; |
|||
int bitsFromByte = Math.Min(remaining, 8 - bitOffset); |
|||
int shift = 8 - bitOffset - bitsFromByte; |
|||
uint mask = (1U << bitsFromByte) - 1; |
|||
|
|||
value = (value << bitsFromByte) | ((uint)(this.data[byteOffset] >> shift) & mask); |
|||
this.bitPosition += bitsFromByte; |
|||
remaining -= bitsFromByte; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a one-bit HEVC flag.
|
|||
/// </summary>
|
|||
/// <returns><see langword="true"/> when the coded flag is one; otherwise, <see langword="false"/>.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The flag extends beyond the bounded HEVC syntax.</exception>
|
|||
public bool ReadFlag() => this.ReadBits(1) != 0; |
|||
|
|||
/// <summary>
|
|||
/// Determines whether unread syntax remains before the raw byte sequence payload trailing bits.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// <see langword="true"/> when the unread bits contain syntax before the stop bit; otherwise,
|
|||
/// <see langword="false"/>.
|
|||
/// </returns>
|
|||
public bool HasMoreRbspData() |
|||
{ |
|||
int bitsRemaining = this.BitsRemaining; |
|||
if (bitsRemaining == 0) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (bitsRemaining > 8) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
int savedBitPosition = this.bitPosition; |
|||
uint remainingValue = this.ReadBits(bitsRemaining); |
|||
this.bitPosition = savedBitPosition; |
|||
|
|||
// At most one partial byte can contain only rbsp_stop_one_bit followed by alignment zeros.
|
|||
return remainingValue != 1U << (bitsRemaining - 1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads an unsigned exponential-Golomb value.
|
|||
/// </summary>
|
|||
/// <returns>The decoded unsigned value.</returns>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The code is truncated or exceeds the range of a 32-bit unsigned integer.
|
|||
/// </exception>
|
|||
public uint ReadUnsignedExpGolomb() |
|||
{ |
|||
int leadingZeroBits = 0; |
|||
while (!this.ReadFlag()) |
|||
{ |
|||
leadingZeroBits++; |
|||
if (leadingZeroBits > 32) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC unsigned Exp-Golomb value exceeds 32 bits."); |
|||
} |
|||
} |
|||
|
|||
// In ue(v), the zero-prefix length selects an all-one basis and the equally wide suffix selects the
|
|||
// offset from that basis. Keeping those parts separate makes the 32-bit overflow boundary explicit.
|
|||
uint suffix = this.ReadBits(leadingZeroBits); |
|||
if (leadingZeroBits == 32) |
|||
{ |
|||
// Only an all-zero suffix fits after the 32-bit all-one basis.
|
|||
if (suffix != 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC unsigned Exp-Golomb value exceeds 32 bits."); |
|||
} |
|||
|
|||
return uint.MaxValue; |
|||
} |
|||
|
|||
return ((1U << leadingZeroBits) - 1) + suffix; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a signed exponential-Golomb value.
|
|||
/// </summary>
|
|||
/// <returns>The decoded signed value.</returns>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The code is truncated or exceeds the range of a 32-bit signed integer.
|
|||
/// </exception>
|
|||
public int ReadSignedExpGolomb() |
|||
{ |
|||
uint codeNumber = this.ReadUnsignedExpGolomb(); |
|||
|
|||
// HEVC's se(v) mapping alternates positive and negative magnitudes: 0, 1, -1, 2, -2, and so on.
|
|||
if ((codeNumber & 1) == 0) |
|||
{ |
|||
return -(int)(codeNumber >> 1); |
|||
} |
|||
|
|||
ulong magnitude = ((ulong)codeNumber + 1) >> 1; |
|||
if (magnitude > int.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC signed Exp-Golomb value exceeds 32 bits."); |
|||
} |
|||
|
|||
return (int)magnitude; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the one-bit marker and zero padding that align slice data to the next byte boundary.
|
|||
/// </summary>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The alignment marker is zero or any following alignment bit is nonzero.
|
|||
/// </exception>
|
|||
public void ReadByteAlignment() |
|||
{ |
|||
if (!this.ReadFlag()) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice-header alignment marker is not set."); |
|||
} |
|||
|
|||
while (!this.IsByteAligned) |
|||
{ |
|||
if (this.ReadFlag()) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice header has a nonzero alignment bit."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads and validates the stop bit and zero alignment bits that terminate an HEVC raw byte sequence payload.
|
|||
/// </summary>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The trailing-bit pattern is truncated, malformed, or followed by additional data.
|
|||
/// </exception>
|
|||
public void ReadRbspTrailingBits() |
|||
{ |
|||
// An RBSP ends with one stop bit followed only by zero bits up to the next byte boundary.
|
|||
if (!this.ReadFlag()) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC RBSP stop bit is not set."); |
|||
} |
|||
|
|||
while (!this.IsByteAligned) |
|||
{ |
|||
if (this.ReadFlag()) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC RBSP has a nonzero alignment bit."); |
|||
} |
|||
} |
|||
|
|||
// Each reader is bounded to one RBSP, so reaching alignment before the buffer end means the caller left
|
|||
// syntax unread or the NAL unit contains bytes beyond its normative terminator.
|
|||
if (this.BitsRemaining != 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC RBSP contains unexpected trailing data."); |
|||
} |
|||
} |
|||
} |
|||
@ -1,88 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Maintains the adaptive probability state for one HEVC context-coded binary syntax element.
|
|||
/// </summary>
|
|||
internal struct HevcCabacContext |
|||
{ |
|||
/// <summary>
|
|||
/// The packed probability-state index and most-probable-symbol value.
|
|||
/// </summary>
|
|||
private byte state; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcCabacContext"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="quantizationParameter">The luma quantization parameter that selects the initial probability.</param>
|
|||
/// <param name="initializationValue">The syntax-element initialization value.</param>
|
|||
public HevcCabacContext(int quantizationParameter, byte initializationValue) |
|||
{ |
|||
int clippedQuantizationParameter = Math.Clamp(quantizationParameter, 0, 51); |
|||
int slope = ((initializationValue >> 4) * 5) - 45; |
|||
int offset = ((initializationValue & 15) << 3) - 16; |
|||
int initializationState = Math.Clamp( |
|||
((slope * clippedQuantizationParameter) >> 4) + offset, |
|||
1, |
|||
126); |
|||
|
|||
bool mostProbableSymbol = initializationState >= 64; |
|||
this.state = (byte)( |
|||
((mostProbableSymbol ? initializationState - 64 : 63 - initializationState) << 1) |
|||
+ (mostProbableSymbol ? 1 : 0)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a mapping from each packed context state to the state that follows its most-probable symbol.
|
|||
/// </summary>
|
|||
// ReadOnlySpan allows the compiler to embed both normative tables in static data instead of allocating
|
|||
// mutable arrays when this type is initialized.
|
|||
private static ReadOnlySpan<byte> MostProbableStateTransitions => |
|||
[ |
|||
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, |
|||
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, |
|||
34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, |
|||
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, |
|||
66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, |
|||
82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, |
|||
98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, |
|||
114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 124, 125, 126, 127 |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets a mapping from each packed context state to the state that follows its least-probable symbol.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> LeastProbableStateTransitions => |
|||
[ |
|||
1, 0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 9, 8, 9, 10, 11, |
|||
12, 13, 14, 15, 16, 17, 18, 19, 18, 19, 22, 23, 22, 23, 24, 25, |
|||
26, 27, 26, 27, 30, 31, 30, 31, 32, 33, 32, 33, 36, 37, 36, 37, |
|||
38, 39, 38, 39, 42, 43, 42, 43, 44, 45, 44, 45, 46, 47, 48, 49, |
|||
48, 49, 50, 51, 52, 53, 52, 53, 54, 55, 54, 55, 56, 57, 58, 59, |
|||
58, 59, 60, 61, 60, 61, 60, 61, 62, 63, 64, 65, 64, 65, 66, 67, |
|||
66, 67, 66, 67, 68, 69, 68, 69, 70, 71, 70, 71, 70, 71, 72, 73, |
|||
72, 73, 72, 73, 74, 75, 74, 75, 74, 75, 76, 77, 76, 77, 126, 127 |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the probability-state index used to select the least-probable-symbol range.
|
|||
/// </summary>
|
|||
public readonly int StateIndex => this.state >> 1; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether one is the current most-probable symbol.
|
|||
/// </summary>
|
|||
public readonly bool MostProbableSymbol => (this.state & 1) != 0; |
|||
|
|||
/// <summary>
|
|||
/// Advances the context after decoding its most-probable symbol.
|
|||
/// </summary>
|
|||
public void UpdateMostProbableSymbol() => this.state = MostProbableStateTransitions[this.state]; |
|||
|
|||
/// <summary>
|
|||
/// Advances the context after decoding its least-probable symbol.
|
|||
/// </summary>
|
|||
public void UpdateLeastProbableSymbol() => this.state = LeastProbableStateTransitions[this.state]; |
|||
} |
|||
@ -1,319 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Owns the adaptive CABAC probability contexts used to decode one intra-coded HEVC entropy substream.
|
|||
/// </summary>
|
|||
internal sealed class HevcCabacContexts |
|||
{ |
|||
/// <summary>
|
|||
/// The first transquant-bypass context.
|
|||
/// </summary>
|
|||
private const int TransquantBypassOffset = 0; |
|||
|
|||
/// <summary>
|
|||
/// The first coding-unit split context.
|
|||
/// </summary>
|
|||
private const int SplitOffset = 1; |
|||
|
|||
/// <summary>
|
|||
/// The intra partition-size context.
|
|||
/// </summary>
|
|||
private const int PartitionSizeOffset = 4; |
|||
|
|||
/// <summary>
|
|||
/// The luma intra-prediction context.
|
|||
/// </summary>
|
|||
private const int IntraPredictionOffset = 5; |
|||
|
|||
/// <summary>
|
|||
/// The first chroma intra-prediction context.
|
|||
/// </summary>
|
|||
private const int ChromaPredictionOffset = 6; |
|||
|
|||
/// <summary>
|
|||
/// The first luma quantization-delta context.
|
|||
/// </summary>
|
|||
private const int DeltaQuantizationOffset = 8; |
|||
|
|||
/// <summary>
|
|||
/// The chroma quantization-adjustment flag context.
|
|||
/// </summary>
|
|||
private const int ChromaQuantizationAdjustmentFlagOffset = 11; |
|||
|
|||
/// <summary>
|
|||
/// The chroma quantization-adjustment index context.
|
|||
/// </summary>
|
|||
private const int ChromaQuantizationAdjustmentIndexOffset = 12; |
|||
|
|||
/// <summary>
|
|||
/// The first transform-tree coded-block-flag context.
|
|||
/// </summary>
|
|||
private const int TransformCodedBlockFlagOffset = 13; |
|||
|
|||
/// <summary>
|
|||
/// The first horizontal last-significant-coefficient context.
|
|||
/// </summary>
|
|||
private const int LastSignificantXOffset = 23; |
|||
|
|||
/// <summary>
|
|||
/// The first vertical last-significant-coefficient context.
|
|||
/// </summary>
|
|||
private const int LastSignificantYOffset = 53; |
|||
|
|||
/// <summary>
|
|||
/// The first significant-coefficient-group context.
|
|||
/// </summary>
|
|||
private const int SignificantCoefficientGroupOffset = 83; |
|||
|
|||
/// <summary>
|
|||
/// The first significant-coefficient context.
|
|||
/// </summary>
|
|||
private const int SignificantCoefficientOffset = 87; |
|||
|
|||
/// <summary>
|
|||
/// The first greater-than-one coefficient-level context.
|
|||
/// </summary>
|
|||
private const int GreaterThanOneOffset = 131; |
|||
|
|||
/// <summary>
|
|||
/// The first greater-than-two coefficient-level context.
|
|||
/// </summary>
|
|||
private const int GreaterThanTwoOffset = 155; |
|||
|
|||
/// <summary>
|
|||
/// The sample-adaptive-offset merge context.
|
|||
/// </summary>
|
|||
private const int SampleAdaptiveOffsetMergeOffset = 161; |
|||
|
|||
/// <summary>
|
|||
/// The sample-adaptive-offset type context.
|
|||
/// </summary>
|
|||
private const int SampleAdaptiveOffsetTypeOffset = 162; |
|||
|
|||
/// <summary>
|
|||
/// The first transform-tree subdivision context.
|
|||
/// </summary>
|
|||
private const int TransformSubdivisionOffset = 163; |
|||
|
|||
/// <summary>
|
|||
/// The first transform-skip context.
|
|||
/// </summary>
|
|||
private const int TransformSkipOffset = 166; |
|||
|
|||
/// <summary>
|
|||
/// The first cross-component prediction context.
|
|||
/// </summary>
|
|||
private const int CrossComponentPredictionOffset = 168; |
|||
|
|||
/// <summary>
|
|||
/// The number of contexts used by the independently coded intra-picture syntax.
|
|||
/// </summary>
|
|||
public const int ContextCount = 178; |
|||
|
|||
/// <summary>
|
|||
/// The contiguous adaptive context storage owned by the entropy substream.
|
|||
/// </summary>
|
|||
private readonly HevcCabacContext[] contexts; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcCabacContexts"/> class for an intra-coded slice.
|
|||
/// </summary>
|
|||
/// <param name="quantizationParameter">The slice luma quantization parameter.</param>
|
|||
public HevcCabacContexts(int quantizationParameter) |
|||
{ |
|||
this.contexts = new HevcCabacContext[ContextCount]; |
|||
for (int index = 0; index < this.contexts.Length; index++) |
|||
{ |
|||
this.contexts[index] = new HevcCabacContext(quantizationParameter, IntraInitializationValues[index]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the HEVC intra-slice initialization values in the same order as the owned context ranges.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> IntraInitializationValues => |
|||
[ |
|||
|
|||
// cu_transquant_bypass_flag
|
|||
154, |
|||
|
|||
// split_cu_flag
|
|||
139, 141, 157, |
|||
|
|||
// part_mode and prev_intra_luma_pred_flag
|
|||
184, |
|||
184, |
|||
|
|||
// intra_chroma_pred_mode
|
|||
63, 139, |
|||
|
|||
// cu_qp_delta_abs, cu_chroma_qp_offset_flag, and cu_chroma_qp_offset_idx
|
|||
154, 154, 154, |
|||
154, |
|||
154, |
|||
|
|||
// cbf_luma followed by the chroma coded-block flags
|
|||
111, 141, 154, 154, 154, |
|||
94, 138, 182, 154, 154, |
|||
|
|||
// last_sig_coeff_x_prefix: luma followed by chroma
|
|||
110, 110, 124, 125, 140, 153, 125, 127, 140, 109, 111, 143, 127, 111, 79, |
|||
108, 123, 63, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, |
|||
|
|||
// last_sig_coeff_y_prefix: luma followed by chroma
|
|||
110, 110, 124, 125, 140, 153, 125, 127, 140, 109, 111, 143, 127, 111, 79, |
|||
108, 123, 63, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, |
|||
|
|||
// coded_sub_block_flag: luma followed by chroma
|
|||
91, 171, 134, 141, |
|||
|
|||
// sig_coeff_flag: luma followed by chroma
|
|||
111, 111, 125, 110, 110, 94, 124, 108, 124, 107, 125, 141, 179, 153, |
|||
125, 107, 125, 141, 179, 153, 125, 107, 125, 141, 179, 153, 125, 141, |
|||
140, 139, 182, 182, 152, 136, 152, 136, 153, 136, 139, 111, 136, 139, 111, 111, |
|||
|
|||
// coeff_abs_level_greater1_flag: luma followed by chroma
|
|||
140, 92, 137, 138, 140, 152, 138, 139, 153, 74, 149, 92, 139, 107, 122, 152, |
|||
140, 179, 166, 182, 140, 227, 122, 197, |
|||
|
|||
// coeff_abs_level_greater2_flag: luma followed by chroma
|
|||
138, 153, 136, 167, 152, 152, |
|||
|
|||
// sao_merge_flag and sao_type_idx
|
|||
153, |
|||
200, |
|||
|
|||
// split_transform_flag
|
|||
153, 138, 138, |
|||
|
|||
// transform_skip_flag: luma followed by chroma
|
|||
139, 139, |
|||
|
|||
// cross_comp_pred: five sign/magnitude contexts for Cb followed by five for Cr
|
|||
154, 154, 154, 154, 154, 154, 154, 154, 154, 154 |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the coding-unit transquant-bypass context.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> TransquantBypass => this.contexts.AsSpan(TransquantBypassOffset, 1); |
|||
|
|||
/// <summary>
|
|||
/// Gets the coding-unit split contexts, ordered by neighboring split depth.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> Split => this.contexts.AsSpan(SplitOffset, 3); |
|||
|
|||
/// <summary>
|
|||
/// Gets the intra partition-size context.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> PartitionSize => this.contexts.AsSpan(PartitionSizeOffset, 1); |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma intra-prediction context.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> IntraPrediction => this.contexts.AsSpan(IntraPredictionOffset, 1); |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma intra-prediction contexts.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> ChromaPrediction => this.contexts.AsSpan(ChromaPredictionOffset, 2); |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma quantization-delta contexts.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> DeltaQuantization => this.contexts.AsSpan(DeltaQuantizationOffset, 3); |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma quantization-adjustment flag context.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> ChromaQuantizationAdjustmentFlag => |
|||
this.contexts.AsSpan(ChromaQuantizationAdjustmentFlagOffset, 1); |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma quantization-adjustment index context.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> ChromaQuantizationAdjustmentIndex => |
|||
this.contexts.AsSpan(ChromaQuantizationAdjustmentIndexOffset, 1); |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform-tree coded-block-flag contexts, with luma preceding chroma.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> TransformCodedBlockFlag => |
|||
this.contexts.AsSpan(TransformCodedBlockFlagOffset, 10); |
|||
|
|||
/// <summary>
|
|||
/// Gets the horizontal last-significant-coefficient contexts, with luma preceding chroma.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> LastSignificantX => this.contexts.AsSpan(LastSignificantXOffset, 30); |
|||
|
|||
/// <summary>
|
|||
/// Gets the vertical last-significant-coefficient contexts, with luma preceding chroma.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> LastSignificantY => this.contexts.AsSpan(LastSignificantYOffset, 30); |
|||
|
|||
/// <summary>
|
|||
/// Gets the significant-coefficient-group contexts, with luma preceding chroma.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> SignificantCoefficientGroup => |
|||
this.contexts.AsSpan(SignificantCoefficientGroupOffset, 4); |
|||
|
|||
/// <summary>
|
|||
/// Gets the significant-coefficient contexts, with luma preceding chroma.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> SignificantCoefficient => |
|||
this.contexts.AsSpan(SignificantCoefficientOffset, 44); |
|||
|
|||
/// <summary>
|
|||
/// Gets the greater-than-one coefficient-level contexts, with luma preceding chroma.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> GreaterThanOne => this.contexts.AsSpan(GreaterThanOneOffset, 24); |
|||
|
|||
/// <summary>
|
|||
/// Gets the greater-than-two coefficient-level contexts, with luma preceding chroma.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> GreaterThanTwo => this.contexts.AsSpan(GreaterThanTwoOffset, 6); |
|||
|
|||
/// <summary>
|
|||
/// Gets the sample-adaptive-offset merge context.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> SampleAdaptiveOffsetMerge => |
|||
this.contexts.AsSpan(SampleAdaptiveOffsetMergeOffset, 1); |
|||
|
|||
/// <summary>
|
|||
/// Gets the sample-adaptive-offset type context.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> SampleAdaptiveOffsetType => |
|||
this.contexts.AsSpan(SampleAdaptiveOffsetTypeOffset, 1); |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform-tree subdivision contexts.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> TransformSubdivision => |
|||
this.contexts.AsSpan(TransformSubdivisionOffset, 3); |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform-skip contexts, with luma preceding chroma.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> TransformSkip => this.contexts.AsSpan(TransformSkipOffset, 2); |
|||
|
|||
/// <summary>
|
|||
/// Gets the cross-component prediction contexts, with Cb preceding Cr.
|
|||
/// </summary>
|
|||
public Span<HevcCabacContext> CrossComponentPrediction => |
|||
this.contexts.AsSpan(CrossComponentPredictionOffset, 10); |
|||
|
|||
/// <summary>
|
|||
/// Copies every adaptive probability context to caller-owned wavefront state.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination containing at least <see cref="ContextCount"/> elements.</param>
|
|||
public void CopyTo(Span<HevcCabacContext> destination) => this.contexts.CopyTo(destination); |
|||
|
|||
/// <summary>
|
|||
/// Restores every adaptive probability context from caller-owned wavefront state.
|
|||
/// </summary>
|
|||
/// <param name="source">The source containing at least <see cref="ContextCount"/> elements.</param>
|
|||
public void CopyFrom(ReadOnlySpan<HevcCabacContext> source) => source[..ContextCount].CopyTo(this.contexts); |
|||
} |
|||
@ -1,396 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Decodes context-adaptive and bypass-coded binary values from one bounded HEVC entropy substream.
|
|||
/// </summary>
|
|||
internal ref struct HevcCabacDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// The complete bounded entropy-substream bytes.
|
|||
/// </summary>
|
|||
private readonly ReadOnlySpan<byte> data; |
|||
|
|||
/// <summary>
|
|||
/// The zero-based offset of the next byte that can refill the arithmetic value register.
|
|||
/// </summary>
|
|||
private int byteOffset; |
|||
|
|||
/// <summary>
|
|||
/// The current arithmetic interval width.
|
|||
/// </summary>
|
|||
private uint range; |
|||
|
|||
/// <summary>
|
|||
/// The current arithmetic code value, scaled by seven fractional bits.
|
|||
/// </summary>
|
|||
private uint value; |
|||
|
|||
/// <summary>
|
|||
/// The number of normalization shifts remaining before the value register requires another byte.
|
|||
/// </summary>
|
|||
private int bitsNeeded; |
|||
|
|||
/// <summary>
|
|||
/// The raw-bit position used while a pulse-code-modulated coding unit suspends arithmetic decoding.
|
|||
/// </summary>
|
|||
private int pcmBitOffset; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcCabacDecoder"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="data">The bytes of one independently bounded HEVC entropy substream.</param>
|
|||
/// <exception cref="InvalidImageContentException">The entropy substream is shorter than its initial value register.</exception>
|
|||
public HevcCabacDecoder(ReadOnlySpan<byte> data) |
|||
{ |
|||
if (data.Length < 2) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC CABAC substream is truncated."); |
|||
} |
|||
|
|||
this.data = data; |
|||
this.byteOffset = 2; |
|||
this.range = 510; |
|||
this.value = ((uint)data[0] << 8) | data[1]; |
|||
this.bitsNeeded = -8; |
|||
this.pcmBitOffset = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the least-probable-symbol subrange for each probability state and current range class.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> LeastProbableSymbolRanges => |
|||
[ |
|||
128, 176, 208, 240, 128, 167, 197, 227, 128, 158, 187, 216, 123, 150, 178, 205, |
|||
116, 142, 169, 195, 111, 135, 160, 185, 105, 128, 152, 175, 100, 122, 144, 166, |
|||
95, 116, 137, 158, 90, 110, 130, 150, 85, 104, 123, 142, 81, 99, 117, 135, |
|||
77, 94, 111, 128, 73, 89, 105, 122, 69, 85, 100, 116, 66, 80, 95, 110, |
|||
62, 76, 90, 104, 59, 72, 86, 99, 56, 69, 81, 94, 53, 65, 77, 89, |
|||
51, 62, 73, 85, 48, 59, 69, 80, 46, 56, 66, 76, 43, 53, 63, 72, |
|||
41, 50, 59, 69, 39, 48, 56, 65, 37, 45, 54, 62, 35, 43, 51, 59, |
|||
33, 41, 48, 56, 32, 39, 46, 53, 30, 37, 43, 50, 29, 35, 41, 48, |
|||
27, 33, 39, 45, 26, 31, 37, 43, 24, 30, 35, 41, 23, 28, 33, 39, |
|||
22, 27, 32, 37, 21, 26, 30, 35, 20, 24, 29, 33, 19, 23, 27, 31, |
|||
18, 22, 26, 30, 17, 21, 25, 28, 16, 20, 23, 27, 15, 19, 22, 25, |
|||
14, 18, 21, 24, 14, 17, 20, 23, 13, 16, 19, 22, 12, 15, 18, 21, |
|||
12, 14, 17, 20, 11, 14, 16, 19, 11, 13, 15, 18, 10, 12, 15, 17, |
|||
10, 12, 14, 16, 9, 11, 13, 15, 9, 11, 12, 14, 8, 10, 12, 14, |
|||
8, 9, 11, 13, 7, 9, 11, 12, 7, 9, 10, 12, 7, 8, 10, 11, |
|||
6, 8, 9, 11, 6, 7, 9, 10, 6, 7, 8, 9, 2, 2, 2, 2, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the normalization shift for each quantized least-probable-symbol range.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> LeastProbableSymbolNormalizationShifts => |
|||
[ |
|||
6, 5, 4, 4, |
|||
3, 3, 3, 3, |
|||
2, 2, 2, 2, |
|||
2, 2, 2, 2, |
|||
1, 1, 1, 1, |
|||
1, 1, 1, 1, |
|||
1, 1, 1, 1, |
|||
1, 1, 1, 1, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of whole entropy-substream bytes loaded into the arithmetic decoder.
|
|||
/// </summary>
|
|||
public readonly int BytesConsumed => this.byteOffset; |
|||
|
|||
/// <summary>
|
|||
/// Decodes one context-adaptive binary value and advances its probability state.
|
|||
/// </summary>
|
|||
/// <param name="context">The adaptive probability context selected for the syntax element.</param>
|
|||
/// <returns>The decoded binary value.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The entropy substream ends while normalizing the decoded value.</exception>
|
|||
public bool ReadDecision(ref HevcCabacContext context) |
|||
{ |
|||
int rangeClass = ((int)this.range >> 6) - 4; |
|||
uint leastProbableSymbolRange = LeastProbableSymbolRanges[(context.StateIndex * 4) + rangeClass]; |
|||
this.range -= leastProbableSymbolRange; |
|||
uint scaledRange = this.range << 7; |
|||
|
|||
if (this.value < scaledRange) |
|||
{ |
|||
bool symbol = context.MostProbableSymbol; |
|||
context.UpdateMostProbableSymbol(); |
|||
|
|||
if (scaledRange < (256U << 7)) |
|||
{ |
|||
// Renormalization shifts both registers together so their comparison continues to describe the
|
|||
// same arithmetic interval; a byte is loaded only when the buffered fractional bits are exhausted.
|
|||
this.range = scaledRange >> 6; |
|||
this.value <<= 1; |
|||
if (++this.bitsNeeded == 0) |
|||
{ |
|||
this.bitsNeeded = -8; |
|||
this.value += this.ReadByte(); |
|||
} |
|||
} |
|||
|
|||
return symbol; |
|||
} |
|||
|
|||
bool leastProbableSymbol = !context.MostProbableSymbol; |
|||
int normalizationShift = LeastProbableSymbolNormalizationShifts[(int)(leastProbableSymbolRange >> 3)]; |
|||
this.value = (this.value - scaledRange) << normalizationShift; |
|||
this.range = leastProbableSymbolRange << normalizationShift; |
|||
context.UpdateLeastProbableSymbol(); |
|||
this.bitsNeeded += normalizationShift; |
|||
if (this.bitsNeeded >= 0) |
|||
{ |
|||
this.value += (uint)this.ReadByte() << this.bitsNeeded; |
|||
this.bitsNeeded -= 8; |
|||
} |
|||
|
|||
return leastProbableSymbol; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes one equal-probability binary value without changing an adaptive context.
|
|||
/// </summary>
|
|||
/// <returns>The decoded binary value.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The entropy substream ends while loading the decoded value.</exception>
|
|||
public bool ReadBypass() |
|||
{ |
|||
if (this.range == 256) |
|||
{ |
|||
return this.ReadAlignedBypassBits(1) != 0; |
|||
} |
|||
|
|||
this.value <<= 1; |
|||
if (++this.bitsNeeded >= 0) |
|||
{ |
|||
this.bitsNeeded = -8; |
|||
this.value += this.ReadByte(); |
|||
} |
|||
|
|||
uint scaledRange = this.range << 7; |
|||
if (this.value < scaledRange) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.value -= scaledRange; |
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a most-significant-bit-first sequence of equal-probability binary values.
|
|||
/// </summary>
|
|||
/// <param name="bitCount">The number of values to decode.</param>
|
|||
/// <returns>The decoded unsigned value.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The entropy substream ends while loading the decoded value.</exception>
|
|||
public uint ReadBypassBits(int bitCount) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount)); |
|||
if (this.range == 256) |
|||
{ |
|||
return this.ReadAlignedBypassBits(bitCount); |
|||
} |
|||
|
|||
uint bins = 0; |
|||
int remaining = bitCount; |
|||
while (remaining > 8) |
|||
{ |
|||
this.value = (this.value << 8) + ((uint)this.ReadByte() << (8 + this.bitsNeeded)); |
|||
uint scaledRange = this.range << 15; |
|||
for (int bitIndex = 0; bitIndex < 8; bitIndex++) |
|||
{ |
|||
bins <<= 1; |
|||
scaledRange >>= 1; |
|||
if (this.value >= scaledRange) |
|||
{ |
|||
bins++; |
|||
this.value -= scaledRange; |
|||
} |
|||
} |
|||
|
|||
remaining -= 8; |
|||
} |
|||
|
|||
this.bitsNeeded += remaining; |
|||
this.value <<= remaining; |
|||
if (this.bitsNeeded >= 0) |
|||
{ |
|||
this.value += (uint)this.ReadByte() << this.bitsNeeded; |
|||
this.bitsNeeded -= 8; |
|||
} |
|||
|
|||
uint finalScaledRange = this.range << (remaining + 7); |
|||
for (int bitIndex = 0; bitIndex < remaining; bitIndex++) |
|||
{ |
|||
bins <<= 1; |
|||
finalScaledRange >>= 1; |
|||
if (this.value >= finalScaledRange) |
|||
{ |
|||
bins++; |
|||
this.value -= finalScaledRange; |
|||
} |
|||
} |
|||
|
|||
return bins; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects the byte-aligned equal-probability range used by aligned bypass syntax.
|
|||
/// </summary>
|
|||
public void AlignBypass() => this.range = 256; |
|||
|
|||
/// <summary>
|
|||
/// Decodes the binary value that terminates a coding-tree block or entropy substream.
|
|||
/// </summary>
|
|||
/// <returns><see langword="true"/> when the current entropy substream terminates; otherwise, <see langword="false"/>.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The entropy substream ends while normalizing a non-terminating value.</exception>
|
|||
public bool ReadTerminate() |
|||
{ |
|||
this.range -= 2; |
|||
uint scaledRange = this.range << 7; |
|||
if (this.value >= scaledRange) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (scaledRange < (256U << 7)) |
|||
{ |
|||
this.range = scaledRange >> 6; |
|||
this.value <<= 1; |
|||
if (++this.bitsNeeded == 0) |
|||
{ |
|||
this.bitsNeeded = -8; |
|||
this.value += this.ReadByte(); |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes the terminating-bin flag that enters pulse-code-modulated sample syntax.
|
|||
/// </summary>
|
|||
/// <returns><see langword="true"/> when raw PCM samples follow; otherwise, <see langword="false"/>.</returns>
|
|||
public bool ReadPcmFlag() |
|||
{ |
|||
bool pcm = this.ReadTerminate(); |
|||
if (pcm) |
|||
{ |
|||
// A successful terminating bin leaves the underlying byte reader at the first byte after the CABAC
|
|||
// alignment pattern. PCM sample bits start there and temporarily bypass the arithmetic registers.
|
|||
this.pcmBitOffset = this.byteOffset * 8; |
|||
} |
|||
|
|||
return pcm; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads one unsigned pulse-code-modulated sample while arithmetic decoding is suspended.
|
|||
/// </summary>
|
|||
/// <param name="bitDepth">The number of most-significant-bit-first sample bits.</param>
|
|||
/// <returns>The decoded sample value.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The entropy substream ends within the PCM sample.</exception>
|
|||
public ushort ReadPcmSample(int bitDepth) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(bitDepth, 1, 16, nameof(bitDepth)); |
|||
if (this.pcmBitOffset > (this.data.Length * 8) - bitDepth) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC pulse-code-modulated sample data is truncated."); |
|||
} |
|||
|
|||
uint sample = 0; |
|||
int bitsRemaining = bitDepth; |
|||
while (bitsRemaining > 0) |
|||
{ |
|||
int byteIndex = this.pcmBitOffset >> 3; |
|||
int bitIndex = this.pcmBitOffset & 7; |
|||
int bitsFromByte = Math.Min(8 - bitIndex, bitsRemaining); |
|||
int shift = 8 - bitIndex - bitsFromByte; |
|||
uint mask = (uint)((1 << bitsFromByte) - 1); |
|||
sample = (sample << bitsFromByte) | ((uint)(this.data[byteIndex] >> shift) & mask); |
|||
this.pcmBitOffset += bitsFromByte; |
|||
bitsRemaining -= bitsFromByte; |
|||
} |
|||
|
|||
return (ushort)sample; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Restarts arithmetic decoding after a complete byte-aligned PCM coding unit.
|
|||
/// </summary>
|
|||
/// <exception cref="InvalidImageContentException">The following arithmetic substream is truncated.</exception>
|
|||
public void RestartAfterPcm() |
|||
{ |
|||
DebugGuard.IsTrue((this.pcmBitOffset & 7) == 0, "The complete HEVC PCM payload must end on a byte boundary."); |
|||
this.byteOffset = this.pcmBitOffset >> 3; |
|||
this.range = 510; |
|||
this.bitsNeeded = -8; |
|||
this.value = ((uint)this.ReadByte() << 8) | this.ReadByte(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Validates the stop bit and zero padding following a terminating entropy-coded value.
|
|||
/// </summary>
|
|||
/// <exception cref="InvalidImageContentException">The entropy substream has an invalid stop or alignment bit.</exception>
|
|||
public readonly void ValidateTerminationAlignment() |
|||
{ |
|||
int alignmentShift = 8 + this.bitsNeeded; |
|||
|
|||
// CABAC refills whole bytes ahead of consumption. The stop bit therefore remains in the most recently
|
|||
// loaded byte, and bitsNeeded identifies its exact position without rewinding the arithmetic decoder.
|
|||
int alignmentPattern = (this.data[this.byteOffset - 1] << alignmentShift) & 0xFF; |
|||
if (alignmentPattern != 0x80) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC CABAC substream has invalid termination alignment."); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes equal-probability values while the arithmetic range is byte aligned.
|
|||
/// </summary>
|
|||
/// <param name="bitCount">The number of values to decode.</param>
|
|||
/// <returns>The decoded unsigned value.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The entropy substream ends while loading the decoded value.</exception>
|
|||
private uint ReadAlignedBypassBits(int bitCount) |
|||
{ |
|||
uint bins = 0; |
|||
int remaining = bitCount; |
|||
while (remaining > 0) |
|||
{ |
|||
int binsToRead = Math.Min(remaining, 8); |
|||
uint binMask = (1U << binsToRead) - 1; |
|||
|
|||
// With a range of 256 the high value bit is known to be zero, so the following bits can be copied
|
|||
// directly while preserving the same register refill schedule as individual bypass decisions.
|
|||
uint newBins = (this.value >> (15 - binsToRead)) & binMask; |
|||
bins = (bins << binsToRead) | newBins; |
|||
this.value = (this.value << binsToRead) & 0x7FFF; |
|||
remaining -= binsToRead; |
|||
this.bitsNeeded += binsToRead; |
|||
if (this.bitsNeeded >= 0) |
|||
{ |
|||
this.value |= (uint)this.ReadByte() << this.bitsNeeded; |
|||
this.bitsNeeded -= 8; |
|||
} |
|||
} |
|||
|
|||
return bins; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads the next byte into the arithmetic decoder.
|
|||
/// </summary>
|
|||
/// <returns>The next entropy-substream byte.</returns>
|
|||
/// <exception cref="InvalidImageContentException">No byte remains in the bounded entropy substream.</exception>
|
|||
private byte ReadByte() |
|||
{ |
|||
if ((uint)this.byteOffset >= (uint)this.data.Length) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC CABAC substream is truncated."); |
|||
} |
|||
|
|||
return this.data[this.byteOffset++]; |
|||
} |
|||
} |
|||
@ -1,575 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Decodes the CABAC syntax values used to reconstruct one independently coded HEVC still picture.
|
|||
/// </summary>
|
|||
internal ref struct HevcCabacSyntaxReader |
|||
{ |
|||
/// <summary>
|
|||
/// The truncated-unary cutoff for a coding-unit luma quantization delta.
|
|||
/// </summary>
|
|||
private const int DeltaQuantizationCutoff = 5; |
|||
|
|||
/// <summary>
|
|||
/// The prefix length at which coefficient levels switch from Rice to exponential-Golomb coding.
|
|||
/// </summary>
|
|||
private const int CoefficientRemainingReduction = 3; |
|||
|
|||
/// <summary>
|
|||
/// The binary arithmetic decoder for the current entropy substream.
|
|||
/// </summary>
|
|||
private HevcCabacDecoder decoder; |
|||
|
|||
/// <summary>
|
|||
/// The adaptive intra-picture probability contexts for the current entropy substream.
|
|||
/// </summary>
|
|||
private readonly HevcCabacContexts contexts; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcCabacSyntaxReader"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="data">The bytes of one bounded slice tile or wavefront entropy substream.</param>
|
|||
/// <param name="quantizationParameter">The slice luma quantization parameter.</param>
|
|||
/// <exception cref="InvalidImageContentException">The entropy substream is truncated.</exception>
|
|||
public HevcCabacSyntaxReader(ReadOnlySpan<byte> data, int quantizationParameter) |
|||
{ |
|||
this.decoder = new HevcCabacDecoder(data); |
|||
this.contexts = new HevcCabacContexts(quantizationParameter); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of entropy-substream bytes loaded by the arithmetic decoder.
|
|||
/// </summary>
|
|||
public readonly int BytesConsumed => this.decoder.BytesConsumed; |
|||
|
|||
/// <summary>
|
|||
/// Copies the adaptive contexts required to initialize a later wavefront row.
|
|||
/// </summary>
|
|||
/// <param name="destination">The caller-owned context destination.</param>
|
|||
public readonly void CopyContextsTo(Span<HevcCabacContext> destination) => this.contexts.CopyTo(destination); |
|||
|
|||
/// <summary>
|
|||
/// Restores adaptive contexts captured after the second coding-tree block of the preceding wavefront row.
|
|||
/// </summary>
|
|||
/// <param name="source">The saved wavefront contexts.</param>
|
|||
public readonly void CopyContextsFrom(ReadOnlySpan<HevcCabacContext> source) => this.contexts.CopyFrom(source); |
|||
|
|||
/// <summary>
|
|||
/// Decodes the coding-unit transquant-bypass flag.
|
|||
/// </summary>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadTransquantBypass() |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.TransquantBypass; |
|||
return this.decoder.ReadDecision(ref selectedContexts[0]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a coding-unit split flag.
|
|||
/// </summary>
|
|||
/// <param name="contextIndex">The context derived from the available neighboring coding-unit depths.</param>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadSplit(int contextIndex) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(contextIndex, 0, 2, nameof(contextIndex)); |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.Split; |
|||
return this.decoder.ReadDecision(ref selectedContexts[contextIndex]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes whether a minimum-size intra coding unit uses four square prediction partitions.
|
|||
/// </summary>
|
|||
/// <param name="isMinimumCodingBlockSize">
|
|||
/// A value indicating whether the coding unit is at the minimum coding-block size.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// <see langword="true"/> for four square prediction partitions; <see langword="false"/> for one square partition.
|
|||
/// </returns>
|
|||
public bool ReadIntraNxNPartition(bool isMinimumCodingBlockSize) |
|||
{ |
|||
if (!isMinimumCodingBlockSize) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
Span<HevcCabacContext> selectedContexts = this.contexts.PartitionSize; |
|||
return !this.decoder.ReadDecision(ref selectedContexts[0]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes whether a square intra coding unit carries raw pulse-code-modulated samples.
|
|||
/// </summary>
|
|||
/// <returns><see langword="true"/> when PCM sample syntax follows; otherwise, <see langword="false"/>.</returns>
|
|||
public bool ReadPcmFlag() => this.decoder.ReadPcmFlag(); |
|||
|
|||
/// <summary>
|
|||
/// Reads one pulse-code-modulated component sample.
|
|||
/// </summary>
|
|||
/// <param name="bitDepth">The PCM sample precision.</param>
|
|||
/// <returns>The decoded unsigned sample.</returns>
|
|||
public ushort ReadPcmSample(int bitDepth) => this.decoder.ReadPcmSample(bitDepth); |
|||
|
|||
/// <summary>
|
|||
/// Restarts arithmetic decoding after the complete PCM coding-unit payload.
|
|||
/// </summary>
|
|||
public void RestartAfterPcm() => this.decoder.RestartAfterPcm(); |
|||
|
|||
/// <summary>
|
|||
/// Decodes whether a luma intra mode is selected from the three most-probable modes.
|
|||
/// </summary>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadPreviousIntraLumaPredictionFlag() |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.IntraPrediction; |
|||
return this.decoder.ReadDecision(ref selectedContexts[0]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes the zero-based selector for one of the three most-probable luma intra modes.
|
|||
/// </summary>
|
|||
/// <returns>The selector in the inclusive range zero through two.</returns>
|
|||
public int ReadMostProbableIntraLumaPredictionIndex() |
|||
{ |
|||
if (!this.decoder.ReadBypass()) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
return this.decoder.ReadBypass() ? 2 : 1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes the five-bit selector for a luma intra mode outside the most-probable set.
|
|||
/// </summary>
|
|||
/// <returns>The decoded selector in the inclusive range zero through thirty-one.</returns>
|
|||
public int ReadRemainingIntraLumaPredictionMode() => (int)this.decoder.ReadBypassBits(5); |
|||
|
|||
/// <summary>
|
|||
/// Decodes the chroma intra prediction selector.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// Negative one when chroma derives its mode from luma; otherwise, the decoded selector in the inclusive range
|
|||
/// zero through three.
|
|||
/// </returns>
|
|||
public int ReadChromaPredictionModeIndex() |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.ChromaPrediction; |
|||
if (!this.decoder.ReadDecision(ref selectedContexts[0])) |
|||
{ |
|||
return -1; |
|||
} |
|||
|
|||
return (int)this.decoder.ReadBypassBits(2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a transform-tree subdivision flag.
|
|||
/// </summary>
|
|||
/// <param name="log2TransformBlockSize">The base-two logarithm of the current transform-block size.</param>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadTransformSubdivision(int log2TransformBlockSize) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2TransformBlockSize, 3, 5, nameof(log2TransformBlockSize)); |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.TransformSubdivision; |
|||
return this.decoder.ReadDecision(ref selectedContexts[5 - log2TransformBlockSize]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a transform-tree coded-block flag.
|
|||
/// </summary>
|
|||
/// <param name="isChroma">A value indicating whether the flag describes a chroma transform block.</param>
|
|||
/// <param name="contextIndex">The transform-depth-derived context index.</param>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadTransformCodedBlockFlag(bool isChroma, int contextIndex) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(contextIndex, 0, 4, nameof(contextIndex)); |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.TransformCodedBlockFlag; |
|||
int channelOffset = isChroma ? 5 : 0; |
|||
return this.decoder.ReadDecision(ref selectedContexts[channelOffset + contextIndex]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes whether a transform block bypasses the inverse transform.
|
|||
/// </summary>
|
|||
/// <param name="isChroma">A value indicating whether the transform block belongs to a chroma channel.</param>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadTransformSkip(bool isChroma) |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.TransformSkip; |
|||
return this.decoder.ReadDecision(ref selectedContexts[isChroma ? 1 : 0]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes the signed coding-unit luma quantization-parameter delta.
|
|||
/// </summary>
|
|||
/// <returns>The signed delta value.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The coded magnitude exceeds a 32-bit signed value.</exception>
|
|||
public int ReadDeltaQuantizationParameter() |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.DeltaQuantization; |
|||
ulong magnitude = this.ReadTruncatedUnary(selectedContexts, 0, 1, DeltaQuantizationCutoff); |
|||
if (magnitude == DeltaQuantizationCutoff) |
|||
{ |
|||
magnitude += this.ReadBypassExponentialGolomb(0); |
|||
} |
|||
|
|||
if (magnitude > int.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC coding-unit quantization delta is too large."); |
|||
} |
|||
|
|||
if (magnitude == 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
int signedMagnitude = (int)magnitude; |
|||
return this.decoder.ReadBypass() ? -signedMagnitude : signedMagnitude; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes the coding-unit chroma quantization-adjustment selector.
|
|||
/// </summary>
|
|||
/// <param name="listLength">The number of chroma offset pairs declared by the picture parameters.</param>
|
|||
/// <returns>Zero when no adjustment applies; otherwise, the one-based offset-list selector.</returns>
|
|||
public int ReadChromaQuantizationAdjustment(int listLength) |
|||
{ |
|||
Span<HevcCabacContext> flagContexts = this.contexts.ChromaQuantizationAdjustmentFlag; |
|||
if (!this.decoder.ReadDecision(ref flagContexts[0])) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
if (listLength == 1) |
|||
{ |
|||
return 1; |
|||
} |
|||
|
|||
Span<HevcCabacContext> indexContexts = this.contexts.ChromaQuantizationAdjustmentIndex; |
|||
return (int)this.ReadTruncatedUnary(indexContexts, 0, 0, listLength - 1) + 1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes the cross-component residual-prediction scale for one chroma plane.
|
|||
/// </summary>
|
|||
/// <param name="chromaPlaneIndex">Zero for Cb or one for Cr.</param>
|
|||
/// <returns>Zero when prediction is disabled; otherwise, a signed power of two from one through eight.</returns>
|
|||
public int ReadCrossComponentPredictionScale(int chromaPlaneIndex) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(chromaPlaneIndex, 0, 1, nameof(chromaPlaneIndex)); |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.CrossComponentPrediction; |
|||
int contextOffset = chromaPlaneIndex * 5; |
|||
if (!this.decoder.ReadDecision(ref selectedContexts[contextOffset])) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
int magnitudeLog2 = 0; |
|||
if (this.decoder.ReadDecision(ref selectedContexts[contextOffset + 1])) |
|||
{ |
|||
Span<HevcCabacContext> magnitudeContexts = selectedContexts.Slice(contextOffset + 2, 2); |
|||
magnitudeLog2 = (int)this.ReadTruncatedUnary(magnitudeContexts, 0, 1, 2) + 1; |
|||
} |
|||
|
|||
int magnitude = 1 << magnitudeLog2; |
|||
return this.decoder.ReadDecision(ref selectedContexts[contextOffset + 4]) ? -magnitude : magnitude; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a sample-adaptive-offset merge flag.
|
|||
/// </summary>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadSampleAdaptiveOffsetMerge() |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.SampleAdaptiveOffsetMerge; |
|||
return this.decoder.ReadDecision(ref selectedContexts[0]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes the sample-adaptive-offset mode selector.
|
|||
/// </summary>
|
|||
/// <returns>Zero for off, one for band offset, or two for edge offset.</returns>
|
|||
public int ReadSampleAdaptiveOffsetType() |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.SampleAdaptiveOffsetType; |
|||
if (!this.decoder.ReadDecision(ref selectedContexts[0])) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
return this.decoder.ReadBypass() ? 2 : 1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a truncated-unary absolute sample-adaptive-offset value.
|
|||
/// </summary>
|
|||
/// <param name="maximumValue">The inclusive maximum offset magnitude.</param>
|
|||
/// <returns>The decoded offset magnitude.</returns>
|
|||
public int ReadSampleAdaptiveOffsetAbsolute(int maximumValue) |
|||
{ |
|||
if (maximumValue == 0 || !this.decoder.ReadBypass()) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
int value = 1; |
|||
while (value < maximumValue && this.decoder.ReadBypass()) |
|||
{ |
|||
value++; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes the five-bit sample-adaptive band-offset starting position.
|
|||
/// </summary>
|
|||
/// <returns>The decoded band position.</returns>
|
|||
public int ReadSampleAdaptiveOffsetBandPosition() => (int)this.decoder.ReadBypassBits(5); |
|||
|
|||
/// <summary>
|
|||
/// Decodes the two-bit sample-adaptive edge-offset class.
|
|||
/// </summary>
|
|||
/// <returns>The decoded edge class.</returns>
|
|||
public int ReadSampleAdaptiveOffsetEdgeClass() => (int)this.decoder.ReadBypassBits(2); |
|||
|
|||
/// <summary>
|
|||
/// Decodes a sample-adaptive band-offset sign.
|
|||
/// </summary>
|
|||
/// <returns><see langword="true"/> for a negative offset; otherwise, <see langword="false"/>.</returns>
|
|||
public bool ReadSampleAdaptiveOffsetSign() => this.decoder.ReadBypass(); |
|||
|
|||
/// <summary>
|
|||
/// Decodes a horizontal last-significant-coefficient prefix flag.
|
|||
/// </summary>
|
|||
/// <param name="isChroma">A value indicating whether the coefficient belongs to a chroma channel.</param>
|
|||
/// <param name="contextIndex">The block-size and prefix-derived context index within the channel.</param>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadLastSignificantX(bool isChroma, int contextIndex) |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.LastSignificantX; |
|||
return this.decoder.ReadDecision(ref selectedContexts[(isChroma ? 15 : 0) + contextIndex]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a vertical last-significant-coefficient prefix flag.
|
|||
/// </summary>
|
|||
/// <param name="isChroma">A value indicating whether the coefficient belongs to a chroma channel.</param>
|
|||
/// <param name="contextIndex">The block-size and prefix-derived context index within the channel.</param>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadLastSignificantY(bool isChroma, int contextIndex) |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.LastSignificantY; |
|||
return this.decoder.ReadDecision(ref selectedContexts[(isChroma ? 15 : 0) + contextIndex]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a significant-coefficient-group flag.
|
|||
/// </summary>
|
|||
/// <param name="isChroma">A value indicating whether the coefficient group belongs to a chroma channel.</param>
|
|||
/// <param name="contextIndex">The neighboring-group-derived context index.</param>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadSignificantCoefficientGroup(bool isChroma, int contextIndex) |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.SignificantCoefficientGroup; |
|||
return this.decoder.ReadDecision(ref selectedContexts[(isChroma ? 2 : 0) + contextIndex]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a significant-coefficient flag.
|
|||
/// </summary>
|
|||
/// <param name="isChroma">A value indicating whether the coefficient belongs to a chroma channel.</param>
|
|||
/// <param name="contextIndex">The scan-position-derived context index within the channel.</param>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadSignificantCoefficient(bool isChroma, int contextIndex) |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.SignificantCoefficient; |
|||
return this.decoder.ReadDecision(ref selectedContexts[(isChroma ? 28 : 0) + contextIndex]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes whether a significant coefficient has an absolute level greater than one.
|
|||
/// </summary>
|
|||
/// <param name="isChroma">A value indicating whether the coefficient belongs to a chroma channel.</param>
|
|||
/// <param name="contextIndex">The coefficient-group and preceding-level-derived context index.</param>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadCoefficientGreaterThanOne(bool isChroma, int contextIndex) |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.GreaterThanOne; |
|||
return this.decoder.ReadDecision(ref selectedContexts[(isChroma ? 16 : 0) + contextIndex]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes whether the first eligible coefficient has an absolute level greater than two.
|
|||
/// </summary>
|
|||
/// <param name="isChroma">A value indicating whether the coefficient belongs to a chroma channel.</param>
|
|||
/// <param name="contextIndex">The coefficient-group-derived context index within the channel.</param>
|
|||
/// <returns>The decoded flag value.</returns>
|
|||
public bool ReadCoefficientGreaterThanTwo(bool isChroma, int contextIndex) |
|||
{ |
|||
Span<HevcCabacContext> selectedContexts = this.contexts.GreaterThanTwo; |
|||
return this.decoder.ReadDecision(ref selectedContexts[(isChroma ? 4 : 0) + contextIndex]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes an absolute coefficient-level remainder.
|
|||
/// </summary>
|
|||
/// <param name="riceParameter">The current Golomb-Rice parameter.</param>
|
|||
/// <param name="useLimitedPrefixLength">
|
|||
/// A value indicating whether extended-precision processing limits the prefix length.
|
|||
/// </param>
|
|||
/// <param name="maximumLog2TransformDynamicRange">The channel's maximum transform dynamic range.</param>
|
|||
/// <returns>The decoded nonnegative coefficient-level remainder.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The coded remainder exceeds a 32-bit unsigned value.</exception>
|
|||
public uint ReadCoefficientRemaining( |
|||
int riceParameter, |
|||
bool useLimitedPrefixLength, |
|||
int maximumLog2TransformDynamicRange) |
|||
{ |
|||
int longestPrefix = useLimitedPrefixLength |
|||
? 32 - maximumLog2TransformDynamicRange |
|||
: int.MaxValue; |
|||
|
|||
// Extended-precision streams cap the unary prefix at the transform dynamic range. Reaching that cap
|
|||
// implies the end of the prefix even when the final bypass bin is one, so no terminating zero is required.
|
|||
int prefix = 0; |
|||
while (prefix < longestPrefix && this.decoder.ReadBypass()) |
|||
{ |
|||
prefix++; |
|||
} |
|||
|
|||
if (prefix < CoefficientRemainingReduction) |
|||
{ |
|||
uint suffix = this.decoder.ReadBypassBits(riceParameter); |
|||
ulong value = ((ulong)prefix << riceParameter) + suffix; |
|||
if (value > uint.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC coefficient level is too large."); |
|||
} |
|||
|
|||
return (uint)value; |
|||
} |
|||
|
|||
int prefixLength = prefix - CoefficientRemainingReduction; |
|||
int suffixLength; |
|||
if (useLimitedPrefixLength) |
|||
{ |
|||
int maximumPrefixLength = 32 |
|||
- (CoefficientRemainingReduction + maximumLog2TransformDynamicRange); |
|||
|
|||
suffixLength = prefixLength == maximumPrefixLength |
|||
? maximumLog2TransformDynamicRange - riceParameter |
|||
: prefixLength; |
|||
} |
|||
else |
|||
{ |
|||
suffixLength = prefixLength; |
|||
} |
|||
|
|||
int codedSuffixLength = suffixLength + riceParameter; |
|||
if (prefixLength >= 32 || codedSuffixLength > 32) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC coefficient level is too large."); |
|||
} |
|||
|
|||
// Prefixes beyond the first three represent an exponential-Golomb basis; the Rice parameter scales both
|
|||
// that basis and the suffix while the bounded arithmetic reader supplies the remaining low bits.
|
|||
uint codeWord = this.decoder.ReadBypassBits(codedSuffixLength); |
|||
ulong baseValue = (((1UL << prefixLength) - 1) + CoefficientRemainingReduction) << riceParameter; |
|||
ulong result = baseValue + codeWord; |
|||
if (result > uint.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC coefficient level is too large."); |
|||
} |
|||
|
|||
return (uint)result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes a most-significant-bit-first sequence of equal-probability flags.
|
|||
/// </summary>
|
|||
/// <param name="bitCount">The number of flags to decode.</param>
|
|||
/// <returns>The decoded unsigned value.</returns>
|
|||
public uint ReadBypassBits(int bitCount) => this.decoder.ReadBypassBits(bitCount); |
|||
|
|||
/// <summary>
|
|||
/// Selects the byte-aligned range used by aligned bypass syntax.
|
|||
/// </summary>
|
|||
public void AlignBypass() => this.decoder.AlignBypass(); |
|||
|
|||
/// <summary>
|
|||
/// Decodes the flag that terminates a coding-tree block or entropy substream.
|
|||
/// </summary>
|
|||
/// <returns>The decoded termination flag.</returns>
|
|||
public bool ReadTerminate() => this.decoder.ReadTerminate(); |
|||
|
|||
/// <summary>
|
|||
/// Validates the stop bit and zero padding after a terminating entropy-coded value.
|
|||
/// </summary>
|
|||
/// <exception cref="InvalidImageContentException">The entropy substream has invalid termination alignment.</exception>
|
|||
public readonly void ValidateTerminationAlignment() => this.decoder.ValidateTerminationAlignment(); |
|||
|
|||
/// <summary>
|
|||
/// Decodes a context-adaptive truncated-unary value.
|
|||
/// </summary>
|
|||
/// <param name="selectedContexts">The context set selected for the syntax element.</param>
|
|||
/// <param name="firstContextIndex">The context used by the first binary decision.</param>
|
|||
/// <param name="continuationContextIndex">The context used by each subsequent decision.</param>
|
|||
/// <param name="maximumValue">The inclusive maximum decoded value.</param>
|
|||
/// <returns>The decoded truncated-unary value.</returns>
|
|||
private uint ReadTruncatedUnary( |
|||
Span<HevcCabacContext> selectedContexts, |
|||
int firstContextIndex, |
|||
int continuationContextIndex, |
|||
int maximumValue) |
|||
{ |
|||
if (maximumValue == 0 |
|||
|| !this.decoder.ReadDecision(ref selectedContexts[firstContextIndex])) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
uint value = 1; |
|||
while (value < maximumValue |
|||
&& this.decoder.ReadDecision(ref selectedContexts[continuationContextIndex])) |
|||
{ |
|||
value++; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes an equal-probability exponential-Golomb value.
|
|||
/// </summary>
|
|||
/// <param name="order">The initial suffix width.</param>
|
|||
/// <returns>The decoded unsigned value.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The coded value exceeds a 32-bit unsigned value.</exception>
|
|||
private uint ReadBypassExponentialGolomb(int order) |
|||
{ |
|||
ulong value = 0; |
|||
int suffixWidth = order; |
|||
while (this.decoder.ReadBypass()) |
|||
{ |
|||
if (suffixWidth >= 32) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC exponential-Golomb value is too large."); |
|||
} |
|||
|
|||
value += 1UL << suffixWidth; |
|||
suffixWidth++; |
|||
} |
|||
|
|||
// Each leading one adds the basis for the current order and widens the final suffix by one bit.
|
|||
value += this.decoder.ReadBypassBits(suffixWidth); |
|||
if (value > uint.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC exponential-Golomb value is too large."); |
|||
} |
|||
|
|||
return (uint)value; |
|||
} |
|||
} |
|||
@ -1,40 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the location of a 4:2:0 chroma sample relative to its associated two-by-two luma sample region.
|
|||
/// </summary>
|
|||
internal enum HevcChromaSampleLocation : byte |
|||
{ |
|||
/// <summary>
|
|||
/// The chroma sample is horizontally co-sited with the left luma column and vertically centered.
|
|||
/// </summary>
|
|||
Left = 0, |
|||
|
|||
/// <summary>
|
|||
/// The chroma sample is horizontally and vertically centered.
|
|||
/// </summary>
|
|||
Center = 1, |
|||
|
|||
/// <summary>
|
|||
/// The chroma sample is co-sited with the top-left luma sample.
|
|||
/// </summary>
|
|||
TopLeft = 2, |
|||
|
|||
/// <summary>
|
|||
/// The chroma sample is horizontally centered and co-sited with the top luma row.
|
|||
/// </summary>
|
|||
Top = 3, |
|||
|
|||
/// <summary>
|
|||
/// The chroma sample is horizontally co-sited with the left luma column and vertically co-sited with the bottom luma row.
|
|||
/// </summary>
|
|||
BottomLeft = 4, |
|||
|
|||
/// <summary>
|
|||
/// The chroma sample is horizontally centered and vertically co-sited with the bottom luma row.
|
|||
/// </summary>
|
|||
Bottom = 5, |
|||
} |
|||
@ -1,381 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers.Binary; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the image-description fields and parameter-set arrays stored in an HEVC codec-configuration item
|
|||
/// property.
|
|||
/// </summary>
|
|||
internal sealed class HevcCodecConfiguration |
|||
{ |
|||
/// <summary>
|
|||
/// The NAL-unit arrays carried by the codec-configuration property.
|
|||
/// </summary>
|
|||
private readonly HevcNalUnitArray[] nalUnitArrays; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcCodecConfiguration"/> class from an HEVC
|
|||
/// codec-configuration item-property payload.
|
|||
/// </summary>
|
|||
/// <param name="data">The complete bounded configuration payload.</param>
|
|||
public HevcCodecConfiguration(ReadOnlySpan<byte> data) |
|||
{ |
|||
const int fixedRecordLength = 23; |
|||
if (data.Length < fixedRecordLength) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC codec configuration is truncated."); |
|||
} |
|||
|
|||
int offset = 0; |
|||
if (data[offset++] != 1) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC codec configuration has an unsupported version."); |
|||
} |
|||
|
|||
byte profile = data[offset++]; |
|||
this.GeneralProfileSpace = (byte)(profile >> 6); |
|||
this.GeneralTierFlag = (profile & 0x20) != 0; |
|||
this.GeneralProfileIdc = (byte)(profile & 0x1F); |
|||
this.GeneralProfileCompatibilityFlags = BinaryPrimitives.ReadUInt32BigEndian(data[offset..]); |
|||
offset += 4; |
|||
this.GeneralConstraintIndicatorFlags = ((ulong)BinaryPrimitives.ReadUInt32BigEndian(data[offset..]) << 16) |
|||
| BinaryPrimitives.ReadUInt16BigEndian(data[(offset + 4)..]); |
|||
|
|||
offset += 6; |
|||
this.GeneralLevelIdc = data[offset++]; |
|||
|
|||
ushort spatialSegmentation = BinaryPrimitives.ReadUInt16BigEndian(data[offset..]); |
|||
offset += 2; |
|||
byte parallelism = data[offset++]; |
|||
byte chromaFormat = data[offset++]; |
|||
byte lumaBitDepth = data[offset++]; |
|||
byte chromaBitDepth = data[offset++]; |
|||
if ((spatialSegmentation & 0xF000) != 0xF000 |
|||
|| (parallelism & 0xFC) != 0xFC |
|||
|| (chromaFormat & 0xFC) != 0xFC |
|||
|| (lumaBitDepth & 0xF8) != 0xF8 |
|||
|| (chromaBitDepth & 0xF8) != 0xF8) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC codec configuration has invalid reserved bits."); |
|||
} |
|||
|
|||
this.ChromaFormat = (byte)(chromaFormat & 3); |
|||
this.BitDepthLuma = 8 + (lumaBitDepth & 7); |
|||
this.BitDepthChroma = 8 + (chromaBitDepth & 7); |
|||
|
|||
// Reject precisions outside the public HEIF profile matrix before an unrepresentable value can enter the
|
|||
// typed image metadata or reach a sample pipeline that only implements 8, 10, and 12-bit arithmetic.
|
|||
if (this.BitDepthLuma is not 8 and not 10 and not 12 |
|||
|| (this.ChromaFormat != 0 && this.BitDepthChroma is not 8 and not 10 and not 12)) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC codec configuration uses an unsupported component bit depth."); |
|||
} |
|||
|
|||
// Average frame rate and temporal-layer signaling describe timed samples. Consume those fixed-record fields
|
|||
// to reach the image item's NAL length width without retaining playback state in the still-image model.
|
|||
offset += 2; |
|||
byte temporalAndLengthFields = data[offset++]; |
|||
int temporalLayerCount = (temporalAndLengthFields >> 3) & 7; |
|||
bool temporalIdNested = (temporalAndLengthFields & 4) != 0; |
|||
this.NalUnitLengthSize = (temporalAndLengthFields & 3) + 1; |
|||
|
|||
int arrayCount = data[offset++]; |
|||
this.nalUnitArrays = new HevcNalUnitArray[arrayCount]; |
|||
Span<bool> seenNalUnitTypes = stackalloc bool[64]; |
|||
for (int arrayIndex = 0; arrayIndex < arrayCount; arrayIndex++) |
|||
{ |
|||
if (data.Length - offset < 3) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC codec configuration contains a truncated NAL-unit array header."); |
|||
} |
|||
|
|||
byte arrayHeader = data[offset++]; |
|||
if ((arrayHeader & 0x40) != 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC codec configuration NAL-unit array has a nonzero reserved bit."); |
|||
} |
|||
|
|||
bool isComplete = (arrayHeader & 0x80) != 0; |
|||
byte nalUnitType = (byte)(arrayHeader & 0x3F); |
|||
if (seenNalUnitTypes[nalUnitType]) |
|||
{ |
|||
throw new InvalidImageContentException($"The HEVC codec configuration contains more than one array for NAL-unit type {nalUnitType}."); |
|||
} |
|||
|
|||
seenNalUnitTypes[nalUnitType] = true; |
|||
int nalUnitCount = BinaryPrimitives.ReadUInt16BigEndian(data[offset..]); |
|||
offset += 2; |
|||
HevcNalUnit[] nalUnits = new HevcNalUnit[nalUnitCount]; |
|||
for (int nalUnitIndex = 0; nalUnitIndex < nalUnitCount; nalUnitIndex++) |
|||
{ |
|||
if (data.Length - offset < 2) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC codec configuration contains a truncated NAL-unit length."); |
|||
} |
|||
|
|||
int nalUnitLength = BinaryPrimitives.ReadUInt16BigEndian(data[offset..]); |
|||
offset += 2; |
|||
if (nalUnitLength < 2 || nalUnitLength > data.Length - offset) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC codec configuration contains an invalid NAL-unit length."); |
|||
} |
|||
|
|||
HevcNalUnit nalUnit = new(data.Slice(offset, nalUnitLength)); |
|||
|
|||
// The array header repeats the type so a damaged or misrouted parameter set is rejected before
|
|||
// its RBSP syntax can affect the image configuration.
|
|||
if (nalUnit.Header.NalUnitType != nalUnitType) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC codec configuration NAL-unit type does not match its array."); |
|||
} |
|||
|
|||
nalUnits[nalUnitIndex] = nalUnit; |
|||
offset += nalUnitLength; |
|||
} |
|||
|
|||
this.nalUnitArrays[arrayIndex] = new HevcNalUnitArray(nalUnitType, isComplete, nalUnits); |
|||
} |
|||
|
|||
if (offset != data.Length) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC codec configuration contains unexpected trailing data."); |
|||
} |
|||
|
|||
List<HevcVideoParameterSet> videoParameterSets = new(); |
|||
foreach (HevcNalUnitArray nalUnitArray in this.nalUnitArrays) |
|||
{ |
|||
const byte videoParameterSetNalUnitType = 32; |
|||
if (nalUnitArray.NalUnitType != videoParameterSetNalUnitType) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
foreach (HevcNalUnit nalUnit in nalUnitArray.NalUnits) |
|||
{ |
|||
HevcVideoParameterSet videoParameterSet = new(nalUnit); |
|||
|
|||
// Legacy HEIC muxers commonly preserve only the original four source/packing constraint bits in
|
|||
// hvcC and zero later profile-specific constraint bits. SPS validation provides the authoritative
|
|||
// chroma and bit-depth checks, so do not reject otherwise matching Range Extensions images here.
|
|||
if (!videoParameterSet.ProfileTierLevel.Matches(this) |
|||
|| (temporalLayerCount != 0 && videoParameterSet.MaxSubLayers != temporalLayerCount) |
|||
|| (temporalLayerCount != 0 && videoParameterSet.TemporalIdNestingFlag != temporalIdNested)) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC video parameter set does not match its codec configuration."); |
|||
} |
|||
|
|||
videoParameterSets.Add(videoParameterSet); |
|||
} |
|||
} |
|||
|
|||
this.VideoParameterSets = videoParameterSets; |
|||
|
|||
List<HevcSequenceParameterSet> sequenceParameterSets = new(); |
|||
foreach (HevcNalUnitArray nalUnitArray in this.nalUnitArrays) |
|||
{ |
|||
const byte sequenceParameterSetNalUnitType = 33; |
|||
if (nalUnitArray.NalUnitType != sequenceParameterSetNalUnitType) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
foreach (HevcNalUnit nalUnit in nalUnitArray.NalUnits) |
|||
{ |
|||
HevcSequenceParameterSet sequenceParameterSet = new(nalUnit); |
|||
bool referencesKnownVideoParameterSet = false; |
|||
foreach (HevcVideoParameterSet videoParameterSet in videoParameterSets) |
|||
{ |
|||
referencesKnownVideoParameterSet |= videoParameterSet.Id == sequenceParameterSet.VideoParameterSetId; |
|||
} |
|||
|
|||
if (!referencesKnownVideoParameterSet |
|||
|| !sequenceParameterSet.ProfileTierLevel.Matches(this) |
|||
|| sequenceParameterSet.ChromaFormat != this.ChromaFormat |
|||
|| sequenceParameterSet.BitDepthLuma != this.BitDepthLuma |
|||
|| sequenceParameterSet.BitDepthChroma != this.BitDepthChroma |
|||
|| (temporalLayerCount != 0 && sequenceParameterSet.MaxSubLayers != temporalLayerCount) |
|||
|| (temporalLayerCount != 0 && sequenceParameterSet.TemporalIdNestingFlag != temporalIdNested)) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC sequence parameter set does not match its codec configuration."); |
|||
} |
|||
|
|||
sequenceParameterSets.Add(sequenceParameterSet); |
|||
} |
|||
} |
|||
|
|||
this.SequenceParameterSets = sequenceParameterSets; |
|||
|
|||
List<HevcPictureParameterSet> pictureParameterSets = new(); |
|||
foreach (HevcNalUnitArray nalUnitArray in this.nalUnitArrays) |
|||
{ |
|||
const byte pictureParameterSetNalUnitType = 34; |
|||
if (nalUnitArray.NalUnitType != pictureParameterSetNalUnitType) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
foreach (HevcNalUnit nalUnit in nalUnitArray.NalUnits) |
|||
{ |
|||
pictureParameterSets.Add(new HevcPictureParameterSet(nalUnit, sequenceParameterSets)); |
|||
} |
|||
} |
|||
|
|||
this.PictureParameterSets = pictureParameterSets; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the profile namespace declared by the coded image.
|
|||
/// </summary>
|
|||
public byte GeneralProfileSpace { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the coded image uses the high tier.
|
|||
/// </summary>
|
|||
public bool GeneralTierFlag { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the profile identifier declared by the coded image.
|
|||
/// </summary>
|
|||
public byte GeneralProfileIdc { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the profile-compatibility flags declared by the coded image.
|
|||
/// </summary>
|
|||
public uint GeneralProfileCompatibilityFlags { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the 48-bit profile-constraint flags declared by the coded image.
|
|||
/// </summary>
|
|||
public ulong GeneralConstraintIndicatorFlags { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the level identifier declared by the coded image.
|
|||
/// </summary>
|
|||
public byte GeneralLevelIdc { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded chroma format, where zero denotes monochrome and one through three denote 4:2:0, 4:2:2,
|
|||
/// and 4:4:4 respectively.
|
|||
/// </summary>
|
|||
public byte ChromaFormat { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded luma sample precision in bits.
|
|||
/// </summary>
|
|||
public int BitDepthLuma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded chroma sample precision in bits.
|
|||
/// </summary>
|
|||
public int BitDepthChroma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum coded color-component precision in bits.
|
|||
/// </summary>
|
|||
public HeifBitDepth BitDepth |
|||
=> (HeifBitDepth)(this.IsMonochrome ? this.BitDepthLuma : Math.Max(this.BitDepthLuma, this.BitDepthChroma)); |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the coded image contains only a luma plane.
|
|||
/// </summary>
|
|||
public bool IsMonochrome => this.ChromaFormat == 0; |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of bytes used by each length-delimited NAL unit in the associated image item.
|
|||
/// </summary>
|
|||
public int NalUnitLengthSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the bounded NAL-unit arrays carried by the codec-configuration property.
|
|||
/// </summary>
|
|||
public IReadOnlyList<HevcNalUnitArray> NalUnitArrays => this.nalUnitArrays; |
|||
|
|||
/// <summary>
|
|||
/// Gets the validated video parameter sets carried by the codec-configuration property.
|
|||
/// </summary>
|
|||
public IReadOnlyList<HevcVideoParameterSet> VideoParameterSets { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the validated sequence parameter sets carried by the codec-configuration property.
|
|||
/// </summary>
|
|||
public IReadOnlyList<HevcSequenceParameterSet> SequenceParameterSets { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the validated picture parameter sets carried by the codec-configuration property.
|
|||
/// </summary>
|
|||
public IReadOnlyList<HevcPictureParameterSet> PictureParameterSets { get; } |
|||
|
|||
/// <summary>
|
|||
/// Validates the associated pixel-information property against the coded luma and chroma sample precisions.
|
|||
/// </summary>
|
|||
/// <param name="channelBitDepths">The per-channel precisions associated with the HEVC image item.</param>
|
|||
public void ValidateChannelBitDepths(ReadOnlySpan<byte> channelBitDepths) |
|||
{ |
|||
int expectedChannelCount = this.IsMonochrome ? 1 : 3; |
|||
if (channelBitDepths.Length != expectedChannelCount || channelBitDepths[0] != this.BitDepthLuma) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC item pixel information does not match its codec configuration."); |
|||
} |
|||
|
|||
for (int channel = 1; channel < channelBitDepths.Length; channel++) |
|||
{ |
|||
if (channelBitDepths[channel] != this.BitDepthChroma) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC item pixel information does not match its codec configuration."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether another configuration describes the same coded-image sample layout.
|
|||
/// </summary>
|
|||
/// <param name="other">The configuration to compare.</param>
|
|||
/// <returns><see langword="true"/> when the profile, level, chroma format, and sample precisions match.</returns>
|
|||
public bool HasMatchingImageConfiguration(HevcCodecConfiguration other) |
|||
=> this.GeneralProfileSpace == other.GeneralProfileSpace |
|||
&& this.GeneralTierFlag == other.GeneralTierFlag |
|||
&& this.GeneralProfileIdc == other.GeneralProfileIdc |
|||
&& this.GeneralProfileCompatibilityFlags == other.GeneralProfileCompatibilityFlags |
|||
&& this.GeneralConstraintIndicatorFlags == other.GeneralConstraintIndicatorFlags |
|||
&& this.GeneralLevelIdc == other.GeneralLevelIdc |
|||
&& this.ChromaFormat == other.ChromaFormat |
|||
&& this.BitDepthLuma == other.BitDepthLuma |
|||
&& this.BitDepthChroma == other.BitDepthChroma; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains every configuration NAL unit declared for one HEVC NAL-unit type.
|
|||
/// </summary>
|
|||
internal sealed class HevcNalUnitArray |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcNalUnitArray"/> class.
|
|||
/// </summary>
|
|||
/// <param name="nalUnitType">The six-bit HEVC NAL-unit type.</param>
|
|||
/// <param name="isComplete">A value indicating whether the array contains every NAL unit of this type.</param>
|
|||
/// <param name="nalUnits">The decoded bounded NAL units.</param>
|
|||
public HevcNalUnitArray(byte nalUnitType, bool isComplete, HevcNalUnit[] nalUnits) |
|||
{ |
|||
this.NalUnitType = nalUnitType; |
|||
this.IsComplete = isComplete; |
|||
this.NalUnits = nalUnits; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the six-bit HEVC NAL-unit type shared by every entry in the array.
|
|||
/// </summary>
|
|||
public byte NalUnitType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the array contains every NAL unit of this type for the coded image.
|
|||
/// </summary>
|
|||
public bool IsComplete { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the decoded NAL units.
|
|||
/// </summary>
|
|||
public IReadOnlyList<HevcNalUnit> NalUnits { get; } |
|||
} |
|||
@ -1,53 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains one or two coded-block flags for a square or vertically split HEVC component transform section.
|
|||
/// </summary>
|
|||
internal readonly struct HevcCodedBlockFlags |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcCodedBlockFlags"/> struct for one square block.
|
|||
/// </summary>
|
|||
/// <param name="first">The square block's coded-block flag.</param>
|
|||
public HevcCodedBlockFlags(bool first) |
|||
{ |
|||
this.First = first; |
|||
this.Second = false; |
|||
this.IsSplit = false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcCodedBlockFlags"/> struct for two rectangular sub-blocks.
|
|||
/// </summary>
|
|||
/// <param name="first">The first square sub-block's coded-block flag.</param>
|
|||
/// <param name="second">The second square sub-block's coded-block flag.</param>
|
|||
public HevcCodedBlockFlags(bool first, bool second) |
|||
{ |
|||
this.First = first; |
|||
this.Second = second; |
|||
this.IsSplit = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the first or only coefficient block contains coded residual data.
|
|||
/// </summary>
|
|||
public bool First { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the second rectangular sub-block contains coded residual data.
|
|||
/// </summary>
|
|||
public bool Second { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether two square sub-block flags are present.
|
|||
/// </summary>
|
|||
public bool IsSplit { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether either governed coefficient block contains coded residual data.
|
|||
/// </summary>
|
|||
public bool Any => this.First || this.Second; |
|||
} |
|||
@ -1,217 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Stores the spatial coding-unit state required by later HEVC still-picture syntax and reconstruction stages.
|
|||
/// </summary>
|
|||
internal sealed class HevcCodingTreeState : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The coding-unit flag indicating transform and quantization bypass.
|
|||
/// </summary>
|
|||
private const byte TransquantBypassFlag = 1 << 0; |
|||
|
|||
/// <summary>
|
|||
/// The coding-unit flag indicating pulse-code-modulated samples.
|
|||
/// </summary>
|
|||
private const byte PcmFlag = 1 << 1; |
|||
|
|||
/// <summary>
|
|||
/// The decoded coding-unit depth at minimum-coding-block resolution.
|
|||
/// </summary>
|
|||
private readonly Buffer2D<byte> depths; |
|||
|
|||
/// <summary>
|
|||
/// The effective luma quantization parameter at minimum-coding-block resolution.
|
|||
/// </summary>
|
|||
private readonly Buffer2D<sbyte> quantizationParameters; |
|||
|
|||
/// <summary>
|
|||
/// The packed bypass and PCM flags at minimum-coding-block resolution.
|
|||
/// </summary>
|
|||
private readonly Buffer2D<byte> flags; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcCodingTreeState"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration providing the image memory allocator.</param>
|
|||
/// <param name="sequenceParameterSet">The coded picture and minimum coding-block geometry.</param>
|
|||
public HevcCodingTreeState(Configuration configuration, HevcSequenceParameterSet sequenceParameterSet) |
|||
{ |
|||
this.MinCodingBlockLog2 = sequenceParameterSet.MinCodingBlockLog2; |
|||
this.WidthInMinCodingBlocks = DivideCeilingByPowerOfTwo( |
|||
sequenceParameterSet.Width, |
|||
this.MinCodingBlockLog2); |
|||
|
|||
this.HeightInMinCodingBlocks = DivideCeilingByPowerOfTwo( |
|||
sequenceParameterSet.Height, |
|||
this.MinCodingBlockLog2); |
|||
|
|||
Buffer2D<byte>? depths = null; |
|||
Buffer2D<sbyte>? quantizationParameters = null; |
|||
Buffer2D<byte>? flags = null; |
|||
try |
|||
{ |
|||
depths = configuration.MemoryAllocator.Allocate2D<byte>( |
|||
this.WidthInMinCodingBlocks, |
|||
this.HeightInMinCodingBlocks); |
|||
|
|||
quantizationParameters = configuration.MemoryAllocator.Allocate2D<sbyte>( |
|||
this.WidthInMinCodingBlocks, |
|||
this.HeightInMinCodingBlocks); |
|||
|
|||
flags = configuration.MemoryAllocator.Allocate2D<byte>( |
|||
this.WidthInMinCodingBlocks, |
|||
this.HeightInMinCodingBlocks); |
|||
|
|||
this.depths = depths; |
|||
this.quantizationParameters = quantizationParameters; |
|||
this.flags = flags; |
|||
} |
|||
catch |
|||
{ |
|||
flags?.Dispose(); |
|||
quantizationParameters?.Dispose(); |
|||
depths?.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the state map's luma sample unit.
|
|||
/// </summary>
|
|||
public int MinCodingBlockLog2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the state-map width in minimum coding blocks.
|
|||
/// </summary>
|
|||
public int WidthInMinCodingBlocks { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the state-map height in minimum coding blocks.
|
|||
/// </summary>
|
|||
public int HeightInMinCodingBlocks { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the split-flag context derived from available left and above coding units.
|
|||
/// </summary>
|
|||
/// <param name="x">The current coding-unit left coordinate in luma samples.</param>
|
|||
/// <param name="y">The current coding-unit top coordinate in luma samples.</param>
|
|||
/// <param name="depth">The current coding-tree depth.</param>
|
|||
/// <param name="leftAvailable">A value indicating whether the left coding unit is available for prediction.</param>
|
|||
/// <param name="aboveAvailable">A value indicating whether the above coding unit is available for prediction.</param>
|
|||
/// <returns>The split context in the inclusive range zero through two.</returns>
|
|||
public int GetSplitContext(int x, int y, int depth, bool leftAvailable, bool aboveAvailable) |
|||
{ |
|||
int unitX = x >> this.MinCodingBlockLog2; |
|||
int unitY = y >> this.MinCodingBlockLog2; |
|||
int context = 0; |
|||
if (leftAvailable && this.depths.DangerousGetRowSpan(unitY)[unitX - 1] > depth) |
|||
{ |
|||
context++; |
|||
} |
|||
|
|||
if (aboveAvailable && this.depths.DangerousGetRowSpan(unitY - 1)[unitX] > depth) |
|||
{ |
|||
context++; |
|||
} |
|||
|
|||
return context; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Records the state shared by every minimum coding block covered by one leaf coding unit.
|
|||
/// </summary>
|
|||
/// <param name="x">The coding-unit left coordinate in luma samples.</param>
|
|||
/// <param name="y">The coding-unit top coordinate in luma samples.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the square coding-unit size.</param>
|
|||
/// <param name="depth">The coding-tree depth.</param>
|
|||
/// <param name="quantizationParameter">The effective luma quantization parameter.</param>
|
|||
/// <param name="transquantBypass">A value indicating whether transform and quantization are bypassed.</param>
|
|||
/// <param name="pcm">A value indicating whether the coding unit contains pulse-code-modulated samples.</param>
|
|||
public void SetCodingUnit( |
|||
int x, |
|||
int y, |
|||
int log2Size, |
|||
int depth, |
|||
int quantizationParameter, |
|||
bool transquantBypass, |
|||
bool pcm) |
|||
{ |
|||
int unitX = x >> this.MinCodingBlockLog2; |
|||
int unitY = y >> this.MinCodingBlockLog2; |
|||
int unitCount = 1 << (log2Size - this.MinCodingBlockLog2); |
|||
int endX = Math.Min(unitX + unitCount, this.WidthInMinCodingBlocks); |
|||
int endY = Math.Min(unitY + unitCount, this.HeightInMinCodingBlocks); |
|||
byte packedFlags = (byte)((transquantBypass ? TransquantBypassFlag : 0) | (pcm ? PcmFlag : 0)); |
|||
|
|||
// Edge coding units still cover a complete power-of-two block in syntax, but the state map contains only
|
|||
// displayed picture coordinates. Clipping here keeps later neighbor lookup within the owned picture state.
|
|||
for (int row = unitY; row < endY; row++) |
|||
{ |
|||
this.depths.DangerousGetRowSpan(row)[unitX..endX].Fill((byte)depth); |
|||
this.quantizationParameters.DangerousGetRowSpan(row)[unitX..endX].Fill((sbyte)quantizationParameter); |
|||
this.flags.DangerousGetRowSpan(row)[unitX..endX].Fill(packedFlags); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the recorded coding-tree depth at a luma sample coordinate.
|
|||
/// </summary>
|
|||
/// <param name="x">The luma sample X coordinate.</param>
|
|||
/// <param name="y">The luma sample Y coordinate.</param>
|
|||
/// <returns>The leaf coding-unit depth.</returns>
|
|||
public int GetDepth(int x, int y) |
|||
=> this.depths.DangerousGetRowSpan(y >> this.MinCodingBlockLog2)[x >> this.MinCodingBlockLog2]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the effective luma quantization parameter at a luma sample coordinate.
|
|||
/// </summary>
|
|||
/// <param name="x">The luma sample X coordinate.</param>
|
|||
/// <param name="y">The luma sample Y coordinate.</param>
|
|||
/// <returns>The effective luma quantization parameter.</returns>
|
|||
public int GetQuantizationParameter(int x, int y) |
|||
=> this.quantizationParameters.DangerousGetRowSpan(y >> this.MinCodingBlockLog2)[x >> this.MinCodingBlockLog2]; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the coding unit at a luma sample coordinate bypasses transform and quantization.
|
|||
/// </summary>
|
|||
/// <param name="x">The luma sample X coordinate.</param>
|
|||
/// <param name="y">The luma sample Y coordinate.</param>
|
|||
/// <returns><see langword="true"/> when bypass is enabled; otherwise, <see langword="false"/>.</returns>
|
|||
public bool IsTransquantBypass(int x, int y) |
|||
=> (this.flags.DangerousGetRowSpan(y >> this.MinCodingBlockLog2)[x >> this.MinCodingBlockLog2] |
|||
& TransquantBypassFlag) != 0; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the coding unit at a luma sample coordinate contains PCM samples.
|
|||
/// </summary>
|
|||
/// <param name="x">The luma sample X coordinate.</param>
|
|||
/// <param name="y">The luma sample Y coordinate.</param>
|
|||
/// <returns><see langword="true"/> for pulse-code-modulated samples; otherwise, <see langword="false"/>.</returns>
|
|||
public bool IsPcm(int x, int y) |
|||
=> (this.flags.DangerousGetRowSpan(y >> this.MinCodingBlockLog2)[x >> this.MinCodingBlockLog2] |
|||
& PcmFlag) != 0; |
|||
|
|||
/// <summary>
|
|||
/// Releases the owned coding-tree state maps.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.depths.Dispose(); |
|||
this.quantizationParameters.Dispose(); |
|||
this.flags.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Divides a nonnegative sample count by a power of two with upward rounding.
|
|||
/// </summary>
|
|||
/// <param name="value">The sample count.</param>
|
|||
/// <param name="shift">The base-two divisor logarithm.</param>
|
|||
/// <returns>The upward-rounded quotient.</returns>
|
|||
private static int DivideCeilingByPowerOfTwo(int value, int shift) => (value + (1 << shift) - 1) >> shift; |
|||
} |
|||
@ -1,346 +0,0 @@ |
|||
// 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>
|
|||
/// <param name="useLumaSyntax">Whether a separately coded color plane uses the luma coefficient context set.</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, |
|||
bool useLumaSyntax = false) |
|||
{ |
|||
HevcSequenceParameterSet sequenceParameterSet = pictureParameterSet.SequenceParameterSet; |
|||
HevcPlane codingPlane = useLumaSyntax ? HevcPlane.Y : plane; |
|||
bool isChroma = codingPlane != HevcPlane.Y; |
|||
bool nonTransformed = transformSkip || transquantBypass; |
|||
HevcCoefficientScanType scanType = SelectScanType( |
|||
width, |
|||
height, |
|||
codingPlane, |
|||
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; |
|||
} |
|||
} |
|||
@ -1,422 +0,0 @@ |
|||
// 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>
|
|||
/// Clears all persistent Rice adaptation statistics for a newly initialized entropy substream.
|
|||
/// </summary>
|
|||
public void ResetRiceAdaptation() => this.riceAdaptationStatistics = default; |
|||
|
|||
/// <summary>
|
|||
/// Copies the four persistent Rice adaptation statistics to caller-owned wavefront state.
|
|||
/// </summary>
|
|||
/// <param name="destination">The four-element destination.</param>
|
|||
public void CopyRiceAdaptationTo(Span<int> destination) => this.riceAdaptationStatistics[..4].CopyTo(destination); |
|||
|
|||
/// <summary>
|
|||
/// Restores the four persistent Rice adaptation statistics captured for a later wavefront row.
|
|||
/// </summary>
|
|||
/// <param name="source">The four saved statistics.</param>
|
|||
public void CopyRiceAdaptationFrom(ReadOnlySpan<int> source) => source[..4].CopyTo(this.riceAdaptationStatistics[..4]); |
|||
|
|||
/// <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; |
|||
int xPrefix = ReadLastSignificantPrefix(ref reader, isChroma, syntaxWidth, true); |
|||
int yPrefix = ReadLastSignificantPrefix(ref reader, isChroma, syntaxHeight, false); |
|||
|
|||
// The HEVC syntax carries both context-coded prefixes before either bypass-coded suffix. Decoding a suffix
|
|||
// immediately after its own prefix changes the arithmetic bit order whenever both coordinates need suffixes.
|
|||
x = ReadLastSignificantSuffix(ref reader, xPrefix); |
|||
y = ReadLastSignificantSuffix(ref reader, yPrefix); |
|||
if (verticalScan) |
|||
{ |
|||
(x, y) = (y, x); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes one context-coded last-significant coefficient coordinate prefix.
|
|||
/// </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 coordinate prefix.</returns>
|
|||
private static int ReadLastSignificantPrefix(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; |
|||
} |
|||
} |
|||
|
|||
return prefix; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Expands one last-significant coordinate prefix with its bypass-coded suffix.
|
|||
/// </summary>
|
|||
/// <param name="reader">The current entropy-substream syntax reader.</param>
|
|||
/// <param name="prefix">The context-coded coordinate prefix.</param>
|
|||
/// <returns>The decoded zero-based coefficient coordinate.</returns>
|
|||
private static int ReadLastSignificantSuffix(ref HevcCabacSyntaxReader reader, int prefix) |
|||
{ |
|||
if (prefix <= 3) |
|||
{ |
|||
return prefix; |
|||
} |
|||
|
|||
int suffixLength = (prefix - 2) >> 1; |
|||
return MinimumCoordinateInGroup[prefix] + (int)reader.ReadBypassBits(suffixLength); |
|||
} |
|||
} |
|||
@ -1,150 +0,0 @@ |
|||
// 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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
// 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, |
|||
} |
|||
@ -1,64 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
internal static partial class HevcDeblockingFilter |
|||
{ |
|||
/// <summary>
|
|||
/// Accesses four columns across a horizontal edge.
|
|||
/// </summary>
|
|||
private readonly struct HorizontalEdgeOperator : IEdgeOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<int> LoadVector(HevcPictureBuffer picture, HevcPlane plane, int x, int y, int distance, int count) |
|||
{ |
|||
ref ushort source = ref picture.GetRowSpan(plane, y + distance)[x]; |
|||
if (count == 2) |
|||
{ |
|||
// The packed load used by full-width segments would read two samples beyond a subsampled edge.
|
|||
return Vector128.Create((int)source, Unsafe.Add(ref source, 1), 0, 0); |
|||
} |
|||
|
|||
Vector64<ushort> packed = Unsafe.As<ushort, Vector64<ushort>>(ref source); |
|||
return Vector128.WidenLower(Vector128.Create(packed, Vector64<ushort>.Zero)).AsInt32(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void StoreVector( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int distance, |
|||
Vector128<int> value, |
|||
int count) |
|||
{ |
|||
ref ushort destination = ref picture.GetRowSpan(plane, y + distance)[x]; |
|||
if (count == 4) |
|||
{ |
|||
Vector64<ushort> packed = Vector128.Narrow(value, Vector128<int>.Zero).AsUInt16().GetLower(); |
|||
Unsafe.As<ushort, Vector64<ushort>>(ref destination) = packed; |
|||
return; |
|||
} |
|||
|
|||
destination = (ushort)value.GetElement(0); |
|||
Unsafe.Add(ref destination, 1) = (ushort)value.GetElement(1); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int LoadScalar(HevcPictureBuffer picture, HevcPlane plane, int x, int y, int distance, int index) |
|||
=> picture.GetRowSpan(plane, y + distance)[x + index]; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void StoreScalar(HevcPictureBuffer picture, HevcPlane plane, int x, int y, int distance, int index, int value) |
|||
=> picture.GetRowSpan(plane, y + distance)[x + index] = (ushort)value; |
|||
} |
|||
} |
|||
@ -1,70 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
internal static partial class HevcDeblockingFilter |
|||
{ |
|||
/// <summary>
|
|||
/// Defines orientation-specific access to the four samples running along one deblocking edge segment.
|
|||
/// </summary>
|
|||
private interface IEdgeOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Loads samples at one signed distance across the edge.
|
|||
/// </summary>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The first Q-side sample X coordinate.</param>
|
|||
/// <param name="y">The first Q-side sample Y coordinate.</param>
|
|||
/// <param name="distance">The signed sample distance across the edge.</param>
|
|||
/// <param name="count">The number of valid low lanes to load.</param>
|
|||
/// <returns>The widened samples ordered along the edge.</returns>
|
|||
public static abstract Vector128<int> LoadVector(HevcPictureBuffer picture, HevcPlane plane, int x, int y, int distance, int count); |
|||
|
|||
/// <summary>
|
|||
/// Stores four samples at one signed distance across the edge.
|
|||
/// </summary>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The first Q-side sample X coordinate.</param>
|
|||
/// <param name="y">The first Q-side sample Y coordinate.</param>
|
|||
/// <param name="distance">The signed sample distance across the edge.</param>
|
|||
/// <param name="value">The four widened samples ordered along the edge.</param>
|
|||
/// <param name="count">The number of low lanes to store.</param>
|
|||
public static abstract void StoreVector( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int distance, |
|||
Vector128<int> value, |
|||
int count); |
|||
|
|||
/// <summary>
|
|||
/// Loads one scalar sample at a signed distance across and an offset along the edge.
|
|||
/// </summary>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The first Q-side sample X coordinate.</param>
|
|||
/// <param name="y">The first Q-side sample Y coordinate.</param>
|
|||
/// <param name="distance">The signed sample distance across the edge.</param>
|
|||
/// <param name="index">The sample offset along the edge.</param>
|
|||
/// <returns>The selected sample.</returns>
|
|||
public static abstract int LoadScalar(HevcPictureBuffer picture, HevcPlane plane, int x, int y, int distance, int index); |
|||
|
|||
/// <summary>
|
|||
/// Stores one scalar sample at a signed distance across and an offset along the edge.
|
|||
/// </summary>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The first Q-side sample X coordinate.</param>
|
|||
/// <param name="y">The first Q-side sample Y coordinate.</param>
|
|||
/// <param name="distance">The signed sample distance across the edge.</param>
|
|||
/// <param name="index">The sample offset along the edge.</param>
|
|||
/// <param name="value">The filtered sample.</param>
|
|||
public static abstract void StoreScalar(HevcPictureBuffer picture, HevcPlane plane, int x, int y, int distance, int index, int value); |
|||
} |
|||
} |
|||
@ -1,65 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
internal static partial class HevcDeblockingFilter |
|||
{ |
|||
/// <summary>
|
|||
/// Accesses four rows across a vertical edge.
|
|||
/// </summary>
|
|||
private readonly struct VerticalEdgeOperator : IEdgeOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<int> LoadVector(HevcPictureBuffer picture, HevcPlane plane, int x, int y, int distance, int count) |
|||
{ |
|||
if (count == 4) |
|||
{ |
|||
return Vector128.Create( |
|||
(int)picture.GetRowSpan(plane, y)[x + distance], |
|||
picture.GetRowSpan(plane, y + 1)[x + distance], |
|||
picture.GetRowSpan(plane, y + 2)[x + distance], |
|||
picture.GetRowSpan(plane, y + 3)[x + distance]); |
|||
} |
|||
|
|||
// Subsampled chroma edges contain two samples. Zeroing the unused lanes keeps the vector path within
|
|||
// the plane while allowing the shared kernel to operate on both valid samples in one instruction stream.
|
|||
return Vector128.Create( |
|||
(int)picture.GetRowSpan(plane, y)[x + distance], |
|||
picture.GetRowSpan(plane, y + 1)[x + distance], |
|||
0, |
|||
0); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void StoreVector( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int distance, |
|||
Vector128<int> value, |
|||
int count) |
|||
{ |
|||
for (int index = 0; index < count; index++) |
|||
{ |
|||
picture.GetRowSpan(plane, y + index)[x + distance] = (ushort)value.GetElement(index); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int LoadScalar(HevcPictureBuffer picture, HevcPlane plane, int x, int y, int distance, int index) |
|||
=> picture.GetRowSpan(plane, y + index)[x + distance]; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void StoreScalar(HevcPictureBuffer picture, HevcPlane plane, int x, int y, int distance, int index, int value) |
|||
=> picture.GetRowSpan(plane, y + index)[x + distance] = (ushort)value; |
|||
} |
|||
} |
|||
@ -1,470 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Applies the HEVC luma and chroma deblocking kernels to four-sample edge segments.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Each 32-bit lane represents one position along an edge segment. Orientation-specific operators gather the samples
|
|||
/// at a common signed distance across that edge; the luma and chroma masks and adjustments then execute lane-wise.
|
|||
/// Loads with fewer than four valid positions populate only the low lanes, which the matching store writes without
|
|||
/// touching samples beyond the picture boundary.
|
|||
/// </remarks>
|
|||
internal static partial class HevcDeblockingFilter |
|||
{ |
|||
/// <summary>
|
|||
/// Filters four rows crossing one vertical luma boundary.
|
|||
/// </summary>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The first Q-side sample X coordinate.</param>
|
|||
/// <param name="y">The top sample Y coordinate.</param>
|
|||
/// <param name="beta">The scaled discontinuity threshold.</param>
|
|||
/// <param name="tc">The scaled clipping threshold.</param>
|
|||
/// <param name="partPNoFilter">Whether the P-side block retains its original samples.</param>
|
|||
/// <param name="partQNoFilter">Whether the Q-side block retains its original samples.</param>
|
|||
/// <param name="bitDepth">The component sample precision.</param>
|
|||
public static void FilterVerticalLuma( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int beta, |
|||
int tc, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int bitDepth) |
|||
=> FilterLuma<VerticalEdgeOperator>(picture, plane, x, y, beta, tc, partPNoFilter, partQNoFilter, bitDepth); |
|||
|
|||
/// <summary>
|
|||
/// Filters four columns crossing one horizontal luma boundary.
|
|||
/// </summary>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The left sample X coordinate.</param>
|
|||
/// <param name="y">The first Q-side sample Y coordinate.</param>
|
|||
/// <param name="beta">The scaled discontinuity threshold.</param>
|
|||
/// <param name="tc">The scaled clipping threshold.</param>
|
|||
/// <param name="partPNoFilter">Whether the P-side block retains its original samples.</param>
|
|||
/// <param name="partQNoFilter">Whether the Q-side block retains its original samples.</param>
|
|||
/// <param name="bitDepth">The component sample precision.</param>
|
|||
public static void FilterHorizontalLuma( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int beta, |
|||
int tc, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int bitDepth) |
|||
=> FilterLuma<HorizontalEdgeOperator>(picture, plane, x, y, beta, tc, partPNoFilter, partQNoFilter, bitDepth); |
|||
|
|||
/// <summary>
|
|||
/// Filters four rows crossing one vertical chroma boundary.
|
|||
/// </summary>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The Cb or Cr component plane.</param>
|
|||
/// <param name="x">The first Q-side sample X coordinate.</param>
|
|||
/// <param name="y">The top sample Y coordinate.</param>
|
|||
/// <param name="tc">The scaled clipping threshold.</param>
|
|||
/// <param name="partPNoFilter">Whether the P-side block retains its original samples.</param>
|
|||
/// <param name="partQNoFilter">Whether the Q-side block retains its original samples.</param>
|
|||
/// <param name="bitDepth">The component sample precision.</param>
|
|||
/// <param name="count">The number of samples in the edge segment.</param>
|
|||
public static void FilterVerticalChroma( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int tc, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int bitDepth, |
|||
int count) |
|||
=> FilterChroma<VerticalEdgeOperator>(picture, plane, x, y, tc, partPNoFilter, partQNoFilter, bitDepth, count); |
|||
|
|||
/// <summary>
|
|||
/// Filters four columns crossing one horizontal chroma boundary.
|
|||
/// </summary>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The Cb or Cr component plane.</param>
|
|||
/// <param name="x">The left sample X coordinate.</param>
|
|||
/// <param name="y">The first Q-side sample Y coordinate.</param>
|
|||
/// <param name="tc">The scaled clipping threshold.</param>
|
|||
/// <param name="partPNoFilter">Whether the P-side block retains its original samples.</param>
|
|||
/// <param name="partQNoFilter">Whether the Q-side block retains its original samples.</param>
|
|||
/// <param name="bitDepth">The component sample precision.</param>
|
|||
/// <param name="count">The number of samples in the edge segment.</param>
|
|||
public static void FilterHorizontalChroma( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int tc, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int bitDepth, |
|||
int count) |
|||
=> FilterChroma<HorizontalEdgeOperator>(picture, plane, x, y, tc, partPNoFilter, partQNoFilter, bitDepth, count); |
|||
|
|||
/// <summary>
|
|||
/// Applies the strong or weak luma kernel through one closed edge-orientation operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The vertical or horizontal sample-access operator.</typeparam>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The first Q-side sample X coordinate.</param>
|
|||
/// <param name="y">The first Q-side sample Y coordinate.</param>
|
|||
/// <param name="beta">The scaled discontinuity threshold.</param>
|
|||
/// <param name="tc">The scaled clipping threshold.</param>
|
|||
/// <param name="partPNoFilter">Whether the P-side block retains its original samples.</param>
|
|||
/// <param name="partQNoFilter">Whether the Q-side block retains its original samples.</param>
|
|||
/// <param name="bitDepth">The component sample precision.</param>
|
|||
private static void FilterLuma<TOperator>( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int beta, |
|||
int tc, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int bitDepth) |
|||
where TOperator : struct, IEdgeOperator |
|||
{ |
|||
if (beta == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int p2Start = TOperator.LoadScalar(picture, plane, x, y, -3, 0); |
|||
int p1Start = TOperator.LoadScalar(picture, plane, x, y, -2, 0); |
|||
int p0Start = TOperator.LoadScalar(picture, plane, x, y, -1, 0); |
|||
int q0Start = TOperator.LoadScalar(picture, plane, x, y, 0, 0); |
|||
int q1Start = TOperator.LoadScalar(picture, plane, x, y, 1, 0); |
|||
int q2Start = TOperator.LoadScalar(picture, plane, x, y, 2, 0); |
|||
int p2End = TOperator.LoadScalar(picture, plane, x, y, -3, 3); |
|||
int p1End = TOperator.LoadScalar(picture, plane, x, y, -2, 3); |
|||
int p0End = TOperator.LoadScalar(picture, plane, x, y, -1, 3); |
|||
int q0End = TOperator.LoadScalar(picture, plane, x, y, 0, 3); |
|||
int q1End = TOperator.LoadScalar(picture, plane, x, y, 1, 3); |
|||
int q2End = TOperator.LoadScalar(picture, plane, x, y, 2, 3); |
|||
int dpStart = Math.Abs(p2Start - (2 * p1Start) + p0Start); |
|||
int dqStart = Math.Abs(q0Start - (2 * q1Start) + q2Start); |
|||
int dpEnd = Math.Abs(p2End - (2 * p1End) + p0End); |
|||
int dqEnd = Math.Abs(q0End - (2 * q1End) + q2End); |
|||
int dp = dpStart + dpEnd; |
|||
int dq = dqStart + dqEnd; |
|||
int discontinuity = dp + dq; |
|||
if (discontinuity >= beta) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int sideThreshold = (beta + (beta >> 1)) >> 3; |
|||
bool filterSecondP = dp < sideThreshold; |
|||
bool filterSecondQ = dq < sideThreshold; |
|||
bool strong = UsesStrongFiltering<TOperator>(picture, plane, x, y, 0, 2 * (dpStart + dqStart), beta, tc) |
|||
&& UsesStrongFiltering<TOperator>(picture, plane, x, y, 3, 2 * (dpEnd + dqEnd), beta, tc); |
|||
|
|||
if (!Vector128.IsHardwareAccelerated) |
|||
{ |
|||
for (int index = 0; index < 4; index++) |
|||
{ |
|||
FilterLumaScalar<TOperator>( |
|||
picture, |
|||
plane, |
|||
x, |
|||
y, |
|||
index, |
|||
tc, |
|||
strong, |
|||
partPNoFilter, |
|||
partQNoFilter, |
|||
tc * 10, |
|||
filterSecondP, |
|||
filterSecondQ, |
|||
bitDepth); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
Vector128<int> p3 = TOperator.LoadVector(picture, plane, x, y, -4, 4); |
|||
Vector128<int> p2 = TOperator.LoadVector(picture, plane, x, y, -3, 4); |
|||
Vector128<int> p1 = TOperator.LoadVector(picture, plane, x, y, -2, 4); |
|||
Vector128<int> p0 = TOperator.LoadVector(picture, plane, x, y, -1, 4); |
|||
Vector128<int> q0 = TOperator.LoadVector(picture, plane, x, y, 0, 4); |
|||
Vector128<int> q1 = TOperator.LoadVector(picture, plane, x, y, 1, 4); |
|||
Vector128<int> q2 = TOperator.LoadVector(picture, plane, x, y, 2, 4); |
|||
Vector128<int> q3 = TOperator.LoadVector(picture, plane, x, y, 3, 4); |
|||
|
|||
// Each Int32 lane is one row or column along the edge. The threshold decision is shared by all four lanes,
|
|||
// while the filter arithmetic stays lane-local and exactly matches the scalar equations below.
|
|||
if (strong) |
|||
{ |
|||
Vector128<int> twiceTc = Vector128.Create(2 * tc); |
|||
Vector128<int> four = Vector128.Create(4); |
|||
Vector128<int> two = Vector128.Create(2); |
|||
Vector128<int> filteredP0 = Vector128.Clamp((p2 + (p1 * 2) + (p0 * 2) + (q0 * 2) + q1 + four) >> 3, p0 - twiceTc, p0 + twiceTc); |
|||
Vector128<int> filteredQ0 = Vector128.Clamp((p1 + (p0 * 2) + (q0 * 2) + (q1 * 2) + q2 + four) >> 3, q0 - twiceTc, q0 + twiceTc); |
|||
Vector128<int> filteredP1 = Vector128.Clamp((p2 + p1 + p0 + q0 + two) >> 2, p1 - twiceTc, p1 + twiceTc); |
|||
Vector128<int> filteredQ1 = Vector128.Clamp((p0 + q0 + q1 + q2 + two) >> 2, q1 - twiceTc, q1 + twiceTc); |
|||
Vector128<int> filteredP2 = Vector128.Clamp(((p3 * 2) + (p2 * 3) + p1 + p0 + q0 + four) >> 3, p2 - twiceTc, p2 + twiceTc); |
|||
Vector128<int> filteredQ2 = Vector128.Clamp((p0 + q0 + q1 + (q2 * 3) + (q3 * 2) + four) >> 3, q2 - twiceTc, q2 + twiceTc); |
|||
|
|||
TOperator.StoreVector(picture, plane, x, y, -3, partPNoFilter ? p2 : filteredP2, 4); |
|||
TOperator.StoreVector(picture, plane, x, y, -2, partPNoFilter ? p1 : filteredP1, 4); |
|||
TOperator.StoreVector(picture, plane, x, y, -1, partPNoFilter ? p0 : filteredP0, 4); |
|||
TOperator.StoreVector(picture, plane, x, y, 0, partQNoFilter ? q0 : filteredQ0, 4); |
|||
TOperator.StoreVector(picture, plane, x, y, 1, partQNoFilter ? q1 : filteredQ1, 4); |
|||
TOperator.StoreVector(picture, plane, x, y, 2, partQNoFilter ? q2 : filteredQ2, 4); |
|||
return; |
|||
} |
|||
|
|||
Vector128<int> primaryDifference = (q0 - p0) * 9; |
|||
Vector128<int> secondaryDifference = (q1 - p1) * 3; |
|||
Vector128<int> delta = (primaryDifference - secondaryDifference + Vector128.Create(8)) >> 4; |
|||
Vector128<int> filterMask = Vector128.LessThan(Vector128.Abs(delta), Vector128.Create(tc * 10)); |
|||
delta = Vector128.Clamp(delta, Vector128.Create(-tc), Vector128.Create(tc)); |
|||
Vector128<int> minimum = Vector128<int>.Zero; |
|||
Vector128<int> maximum = Vector128.Create((1 << bitDepth) - 1); |
|||
Vector128<int> filteredP0Weak = Vector128.ConditionalSelect(filterMask, Vector128.Clamp(p0 + delta, minimum, maximum), p0); |
|||
Vector128<int> filteredQ0Weak = Vector128.ConditionalSelect(filterMask, Vector128.Clamp(q0 - delta, minimum, maximum), q0); |
|||
TOperator.StoreVector(picture, plane, x, y, -1, partPNoFilter ? p0 : filteredP0Weak, 4); |
|||
TOperator.StoreVector(picture, plane, x, y, 0, partQNoFilter ? q0 : filteredQ0Weak, 4); |
|||
|
|||
int halfTc = tc >> 1; |
|||
if (filterSecondP && !partPNoFilter) |
|||
{ |
|||
Vector128<int> secondary = (((p2 + p0 + Vector128<int>.One) >> 1) - p1 + delta) >> 1; |
|||
secondary = Vector128.Clamp(secondary, Vector128.Create(-halfTc), Vector128.Create(halfTc)); |
|||
Vector128<int> filtered = Vector128.ConditionalSelect(filterMask, Vector128.Clamp(p1 + secondary, minimum, maximum), p1); |
|||
TOperator.StoreVector(picture, plane, x, y, -2, filtered, 4); |
|||
} |
|||
|
|||
if (filterSecondQ && !partQNoFilter) |
|||
{ |
|||
Vector128<int> secondary = (((q2 + q0 + Vector128<int>.One) >> 1) - q1 - delta) >> 1; |
|||
secondary = Vector128.Clamp(secondary, Vector128.Create(-halfTc), Vector128.Create(halfTc)); |
|||
Vector128<int> filtered = Vector128.ConditionalSelect(filterMask, Vector128.Clamp(q1 + secondary, minimum, maximum), q1); |
|||
TOperator.StoreVector(picture, plane, x, y, 1, filtered, 4); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the chroma kernel through one closed edge-orientation operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The vertical or horizontal sample-access operator.</typeparam>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The Cb or Cr component plane.</param>
|
|||
/// <param name="x">The first Q-side sample X coordinate.</param>
|
|||
/// <param name="y">The first Q-side sample Y coordinate.</param>
|
|||
/// <param name="tc">The scaled clipping threshold.</param>
|
|||
/// <param name="partPNoFilter">Whether the P-side block retains its original samples.</param>
|
|||
/// <param name="partQNoFilter">Whether the Q-side block retains its original samples.</param>
|
|||
/// <param name="bitDepth">The component sample precision.</param>
|
|||
/// <param name="count">The number of samples in the edge segment.</param>
|
|||
private static void FilterChroma<TOperator>( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int tc, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int bitDepth, |
|||
int count) |
|||
where TOperator : struct, IEdgeOperator |
|||
{ |
|||
if (tc == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (!Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int maximum = (1 << bitDepth) - 1; |
|||
for (int index = 0; index < count; index++) |
|||
{ |
|||
int p1 = TOperator.LoadScalar(picture, plane, x, y, -2, index); |
|||
int p0 = TOperator.LoadScalar(picture, plane, x, y, -1, index); |
|||
int q0 = TOperator.LoadScalar(picture, plane, x, y, 0, index); |
|||
int q1 = TOperator.LoadScalar(picture, plane, x, y, 1, index); |
|||
int delta = Math.Clamp((((q0 - p0) << 2) + p1 - q1 + 4) >> 3, -tc, tc); |
|||
if (!partPNoFilter) |
|||
{ |
|||
TOperator.StoreScalar(picture, plane, x, y, -1, index, Math.Clamp(p0 + delta, 0, maximum)); |
|||
} |
|||
|
|||
if (!partQNoFilter) |
|||
{ |
|||
TOperator.StoreScalar(picture, plane, x, y, 0, index, Math.Clamp(q0 - delta, 0, maximum)); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
Vector128<int> p1Vector = TOperator.LoadVector(picture, plane, x, y, -2, count); |
|||
Vector128<int> p0Vector = TOperator.LoadVector(picture, plane, x, y, -1, count); |
|||
Vector128<int> q0Vector = TOperator.LoadVector(picture, plane, x, y, 0, count); |
|||
Vector128<int> q1Vector = TOperator.LoadVector(picture, plane, x, y, 1, count); |
|||
Vector128<int> deltaVector = (((q0Vector - p0Vector) * 4) + p1Vector - q1Vector + Vector128.Create(4)) >> 3; |
|||
deltaVector = Vector128.Clamp(deltaVector, Vector128.Create(-tc), Vector128.Create(tc)); |
|||
Vector128<int> minimum = Vector128<int>.Zero; |
|||
Vector128<int> maximumVector = Vector128.Create((1 << bitDepth) - 1); |
|||
|
|||
if (!partPNoFilter) |
|||
{ |
|||
TOperator.StoreVector(picture, plane, x, y, -1, Vector128.Clamp(p0Vector + deltaVector, minimum, maximumVector), count); |
|||
} |
|||
|
|||
if (!partQNoFilter) |
|||
{ |
|||
TOperator.StoreVector(picture, plane, x, y, 0, Vector128.Clamp(q0Vector - deltaVector, minimum, maximumVector), count); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the scalar luma equations to one sample along an edge.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The vertical or horizontal sample-access operator.</typeparam>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The first Q-side sample X coordinate.</param>
|
|||
/// <param name="y">The first Q-side sample Y coordinate.</param>
|
|||
/// <param name="index">The sample offset along the edge.</param>
|
|||
/// <param name="tc">The scaled clipping threshold.</param>
|
|||
/// <param name="strong">Whether the strong six-sample filter is selected.</param>
|
|||
/// <param name="partPNoFilter">Whether the P-side block retains its original samples.</param>
|
|||
/// <param name="partQNoFilter">Whether the Q-side block retains its original samples.</param>
|
|||
/// <param name="thresholdCut">The weak-filter delta threshold.</param>
|
|||
/// <param name="filterSecondP">Whether the second P-side sample is filtered.</param>
|
|||
/// <param name="filterSecondQ">Whether the second Q-side sample is filtered.</param>
|
|||
/// <param name="bitDepth">The component sample precision.</param>
|
|||
private static void FilterLumaScalar<TOperator>( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int index, |
|||
int tc, |
|||
bool strong, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int thresholdCut, |
|||
bool filterSecondP, |
|||
bool filterSecondQ, |
|||
int bitDepth) |
|||
where TOperator : struct, IEdgeOperator |
|||
{ |
|||
int p3 = TOperator.LoadScalar(picture, plane, x, y, -4, index); |
|||
int p2 = TOperator.LoadScalar(picture, plane, x, y, -3, index); |
|||
int p1 = TOperator.LoadScalar(picture, plane, x, y, -2, index); |
|||
int p0 = TOperator.LoadScalar(picture, plane, x, y, -1, index); |
|||
int q0 = TOperator.LoadScalar(picture, plane, x, y, 0, index); |
|||
int q1 = TOperator.LoadScalar(picture, plane, x, y, 1, index); |
|||
int q2 = TOperator.LoadScalar(picture, plane, x, y, 2, index); |
|||
int q3 = TOperator.LoadScalar(picture, plane, x, y, 3, index); |
|||
if (strong) |
|||
{ |
|||
if (!partPNoFilter) |
|||
{ |
|||
int filteredP0 = Math.Clamp( |
|||
(p2 + (2 * p1) + (2 * p0) + (2 * q0) + q1 + 4) >> 3, |
|||
p0 - (2 * tc), |
|||
p0 + (2 * tc)); |
|||
|
|||
TOperator.StoreScalar(picture, plane, x, y, -1, index, filteredP0); |
|||
TOperator.StoreScalar(picture, plane, x, y, -2, index, Math.Clamp((p2 + p1 + p0 + q0 + 2) >> 2, p1 - (2 * tc), p1 + (2 * tc))); |
|||
TOperator.StoreScalar(picture, plane, x, y, -3, index, Math.Clamp(((2 * p3) + (3 * p2) + p1 + p0 + q0 + 4) >> 3, p2 - (2 * tc), p2 + (2 * tc))); |
|||
} |
|||
|
|||
if (!partQNoFilter) |
|||
{ |
|||
int filteredQ0 = Math.Clamp( |
|||
(p1 + (2 * p0) + (2 * q0) + (2 * q1) + q2 + 4) >> 3, |
|||
q0 - (2 * tc), |
|||
q0 + (2 * tc)); |
|||
|
|||
TOperator.StoreScalar(picture, plane, x, y, 0, index, filteredQ0); |
|||
TOperator.StoreScalar(picture, plane, x, y, 1, index, Math.Clamp((p0 + q0 + q1 + q2 + 2) >> 2, q1 - (2 * tc), q1 + (2 * tc))); |
|||
TOperator.StoreScalar(picture, plane, x, y, 2, index, Math.Clamp((p0 + q0 + q1 + (3 * q2) + (2 * q3) + 4) >> 3, q2 - (2 * tc), q2 + (2 * tc))); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
int delta = ((9 * (q0 - p0)) - (3 * (q1 - p1)) + 8) >> 4; |
|||
if (Math.Abs(delta) >= thresholdCut) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
delta = Math.Clamp(delta, -tc, tc); |
|||
int maximum = (1 << bitDepth) - 1; |
|||
if (!partPNoFilter) |
|||
{ |
|||
TOperator.StoreScalar(picture, plane, x, y, -1, index, Math.Clamp(p0 + delta, 0, maximum)); |
|||
if (filterSecondP) |
|||
{ |
|||
int secondary = (((p2 + p0 + 1) >> 1) - p1 + delta) >> 1; |
|||
secondary = Math.Clamp(secondary, -(tc >> 1), tc >> 1); |
|||
TOperator.StoreScalar(picture, plane, x, y, -2, index, Math.Clamp(p1 + secondary, 0, maximum)); |
|||
} |
|||
} |
|||
|
|||
if (!partQNoFilter) |
|||
{ |
|||
TOperator.StoreScalar(picture, plane, x, y, 0, index, Math.Clamp(q0 - delta, 0, maximum)); |
|||
if (filterSecondQ) |
|||
{ |
|||
int secondary = (((q2 + q0 + 1) >> 1) - q1 - delta) >> 1; |
|||
secondary = Math.Clamp(secondary, -(tc >> 1), tc >> 1); |
|||
TOperator.StoreScalar(picture, plane, x, y, 1, index, Math.Clamp(q1 + secondary, 0, maximum)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether one endpoint satisfies the strong-filter conditions.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The vertical or horizontal sample-access operator.</typeparam>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The first Q-side sample X coordinate.</param>
|
|||
/// <param name="y">The first Q-side sample Y coordinate.</param>
|
|||
/// <param name="index">The endpoint offset along the edge.</param>
|
|||
/// <param name="discontinuity">Twice the endpoint's second-derivative sum.</param>
|
|||
/// <param name="beta">The scaled discontinuity threshold.</param>
|
|||
/// <param name="tc">The scaled clipping threshold.</param>
|
|||
/// <returns><see langword="true"/> when strong filtering is permitted; otherwise, <see langword="false"/>.</returns>
|
|||
private static bool UsesStrongFiltering<TOperator>( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int index, |
|||
int discontinuity, |
|||
int beta, |
|||
int tc) |
|||
where TOperator : struct, IEdgeOperator |
|||
{ |
|||
int p3 = TOperator.LoadScalar(picture, plane, x, y, -4, index); |
|||
int p0 = TOperator.LoadScalar(picture, plane, x, y, -1, index); |
|||
int q0 = TOperator.LoadScalar(picture, plane, x, y, 0, index); |
|||
int q3 = TOperator.LoadScalar(picture, plane, x, y, 3, index); |
|||
int strongDiscontinuity = Math.Abs(p3 - p0) + Math.Abs(q3 - q0); |
|||
int strongThreshold = ((5 * tc) + 1) >> 1; |
|||
return strongDiscontinuity < (beta >> 3) |
|||
&& discontinuity < (beta >> 2) |
|||
&& Math.Abs(p0 - q0) < strongThreshold; |
|||
} |
|||
} |
|||
@ -1,131 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Tracks luma transform and prediction boundaries at the four-sample resolution used to derive HEVC deblocking edges.
|
|||
/// </summary>
|
|||
internal sealed class HevcDeblockingState : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The base-two logarithm of the boundary-map unit side.
|
|||
/// </summary>
|
|||
private const int UnitLog2 = 2; |
|||
|
|||
/// <summary>
|
|||
/// The packed flag identifying a vertical boundary at a unit's left edge.
|
|||
/// </summary>
|
|||
private const byte VerticalBoundary = 1 << 0; |
|||
|
|||
/// <summary>
|
|||
/// The packed flag identifying a horizontal boundary at a unit's top edge.
|
|||
/// </summary>
|
|||
private const byte HorizontalBoundary = 1 << 1; |
|||
|
|||
/// <summary>
|
|||
/// The boundary maps for the primary plane of combined coding or each independently coded color plane.
|
|||
/// </summary>
|
|||
private readonly Buffer2D<byte>[] boundaries; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcDeblockingState"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration providing the image memory allocator.</param>
|
|||
/// <param name="sequenceParameterSet">The coded picture dimensions.</param>
|
|||
public HevcDeblockingState(Configuration configuration, HevcSequenceParameterSet sequenceParameterSet) |
|||
{ |
|||
int width = DivideCeilingByPowerOfTwo(sequenceParameterSet.Width, UnitLog2); |
|||
int height = DivideCeilingByPowerOfTwo(sequenceParameterSet.Height, UnitLog2); |
|||
|
|||
Buffer2D<byte>? lumaBoundaries = null; |
|||
Buffer2D<byte>? chromaBlueBoundaries = null; |
|||
Buffer2D<byte>? chromaRedBoundaries = null; |
|||
try |
|||
{ |
|||
// MarkBlock combines sparse edge flags with existing values, so zero initialization is part of the state contract.
|
|||
lumaBoundaries = configuration.MemoryAllocator.Allocate2D<byte>(width, height, AllocationOptions.Clean); |
|||
chromaBlueBoundaries = configuration.MemoryAllocator.Allocate2D<byte>(width, height, AllocationOptions.Clean); |
|||
chromaRedBoundaries = configuration.MemoryAllocator.Allocate2D<byte>(width, height, AllocationOptions.Clean); |
|||
this.boundaries = [lumaBoundaries, chromaBlueBoundaries, chromaRedBoundaries]; |
|||
} |
|||
catch |
|||
{ |
|||
chromaRedBoundaries?.Dispose(); |
|||
chromaBlueBoundaries?.Dispose(); |
|||
lumaBoundaries?.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Records the left and top edges of one leaf transform or pulse-code-modulated coding block.
|
|||
/// </summary>
|
|||
/// <param name="plane">The primary coding plane.</param>
|
|||
/// <param name="x">The block left coordinate in full-resolution primary-plane samples.</param>
|
|||
/// <param name="y">The block top coordinate in full-resolution primary-plane samples.</param>
|
|||
/// <param name="width">The block width in samples.</param>
|
|||
/// <param name="height">The block height in samples.</param>
|
|||
public void MarkBlock(HevcPlane plane, int x, int y, int width, int height) |
|||
{ |
|||
Buffer2D<byte> map = this.boundaries[(int)plane]; |
|||
int unitX = x >> UnitLog2; |
|||
int unitY = y >> UnitLog2; |
|||
int endX = Math.Min(DivideCeilingByPowerOfTwo(x + width, UnitLog2), map.Width); |
|||
int endY = Math.Min(DivideCeilingByPowerOfTwo(y + height, UnitLog2), map.Height); |
|||
|
|||
// A transform boundary covers every four-sample segment along its edge. Packing both orientations into one
|
|||
// byte keeps the decoder state contiguous and lets the later eight-sample deblocking traversal reject edges cheaply.
|
|||
for (int row = unitY; row < endY; row++) |
|||
{ |
|||
map.DangerousGetRowSpan(row)[unitX] |= VerticalBoundary; |
|||
} |
|||
|
|||
Span<byte> top = map.DangerousGetRowSpan(unitY); |
|||
for (int column = unitX; column < endX; column++) |
|||
{ |
|||
top[column] |= HorizontalBoundary; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets whether a four-sample segment begins at a vertical transform or prediction boundary.
|
|||
/// </summary>
|
|||
/// <param name="plane">The primary coding plane.</param>
|
|||
/// <param name="x">The segment left coordinate in full-resolution primary-plane samples.</param>
|
|||
/// <param name="y">The segment top coordinate in full-resolution primary-plane samples.</param>
|
|||
/// <returns><see langword="true"/> when the segment is a vertical boundary; otherwise, <see langword="false"/>.</returns>
|
|||
public bool IsVerticalBoundary(HevcPlane plane, int x, int y) |
|||
=> (this.boundaries[(int)plane].DangerousGetRowSpan(y >> UnitLog2)[x >> UnitLog2] & VerticalBoundary) != 0; |
|||
|
|||
/// <summary>
|
|||
/// Gets whether a four-sample segment begins at a horizontal transform or prediction boundary.
|
|||
/// </summary>
|
|||
/// <param name="plane">The primary coding plane.</param>
|
|||
/// <param name="x">The segment left coordinate in full-resolution primary-plane samples.</param>
|
|||
/// <param name="y">The segment top coordinate in full-resolution primary-plane samples.</param>
|
|||
/// <returns><see langword="true"/> when the segment is a horizontal boundary; otherwise, <see langword="false"/>.</returns>
|
|||
public bool IsHorizontalBoundary(HevcPlane plane, int x, int y) |
|||
=> (this.boundaries[(int)plane].DangerousGetRowSpan(y >> UnitLog2)[x >> UnitLog2] & HorizontalBoundary) != 0; |
|||
|
|||
/// <summary>
|
|||
/// Releases the allocator-owned boundary maps.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
foreach (Buffer2D<byte> map in this.boundaries) |
|||
{ |
|||
map.Dispose(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Divides a nonnegative sample count by a power of two with upward rounding.
|
|||
/// </summary>
|
|||
/// <param name="value">The sample count.</param>
|
|||
/// <param name="shift">The base-two divisor logarithm.</param>
|
|||
/// <returns>The upward-rounded quotient.</returns>
|
|||
private static int DivideCeilingByPowerOfTwo(int value, int shift) => (value + (1 << shift) - 1) >> shift; |
|||
} |
|||
@ -1,144 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the length-delimited NAL units and IDR slice segments carried by one HEVC still-image item.
|
|||
/// </summary>
|
|||
internal sealed class HevcImageItemBitstream |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcImageItemBitstream"/> class.
|
|||
/// </summary>
|
|||
/// <param name="data">The complete bounded payload of one <c>hvc1</c> image item.</param>
|
|||
/// <param name="configuration">The codec configuration associated with the same image item.</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// NAL-unit framing is malformed, the payload contains sequence or layered coding, or the item does not contain
|
|||
/// exactly one independently decodable IDR picture.
|
|||
/// </exception>
|
|||
public HevcImageItemBitstream(ReadOnlySpan<byte> data, HevcCodecConfiguration configuration) |
|||
{ |
|||
List<HevcNalUnit> nalUnits = []; |
|||
List<HevcSliceSegmentHeader> sliceSegments = []; |
|||
HevcSupplementalEnhancementInformation supplementalEnhancementInformation = new(); |
|||
int offset = 0; |
|||
while (offset < data.Length) |
|||
{ |
|||
if (data.Length - offset < configuration.NalUnitLengthSize) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC image item has a truncated NAL-unit length."); |
|||
} |
|||
|
|||
int nalUnitLength = ReadNalUnitLength(data[offset..], configuration.NalUnitLengthSize); |
|||
offset += configuration.NalUnitLengthSize; |
|||
if (nalUnitLength < 2 || nalUnitLength > data.Length - offset) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC image item has an invalid NAL-unit length."); |
|||
} |
|||
|
|||
HevcNalUnit nalUnit = new(data.Slice(offset, nalUnitLength)); |
|||
offset += nalUnitLength; |
|||
if (nalUnit.Header.LayerId != 0 || nalUnit.Header.TemporalId != 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC image item contains layered or temporal-substream NAL units."); |
|||
} |
|||
|
|||
nalUnits.Add(nalUnit); |
|||
if (nalUnit.Header.IsVideoCodingLayer) |
|||
{ |
|||
if (!nalUnit.Header.IsInstantaneousDecoderRefresh) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC image item contains a coded picture that is not independently decodable."); |
|||
} |
|||
|
|||
HevcSliceSegmentHeader sliceSegment = new(nalUnit, configuration.PictureParameterSets); |
|||
if (sliceSegments.Count == 0 && !sliceSegment.FirstSliceSegmentInPicture) |
|||
{ |
|||
throw new InvalidImageContentException("The first HEVC image-item slice is not marked as the first picture segment."); |
|||
} |
|||
|
|||
if (sliceSegments.Count != 0 && sliceSegment.FirstSliceSegmentInPicture) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC image item contains more than one coded picture."); |
|||
} |
|||
|
|||
sliceSegments.Add(sliceSegment); |
|||
continue; |
|||
} |
|||
|
|||
if (nalUnit.Header.NalUnitType is 32 or 33 or 34) |
|||
{ |
|||
// hvc1 image items obtain all parameter sets from the associated hvcC property. Accepting in-band
|
|||
// replacements would silently apply the more permissive hev1 sample contract to this still image.
|
|||
throw new InvalidImageContentException("The HEVC hvc1 image item contains an in-band parameter set."); |
|||
} |
|||
|
|||
if (nalUnit.Header.NalUnitType is 36 or 37) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC image item contains an end-of-sequence NAL unit."); |
|||
} |
|||
|
|||
if (nalUnit.Header.NalUnitType == 39) |
|||
{ |
|||
// Prefix SEI belongs to the following VCL NAL unit. Once this bounded item has started its only
|
|||
// picture, another prefix unit would describe a second access unit that the item is not allowed to carry.
|
|||
if (sliceSegments.Count != 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC image item contains prefix SEI after its first coded slice."); |
|||
} |
|||
|
|||
// Prefix SEI messages are associated with this item's only access unit. Parse the observable still-image
|
|||
// state in NAL and message order without retaining generic video persistence or timing state.
|
|||
supplementalEnhancementInformation.ReadPrefixNalUnit(nalUnit.Rbsp.Span); |
|||
} |
|||
} |
|||
|
|||
if (sliceSegments.Count == 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC image item contains no independently decodable picture."); |
|||
} |
|||
|
|||
this.NalUnits = nalUnits; |
|||
this.SliceSegments = sliceSegments; |
|||
this.SupplementalEnhancementInformation = supplementalEnhancementInformation; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets every decoded NAL unit in item order, including permitted delimiter, filler, and supplemental units.
|
|||
/// </summary>
|
|||
public IReadOnlyList<HevcNalUnit> NalUnits { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the ordered slice segments that reconstruct the item's single IDR picture.
|
|||
/// </summary>
|
|||
public IReadOnlyList<HevcSliceSegmentHeader> SliceSegments { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the presentation and exposed metadata decoded from prefix SEI NAL units.
|
|||
/// </summary>
|
|||
public HevcSupplementalEnhancementInformation SupplementalEnhancementInformation { get; } |
|||
|
|||
/// <summary>
|
|||
/// Reads an unsigned one-through-four-byte NAL-unit length without assuming four-byte item framing.
|
|||
/// </summary>
|
|||
/// <param name="data">The item bytes beginning at the length field.</param>
|
|||
/// <param name="lengthSize">The codec-configuration-selected length-field width.</param>
|
|||
/// <returns>The bounded signed integer NAL-unit length.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The unsigned length exceeds the supported item-buffer range.</exception>
|
|||
private static int ReadNalUnitLength(ReadOnlySpan<byte> data, int lengthSize) |
|||
{ |
|||
uint value = 0; |
|||
for (int byteIndex = 0; byteIndex < lengthSize; byteIndex++) |
|||
{ |
|||
value = (value << 8) | data[byteIndex]; |
|||
} |
|||
|
|||
if (value > int.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC image-item NAL-unit length is too large."); |
|||
} |
|||
|
|||
return (int)value; |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
// 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]; |
|||
} |
|||
@ -1,394 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Stores and decodes the luma and chroma intra prediction modes for one HEVC still picture.
|
|||
/// </summary>
|
|||
internal sealed class HevcIntraPredictionState : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The base-two logarithm of the minimum luma prediction-block size.
|
|||
/// </summary>
|
|||
private const int MinPredictionBlockLog2 = 2; |
|||
|
|||
/// <summary>
|
|||
/// The planar intra prediction mode.
|
|||
/// </summary>
|
|||
private const byte PlanarMode = 0; |
|||
|
|||
/// <summary>
|
|||
/// The DC intra prediction mode.
|
|||
/// </summary>
|
|||
private const byte DcMode = 1; |
|||
|
|||
/// <summary>
|
|||
/// The horizontal intra prediction mode.
|
|||
/// </summary>
|
|||
private const byte HorizontalMode = 10; |
|||
|
|||
/// <summary>
|
|||
/// The vertical intra prediction mode.
|
|||
/// </summary>
|
|||
private const byte VerticalMode = 26; |
|||
|
|||
/// <summary>
|
|||
/// The replacement chroma mode used when an explicit chroma candidate equals the luma mode.
|
|||
/// </summary>
|
|||
private const byte ChromaReplacementMode = 34; |
|||
|
|||
/// <summary>
|
|||
/// The chroma mode that derives its direction from the colocated luma prediction block.
|
|||
/// </summary>
|
|||
private const byte DerivedChromaMode = 36; |
|||
|
|||
/// <summary>
|
|||
/// The luma intra mode at minimum-prediction-block resolution.
|
|||
/// </summary>
|
|||
private readonly Buffer2D<byte> lumaModes; |
|||
|
|||
/// <summary>
|
|||
/// The coded chroma intra mode at minimum-prediction-block resolution in luma coordinates.
|
|||
/// </summary>
|
|||
private readonly Buffer2D<byte> chromaModes; |
|||
|
|||
/// <summary>
|
|||
/// The resolved chroma intra mode at minimum-prediction-block resolution in luma coordinates.
|
|||
/// </summary>
|
|||
private readonly Buffer2D<byte> effectiveChromaModes; |
|||
|
|||
/// <summary>
|
|||
/// Whether derived chroma prediction selects the colocated luma prediction block.
|
|||
/// </summary>
|
|||
private readonly bool derivedChromaUsesColocatedLuma; |
|||
|
|||
/// <summary>
|
|||
/// The mask selecting a luma coordinate within its coding-tree block.
|
|||
/// </summary>
|
|||
private readonly int codingTreeBlockMask; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcIntraPredictionState"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration providing the image memory allocator.</param>
|
|||
/// <param name="sequenceParameterSet">The coded luma picture dimensions.</param>
|
|||
public HevcIntraPredictionState(Configuration configuration, HevcSequenceParameterSet sequenceParameterSet) |
|||
{ |
|||
this.WidthInMinPredictionBlocks = DivideCeilingByPowerOfTwo( |
|||
sequenceParameterSet.Width, |
|||
MinPredictionBlockLog2); |
|||
|
|||
this.HeightInMinPredictionBlocks = DivideCeilingByPowerOfTwo( |
|||
sequenceParameterSet.Height, |
|||
MinPredictionBlockLog2); |
|||
|
|||
Buffer2D<byte>? lumaModes = null; |
|||
Buffer2D<byte>? chromaModes = null; |
|||
Buffer2D<byte>? effectiveChromaModes = null; |
|||
try |
|||
{ |
|||
lumaModes = configuration.MemoryAllocator.Allocate2D<byte>( |
|||
this.WidthInMinPredictionBlocks, |
|||
this.HeightInMinPredictionBlocks); |
|||
|
|||
chromaModes = configuration.MemoryAllocator.Allocate2D<byte>( |
|||
this.WidthInMinPredictionBlocks, |
|||
this.HeightInMinPredictionBlocks); |
|||
|
|||
effectiveChromaModes = configuration.MemoryAllocator.Allocate2D<byte>( |
|||
this.WidthInMinPredictionBlocks, |
|||
this.HeightInMinPredictionBlocks); |
|||
|
|||
// PCM coding units skip intra-mode syntax but remain available as most-probable-mode neighbors. HM
|
|||
// initializes every luma direction to DC so those units provide the required default until syntax replaces it.
|
|||
for (int row = 0; row < this.HeightInMinPredictionBlocks; row++) |
|||
{ |
|||
lumaModes.DangerousGetRowSpan(row).Fill(DcMode); |
|||
} |
|||
|
|||
this.lumaModes = lumaModes; |
|||
this.chromaModes = chromaModes; |
|||
this.effectiveChromaModes = effectiveChromaModes; |
|||
} |
|||
catch |
|||
{ |
|||
effectiveChromaModes?.Dispose(); |
|||
chromaModes?.Dispose(); |
|||
lumaModes?.Dispose(); |
|||
throw; |
|||
} |
|||
|
|||
this.derivedChromaUsesColocatedLuma = sequenceParameterSet.ChromaFormat == 3; |
|||
this.codingTreeBlockMask = (1 << sequenceParameterSet.CodingTreeBlockLog2) - 1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the map width in minimum luma prediction blocks.
|
|||
/// </summary>
|
|||
public int WidthInMinPredictionBlocks { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the map height in minimum luma prediction blocks.
|
|||
/// </summary>
|
|||
public int HeightInMinPredictionBlocks { get; } |
|||
|
|||
/// <summary>
|
|||
/// Decodes and records the luma intra modes of one leaf coding unit.
|
|||
/// </summary>
|
|||
/// <param name="reader">The current entropy-substream syntax reader.</param>
|
|||
/// <param name="x">The coding-unit left coordinate in luma samples.</param>
|
|||
/// <param name="y">The coding-unit top coordinate in luma samples.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the square coding-unit size.</param>
|
|||
/// <param name="usesNxNPartitions">A value indicating whether the coding unit has four square prediction blocks.</param>
|
|||
/// <param name="leftAvailable">A value indicating whether the external left prediction block is available.</param>
|
|||
/// <param name="aboveAvailable">A value indicating whether the external above prediction block is available.</param>
|
|||
public void DecodeLumaModes( |
|||
ref HevcCabacSyntaxReader reader, |
|||
int x, |
|||
int y, |
|||
int log2Size, |
|||
bool usesNxNPartitions, |
|||
bool leftAvailable, |
|||
bool aboveAvailable) |
|||
{ |
|||
int predictionBlockLog2 = usesNxNPartitions ? log2Size - 1 : log2Size; |
|||
int predictionBlockSize = 1 << predictionBlockLog2; |
|||
int predictionBlockCount = usesNxNPartitions ? 4 : 1; |
|||
InlineArray4<byte> mostProbableFlags = default; |
|||
|
|||
// HEVC codes every prev_intra_luma_pred_flag before any associated mode suffix. Preserve that two-pass
|
|||
// ordering because decoding one complete mode at a time would consume a different CABAC bit sequence.
|
|||
for (int index = 0; index < predictionBlockCount; index++) |
|||
{ |
|||
mostProbableFlags[index] = reader.ReadPreviousIntraLumaPredictionFlag() ? (byte)1 : (byte)0; |
|||
} |
|||
|
|||
InlineArray4<byte> mostProbableModes = default; |
|||
Span<byte> mostProbableModeSpan = mostProbableModes[..3]; |
|||
|
|||
for (int index = 0; index < predictionBlockCount; index++) |
|||
{ |
|||
int offsetX = (index & 1) * predictionBlockSize; |
|||
int offsetY = (index >> 1) * predictionBlockSize; |
|||
int predictionX = x + offsetX; |
|||
int predictionY = y + offsetY; |
|||
bool predictionLeftAvailable = offsetX != 0 || leftAvailable; |
|||
|
|||
// Luma MPM derivation treats an above prediction unit across a CTB boundary as unavailable. This is
|
|||
// narrower than sample reconstruction availability and keeps the candidate order synchronized with CABAC.
|
|||
bool predictionAboveAvailable = (predictionY & this.codingTreeBlockMask) != 0 && (offsetY != 0 || aboveAvailable); |
|||
|
|||
this.GetMostProbableLumaModes( |
|||
predictionX, |
|||
predictionY, |
|||
predictionLeftAvailable, |
|||
predictionAboveAvailable, |
|||
mostProbableModeSpan); |
|||
|
|||
int mode; |
|||
if (mostProbableFlags[index] != 0) |
|||
{ |
|||
mode = mostProbableModeSpan[reader.ReadMostProbableIntraLumaPredictionIndex()]; |
|||
} |
|||
else |
|||
{ |
|||
SortThree(mostProbableModeSpan); |
|||
mode = reader.ReadRemainingIntraLumaPredictionMode(); |
|||
for (int candidate = 0; candidate < mostProbableModeSpan.Length; candidate++) |
|||
{ |
|||
// The remaining-mode code omits the three probable values, so each candidate at or below the
|
|||
// provisional result advances the decoded mode over that omitted slot.
|
|||
mode += mode >= mostProbableModeSpan[candidate] ? 1 : 0; |
|||
} |
|||
} |
|||
|
|||
this.SetMode(this.lumaModes, predictionX, predictionY, predictionBlockLog2, (byte)mode); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes and records the chroma intra modes of one leaf coding unit.
|
|||
/// </summary>
|
|||
/// <param name="reader">The current entropy-substream syntax reader.</param>
|
|||
/// <param name="x">The coding-unit left coordinate in luma samples.</param>
|
|||
/// <param name="y">The coding-unit top coordinate in luma samples.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the square coding-unit size.</param>
|
|||
/// <param name="usesNxNPartitions">Whether the coding unit contains four luma prediction units.</param>
|
|||
public void DecodeChromaModes(ref HevcCabacSyntaxReader reader, int x, int y, int log2Size, bool usesNxNPartitions) |
|||
{ |
|||
bool usesFourChromaPredictionUnits = this.derivedChromaUsesColocatedLuma && usesNxNPartitions; |
|||
int predictionBlockLog2 = usesFourChromaPredictionUnits ? log2Size - 1 : log2Size; |
|||
int predictionBlockSize = 1 << predictionBlockLog2; |
|||
int predictionBlockCount = usesFourChromaPredictionUnits ? 4 : 1; |
|||
for (int index = 0; index < predictionBlockCount; index++) |
|||
{ |
|||
int predictionX = x + ((index & 1) * predictionBlockSize); |
|||
int predictionY = y + ((index >> 1) * predictionBlockSize); |
|||
int selector = reader.ReadChromaPredictionModeIndex(); |
|||
byte mode; |
|||
if (selector < 0) |
|||
{ |
|||
mode = DerivedChromaMode; |
|||
} |
|||
else |
|||
{ |
|||
ReadOnlySpan<byte> candidates = [PlanarMode, VerticalMode, HorizontalMode, DcMode]; |
|||
mode = candidates[selector]; |
|||
if (mode == this.GetLumaMode(predictionX, predictionY)) |
|||
{ |
|||
mode = ChromaReplacementMode; |
|||
} |
|||
} |
|||
|
|||
// Combined 4:4:4 follows the four luma prediction partitions of an NxN coding unit. Subsampled formats
|
|||
// carry one chroma mode for the coding unit and derive it from the top-left luma partition when requested.
|
|||
this.SetMode(this.chromaModes, predictionX, predictionY, predictionBlockLog2, mode); |
|||
byte effectiveMode = mode == DerivedChromaMode ? this.GetLumaMode(predictionX, predictionY) : mode; |
|||
this.SetMode(this.effectiveChromaModes, predictionX, predictionY, predictionBlockLog2, effectiveMode); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma intra mode at a luma sample coordinate.
|
|||
/// </summary>
|
|||
/// <param name="x">The luma sample X coordinate.</param>
|
|||
/// <param name="y">The luma sample Y coordinate.</param>
|
|||
/// <returns>The luma intra mode in the inclusive range zero through thirty-four.</returns>
|
|||
public byte GetLumaMode(int x, int y) |
|||
=> this.lumaModes.DangerousGetRowSpan(y >> MinPredictionBlockLog2)[x >> MinPredictionBlockLog2]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded chroma intra mode at a luma sample coordinate.
|
|||
/// </summary>
|
|||
/// <param name="x">The luma sample X coordinate.</param>
|
|||
/// <param name="y">The luma sample Y coordinate.</param>
|
|||
/// <returns>An explicit chroma direction or the derived-mode value.</returns>
|
|||
public byte GetChromaMode(int x, int y) |
|||
=> this.chromaModes.DangerousGetRowSpan(y >> MinPredictionBlockLog2)[x >> MinPredictionBlockLog2]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the effective chroma intra mode at a luma sample coordinate.
|
|||
/// </summary>
|
|||
/// <param name="x">The luma sample X coordinate.</param>
|
|||
/// <param name="y">The luma sample Y coordinate.</param>
|
|||
/// <returns>The explicit chroma mode, or the colocated luma mode when chroma uses derived mode.</returns>
|
|||
public byte GetEffectiveChromaMode(int x, int y) |
|||
=> this.effectiveChromaModes.DangerousGetRowSpan(y >> MinPredictionBlockLog2)[x >> MinPredictionBlockLog2]; |
|||
|
|||
/// <summary>
|
|||
/// Releases the owned intra-mode maps.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.lumaModes.Dispose(); |
|||
this.chromaModes.Dispose(); |
|||
this.effectiveChromaModes.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Derives the three most-probable luma intra modes from available spatial neighbors.
|
|||
/// </summary>
|
|||
/// <param name="x">The prediction-block left coordinate in luma samples.</param>
|
|||
/// <param name="y">The prediction-block top coordinate in luma samples.</param>
|
|||
/// <param name="leftAvailable">A value indicating whether the left prediction block is available.</param>
|
|||
/// <param name="aboveAvailable">A value indicating whether the above prediction block is available.</param>
|
|||
/// <param name="modes">The three-element destination span.</param>
|
|||
private void GetMostProbableLumaModes( |
|||
int x, |
|||
int y, |
|||
bool leftAvailable, |
|||
bool aboveAvailable, |
|||
Span<byte> modes) |
|||
{ |
|||
byte leftMode = leftAvailable ? this.GetLumaMode(x - 1, y) : DcMode; |
|||
byte aboveMode = aboveAvailable ? this.GetLumaMode(x, y - 1) : DcMode; |
|||
if (leftMode == aboveMode) |
|||
{ |
|||
if (leftMode > DcMode) |
|||
{ |
|||
modes[0] = leftMode; |
|||
modes[1] = (byte)(((leftMode + 29) % 32) + 2); |
|||
modes[2] = (byte)(((leftMode - 1) % 32) + 2); |
|||
} |
|||
else |
|||
{ |
|||
modes[0] = PlanarMode; |
|||
modes[1] = DcMode; |
|||
modes[2] = VerticalMode; |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
modes[0] = leftMode; |
|||
modes[1] = aboveMode; |
|||
if (leftMode != PlanarMode && aboveMode != PlanarMode) |
|||
{ |
|||
modes[2] = PlanarMode; |
|||
} |
|||
else |
|||
{ |
|||
modes[2] = leftMode + aboveMode < 2 ? VerticalMode : DcMode; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Records one prediction mode over a square luma-coordinate region.
|
|||
/// </summary>
|
|||
/// <param name="map">The luma or chroma mode map.</param>
|
|||
/// <param name="x">The region left coordinate in luma samples.</param>
|
|||
/// <param name="y">The region top coordinate in luma samples.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the square region size.</param>
|
|||
/// <param name="mode">The prediction mode.</param>
|
|||
private void SetMode(Buffer2D<byte> map, int x, int y, int log2Size, byte mode) |
|||
{ |
|||
int unitX = x >> MinPredictionBlockLog2; |
|||
int unitY = y >> MinPredictionBlockLog2; |
|||
int unitCount = 1 << (log2Size - MinPredictionBlockLog2); |
|||
int endX = Math.Min(unitX + unitCount, this.WidthInMinPredictionBlocks); |
|||
int endY = Math.Min(unitY + unitCount, this.HeightInMinPredictionBlocks); |
|||
for (int row = unitY; row < endY; row++) |
|||
{ |
|||
map.DangerousGetRowSpan(row)[unitX..endX].Fill(mode); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sorts three intra-mode values into ascending order.
|
|||
/// </summary>
|
|||
/// <param name="values">The three-element mode span.</param>
|
|||
private static void SortThree(Span<byte> values) |
|||
{ |
|||
if (values[0] > values[1]) |
|||
{ |
|||
byte value = values[0]; |
|||
values[0] = values[1]; |
|||
values[1] = value; |
|||
} |
|||
|
|||
if (values[0] > values[2]) |
|||
{ |
|||
byte value = values[0]; |
|||
values[0] = values[2]; |
|||
values[2] = value; |
|||
} |
|||
|
|||
if (values[1] > values[2]) |
|||
{ |
|||
byte value = values[1]; |
|||
values[1] = values[2]; |
|||
values[2] = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Divides a nonnegative sample count by a power of two with upward rounding.
|
|||
/// </summary>
|
|||
/// <param name="value">The sample count.</param>
|
|||
/// <param name="shift">The base-two divisor logarithm.</param>
|
|||
/// <returns>The upward-rounded quotient.</returns>
|
|||
private static int DivideCeilingByPowerOfTwo(int value, int shift) => (value + (1 << shift) - 1) >> shift; |
|||
} |
|||
@ -1,346 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Defines angular intra-prediction arithmetic.
|
|||
/// </content>
|
|||
internal static partial class HevcIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the thirty-three directional intra-prediction modes.
|
|||
/// </summary>
|
|||
private readonly struct AngularOperator : IHevcIntraPredictionOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges, |
|||
Span<ushort> scratch) |
|||
{ |
|||
if (mode == VerticalMode) |
|||
{ |
|||
PredictVertical(top, left, destination, destinationStride, size, bitDepth, filterPredictionEdges); |
|||
return; |
|||
} |
|||
|
|||
if (mode == HorizontalMode) |
|||
{ |
|||
PredictHorizontal(top, left, destination, destinationStride, size, bitDepth, filterPredictionEdges); |
|||
return; |
|||
} |
|||
|
|||
bool vertical = mode >= FirstVerticalMode; |
|||
int angleMode = vertical ? mode - VerticalMode : HorizontalMode - mode; |
|||
int absoluteAngleMode = Math.Abs(angleMode); |
|||
int angle = PredictionAngles[absoluteAngleMode] * Math.Sign(angleMode); |
|||
ReadOnlySpan<ushort> main = vertical ? top : left; |
|||
ReadOnlySpan<ushort> side = vertical ? left : top; |
|||
Span<ushort> temporaryBlock = scratch[..(size * size)]; |
|||
Span<ushort> extendedReference = scratch.Slice(size * size, (4 * size) + 1); |
|||
int mainOrigin = 0; |
|||
|
|||
if (angle < 0) |
|||
{ |
|||
mainOrigin = size * 2; |
|||
main[..(size + 1)].CopyTo(extendedReference[mainOrigin..]); |
|||
int inverseAngle = InversePredictionAngles[absoluteAngleMode]; |
|||
int inverseAngleSum = 128; |
|||
int minimumIndex = (size * angle) >> 5; |
|||
for (int index = -1; index > minimumIndex; index--) |
|||
{ |
|||
inverseAngleSum += inverseAngle; |
|||
extendedReference[mainOrigin + index] = side[inverseAngleSum >> 8]; |
|||
} |
|||
|
|||
main = extendedReference; |
|||
} |
|||
|
|||
Span<ushort> prediction = vertical ? destination : temporaryBlock; |
|||
int predictionStride = vertical ? destinationStride : size; |
|||
PredictAngularRows(main, mainOrigin, prediction, predictionStride, size, angle); |
|||
if (!vertical) |
|||
{ |
|||
TransposeBlock(temporaryBlock, destination, destinationStride, size); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies the top reference into every row and optionally filters the first column.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="left">The left reference samples.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the vertical luma edge filter applies.</param>
|
|||
private static void PredictVertical( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int bitDepth, |
|||
bool filterPredictionEdges) |
|||
{ |
|||
ReadOnlySpan<ushort> row = top.Slice(1, size); |
|||
int maximum = (1 << bitDepth) - 1; |
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
row.CopyTo(destination.Slice(y * destinationStride, size)); |
|||
if (filterPredictionEdges) |
|||
{ |
|||
int sample = destination[y * destinationStride] + ((left[y + 1] - left[0]) >> 1); |
|||
destination[y * destinationStride] = (ushort)Math.Clamp(sample, 0, maximum); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills each row from its left reference and optionally filters the first row.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="left">The left reference samples.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the horizontal luma edge filter applies.</param>
|
|||
private static void PredictHorizontal( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int bitDepth, |
|||
bool filterPredictionEdges) |
|||
{ |
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
destination.Slice(y * destinationStride, size).Fill(left[y + 1]); |
|||
} |
|||
|
|||
if (!filterPredictionEdges) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int maximum = (1 << bitDepth) - 1; |
|||
for (int x = 0; x < size; x++) |
|||
{ |
|||
int sample = destination[x] + ((top[x + 1] - top[0]) >> 1); |
|||
destination[x] = (ushort)Math.Clamp(sample, 0, maximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Generates a vertical-oriented angular block using contiguous SIMD interpolation within each row.
|
|||
/// </summary>
|
|||
/// <param name="main">The main reference beginning at logical index zero.</param>
|
|||
/// <param name="mainOrigin">The span index corresponding to logical reference index zero.</param>
|
|||
/// <param name="destination">The contiguous destination or transposition scratch block.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="angle">The signed prediction displacement in thirty-second-sample units.</param>
|
|||
private static void PredictAngularRows( |
|||
ReadOnlySpan<ushort> main, |
|||
int mainOrigin, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int angle) |
|||
{ |
|||
for (int y = 0, deltaPosition = angle; y < size; y++, deltaPosition += angle) |
|||
{ |
|||
int deltaInteger = deltaPosition >> 5; |
|||
int deltaFraction = deltaPosition & 31; |
|||
int sourceOffset = mainOrigin + deltaInteger + 1; |
|||
Span<ushort> row = destination.Slice(y * destinationStride, size); |
|||
if (deltaFraction == 0) |
|||
{ |
|||
main.Slice(sourceOffset, size).CopyTo(row); |
|||
} |
|||
else |
|||
{ |
|||
InterpolateAngularRow(main[sourceOffset..], row, deltaFraction); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Interpolates one angular prediction row between consecutive main-reference samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The first main-reference sample for the row.</param>
|
|||
/// <param name="destination">The destination prediction row.</param>
|
|||
/// <param name="fraction">The right-hand weight with a denominator of thirty-two.</param>
|
|||
private static void InterpolateAngularRow(ReadOnlySpan<ushort> source, Span<ushort> destination, int fraction) |
|||
{ |
|||
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
uint leftWeight = (uint)(32 - fraction); |
|||
uint rightWeight = (uint)fraction; |
|||
int i = 0; |
|||
|
|||
// Adjacent source vectors overlap by one sample, aligning each left/right reference pair in the same lane.
|
|||
// Widening keeps the largest 12-bit Q5 weighted sum below the UInt32 limit before narrowing to sample storage.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = destination.Length - Vector512<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> left = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector512<ushort> right = Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
|||
(Vector512<uint> leftLow, Vector512<uint> leftHigh) = Vector512.Widen(left); |
|||
(Vector512<uint> rightLow, Vector512<uint> rightHigh) = Vector512.Widen(right); |
|||
Vector512<uint> low = ((leftLow * leftWeight) + (rightLow * rightWeight) + Vector512.Create(16U)) >> 5; |
|||
Vector512<uint> high = ((leftHigh * leftWeight) + (rightHigh * rightWeight) + Vector512.Create(16U)) >> 5; |
|||
Vector512.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = destination.Length - Vector256<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> left = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector256<ushort> right = Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
|||
(Vector256<uint> leftLow, Vector256<uint> leftHigh) = Vector256.Widen(left); |
|||
(Vector256<uint> rightLow, Vector256<uint> rightHigh) = Vector256.Widen(right); |
|||
Vector256<uint> low = ((leftLow * leftWeight) + (rightLow * rightWeight) + Vector256.Create(16U)) >> 5; |
|||
Vector256<uint> high = ((leftHigh * leftWeight) + (rightHigh * rightWeight) + Vector256.Create(16U)) >> 5; |
|||
Vector256.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = destination.Length - Vector128<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> left = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector128<ushort> right = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
|||
(Vector128<uint> leftLow, Vector128<uint> leftHigh) = Vector128.Widen(left); |
|||
(Vector128<uint> rightLow, Vector128<uint> rightHigh) = Vector128.Widen(right); |
|||
Vector128<uint> low = ((leftLow * leftWeight) + (rightLow * rightWeight) + Vector128.Create(16U)) >> 5; |
|||
Vector128<uint> high = ((leftHigh * leftWeight) + (rightHigh * rightWeight) + Vector128.Create(16U)) >> 5; |
|||
Vector128.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
for (; i < destination.Length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationBase, i) = (ushort)(((source[i] * leftWeight) + (source[i + 1] * rightWeight) + 16) >> 5); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transposes a square horizontal prediction block into the reconstructed destination.
|
|||
/// </summary>
|
|||
/// <param name="source">The contiguous transposed prediction block.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
private static void TransposeBlock(ReadOnlySpan<ushort> source, Span<ushort> destination, int destinationStride, int size) |
|||
{ |
|||
if (Vector128.IsHardwareAccelerated && size >= Vector128<ushort>.Count) |
|||
{ |
|||
for (int y = 0; y < size; y += Vector128<ushort>.Count) |
|||
{ |
|||
for (int x = 0; x < size; x += Vector128<ushort>.Count) |
|||
{ |
|||
Transpose8x8(source, destination, destinationStride, size, x, y); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
for (int x = 0; x < size; x++) |
|||
{ |
|||
destination[(x * destinationStride) + y] = source[(y * size) + x]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transposes one eight-by-eight tile of 16-bit prediction samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The contiguous source block.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="sourceStride">The contiguous source row stride.</param>
|
|||
/// <param name="x">The tile X coordinate in the source block.</param>
|
|||
/// <param name="y">The tile Y coordinate in the source block.</param>
|
|||
private static void Transpose8x8( |
|||
ReadOnlySpan<ushort> source, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int sourceStride, |
|||
int x, |
|||
int y) |
|||
{ |
|||
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
Vector128<short> row0 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 0) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row1 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 1) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row2 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 2) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row3 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 3) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row4 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 4) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row5 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 5) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row6 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 6) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row7 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 7) * sourceStride) + x)).AsInt16(); |
|||
|
|||
// Three zip stages exchange one, two, then four 16-bit coordinates. The resulting vectors are the eight
|
|||
// source columns in row order, so each can be stored contiguously into one destination row.
|
|||
Vector128<short> pair0 = Vector128_.UnpackLow(row0, row1); |
|||
Vector128<short> pair1 = Vector128_.UnpackHigh(row0, row1); |
|||
Vector128<short> pair2 = Vector128_.UnpackLow(row2, row3); |
|||
Vector128<short> pair3 = Vector128_.UnpackHigh(row2, row3); |
|||
Vector128<short> pair4 = Vector128_.UnpackLow(row4, row5); |
|||
Vector128<short> pair5 = Vector128_.UnpackHigh(row4, row5); |
|||
Vector128<short> pair6 = Vector128_.UnpackLow(row6, row7); |
|||
Vector128<short> pair7 = Vector128_.UnpackHigh(row6, row7); |
|||
Vector128<int> quad0 = Vector128_.UnpackLow(pair0.AsInt32(), pair2.AsInt32()); |
|||
Vector128<int> quad1 = Vector128_.UnpackHigh(pair0.AsInt32(), pair2.AsInt32()); |
|||
Vector128<int> quad2 = Vector128_.UnpackLow(pair1.AsInt32(), pair3.AsInt32()); |
|||
Vector128<int> quad3 = Vector128_.UnpackHigh(pair1.AsInt32(), pair3.AsInt32()); |
|||
Vector128<int> quad4 = Vector128_.UnpackLow(pair4.AsInt32(), pair6.AsInt32()); |
|||
Vector128<int> quad5 = Vector128_.UnpackHigh(pair4.AsInt32(), pair6.AsInt32()); |
|||
Vector128<int> quad6 = Vector128_.UnpackLow(pair5.AsInt32(), pair7.AsInt32()); |
|||
Vector128<int> quad7 = Vector128_.UnpackHigh(pair5.AsInt32(), pair7.AsInt32()); |
|||
Vector128<ushort> column0 = Vector128_.UnpackLow(quad0.AsInt64(), quad4.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column1 = Vector128_.UnpackHigh(quad0.AsInt64(), quad4.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column2 = Vector128_.UnpackLow(quad1.AsInt64(), quad5.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column3 = Vector128_.UnpackHigh(quad1.AsInt64(), quad5.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column4 = Vector128_.UnpackLow(quad2.AsInt64(), quad6.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column5 = Vector128_.UnpackHigh(quad2.AsInt64(), quad6.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column6 = Vector128_.UnpackLow(quad3.AsInt64(), quad7.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column7 = Vector128_.UnpackHigh(quad3.AsInt64(), quad7.AsInt64()).AsUInt16(); |
|||
column0.StoreUnsafe(ref destinationBase, (nuint)(((x + 0) * destinationStride) + y)); |
|||
column1.StoreUnsafe(ref destinationBase, (nuint)(((x + 1) * destinationStride) + y)); |
|||
column2.StoreUnsafe(ref destinationBase, (nuint)(((x + 2) * destinationStride) + y)); |
|||
column3.StoreUnsafe(ref destinationBase, (nuint)(((x + 3) * destinationStride) + y)); |
|||
column4.StoreUnsafe(ref destinationBase, (nuint)(((x + 4) * destinationStride) + y)); |
|||
column5.StoreUnsafe(ref destinationBase, (nuint)(((x + 5) * destinationStride) + y)); |
|||
column6.StoreUnsafe(ref destinationBase, (nuint)(((x + 6) * destinationStride) + y)); |
|||
column7.StoreUnsafe(ref destinationBase, (nuint)(((x + 7) * destinationStride) + y)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,108 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Defines DC intra-prediction arithmetic.
|
|||
/// </content>
|
|||
internal static partial class HevcIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Implements DC prediction and its optional luma boundary filter.
|
|||
/// </summary>
|
|||
private readonly struct DcOperator : IHevcIntraPredictionOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges, |
|||
Span<ushort> scratch) |
|||
{ |
|||
uint sum = SumSamples(top.Slice(1, size)) + SumSamples(left.Slice(1, size)); |
|||
ushort dc = (ushort)((sum + (uint)size) >> (BitOperations.Log2((uint)size) + 1)); |
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
destination.Slice(y * destinationStride, size).Fill(dc); |
|||
} |
|||
|
|||
if (!filterPredictionEdges) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
destination[0] = (ushort)((top[1] + left[1] + (2 * dc) + 2) >> 2); |
|||
for (int x = 1; x < size; x++) |
|||
{ |
|||
destination[x] = (ushort)((top[x + 1] + (3 * dc) + 2) >> 2); |
|||
} |
|||
|
|||
for (int y = 1; y < size; y++) |
|||
{ |
|||
destination[y * destinationStride] = (ushort)((left[y + 1] + (3 * dc) + 2) >> 2); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sums reconstructed reference samples without overflowing their 16-bit storage.
|
|||
/// </summary>
|
|||
/// <param name="samples">The samples to sum.</param>
|
|||
/// <returns>The exact unsigned sum.</returns>
|
|||
private static uint SumSamples(ReadOnlySpan<ushort> samples) |
|||
{ |
|||
ref ushort samplesBase = ref MemoryMarshal.GetReference(samples); |
|||
uint sum = 0; |
|||
int i = 0; |
|||
|
|||
// Widen before reduction because a complete 64-sample, 12-bit reference edge exceeds UInt16. The shared index
|
|||
// lets narrower vectors consume only the remainder from the widest available path.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector512<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<ushort>.Count) |
|||
{ |
|||
(Vector512<uint> low, Vector512<uint> high) = Vector512.Widen(Vector512.LoadUnsafe(ref samplesBase, (nuint)i)); |
|||
sum += Vector512.Sum(low) + Vector512.Sum(high); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector256<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<ushort>.Count) |
|||
{ |
|||
(Vector256<uint> low, Vector256<uint> high) = Vector256.Widen(Vector256.LoadUnsafe(ref samplesBase, (nuint)i)); |
|||
sum += Vector256.Sum(low) + Vector256.Sum(high); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector128<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<ushort>.Count) |
|||
{ |
|||
(Vector128<uint> low, Vector128<uint> high) = Vector128.Widen(Vector128.LoadUnsafe(ref samplesBase, (nuint)i)); |
|||
sum += Vector128.Sum(low) + Vector128.Sum(high); |
|||
} |
|||
} |
|||
|
|||
for (; i < samples.Length; i++) |
|||
{ |
|||
sum += Unsafe.Add(ref samplesBase, i); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
} |
|||
} |
|||
@ -1,39 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Defines the HEVC intra-prediction operator contract.
|
|||
/// </content>
|
|||
internal static partial class HevcIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines one closed intra-prediction operation selected by the decoded mode.
|
|||
/// </summary>
|
|||
private interface IHevcIntraPredictionOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Reconstructs one square prediction block.
|
|||
/// </summary>
|
|||
/// <param name="top">The top-left, top, and top-right reference samples.</param>
|
|||
/// <param name="left">The top-left, left, and below-left reference samples.</param>
|
|||
/// <param name="destination">The destination buffer beginning at the block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="size">The square block side in samples.</param>
|
|||
/// <param name="mode">The decoded prediction mode.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the luma edge filter applies to the selected block.</param>
|
|||
/// <param name="scratch">The caller-owned block and extended-reference scratch space.</param>
|
|||
public static abstract void Predict( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges, |
|||
Span<ushort> scratch); |
|||
} |
|||
} |
|||
@ -1,271 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Defines planar intra-prediction arithmetic.
|
|||
/// </content>
|
|||
internal static partial class HevcIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Implements planar interpolation between the top, left, bottom-left, and top-right references.
|
|||
/// </summary>
|
|||
private readonly struct PlanarOperator : IHevcIntraPredictionOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges, |
|||
Span<ushort> scratch) |
|||
{ |
|||
ref ushort topBase = ref MemoryMarshal.GetReference(top); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
// Index zero is the shared corner, so the planar endpoint at coordinate N is stored at N + 1.
|
|||
uint bottomLeft = left[size + 1]; |
|||
uint topRight = top[size + 1]; |
|||
int shift = BitOperations.Log2((uint)size) + 1; |
|||
uint rounding = (uint)size; |
|||
|
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
uint leftSample = left[y + 1]; |
|||
uint topWeight = (uint)(size - y - 1); |
|||
uint bottomWeight = (uint)(y + 1); |
|||
ref ushort rowBase = ref Unsafe.Add(ref destinationBase, y * destinationStride); |
|||
int x = 0; |
|||
|
|||
// The two widened halves carry consecutive X coordinates. Each lane evaluates the normative
|
|||
// horizontal and vertical ramps, then narrows after the common rounded power-of-two division.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
Vector512<uint> indices = CreateIndicesVector512(); |
|||
int oneVectorFromEnd = size - Vector512<ushort>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> topSamples = Vector512.LoadUnsafe(ref topBase, (nuint)(x + 1)); |
|||
(Vector512<uint> topLow, Vector512<uint> topHigh) = Vector512.Widen(topSamples); |
|||
Vector512<uint> lowIndices = indices + Vector512.Create((uint)x); |
|||
Vector512<uint> highIndices = lowIndices + Vector512.Create((uint)Vector512<uint>.Count); |
|||
Vector512<uint> low = CalculatePlanarVector( |
|||
topLow, |
|||
lowIndices, |
|||
leftSample, |
|||
topRight, |
|||
bottomLeft, |
|||
topWeight, |
|||
bottomWeight, |
|||
(uint)size, |
|||
rounding, |
|||
shift); |
|||
|
|||
Vector512<uint> high = CalculatePlanarVector( |
|||
topHigh, |
|||
highIndices, |
|||
leftSample, |
|||
topRight, |
|||
bottomLeft, |
|||
topWeight, |
|||
bottomWeight, |
|||
(uint)size, |
|||
rounding, |
|||
shift); |
|||
|
|||
Vector512.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref rowBase, x)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<uint> indices = CreateIndicesVector256(); |
|||
int oneVectorFromEnd = size - Vector256<ushort>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> topSamples = Vector256.LoadUnsafe(ref topBase, (nuint)(x + 1)); |
|||
(Vector256<uint> topLow, Vector256<uint> topHigh) = Vector256.Widen(topSamples); |
|||
Vector256<uint> lowIndices = indices + Vector256.Create((uint)x); |
|||
Vector256<uint> highIndices = lowIndices + Vector256.Create((uint)Vector256<uint>.Count); |
|||
Vector256<uint> low = CalculatePlanarVector( |
|||
topLow, |
|||
lowIndices, |
|||
leftSample, |
|||
topRight, |
|||
bottomLeft, |
|||
topWeight, |
|||
bottomWeight, |
|||
(uint)size, |
|||
rounding, |
|||
shift); |
|||
|
|||
Vector256<uint> high = CalculatePlanarVector( |
|||
topHigh, |
|||
highIndices, |
|||
leftSample, |
|||
topRight, |
|||
bottomLeft, |
|||
topWeight, |
|||
bottomWeight, |
|||
(uint)size, |
|||
rounding, |
|||
shift); |
|||
|
|||
Vector256.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref rowBase, x)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<uint> indices = CreateIndicesVector128(); |
|||
int oneVectorFromEnd = size - Vector128<ushort>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> topSamples = Vector128.LoadUnsafe(ref topBase, (nuint)(x + 1)); |
|||
(Vector128<uint> topLow, Vector128<uint> topHigh) = Vector128.Widen(topSamples); |
|||
Vector128<uint> lowIndices = indices + Vector128.Create((uint)x); |
|||
Vector128<uint> highIndices = lowIndices + Vector128.Create((uint)Vector128<uint>.Count); |
|||
Vector128<uint> low = CalculatePlanarVector( |
|||
topLow, |
|||
lowIndices, |
|||
leftSample, |
|||
topRight, |
|||
bottomLeft, |
|||
topWeight, |
|||
bottomWeight, |
|||
(uint)size, |
|||
rounding, |
|||
shift); |
|||
|
|||
Vector128<uint> high = CalculatePlanarVector( |
|||
topHigh, |
|||
highIndices, |
|||
leftSample, |
|||
topRight, |
|||
bottomLeft, |
|||
topWeight, |
|||
bottomWeight, |
|||
(uint)size, |
|||
rounding, |
|||
shift); |
|||
|
|||
Vector128.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref rowBase, x)); |
|||
} |
|||
} |
|||
|
|||
for (; x < size; x++) |
|||
{ |
|||
uint horizontal = ((uint)(size - x - 1) * leftSample) + ((uint)(x + 1) * topRight); |
|||
uint vertical = ((uint)(size - y - 1) * top[x + 1]) + ((uint)(y + 1) * bottomLeft); |
|||
Unsafe.Add(ref rowBase, x) = (ushort)((horizontal + vertical + (uint)size) >> shift); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates one 512-bit half of a planar prediction row.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="indices">The zero-based X coordinates.</param>
|
|||
/// <param name="left">The left reference sample for the row.</param>
|
|||
/// <param name="topRight">The top-right reference sample.</param>
|
|||
/// <param name="bottomLeft">The bottom-left reference sample.</param>
|
|||
/// <param name="topWeight">The top-reference weight.</param>
|
|||
/// <param name="bottomWeight">The bottom-left-reference weight.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="rounding">The division rounding constant.</param>
|
|||
/// <param name="shift">The division shift.</param>
|
|||
/// <returns>The predicted samples as widened lanes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<uint> CalculatePlanarVector( |
|||
Vector512<uint> top, |
|||
Vector512<uint> indices, |
|||
uint left, |
|||
uint topRight, |
|||
uint bottomLeft, |
|||
uint topWeight, |
|||
uint bottomWeight, |
|||
uint size, |
|||
uint rounding, |
|||
int shift) |
|||
{ |
|||
Vector512<uint> horizontal = ((Vector512.Create(size - 1) - indices) * left) + ((indices + Vector512<uint>.One) * topRight); |
|||
Vector512<uint> vertical = (top * topWeight) + Vector512.Create(bottomLeft * bottomWeight); |
|||
return (horizontal + vertical + Vector512.Create(rounding)) >> shift; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates one 256-bit half of a planar prediction row.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="indices">The zero-based X coordinates.</param>
|
|||
/// <param name="left">The left reference sample for the row.</param>
|
|||
/// <param name="topRight">The top-right reference sample.</param>
|
|||
/// <param name="bottomLeft">The bottom-left reference sample.</param>
|
|||
/// <param name="topWeight">The top-reference weight.</param>
|
|||
/// <param name="bottomWeight">The bottom-left-reference weight.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="rounding">The division rounding constant.</param>
|
|||
/// <param name="shift">The division shift.</param>
|
|||
/// <returns>The predicted samples as widened lanes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<uint> CalculatePlanarVector( |
|||
Vector256<uint> top, |
|||
Vector256<uint> indices, |
|||
uint left, |
|||
uint topRight, |
|||
uint bottomLeft, |
|||
uint topWeight, |
|||
uint bottomWeight, |
|||
uint size, |
|||
uint rounding, |
|||
int shift) |
|||
{ |
|||
Vector256<uint> horizontal = ((Vector256.Create(size - 1) - indices) * left) + ((indices + Vector256<uint>.One) * topRight); |
|||
Vector256<uint> vertical = (top * topWeight) + Vector256.Create(bottomLeft * bottomWeight); |
|||
return (horizontal + vertical + Vector256.Create(rounding)) >> shift; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates one 128-bit half of a planar prediction row.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="indices">The zero-based X coordinates.</param>
|
|||
/// <param name="left">The left reference sample for the row.</param>
|
|||
/// <param name="topRight">The top-right reference sample.</param>
|
|||
/// <param name="bottomLeft">The bottom-left reference sample.</param>
|
|||
/// <param name="topWeight">The top-reference weight.</param>
|
|||
/// <param name="bottomWeight">The bottom-left-reference weight.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="rounding">The division rounding constant.</param>
|
|||
/// <param name="shift">The division shift.</param>
|
|||
/// <returns>The predicted samples as widened lanes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<uint> CalculatePlanarVector( |
|||
Vector128<uint> top, |
|||
Vector128<uint> indices, |
|||
uint left, |
|||
uint topRight, |
|||
uint bottomLeft, |
|||
uint topWeight, |
|||
uint bottomWeight, |
|||
uint size, |
|||
uint rounding, |
|||
int shift) |
|||
{ |
|||
Vector128<uint> horizontal = ((Vector128.Create(size - 1) - indices) * left) + ((indices + Vector128<uint>.One) * topRight); |
|||
Vector128<uint> vertical = (top * topWeight) + Vector128.Create(bottomLeft * bottomWeight); |
|||
return (horizontal + vertical + Vector128.Create(rounding)) >> shift; |
|||
} |
|||
} |
|||
} |
|||
@ -1,180 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Provides reference-sample preparation and filter selection.
|
|||
/// </content>
|
|||
internal static partial class HevcIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the angular-distance threshold for reference filtering at each supported block size.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> ReferenceFilterThresholds => [10, 7, 1, 0]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the temporary sample count required while preparing prediction references.
|
|||
/// </summary>
|
|||
/// <param name="log2Size">The base-two logarithm of the square prediction-block side.</param>
|
|||
/// <param name="unitWidth">The horizontal availability-unit width in plane samples.</param>
|
|||
/// <returns>The required number of <see cref="ushort"/> elements.</returns>
|
|||
public static int GetReferenceScratchLength(int log2Size, int unitWidth) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
|||
return (4 << log2Size) + unitWidth; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the selected mode uses filtered prediction references.
|
|||
/// </summary>
|
|||
/// <param name="plane">The reconstructed plane.</param>
|
|||
/// <param name="mode">The effective prediction mode in the inclusive range zero through thirty-four.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the square prediction-block side.</param>
|
|||
/// <param name="chromaFormat">The HEVC chroma-format identifier.</param>
|
|||
/// <param name="intraSmoothingDisabled">Whether the sequence disables intra-reference smoothing.</param>
|
|||
/// <returns><see langword="true"/> when the prepared references require filtering; otherwise, <see langword="false"/>.</returns>
|
|||
public static bool ShouldFilterReferenceSamples(HevcPlane plane, int mode, int log2Size, byte chromaFormat, bool intraSmoothingDisabled) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(mode, PlanarMode, 34, nameof(mode)); |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
|||
if (intraSmoothingDisabled || (plane != HevcPlane.Y && chromaFormat != 3) || mode == DcMode) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
int angularDistance = Math.Min(Math.Abs(mode - HorizontalMode), Math.Abs(mode - VerticalMode)); |
|||
return angularDistance > ReferenceFilterThresholds[log2Size - 2]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Prepares substituted top and left references from one reconstructed picture plane.
|
|||
/// </summary>
|
|||
/// <param name="picture">The reconstructed still-picture planes.</param>
|
|||
/// <param name="plane">The plane containing the prediction block.</param>
|
|||
/// <param name="x">The prediction-block left coordinate in plane samples.</param>
|
|||
/// <param name="y">The prediction-block top coordinate in plane samples.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the square prediction-block side.</param>
|
|||
/// <param name="unitWidth">The horizontal availability-unit width in plane samples.</param>
|
|||
/// <param name="unitHeight">The vertical availability-unit height in plane samples.</param>
|
|||
/// <param name="availableUnits">
|
|||
/// The availability flags ordered from the bottom-most below-left unit upward through top-left, then from the
|
|||
/// left-most above unit through the right-most above-right unit.
|
|||
/// </param>
|
|||
/// <param name="top">The destination top-left, top, and top-right reference samples.</param>
|
|||
/// <param name="left">The destination top-left, left, and below-left reference samples.</param>
|
|||
/// <param name="scratch">The caller-owned temporary storage sized by <see cref="GetReferenceScratchLength(int, int)"/>.</param>
|
|||
public static void PrepareReferenceSamples( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int log2Size, |
|||
int unitWidth, |
|||
int unitHeight, |
|||
ReadOnlySpan<bool> availableUnits, |
|||
Span<ushort> top, |
|||
Span<ushort> left, |
|||
Span<ushort> scratch) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
|||
int size = 1 << log2Size; |
|||
int referenceLength = (size * 2) + 1; |
|||
int leftUnitCount = (size * 2) / unitHeight; |
|||
int aboveUnitCount = (size * 2) / unitWidth; |
|||
int totalUnitCount = leftUnitCount + aboveUnitCount + 1; |
|||
ReadOnlySpan<bool> availability = availableUnits[..totalUnitCount]; |
|||
|
|||
if (availability.IndexOf(true) < 0) |
|||
{ |
|||
ushort midpoint = (ushort)(1 << (picture.GetBitDepth(plane) - 1)); |
|||
top[..referenceLength].Fill(midpoint); |
|||
left[..referenceLength].Fill(midpoint); |
|||
return; |
|||
} |
|||
|
|||
if (availability.IndexOf(false) < 0) |
|||
{ |
|||
picture.GetRowSpan(plane, y - 1).Slice(x - 1, referenceLength).CopyTo(top); |
|||
left[0] = top[0]; |
|||
for (int i = 1; i < referenceLength; i++) |
|||
{ |
|||
left[i] = picture.GetRowSpan(plane, y + i - 1)[x - 1]; |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
int leftSampleCount = size * 2; |
|||
int lineLength = leftSampleCount + unitWidth + (size * 2); |
|||
Span<ushort> line = scratch[..lineLength]; |
|||
line.Fill((ushort)(1 << (picture.GetBitDepth(plane) - 1))); |
|||
|
|||
// The logical line runs from the bottom-most below-left sample towards the corner and then to the farthest
|
|||
// above-right sample. This makes substitution a forward fill across availability units.
|
|||
for (int unit = 0; unit < leftUnitCount; unit++) |
|||
{ |
|||
if (!availability[unit]) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
int sourceY = y + ((leftUnitCount - unit - 1) * unitHeight); |
|||
int destinationEnd = ((unit + 1) * unitHeight) - 1; |
|||
for (int offset = 0; offset < unitHeight; offset++) |
|||
{ |
|||
line[destinationEnd - offset] = picture.GetRowSpan(plane, sourceY + offset)[x - 1]; |
|||
} |
|||
} |
|||
|
|||
int cornerUnit = leftUnitCount; |
|||
if (availability[cornerUnit]) |
|||
{ |
|||
line.Slice(leftSampleCount, unitWidth).Fill(picture.GetRowSpan(plane, y - 1)[x - 1]); |
|||
} |
|||
|
|||
int topStart = leftSampleCount + unitWidth; |
|||
ReadOnlySpan<bool> aboveAvailability = availability.Slice(cornerUnit + 1, aboveUnitCount); |
|||
if (aboveAvailability.IndexOf(true) >= 0) |
|||
{ |
|||
// A top-edge block can still have reconstructed left references. Load the preceding row only when
|
|||
// the availability derivation proves that at least one above or above-right unit exists.
|
|||
ReadOnlySpan<ushort> aboveRow = picture.GetRowSpan(plane, y - 1); |
|||
for (int unit = 0; unit < aboveUnitCount; unit++) |
|||
{ |
|||
if (aboveAvailability[unit]) |
|||
{ |
|||
aboveRow.Slice(x + (unit * unitWidth), unitWidth).CopyTo(line.Slice(topStart + (unit * unitWidth), unitWidth)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
int firstAvailableUnit = availability.IndexOf(true); |
|||
int firstAvailableOffset = firstAvailableUnit < leftUnitCount |
|||
? firstAvailableUnit * unitHeight |
|||
: leftSampleCount + ((firstAvailableUnit - leftUnitCount) * unitWidth); |
|||
|
|||
int lineOffset = 0; |
|||
ushort precedingSample = line[firstAvailableOffset]; |
|||
for (int unit = 0; unit < totalUnitCount; unit++) |
|||
{ |
|||
int sampleCount = unit < leftUnitCount ? unitHeight : unitWidth; |
|||
Span<ushort> unitSamples = line.Slice(lineOffset, sampleCount); |
|||
if (!availability[unit]) |
|||
{ |
|||
unitSamples.Fill(precedingSample); |
|||
} |
|||
|
|||
precedingSample = unitSamples[^1]; |
|||
lineOffset += sampleCount; |
|||
} |
|||
|
|||
int cornerOffset = leftSampleCount + unitWidth - 1; |
|||
top[0] = left[0] = line[cornerOffset]; |
|||
line.Slice(topStart, size * 2).CopyTo(top[1..]); |
|||
for (int i = 1; i < referenceLength; i++) |
|||
{ |
|||
left[i] = line[leftSampleCount - i]; |
|||
} |
|||
} |
|||
} |
|||
@ -1,387 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs HEVC intra-prediction blocks from prepared neighboring samples.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Closed static operators select planar, DC, or angular arithmetic once per block. SIMD rows keep neighboring output
|
|||
/// columns in consecutive lanes; broadcast left endpoints and vector top references then evaluate the interpolation
|
|||
/// without per-sample mode dispatch. Horizontal angular prediction reuses the vertical kernel in contiguous scratch
|
|||
/// storage and transposes once into the strided destination.
|
|||
/// </remarks>
|
|||
internal static partial class HevcIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// The HEVC planar prediction mode.
|
|||
/// </summary>
|
|||
private const int PlanarMode = 0; |
|||
|
|||
/// <summary>
|
|||
/// The HEVC DC prediction mode.
|
|||
/// </summary>
|
|||
private const int DcMode = 1; |
|||
|
|||
/// <summary>
|
|||
/// The HEVC horizontal prediction mode.
|
|||
/// </summary>
|
|||
private const int HorizontalMode = 10; |
|||
|
|||
/// <summary>
|
|||
/// The first prediction mode whose main reference is the top row.
|
|||
/// </summary>
|
|||
private const int FirstVerticalMode = 18; |
|||
|
|||
/// <summary>
|
|||
/// The HEVC vertical prediction mode.
|
|||
/// </summary>
|
|||
private const int VerticalMode = 26; |
|||
|
|||
/// <summary>
|
|||
/// The largest transform-block side supported by HEVC intra prediction.
|
|||
/// </summary>
|
|||
private const int MaximumBlockSize = 32; |
|||
|
|||
/// <summary>
|
|||
/// Gets the angle selected by each absolute angular-mode displacement.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<int> PredictionAngles => [0, 2, 5, 9, 13, 17, 21, 26, 32]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the reciprocal angle used to extend the main reference for negative directions.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<int> InversePredictionAngles => [0, 4096, 1638, 910, 630, 482, 390, 315, 256]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the scratch length required to predict a block of the specified size.
|
|||
/// </summary>
|
|||
/// <param name="log2Size">The base-two logarithm of the square block side.</param>
|
|||
/// <returns>The required number of <see cref="ushort"/> elements.</returns>
|
|||
public static int GetScratchLength(int log2Size) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
|||
int size = 1 << log2Size; |
|||
return (size * size) + (4 * size) + 1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one square intra-prediction block using a closed operator selected by the decoded mode.
|
|||
/// </summary>
|
|||
/// <param name="top">The top-left, top, and top-right reference samples.</param>
|
|||
/// <param name="left">The top-left, left, and below-left reference samples.</param>
|
|||
/// <param name="destination">The destination buffer beginning at the block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the square block side.</param>
|
|||
/// <param name="mode">The decoded prediction mode in the inclusive range zero through thirty-four.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the luma edge filter applies to the selected block.</param>
|
|||
/// <param name="scratch">The caller-owned scratch returned by <see cref="GetScratchLength(int)"/>.</param>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int log2Size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges, |
|||
Span<ushort> scratch) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
|||
DebugGuard.MustBeBetweenOrEqualTo(mode, PlanarMode, 34, nameof(mode)); |
|||
int size = 1 << log2Size; |
|||
|
|||
switch (mode) |
|||
{ |
|||
case PlanarMode: |
|||
Predict<PlanarOperator>( |
|||
top, |
|||
left, |
|||
destination, |
|||
destinationStride, |
|||
size, |
|||
mode, |
|||
bitDepth, |
|||
filterPredictionEdges, |
|||
scratch); |
|||
break; |
|||
case DcMode: |
|||
Predict<DcOperator>( |
|||
top, |
|||
left, |
|||
destination, |
|||
destinationStride, |
|||
size, |
|||
mode, |
|||
bitDepth, |
|||
filterPredictionEdges, |
|||
scratch); |
|||
break; |
|||
default: |
|||
Predict<AngularOperator>( |
|||
top, |
|||
left, |
|||
destination, |
|||
destinationStride, |
|||
size, |
|||
mode, |
|||
bitDepth, |
|||
filterPredictionEdges, |
|||
scratch); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Filters prepared reference samples using the normative three-tap or strong bilinear filter.
|
|||
/// </summary>
|
|||
/// <param name="top">The unfiltered top-left, top, and top-right samples.</param>
|
|||
/// <param name="left">The unfiltered top-left, left, and below-left samples.</param>
|
|||
/// <param name="filteredTop">The destination top reference.</param>
|
|||
/// <param name="filteredLeft">The destination left reference.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the square prediction-block side.</param>
|
|||
/// <param name="bitDepth">The reconstructed luma precision.</param>
|
|||
/// <param name="strongIntraSmoothingEnabled">Whether the sequence permits strong intra smoothing.</param>
|
|||
public static void FilterReferenceSamples( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> filteredTop, |
|||
Span<ushort> filteredLeft, |
|||
int log2Size, |
|||
int bitDepth, |
|||
bool strongIntraSmoothingEnabled) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
|||
int size = 1 << log2Size; |
|||
int referenceLength = (size * 2) + 1; |
|||
bool useStrongSmoothing = strongIntraSmoothingEnabled && size == MaximumBlockSize; |
|||
if (useStrongSmoothing) |
|||
{ |
|||
int threshold = 1 << (bitDepth - 5); |
|||
int last = referenceLength - 1; |
|||
bool leftIsBilinear = Math.Abs((left[last] + left[0]) - (2 * left[size])) < threshold; |
|||
bool topIsBilinear = Math.Abs((top[0] + top[last]) - (2 * top[size])) < threshold; |
|||
useStrongSmoothing = leftIsBilinear && topIsBilinear; |
|||
} |
|||
|
|||
if (useStrongSmoothing) |
|||
{ |
|||
FilterReferenceBilinear(top[..referenceLength], filteredTop, size); |
|||
FilterReferenceBilinear(left[..referenceLength], filteredLeft, size); |
|||
return; |
|||
} |
|||
|
|||
// The corner belongs to both references. Filtering it once from the first samples on both sides keeps the
|
|||
// two logical arrays identical at index zero before their independent one-dimensional filters continue.
|
|||
ushort filteredCorner = (ushort)((left[1] + (2 * top[0]) + top[1] + 2) >> 2); |
|||
filteredTop[0] = filteredCorner; |
|||
filteredLeft[0] = filteredCorner; |
|||
FilterReferenceThreeTap(top[..referenceLength], filteredTop); |
|||
FilterReferenceThreeTap(left[..referenceLength], filteredLeft); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invokes one statically selected prediction operator without interface dispatch in the block loop.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The selected prediction operator.</typeparam>
|
|||
/// <param name="top">The prepared top reference.</param>
|
|||
/// <param name="left">The prepared left reference.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="size">The square block side in samples.</param>
|
|||
/// <param name="mode">The decoded prediction mode.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the luma edge filter applies.</param>
|
|||
/// <param name="scratch">The caller-owned prediction scratch.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void Predict<TOperator>( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges, |
|||
Span<ushort> scratch) |
|||
where TOperator : struct, IHevcIntraPredictionOperator |
|||
=> TOperator.Predict( |
|||
top, |
|||
left, |
|||
destination, |
|||
destinationStride, |
|||
size, |
|||
mode, |
|||
bitDepth, |
|||
filterPredictionEdges, |
|||
scratch); |
|||
|
|||
/// <summary>
|
|||
/// Applies the strong bilinear filter between the reference endpoints.
|
|||
/// </summary>
|
|||
/// <param name="source">The complete unfiltered reference.</param>
|
|||
/// <param name="destination">The complete filtered reference.</param>
|
|||
/// <param name="size">The prediction-block side in samples.</param>
|
|||
private static void FilterReferenceBilinear(ReadOnlySpan<ushort> source, Span<ushort> destination, int size) |
|||
{ |
|||
int last = source.Length - 1; |
|||
destination[0] = source[0]; |
|||
destination[last] = source[last]; |
|||
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
uint first = source[0]; |
|||
uint final = source[last]; |
|||
int shift = BitOperations.Log2((uint)(size * 2)); |
|||
uint rounding = (uint)size; |
|||
int i = 1; |
|||
|
|||
// Each widened lane represents one reference coordinate. The weights sum to 2N, so narrowing is exact
|
|||
// after the rounded shift for every supported 8, 10, and 12-bit sample.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
Vector512<uint> indices = CreateIndicesVector512(); |
|||
int oneVectorFromEnd = last - Vector512<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<uint> lowerIndices = indices + Vector512.Create((uint)i); |
|||
Vector512<uint> upperIndices = lowerIndices + Vector512.Create((uint)Vector512<uint>.Count); |
|||
Vector512<uint> lower = (((Vector512.Create((uint)last) - lowerIndices) * first) + (lowerIndices * final) + Vector512.Create(rounding)) >> shift; |
|||
Vector512<uint> upper = (((Vector512.Create((uint)last) - upperIndices) * first) + (upperIndices * final) + Vector512.Create(rounding)) >> shift; |
|||
Vector512.Narrow(lower, upper).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<uint> indices = CreateIndicesVector256(); |
|||
int oneVectorFromEnd = last - Vector256<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<uint> lowerIndices = indices + Vector256.Create((uint)i); |
|||
Vector256<uint> upperIndices = lowerIndices + Vector256.Create((uint)Vector256<uint>.Count); |
|||
Vector256<uint> lower = (((Vector256.Create((uint)last) - lowerIndices) * first) + (lowerIndices * final) + Vector256.Create(rounding)) >> shift; |
|||
Vector256<uint> upper = (((Vector256.Create((uint)last) - upperIndices) * first) + (upperIndices * final) + Vector256.Create(rounding)) >> shift; |
|||
Vector256.Narrow(lower, upper).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<uint> indices = CreateIndicesVector128(); |
|||
int oneVectorFromEnd = last - Vector128<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<uint> lowerIndices = indices + Vector128.Create((uint)i); |
|||
Vector128<uint> upperIndices = lowerIndices + Vector128.Create((uint)Vector128<uint>.Count); |
|||
Vector128<uint> lower = (((Vector128.Create((uint)last) - lowerIndices) * first) + (lowerIndices * final) + Vector128.Create(rounding)) >> shift; |
|||
Vector128<uint> upper = (((Vector128.Create((uint)last) - upperIndices) * first) + (upperIndices * final) + Vector128.Create(rounding)) >> shift; |
|||
Vector128.Narrow(lower, upper).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
for (; i < last; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationBase, i) = (ushort)((((last - i) * first) + (i * final) + rounding) >> shift); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the normal three-tap reference filter to every non-endpoint sample.
|
|||
/// </summary>
|
|||
/// <param name="source">The complete unfiltered reference.</param>
|
|||
/// <param name="destination">The complete filtered reference with its corner already initialized.</param>
|
|||
private static void FilterReferenceThreeTap(ReadOnlySpan<ushort> source, Span<ushort> destination) |
|||
{ |
|||
int last = source.Length - 1; |
|||
destination[last] = source[last]; |
|||
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
int i = 1; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = last - Vector512<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> previous = Vector512.LoadUnsafe(ref sourceBase, (nuint)(i - 1)); |
|||
Vector512<ushort> current = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector512<ushort> next = Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
|||
(Vector512<uint> previousLow, Vector512<uint> previousHigh) = Vector512.Widen(previous); |
|||
(Vector512<uint> currentLow, Vector512<uint> currentHigh) = Vector512.Widen(current); |
|||
(Vector512<uint> nextLow, Vector512<uint> nextHigh) = Vector512.Widen(next); |
|||
Vector512<uint> low = (previousLow + (currentLow << 1) + nextLow + Vector512.Create(2U)) >> 2; |
|||
Vector512<uint> high = (previousHigh + (currentHigh << 1) + nextHigh + Vector512.Create(2U)) >> 2; |
|||
Vector512.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = last - Vector256<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> previous = Vector256.LoadUnsafe(ref sourceBase, (nuint)(i - 1)); |
|||
Vector256<ushort> current = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector256<ushort> next = Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
|||
(Vector256<uint> previousLow, Vector256<uint> previousHigh) = Vector256.Widen(previous); |
|||
(Vector256<uint> currentLow, Vector256<uint> currentHigh) = Vector256.Widen(current); |
|||
(Vector256<uint> nextLow, Vector256<uint> nextHigh) = Vector256.Widen(next); |
|||
Vector256<uint> low = (previousLow + (currentLow << 1) + nextLow + Vector256.Create(2U)) >> 2; |
|||
Vector256<uint> high = (previousHigh + (currentHigh << 1) + nextHigh + Vector256.Create(2U)) >> 2; |
|||
Vector256.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = last - Vector128<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> previous = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i - 1)); |
|||
Vector128<ushort> current = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector128<ushort> next = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
|||
(Vector128<uint> previousLow, Vector128<uint> previousHigh) = Vector128.Widen(previous); |
|||
(Vector128<uint> currentLow, Vector128<uint> currentHigh) = Vector128.Widen(current); |
|||
(Vector128<uint> nextLow, Vector128<uint> nextHigh) = Vector128.Widen(next); |
|||
Vector128<uint> low = (previousLow + (currentLow << 1) + nextLow + Vector128.Create(2U)) >> 2; |
|||
Vector128<uint> high = (previousHigh + (currentHigh << 1) + nextHigh + Vector128.Create(2U)) >> 2; |
|||
Vector128.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
for (; i < last; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationBase, i) = (ushort)((source[i - 1] + (2 * source[i]) + source[i + 1] + 2) >> 2); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates the zero-through-fifteen lane indices used by 512-bit weighted interpolation.
|
|||
/// </summary>
|
|||
/// <returns>The ordered lane indices.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<uint> CreateIndicesVector512() |
|||
=> Vector512.Create(0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 11U, 12U, 13U, 14U, 15U); |
|||
|
|||
/// <summary>
|
|||
/// Creates the zero-through-seven lane indices used by 256-bit weighted interpolation.
|
|||
/// </summary>
|
|||
/// <returns>The ordered lane indices.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<uint> CreateIndicesVector256() => Vector256.Create(0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U); |
|||
|
|||
/// <summary>
|
|||
/// Creates the zero-through-three lane indices used by 128-bit weighted interpolation.
|
|||
/// </summary>
|
|||
/// <returns>The ordered lane indices.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<uint> CreateIndicesVector128() => Vector128.Create(0U, 1U, 2U, 3U); |
|||
} |
|||
@ -1,412 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs dequantized HEVC transform coefficients.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Consecutive quantized coefficients occupy consecutive 32-bit lanes. Scaling-list values are widened to the same lane
|
|||
/// shape before the inverse scale, quantization shift, rounding, and transform-range clamp are applied. Vector-width
|
|||
/// loops advance the complete coefficient prefix and leave only the final incomplete group to the scalar equation.
|
|||
/// </remarks>
|
|||
internal static class HevcInverseQuantizer |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the inverse quantization scale selected by the quantization-parameter remainder.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> InverseQuantizationScales => [40, 45, 51, 57, 64, 72]; |
|||
|
|||
/// <summary>
|
|||
/// Dequantizes one square transform block using the effective component quantization parameter.
|
|||
/// </summary>
|
|||
/// <param name="quantized">The decoded quantized coefficients in raster order.</param>
|
|||
/// <param name="destination">The destination dequantized coefficients in raster order.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the transform-block side.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
|||
/// <param name="quantizationParameter">The effective nonnegative component quantization parameter including its bit-depth offset.</param>
|
|||
/// <param name="scalingListEnabled">Whether the governing sequence enables scaling lists.</param>
|
|||
/// <param name="scalingList">The effective picture scaling matrices.</param>
|
|||
/// <param name="plane">The reconstructed color plane.</param>
|
|||
/// <param name="isIntraPredicted">Whether the transform block belongs to an intra-predicted coding unit.</param>
|
|||
/// <param name="transformSkip">Whether the transform block bypasses the inverse transform.</param>
|
|||
/// <param name="extendedPrecisionProcessingEnabled">Whether transform-skip precision is extended by the sequence.</param>
|
|||
public static void Dequantize( |
|||
ReadOnlySpan<int> quantized, |
|||
Span<int> destination, |
|||
int log2Size, |
|||
int bitDepth, |
|||
int maxTransformDynamicRange, |
|||
int quantizationParameter, |
|||
bool scalingListEnabled, |
|||
HevcScalingList scalingList, |
|||
HevcPlane plane, |
|||
bool isIntraPredicted, |
|||
bool transformSkip, |
|||
bool extendedPrecisionProcessingEnabled) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(quantizationParameter, 0, nameof(quantizationParameter)); |
|||
int size = 1 << log2Size; |
|||
int sampleCount = size * size; |
|||
DebugGuard.IsTrue(quantized.Length >= sampleCount, "The quantized coefficient span is shorter than the transform block."); |
|||
DebugGuard.IsTrue(destination.Length >= sampleCount, "The dequantized coefficient span is shorter than the transform block."); |
|||
|
|||
int transformShift = maxTransformDynamicRange - bitDepth - log2Size; |
|||
if (transformSkip && extendedPrecisionProcessingEnabled) |
|||
{ |
|||
transformShift = Math.Max(0, transformShift); |
|||
} |
|||
|
|||
int quantizationParameterPer = quantizationParameter / 6; |
|||
int quantizationParameterRemainder = quantizationParameter % 6; |
|||
int inverseQuantizationScale = InverseQuantizationScales[quantizationParameterRemainder]; |
|||
bool useScalingList = scalingListEnabled && (!transformSkip || log2Size == 2); |
|||
int rightShift = 6 - (transformShift + quantizationParameterPer) + (useScalingList ? 4 : 0); |
|||
int outputMinimum = -(1 << maxTransformDynamicRange); |
|||
int outputMaximum = (1 << maxTransformDynamicRange) - 1; |
|||
|
|||
// The input clip is part of the normative dequantization process. Its right-shift dependency ensures the
|
|||
// following signed 32-bit multiplication and optional left shift cannot overflow for any valid coefficient.
|
|||
int scaleBits = useScalingList ? 15 : 7; |
|||
int targetInputBitDepth = Math.Min(maxTransformDynamicRange + 1, 32 + rightShift - scaleBits); |
|||
int inputMinimum = -(1 << (targetInputBitDepth - 1)); |
|||
int inputMaximum = (1 << (targetInputBitDepth - 1)) - 1; |
|||
|
|||
if (!useScalingList) |
|||
{ |
|||
DequantizeUniform( |
|||
quantized[..sampleCount], |
|||
destination[..sampleCount], |
|||
inverseQuantizationScale, |
|||
rightShift, |
|||
inputMinimum, |
|||
inputMaximum, |
|||
outputMinimum, |
|||
outputMaximum); |
|||
|
|||
return; |
|||
} |
|||
|
|||
int sizeId = log2Size - 2; |
|||
int matrixId = (isIntraPredicted ? 0 : 3) + (int)plane; |
|||
ReadOnlySpan<byte> matrix = scalingList.GetExpandedMatrix(sizeId, matrixId); |
|||
DequantizeScalingList( |
|||
quantized[..sampleCount], |
|||
destination[..sampleCount], |
|||
matrix, |
|||
inverseQuantizationScale, |
|||
rightShift, |
|||
inputMinimum, |
|||
inputMaximum, |
|||
outputMinimum, |
|||
outputMaximum); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Dequantizes coefficients using one uniform inverse-quantization scale.
|
|||
/// </summary>
|
|||
/// <param name="source">The complete quantized coefficient block.</param>
|
|||
/// <param name="destination">The complete dequantized coefficient block.</param>
|
|||
/// <param name="scale">The inverse-quantization scale.</param>
|
|||
/// <param name="rightShift">The signed normalization shift.</param>
|
|||
/// <param name="inputMinimum">The inclusive quantized input minimum.</param>
|
|||
/// <param name="inputMaximum">The inclusive quantized input maximum.</param>
|
|||
/// <param name="outputMinimum">The inclusive dequantized output minimum.</param>
|
|||
/// <param name="outputMaximum">The inclusive dequantized output maximum.</param>
|
|||
private static void DequantizeUniform( |
|||
ReadOnlySpan<int> source, |
|||
Span<int> destination, |
|||
int scale, |
|||
int rightShift, |
|||
int inputMinimum, |
|||
int inputMaximum, |
|||
int outputMinimum, |
|||
int outputMaximum) |
|||
{ |
|||
ref int sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
int i = 0; |
|||
|
|||
// Descending widths advance one shared coefficient offset. Smaller registers consume complete groups left by a
|
|||
// wider path, so the scalar loop sees fewer than four values without any overlapping dequantization stores.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = source.Length - Vector512<int>.Count; |
|||
Vector512<int> weights = Vector512.Create(scale); |
|||
for (; i <= oneVectorFromEnd; i += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> values = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Dequantize(values, weights, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum).StoreUnsafe(ref destinationBase, (nuint)i); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = source.Length - Vector256<int>.Count; |
|||
Vector256<int> weights = Vector256.Create(scale); |
|||
for (; i <= oneVectorFromEnd; i += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> values = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Dequantize(values, weights, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum).StoreUnsafe(ref destinationBase, (nuint)i); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = source.Length - Vector128<int>.Count; |
|||
Vector128<int> weights = Vector128.Create(scale); |
|||
for (; i <= oneVectorFromEnd; i += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> values = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Dequantize(values, weights, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum).StoreUnsafe(ref destinationBase, (nuint)i); |
|||
} |
|||
} |
|||
|
|||
for (; i < source.Length; i++) |
|||
{ |
|||
destination[i] = Dequantize(source[i], scale, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Dequantizes coefficients using the expanded scaling matrix selected for the transform block.
|
|||
/// </summary>
|
|||
/// <param name="source">The complete quantized coefficient block.</param>
|
|||
/// <param name="destination">The complete dequantized coefficient block.</param>
|
|||
/// <param name="matrix">The scaling matrix expanded to the transform dimensions.</param>
|
|||
/// <param name="inverseQuantizationScale">The inverse-quantization scale.</param>
|
|||
/// <param name="rightShift">The signed normalization shift.</param>
|
|||
/// <param name="inputMinimum">The inclusive quantized input minimum.</param>
|
|||
/// <param name="inputMaximum">The inclusive quantized input maximum.</param>
|
|||
/// <param name="outputMinimum">The inclusive dequantized output minimum.</param>
|
|||
/// <param name="outputMaximum">The inclusive dequantized output maximum.</param>
|
|||
private static void DequantizeScalingList( |
|||
ReadOnlySpan<int> source, |
|||
Span<int> destination, |
|||
ReadOnlySpan<byte> matrix, |
|||
int inverseQuantizationScale, |
|||
int rightShift, |
|||
int inputMinimum, |
|||
int inputMaximum, |
|||
int outputMinimum, |
|||
int outputMaximum) |
|||
{ |
|||
ref int sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
ref byte matrixBase = ref MemoryMarshal.GetReference(matrix); |
|||
int i = 0; |
|||
|
|||
// Scaling matrices use one unsigned byte per coefficient. Each width loads exactly its matching byte count,
|
|||
// widens in source order, and multiplies by the common inverse-quantization scale before signed arithmetic.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = source.Length - Vector512<int>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> values = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector512<int> weights = LoadScalingWeightsVector512(ref Unsafe.Add(ref matrixBase, i), inverseQuantizationScale); |
|||
Dequantize(values, weights, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum).StoreUnsafe(ref destinationBase, (nuint)i); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = source.Length - Vector256<int>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> values = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector256<int> weights = LoadScalingWeightsVector256(ref Unsafe.Add(ref matrixBase, i), inverseQuantizationScale); |
|||
Dequantize(values, weights, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum).StoreUnsafe(ref destinationBase, (nuint)i); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = source.Length - Vector128<int>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> values = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector128<int> weights = LoadScalingWeightsVector128(ref Unsafe.Add(ref matrixBase, i), inverseQuantizationScale); |
|||
Dequantize(values, weights, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum).StoreUnsafe(ref destinationBase, (nuint)i); |
|||
} |
|||
} |
|||
|
|||
for (; i < source.Length; i++) |
|||
{ |
|||
int weight = Unsafe.Add(ref matrixBase, i) * inverseQuantizationScale; |
|||
destination[i] = Dequantize(source[i], weight, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads and widens sixteen scaling coefficients for a 512-bit coefficient vector.
|
|||
/// </summary>
|
|||
/// <param name="source">The first scaling coefficient.</param>
|
|||
/// <param name="scale">The inverse-quantization scale.</param>
|
|||
/// <returns>The sixteen ordered dequantization weights.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> LoadScalingWeightsVector512(ref byte source, int scale) |
|||
{ |
|||
Vector128<byte> packed = Vector128.LoadUnsafe(ref source); |
|||
(Vector128<ushort> lower16, Vector128<ushort> upper16) = Vector128.Widen(packed); |
|||
Vector256<uint> lower32 = Vector256.Create(Vector128.WidenLower(lower16), Vector128.WidenUpper(lower16)); |
|||
Vector256<uint> upper32 = Vector256.Create(Vector128.WidenLower(upper16), Vector128.WidenUpper(upper16)); |
|||
return Vector512.Create(lower32, upper32).AsInt32() * Vector512.Create(scale); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads and widens eight scaling coefficients for a 256-bit coefficient vector.
|
|||
/// </summary>
|
|||
/// <param name="source">The first scaling coefficient.</param>
|
|||
/// <param name="scale">The inverse-quantization scale.</param>
|
|||
/// <returns>The eight ordered dequantization weights.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> LoadScalingWeightsVector256(ref byte source, int scale) |
|||
{ |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref source); |
|||
Vector128<ushort> values16 = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()); |
|||
Vector256<uint> values32 = Vector256.Create(Vector128.WidenLower(values16), Vector128.WidenUpper(values16)); |
|||
return values32.AsInt32() * Vector256.Create(scale); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads and widens four scaling coefficients for a 128-bit coefficient vector.
|
|||
/// </summary>
|
|||
/// <param name="source">The first scaling coefficient.</param>
|
|||
/// <param name="scale">The inverse-quantization scale.</param>
|
|||
/// <returns>The four ordered dequantization weights.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> LoadScalingWeightsVector128(ref byte source, int scale) |
|||
{ |
|||
uint packed = Unsafe.ReadUnaligned<uint>(ref source); |
|||
Vector128<ushort> values16 = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()); |
|||
Vector128<uint> values32 = Vector128.WidenLower(values16); |
|||
return values32.AsInt32() * Vector128.Create(scale); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Dequantizes sixteen signed coefficients with independent scaling weights.
|
|||
/// </summary>
|
|||
/// <param name="values">The quantized coefficients.</param>
|
|||
/// <param name="weights">The dequantization weights.</param>
|
|||
/// <param name="rightShift">The signed normalization shift.</param>
|
|||
/// <param name="inputMinimum">The inclusive quantized input minimum.</param>
|
|||
/// <param name="inputMaximum">The inclusive quantized input maximum.</param>
|
|||
/// <param name="outputMinimum">The inclusive dequantized output minimum.</param>
|
|||
/// <param name="outputMaximum">The inclusive dequantized output maximum.</param>
|
|||
/// <returns>The dequantized coefficients.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> Dequantize( |
|||
Vector512<int> values, |
|||
Vector512<int> weights, |
|||
int rightShift, |
|||
int inputMinimum, |
|||
int inputMaximum, |
|||
int outputMinimum, |
|||
int outputMaximum) |
|||
{ |
|||
// A positive rightShift applies nearest-integer rounding before division. A nonpositive value represents an
|
|||
// exact left shift; the earlier input clamp guarantees that multiplication and shifting remain in Int32 range.
|
|||
values = Vector512.Clamp(values, Vector512.Create(inputMinimum), Vector512.Create(inputMaximum)); |
|||
Vector512<int> result = values * weights; |
|||
result = rightShift > 0 |
|||
? (result + Vector512.Create(1 << (rightShift - 1))) >> rightShift |
|||
: result << -rightShift; |
|||
|
|||
return Vector512.Clamp(result, Vector512.Create(outputMinimum), Vector512.Create(outputMaximum)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Dequantizes eight signed coefficients with independent scaling weights.
|
|||
/// </summary>
|
|||
/// <param name="values">The quantized coefficients.</param>
|
|||
/// <param name="weights">The dequantization weights.</param>
|
|||
/// <param name="rightShift">The signed normalization shift.</param>
|
|||
/// <param name="inputMinimum">The inclusive quantized input minimum.</param>
|
|||
/// <param name="inputMaximum">The inclusive quantized input maximum.</param>
|
|||
/// <param name="outputMinimum">The inclusive dequantized output minimum.</param>
|
|||
/// <param name="outputMaximum">The inclusive dequantized output maximum.</param>
|
|||
/// <returns>The dequantized coefficients.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> Dequantize( |
|||
Vector256<int> values, |
|||
Vector256<int> weights, |
|||
int rightShift, |
|||
int inputMinimum, |
|||
int inputMaximum, |
|||
int outputMinimum, |
|||
int outputMaximum) |
|||
{ |
|||
values = Vector256.Clamp(values, Vector256.Create(inputMinimum), Vector256.Create(inputMaximum)); |
|||
Vector256<int> result = values * weights; |
|||
result = rightShift > 0 |
|||
? (result + Vector256.Create(1 << (rightShift - 1))) >> rightShift |
|||
: result << -rightShift; |
|||
|
|||
return Vector256.Clamp(result, Vector256.Create(outputMinimum), Vector256.Create(outputMaximum)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Dequantizes four signed coefficients with independent scaling weights.
|
|||
/// </summary>
|
|||
/// <param name="values">The quantized coefficients.</param>
|
|||
/// <param name="weights">The dequantization weights.</param>
|
|||
/// <param name="rightShift">The signed normalization shift.</param>
|
|||
/// <param name="inputMinimum">The inclusive quantized input minimum.</param>
|
|||
/// <param name="inputMaximum">The inclusive quantized input maximum.</param>
|
|||
/// <param name="outputMinimum">The inclusive dequantized output minimum.</param>
|
|||
/// <param name="outputMaximum">The inclusive dequantized output maximum.</param>
|
|||
/// <returns>The dequantized coefficients.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> Dequantize( |
|||
Vector128<int> values, |
|||
Vector128<int> weights, |
|||
int rightShift, |
|||
int inputMinimum, |
|||
int inputMaximum, |
|||
int outputMinimum, |
|||
int outputMaximum) |
|||
{ |
|||
values = Vector128.Clamp(values, Vector128.Create(inputMinimum), Vector128.Create(inputMaximum)); |
|||
Vector128<int> result = values * weights; |
|||
result = rightShift > 0 |
|||
? (result + Vector128.Create(1 << (rightShift - 1))) >> rightShift |
|||
: result << -rightShift; |
|||
|
|||
return Vector128.Clamp(result, Vector128.Create(outputMinimum), Vector128.Create(outputMaximum)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Dequantizes one signed coefficient.
|
|||
/// </summary>
|
|||
/// <param name="value">The quantized coefficient.</param>
|
|||
/// <param name="weight">The dequantization weight.</param>
|
|||
/// <param name="rightShift">The signed normalization shift.</param>
|
|||
/// <param name="inputMinimum">The inclusive quantized input minimum.</param>
|
|||
/// <param name="inputMaximum">The inclusive quantized input maximum.</param>
|
|||
/// <param name="outputMinimum">The inclusive dequantized output minimum.</param>
|
|||
/// <param name="outputMaximum">The inclusive dequantized output maximum.</param>
|
|||
/// <returns>The dequantized coefficient.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int Dequantize( |
|||
int value, |
|||
int weight, |
|||
int rightShift, |
|||
int inputMinimum, |
|||
int inputMaximum, |
|||
int outputMinimum, |
|||
int outputMaximum) |
|||
{ |
|||
int result = Math.Clamp(value, inputMinimum, inputMaximum) * weight; |
|||
result = rightShift > 0 ? (result + (1 << (rightShift - 1))) >> rightShift : result << -rightShift; |
|||
return Math.Clamp(result, outputMinimum, outputMaximum); |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Defines the sixteen-point inverse discrete cosine transform operator.
|
|||
/// </content>
|
|||
internal static partial class HevcInverseTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the sixteen-point inverse discrete cosine transform.
|
|||
/// </summary>
|
|||
private readonly struct DiscreteCosine16Operator : IHevcInverseTransformOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Size => 16; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesButterfly => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int GetCoefficient(int frequency, int position) |
|||
{ |
|||
if (frequency == 0) |
|||
{ |
|||
return 64; |
|||
} |
|||
|
|||
int angle = ((2 * position) + 1) * frequency * 2; |
|||
angle &= 127; |
|||
if (angle > 64) |
|||
{ |
|||
angle = 128 - angle; |
|||
} |
|||
|
|||
// The second quadrant reuses the first-quadrant magnitude with a negative sign.
|
|||
if (angle > 32) |
|||
{ |
|||
return -DiscreteCosineMagnitudes[64 - angle]; |
|||
} |
|||
|
|||
return DiscreteCosineMagnitudes[angle]; |
|||
} |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Defines the thirty-two-point inverse discrete cosine transform operator.
|
|||
/// </content>
|
|||
internal static partial class HevcInverseTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the thirty-two-point inverse discrete cosine transform.
|
|||
/// </summary>
|
|||
private readonly struct DiscreteCosine32Operator : IHevcInverseTransformOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Size => 32; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesButterfly => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int GetCoefficient(int frequency, int position) |
|||
{ |
|||
if (frequency == 0) |
|||
{ |
|||
return 64; |
|||
} |
|||
|
|||
int angle = ((2 * position) + 1) * frequency * 1; |
|||
angle &= 127; |
|||
if (angle > 64) |
|||
{ |
|||
angle = 128 - angle; |
|||
} |
|||
|
|||
// The second quadrant reuses the first-quadrant magnitude with a negative sign.
|
|||
if (angle > 32) |
|||
{ |
|||
return -DiscreteCosineMagnitudes[64 - angle]; |
|||
} |
|||
|
|||
return DiscreteCosineMagnitudes[angle]; |
|||
} |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Defines the four-point inverse discrete cosine transform operator.
|
|||
/// </content>
|
|||
internal static partial class HevcInverseTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the four-point inverse discrete cosine transform.
|
|||
/// </summary>
|
|||
private readonly struct DiscreteCosine4Operator : IHevcInverseTransformOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Size => 4; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesButterfly => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int GetCoefficient(int frequency, int position) |
|||
{ |
|||
if (frequency == 0) |
|||
{ |
|||
return 64; |
|||
} |
|||
|
|||
int angle = ((2 * position) + 1) * frequency * 8; |
|||
angle &= 127; |
|||
if (angle > 64) |
|||
{ |
|||
angle = 128 - angle; |
|||
} |
|||
|
|||
// The second quadrant reuses the first-quadrant magnitude with a negative sign.
|
|||
if (angle > 32) |
|||
{ |
|||
return -DiscreteCosineMagnitudes[64 - angle]; |
|||
} |
|||
|
|||
return DiscreteCosineMagnitudes[angle]; |
|||
} |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Defines the eight-point inverse discrete cosine transform operator.
|
|||
/// </content>
|
|||
internal static partial class HevcInverseTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the eight-point inverse discrete cosine transform.
|
|||
/// </summary>
|
|||
private readonly struct DiscreteCosine8Operator : IHevcInverseTransformOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Size => 8; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesButterfly => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int GetCoefficient(int frequency, int position) |
|||
{ |
|||
if (frequency == 0) |
|||
{ |
|||
return 64; |
|||
} |
|||
|
|||
int angle = ((2 * position) + 1) * frequency * 4; |
|||
angle &= 127; |
|||
if (angle > 64) |
|||
{ |
|||
angle = 128 - angle; |
|||
} |
|||
|
|||
// The second quadrant reuses the first-quadrant magnitude with a negative sign.
|
|||
if (angle > 32) |
|||
{ |
|||
return -DiscreteCosineMagnitudes[64 - angle]; |
|||
} |
|||
|
|||
return DiscreteCosineMagnitudes[angle]; |
|||
} |
|||
} |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Defines the four-point inverse discrete sine transform operator.
|
|||
/// </content>
|
|||
internal static partial class HevcInverseTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Implements the four-point inverse discrete sine transform.
|
|||
/// </summary>
|
|||
private readonly struct DiscreteSine4Operator : IHevcInverseTransformOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the inverse-DST matrix in frequency-major order.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<sbyte> Coefficients => |
|||
[ |
|||
29, 55, 74, 84, |
|||
74, 74, 0, -74, |
|||
84, -29, -74, 55, |
|||
55, -84, 74, -29 |
|||
]; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static int Size => 4; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesButterfly => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int GetCoefficient(int frequency, int position) |
|||
{ |
|||
return Coefficients[(frequency * Size) + position]; |
|||
} |
|||
} |
|||
} |
|||
@ -1,547 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Provides the shared HEVC inverse-transform stage and transpose operations. Vector lanes represent independent
|
|||
/// transform lines, while consecutive scratch rows represent frequency groups in the partial-butterfly factorization.
|
|||
/// Arithmetic never mixes lines; transposition is the only operation that exchanges row and column coordinates.
|
|||
/// </content>
|
|||
internal static partial class HevcInverseTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the common inverse-DCT magnitudes ordered on the pi-over-sixty-four angle grid.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<sbyte> DiscreteCosineMagnitudes => |
|||
[ |
|||
90, 90, 90, 90, 89, 88, 87, 85, 83, 82, 80, 78, 75, 73, 70, 67, 64, |
|||
61, 57, 54, 50, 46, 43, 38, 36, 31, 25, 22, 18, 13, 9, 4, 0 |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Calculates the disjoint odd-frequency groups that seed the HEVC partial-butterfly reconstruction.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The selected inverse-DCT operator.</typeparam>
|
|||
/// <param name="source">The frequency rows followed by contiguous independent lines.</param>
|
|||
/// <param name="groups">The destination group rows.</param>
|
|||
/// <param name="lineCount">The number of independent lines transformed together.</param>
|
|||
private static void PopulateButterflyGroups<TOperator>(ReadOnlySpan<int> source, Span<int> groups, int lineCount) |
|||
where TOperator : struct, IHevcInverseTransformOperator |
|||
{ |
|||
int size = TOperator.Size; |
|||
int groupOffset = 0; |
|||
for (int frequencyStep = 2; frequencyStep < size; frequencyStep <<= 1) |
|||
{ |
|||
int outputCount = size / frequencyStep; |
|||
int firstFrequency = frequencyStep >> 1; |
|||
for (int position = 0; position < outputCount; position++) |
|||
{ |
|||
PopulateButterflyGroupRow<TOperator>( |
|||
source, |
|||
groups.Slice((groupOffset + position) * lineCount, lineCount), |
|||
lineCount, |
|||
firstFrequency, |
|||
frequencyStep, |
|||
position); |
|||
} |
|||
|
|||
groupOffset += outputCount; |
|||
} |
|||
|
|||
// The deepest even group contains the DC term and the transform's Nyquist-frequency term. It remains a
|
|||
// two-element group for every supported DCT size and closes the recursive butterfly hierarchy.
|
|||
for (int position = 0; position < 2; position++) |
|||
{ |
|||
PopulateButterflyGroupRow<TOperator>( |
|||
source, |
|||
groups.Slice((groupOffset + position) * lineCount, lineCount), |
|||
lineCount, |
|||
0, |
|||
size >> 1, |
|||
position); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates one partial-butterfly group row across all independent lines.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The selected inverse-DCT operator.</typeparam>
|
|||
/// <param name="source">The complete frequency-row input.</param>
|
|||
/// <param name="destination">The destination group row.</param>
|
|||
/// <param name="lineCount">The number of independent lines.</param>
|
|||
/// <param name="firstFrequency">The first frequency included by the group.</param>
|
|||
/// <param name="frequencyStep">The distance between included frequencies.</param>
|
|||
/// <param name="position">The group-relative spatial coordinate.</param>
|
|||
private static void PopulateButterflyGroupRow<TOperator>( |
|||
ReadOnlySpan<int> source, |
|||
Span<int> destination, |
|||
int lineCount, |
|||
int firstFrequency, |
|||
int frequencyStep, |
|||
int position) |
|||
where TOperator : struct, IHevcInverseTransformOperator |
|||
{ |
|||
ref int sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
int x = 0; |
|||
|
|||
// Source storage is frequency-major: advancing one lane moves to the same frequency in another independent
|
|||
// transform line. The shared X offset lets each narrower width continue exactly where the wider loop stopped.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = lineCount - Vector512<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> sum = Vector512<int>.Zero; |
|||
for (int frequency = firstFrequency; frequency < TOperator.Size; frequency += frequencyStep) |
|||
{ |
|||
Vector512<int> values = Vector512.LoadUnsafe(ref sourceBase, (nuint)((frequency * lineCount) + x)); |
|||
sum += values * Vector512.Create(TOperator.GetCoefficient(frequency, position)); |
|||
} |
|||
|
|||
sum.StoreUnsafe(ref destinationBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = lineCount - Vector256<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> sum = Vector256<int>.Zero; |
|||
for (int frequency = firstFrequency; frequency < TOperator.Size; frequency += frequencyStep) |
|||
{ |
|||
Vector256<int> values = Vector256.LoadUnsafe(ref sourceBase, (nuint)((frequency * lineCount) + x)); |
|||
sum += values * Vector256.Create(TOperator.GetCoefficient(frequency, position)); |
|||
} |
|||
|
|||
sum.StoreUnsafe(ref destinationBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = lineCount - Vector128<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> sum = Vector128<int>.Zero; |
|||
for (int frequency = firstFrequency; frequency < TOperator.Size; frequency += frequencyStep) |
|||
{ |
|||
Vector128<int> values = Vector128.LoadUnsafe(ref sourceBase, (nuint)((frequency * lineCount) + x)); |
|||
sum += values * Vector128.Create(TOperator.GetCoefficient(frequency, position)); |
|||
} |
|||
|
|||
sum.StoreUnsafe(ref destinationBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
for (; x < lineCount; x++) |
|||
{ |
|||
int sum = 0; |
|||
for (int frequency = firstFrequency; frequency < TOperator.Size; frequency += frequencyStep) |
|||
{ |
|||
sum += source[(frequency * lineCount) + x] * TOperator.GetCoefficient(frequency, position); |
|||
} |
|||
|
|||
destination[x] = sum; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Expands the disjoint partial-butterfly groups into spatial rows.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The selected inverse-DCT operator.</typeparam>
|
|||
/// <param name="initial">The buffer containing every disjoint group.</param>
|
|||
/// <param name="alternate">The alternate expansion buffer.</param>
|
|||
/// <param name="lineCount">The number of independent lines transformed together.</param>
|
|||
/// <param name="shift">The rounded right shift applied at the final hierarchy level.</param>
|
|||
/// <param name="minimum">The inclusive output minimum.</param>
|
|||
/// <param name="maximum">The inclusive output maximum.</param>
|
|||
/// <returns>The buffer containing the completed spatial rows.</returns>
|
|||
private static Span<int> CombineButterflyGroups<TOperator>( |
|||
Span<int> initial, |
|||
Span<int> alternate, |
|||
int lineCount, |
|||
int shift, |
|||
int minimum, |
|||
int maximum) |
|||
where TOperator : struct, IHevcInverseTransformOperator |
|||
{ |
|||
int size = TOperator.Size; |
|||
int combinedSize = 2; |
|||
int combinedStart = size - combinedSize; |
|||
bool currentIsInitial = true; |
|||
while (combinedSize < size) |
|||
{ |
|||
int oddStart = combinedStart - combinedSize; |
|||
bool finalLevel = (combinedSize << 1) == size; |
|||
ReadOnlySpan<int> even = currentIsInitial ? initial : alternate; |
|||
Span<int> destination = currentIsInitial ? alternate : initial; |
|||
|
|||
// Odd rows remain in the initial disjoint-group buffer while expanded even rows alternate buffers. This
|
|||
// preserves every source row needed by later hierarchy levels without allocating another transform block.
|
|||
for (int position = 0; position < combinedSize; position++) |
|||
{ |
|||
ReadOnlySpan<int> evenRow = even.Slice((combinedStart + position) * lineCount, lineCount); |
|||
ReadOnlySpan<int> oddRow = initial.Slice((oddStart + position) * lineCount, lineCount); |
|||
Span<int> positiveRow = destination.Slice((oddStart + position) * lineCount, lineCount); |
|||
Span<int> negativeRow = destination.Slice((oddStart + (2 * combinedSize) - 1 - position) * lineCount, lineCount); |
|||
CombineButterflyRows(evenRow, oddRow, positiveRow, negativeRow, finalLevel, shift, minimum, maximum); |
|||
} |
|||
|
|||
combinedStart = oddStart; |
|||
combinedSize <<= 1; |
|||
currentIsInitial = !currentIsInitial; |
|||
} |
|||
|
|||
return currentIsInitial ? initial : alternate; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Combines one symmetric pair of partial-butterfly rows.
|
|||
/// </summary>
|
|||
/// <param name="even">The even-frequency contribution.</param>
|
|||
/// <param name="odd">The odd-frequency contribution.</param>
|
|||
/// <param name="positive">The destination receiving the added contribution.</param>
|
|||
/// <param name="negative">The destination receiving the subtracted contribution.</param>
|
|||
/// <param name="roundAndClip">Whether this is the final hierarchy level.</param>
|
|||
/// <param name="shift">The rounded right shift applied at the final hierarchy level.</param>
|
|||
/// <param name="minimum">The inclusive output minimum.</param>
|
|||
/// <param name="maximum">The inclusive output maximum.</param>
|
|||
private static void CombineButterflyRows( |
|||
ReadOnlySpan<int> even, |
|||
ReadOnlySpan<int> odd, |
|||
Span<int> positive, |
|||
Span<int> negative, |
|||
bool roundAndClip, |
|||
int shift, |
|||
int minimum, |
|||
int maximum) |
|||
{ |
|||
ref int evenBase = ref MemoryMarshal.GetReference(even); |
|||
ref int oddBase = ref MemoryMarshal.GetReference(odd); |
|||
ref int positiveBase = ref MemoryMarshal.GetReference(positive); |
|||
ref int negativeBase = ref MemoryMarshal.GetReference(negative); |
|||
int x = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = even.Length - Vector512<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> evenValues = Vector512.LoadUnsafe(ref evenBase, (nuint)x); |
|||
Vector512<int> oddValues = Vector512.LoadUnsafe(ref oddBase, (nuint)x); |
|||
Vector512<int> added = evenValues + oddValues; |
|||
Vector512<int> subtracted = evenValues - oddValues; |
|||
if (roundAndClip) |
|||
{ |
|||
added = RoundShiftAndClamp(added, shift, minimum, maximum); |
|||
subtracted = RoundShiftAndClamp(subtracted, shift, minimum, maximum); |
|||
} |
|||
|
|||
added.StoreUnsafe(ref positiveBase, (nuint)x); |
|||
subtracted.StoreUnsafe(ref negativeBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = even.Length - Vector256<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> evenValues = Vector256.LoadUnsafe(ref evenBase, (nuint)x); |
|||
Vector256<int> oddValues = Vector256.LoadUnsafe(ref oddBase, (nuint)x); |
|||
Vector256<int> added = evenValues + oddValues; |
|||
Vector256<int> subtracted = evenValues - oddValues; |
|||
if (roundAndClip) |
|||
{ |
|||
added = RoundShiftAndClamp(added, shift, minimum, maximum); |
|||
subtracted = RoundShiftAndClamp(subtracted, shift, minimum, maximum); |
|||
} |
|||
|
|||
added.StoreUnsafe(ref positiveBase, (nuint)x); |
|||
subtracted.StoreUnsafe(ref negativeBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = even.Length - Vector128<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> evenValues = Vector128.LoadUnsafe(ref evenBase, (nuint)x); |
|||
Vector128<int> oddValues = Vector128.LoadUnsafe(ref oddBase, (nuint)x); |
|||
Vector128<int> added = evenValues + oddValues; |
|||
Vector128<int> subtracted = evenValues - oddValues; |
|||
if (roundAndClip) |
|||
{ |
|||
added = RoundShiftAndClamp(added, shift, minimum, maximum); |
|||
subtracted = RoundShiftAndClamp(subtracted, shift, minimum, maximum); |
|||
} |
|||
|
|||
added.StoreUnsafe(ref positiveBase, (nuint)x); |
|||
subtracted.StoreUnsafe(ref negativeBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
for (; x < even.Length; x++) |
|||
{ |
|||
int added = even[x] + odd[x]; |
|||
int subtracted = even[x] - odd[x]; |
|||
if (roundAndClip) |
|||
{ |
|||
added = RoundShiftAndClamp(added, shift, minimum, maximum); |
|||
subtracted = RoundShiftAndClamp(subtracted, shift, minimum, maximum); |
|||
} |
|||
|
|||
positive[x] = added; |
|||
negative[x] = subtracted; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the four-point inverse-DST matrix across all independent lines.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The selected dense transform operator.</typeparam>
|
|||
/// <param name="source">The frequency rows followed by contiguous independent lines.</param>
|
|||
/// <param name="destination">The spatial rows followed by contiguous independent lines.</param>
|
|||
/// <param name="lineCount">The number of independent lines transformed together.</param>
|
|||
/// <param name="shift">The rounded right shift applied to each result.</param>
|
|||
/// <param name="minimum">The inclusive output minimum.</param>
|
|||
/// <param name="maximum">The inclusive output maximum.</param>
|
|||
private static void TransformDense<TOperator>( |
|||
ReadOnlySpan<int> source, |
|||
Span<int> destination, |
|||
int lineCount, |
|||
int shift, |
|||
int minimum, |
|||
int maximum) |
|||
where TOperator : struct, IHevcInverseTransformOperator |
|||
{ |
|||
ref int sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
for (int position = 0; position < TOperator.Size; position++) |
|||
{ |
|||
int x = 0; |
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = lineCount - Vector512<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> sum = Vector512<int>.Zero; |
|||
for (int frequency = 0; frequency < TOperator.Size; frequency++) |
|||
{ |
|||
Vector512<int> values = Vector512.LoadUnsafe(ref sourceBase, (nuint)((frequency * lineCount) + x)); |
|||
sum += values * Vector512.Create(TOperator.GetCoefficient(frequency, position)); |
|||
} |
|||
|
|||
RoundShiftAndClamp(sum, shift, minimum, maximum).StoreUnsafe(ref destinationBase, (nuint)((position * lineCount) + x)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = lineCount - Vector256<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> sum = Vector256<int>.Zero; |
|||
for (int frequency = 0; frequency < TOperator.Size; frequency++) |
|||
{ |
|||
Vector256<int> values = Vector256.LoadUnsafe(ref sourceBase, (nuint)((frequency * lineCount) + x)); |
|||
sum += values * Vector256.Create(TOperator.GetCoefficient(frequency, position)); |
|||
} |
|||
|
|||
RoundShiftAndClamp(sum, shift, minimum, maximum).StoreUnsafe(ref destinationBase, (nuint)((position * lineCount) + x)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = lineCount - Vector128<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> sum = Vector128<int>.Zero; |
|||
for (int frequency = 0; frequency < TOperator.Size; frequency++) |
|||
{ |
|||
Vector128<int> values = Vector128.LoadUnsafe(ref sourceBase, (nuint)((frequency * lineCount) + x)); |
|||
sum += values * Vector128.Create(TOperator.GetCoefficient(frequency, position)); |
|||
} |
|||
|
|||
RoundShiftAndClamp(sum, shift, minimum, maximum).StoreUnsafe(ref destinationBase, (nuint)((position * lineCount) + x)); |
|||
} |
|||
} |
|||
|
|||
for (; x < lineCount; x++) |
|||
{ |
|||
int sum = 0; |
|||
for (int frequency = 0; frequency < TOperator.Size; frequency++) |
|||
{ |
|||
sum += source[(frequency * lineCount) + x] * TOperator.GetCoefficient(frequency, position); |
|||
} |
|||
|
|||
destination[(position * lineCount) + x] = RoundShiftAndClamp(sum, shift, minimum, maximum); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transposes one rectangular transform block into a separate full-block buffer.
|
|||
/// </summary>
|
|||
/// <param name="source">The source block in raster order.</param>
|
|||
/// <param name="destination">The transposed destination block.</param>
|
|||
/// <param name="sourceHeight">The source row count.</param>
|
|||
/// <param name="sourceWidth">The source column count.</param>
|
|||
private static void Transpose(ReadOnlySpan<int> source, Span<int> destination, int sourceHeight, int sourceWidth) |
|||
{ |
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
ref int sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
// Every supported HEVC transform dimension is a multiple of four. Complete four-by-four tiles therefore
|
|||
// transpose the rectangular block without masked loads, partial stores, or access to row padding.
|
|||
for (int y = 0; y < sourceHeight; y += 4) |
|||
{ |
|||
for (int x = 0; x < sourceWidth; x += 4) |
|||
{ |
|||
Vector128<int> row0 = Vector128.LoadUnsafe(ref sourceBase, (nuint)((y * sourceWidth) + x)); |
|||
Vector128<int> row1 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 1) * sourceWidth) + x)); |
|||
Vector128<int> row2 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 2) * sourceWidth) + x)); |
|||
Vector128<int> row3 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 3) * sourceWidth) + x)); |
|||
Av1Transform2dOperations.Transpose(ref row0, ref row1, ref row2, ref row3); |
|||
row0.StoreUnsafe(ref destinationBase, (nuint)((x * sourceHeight) + y)); |
|||
row1.StoreUnsafe(ref destinationBase, (nuint)(((x + 1) * sourceHeight) + y)); |
|||
row2.StoreUnsafe(ref destinationBase, (nuint)(((x + 2) * sourceHeight) + y)); |
|||
row3.StoreUnsafe(ref destinationBase, (nuint)(((x + 3) * sourceHeight) + y)); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (int y = 0; y < sourceHeight; y++) |
|||
{ |
|||
for (int x = 0; x < sourceWidth; x++) |
|||
{ |
|||
destination[(x * sourceHeight) + y] = source[(y * sourceWidth) + x]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a complete signed residual block to the predicted samples and clips to the component precision.
|
|||
/// </summary>
|
|||
/// <param name="residual">The signed residual block in raster order.</param>
|
|||
/// <param name="destination">The predicted samples beginning at the block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
public static void AddResidual(ReadOnlySpan<int> residual, Span<ushort> destination, int destinationStride, int width, int height, int bitDepth) |
|||
{ |
|||
int maximum = (1 << bitDepth) - 1; |
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
ReadOnlySpan<int> residualRow = residual.Slice(y * width, width); |
|||
Span<ushort> destinationRow = destination.Slice(y * destinationStride, width); |
|||
ref int residualBase = ref MemoryMarshal.GetReference(residualRow); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destinationRow); |
|||
int x = 0; |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
// Eight UInt16 predictions widen into one Int32 vector so residual addition cannot overflow sample
|
|||
// storage. Narrowing occurs only after clipping and stores exactly the eight logical destination values.
|
|||
int oneVectorFromEnd = width - Vector256<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector256<int>.Count) |
|||
{ |
|||
Vector128<ushort> predicted16 = Vector128.LoadUnsafe(ref destinationBase, (nuint)x); |
|||
Vector256<int> predicted = Vector256.Create(Vector128.WidenLower(predicted16), Vector128.WidenUpper(predicted16)).AsInt32(); |
|||
Vector256<int> reconstructed = Vector256.Clamp( |
|||
predicted + Vector256.LoadUnsafe(ref residualBase, (nuint)x), |
|||
Vector256<int>.Zero, |
|||
Vector256.Create(maximum)); |
|||
|
|||
Vector128.Narrow(reconstructed.GetLower().AsUInt32(), reconstructed.GetUpper().AsUInt32()).StoreUnsafe(ref destinationBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
// The four-sample path uses exact 64-bit loads and stores; it does not depend on writable row padding.
|
|||
int oneVectorFromEnd = width - Vector128<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector128<int>.Count) |
|||
{ |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<ushort, byte>(ref Unsafe.Add(ref destinationBase, x))); |
|||
Vector128<int> predicted = Vector128.WidenLower(Vector128.CreateScalar(packed).AsUInt16()).AsInt32(); |
|||
Vector128<int> reconstructed = Vector128.Clamp( |
|||
predicted + Vector128.LoadUnsafe(ref residualBase, (nuint)x), |
|||
Vector128<int>.Zero, |
|||
Vector128.Create(maximum)); |
|||
|
|||
Vector128<ushort> narrowed = Vector128.Narrow(reconstructed.AsUInt32(), Vector128<uint>.Zero); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<ushort, byte>(ref Unsafe.Add(ref destinationBase, x)), narrowed.AsUInt64().ToScalar()); |
|||
} |
|||
} |
|||
|
|||
for (; x < width; x++) |
|||
{ |
|||
destinationRow[x] = (ushort)Math.Clamp(destinationRow[x] + residualRow[x], 0, maximum); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies HEVC's rounded right shift and inclusive clipping to a 512-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="value">The unnormalized transform values.</param>
|
|||
/// <param name="shift">The right-shift count.</param>
|
|||
/// <param name="minimum">The inclusive result minimum.</param>
|
|||
/// <param name="maximum">The inclusive result maximum.</param>
|
|||
/// <returns>The normalized and clipped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> RoundShiftAndClamp(Vector512<int> value, int shift, int minimum, int maximum) |
|||
=> Vector512.Clamp((value + Vector512.Create(1 << (shift - 1))) >> shift, Vector512.Create(minimum), Vector512.Create(maximum)); |
|||
|
|||
/// <summary>
|
|||
/// Applies HEVC's rounded right shift and inclusive clipping to a 256-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="value">The unnormalized transform values.</param>
|
|||
/// <param name="shift">The right-shift count.</param>
|
|||
/// <param name="minimum">The inclusive result minimum.</param>
|
|||
/// <param name="maximum">The inclusive result maximum.</param>
|
|||
/// <returns>The normalized and clipped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> RoundShiftAndClamp(Vector256<int> value, int shift, int minimum, int maximum) |
|||
=> Vector256.Clamp((value + Vector256.Create(1 << (shift - 1))) >> shift, Vector256.Create(minimum), Vector256.Create(maximum)); |
|||
|
|||
/// <summary>
|
|||
/// Applies HEVC's rounded right shift and inclusive clipping to a 128-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="value">The unnormalized transform values.</param>
|
|||
/// <param name="shift">The right-shift count.</param>
|
|||
/// <param name="minimum">The inclusive result minimum.</param>
|
|||
/// <param name="maximum">The inclusive result maximum.</param>
|
|||
/// <returns>The normalized and clipped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> RoundShiftAndClamp(Vector128<int> value, int shift, int minimum, int maximum) |
|||
=> Vector128.Clamp((value + Vector128.Create(1 << (shift - 1))) >> shift, Vector128.Create(minimum), Vector128.Create(maximum)); |
|||
|
|||
/// <summary>
|
|||
/// Applies HEVC's rounded right shift and inclusive clipping to one scalar value.
|
|||
/// </summary>
|
|||
/// <param name="value">The unnormalized transform value.</param>
|
|||
/// <param name="shift">The right-shift count.</param>
|
|||
/// <param name="minimum">The inclusive result minimum.</param>
|
|||
/// <param name="maximum">The inclusive result maximum.</param>
|
|||
/// <returns>The normalized and clipped value.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int RoundShiftAndClamp(int value, int shift, int minimum, int maximum) |
|||
=> Math.Clamp((value + (1 << (shift - 1))) >> shift, minimum, maximum); |
|||
} |
|||
@ -1,34 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Defines the HEVC inverse-transform operator contract.
|
|||
/// </content>
|
|||
internal static partial class HevcInverseTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines one closed inverse-transform operation selected by the transform-unit syntax.
|
|||
/// </summary>
|
|||
private interface IHevcInverseTransformOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the transform side in samples.
|
|||
/// </summary>
|
|||
public static abstract int Size { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the transform uses the partial-butterfly factorization.
|
|||
/// </summary>
|
|||
public static abstract bool UsesButterfly { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets one inverse-transform matrix coefficient.
|
|||
/// </summary>
|
|||
/// <param name="frequency">The frequency-domain coordinate.</param>
|
|||
/// <param name="position">The spatial-domain coordinate.</param>
|
|||
/// <returns>The signed transform coefficient.</returns>
|
|||
public static abstract int GetCoefficient(int frequency, int position); |
|||
} |
|||
} |
|||
@ -1,244 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Applies HEVC inverse transforms and reconstructs predicted samples.
|
|||
/// </summary>
|
|||
internal static partial class HevcInverseTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// The signed residual precision used after the second inverse-transform pass.
|
|||
/// </summary>
|
|||
private const int ResidualPrecision = 16; |
|||
|
|||
/// <summary>
|
|||
/// Gets the scratch length required for the specified rectangular transform block.
|
|||
/// </summary>
|
|||
/// <param name="log2Width">The base-two logarithm of the transform-block width.</param>
|
|||
/// <param name="log2Height">The base-two logarithm of the transform-block height.</param>
|
|||
/// <returns>The required number of signed thirty-two-bit elements.</returns>
|
|||
public static int GetScratchLength(int log2Width, int log2Height) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Width, 2, 5, nameof(log2Width)); |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Height, 2, 5, nameof(log2Height)); |
|||
return 2 << (log2Width + log2Height); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one transform block by adding its inverse-transformed residual to the predicted samples.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The dequantized transform coefficients in raster order.</param>
|
|||
/// <param name="destination">The predicted samples beginning at the transform-block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="log2Width">The base-two logarithm of the transform-block width.</param>
|
|||
/// <param name="log2Height">The base-two logarithm of the transform-block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
|||
/// <param name="useDiscreteSineTransform">Whether the four-by-four luma intra block uses the discrete sine transform.</param>
|
|||
/// <param name="scratch">The caller-owned scratch returned by <see cref="GetScratchLength(int, int)"/>.</param>
|
|||
public static void TransformAdd( |
|||
ReadOnlySpan<int> coefficients, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int log2Width, |
|||
int log2Height, |
|||
int bitDepth, |
|||
int maxTransformDynamicRange, |
|||
bool useDiscreteSineTransform, |
|||
Span<int> scratch) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Width, 2, 5, nameof(log2Width)); |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Height, 2, 5, nameof(log2Height)); |
|||
DebugGuard.MustBeBetweenOrEqualTo(bitDepth, 8, 16, nameof(bitDepth)); |
|||
DebugGuard.IsTrue(!useDiscreteSineTransform || (log2Width == 2 && log2Height == 2), "The HEVC inverse DST is defined only for four-by-four blocks."); |
|||
|
|||
int width = 1 << log2Width; |
|||
int height = 1 << log2Height; |
|||
int sampleCount = width * height; |
|||
DebugGuard.IsTrue(coefficients.Length >= sampleCount, "The coefficient span is shorter than the transform block."); |
|||
DebugGuard.IsTrue(scratch.Length >= sampleCount * 2, "The scratch span is shorter than the inverse-transform requirement."); |
|||
|
|||
Span<int> first = scratch[..sampleCount]; |
|||
Span<int> second = scratch.Slice(sampleCount, sampleCount); |
|||
Span<int> residual = TransformCore( |
|||
coefficients[..sampleCount], |
|||
first, |
|||
second, |
|||
width, |
|||
height, |
|||
bitDepth, |
|||
maxTransformDynamicRange, |
|||
useDiscreteSineTransform); |
|||
|
|||
AddResidual(residual, destination, destinationStride, width, height, bitDepth); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one two-dimensional inverse transform and writes signed residual samples in raster order.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The dequantized transform coefficients in raster order.</param>
|
|||
/// <param name="residual">The destination residual samples in raster order.</param>
|
|||
/// <param name="log2Width">The base-two logarithm of the transform-block width.</param>
|
|||
/// <param name="log2Height">The base-two logarithm of the transform-block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
|||
/// <param name="useDiscreteSineTransform">Whether the four-by-four luma intra block uses the discrete sine transform.</param>
|
|||
/// <param name="scratch">The caller-owned scratch returned by <see cref="GetScratchLength(int, int)"/>.</param>
|
|||
public static void Transform( |
|||
ReadOnlySpan<int> coefficients, |
|||
Span<int> residual, |
|||
int log2Width, |
|||
int log2Height, |
|||
int bitDepth, |
|||
int maxTransformDynamicRange, |
|||
bool useDiscreteSineTransform, |
|||
Span<int> scratch) |
|||
{ |
|||
int width = 1 << log2Width; |
|||
int height = 1 << log2Height; |
|||
int sampleCount = width * height; |
|||
DebugGuard.IsTrue(residual.Length >= sampleCount, "The residual span is shorter than the transform block."); |
|||
DebugGuard.IsTrue(scratch.Length >= sampleCount * 2, "The scratch span is shorter than the inverse-transform requirement."); |
|||
|
|||
Span<int> transformed = TransformCore( |
|||
coefficients[..sampleCount], |
|||
scratch[..sampleCount], |
|||
scratch.Slice(sampleCount, sampleCount), |
|||
width, |
|||
height, |
|||
bitDepth, |
|||
maxTransformDynamicRange, |
|||
useDiscreteSineTransform); |
|||
|
|||
transformed.CopyTo(residual); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Dispatches both separable transform passes through closed operators selected from the block dimensions.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The complete dequantized coefficient block.</param>
|
|||
/// <param name="first">The first full-block scratch buffer.</param>
|
|||
/// <param name="second">The second full-block scratch buffer.</param>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
|||
/// <param name="useDiscreteSineTransform">Whether both four-point passes use the discrete sine transform.</param>
|
|||
/// <returns>The scratch buffer containing the raster-ordered residual.</returns>
|
|||
private static Span<int> TransformCore( |
|||
ReadOnlySpan<int> coefficients, |
|||
Span<int> first, |
|||
Span<int> second, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
int maxTransformDynamicRange, |
|||
bool useDiscreteSineTransform) |
|||
{ |
|||
int dynamicMinimum = -(1 << maxTransformDynamicRange); |
|||
int dynamicMaximum = (1 << maxTransformDynamicRange) - 1; |
|||
|
|||
// HEVC moves one normalization bit from the second pass to the first. The intermediate clip therefore
|
|||
// belongs after the vertical pass and must not be combined with the final residual clipping operation.
|
|||
Span<int> vertical = TransformDimension( |
|||
coefficients, |
|||
first, |
|||
second, |
|||
height, |
|||
width, |
|||
7, |
|||
dynamicMinimum, |
|||
dynamicMaximum, |
|||
useDiscreteSineTransform); |
|||
|
|||
Span<int> horizontalInput = vertical.Overlaps(first) ? second : first; |
|||
Transpose(vertical, horizontalInput, height, width); |
|||
|
|||
Span<int> horizontalWorkspace = horizontalInput.Overlaps(first) ? second : first; |
|||
int secondShift = maxTransformDynamicRange + 5 - bitDepth; |
|||
Span<int> horizontal = TransformDimension( |
|||
horizontalInput, |
|||
horizontalWorkspace, |
|||
horizontalInput, |
|||
width, |
|||
height, |
|||
secondShift, |
|||
-(1 << (ResidualPrecision - 1)), |
|||
(1 << (ResidualPrecision - 1)) - 1, |
|||
useDiscreteSineTransform); |
|||
|
|||
Span<int> residual = horizontal.Overlaps(first) ? second : first; |
|||
Transpose(horizontal, residual, width, height); |
|||
return residual; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects the statically specialized operator for one transform dimension.
|
|||
/// </summary>
|
|||
/// <param name="source">The frequency rows followed by contiguous independent lines.</param>
|
|||
/// <param name="initial">The initial operator output buffer.</param>
|
|||
/// <param name="alternate">The alternate combination buffer.</param>
|
|||
/// <param name="size">The transform dimension.</param>
|
|||
/// <param name="lineCount">The number of independent lines transformed together.</param>
|
|||
/// <param name="shift">The rounded right shift applied to the spatial results.</param>
|
|||
/// <param name="minimum">The inclusive output minimum.</param>
|
|||
/// <param name="maximum">The inclusive output maximum.</param>
|
|||
/// <param name="useDiscreteSineTransform">Whether the four-point pass uses the discrete sine transform.</param>
|
|||
/// <returns>The buffer containing spatial rows followed by contiguous independent lines.</returns>
|
|||
private static Span<int> TransformDimension( |
|||
ReadOnlySpan<int> source, |
|||
Span<int> initial, |
|||
Span<int> alternate, |
|||
int size, |
|||
int lineCount, |
|||
int shift, |
|||
int minimum, |
|||
int maximum, |
|||
bool useDiscreteSineTransform) |
|||
=> (size, useDiscreteSineTransform) switch |
|||
{ |
|||
(4, true) => TransformDimension<DiscreteSine4Operator>(source, initial, alternate, lineCount, shift, minimum, maximum), |
|||
(4, false) => TransformDimension<DiscreteCosine4Operator>(source, initial, alternate, lineCount, shift, minimum, maximum), |
|||
(8, _) => TransformDimension<DiscreteCosine8Operator>(source, initial, alternate, lineCount, shift, minimum, maximum), |
|||
(16, _) => TransformDimension<DiscreteCosine16Operator>(source, initial, alternate, lineCount, shift, minimum, maximum), |
|||
_ => TransformDimension<DiscreteCosine32Operator>(source, initial, alternate, lineCount, shift, minimum, maximum) |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Invokes one statically selected inverse-transform operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The selected inverse-transform operator.</typeparam>
|
|||
/// <param name="source">The frequency rows followed by contiguous independent lines.</param>
|
|||
/// <param name="initial">The initial operator output buffer.</param>
|
|||
/// <param name="alternate">The alternate combination buffer.</param>
|
|||
/// <param name="lineCount">The number of independent lines transformed together.</param>
|
|||
/// <param name="shift">The rounded right shift applied to the spatial results.</param>
|
|||
/// <param name="minimum">The inclusive output minimum.</param>
|
|||
/// <param name="maximum">The inclusive output maximum.</param>
|
|||
/// <returns>The buffer containing spatial rows followed by contiguous independent lines.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Span<int> TransformDimension<TOperator>( |
|||
ReadOnlySpan<int> source, |
|||
Span<int> initial, |
|||
Span<int> alternate, |
|||
int lineCount, |
|||
int shift, |
|||
int minimum, |
|||
int maximum) |
|||
where TOperator : struct, IHevcInverseTransformOperator |
|||
{ |
|||
if (!TOperator.UsesButterfly) |
|||
{ |
|||
TransformDense<TOperator>(source, initial, lineCount, shift, minimum, maximum); |
|||
return initial; |
|||
} |
|||
|
|||
PopulateButterflyGroups<TOperator>(source, initial, lineCount); |
|||
return CombineButterflyGroups<TOperator>(initial, alternate, lineCount, shift, minimum, maximum); |
|||
} |
|||
} |
|||
@ -1,148 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains one decoded HEVC network abstraction layer unit.
|
|||
/// </summary>
|
|||
internal sealed class HevcNalUnit |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcNalUnit"/> class.
|
|||
/// </summary>
|
|||
/// <param name="data">The complete NAL unit, including its two-byte header.</param>
|
|||
/// <exception cref="InvalidImageContentException">The NAL header or encoded payload is malformed.</exception>
|
|||
public HevcNalUnit(ReadOnlySpan<byte> data) |
|||
{ |
|||
this.Header = HevcNalUnitHeader.Parse(data); |
|||
|
|||
// Container and configuration NAL units carry EBSP bytes. Decode them once at the boundary so every
|
|||
// parameter-set and slice parser observes the same validated RBSP representation.
|
|||
ReadOnlySpan<byte> encodedPayload = data[2..]; |
|||
byte[] rbspBuffer = new byte[encodedPayload.Length]; |
|||
int rbspLength = HevcRbspDecoder.Decode( |
|||
encodedPayload, |
|||
rbspBuffer, |
|||
out ReadOnlyMemory<int> emulationPreventionBytePositions); |
|||
|
|||
this.EncodedPayloadLength = encodedPayload.Length; |
|||
this.Rbsp = rbspBuffer.AsMemory(0, rbspLength); |
|||
this.EmulationPreventionBytePositions = emulationPreventionBytePositions; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the decoded two-byte NAL-unit header.
|
|||
/// </summary>
|
|||
public HevcNalUnitHeader Header { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the raw byte sequence payload after removal of emulation-prevention bytes.
|
|||
/// </summary>
|
|||
public ReadOnlyMemory<byte> Rbsp { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the encoded byte-sequence payload length before removal of emulation-prevention bytes.
|
|||
/// </summary>
|
|||
public int EncodedPayloadLength { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the zero-based encoded-payload positions of removed emulation-prevention bytes.
|
|||
/// </summary>
|
|||
public ReadOnlyMemory<int> EmulationPreventionBytePositions { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes HEVC emulation-prevention bytes from an encoded raw byte sequence payload.
|
|||
/// </summary>
|
|||
internal static class HevcRbspDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes an encoded byte sequence payload into a raw byte sequence payload.
|
|||
/// </summary>
|
|||
/// <param name="encodedPayload">The NAL payload following the two-byte header.</param>
|
|||
/// <param name="destination">A buffer at least as long as <paramref name="encodedPayload"/>.</param>
|
|||
/// <param name="emulationPreventionBytePositions">
|
|||
/// Receives the zero-based encoded-payload positions of removed emulation-prevention bytes.
|
|||
/// </param>
|
|||
/// <returns>The number of decoded bytes written to <paramref name="destination"/>.</returns>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The payload contains a forbidden start-code-like byte sequence or an invalid emulation-prevention byte.
|
|||
/// </exception>
|
|||
public static int Decode( |
|||
ReadOnlySpan<byte> encodedPayload, |
|||
Span<byte> destination, |
|||
out ReadOnlyMemory<int> emulationPreventionBytePositions) |
|||
{ |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(destination.Length, encodedPayload.Length, nameof(destination)); |
|||
|
|||
int destinationOffset = 0; |
|||
int preventionByteCount = 0; |
|||
int consecutiveZeroBytes = 0; |
|||
int[]? rentedPositions = null; |
|||
Span<int> preventionBytePositions = []; |
|||
try |
|||
{ |
|||
for (int sourceOffset = 0; sourceOffset < encodedPayload.Length; sourceOffset++) |
|||
{ |
|||
byte value = encodedPayload[sourceOffset]; |
|||
|
|||
// HEVC section 7.3.1.1 forbids 00 00 00 through 00 00 02 in EBSP form. A 03 after two zeros is an
|
|||
// emulation-prevention byte only when another byte in the range 00 through 03 follows it.
|
|||
if (consecutiveZeroBytes == 2) |
|||
{ |
|||
if (value < 3) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC NAL unit contains a forbidden start-code-like byte sequence."); |
|||
} |
|||
|
|||
if (value == 3) |
|||
{ |
|||
sourceOffset++; |
|||
if (sourceOffset == encodedPayload.Length || encodedPayload[sourceOffset] > 3) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC NAL unit contains an invalid emulation-prevention byte."); |
|||
} |
|||
|
|||
if (preventionByteCount == preventionBytePositions.Length) |
|||
{ |
|||
int[] expandedPositions = ArrayPool<int>.Shared.Rent(preventionBytePositions.IsEmpty ? 16 : preventionBytePositions.Length * 2); |
|||
preventionBytePositions.CopyTo(expandedPositions); |
|||
if (rentedPositions is not null) |
|||
{ |
|||
ArrayPool<int>.Shared.Return(rentedPositions); |
|||
} |
|||
|
|||
rentedPositions = expandedPositions; |
|||
preventionBytePositions = rentedPositions; |
|||
} |
|||
|
|||
preventionBytePositions[preventionByteCount++] = sourceOffset - 1; |
|||
value = encodedPayload[sourceOffset]; |
|||
consecutiveZeroBytes = 0; |
|||
} |
|||
} |
|||
|
|||
destination[destinationOffset++] = value; |
|||
consecutiveZeroBytes = value == 0 ? consecutiveZeroBytes + 1 : 0; |
|||
} |
|||
|
|||
int[] retainedPositions = preventionByteCount == 0 |
|||
? [] |
|||
: GC.AllocateUninitializedArray<int>(preventionByteCount); |
|||
|
|||
preventionBytePositions[..preventionByteCount].CopyTo(retainedPositions); |
|||
emulationPreventionBytePositions = retainedPositions; |
|||
return destinationOffset; |
|||
} |
|||
finally |
|||
{ |
|||
if (rentedPositions is not null) |
|||
{ |
|||
ArrayPool<int>.Shared.Return(rentedPositions); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,78 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the type, layer, and temporal identifier encoded by an HEVC NAL-unit header.
|
|||
/// </summary>
|
|||
internal readonly struct HevcNalUnitHeader |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcNalUnitHeader"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="nalUnitType">The six-bit NAL-unit type.</param>
|
|||
/// <param name="layerId">The six-bit layer identifier.</param>
|
|||
/// <param name="temporalId">The zero-based temporal identifier.</param>
|
|||
private HevcNalUnitHeader(byte nalUnitType, byte layerId, byte temporalId) |
|||
{ |
|||
this.NalUnitType = nalUnitType; |
|||
this.LayerId = layerId; |
|||
this.TemporalId = temporalId; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the six-bit NAL-unit type.
|
|||
/// </summary>
|
|||
public byte NalUnitType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the six-bit layer identifier.
|
|||
/// </summary>
|
|||
public byte LayerId { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the zero-based temporal identifier.
|
|||
/// </summary>
|
|||
public byte TemporalId { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the NAL unit contains coded slice-segment data.
|
|||
/// </summary>
|
|||
public bool IsVideoCodingLayer => this.NalUnitType <= 31; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the NAL unit begins an instantaneous decoder refresh picture.
|
|||
/// </summary>
|
|||
public bool IsInstantaneousDecoderRefresh => this.NalUnitType is 19 or 20; |
|||
|
|||
/// <summary>
|
|||
/// Reads and validates an HEVC NAL-unit header.
|
|||
/// </summary>
|
|||
/// <param name="data">The complete NAL unit beginning with its two-byte header.</param>
|
|||
/// <returns>The decoded header.</returns>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The header is truncated, its forbidden bit is set, or its temporal identifier is reserved.
|
|||
/// </exception>
|
|||
public static HevcNalUnitHeader Parse(ReadOnlySpan<byte> data) |
|||
{ |
|||
if (data.Length < 2) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC NAL-unit header is truncated."); |
|||
} |
|||
|
|||
// Use the same bounded MSB-first reader as the RBSP parsers so header truncation and field ordering have
|
|||
// one behavior model instead of a second set of shifts and masks.
|
|||
HevcBitReader reader = new(data[..2]); |
|||
bool forbiddenZeroBit = reader.ReadFlag(); |
|||
byte nalUnitType = (byte)reader.ReadBits(6); |
|||
byte layerId = (byte)reader.ReadBits(6); |
|||
byte temporalIdPlusOne = (byte)reader.ReadBits(3); |
|||
if (forbiddenZeroBit || temporalIdPlusOne == 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC NAL-unit header is invalid."); |
|||
} |
|||
|
|||
return new HevcNalUnitHeader(nalUnitType, layerId, (byte)(temporalIdPlusOne - 1)); |
|||
} |
|||
} |
|||
@ -1,186 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Provides shared bounded syntax operations used by HEVC parameter-set readers.
|
|||
/// </summary>
|
|||
internal static class HevcParameterSetSyntax |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the horizontal conformance-window unit for an HEVC chroma layout.
|
|||
/// </summary>
|
|||
/// <param name="chromaFormat">The chroma-format identifier.</param>
|
|||
/// <param name="separateColorPlane">Whether 4:4:4 components are coded as separate color planes.</param>
|
|||
/// <returns>The horizontal unit in luma samples.</returns>
|
|||
public static int GetCropUnitWidth(byte chromaFormat, bool separateColorPlane) |
|||
=> !separateColorPlane && chromaFormat is 1 or 2 ? 2 : 1; |
|||
|
|||
/// <summary>
|
|||
/// Gets the vertical conformance-window unit for an HEVC chroma layout.
|
|||
/// </summary>
|
|||
/// <param name="chromaFormat">The chroma-format identifier.</param>
|
|||
/// <param name="separateColorPlane">Whether 4:4:4 components are coded as separate color planes.</param>
|
|||
/// <returns>The vertical unit in luma samples.</returns>
|
|||
public static int GetCropUnitHeight(byte chromaFormat, bool separateColorPlane) |
|||
=> !separateColorPlane && chromaFormat == 1 ? 2 : 1; |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of coding-tree blocks needed to cover one coded picture dimension.
|
|||
/// </summary>
|
|||
/// <param name="sampleCount">The coded luma-sample count.</param>
|
|||
/// <param name="codingTreeBlockLog2">The base-two logarithm of the coding-tree-block size.</param>
|
|||
/// <returns>The covering coding-tree-block count.</returns>
|
|||
public static int GetCodingTreeBlockCount(int sampleCount, int codingTreeBlockLog2) |
|||
=> ((sampleCount - 1) >> codingTreeBlockLog2) + 1; |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of bits required to represent values below a positive exclusive upper bound.
|
|||
/// </summary>
|
|||
/// <param name="exclusiveUpperBound">The positive exclusive upper bound.</param>
|
|||
/// <returns>The ceiling of the base-two logarithm, with zero returned for an upper bound of one.</returns>
|
|||
public static int GetCeilingLog2(int exclusiveUpperBound) |
|||
{ |
|||
DebugGuard.MustBeGreaterThan(exclusiveUpperBound, 0, nameof(exclusiveUpperBound)); |
|||
|
|||
int bitCount = 0; |
|||
int remaining = exclusiveUpperBound - 1; |
|||
while (remaining > 0) |
|||
{ |
|||
bitCount++; |
|||
remaining >>= 1; |
|||
} |
|||
|
|||
return bitCount; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a signed chroma quantization-parameter offset.
|
|||
/// </summary>
|
|||
/// <param name="reader">The HEVC syntax reader.</param>
|
|||
/// <returns>The decoded offset in the registered range from negative twelve through twelve.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The offset is outside its registered range.</exception>
|
|||
public static int ReadQuantizationParameterOffset(ref HevcBitReader reader) |
|||
{ |
|||
int offset = reader.ReadSignedExpGolomb(); |
|||
if (offset is < -12 or > 12) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC chroma quantization-parameter offset is invalid."); |
|||
} |
|||
|
|||
return offset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a signed deblocking-filter threshold offset.
|
|||
/// </summary>
|
|||
/// <param name="reader">The HEVC syntax reader.</param>
|
|||
/// <returns>The decoded half-offset in the registered range from negative six through six.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The offset is outside its registered range.</exception>
|
|||
public static int ReadDeblockingFilterOffset(ref HevcBitReader reader) |
|||
{ |
|||
int offset = reader.ReadSignedExpGolomb(); |
|||
if (offset is < -6 or > 6) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC deblocking-filter offset is invalid."); |
|||
} |
|||
|
|||
return offset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Consumes hypothetical-reference-decoder syntax without adding playback state to the still-image model.
|
|||
/// </summary>
|
|||
/// <param name="reader">The parameter-set raw byte sequence payload reader.</param>
|
|||
/// <param name="commonInformationPresent">Whether common HRD flags are coded for this parameter set.</param>
|
|||
/// <param name="maxSubLayersMinusOne">The highest declared temporal sublayer index.</param>
|
|||
/// <param name="nalHrdParametersPresent">The effective NAL HRD presence flag.</param>
|
|||
/// <param name="vclHrdParametersPresent">The effective VCL HRD presence flag.</param>
|
|||
/// <param name="subPictureHrdParametersPresent">The effective sub-picture HRD presence flag.</param>
|
|||
/// <exception cref="InvalidImageContentException">The HRD syntax is truncated or exceeds its registered bounds.</exception>
|
|||
public static void SkipHrdParameters( |
|||
ref HevcBitReader reader, |
|||
bool commonInformationPresent, |
|||
int maxSubLayersMinusOne, |
|||
ref bool nalHrdParametersPresent, |
|||
ref bool vclHrdParametersPresent, |
|||
ref bool subPictureHrdParametersPresent) |
|||
{ |
|||
if (commonInformationPresent) |
|||
{ |
|||
nalHrdParametersPresent = reader.ReadFlag(); |
|||
vclHrdParametersPresent = reader.ReadFlag(); |
|||
subPictureHrdParametersPresent = false; |
|||
if (nalHrdParametersPresent || vclHrdParametersPresent) |
|||
{ |
|||
subPictureHrdParametersPresent = reader.ReadFlag(); |
|||
if (subPictureHrdParametersPresent) |
|||
{ |
|||
reader.ReadBits(8); |
|||
reader.ReadBits(5); |
|||
reader.ReadFlag(); |
|||
reader.ReadBits(5); |
|||
} |
|||
|
|||
reader.ReadBits(4); |
|||
reader.ReadBits(4); |
|||
if (subPictureHrdParametersPresent) |
|||
{ |
|||
reader.ReadBits(4); |
|||
} |
|||
|
|||
reader.ReadBits(5); |
|||
reader.ReadBits(5); |
|||
reader.ReadBits(5); |
|||
} |
|||
} |
|||
|
|||
for (int subLayer = 0; subLayer <= maxSubLayersMinusOne; subLayer++) |
|||
{ |
|||
bool fixedPictureRateGeneral = reader.ReadFlag(); |
|||
bool fixedPictureRateWithinCvs = fixedPictureRateGeneral || reader.ReadFlag(); |
|||
bool lowDelayHrd = false; |
|||
if (fixedPictureRateWithinCvs) |
|||
{ |
|||
reader.ReadUnsignedExpGolomb(); |
|||
} |
|||
else |
|||
{ |
|||
lowDelayHrd = reader.ReadFlag(); |
|||
} |
|||
|
|||
uint cpbCountMinusOne = 0; |
|||
if (!lowDelayHrd) |
|||
{ |
|||
cpbCountMinusOne = reader.ReadUnsignedExpGolomb(); |
|||
if (cpbCountMinusOne > 31) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC HRD syntax declares too many coded-picture buffers."); |
|||
} |
|||
} |
|||
|
|||
for (int hrdKind = 0; hrdKind < 2; hrdKind++) |
|||
{ |
|||
bool parametersPresent = hrdKind == 0 ? nalHrdParametersPresent : vclHrdParametersPresent; |
|||
if (!parametersPresent) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
for (uint cpbIndex = 0; cpbIndex <= cpbCountMinusOne; cpbIndex++) |
|||
{ |
|||
reader.ReadUnsignedExpGolomb(); |
|||
reader.ReadUnsignedExpGolomb(); |
|||
if (subPictureHrdParametersPresent) |
|||
{ |
|||
reader.ReadUnsignedExpGolomb(); |
|||
reader.ReadUnsignedExpGolomb(); |
|||
} |
|||
|
|||
reader.ReadFlag(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,235 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Owns the native-precision luma and chroma sample planes for one reconstructed HEVC still picture.
|
|||
/// </summary>
|
|||
internal sealed class HevcPictureBuffer : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The horizontal chroma subsampling shift.
|
|||
/// </summary>
|
|||
private readonly int chromaSubsamplingX; |
|||
|
|||
/// <summary>
|
|||
/// The vertical chroma subsampling shift.
|
|||
/// </summary>
|
|||
private readonly int chromaSubsamplingY; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcPictureBuffer"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration providing the image memory allocator.</param>
|
|||
/// <param name="sequenceParameterSet">The coded dimensions, precision, and chroma layout.</param>
|
|||
public HevcPictureBuffer(Configuration configuration, HevcSequenceParameterSet sequenceParameterSet) |
|||
: this( |
|||
configuration, |
|||
sequenceParameterSet.Width, |
|||
sequenceParameterSet.Height, |
|||
sequenceParameterSet.BitDepthLuma, |
|||
sequenceParameterSet.BitDepthChroma, |
|||
sequenceParameterSet.ChromaFormat, |
|||
sequenceParameterSet.SeparateColorPlaneFlag, |
|||
1 << sequenceParameterSet.MinCodingBlockLog2) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcPictureBuffer"/> class for encoder-owned component planes.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration providing the image memory allocator.</param>
|
|||
/// <param name="width">The coded luma width.</param>
|
|||
/// <param name="height">The coded luma height.</param>
|
|||
/// <param name="bitDepthLuma">The luma sample precision.</param>
|
|||
/// <param name="bitDepthChroma">The chroma sample precision.</param>
|
|||
/// <param name="chromaFormat">The HEVC chroma-format identifier.</param>
|
|||
/// <param name="separateColorPlane">Whether 4:4:4 components are coded as separate color planes.</param>
|
|||
/// <param name="storageAlignment">The luma sample alignment applied to the owned reconstruction planes.</param>
|
|||
public HevcPictureBuffer( |
|||
Configuration configuration, |
|||
int width, |
|||
int height, |
|||
int bitDepthLuma, |
|||
int bitDepthChroma, |
|||
byte chromaFormat, |
|||
bool separateColorPlane, |
|||
int storageAlignment = 1) |
|||
{ |
|||
this.Width = width; |
|||
this.Height = height; |
|||
this.BitDepthLuma = bitDepthLuma; |
|||
this.BitDepthChroma = bitDepthChroma; |
|||
this.ChromaFormat = chromaFormat; |
|||
this.SeparateColorPlane = separateColorPlane; |
|||
|
|||
// Separate color planes are independently coded at full resolution even though chroma_format_idc is 4:4:4.
|
|||
this.chromaSubsamplingX = !this.SeparateColorPlane && this.ChromaFormat is 1 or 2 ? 1 : 0; |
|||
this.chromaSubsamplingY = !this.SeparateColorPlane && this.ChromaFormat == 1 ? 1 : 0; |
|||
int storageWidth = DivideCeilingByPowerOfTwo(this.Width, BitOperations.Log2((uint)storageAlignment)) * storageAlignment; |
|||
int storageHeight = DivideCeilingByPowerOfTwo(this.Height, BitOperations.Log2((uint)storageAlignment)) * storageAlignment; |
|||
Buffer2D<ushort>? luma = null; |
|||
Buffer2D<ushort>? chromaBlue = null; |
|||
Buffer2D<ushort>? chromaRed = null; |
|||
try |
|||
{ |
|||
luma = configuration.MemoryAllocator.Allocate2D<ushort>(storageWidth, storageHeight); |
|||
if (this.ChromaFormat != 0) |
|||
{ |
|||
int chromaWidth = DivideCeilingByPowerOfTwo(storageWidth, this.chromaSubsamplingX); |
|||
int chromaHeight = DivideCeilingByPowerOfTwo(storageHeight, this.chromaSubsamplingY); |
|||
|
|||
chromaBlue = configuration.MemoryAllocator.Allocate2D<ushort>(chromaWidth, chromaHeight); |
|||
chromaRed = configuration.MemoryAllocator.Allocate2D<ushort>(chromaWidth, chromaHeight); |
|||
} |
|||
|
|||
this.Luma = luma; |
|||
this.ChromaBlue = chromaBlue; |
|||
this.ChromaRed = chromaRed; |
|||
} |
|||
catch |
|||
{ |
|||
// Construction transfers no plane ownership when a later rent fails, so unwind the unpublished owners
|
|||
// here instead of relying on Dispose being reachable through a fully constructed picture buffer.
|
|||
chromaRed?.Dispose(); |
|||
chromaBlue?.Dispose(); |
|||
luma?.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded luma width in samples.
|
|||
/// </summary>
|
|||
public int Width { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded luma height in samples.
|
|||
/// </summary>
|
|||
public int Height { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma sample precision in bits.
|
|||
/// </summary>
|
|||
public int BitDepthLuma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma sample precision in bits.
|
|||
/// </summary>
|
|||
public int BitDepthChroma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the HEVC chroma-format identifier.
|
|||
/// </summary>
|
|||
public byte ChromaFormat { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the three planes are coded as independent full-resolution color planes.
|
|||
/// </summary>
|
|||
public bool SeparateColorPlane { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma or first separate-color-plane allocation.
|
|||
/// </summary>
|
|||
public Buffer2D<ushort> Luma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue-difference chroma or second separate-color-plane allocation.
|
|||
/// </summary>
|
|||
public Buffer2D<ushort>? ChromaBlue { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the red-difference chroma or third separate-color-plane allocation.
|
|||
/// </summary>
|
|||
public Buffer2D<ushort>? ChromaRed { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the horizontal chroma subsampling shift for the selected plane.
|
|||
/// </summary>
|
|||
/// <param name="plane">The reconstruction plane.</param>
|
|||
/// <returns>Zero for luma and full-resolution planes; otherwise, the chroma shift.</returns>
|
|||
public int GetSubsamplingX(HevcPlane plane) => plane == HevcPlane.Y ? 0 : this.chromaSubsamplingX; |
|||
|
|||
/// <summary>
|
|||
/// Gets the vertical chroma subsampling shift for the selected plane.
|
|||
/// </summary>
|
|||
/// <param name="plane">The reconstruction plane.</param>
|
|||
/// <returns>Zero for luma and full-resolution planes; otherwise, the chroma shift.</returns>
|
|||
public int GetSubsamplingY(HevcPlane plane) => plane == HevcPlane.Y ? 0 : this.chromaSubsamplingY; |
|||
|
|||
/// <summary>
|
|||
/// Gets the sample precision for the selected reconstruction plane.
|
|||
/// </summary>
|
|||
/// <param name="plane">The reconstruction plane.</param>
|
|||
/// <returns>The plane sample precision in bits.</returns>
|
|||
public int GetBitDepth(HevcPlane plane) => plane == HevcPlane.Y ? this.BitDepthLuma : this.BitDepthChroma; |
|||
|
|||
/// <summary>
|
|||
/// Gets the selected plane width in samples.
|
|||
/// </summary>
|
|||
/// <param name="plane">The reconstruction plane.</param>
|
|||
/// <returns>The coded plane width.</returns>
|
|||
public int GetWidth(HevcPlane plane) => DivideCeilingByPowerOfTwo(this.Width, this.GetSubsamplingX(plane)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the selected plane height in samples.
|
|||
/// </summary>
|
|||
/// <param name="plane">The reconstruction plane.</param>
|
|||
/// <returns>The coded plane height.</returns>
|
|||
public int GetHeight(HevcPlane plane) => DivideCeilingByPowerOfTwo(this.Height, this.GetSubsamplingY(plane)); |
|||
|
|||
/// <summary>
|
|||
/// Gets one coded row from the selected reconstruction plane.
|
|||
/// </summary>
|
|||
/// <param name="plane">The reconstruction plane.</param>
|
|||
/// <param name="row">The zero-based row index in plane samples.</param>
|
|||
/// <returns>The complete coded plane row.</returns>
|
|||
public Span<ushort> GetRowSpan(HevcPlane plane, int row) |
|||
=> plane switch |
|||
{ |
|||
HevcPlane.Y => this.Luma.DangerousGetRowSpan(row), |
|||
HevcPlane.Cb => this.ChromaBlue!.DangerousGetRowSpan(row), |
|||
_ => this.ChromaRed!.DangerousGetRowSpan(row), |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Copies the complete coded component planes to another picture buffer with the same dimensions and chroma layout.
|
|||
/// </summary>
|
|||
/// <param name="destination">The destination picture buffer.</param>
|
|||
public void CopyTo(HevcPictureBuffer destination) |
|||
{ |
|||
int planeCount = this.ChromaFormat == 0 ? 1 : 3; |
|||
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) |
|||
{ |
|||
HevcPlane plane = (HevcPlane)planeIndex; |
|||
int width = this.GetWidth(plane); |
|||
int height = this.GetHeight(plane); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
this.GetRowSpan(plane, row)[..width].CopyTo(destination.GetRowSpan(plane, row)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Releases the owned luma and chroma plane allocations.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.Luma.Dispose(); |
|||
this.ChromaBlue?.Dispose(); |
|||
this.ChromaRed?.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Divides a nonnegative sample count by a power of two with upward rounding.
|
|||
/// </summary>
|
|||
/// <param name="value">The sample count.</param>
|
|||
/// <param name="shift">The base-two divisor logarithm.</param>
|
|||
/// <returns>The upward-rounded quotient.</returns>
|
|||
private static int DivideCeilingByPowerOfTwo(int value, int shift) => (value + (1 << shift) - 1) >> shift; |
|||
} |
|||
@ -1,396 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Implements picture-level HEVC deblocking traversal and threshold derivation.
|
|||
/// </content>
|
|||
internal sealed partial class HevcPictureDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the orientation-dependent boundary lookup and four-sample filter dispatch.
|
|||
/// </summary>
|
|||
private interface IDeblockingDirection |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether the boundary is vertical.
|
|||
/// </summary>
|
|||
public static abstract bool IsVertical { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets whether the selected four-sample segment is a transform or prediction boundary.
|
|||
/// </summary>
|
|||
/// <param name="state">The decoded deblocking boundary state.</param>
|
|||
/// <param name="plane">The primary coding plane.</param>
|
|||
/// <param name="x">The segment left luma coordinate.</param>
|
|||
/// <param name="y">The segment top luma coordinate.</param>
|
|||
/// <returns><see langword="true"/> when the segment is a filter candidate; otherwise, <see langword="false"/>.</returns>
|
|||
public static abstract bool IsBoundary(HevcDeblockingState state, HevcPlane plane, int x, int y); |
|||
|
|||
/// <summary>
|
|||
/// Applies the orientation-specific luma kernel.
|
|||
/// </summary>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The first Q-side sample X coordinate.</param>
|
|||
/// <param name="y">The first Q-side sample Y coordinate.</param>
|
|||
/// <param name="beta">The scaled discontinuity threshold.</param>
|
|||
/// <param name="tc">The scaled clipping threshold.</param>
|
|||
/// <param name="partPNoFilter">Whether the P-side block retains its original samples.</param>
|
|||
/// <param name="partQNoFilter">Whether the Q-side block retains its original samples.</param>
|
|||
/// <param name="bitDepth">The component sample precision.</param>
|
|||
public static abstract void FilterLuma( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int beta, |
|||
int tc, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int bitDepth); |
|||
|
|||
/// <summary>
|
|||
/// Applies the orientation-specific chroma kernel.
|
|||
/// </summary>
|
|||
/// <param name="picture">The reconstructed picture.</param>
|
|||
/// <param name="plane">The Cb or Cr component plane.</param>
|
|||
/// <param name="x">The first Q-side sample X coordinate.</param>
|
|||
/// <param name="y">The first Q-side sample Y coordinate.</param>
|
|||
/// <param name="tc">The scaled clipping threshold.</param>
|
|||
/// <param name="partPNoFilter">Whether the P-side block retains its original samples.</param>
|
|||
/// <param name="partQNoFilter">Whether the Q-side block retains its original samples.</param>
|
|||
/// <param name="bitDepth">The component sample precision.</param>
|
|||
/// <param name="count">The number of samples in the edge segment.</param>
|
|||
public static abstract void FilterChroma( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int tc, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int bitDepth, |
|||
int count); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the H.265 Table 8-20 clipping thresholds indexed by the effective boundary quantization parameter.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> DeblockingTcTable => |
|||
[ |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, |
|||
4, 4, 5, 5, 6, 6, 7, 8, 9, 10, 11, 13, 14, 16, 18, 20, 22, 24, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the H.265 Table 8-20 discontinuity thresholds indexed by the effective boundary quantization parameter.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> DeblockingBetaTable => |
|||
[ |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, |
|||
28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Applies vertical edges across the complete picture before applying any horizontal edge.
|
|||
/// </summary>
|
|||
/// <param name="tileLayout">The picture tile mapping.</param>
|
|||
private void ApplyDeblockingFilter(in HevcTileLayout tileLayout) |
|||
{ |
|||
this.ApplyDeblockingDirection<VerticalDeblockingDirection>(in tileLayout); |
|||
this.ApplyDeblockingDirection<HorizontalDeblockingDirection>(in tileLayout); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one closed deblocking direction to every coded component plane.
|
|||
/// </summary>
|
|||
/// <typeparam name="TDirection">The vertical or horizontal boundary operator.</typeparam>
|
|||
/// <param name="tileLayout">The picture tile mapping.</param>
|
|||
private void ApplyDeblockingDirection<TDirection>(in HevcTileLayout tileLayout) |
|||
where TDirection : struct, IDeblockingDirection |
|||
{ |
|||
if (this.sequenceParameterSet.SeparateColorPlaneFlag) |
|||
{ |
|||
for (int planeIndex = 0; planeIndex < 3; planeIndex++) |
|||
{ |
|||
this.ApplyLumaDeblocking<TDirection>((HevcPlane)planeIndex, planeIndex, in tileLayout); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
this.ApplyLumaDeblocking<TDirection>(HevcPlane.Y, 0, in tileLayout); |
|||
if (this.sequenceParameterSet.ChromaFormat != 0) |
|||
{ |
|||
this.ApplyChromaDeblocking<TDirection>(HevcPlane.Cb, in tileLayout); |
|||
this.ApplyChromaDeblocking<TDirection>(HevcPlane.Cr, in tileLayout); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one deblocking direction with the luma kernel to a primary coded plane.
|
|||
/// </summary>
|
|||
/// <typeparam name="TDirection">The vertical or horizontal boundary operator.</typeparam>
|
|||
/// <param name="plane">The primary coded plane.</param>
|
|||
/// <param name="codingTreeStateIndex">The coding-tree state selected for the plane.</param>
|
|||
/// <param name="tileLayout">The picture tile mapping.</param>
|
|||
private void ApplyLumaDeblocking<TDirection>(HevcPlane plane, int codingTreeStateIndex, in HevcTileLayout tileLayout) |
|||
where TDirection : struct, IDeblockingDirection |
|||
{ |
|||
int width = this.Picture.GetWidth(plane); |
|||
int height = this.Picture.GetHeight(plane); |
|||
int acrossLimit = TDirection.IsVertical ? width : height; |
|||
int alongLimit = TDirection.IsVertical ? height : width; |
|||
int bitDepth = this.Picture.GetBitDepth(plane); |
|||
int bitDepthScale = 1 << (bitDepth - 8); |
|||
int codingTreeBlockSize = 1 << this.sequenceParameterSet.CodingTreeBlockLog2; |
|||
HevcCodingTreeState codingTreeState = this.codingTreeStates[codingTreeStateIndex]; |
|||
|
|||
// Deblocking visits only eight-sample grid lines, but each candidate is retained at four-sample resolution
|
|||
// because transform and prediction boundaries can differ between the two halves of that grid interval.
|
|||
for (int edge = 8; edge < acrossLimit; edge += 8) |
|||
{ |
|||
for (int along = 0; along < alongLimit; along += 4) |
|||
{ |
|||
int x = TDirection.IsVertical ? edge : along; |
|||
int y = TDirection.IsVertical ? along : edge; |
|||
if (!TDirection.IsBoundary(this.deblockingState, plane, x, y)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
int rasterAddress = ((y / codingTreeBlockSize) * tileLayout.Width) + (x / codingTreeBlockSize); |
|||
HevcLoopFilterRegion region = this.sampleAdaptiveOffsetState.GetLoopFilterRegion(rasterAddress, plane); |
|||
if (region.DeblockingFilterDisabled |
|||
|| !this.IsDeblockingCtbBoundaryAvailable<TDirection>(rasterAddress, plane, x, y, codingTreeBlockSize, in tileLayout)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
int pX = x - (TDirection.IsVertical ? 1 : 0); |
|||
int pY = y - (TDirection.IsVertical ? 0 : 1); |
|||
int qX = x; |
|||
int qY = y; |
|||
int quantizationParameterP = codingTreeState.GetQuantizationParameter(pX, pY); |
|||
int quantizationParameterQ = codingTreeState.GetQuantizationParameter(qX, qY); |
|||
int averageQuantizationParameter = (quantizationParameterP + quantizationParameterQ + 1) >> 1; |
|||
int tcIndex = Math.Clamp(averageQuantizationParameter + 2 + (region.DeblockingFilterTcOffsetDiv2 << 1), 0, 53); |
|||
int betaIndex = Math.Clamp(averageQuantizationParameter + (region.DeblockingFilterBetaOffsetDiv2 << 1), 0, 51); |
|||
int tc = DeblockingTcTable[tcIndex] * bitDepthScale; |
|||
int beta = DeblockingBetaTable[betaIndex] * bitDepthScale; |
|||
bool partPNoFilter = this.IsDeblockingSuppressed(codingTreeState, pX, pY); |
|||
bool partQNoFilter = this.IsDeblockingSuppressed(codingTreeState, qX, qY); |
|||
|
|||
TDirection.FilterLuma(this.Picture, plane, x, y, beta, tc, partPNoFilter, partQNoFilter, bitDepth); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one deblocking direction with the chroma kernel to a combined Cb or Cr plane.
|
|||
/// </summary>
|
|||
/// <typeparam name="TDirection">The vertical or horizontal boundary operator.</typeparam>
|
|||
/// <param name="plane">The Cb or Cr component plane.</param>
|
|||
/// <param name="tileLayout">The picture tile mapping.</param>
|
|||
private void ApplyChromaDeblocking<TDirection>(HevcPlane plane, in HevcTileLayout tileLayout) |
|||
where TDirection : struct, IDeblockingDirection |
|||
{ |
|||
int subsamplingX = this.Picture.GetSubsamplingX(plane); |
|||
int subsamplingY = this.Picture.GetSubsamplingY(plane); |
|||
int width = this.Picture.GetWidth(plane); |
|||
int height = this.Picture.GetHeight(plane); |
|||
int acrossLimit = TDirection.IsVertical ? width : height; |
|||
int alongLimit = TDirection.IsVertical ? height : width; |
|||
int alongSubsampling = TDirection.IsVertical ? subsamplingY : subsamplingX; |
|||
int segmentLength = 4 >> alongSubsampling; |
|||
int bitDepth = this.Picture.GetBitDepth(plane); |
|||
int bitDepthScale = 1 << (bitDepth - 8); |
|||
int codingTreeBlockSize = 1 << this.sequenceParameterSet.CodingTreeBlockLog2; |
|||
HevcCodingTreeState codingTreeState = this.codingTreeStates[0]; |
|||
|
|||
// Chroma deblocking uses eight-sample component-grid edges. A two-lane segment in subsampled directions still
|
|||
// enters the SIMD kernel, but only its valid low lanes are committed because QP and suppression state can change next.
|
|||
for (int edge = 8; edge < acrossLimit; edge += 8) |
|||
{ |
|||
for (int along = 0; along < alongLimit; along += segmentLength) |
|||
{ |
|||
int x = TDirection.IsVertical ? edge : along; |
|||
int y = TDirection.IsVertical ? along : edge; |
|||
int lumaX = x << subsamplingX; |
|||
int lumaY = y << subsamplingY; |
|||
if (!TDirection.IsBoundary(this.deblockingState, HevcPlane.Y, lumaX, lumaY)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
int rasterAddress = ((lumaY / codingTreeBlockSize) * tileLayout.Width) + (lumaX / codingTreeBlockSize); |
|||
HevcLoopFilterRegion region = this.sampleAdaptiveOffsetState.GetLoopFilterRegion(rasterAddress, HevcPlane.Y); |
|||
if (region.DeblockingFilterDisabled |
|||
|| !this.IsDeblockingCtbBoundaryAvailable<TDirection>( |
|||
rasterAddress, |
|||
HevcPlane.Y, |
|||
lumaX, |
|||
lumaY, |
|||
codingTreeBlockSize, |
|||
in tileLayout)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
int pX = lumaX - (TDirection.IsVertical ? 1 : 0); |
|||
int pY = lumaY - (TDirection.IsVertical ? 0 : 1); |
|||
int qX = lumaX; |
|||
int qY = lumaY; |
|||
int quantizationParameterP = codingTreeState.GetQuantizationParameter(pX, pY); |
|||
int quantizationParameterQ = codingTreeState.GetQuantizationParameter(qX, qY); |
|||
int averageQuantizationParameter = (quantizationParameterP + quantizationParameterQ + 1) >> 1; |
|||
|
|||
// Chroma deblocking uses only the picture-level component offset. Slice offsets and the RExt
|
|||
// coding-unit adjustment affect inverse quantization, but H.265 excludes both from tc derivation.
|
|||
int componentOffset = plane == HevcPlane.Cb |
|||
? this.pictureParameterSet.ChromaCbQuantizationParameterOffset |
|||
: this.pictureParameterSet.ChromaCrQuantizationParameterOffset; |
|||
|
|||
int chromaQuantizationParameter = HevcQuantizationParameters.GetChromaQuantizationParameter( |
|||
averageQuantizationParameter, |
|||
componentOffset, |
|||
0, |
|||
this.sequenceParameterSet.ChromaFormat); |
|||
|
|||
int tcIndex = Math.Clamp(chromaQuantizationParameter + 2 + (region.DeblockingFilterTcOffsetDiv2 << 1), 0, 53); |
|||
int tc = DeblockingTcTable[tcIndex] * bitDepthScale; |
|||
bool partPNoFilter = this.IsDeblockingSuppressed(codingTreeState, pX, pY); |
|||
bool partQNoFilter = this.IsDeblockingSuppressed(codingTreeState, qX, qY); |
|||
|
|||
TDirection.FilterChroma(this.Picture, plane, x, y, tc, partPNoFilter, partQNoFilter, bitDepth, segmentLength); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets whether an edge crossing a coding-tree-block boundary is permitted by slice and tile rules.
|
|||
/// </summary>
|
|||
/// <typeparam name="TDirection">The vertical or horizontal boundary operator.</typeparam>
|
|||
/// <param name="rasterAddress">The Q-side coding-tree-block raster address.</param>
|
|||
/// <param name="plane">The primary coding plane.</param>
|
|||
/// <param name="x">The edge luma X coordinate.</param>
|
|||
/// <param name="y">The edge luma Y coordinate.</param>
|
|||
/// <param name="codingTreeBlockSize">The coding-tree-block side in luma samples.</param>
|
|||
/// <param name="tileLayout">The picture tile mapping.</param>
|
|||
/// <returns><see langword="true"/> for an internal or permitted external boundary; otherwise, <see langword="false"/>.</returns>
|
|||
private bool IsDeblockingCtbBoundaryAvailable<TDirection>( |
|||
int rasterAddress, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int codingTreeBlockSize, |
|||
in HevcTileLayout tileLayout) |
|||
where TDirection : struct, IDeblockingDirection |
|||
{ |
|||
int acrossCoordinate = TDirection.IsVertical ? x : y; |
|||
if (acrossCoordinate % codingTreeBlockSize != 0) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
HevcLoopFilterBoundaryAvailability availability = this.sampleAdaptiveOffsetState.GetLoopFilterBoundaryAvailability( |
|||
rasterAddress, |
|||
plane, |
|||
tileLayout.Width, |
|||
tileLayout.Height, |
|||
this.pictureParameterSet.LoopFilterAcrossTilesEnabled); |
|||
|
|||
return TDirection.IsVertical ? availability.Left : availability.Above; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets whether PCM or transform-bypass syntax preserves one side of a filtered boundary.
|
|||
/// </summary>
|
|||
/// <param name="state">The coding-tree state for the selected primary plane.</param>
|
|||
/// <param name="x">The luma sample X coordinate.</param>
|
|||
/// <param name="y">The luma sample Y coordinate.</param>
|
|||
/// <returns><see langword="true"/> when the reconstructed side must not be modified; otherwise, <see langword="false"/>.</returns>
|
|||
private bool IsDeblockingSuppressed(HevcCodingTreeState state, int x, int y) |
|||
=> (this.sequenceParameterSet.PcmLoopFilterDisabled && state.IsPcm(x, y)) |
|||
|| (this.pictureParameterSet.TransquantizationBypassEnabled && state.IsTransquantBypass(x, y)); |
|||
|
|||
/// <summary>
|
|||
/// Selects vertical boundary lookup and filtering.
|
|||
/// </summary>
|
|||
private readonly struct VerticalDeblockingDirection : IDeblockingDirection |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool IsVertical => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool IsBoundary(HevcDeblockingState state, HevcPlane plane, int x, int y) |
|||
=> state.IsVerticalBoundary(plane, x, y); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FilterLuma( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int beta, |
|||
int tc, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int bitDepth) |
|||
=> HevcDeblockingFilter.FilterVerticalLuma(picture, plane, x, y, beta, tc, partPNoFilter, partQNoFilter, bitDepth); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FilterChroma( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int tc, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int bitDepth, |
|||
int count) |
|||
=> HevcDeblockingFilter.FilterVerticalChroma(picture, plane, x, y, tc, partPNoFilter, partQNoFilter, bitDepth, count); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects horizontal boundary lookup and filtering.
|
|||
/// </summary>
|
|||
private readonly struct HorizontalDeblockingDirection : IDeblockingDirection |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool IsVertical => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool IsBoundary(HevcDeblockingState state, HevcPlane plane, int x, int y) |
|||
=> state.IsHorizontalBoundary(plane, x, y); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FilterLuma( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int beta, |
|||
int tc, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int bitDepth) |
|||
=> HevcDeblockingFilter.FilterHorizontalLuma(picture, plane, x, y, beta, tc, partPNoFilter, partQNoFilter, bitDepth); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FilterChroma( |
|||
HevcPictureBuffer picture, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int tc, |
|||
bool partPNoFilter, |
|||
bool partQNoFilter, |
|||
int bitDepth, |
|||
int count) |
|||
=> HevcDeblockingFilter.FilterHorizontalChroma(picture, plane, x, y, tc, partPNoFilter, partQNoFilter, bitDepth, count); |
|||
} |
|||
} |
|||
@ -1,232 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Implements intra prediction, reconstructed-plane writes, and PCM sample reconstruction.
|
|||
/// </content>
|
|||
internal sealed partial class HevcPictureDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// Reconstructs one packed intra-prediction block in caller-owned scratch.
|
|||
/// </summary>
|
|||
/// <param name="plane">The reconstructed component plane.</param>
|
|||
/// <param name="x">The prediction-block left coordinate in component samples.</param>
|
|||
/// <param name="y">The prediction-block top coordinate in component samples.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the square prediction-block side.</param>
|
|||
/// <param name="regionId">The current independent-slice and tile prediction region.</param>
|
|||
/// <param name="colorPlaneIndex">The selected separate-color plane, or zero for combined coding.</param>
|
|||
/// <param name="transquantBypass">Whether the governing coding unit bypasses inverse quantization and transform.</param>
|
|||
/// <returns>The packed predicted samples.</returns>
|
|||
private Span<ushort> PredictComponentBlock(HevcPlane plane, int x, int y, int log2Size, int regionId, int colorPlaneIndex, bool transquantBypass) |
|||
{ |
|||
int size = 1 << log2Size; |
|||
int sampleCount = size * size; |
|||
int referenceLength = (size * 2) + 1; |
|||
Span<ushort> scratch = this.predictionScratch.Memory.Span; |
|||
Span<ushort> prediction = scratch[..sampleCount]; |
|||
Span<ushort> top = scratch.Slice(MaximumTransformSampleCount, MaximumReferenceLength); |
|||
Span<ushort> left = scratch.Slice(MaximumTransformSampleCount + MaximumReferenceLength, MaximumReferenceLength); |
|||
Span<ushort> filteredTop = scratch.Slice(MaximumTransformSampleCount + (MaximumReferenceLength * 2), MaximumReferenceLength); |
|||
Span<ushort> filteredLeft = scratch.Slice(MaximumTransformSampleCount + (MaximumReferenceLength * 3), MaximumReferenceLength); |
|||
int referenceScratchOffset = MaximumTransformSampleCount + (MaximumReferenceLength * 4); |
|||
int unitWidth = this.reconstructionState.GetUnitWidth(plane); |
|||
int unitHeight = this.reconstructionState.GetUnitHeight(plane); |
|||
int referenceScratchLength = HevcIntraPredictor.GetReferenceScratchLength(log2Size, unitWidth); |
|||
Span<ushort> referenceScratch = scratch.Slice(referenceScratchOffset, referenceScratchLength); |
|||
Span<ushort> operationScratch = scratch[(referenceScratchOffset + referenceScratchLength)..]; |
|||
Span<bool> availability = this.availabilityScratch.Memory.Span; |
|||
int availabilityCount = this.reconstructionState.BuildReferenceAvailability( |
|||
plane, |
|||
x, |
|||
y, |
|||
log2Size, |
|||
regionId, |
|||
availability); |
|||
|
|||
HevcIntraPredictor.PrepareReferenceSamples( |
|||
this.Picture, |
|||
plane, |
|||
x, |
|||
y, |
|||
log2Size, |
|||
unitWidth, |
|||
unitHeight, |
|||
availability[..availabilityCount], |
|||
top, |
|||
left, |
|||
referenceScratch); |
|||
|
|||
int lumaX = x << this.Picture.GetSubsamplingX(plane); |
|||
int lumaY = y << this.Picture.GetSubsamplingY(plane); |
|||
bool useLumaSyntax = plane == HevcPlane.Y || this.sequenceParameterSet.SeparateColorPlaneFlag; |
|||
int mode = useLumaSyntax |
|||
? this.intraPredictionStates[colorPlaneIndex].GetLumaMode(lumaX, lumaY) |
|||
: this.intraPredictionStates[colorPlaneIndex].GetEffectiveChromaMode(lumaX, lumaY); |
|||
|
|||
if (!useLumaSyntax && this.sequenceParameterSet.ChromaFormat == 2) |
|||
{ |
|||
mode = HevcIntraPredictionMode.RemapChroma422(mode); |
|||
} |
|||
|
|||
// H.265 8.4.4.2.3 and 8.4.4.2.6 restrict prediction-edge filtering to luma blocks no larger than 16 samples.
|
|||
// Implicit RDPCM bypasses that filtering for the lossless horizontal and vertical prediction modes.
|
|||
bool filterPredictionEdges = useLumaSyntax |
|||
&& size <= 16 |
|||
&& !(transquantBypass |
|||
&& this.sequenceParameterSet.ImplicitResidualDpcmEnabled |
|||
&& (mode == HevcIntraPredictionMode.Horizontal || mode == HevcIntraPredictionMode.Vertical)); |
|||
|
|||
bool filterReferences = HevcIntraPredictor.ShouldFilterReferenceSamples( |
|||
useLumaSyntax ? HevcPlane.Y : plane, |
|||
mode, |
|||
log2Size, |
|||
this.sequenceParameterSet.ChromaFormat, |
|||
this.sequenceParameterSet.IntraSmoothingDisabled); |
|||
|
|||
ReadOnlySpan<ushort> selectedTop = top[..referenceLength]; |
|||
ReadOnlySpan<ushort> selectedLeft = left[..referenceLength]; |
|||
if (filterReferences) |
|||
{ |
|||
// Normal three-tap smoothing extends to combined 4:4:4 chroma, but strong bilinear smoothing is a luma
|
|||
// operation. Separate color planes use luma syntax and therefore retain the luma behavior.
|
|||
bool useStrongSmoothing = useLumaSyntax && this.sequenceParameterSet.StrongIntraSmoothingEnabled; |
|||
|
|||
HevcIntraPredictor.FilterReferenceSamples( |
|||
selectedTop, |
|||
selectedLeft, |
|||
filteredTop, |
|||
filteredLeft, |
|||
log2Size, |
|||
this.Picture.GetBitDepth(plane), |
|||
useStrongSmoothing); |
|||
|
|||
selectedTop = filteredTop[..referenceLength]; |
|||
selectedLeft = filteredLeft[..referenceLength]; |
|||
} |
|||
|
|||
HevcIntraPredictor.Predict( |
|||
selectedTop, |
|||
selectedLeft, |
|||
prediction, |
|||
size, |
|||
log2Size, |
|||
mode, |
|||
this.Picture.GetBitDepth(plane), |
|||
filterPredictionEdges, |
|||
operationScratch); |
|||
|
|||
return prediction; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies one packed reconstructed block into the allocator-owned picture plane.
|
|||
/// </summary>
|
|||
/// <param name="source">The packed reconstructed samples.</param>
|
|||
/// <param name="plane">The destination component plane.</param>
|
|||
/// <param name="x">The destination left coordinate.</param>
|
|||
/// <param name="y">The destination top coordinate.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
private void CopyPredictionToPicture(ReadOnlySpan<ushort> source, HevcPlane plane, int x, int y, int size) |
|||
{ |
|||
for (int row = 0; row < size; row++) |
|||
{ |
|||
source.Slice(row * size, size).CopyTo(this.Picture.GetRowSpan(plane, y + row)[x..]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads and writes every raw sample in one PCM coding unit before arithmetic decoding restarts.
|
|||
/// </summary>
|
|||
/// <param name="reader">The suspended entropy-substream reader.</param>
|
|||
/// <param name="x">The coding-unit left luma coordinate.</param>
|
|||
/// <param name="y">The coding-unit top luma coordinate.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the coding-unit side.</param>
|
|||
/// <param name="regionId">The current independent-slice and tile prediction region.</param>
|
|||
/// <param name="colorPlaneIndex">The selected separate-color plane, or zero for combined coding.</param>
|
|||
private void DecodePcmCodingUnit( |
|||
ref HevcCabacSyntaxReader reader, |
|||
int x, |
|||
int y, |
|||
int log2Size, |
|||
int regionId, |
|||
int colorPlaneIndex) |
|||
{ |
|||
int size = 1 << log2Size; |
|||
if (this.sequenceParameterSet.SeparateColorPlaneFlag) |
|||
{ |
|||
HevcPlane plane = (HevcPlane)colorPlaneIndex; |
|||
this.DecodePcmPlane(ref reader, plane, x, y, size, size, this.sequenceParameterSet.PcmBitDepthLuma, regionId); |
|||
return; |
|||
} |
|||
|
|||
this.DecodePcmPlane(ref reader, HevcPlane.Y, x, y, size, size, this.sequenceParameterSet.PcmBitDepthLuma, regionId); |
|||
if (this.sequenceParameterSet.ChromaFormat == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int subsamplingX = this.Picture.GetSubsamplingX(HevcPlane.Cb); |
|||
int subsamplingY = this.Picture.GetSubsamplingY(HevcPlane.Cb); |
|||
int chromaWidth = size >> subsamplingX; |
|||
int chromaHeight = size >> subsamplingY; |
|||
int chromaX = x >> subsamplingX; |
|||
int chromaY = y >> subsamplingY; |
|||
this.DecodePcmPlane( |
|||
ref reader, |
|||
HevcPlane.Cb, |
|||
chromaX, |
|||
chromaY, |
|||
chromaWidth, |
|||
chromaHeight, |
|||
this.sequenceParameterSet.PcmBitDepthChroma, |
|||
regionId); |
|||
|
|||
this.DecodePcmPlane( |
|||
ref reader, |
|||
HevcPlane.Cr, |
|||
chromaX, |
|||
chromaY, |
|||
chromaWidth, |
|||
chromaHeight, |
|||
this.sequenceParameterSet.PcmBitDepthChroma, |
|||
regionId); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads one rectangular PCM component plane directly into the reconstructed picture.
|
|||
/// </summary>
|
|||
/// <param name="reader">The suspended entropy-substream reader.</param>
|
|||
/// <param name="plane">The destination component plane.</param>
|
|||
/// <param name="x">The destination left coordinate.</param>
|
|||
/// <param name="y">The destination top coordinate.</param>
|
|||
/// <param name="width">The component rectangle width.</param>
|
|||
/// <param name="height">The component rectangle height.</param>
|
|||
/// <param name="bitDepth">The PCM sample precision.</param>
|
|||
/// <param name="regionId">The current independent-slice and tile prediction region.</param>
|
|||
private void DecodePcmPlane( |
|||
ref HevcCabacSyntaxReader reader, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
int regionId) |
|||
{ |
|||
// PCM samples can use fewer bits than the reconstructed component. H.265 places those bits at the
|
|||
// most-significant end of the component range, so the raw code value must be restored before filtering.
|
|||
int bitDepthShift = this.Picture.GetBitDepth(plane) - bitDepth; |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<ushort> destination = this.Picture.GetRowSpan(plane, y + row).Slice(x, width); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
destination[column] = (ushort)(reader.ReadPcmSample(bitDepth) << bitDepthShift); |
|||
} |
|||
} |
|||
|
|||
this.reconstructionState.MarkReconstructed(plane, x, y, width, height, regionId); |
|||
} |
|||
} |
|||
@ -1,250 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Implements sample-adaptive-offset syntax decoding and merge resolution.
|
|||
/// </content>
|
|||
internal sealed partial class HevcPictureDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the resolved sample-adaptive offsets to every component after deblocking has completed.
|
|||
/// </summary>
|
|||
/// <param name="source">The immutable deblocked picture used to classify every sample.</param>
|
|||
/// <param name="tileLayout">The picture tile mapping used to derive coding-tree-block boundaries.</param>
|
|||
private void ApplySampleAdaptiveOffset(HevcPictureBuffer source, in HevcTileLayout tileLayout) |
|||
{ |
|||
int codingTreeBlockSize = 1 << this.sequenceParameterSet.CodingTreeBlockLog2; |
|||
int planeCount = this.sequenceParameterSet.ChromaFormat == 0 ? 1 : 3; |
|||
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) |
|||
{ |
|||
HevcPlane plane = (HevcPlane)planeIndex; |
|||
HevcPlane regionPlane = this.sequenceParameterSet.SeparateColorPlaneFlag ? plane : HevcPlane.Y; |
|||
int subsamplingX = this.Picture.GetSubsamplingX(plane); |
|||
int subsamplingY = this.Picture.GetSubsamplingY(plane); |
|||
int blockWidth = codingTreeBlockSize >> subsamplingX; |
|||
int blockHeight = codingTreeBlockSize >> subsamplingY; |
|||
int planeWidth = this.Picture.GetWidth(plane); |
|||
int planeHeight = this.Picture.GetHeight(plane); |
|||
int offsetScaleLog2 = plane == HevcPlane.Y |
|||
? this.pictureParameterSet.SampleAdaptiveOffsetScaleLumaLog2 |
|||
: this.pictureParameterSet.SampleAdaptiveOffsetScaleChromaLog2; |
|||
|
|||
for (int codingTreeBlockY = 0; codingTreeBlockY < tileLayout.Height; codingTreeBlockY++) |
|||
{ |
|||
for (int codingTreeBlockX = 0; codingTreeBlockX < tileLayout.Width; codingTreeBlockX++) |
|||
{ |
|||
int rasterAddress = (codingTreeBlockY * tileLayout.Width) + codingTreeBlockX; |
|||
HevcSampleAdaptiveOffsetParameters parameters = this.sampleAdaptiveOffsetState.Get(rasterAddress, plane); |
|||
if (parameters.Type == HevcSampleAdaptiveOffsetType.Off) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
HevcLoopFilterBoundaryAvailability availability = this.sampleAdaptiveOffsetState.GetLoopFilterBoundaryAvailability( |
|||
rasterAddress, |
|||
regionPlane, |
|||
tileLayout.Width, |
|||
tileLayout.Height, |
|||
this.pictureParameterSet.LoopFilterAcrossTilesEnabled); |
|||
|
|||
int x = codingTreeBlockX * blockWidth; |
|||
int y = codingTreeBlockY * blockHeight; |
|||
int width = Math.Min(blockWidth, planeWidth - x); |
|||
int height = Math.Min(blockHeight, planeHeight - y); |
|||
|
|||
// Every classification reads the immutable post-deblocking picture. Later CTBs can therefore never
|
|||
// observe offsets already written by an earlier CTB, including across permitted slice and tile boundaries.
|
|||
HevcSampleAdaptiveOffsetFilter.ApplyBlock( |
|||
source, |
|||
this.Picture, |
|||
plane, |
|||
x, |
|||
y, |
|||
width, |
|||
height, |
|||
in parameters, |
|||
offsetScaleLog2, |
|||
availability.Left, |
|||
availability.Right, |
|||
availability.Above, |
|||
availability.Below, |
|||
availability.AboveLeft, |
|||
availability.AboveRight, |
|||
availability.BelowLeft, |
|||
availability.BelowRight); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes and resolves the sample-adaptive-offset parameters for one coding-tree block.
|
|||
/// </summary>
|
|||
/// <param name="reader">The active entropy-substream reader.</param>
|
|||
/// <param name="independentSlice">The independent slice governing component enable flags.</param>
|
|||
/// <param name="rasterAddress">The coding-tree block's raster-scan address.</param>
|
|||
/// <param name="codingTreeBlockX">The horizontal coding-tree-block coordinate.</param>
|
|||
/// <param name="codingTreeBlockY">The vertical coding-tree-block coordinate.</param>
|
|||
/// <param name="regionId">The current independent-slice and tile prediction region.</param>
|
|||
private void DecodeSampleAdaptiveOffset( |
|||
ref HevcCabacSyntaxReader reader, |
|||
HevcSliceSegmentHeader independentSlice, |
|||
int rasterAddress, |
|||
int codingTreeBlockX, |
|||
int codingTreeBlockY, |
|||
int regionId) |
|||
{ |
|||
HevcPlane regionPlane = this.sequenceParameterSet.SeparateColorPlaneFlag ? (HevcPlane)independentSlice.ColorPlaneId : HevcPlane.Y; |
|||
bool lumaEnabled = independentSlice.SampleAdaptiveOffsetLumaEnabled == true; |
|||
bool chromaEnabled = independentSlice.SampleAdaptiveOffsetChromaEnabled == true; |
|||
if (!lumaEnabled && !chromaEnabled) |
|||
{ |
|||
this.sampleAdaptiveOffsetState.SetRegion(rasterAddress, regionPlane, regionId); |
|||
return; |
|||
} |
|||
|
|||
int codingTreeBlockWidth = HevcParameterSetSyntax.GetCodingTreeBlockCount( |
|||
this.sequenceParameterSet.Width, |
|||
this.sequenceParameterSet.CodingTreeBlockLog2); |
|||
|
|||
int leftAddress = rasterAddress - 1; |
|||
bool leftAvailable = codingTreeBlockX > 0 && this.sampleAdaptiveOffsetState.IsInRegion(leftAddress, regionPlane, regionId); |
|||
bool mergeLeft = leftAvailable && reader.ReadSampleAdaptiveOffsetMerge(); |
|||
int aboveAddress = rasterAddress - codingTreeBlockWidth; |
|||
bool aboveAvailable = codingTreeBlockY > 0 && this.sampleAdaptiveOffsetState.IsInRegion(aboveAddress, regionPlane, regionId); |
|||
bool mergeAbove = !mergeLeft && aboveAvailable && reader.ReadSampleAdaptiveOffsetMerge(); |
|||
if (mergeLeft || mergeAbove) |
|||
{ |
|||
int sourceAddress = mergeLeft ? leftAddress : aboveAddress; |
|||
this.CopySampleAdaptiveOffsetParameters(sourceAddress, rasterAddress, lumaEnabled, chromaEnabled, independentSlice.ColorPlaneId); |
|||
this.sampleAdaptiveOffsetState.SetRegion(rasterAddress, regionPlane, regionId); |
|||
return; |
|||
} |
|||
|
|||
if (this.sequenceParameterSet.SeparateColorPlaneFlag) |
|||
{ |
|||
HevcPlane plane = (HevcPlane)independentSlice.ColorPlaneId; |
|||
this.sampleAdaptiveOffsetState.Set(rasterAddress, plane, ReadSampleAdaptiveOffsetParameters(ref reader, this.Picture.GetBitDepth(plane), -1)); |
|||
} |
|||
else |
|||
{ |
|||
if (lumaEnabled) |
|||
{ |
|||
this.sampleAdaptiveOffsetState.Set( |
|||
rasterAddress, |
|||
HevcPlane.Y, |
|||
ReadSampleAdaptiveOffsetParameters(ref reader, this.sequenceParameterSet.BitDepthLuma, -1)); |
|||
} |
|||
|
|||
if (chromaEnabled) |
|||
{ |
|||
HevcSampleAdaptiveOffsetParameters chromaBlue = ReadSampleAdaptiveOffsetParameters( |
|||
ref reader, |
|||
this.sequenceParameterSet.BitDepthChroma, |
|||
-1); |
|||
|
|||
this.sampleAdaptiveOffsetState.Set(rasterAddress, HevcPlane.Cb, chromaBlue); |
|||
this.sampleAdaptiveOffsetState.Set( |
|||
rasterAddress, |
|||
HevcPlane.Cr, |
|||
ReadSampleAdaptiveOffsetParameters(ref reader, this.sequenceParameterSet.BitDepthChroma, (int)chromaBlue.Type)); |
|||
} |
|||
} |
|||
|
|||
this.sampleAdaptiveOffsetState.SetRegion(rasterAddress, regionPlane, regionId); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies resolved merge-source parameters for the components enabled by the current slice.
|
|||
/// </summary>
|
|||
/// <param name="sourceAddress">The merge-source coding-tree-block address.</param>
|
|||
/// <param name="destinationAddress">The current coding-tree-block address.</param>
|
|||
/// <param name="lumaEnabled">Whether the current slice enables luma sample-adaptive offset.</param>
|
|||
/// <param name="chromaEnabled">Whether the current slice enables chroma sample-adaptive offset.</param>
|
|||
/// <param name="colorPlaneId">The selected separate-color-plane identifier.</param>
|
|||
private void CopySampleAdaptiveOffsetParameters( |
|||
int sourceAddress, |
|||
int destinationAddress, |
|||
bool lumaEnabled, |
|||
bool chromaEnabled, |
|||
byte colorPlaneId) |
|||
{ |
|||
if (this.sequenceParameterSet.SeparateColorPlaneFlag) |
|||
{ |
|||
HevcPlane plane = (HevcPlane)colorPlaneId; |
|||
this.sampleAdaptiveOffsetState.Set(destinationAddress, plane, this.sampleAdaptiveOffsetState.Get(sourceAddress, plane)); |
|||
return; |
|||
} |
|||
|
|||
if (lumaEnabled) |
|||
{ |
|||
this.sampleAdaptiveOffsetState.Set(destinationAddress, HevcPlane.Y, this.sampleAdaptiveOffsetState.Get(sourceAddress, HevcPlane.Y)); |
|||
} |
|||
|
|||
if (chromaEnabled) |
|||
{ |
|||
this.sampleAdaptiveOffsetState.Set(destinationAddress, HevcPlane.Cb, this.sampleAdaptiveOffsetState.Get(sourceAddress, HevcPlane.Cb)); |
|||
this.sampleAdaptiveOffsetState.Set(destinationAddress, HevcPlane.Cr, this.sampleAdaptiveOffsetState.Get(sourceAddress, HevcPlane.Cr)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes one component's new or disabled sample-adaptive-offset mode.
|
|||
/// </summary>
|
|||
/// <param name="reader">The active entropy-substream reader.</param>
|
|||
/// <param name="bitDepth">The component sample precision.</param>
|
|||
/// <param name="inheritedType">The Cb type inherited by Cr, or negative one when the type is signaled.</param>
|
|||
/// <returns>The resolved component parameters.</returns>
|
|||
private static HevcSampleAdaptiveOffsetParameters ReadSampleAdaptiveOffsetParameters( |
|||
ref HevcCabacSyntaxReader reader, |
|||
int bitDepth, |
|||
int inheritedType) |
|||
{ |
|||
int type = inheritedType >= 0 |
|||
? inheritedType == (int)HevcSampleAdaptiveOffsetType.Off ? 0 : inheritedType == (int)HevcSampleAdaptiveOffsetType.Band ? 1 : 2 |
|||
: reader.ReadSampleAdaptiveOffsetType(); |
|||
|
|||
if (type == 0) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
int maximumOffset = (1 << (Math.Min(bitDepth, 10) - 5)) - 1; |
|||
int offset0 = reader.ReadSampleAdaptiveOffsetAbsolute(maximumOffset); |
|||
int offset1 = reader.ReadSampleAdaptiveOffsetAbsolute(maximumOffset); |
|||
int offset2 = reader.ReadSampleAdaptiveOffsetAbsolute(maximumOffset); |
|||
int offset3 = reader.ReadSampleAdaptiveOffsetAbsolute(maximumOffset); |
|||
if (type == 1) |
|||
{ |
|||
offset0 = ApplySampleAdaptiveOffsetSign(ref reader, offset0); |
|||
offset1 = ApplySampleAdaptiveOffsetSign(ref reader, offset1); |
|||
offset2 = ApplySampleAdaptiveOffsetSign(ref reader, offset2); |
|||
offset3 = ApplySampleAdaptiveOffsetSign(ref reader, offset3); |
|||
return new HevcSampleAdaptiveOffsetParameters( |
|||
HevcSampleAdaptiveOffsetType.Band, |
|||
reader.ReadSampleAdaptiveOffsetBandPosition(), |
|||
offset0, |
|||
offset1, |
|||
offset2, |
|||
offset3, |
|||
0); |
|||
} |
|||
|
|||
HevcSampleAdaptiveOffsetType edgeType = inheritedType >= 0 |
|||
? (HevcSampleAdaptiveOffsetType)inheritedType |
|||
: (HevcSampleAdaptiveOffsetType)((int)HevcSampleAdaptiveOffsetType.EdgeHorizontal + reader.ReadSampleAdaptiveOffsetEdgeClass()); |
|||
|
|||
return new HevcSampleAdaptiveOffsetParameters(edgeType, 0, offset0, offset1, 0, -offset2, -offset3); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies an explicitly coded sign to a nonzero band-offset magnitude.
|
|||
/// </summary>
|
|||
/// <param name="reader">The active entropy-substream reader.</param>
|
|||
/// <param name="magnitude">The decoded unsigned magnitude.</param>
|
|||
/// <returns>The signed magnitude.</returns>
|
|||
private static int ApplySampleAdaptiveOffsetSign(ref HevcCabacSyntaxReader reader, int magnitude) |
|||
=> magnitude != 0 && reader.ReadSampleAdaptiveOffsetSign() ? -magnitude : magnitude; |
|||
} |
|||
@ -1,564 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Implements transform-tree syntax, coefficient reconstruction, and intra sample reconstruction.
|
|||
/// </content>
|
|||
internal sealed partial class HevcPictureDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes and reconstructs one transform-tree node.
|
|||
/// </summary>
|
|||
/// <param name="reader">The active entropy-substream reader.</param>
|
|||
/// <param name="geometry">The luma and component rectangles at this transform depth.</param>
|
|||
/// <param name="transformDepth">The transform depth relative to the coding-unit root.</param>
|
|||
/// <param name="minimumTransformLog2">The smallest luma transform permitted in the coding unit.</param>
|
|||
/// <param name="usesNxNPartitions">Whether the coding unit has four luma prediction partitions.</param>
|
|||
/// <param name="transquantBypass">Whether the coding unit bypasses inverse quantization and transform.</param>
|
|||
/// <param name="regionId">The current independent-slice and tile prediction region.</param>
|
|||
/// <param name="colorPlaneIndex">The selected separate-color plane, or zero for combined coding.</param>
|
|||
/// <param name="parentChromaBlueFlags">The blue-difference coded-block flags inherited from the parent.</param>
|
|||
/// <param name="parentChromaRedFlags">The red-difference coded-block flags inherited from the parent.</param>
|
|||
private void DecodeTransformTree( |
|||
ref HevcCabacSyntaxReader reader, |
|||
in HevcTransformUnitGeometry geometry, |
|||
int transformDepth, |
|||
int minimumTransformLog2, |
|||
bool usesNxNPartitions, |
|||
bool transquantBypass, |
|||
int regionId, |
|||
int colorPlaneIndex, |
|||
HevcCodedBlockFlags parentChromaBlueFlags, |
|||
HevcCodedBlockFlags parentChromaRedFlags) |
|||
{ |
|||
int log2Size = geometry.Log2LumaSize; |
|||
HevcTransformComponentGeometry primaryGeometry = geometry.Primary; |
|||
HevcTransformComponentGeometry chromaBlueGeometry = geometry.ChromaBlue; |
|||
HevcTransformComponentGeometry chromaRedGeometry = geometry.ChromaRed; |
|||
bool split; |
|||
if (usesNxNPartitions && transformDepth == 0) |
|||
{ |
|||
split = true; |
|||
} |
|||
else if (log2Size > this.sequenceParameterSet.MaxTransformBlockLog2) |
|||
{ |
|||
split = true; |
|||
} |
|||
else if (log2Size == this.sequenceParameterSet.MinTransformBlockLog2 || log2Size == minimumTransformLog2) |
|||
{ |
|||
split = false; |
|||
} |
|||
else |
|||
{ |
|||
split = reader.ReadTransformSubdivision(log2Size); |
|||
} |
|||
|
|||
HevcCodedBlockFlags chromaBlueFlags = parentChromaBlueFlags; |
|||
HevcCodedBlockFlags chromaRedFlags = parentChromaRedFlags; |
|||
if (geometry.HasCombinedChroma) |
|||
{ |
|||
chromaBlueFlags = DecodeChromaCodedBlockFlags( |
|||
ref reader, |
|||
in chromaBlueGeometry, |
|||
transformDepth, |
|||
split, |
|||
parentChromaBlueFlags); |
|||
|
|||
chromaRedFlags = DecodeChromaCodedBlockFlags( |
|||
ref reader, |
|||
in chromaRedGeometry, |
|||
transformDepth, |
|||
split, |
|||
parentChromaRedFlags); |
|||
} |
|||
|
|||
if (split) |
|||
{ |
|||
for (int child = 0; child < 4; child++) |
|||
{ |
|||
HevcTransformUnitGeometry childGeometry = geometry.CreateChild(child); |
|||
this.DecodeTransformTree( |
|||
ref reader, |
|||
in childGeometry, |
|||
transformDepth + 1, |
|||
minimumTransformLog2, |
|||
usesNxNPartitions, |
|||
transquantBypass, |
|||
regionId, |
|||
colorPlaneIndex, |
|||
chromaBlueFlags, |
|||
chromaRedFlags); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
this.deblockingState.MarkBlock( |
|||
geometry.PrimaryPlane, |
|||
primaryGeometry.X, |
|||
primaryGeometry.Y, |
|||
primaryGeometry.Width, |
|||
primaryGeometry.Height); |
|||
|
|||
HevcCodedBlockFlags primaryFlags = new(reader.ReadTransformCodedBlockFlag(false, transformDepth == 0 ? 1 : 0)); |
|||
bool hasCodedResidual = primaryFlags.Any || chromaBlueFlags.Any || chromaRedFlags.Any; |
|||
if (hasCodedResidual && this.quantizationParameterDeltaPending) |
|||
{ |
|||
this.ApplyQuantizationParameterDelta(reader.ReadDeltaQuantizationParameter()); |
|||
this.quantizationParameterDeltaPending = false; |
|||
} |
|||
|
|||
if ((chromaBlueFlags.Any || chromaRedFlags.Any) |
|||
&& this.chromaQuantizationAdjustmentPending |
|||
&& !transquantBypass) |
|||
{ |
|||
this.currentChromaQuantizationAdjustment = reader.ReadChromaQuantizationAdjustment( |
|||
this.pictureParameterSet.ChromaQuantizationParameterOffsetsCb.Count); |
|||
|
|||
this.chromaQuantizationAdjustmentPending = false; |
|||
} |
|||
|
|||
HevcQuantizationParameters quantizationParameters = this.CreateQuantizationParameters(); |
|||
Span<int> lumaResidual = this.integerScratch.Memory.Span.Slice(MaximumTransformSampleCount * 3, MaximumTransformSampleCount); |
|||
lumaResidual.Clear(); |
|||
this.DecodeComponentSections( |
|||
ref reader, |
|||
geometry.PrimaryPlane, |
|||
in primaryGeometry, |
|||
primaryFlags, |
|||
transquantBypass, |
|||
regionId, |
|||
colorPlaneIndex, |
|||
in quantizationParameters, |
|||
lumaResidual, |
|||
true, |
|||
0, |
|||
in primaryGeometry); |
|||
|
|||
if (!geometry.HasCombinedChroma) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int chromaMode = this.intraPredictionStates[colorPlaneIndex].GetChromaMode(geometry.Primary.X, geometry.Primary.Y); |
|||
int chromaBlueAlpha = 0; |
|||
bool canPredictAcrossComponents = this.pictureParameterSet.CrossComponentPredictionEnabled |
|||
&& primaryFlags.Any |
|||
&& chromaMode == 36 |
|||
&& chromaBlueGeometry.Process |
|||
&& chromaBlueGeometry.Width == chromaBlueGeometry.Height; |
|||
|
|||
if (canPredictAcrossComponents) |
|||
{ |
|||
chromaBlueAlpha = reader.ReadCrossComponentPredictionScale(0); |
|||
} |
|||
|
|||
this.DecodeComponentSections( |
|||
ref reader, |
|||
HevcPlane.Cb, |
|||
in chromaBlueGeometry, |
|||
chromaBlueFlags, |
|||
transquantBypass, |
|||
regionId, |
|||
colorPlaneIndex, |
|||
in quantizationParameters, |
|||
lumaResidual, |
|||
false, |
|||
chromaBlueAlpha, |
|||
in primaryGeometry); |
|||
|
|||
int chromaRedAlpha = 0; |
|||
if (canPredictAcrossComponents) |
|||
{ |
|||
// The Cr scale follows the complete Cb residual syntax. Reading both scales together changes every
|
|||
// subsequent CABAC decision whenever Cb carries coefficients.
|
|||
chromaRedAlpha = reader.ReadCrossComponentPredictionScale(1); |
|||
} |
|||
|
|||
this.DecodeComponentSections( |
|||
ref reader, |
|||
HevcPlane.Cr, |
|||
in chromaRedGeometry, |
|||
chromaRedFlags, |
|||
transquantBypass, |
|||
regionId, |
|||
colorPlaneIndex, |
|||
in quantizationParameters, |
|||
lumaResidual, |
|||
false, |
|||
chromaRedAlpha, |
|||
in primaryGeometry); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes chroma coded-block flags at the highest transform level that owns the component rectangle.
|
|||
/// </summary>
|
|||
/// <param name="reader">The active entropy-substream reader.</param>
|
|||
/// <param name="geometry">The current chroma component rectangle.</param>
|
|||
/// <param name="transformDepth">The luma transform depth.</param>
|
|||
/// <param name="lumaSplit">Whether the current luma transform node subdivides.</param>
|
|||
/// <param name="parentFlags">The coded-block flags inherited from the parent transform node.</param>
|
|||
/// <returns>The flags governing the current component rectangle.</returns>
|
|||
private static HevcCodedBlockFlags DecodeChromaCodedBlockFlags( |
|||
ref HevcCabacSyntaxReader reader, |
|||
in HevcTransformComponentGeometry geometry, |
|||
int transformDepth, |
|||
bool lumaSplit, |
|||
HevcCodedBlockFlags parentFlags) |
|||
{ |
|||
if (!geometry.Process) |
|||
{ |
|||
return parentFlags; |
|||
} |
|||
|
|||
bool shouldDecode = transformDepth == 0 || (geometry.ProcessesAllQuadrants && parentFlags.Any); |
|||
if (!shouldDecode) |
|||
{ |
|||
return parentFlags; |
|||
} |
|||
|
|||
int context = transformDepth; |
|||
bool canQuadSplit = geometry.Width >= 8 && geometry.Height >= 8; |
|||
if (geometry.Width != geometry.Height && (!lumaSplit || !canQuadSplit)) |
|||
{ |
|||
bool first = reader.ReadTransformCodedBlockFlag(true, context); |
|||
bool second = reader.ReadTransformCodedBlockFlag(true, context); |
|||
return new HevcCodedBlockFlags(first, second); |
|||
} |
|||
|
|||
return new HevcCodedBlockFlags(reader.ReadTransformCodedBlockFlag(true, context)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes one square component block or the two square sub-blocks of a rectangular 4:2:2 transform section.
|
|||
/// </summary>
|
|||
/// <param name="reader">The active entropy-substream reader.</param>
|
|||
/// <param name="plane">The reconstructed component plane.</param>
|
|||
/// <param name="geometry">The component rectangle.</param>
|
|||
/// <param name="codedBlockFlags">The component coded-block flags.</param>
|
|||
/// <param name="transquantBypass">Whether the coding unit bypasses inverse quantization and transform.</param>
|
|||
/// <param name="regionId">The current independent-slice and tile prediction region.</param>
|
|||
/// <param name="colorPlaneIndex">The selected separate-color plane, or zero for combined coding.</param>
|
|||
/// <param name="quantizationParameters">The effective component quantization parameters.</param>
|
|||
/// <param name="lumaResidual">The current luma residual retained for cross-component prediction.</param>
|
|||
/// <param name="retainResidual">Whether reconstructed residuals are copied to <paramref name="lumaResidual"/>.</param>
|
|||
/// <param name="crossComponentAlpha">The signed inverse cross-component prediction scale.</param>
|
|||
/// <param name="lumaGeometry">The luma transform rectangle governing cross-component residual addressing.</param>
|
|||
private void DecodeComponentSections( |
|||
ref HevcCabacSyntaxReader reader, |
|||
HevcPlane plane, |
|||
in HevcTransformComponentGeometry geometry, |
|||
HevcCodedBlockFlags codedBlockFlags, |
|||
bool transquantBypass, |
|||
int regionId, |
|||
int colorPlaneIndex, |
|||
in HevcQuantizationParameters quantizationParameters, |
|||
Span<int> lumaResidual, |
|||
bool retainResidual, |
|||
int crossComponentAlpha, |
|||
in HevcTransformComponentGeometry lumaGeometry) |
|||
{ |
|||
if (!geometry.Process) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (geometry.Width == geometry.Height) |
|||
{ |
|||
this.DecodeComponentBlock( |
|||
ref reader, |
|||
plane, |
|||
geometry.X, |
|||
geometry.Y, |
|||
geometry.Width, |
|||
codedBlockFlags.First, |
|||
transquantBypass, |
|||
regionId, |
|||
colorPlaneIndex, |
|||
in quantizationParameters, |
|||
lumaResidual, |
|||
retainResidual, |
|||
crossComponentAlpha, |
|||
this.GetLumaResidualOffset(plane, geometry.X, geometry.Y, in lumaGeometry), |
|||
lumaGeometry.Width); |
|||
|
|||
return; |
|||
} |
|||
|
|||
int size = Math.Min(geometry.Width, geometry.Height); |
|||
int secondX = geometry.Width > geometry.Height ? geometry.X + size : geometry.X; |
|||
int secondY = geometry.Height > geometry.Width ? geometry.Y + size : geometry.Y; |
|||
this.DecodeComponentBlock( |
|||
ref reader, |
|||
plane, |
|||
geometry.X, |
|||
geometry.Y, |
|||
size, |
|||
codedBlockFlags.First, |
|||
transquantBypass, |
|||
regionId, |
|||
colorPlaneIndex, |
|||
in quantizationParameters, |
|||
lumaResidual, |
|||
retainResidual, |
|||
crossComponentAlpha, |
|||
this.GetLumaResidualOffset(plane, geometry.X, geometry.Y, in lumaGeometry), |
|||
lumaGeometry.Width); |
|||
|
|||
this.DecodeComponentBlock( |
|||
ref reader, |
|||
plane, |
|||
secondX, |
|||
secondY, |
|||
size, |
|||
codedBlockFlags.Second, |
|||
transquantBypass, |
|||
regionId, |
|||
colorPlaneIndex, |
|||
in quantizationParameters, |
|||
lumaResidual, |
|||
retainResidual, |
|||
crossComponentAlpha, |
|||
this.GetLumaResidualOffset(plane, secondX, secondY, in lumaGeometry), |
|||
lumaGeometry.Width); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes, predicts, and reconstructs one square transform block.
|
|||
/// </summary>
|
|||
/// <param name="reader">The active entropy-substream reader.</param>
|
|||
/// <param name="plane">The reconstructed component plane.</param>
|
|||
/// <param name="x">The block left coordinate in component samples.</param>
|
|||
/// <param name="y">The block top coordinate in component samples.</param>
|
|||
/// <param name="size">The square transform-block side.</param>
|
|||
/// <param name="codedBlockFlag">Whether coefficient syntax is present.</param>
|
|||
/// <param name="transquantBypass">Whether the coding unit bypasses inverse quantization and transform.</param>
|
|||
/// <param name="regionId">The current independent-slice and tile prediction region.</param>
|
|||
/// <param name="colorPlaneIndex">The selected separate-color plane, or zero for combined coding.</param>
|
|||
/// <param name="quantizationParameters">The effective component quantization parameters.</param>
|
|||
/// <param name="lumaResidual">The current luma residual retained for cross-component prediction.</param>
|
|||
/// <param name="retainResidual">Whether reconstructed residuals are copied to <paramref name="lumaResidual"/>.</param>
|
|||
/// <param name="crossComponentAlpha">The signed inverse cross-component prediction scale.</param>
|
|||
/// <param name="lumaResidualOffset">The first colocated sample in the retained luma residual.</param>
|
|||
/// <param name="lumaResidualStride">The retained luma residual row stride.</param>
|
|||
private void DecodeComponentBlock( |
|||
ref HevcCabacSyntaxReader reader, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int size, |
|||
bool codedBlockFlag, |
|||
bool transquantBypass, |
|||
int regionId, |
|||
int colorPlaneIndex, |
|||
in HevcQuantizationParameters quantizationParameters, |
|||
Span<int> lumaResidual, |
|||
bool retainResidual, |
|||
int crossComponentAlpha, |
|||
int lumaResidualOffset, |
|||
int lumaResidualStride) |
|||
{ |
|||
int log2Size = BitOperations.Log2((uint)size); |
|||
int sampleCount = size * size; |
|||
Span<int> integerScratch = this.integerScratch.Memory.Span; |
|||
Span<int> quantized = integerScratch[..MaximumTransformSampleCount]; |
|||
Span<int> dequantized = integerScratch.Slice(MaximumTransformSampleCount, MaximumTransformSampleCount); |
|||
Span<int> residual = integerScratch.Slice(MaximumTransformSampleCount * 2, MaximumTransformSampleCount); |
|||
Span<int> transformScratch = integerScratch.Slice(MaximumTransformSampleCount * 4, MaximumTransformSampleCount * 2); |
|||
Span<ushort> prediction = this.PredictComponentBlock(plane, x, y, log2Size, regionId, colorPlaneIndex, transquantBypass); |
|||
residual[..sampleCount].Clear(); |
|||
bool useLumaSyntax = this.sequenceParameterSet.SeparateColorPlaneFlag; |
|||
HevcPlane codingPlane = useLumaSyntax ? HevcPlane.Y : plane; |
|||
int lumaX = x << this.Picture.GetSubsamplingX(plane); |
|||
int lumaY = y << this.Picture.GetSubsamplingY(plane); |
|||
int codingPredictionMode = plane == HevcPlane.Y || useLumaSyntax |
|||
? this.intraPredictionStates[colorPlaneIndex].GetLumaMode(lumaX, lumaY) |
|||
: this.intraPredictionStates[colorPlaneIndex].GetEffectiveChromaMode(lumaX, lumaY); |
|||
|
|||
int predictionMode = codingPredictionMode; |
|||
if (plane != HevcPlane.Y && !useLumaSyntax && this.sequenceParameterSet.ChromaFormat == 2) |
|||
{ |
|||
predictionMode = HevcIntraPredictionMode.RemapChroma422(predictionMode); |
|||
} |
|||
|
|||
bool transformSkip = codedBlockFlag |
|||
&& !transquantBypass |
|||
&& this.pictureParameterSet.TransformSkipEnabled |
|||
&& log2Size <= this.pictureParameterSet.MaxTransformSkipBlockLog2 |
|||
&& reader.ReadTransformSkip(codingPlane != HevcPlane.Y); |
|||
|
|||
HevcResidualDpcmMode residualDpcmMode = this.sequenceParameterSet.ImplicitResidualDpcmEnabled && (transformSkip || transquantBypass) |
|||
? HevcResidualReconstructor.GetImplicitResidualDpcmMode(predictionMode, false) |
|||
: HevcResidualDpcmMode.None; |
|||
|
|||
if (codedBlockFlag) |
|||
{ |
|||
HevcCoefficientCodingParameters codingParameters = HevcCoefficientCodingParameters.Create( |
|||
this.pictureParameterSet, |
|||
size, |
|||
size, |
|||
plane, |
|||
true, |
|||
codingPredictionMode, |
|||
transformSkip, |
|||
transquantBypass, |
|||
residualDpcmMode, |
|||
useLumaSyntax); |
|||
|
|||
this.coefficientDecoder.Decode(ref reader, quantized, in codingParameters); |
|||
|
|||
bool rotate = HevcResidualReconstructor.IsNonTransformedResidualRotated( |
|||
this.sequenceParameterSet.TransformSkipRotationEnabled, |
|||
true, |
|||
size); |
|||
|
|||
if (transquantBypass) |
|||
{ |
|||
HevcResidualReconstructor.CopyBypassed(quantized[..sampleCount], residual, rotate); |
|||
} |
|||
else |
|||
{ |
|||
int bitDepth = this.Picture.GetBitDepth(plane); |
|||
int maxTransformDynamicRange = this.sequenceParameterSet.GetMaxTransformDynamicRange(codingPlane); |
|||
int quantizationParameter = useLumaSyntax |
|||
? quantizationParameters.Luma |
|||
: quantizationParameters.Get(plane); |
|||
|
|||
HevcInverseQuantizer.Dequantize( |
|||
quantized, |
|||
dequantized, |
|||
log2Size, |
|||
bitDepth, |
|||
maxTransformDynamicRange, |
|||
quantizationParameter, |
|||
this.sequenceParameterSet.ScalingListEnabled, |
|||
this.pictureParameterSet.ScalingList, |
|||
codingPlane, |
|||
true, |
|||
transformSkip, |
|||
this.sequenceParameterSet.ExtendedPrecisionProcessingEnabled); |
|||
|
|||
if (transformSkip) |
|||
{ |
|||
HevcResidualReconstructor.ApplyTransformSkip( |
|||
dequantized, |
|||
residual, |
|||
size, |
|||
size, |
|||
bitDepth, |
|||
maxTransformDynamicRange, |
|||
log2Size, |
|||
this.sequenceParameterSet.ExtendedPrecisionProcessingEnabled, |
|||
rotate); |
|||
} |
|||
else |
|||
{ |
|||
HevcInverseTransformer.Transform( |
|||
dequantized, |
|||
residual, |
|||
log2Size, |
|||
log2Size, |
|||
bitDepth, |
|||
maxTransformDynamicRange, |
|||
codingPlane == HevcPlane.Y && log2Size == 2, |
|||
transformScratch); |
|||
} |
|||
} |
|||
|
|||
HevcResidualReconstructor.ApplyResidualDpcm(residual, size, size, residualDpcmMode); |
|||
} |
|||
|
|||
if (crossComponentAlpha != 0) |
|||
{ |
|||
for (int row = 0; row < size; row++) |
|||
{ |
|||
HevcResidualReconstructor.ApplyCrossComponentPrediction( |
|||
lumaResidual.Slice(lumaResidualOffset + (row * lumaResidualStride), size), |
|||
residual.Slice(row * size, size), |
|||
size, |
|||
crossComponentAlpha, |
|||
this.sequenceParameterSet.BitDepthLuma - this.sequenceParameterSet.BitDepthChroma); |
|||
} |
|||
} |
|||
|
|||
if (retainResidual) |
|||
{ |
|||
for (int row = 0; row < size; row++) |
|||
{ |
|||
residual.Slice(row * size, size).CopyTo(lumaResidual.Slice(lumaResidualOffset + (row * lumaResidualStride), size)); |
|||
} |
|||
} |
|||
|
|||
HevcInverseTransformer.AddResidual( |
|||
residual, |
|||
prediction, |
|||
size, |
|||
size, |
|||
size, |
|||
this.Picture.GetBitDepth(plane)); |
|||
|
|||
this.CopyPredictionToPicture(prediction, plane, x, y, size); |
|||
this.reconstructionState.MarkReconstructed(plane, x, y, size, size, regionId); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the packed luma-residual offset colocated with one component block.
|
|||
/// </summary>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The component block left coordinate.</param>
|
|||
/// <param name="y">The component block top coordinate.</param>
|
|||
/// <param name="lumaGeometry">The governing luma transform rectangle.</param>
|
|||
/// <returns>The zero-based packed luma-residual offset.</returns>
|
|||
private int GetLumaResidualOffset(HevcPlane plane, int x, int y, in HevcTransformComponentGeometry lumaGeometry) |
|||
{ |
|||
int lumaX = x << this.Picture.GetSubsamplingX(plane); |
|||
int lumaY = y << this.Picture.GetSubsamplingY(plane); |
|||
return ((lumaY - lumaGeometry.Y) * lumaGeometry.Width) + lumaX - lumaGeometry.X; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the signed coding-unit luma quantization delta with bit-depth-dependent modular wrapping.
|
|||
/// </summary>
|
|||
/// <param name="delta">The decoded signed delta.</param>
|
|||
private void ApplyQuantizationParameterDelta(int delta) |
|||
{ |
|||
int bitDepthOffset = 6 * (this.sequenceParameterSet.BitDepthLuma - 8); |
|||
int modulus = 52 + bitDepthOffset; |
|||
int value = this.currentQuantizationParameter + delta + bitDepthOffset; |
|||
value %= modulus; |
|||
if (value < 0) |
|||
{ |
|||
value += modulus; |
|||
} |
|||
|
|||
this.currentQuantizationParameter = value - bitDepthOffset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates the component quantization parameters selected by picture, slice, and coding-unit offsets.
|
|||
/// </summary>
|
|||
/// <returns>The effective luma, Cb, and Cr quantization parameters.</returns>
|
|||
private HevcQuantizationParameters CreateQuantizationParameters() |
|||
{ |
|||
int cbOffset = this.pictureParameterSet.ChromaCbQuantizationParameterOffset + this.currentSliceChromaBlueQuantizationOffset; |
|||
int crOffset = this.pictureParameterSet.ChromaCrQuantizationParameterOffset + this.currentSliceChromaRedQuantizationOffset; |
|||
if (this.currentChromaQuantizationAdjustment > 0) |
|||
{ |
|||
int adjustmentIndex = this.currentChromaQuantizationAdjustment - 1; |
|||
cbOffset += this.pictureParameterSet.ChromaQuantizationParameterOffsetsCb[adjustmentIndex]; |
|||
crOffset += this.pictureParameterSet.ChromaQuantizationParameterOffsetsCr[adjustmentIndex]; |
|||
} |
|||
|
|||
return new HevcQuantizationParameters( |
|||
this.currentQuantizationParameter, |
|||
this.sequenceParameterSet.BitDepthLuma, |
|||
this.sequenceParameterSet.BitDepthChroma, |
|||
this.sequenceParameterSet.ChromaFormat, |
|||
cbOffset, |
|||
crOffset); |
|||
} |
|||
} |
|||
@ -1,409 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Implements slice, coding-tree, and coding-unit traversal.
|
|||
/// </content>
|
|||
internal sealed partial class HevcPictureDecoder |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes one ordered slice segment and returns the next tile-scan coding-tree-block address.
|
|||
/// </summary>
|
|||
/// <param name="slice">The current independent or dependent slice segment.</param>
|
|||
/// <param name="independentSlice">The independent header governing inherited slice fields.</param>
|
|||
/// <param name="independentSliceIndex">The one-based independent-slice index within the selected color plane.</param>
|
|||
/// <param name="tileLayout">The picture tile mapping.</param>
|
|||
/// <param name="startAddressInTileScan">The first coding-tree block in tile-scan order.</param>
|
|||
/// <param name="independentSliceStartAddressInTileScan">The governing independent slice's first coding-tree block in tile-scan order.</param>
|
|||
/// <returns>The tile-scan address immediately following the decoded segment.</returns>
|
|||
private int DecodeSliceSegment( |
|||
HevcSliceSegmentHeader slice, |
|||
HevcSliceSegmentHeader independentSlice, |
|||
int independentSliceIndex, |
|||
in HevcTileLayout tileLayout, |
|||
int startAddressInTileScan, |
|||
int independentSliceStartAddressInTileScan) |
|||
{ |
|||
int sliceQuantizationParameter = independentSlice.QuantizationParameter!.Value; |
|||
int colorPlaneIndex = this.sequenceParameterSet.SeparateColorPlaneFlag ? independentSlice.ColorPlaneId : 0; |
|||
this.lastCodedQuantizationParameter = sliceQuantizationParameter; |
|||
this.currentQuantizationParameter = sliceQuantizationParameter; |
|||
this.currentChromaQuantizationAdjustment = 0; |
|||
this.currentSliceChromaBlueQuantizationOffset = independentSlice.ChromaCbQuantizationParameterOffset; |
|||
this.currentSliceChromaRedQuantizationOffset = independentSlice.ChromaCrQuantizationParameterOffset; |
|||
this.quantizationParameterDeltaPending = this.pictureParameterSet.CodingUnitQuantizationParameterDeltaEnabled; |
|||
this.chromaQuantizationAdjustmentPending = independentSlice.ChromaQuantizationParameterOffsetListEnabled == true; |
|||
int substreamIndex = 0; |
|||
HevcCabacSyntaxReader reader = new(slice.GetEntropySubstream(substreamIndex).Span, sliceQuantizationParameter); |
|||
this.coefficientDecoder.ResetRiceAdaptation(); |
|||
|
|||
int contextOffset = colorPlaneIndex * HevcCabacContexts.ContextCount; |
|||
int riceOffset = colorPlaneIndex * 4; |
|||
int startRasterAddress = tileLayout.GetRasterAddress(startAddressInTileScan); |
|||
tileLayout.GetTilePosition( |
|||
startRasterAddress, |
|||
out int startTileIndex, |
|||
out int startColumnInTile, |
|||
out int startRowInTile, |
|||
out int startTileWidth, |
|||
out _); |
|||
|
|||
bool startsAtTileOrigin = startColumnInTile == 0 && startRowInTile == 0; |
|||
bool canInheritSliceSegmentContexts = !startsAtTileOrigin |
|||
&& (startTileWidth >= 2 || !this.pictureParameterSet.EntropyCodingSynchronizationEnabled); |
|||
|
|||
// A dependent segment normally resumes the preceding CABAC state. Tile origins and one-CTB-wide WPP
|
|||
// rows are initialization boundaries instead, matching the availability rules used by the reference decoder.
|
|||
if (slice.DependentSliceSegment |
|||
&& canInheritSliceSegmentContexts |
|||
&& this.hasSliceSegmentContexts[colorPlaneIndex]) |
|||
{ |
|||
reader.CopyContextsFrom(this.sliceSegmentContexts.AsSpan(contextOffset, HevcCabacContexts.ContextCount)); |
|||
this.coefficientDecoder.CopyRiceAdaptationFrom(this.sliceSegmentRiceAdaptation.AsSpan(riceOffset, 4)); |
|||
} |
|||
|
|||
if (!slice.DependentSliceSegment) |
|||
{ |
|||
// An independent slice starts a new prediction region, so an upper-right CTB from the preceding
|
|||
// independent slice cannot supply wavefront contexts to its first row.
|
|||
this.hasWavefrontContexts[colorPlaneIndex] = false; |
|||
} |
|||
|
|||
bool startsAtWavefrontRow = this.pictureParameterSet.EntropyCodingSynchronizationEnabled |
|||
&& startColumnInTile == 0 |
|||
&& startRowInTile > 0; |
|||
|
|||
if (startsAtWavefrontRow |
|||
&& startTileWidth > 1 |
|||
&& this.hasWavefrontContexts[colorPlaneIndex] |
|||
&& this.wavefrontContextTileIndices[colorPlaneIndex] == startTileIndex) |
|||
{ |
|||
// A dependent segment can begin exactly at a wavefront row boundary. Its first substream still uses
|
|||
// the upper-right state captured from the preceding row; no substream transition occurs inside this call.
|
|||
reader.CopyContextsFrom(this.wavefrontContexts.AsSpan(contextOffset, HevcCabacContexts.ContextCount)); |
|||
this.coefficientDecoder.CopyRiceAdaptationFrom(this.wavefrontRiceAdaptation.AsSpan(riceOffset, 4)); |
|||
} |
|||
|
|||
int codingTreeBlockSize = 1 << this.sequenceParameterSet.CodingTreeBlockLog2; |
|||
int tileScanAddress = startAddressInTileScan; |
|||
bool firstCodingTreeBlock = true; |
|||
while (tileScanAddress < tileLayout.Width * tileLayout.Height) |
|||
{ |
|||
int rasterAddress = tileLayout.GetRasterAddress(tileScanAddress); |
|||
tileLayout.GetTilePosition( |
|||
rasterAddress, |
|||
out int tileIndex, |
|||
out int columnInTile, |
|||
out int rowInTile, |
|||
out int tileWidth, |
|||
out int tileHeight); |
|||
|
|||
bool startsTile = columnInTile == 0 && rowInTile == 0; |
|||
bool startsWavefrontRow = this.pictureParameterSet.EntropyCodingSynchronizationEnabled && columnInTile == 0 && rowInTile > 0; |
|||
if (!firstCodingTreeBlock && (startsTile || startsWavefrontRow)) |
|||
{ |
|||
if (!reader.ReadTerminate()) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC entropy substream does not terminate at its tile or wavefront boundary."); |
|||
} |
|||
|
|||
reader.ValidateTerminationAlignment(); |
|||
substreamIndex++; |
|||
if (substreamIndex >= slice.EntropySubstreamCount) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice segment has too few entropy entry points."); |
|||
} |
|||
|
|||
reader = new HevcCabacSyntaxReader(slice.GetEntropySubstream(substreamIndex).Span, sliceQuantizationParameter); |
|||
this.coefficientDecoder.ResetRiceAdaptation(); |
|||
this.lastCodedQuantizationParameter = sliceQuantizationParameter; |
|||
if (startsWavefrontRow |
|||
&& tileWidth > 1 |
|||
&& this.hasWavefrontContexts[colorPlaneIndex] |
|||
&& this.wavefrontContextTileIndices[colorPlaneIndex] == tileIndex) |
|||
{ |
|||
reader.CopyContextsFrom(this.wavefrontContexts.AsSpan(contextOffset, HevcCabacContexts.ContextCount)); |
|||
this.coefficientDecoder.CopyRiceAdaptationFrom(this.wavefrontRiceAdaptation.AsSpan(riceOffset, 4)); |
|||
} |
|||
} |
|||
|
|||
int ctbX = rasterAddress % tileLayout.Width; |
|||
int ctbY = rasterAddress / tileLayout.Width; |
|||
int x = ctbX * codingTreeBlockSize; |
|||
int y = ctbY * codingTreeBlockSize; |
|||
int regionId = ((independentSliceIndex - 1) * tileLayout.TileCount) + tileIndex + 1; |
|||
HevcPlane regionPlane = this.sequenceParameterSet.SeparateColorPlaneFlag ? (HevcPlane)colorPlaneIndex : HevcPlane.Y; |
|||
HevcLoopFilterRegion loopFilterRegion = new( |
|||
independentSliceStartAddressInTileScan, |
|||
tileIndex, |
|||
independentSlice.LoopFilterAcrossSlicesEnabled == true, |
|||
independentSlice.DeblockingFilterDisabled == true, |
|||
independentSlice.DeblockingFilterBetaOffsetDiv2, |
|||
independentSlice.DeblockingFilterTcOffsetDiv2); |
|||
|
|||
this.sampleAdaptiveOffsetState.SetLoopFilterRegion(rasterAddress, regionPlane, loopFilterRegion); |
|||
this.DecodeSampleAdaptiveOffset(ref reader, independentSlice, rasterAddress, ctbX, ctbY, regionId); |
|||
this.DecodeCodingTree( |
|||
ref reader, |
|||
x, |
|||
y, |
|||
this.sequenceParameterSet.CodingTreeBlockLog2, |
|||
0, |
|||
regionId, |
|||
colorPlaneIndex); |
|||
|
|||
// HEVC places end_of_slice_segment_flag after the final coding unit of each complete CTB. Reading it
|
|||
// inside the recursive leaf traversal consumes coefficient data whenever a CTB contains multiple CUs.
|
|||
bool endOfSliceSegment = reader.ReadTerminate(); |
|||
|
|||
// Wavefront synchronization copies probability and persistent Rice state after the second CTB of each
|
|||
// row. The next row starts with those contexts but a newly initialized arithmetic register.
|
|||
if (this.pictureParameterSet.EntropyCodingSynchronizationEnabled && columnInTile == 1) |
|||
{ |
|||
reader.CopyContextsTo(this.wavefrontContexts.AsSpan(contextOffset, HevcCabacContexts.ContextCount)); |
|||
this.coefficientDecoder.CopyRiceAdaptationTo(this.wavefrontRiceAdaptation.AsSpan(riceOffset, 4)); |
|||
this.hasWavefrontContexts[colorPlaneIndex] = true; |
|||
this.wavefrontContextTileIndices[colorPlaneIndex] = tileIndex; |
|||
} |
|||
|
|||
tileScanAddress++; |
|||
firstCodingTreeBlock = false; |
|||
if (endOfSliceSegment) |
|||
{ |
|||
reader.ValidateTerminationAlignment(); |
|||
if (substreamIndex + 1 != slice.EntropySubstreamCount) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice segment has unused entropy entry points."); |
|||
} |
|||
|
|||
reader.CopyContextsTo(this.sliceSegmentContexts.AsSpan(contextOffset, HevcCabacContexts.ContextCount)); |
|||
this.coefficientDecoder.CopyRiceAdaptationTo(this.sliceSegmentRiceAdaptation.AsSpan(riceOffset, 4)); |
|||
this.hasSliceSegmentContexts[colorPlaneIndex] = true; |
|||
return tileScanAddress; |
|||
} |
|||
|
|||
bool atTileEnd = columnInTile == tileWidth - 1 && rowInTile == tileHeight - 1; |
|||
bool atWavefrontRowEnd = this.pictureParameterSet.EntropyCodingSynchronizationEnabled && columnInTile == tileWidth - 1; |
|||
if (atTileEnd || atWavefrontRowEnd) |
|||
{ |
|||
// A non-final tile or wavefront row has a second terminating bin after the coding-unit end flag.
|
|||
// It is consumed when the following loop iteration opens the next bounded entropy substream.
|
|||
continue; |
|||
} |
|||
} |
|||
|
|||
throw new InvalidImageContentException("The HEVC slice segment reaches the picture boundary without termination."); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes one coding-tree node in depth-first Z order.
|
|||
/// </summary>
|
|||
/// <param name="reader">The active entropy-substream reader.</param>
|
|||
/// <param name="x">The coding-node left luma coordinate.</param>
|
|||
/// <param name="y">The coding-node top luma coordinate.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the coding-node side.</param>
|
|||
/// <param name="depth">The coding-tree depth below the coding-tree-block root.</param>
|
|||
/// <param name="regionId">The current independent-slice and tile prediction region.</param>
|
|||
/// <param name="colorPlaneIndex">The selected separate-color plane, or zero for combined coding.</param>
|
|||
private void DecodeCodingTree( |
|||
ref HevcCabacSyntaxReader reader, |
|||
int x, |
|||
int y, |
|||
int log2Size, |
|||
int depth, |
|||
int regionId, |
|||
int colorPlaneIndex) |
|||
{ |
|||
int size = 1 << log2Size; |
|||
bool crossesPictureBoundary = x + size > this.sequenceParameterSet.Width || y + size > this.sequenceParameterSet.Height; |
|||
bool canSplit = log2Size > this.sequenceParameterSet.MinCodingBlockLog2; |
|||
HevcCodingTreeState codingTreeState = this.codingTreeStates[colorPlaneIndex]; |
|||
bool split = false; |
|||
if (canSplit) |
|||
{ |
|||
if (crossesPictureBoundary) |
|||
{ |
|||
split = true; |
|||
} |
|||
else |
|||
{ |
|||
bool leftAvailable = this.reconstructionState.IsReconstructed((HevcPlane)colorPlaneIndex, x - 1, y, regionId); |
|||
bool aboveAvailable = this.reconstructionState.IsReconstructed((HevcPlane)colorPlaneIndex, x, y - 1, regionId); |
|||
int context = codingTreeState.GetSplitContext(x, y, depth, leftAvailable, aboveAvailable); |
|||
split = reader.ReadSplit(context); |
|||
} |
|||
} |
|||
|
|||
bool startsQuantizationGroup = depth == this.pictureParameterSet.QuantizationParameterDeltaDepth |
|||
|| (!split && depth < this.pictureParameterSet.QuantizationParameterDeltaDepth); |
|||
if (startsQuantizationGroup && this.pictureParameterSet.CodingUnitQuantizationParameterDeltaEnabled) |
|||
{ |
|||
// A leaf above the configured QG depth owns one complete quantization group. Waiting for the configured
|
|||
// depth would carry the preceding group's coded-delta state into this coding unit and skip required syntax.
|
|||
this.BeginQuantizationGroup(x, y, regionId, colorPlaneIndex); |
|||
} |
|||
|
|||
bool startsChromaQuantizationGroup = depth == this.pictureParameterSet.ChromaQuantizationParameterOffsetDepth |
|||
|| (!split && depth < this.pictureParameterSet.ChromaQuantizationParameterOffsetDepth); |
|||
if (startsChromaQuantizationGroup && this.pictureParameterSet.ChromaQuantizationParameterOffsetsCb.Count != 0) |
|||
{ |
|||
this.currentChromaQuantizationAdjustment = 0; |
|||
this.chromaQuantizationAdjustmentPending = true; |
|||
} |
|||
|
|||
if (split) |
|||
{ |
|||
int childLog2Size = log2Size - 1; |
|||
int childSize = 1 << childLog2Size; |
|||
for (int child = 0; child < 4; child++) |
|||
{ |
|||
int childX = x + ((child & 1) * childSize); |
|||
int childY = y + ((child >> 1) * childSize); |
|||
if (childX >= this.sequenceParameterSet.Width || childY >= this.sequenceParameterSet.Height) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
this.DecodeCodingTree( |
|||
ref reader, |
|||
childX, |
|||
childY, |
|||
childLog2Size, |
|||
depth + 1, |
|||
regionId, |
|||
colorPlaneIndex); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
this.DecodeCodingUnit(ref reader, x, y, log2Size, depth, regionId, colorPlaneIndex); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decodes and reconstructs one intra-coded leaf coding unit.
|
|||
/// </summary>
|
|||
/// <param name="reader">The active entropy-substream reader.</param>
|
|||
/// <param name="x">The coding-unit left luma coordinate.</param>
|
|||
/// <param name="y">The coding-unit top luma coordinate.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the coding-unit side.</param>
|
|||
/// <param name="depth">The coding-tree depth.</param>
|
|||
/// <param name="regionId">The current independent-slice and tile prediction region.</param>
|
|||
/// <param name="colorPlaneIndex">The selected separate-color plane, or zero for combined coding.</param>
|
|||
private void DecodeCodingUnit( |
|||
ref HevcCabacSyntaxReader reader, |
|||
int x, |
|||
int y, |
|||
int log2Size, |
|||
int depth, |
|||
int regionId, |
|||
int colorPlaneIndex) |
|||
{ |
|||
bool transquantBypass = this.pictureParameterSet.TransquantizationBypassEnabled && reader.ReadTransquantBypass(); |
|||
bool usesNxNPartitions = reader.ReadIntraNxNPartition(log2Size == this.sequenceParameterSet.MinCodingBlockLog2); |
|||
HevcPlane primaryPlane = this.sequenceParameterSet.SeparateColorPlaneFlag ? (HevcPlane)colorPlaneIndex : HevcPlane.Y; |
|||
bool pcm = this.sequenceParameterSet.PcmEnabled |
|||
&& !usesNxNPartitions |
|||
&& log2Size >= this.sequenceParameterSet.MinPcmCodingBlockLog2 |
|||
&& log2Size <= this.sequenceParameterSet.MaxPcmCodingBlockLog2 |
|||
&& reader.ReadPcmFlag(); |
|||
|
|||
if (pcm) |
|||
{ |
|||
int size = 1 << log2Size; |
|||
this.deblockingState.MarkBlock(primaryPlane, x, y, size, size); |
|||
this.DecodePcmCodingUnit(ref reader, x, y, log2Size, regionId, colorPlaneIndex); |
|||
reader.RestartAfterPcm(); |
|||
} |
|||
else |
|||
{ |
|||
HevcIntraPredictionState predictionState = this.intraPredictionStates[colorPlaneIndex]; |
|||
HevcPlane boundaryPlane = this.sequenceParameterSet.SeparateColorPlaneFlag ? (HevcPlane)colorPlaneIndex : HevcPlane.Y; |
|||
bool leftAvailable = this.reconstructionState.IsReconstructed(boundaryPlane, x - 1, y, regionId); |
|||
bool aboveAvailable = this.reconstructionState.IsReconstructed(boundaryPlane, x, y - 1, regionId); |
|||
predictionState.DecodeLumaModes(ref reader, x, y, log2Size, usesNxNPartitions, leftAvailable, aboveAvailable); |
|||
if (this.sequenceParameterSet.ChromaFormat != 0 && !this.sequenceParameterSet.SeparateColorPlaneFlag) |
|||
{ |
|||
predictionState.DecodeChromaModes(ref reader, x, y, log2Size, usesNxNPartitions); |
|||
} |
|||
|
|||
int minimumTransformLog2 = GetMinimumTransformLog2Size(this.sequenceParameterSet, log2Size, usesNxNPartitions); |
|||
HevcTransformUnitGeometry geometry = HevcTransformUnitGeometry.CreateRoot( |
|||
x, |
|||
y, |
|||
log2Size, |
|||
this.sequenceParameterSet.ChromaFormat, |
|||
this.sequenceParameterSet.SeparateColorPlaneFlag, |
|||
colorPlaneIndex); |
|||
|
|||
this.DecodeTransformTree( |
|||
ref reader, |
|||
in geometry, |
|||
0, |
|||
minimumTransformLog2, |
|||
usesNxNPartitions, |
|||
transquantBypass, |
|||
regionId, |
|||
colorPlaneIndex, |
|||
default, |
|||
default); |
|||
} |
|||
|
|||
this.codingTreeStates[colorPlaneIndex].SetCodingUnit( |
|||
x, |
|||
y, |
|||
log2Size, |
|||
depth, |
|||
this.currentQuantizationParameter, |
|||
transquantBypass, |
|||
pcm); |
|||
|
|||
this.lastCodedQuantizationParameter = this.currentQuantizationParameter; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Begins one luma quantization group using available spatial predictors.
|
|||
/// </summary>
|
|||
/// <param name="x">The quantization-group left luma coordinate.</param>
|
|||
/// <param name="y">The quantization-group top luma coordinate.</param>
|
|||
/// <param name="regionId">The current independent-slice and tile prediction region.</param>
|
|||
/// <param name="colorPlaneIndex">The selected separate-color plane, or zero for combined coding.</param>
|
|||
private void BeginQuantizationGroup(int x, int y, int regionId, int colorPlaneIndex) |
|||
{ |
|||
HevcPlane plane = this.sequenceParameterSet.SeparateColorPlaneFlag ? (HevcPlane)colorPlaneIndex : HevcPlane.Y; |
|||
int codingTreeBlockMask = (1 << this.sequenceParameterSet.CodingTreeBlockLog2) - 1; |
|||
|
|||
// QP prediction neighbours are confined to the current CTB. This differs from intra sample availability,
|
|||
// which may legitimately use reconstructed samples across the same left or upper CTB boundary.
|
|||
bool leftAvailable = (x & codingTreeBlockMask) != 0 && this.reconstructionState.IsReconstructed(plane, x - 1, y, regionId); |
|||
bool aboveAvailable = (y & codingTreeBlockMask) != 0 && this.reconstructionState.IsReconstructed(plane, x, y - 1, regionId); |
|||
int fallback = this.lastCodedQuantizationParameter; |
|||
HevcCodingTreeState codingTreeState = this.codingTreeStates[colorPlaneIndex]; |
|||
int left = leftAvailable ? codingTreeState.GetQuantizationParameter(x - 1, y) : fallback; |
|||
int above = aboveAvailable ? codingTreeState.GetQuantizationParameter(x, y - 1) : fallback; |
|||
this.currentQuantizationParameter = (left + above + 1) >> 1; |
|||
this.quantizationParameterDeltaPending = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Derives the smallest luma transform permitted within one intra coding unit.
|
|||
/// </summary>
|
|||
/// <param name="sequenceParameterSet">The transform hierarchy limits.</param>
|
|||
/// <param name="codingUnitLog2Size">The base-two logarithm of the coding-unit side.</param>
|
|||
/// <param name="usesNxNPartitions">Whether the coding unit has four luma prediction partitions.</param>
|
|||
/// <returns>The minimum luma transform side as a base-two logarithm.</returns>
|
|||
private static int GetMinimumTransformLog2Size( |
|||
HevcSequenceParameterSet sequenceParameterSet, |
|||
int codingUnitLog2Size, |
|||
bool usesNxNPartitions) |
|||
{ |
|||
int hierarchyReduction = sequenceParameterSet.MaxTransformHierarchyDepthIntra - 1 + (usesNxNPartitions ? 1 : 0); |
|||
int minimum = codingUnitLog2Size < sequenceParameterSet.MinTransformBlockLog2 + hierarchyReduction |
|||
? sequenceParameterSet.MinTransformBlockLog2 |
|||
: codingUnitLog2Size - hierarchyReduction; |
|||
|
|||
return Math.Min(minimum, sequenceParameterSet.MaxTransformBlockLog2); |
|||
} |
|||
} |
|||
@ -1,363 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Owns the bounded state used to reconstruct one independently decodable HEVC still picture.
|
|||
/// </summary>
|
|||
internal sealed partial class HevcPictureDecoder : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The maximum square transform-block sample count.
|
|||
/// </summary>
|
|||
private const int MaximumTransformSampleCount = 32 * 32; |
|||
|
|||
/// <summary>
|
|||
/// The largest reference array used by a thirty-two-sample prediction block.
|
|||
/// </summary>
|
|||
private const int MaximumReferenceLength = (2 * 32) + 1; |
|||
|
|||
/// <summary>
|
|||
/// The configuration providing picture-lifetime allocations.
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// The active picture parameters.
|
|||
/// </summary>
|
|||
private readonly HevcPictureParameterSet pictureParameterSet; |
|||
|
|||
/// <summary>
|
|||
/// The active sequence parameters.
|
|||
/// </summary>
|
|||
private readonly HevcSequenceParameterSet sequenceParameterSet; |
|||
|
|||
/// <summary>
|
|||
/// The decoded coding-unit state.
|
|||
/// </summary>
|
|||
private readonly HevcCodingTreeState[] codingTreeStates; |
|||
|
|||
/// <summary>
|
|||
/// The decoded intra-prediction modes.
|
|||
/// </summary>
|
|||
private readonly HevcIntraPredictionState[] intraPredictionStates; |
|||
|
|||
/// <summary>
|
|||
/// The completed prediction-block state used for reference availability.
|
|||
/// </summary>
|
|||
private readonly HevcReconstructionState reconstructionState; |
|||
|
|||
/// <summary>
|
|||
/// The reusable coefficient entropy decoder.
|
|||
/// </summary>
|
|||
private readonly HevcCoefficientDecoder coefficientDecoder; |
|||
|
|||
/// <summary>
|
|||
/// The resolved sample-adaptive-offset parameters for every coding-tree block.
|
|||
/// </summary>
|
|||
private readonly HevcSampleAdaptiveOffsetState sampleAdaptiveOffsetState; |
|||
|
|||
/// <summary>
|
|||
/// The transform and prediction boundaries required by the deblocking stage.
|
|||
/// </summary>
|
|||
private readonly HevcDeblockingState deblockingState; |
|||
|
|||
/// <summary>
|
|||
/// The integer coefficient, residual, and transform workspace.
|
|||
/// </summary>
|
|||
private readonly IMemoryOwner<int> integerScratch; |
|||
|
|||
/// <summary>
|
|||
/// The prediction, reference, and reference-substitution workspace.
|
|||
/// </summary>
|
|||
private readonly IMemoryOwner<ushort> predictionScratch; |
|||
|
|||
/// <summary>
|
|||
/// The ordered intra-reference availability workspace.
|
|||
/// </summary>
|
|||
private readonly IMemoryOwner<bool> availabilityScratch; |
|||
|
|||
/// <summary>
|
|||
/// The per-color-plane adaptive contexts captured after the second coding-tree block of a wavefront row.
|
|||
/// </summary>
|
|||
private readonly HevcCabacContext[] wavefrontContexts = new HevcCabacContext[HevcCabacContexts.ContextCount * 3]; |
|||
|
|||
/// <summary>
|
|||
/// The per-color-plane persistent Rice statistics captured with the wavefront probability contexts.
|
|||
/// </summary>
|
|||
private readonly int[] wavefrontRiceAdaptation = new int[12]; |
|||
|
|||
/// <summary>
|
|||
/// Whether retained wavefront contexts are available for each color plane.
|
|||
/// </summary>
|
|||
private InlineArray4<bool> hasWavefrontContexts; |
|||
|
|||
/// <summary>
|
|||
/// The tile that owns each color plane's retained wavefront contexts.
|
|||
/// </summary>
|
|||
private InlineArray4<int> wavefrontContextTileIndices; |
|||
|
|||
/// <summary>
|
|||
/// The adaptive contexts retained at the end of a dependent-slice prediction region.
|
|||
/// </summary>
|
|||
private readonly HevcCabacContext[] sliceSegmentContexts = new HevcCabacContext[HevcCabacContexts.ContextCount * 3]; |
|||
|
|||
/// <summary>
|
|||
/// The persistent Rice statistics retained with dependent-slice probability contexts.
|
|||
/// </summary>
|
|||
private readonly int[] sliceSegmentRiceAdaptation = new int[12]; |
|||
|
|||
/// <summary>
|
|||
/// Whether retained dependent-slice contexts are available.
|
|||
/// </summary>
|
|||
private InlineArray4<bool> hasSliceSegmentContexts; |
|||
|
|||
/// <summary>
|
|||
/// The luma quantization parameter most recently coded in the current prediction region.
|
|||
/// </summary>
|
|||
private int lastCodedQuantizationParameter; |
|||
|
|||
/// <summary>
|
|||
/// The effective luma quantization parameter of the current quantization group.
|
|||
/// </summary>
|
|||
private int currentQuantizationParameter; |
|||
|
|||
/// <summary>
|
|||
/// The one-based chroma quantization-offset-list selector of the current quantization group.
|
|||
/// </summary>
|
|||
private int currentChromaQuantizationAdjustment; |
|||
|
|||
/// <summary>
|
|||
/// The Cb quantization-parameter offset signaled by the governing independent slice.
|
|||
/// </summary>
|
|||
private int currentSliceChromaBlueQuantizationOffset; |
|||
|
|||
/// <summary>
|
|||
/// The Cr quantization-parameter offset signaled by the governing independent slice.
|
|||
/// </summary>
|
|||
private int currentSliceChromaRedQuantizationOffset; |
|||
|
|||
/// <summary>
|
|||
/// Whether the current quantization group can still signal its luma delta.
|
|||
/// </summary>
|
|||
private bool quantizationParameterDeltaPending; |
|||
|
|||
/// <summary>
|
|||
/// Whether the current quantization group can still signal its chroma adjustment.
|
|||
/// </summary>
|
|||
private bool chromaQuantizationAdjustmentPending; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcPictureDecoder"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration providing all decoder-owned memory.</param>
|
|||
/// <param name="pictureParameterSet">The picture parameters governing the coded still image.</param>
|
|||
public HevcPictureDecoder(Configuration configuration, HevcPictureParameterSet pictureParameterSet) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.pictureParameterSet = pictureParameterSet; |
|||
this.sequenceParameterSet = pictureParameterSet.SequenceParameterSet; |
|||
HevcPictureBuffer? picture = null; |
|||
HevcCodingTreeState[]? codingTreeStates = null; |
|||
HevcIntraPredictionState[]? intraPredictionStates = null; |
|||
HevcReconstructionState? reconstructionState = null; |
|||
HevcCoefficientDecoder? coefficientDecoder = null; |
|||
HevcSampleAdaptiveOffsetState? sampleAdaptiveOffsetState = null; |
|||
HevcDeblockingState? deblockingState = null; |
|||
IMemoryOwner<int>? integerScratch = null; |
|||
IMemoryOwner<ushort>? predictionScratch = null; |
|||
IMemoryOwner<bool>? availabilityScratch = null; |
|||
try |
|||
{ |
|||
picture = new HevcPictureBuffer(configuration, this.sequenceParameterSet); |
|||
int codingTreeStateCount = this.sequenceParameterSet.SeparateColorPlaneFlag ? 3 : 1; |
|||
codingTreeStates = new HevcCodingTreeState[codingTreeStateCount]; |
|||
for (int index = 0; index < codingTreeStates.Length; index++) |
|||
{ |
|||
codingTreeStates[index] = new HevcCodingTreeState(configuration, this.sequenceParameterSet); |
|||
} |
|||
|
|||
int intraPredictionStateCount = this.sequenceParameterSet.SeparateColorPlaneFlag ? 3 : 1; |
|||
intraPredictionStates = new HevcIntraPredictionState[intraPredictionStateCount]; |
|||
for (int index = 0; index < intraPredictionStates.Length; index++) |
|||
{ |
|||
intraPredictionStates[index] = new HevcIntraPredictionState(configuration, this.sequenceParameterSet); |
|||
} |
|||
|
|||
reconstructionState = new HevcReconstructionState(configuration, this.sequenceParameterSet); |
|||
coefficientDecoder = new HevcCoefficientDecoder(configuration); |
|||
int codingTreeBlockCount = HevcParameterSetSyntax.GetCodingTreeBlockCount( |
|||
this.sequenceParameterSet.Width, |
|||
this.sequenceParameterSet.CodingTreeBlockLog2) |
|||
* HevcParameterSetSyntax.GetCodingTreeBlockCount( |
|||
this.sequenceParameterSet.Height, |
|||
this.sequenceParameterSet.CodingTreeBlockLog2); |
|||
|
|||
sampleAdaptiveOffsetState = new HevcSampleAdaptiveOffsetState(configuration, codingTreeBlockCount); |
|||
deblockingState = new HevcDeblockingState(configuration, this.sequenceParameterSet); |
|||
|
|||
// Six transform-sized integer regions retain quantized, dequantized, reconstructed, cross-component, and
|
|||
// two-pass inverse-transform data without allocating in coding-unit or transform-unit loops.
|
|||
integerScratch = configuration.MemoryAllocator.Allocate<int>(MaximumTransformSampleCount * 6); |
|||
int maximumPredictionScratch = HevcIntraPredictor.GetScratchLength(5); |
|||
int maximumReferenceScratch = HevcIntraPredictor.GetReferenceScratchLength(5, 4); |
|||
predictionScratch = configuration.MemoryAllocator.Allocate<ushort>( |
|||
MaximumTransformSampleCount + maximumPredictionScratch + maximumReferenceScratch + (MaximumReferenceLength * 4)); |
|||
|
|||
availabilityScratch = configuration.MemoryAllocator.Allocate<bool>((4 * 32 / 2) + 1); |
|||
|
|||
this.Picture = picture; |
|||
this.codingTreeStates = codingTreeStates; |
|||
this.intraPredictionStates = intraPredictionStates; |
|||
this.reconstructionState = reconstructionState; |
|||
this.coefficientDecoder = coefficientDecoder; |
|||
this.sampleAdaptiveOffsetState = sampleAdaptiveOffsetState; |
|||
this.deblockingState = deblockingState; |
|||
this.integerScratch = integerScratch; |
|||
this.predictionScratch = predictionScratch; |
|||
this.availabilityScratch = availabilityScratch; |
|||
} |
|||
catch |
|||
{ |
|||
// No decoder ownership is published when construction fails. Unwind every completed child owner in reverse
|
|||
// order because the caller cannot dispose an object whose constructor did not return.
|
|||
availabilityScratch?.Dispose(); |
|||
predictionScratch?.Dispose(); |
|||
integerScratch?.Dispose(); |
|||
deblockingState?.Dispose(); |
|||
sampleAdaptiveOffsetState?.Dispose(); |
|||
coefficientDecoder?.Dispose(); |
|||
reconstructionState?.Dispose(); |
|||
if (intraPredictionStates is not null) |
|||
{ |
|||
for (int index = intraPredictionStates.Length - 1; index >= 0; index--) |
|||
{ |
|||
intraPredictionStates[index]?.Dispose(); |
|||
} |
|||
} |
|||
|
|||
if (codingTreeStates is not null) |
|||
{ |
|||
for (int index = codingTreeStates.Length - 1; index >= 0; index--) |
|||
{ |
|||
codingTreeStates[index]?.Dispose(); |
|||
} |
|||
} |
|||
|
|||
picture?.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the native-precision reconstructed component planes.
|
|||
/// </summary>
|
|||
public HevcPictureBuffer Picture { get; } |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs every ordered slice segment in one independently decodable image item.
|
|||
/// </summary>
|
|||
/// <param name="bitstream">The validated image-item NAL units and slice segments.</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// A slice changes the coded picture parameters, overlaps an earlier segment, or does not terminate at a valid
|
|||
/// coding-tree boundary.
|
|||
/// </exception>
|
|||
public void Decode(HevcImageItemBitstream bitstream) |
|||
{ |
|||
HevcTileLayout tileLayout = new(this.pictureParameterSet); |
|||
int planeCount = this.sequenceParameterSet.SeparateColorPlaneFlag ? 3 : 1; |
|||
int[] nextCodingTreeBlockAddressesInTileScan = new int[planeCount]; |
|||
int[] independentSliceIndices = new int[planeCount]; |
|||
HevcSliceSegmentHeader?[] independentSlices = new HevcSliceSegmentHeader?[planeCount]; |
|||
for (int sliceIndex = 0; sliceIndex < bitstream.SliceSegments.Count; sliceIndex++) |
|||
{ |
|||
HevcSliceSegmentHeader slice = bitstream.SliceSegments[sliceIndex]; |
|||
int colorPlane = this.sequenceParameterSet.SeparateColorPlaneFlag ? slice.ColorPlaneId : 0; |
|||
if (slice.PictureParameterSet.Id != this.pictureParameterSet.Id |
|||
|| slice.PictureParameterSet.SequenceParameterSetId != this.pictureParameterSet.SequenceParameterSetId) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC still picture changes parameter sets between slice segments."); |
|||
} |
|||
|
|||
if (!slice.DependentSliceSegment) |
|||
{ |
|||
independentSlices[colorPlane] = slice; |
|||
independentSliceIndices[colorPlane]++; |
|||
} |
|||
|
|||
HevcSliceSegmentHeader? independentSlice = independentSlices[colorPlane]; |
|||
if (independentSlice is null) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC still picture begins with a dependent slice segment."); |
|||
} |
|||
|
|||
int sliceStartAddressInTileScan = tileLayout.GetTileScanAddress(slice.SliceSegmentAddress); |
|||
if (sliceStartAddressInTileScan != nextCodingTreeBlockAddressesInTileScan[colorPlane]) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice segments do not cover the coded picture in order."); |
|||
} |
|||
|
|||
nextCodingTreeBlockAddressesInTileScan[colorPlane] = this.DecodeSliceSegment( |
|||
slice, |
|||
independentSlice, |
|||
independentSliceIndices[colorPlane], |
|||
in tileLayout, |
|||
sliceStartAddressInTileScan, |
|||
tileLayout.GetTileScanAddress(independentSlice.SliceSegmentAddress)); |
|||
} |
|||
|
|||
int codingTreeBlockCount = HevcParameterSetSyntax.GetCodingTreeBlockCount( |
|||
this.sequenceParameterSet.Width, |
|||
this.sequenceParameterSet.CodingTreeBlockLog2) |
|||
* HevcParameterSetSyntax.GetCodingTreeBlockCount( |
|||
this.sequenceParameterSet.Height, |
|||
this.sequenceParameterSet.CodingTreeBlockLog2); |
|||
|
|||
foreach (int nextAddress in nextCodingTreeBlockAddressesInTileScan) |
|||
{ |
|||
if (nextAddress != codingTreeBlockCount) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice segments do not reconstruct the complete coded picture."); |
|||
} |
|||
} |
|||
|
|||
this.ApplyDeblockingFilter(in tileLayout); |
|||
if (this.sampleAdaptiveOffsetState.HasEnabledParameters) |
|||
{ |
|||
// SAO classification always observes the complete post-deblocking picture, never samples already offset by an
|
|||
// earlier CTB. One picture-lifetime snapshot provides that invariant without row allocations or filter-order coupling.
|
|||
using HevcPictureBuffer sampleAdaptiveOffsetSource = new(this.configuration, this.sequenceParameterSet); |
|||
this.Picture.CopyTo(sampleAdaptiveOffsetSource); |
|||
this.ApplySampleAdaptiveOffset(sampleAdaptiveOffsetSource, in tileLayout); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Releases all current-picture state and reconstructed planes.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.availabilityScratch.Dispose(); |
|||
this.predictionScratch.Dispose(); |
|||
this.integerScratch.Dispose(); |
|||
this.deblockingState.Dispose(); |
|||
this.sampleAdaptiveOffsetState.Dispose(); |
|||
this.coefficientDecoder.Dispose(); |
|||
this.reconstructionState.Dispose(); |
|||
foreach (HevcIntraPredictionState state in this.intraPredictionStates) |
|||
{ |
|||
state.Dispose(); |
|||
} |
|||
|
|||
foreach (HevcCodingTreeState state in this.codingTreeStates) |
|||
{ |
|||
state.Dispose(); |
|||
} |
|||
|
|||
this.Picture.Dispose(); |
|||
} |
|||
} |
|||
@ -1,558 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the HEVC picture fields required to decode the independently coded picture in one still-image item.
|
|||
/// </summary>
|
|||
internal sealed class HevcPictureParameterSet |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcPictureParameterSet"/> class.
|
|||
/// </summary>
|
|||
/// <param name="nalUnit">The decoded picture-parameter-set NAL unit.</param>
|
|||
/// <param name="sequenceParameterSets">The sequence parameter sets available to the coded image item.</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The picture parameter set is malformed, references an unavailable sequence parameter set, or declares
|
|||
/// picture geometry outside that sequence parameter set.
|
|||
/// </exception>
|
|||
public HevcPictureParameterSet( |
|||
HevcNalUnit nalUnit, |
|||
IReadOnlyList<HevcSequenceParameterSet> sequenceParameterSets) |
|||
{ |
|||
const byte pictureParameterSetNalUnitType = 34; |
|||
if (nalUnit.Header.NalUnitType != pictureParameterSetNalUnitType |
|||
|| nalUnit.Header.LayerId != 0 |
|||
|| nalUnit.Header.TemporalId != 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid NAL-unit header."); |
|||
} |
|||
|
|||
HevcBitReader reader = new(nalUnit.Rbsp.Span); |
|||
uint pictureParameterSetId = reader.ReadUnsignedExpGolomb(); |
|||
uint sequenceParameterSetId = reader.ReadUnsignedExpGolomb(); |
|||
if (pictureParameterSetId > 63 || sequenceParameterSetId > 15) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid identifier."); |
|||
} |
|||
|
|||
this.Id = (byte)pictureParameterSetId; |
|||
this.SequenceParameterSetId = (byte)sequenceParameterSetId; |
|||
|
|||
HevcSequenceParameterSet? sequenceParameterSet = null; |
|||
foreach (HevcSequenceParameterSet candidate in sequenceParameterSets) |
|||
{ |
|||
if (candidate.Id == this.SequenceParameterSetId) |
|||
{ |
|||
sequenceParameterSet = candidate; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (sequenceParameterSet is null) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set references an unavailable sequence parameter set."); |
|||
} |
|||
|
|||
this.SequenceParameterSet = sequenceParameterSet; |
|||
this.DependentSliceSegmentsEnabled = reader.ReadFlag(); |
|||
this.OutputFlagPresent = reader.ReadFlag(); |
|||
this.ExtraSliceHeaderBitCount = (int)reader.ReadBits(3); |
|||
this.SignDataHidingEnabled = reader.ReadFlag(); |
|||
this.CabacInitializationPresent = reader.ReadFlag(); |
|||
|
|||
uint defaultReferenceIndexCountList0MinusOne = reader.ReadUnsignedExpGolomb(); |
|||
uint defaultReferenceIndexCountList1MinusOne = reader.ReadUnsignedExpGolomb(); |
|||
if (defaultReferenceIndexCountList0MinusOne > 14 || defaultReferenceIndexCountList1MinusOne > 14) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set declares too many default reference indices."); |
|||
} |
|||
|
|||
this.DefaultReferenceIndexCountList0 = (int)defaultReferenceIndexCountList0MinusOne + 1; |
|||
this.DefaultReferenceIndexCountList1 = (int)defaultReferenceIndexCountList1MinusOne + 1; |
|||
|
|||
this.InitialQuantizationParameterMinus26 = reader.ReadSignedExpGolomb(); |
|||
int minimumInitialQuantizationParameter = -26 - (6 * (sequenceParameterSet.BitDepthLuma - 8)); |
|||
if (this.InitialQuantizationParameterMinus26 < minimumInitialQuantizationParameter |
|||
|| this.InitialQuantizationParameterMinus26 > 25) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid initial quantization parameter."); |
|||
} |
|||
|
|||
this.ConstrainedIntraPredictionEnabled = reader.ReadFlag(); |
|||
this.TransformSkipEnabled = reader.ReadFlag(); |
|||
this.CodingUnitQuantizationParameterDeltaEnabled = reader.ReadFlag(); |
|||
if (this.CodingUnitQuantizationParameterDeltaEnabled) |
|||
{ |
|||
uint quantizationParameterDeltaDepth = reader.ReadUnsignedExpGolomb(); |
|||
int maximumDepth = sequenceParameterSet.CodingTreeBlockLog2 - sequenceParameterSet.MinCodingBlockLog2; |
|||
if (quantizationParameterDeltaDepth > maximumDepth) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid quantization-parameter delta depth."); |
|||
} |
|||
|
|||
this.QuantizationParameterDeltaDepth = (int)quantizationParameterDeltaDepth; |
|||
} |
|||
|
|||
this.ChromaCbQuantizationParameterOffset = HevcParameterSetSyntax.ReadQuantizationParameterOffset(ref reader); |
|||
this.ChromaCrQuantizationParameterOffset = HevcParameterSetSyntax.ReadQuantizationParameterOffset(ref reader); |
|||
this.SliceChromaQuantizationParameterOffsetsPresent = reader.ReadFlag(); |
|||
this.WeightedPredictionEnabled = reader.ReadFlag(); |
|||
this.WeightedBiPredictionEnabled = reader.ReadFlag(); |
|||
this.TransquantizationBypassEnabled = reader.ReadFlag(); |
|||
this.TilesEnabled = reader.ReadFlag(); |
|||
this.EntropyCodingSynchronizationEnabled = reader.ReadFlag(); |
|||
|
|||
int codingTreeBlockColumns = HevcParameterSetSyntax.GetCodingTreeBlockCount( |
|||
sequenceParameterSet.Width, |
|||
sequenceParameterSet.CodingTreeBlockLog2); |
|||
|
|||
int codingTreeBlockRows = HevcParameterSetSyntax.GetCodingTreeBlockCount( |
|||
sequenceParameterSet.Height, |
|||
sequenceParameterSet.CodingTreeBlockLog2); |
|||
|
|||
if (this.TilesEnabled) |
|||
{ |
|||
uint tileColumnCountMinusOne = reader.ReadUnsignedExpGolomb(); |
|||
uint tileRowCountMinusOne = reader.ReadUnsignedExpGolomb(); |
|||
if (tileColumnCountMinusOne >= codingTreeBlockColumns |
|||
|| tileRowCountMinusOne >= codingTreeBlockRows |
|||
|| (tileColumnCountMinusOne == 0 && tileRowCountMinusOne == 0)) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid tile grid."); |
|||
} |
|||
|
|||
int tileColumnCount = (int)tileColumnCountMinusOne + 1; |
|||
int tileRowCount = (int)tileRowCountMinusOne + 1; |
|||
this.UniformTileSpacing = reader.ReadFlag(); |
|||
this.TileColumnWidths = ReadTileDimensions( |
|||
ref reader, |
|||
codingTreeBlockColumns, |
|||
tileColumnCount, |
|||
this.UniformTileSpacing); |
|||
|
|||
this.TileRowHeights = ReadTileDimensions( |
|||
ref reader, |
|||
codingTreeBlockRows, |
|||
tileRowCount, |
|||
this.UniformTileSpacing); |
|||
|
|||
this.LoopFilterAcrossTilesEnabled = reader.ReadFlag(); |
|||
} |
|||
else |
|||
{ |
|||
// A picture without tile syntax is one tile spanning the coded CTB grid. Materializing that inferred
|
|||
// layout lets slice addressing use the same bounded arrays for tiled and untiled image items.
|
|||
this.UniformTileSpacing = true; |
|||
this.TileColumnWidths = [codingTreeBlockColumns]; |
|||
this.TileRowHeights = [codingTreeBlockRows]; |
|||
this.LoopFilterAcrossTilesEnabled = true; |
|||
} |
|||
|
|||
this.LoopFilterAcrossSlicesEnabled = reader.ReadFlag(); |
|||
this.DeblockingFilterControlPresent = reader.ReadFlag(); |
|||
if (this.DeblockingFilterControlPresent) |
|||
{ |
|||
this.DeblockingFilterOverrideEnabled = reader.ReadFlag(); |
|||
this.DeblockingFilterDisabled = reader.ReadFlag(); |
|||
if (!this.DeblockingFilterDisabled) |
|||
{ |
|||
this.DeblockingFilterBetaOffsetDiv2 = HevcParameterSetSyntax.ReadDeblockingFilterOffset(ref reader); |
|||
this.DeblockingFilterTcOffsetDiv2 = HevcParameterSetSyntax.ReadDeblockingFilterOffset(ref reader); |
|||
} |
|||
} |
|||
|
|||
this.ScalingListDataPresent = reader.ReadFlag(); |
|||
if (this.ScalingListDataPresent && !sequenceParameterSet.ScalingListEnabled) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set declares scaling data disabled by its sequence parameter set."); |
|||
} |
|||
|
|||
this.ScalingList = this.ScalingListDataPresent |
|||
? HevcScalingList.Parse(ref reader) |
|||
: sequenceParameterSet.ScalingList; |
|||
|
|||
this.ReferenceListModificationPresent = reader.ReadFlag(); |
|||
uint parallelMergeLevelMinusTwo = reader.ReadUnsignedExpGolomb(); |
|||
if (parallelMergeLevelMinusTwo > sequenceParameterSet.CodingTreeBlockLog2 - 2) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid parallel merge level."); |
|||
} |
|||
|
|||
this.ParallelMergeLevelLog2 = (int)parallelMergeLevelMinusTwo + 2; |
|||
this.SliceSegmentHeaderExtensionPresent = reader.ReadFlag(); |
|||
|
|||
this.MaxTransformSkipBlockLog2 = 2; |
|||
if (reader.ReadFlag()) |
|||
{ |
|||
Span<bool> extensionFlags = stackalloc bool[8]; |
|||
for (int extensionFlag = 0; extensionFlag < extensionFlags.Length; extensionFlag++) |
|||
{ |
|||
extensionFlags[extensionFlag] = reader.ReadFlag(); |
|||
} |
|||
|
|||
if (extensionFlags[1]) |
|||
{ |
|||
throw new InvalidImageContentException("Layered HEVC picture extensions are not supported for still-image items."); |
|||
} |
|||
|
|||
if (extensionFlags[0]) |
|||
{ |
|||
this.ReadRangeExtension(ref reader); |
|||
} |
|||
|
|||
bool unknownExtensionPresent = false; |
|||
for (int extensionFlag = 2; extensionFlag < extensionFlags.Length; extensionFlag++) |
|||
{ |
|||
unknownExtensionPresent |= extensionFlags[extensionFlag]; |
|||
} |
|||
|
|||
if (unknownExtensionPresent) |
|||
{ |
|||
while (reader.HasMoreRbspData()) |
|||
{ |
|||
reader.ReadFlag(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
reader.ReadRbspTrailingBits(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the picture-parameter-set identifier.
|
|||
/// </summary>
|
|||
public byte Id { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the referenced sequence-parameter-set identifier.
|
|||
/// </summary>
|
|||
public byte SequenceParameterSetId { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the sequence parameters governing this picture parameter set.
|
|||
/// </summary>
|
|||
public HevcSequenceParameterSet SequenceParameterSet { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether dependent slice segments can occur.
|
|||
/// </summary>
|
|||
public bool DependentSliceSegmentsEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether slice headers contain the picture-output flag.
|
|||
/// </summary>
|
|||
public bool OutputFlagPresent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of reserved extra bits at the start of each independent slice header.
|
|||
/// </summary>
|
|||
public int ExtraSliceHeaderBitCount { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether transform-coefficient sign hiding is enabled.
|
|||
/// </summary>
|
|||
public bool SignDataHidingEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether slices can select an alternate CABAC initialization table.
|
|||
/// </summary>
|
|||
public bool CabacInitializationPresent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the default active reference-index count for reference list zero.
|
|||
/// </summary>
|
|||
public int DefaultReferenceIndexCountList0 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the default active reference-index count for reference list one.
|
|||
/// </summary>
|
|||
public int DefaultReferenceIndexCountList1 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the picture quantization-parameter initializer relative to 26.
|
|||
/// </summary>
|
|||
public int InitialQuantizationParameterMinus26 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether inter-coded neighbors are excluded from intra prediction.
|
|||
/// </summary>
|
|||
public bool ConstrainedIntraPredictionEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether residual transform skipping can be selected.
|
|||
/// </summary>
|
|||
public bool TransformSkipEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether coding units can change the quantization parameter.
|
|||
/// </summary>
|
|||
public bool CodingUnitQuantizationParameterDeltaEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coding-tree depth at which quantization-parameter deltas are signaled.
|
|||
/// </summary>
|
|||
public int QuantizationParameterDeltaDepth { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the picture-level Cb quantization-parameter offset.
|
|||
/// </summary>
|
|||
public int ChromaCbQuantizationParameterOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the picture-level Cr quantization-parameter offset.
|
|||
/// </summary>
|
|||
public int ChromaCrQuantizationParameterOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether slices can add Cb and Cr quantization-parameter offsets.
|
|||
/// </summary>
|
|||
public bool SliceChromaQuantizationParameterOffsetsPresent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether weighted prediction can be used by predictive slices.
|
|||
/// </summary>
|
|||
public bool WeightedPredictionEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether weighted prediction can be used by bidirectional slices.
|
|||
/// </summary>
|
|||
public bool WeightedBiPredictionEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether coding units can bypass transform and quantization.
|
|||
/// </summary>
|
|||
public bool TransquantizationBypassEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the coded picture is partitioned into tiles.
|
|||
/// </summary>
|
|||
public bool TilesEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether wavefront entropy-coding synchronization is enabled.
|
|||
/// </summary>
|
|||
public bool EntropyCodingSynchronizationEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the tile grid uses uniform proportional spacing.
|
|||
/// </summary>
|
|||
public bool UniformTileSpacing { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the tile-column widths in coding-tree blocks.
|
|||
/// </summary>
|
|||
public IReadOnlyList<int> TileColumnWidths { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the tile-row heights in coding-tree blocks.
|
|||
/// </summary>
|
|||
public IReadOnlyList<int> TileRowHeights { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether in-loop filtering crosses tile boundaries.
|
|||
/// </summary>
|
|||
public bool LoopFilterAcrossTilesEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether in-loop filtering crosses slice boundaries.
|
|||
/// </summary>
|
|||
public bool LoopFilterAcrossSlicesEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether picture or slice syntax controls deblocking.
|
|||
/// </summary>
|
|||
public bool DeblockingFilterControlPresent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether slice headers can override picture-level deblocking.
|
|||
/// </summary>
|
|||
public bool DeblockingFilterOverrideEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether deblocking is disabled by default for the picture.
|
|||
/// </summary>
|
|||
public bool DeblockingFilterDisabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets half the picture-level deblocking beta-threshold offset.
|
|||
/// </summary>
|
|||
public int DeblockingFilterBetaOffsetDiv2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets half the picture-level deblocking clipping-threshold offset.
|
|||
/// </summary>
|
|||
public int DeblockingFilterTcOffsetDiv2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this picture parameter set supplies scaling-list data.
|
|||
/// </summary>
|
|||
public bool ScalingListDataPresent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the effective quantization scaling matrices for slices using this picture parameter set.
|
|||
/// </summary>
|
|||
public HevcScalingList ScalingList { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether slice headers can modify the initial reference-picture lists.
|
|||
/// </summary>
|
|||
public bool ReferenceListModificationPresent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the parallel merge-estimation region width and height.
|
|||
/// </summary>
|
|||
public int ParallelMergeLevelLog2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether slice-segment headers carry extension bytes.
|
|||
/// </summary>
|
|||
public bool SliceSegmentHeaderExtensionPresent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the maximum transform-skip block width and height.
|
|||
/// </summary>
|
|||
public int MaxTransformSkipBlockLog2 { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether cross-component residual prediction is enabled.
|
|||
/// </summary>
|
|||
public bool CrossComponentPredictionEnabled { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coding-tree depth at which chroma quantization-offset indices are signaled.
|
|||
/// </summary>
|
|||
public int ChromaQuantizationParameterOffsetDepth { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Cb offsets in the selectable chroma quantization-parameter offset list.
|
|||
/// </summary>
|
|||
public IReadOnlyList<int> ChromaQuantizationParameterOffsetsCb { get; private set; } = Array.Empty<int>(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Cr offsets in the selectable chroma quantization-parameter offset list.
|
|||
/// </summary>
|
|||
public IReadOnlyList<int> ChromaQuantizationParameterOffsetsCr { get; private set; } = Array.Empty<int>(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the luma sample-adaptive-offset value scale.
|
|||
/// </summary>
|
|||
public int SampleAdaptiveOffsetScaleLumaLog2 { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the chroma sample-adaptive-offset value scale.
|
|||
/// </summary>
|
|||
public int SampleAdaptiveOffsetScaleChromaLog2 { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Reads the Range Extensions fields that change transform, chroma quantization, and SAO reconstruction.
|
|||
/// </summary>
|
|||
/// <param name="reader">The picture-parameter-set raw byte sequence payload reader.</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// A transform, coding-tree-depth, chroma offset, or sample-adaptive-offset scale is outside the governing
|
|||
/// sequence-parameter-set bounds.
|
|||
/// </exception>
|
|||
private void ReadRangeExtension(ref HevcBitReader reader) |
|||
{ |
|||
if (this.TransformSkipEnabled) |
|||
{ |
|||
uint maxTransformSkipBlockLog2MinusTwo = reader.ReadUnsignedExpGolomb(); |
|||
if (maxTransformSkipBlockLog2MinusTwo > this.SequenceParameterSet.MaxTransformBlockLog2 - 2) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid transform-skip block size."); |
|||
} |
|||
|
|||
this.MaxTransformSkipBlockLog2 = (int)maxTransformSkipBlockLog2MinusTwo + 2; |
|||
} |
|||
|
|||
this.CrossComponentPredictionEnabled = reader.ReadFlag(); |
|||
if (reader.ReadFlag()) |
|||
{ |
|||
uint chromaOffsetDepth = reader.ReadUnsignedExpGolomb(); |
|||
int maximumDepth = this.SequenceParameterSet.CodingTreeBlockLog2 - this.SequenceParameterSet.MinCodingBlockLog2; |
|||
if (chromaOffsetDepth > maximumDepth) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid chroma quantization-offset depth."); |
|||
} |
|||
|
|||
this.ChromaQuantizationParameterOffsetDepth = (int)chromaOffsetDepth; |
|||
|
|||
uint chromaOffsetCountMinusOne = reader.ReadUnsignedExpGolomb(); |
|||
if (chromaOffsetCountMinusOne > 5) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set declares too many chroma quantization offsets."); |
|||
} |
|||
|
|||
int chromaOffsetCount = (int)chromaOffsetCountMinusOne + 1; |
|||
int[] cbOffsets = new int[chromaOffsetCount]; |
|||
int[] crOffsets = new int[chromaOffsetCount]; |
|||
for (int offset = 0; offset < chromaOffsetCount; offset++) |
|||
{ |
|||
cbOffsets[offset] = HevcParameterSetSyntax.ReadQuantizationParameterOffset(ref reader); |
|||
crOffsets[offset] = HevcParameterSetSyntax.ReadQuantizationParameterOffset(ref reader); |
|||
} |
|||
|
|||
this.ChromaQuantizationParameterOffsetsCb = cbOffsets; |
|||
this.ChromaQuantizationParameterOffsetsCr = crOffsets; |
|||
} |
|||
|
|||
uint lumaScale = reader.ReadUnsignedExpGolomb(); |
|||
uint chromaScale = reader.ReadUnsignedExpGolomb(); |
|||
int maximumLumaScale = Math.Max(this.SequenceParameterSet.BitDepthLuma, 10) - 10; |
|||
int maximumChromaScale = Math.Max(this.SequenceParameterSet.BitDepthChroma, 10) - 10; |
|||
if (lumaScale > maximumLumaScale || chromaScale > maximumChromaScale) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid sample-adaptive-offset scale."); |
|||
} |
|||
|
|||
this.SampleAdaptiveOffsetScaleLumaLog2 = (int)lumaScale; |
|||
this.SampleAdaptiveOffsetScaleChromaLog2 = (int)chromaScale; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads or derives one axis of the tile grid.
|
|||
/// </summary>
|
|||
/// <param name="reader">The picture-parameter-set raw byte sequence payload reader.</param>
|
|||
/// <param name="codingTreeBlockCount">The complete picture dimension in coding-tree blocks.</param>
|
|||
/// <param name="tileCount">The tile count on the same axis.</param>
|
|||
/// <param name="uniformSpacing">Whether the widths or heights use proportional uniform spacing.</param>
|
|||
/// <returns>Every tile width or height in coding-tree blocks, including the inferred final tile.</returns>
|
|||
/// <exception cref="InvalidImageContentException">An explicit tile consumes the final block required by a later tile.</exception>
|
|||
private static int[] ReadTileDimensions( |
|||
ref HevcBitReader reader, |
|||
int codingTreeBlockCount, |
|||
int tileCount, |
|||
bool uniformSpacing) |
|||
{ |
|||
int[] dimensions = new int[tileCount]; |
|||
if (uniformSpacing) |
|||
{ |
|||
for (int tile = 0; tile < tileCount; tile++) |
|||
{ |
|||
// The normative floor-difference formula assigns every CTB exactly once even when the picture
|
|||
// dimension is not divisible by the number of tiles.
|
|||
dimensions[tile] = (((tile + 1) * codingTreeBlockCount) / tileCount) |
|||
- ((tile * codingTreeBlockCount) / tileCount); |
|||
} |
|||
|
|||
return dimensions; |
|||
} |
|||
|
|||
int consumed = 0; |
|||
for (int tile = 0; tile < tileCount - 1; tile++) |
|||
{ |
|||
uint dimensionMinusOne = reader.ReadUnsignedExpGolomb(); |
|||
if (dimensionMinusOne >= codingTreeBlockCount - consumed - 1) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid explicit tile dimension."); |
|||
} |
|||
|
|||
dimensions[tile] = (int)dimensionMinusOne + 1; |
|||
consumed += dimensions[tile]; |
|||
} |
|||
|
|||
dimensions[^1] = codingTreeBlockCount - consumed; |
|||
return dimensions; |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Identifies an HEVC luma or chroma reconstruction plane.
|
|||
/// </summary>
|
|||
internal enum HevcPlane |
|||
{ |
|||
/// <summary>
|
|||
/// The luma or first separate-color plane.
|
|||
/// </summary>
|
|||
Y = 0, |
|||
|
|||
/// <summary>
|
|||
/// The blue-difference chroma or second separate-color plane.
|
|||
/// </summary>
|
|||
Cb = 1, |
|||
|
|||
/// <summary>
|
|||
/// The red-difference chroma or third separate-color plane.
|
|||
/// </summary>
|
|||
Cr = 2, |
|||
} |
|||
@ -1,115 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the general HEVC profile, tier, constraint, and level description declared by a parameter set.
|
|||
/// </summary>
|
|||
internal sealed class HevcProfileTierLevel |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcProfileTierLevel"/> class.
|
|||
/// </summary>
|
|||
/// <param name="reader">The parameter-set raw byte sequence payload reader.</param>
|
|||
/// <param name="maxSubLayersMinusOne">The highest declared temporal sublayer index.</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The profile-tier-level syntax is truncated or contains nonzero reserved bits.
|
|||
/// </exception>
|
|||
public HevcProfileTierLevel(ref HevcBitReader reader, int maxSubLayersMinusOne) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(maxSubLayersMinusOne, 0, 6, nameof(maxSubLayersMinusOne)); |
|||
|
|||
this.ProfileSpace = (byte)reader.ReadBits(2); |
|||
this.TierFlag = reader.ReadFlag(); |
|||
this.ProfileIdc = (byte)reader.ReadBits(5); |
|||
this.ProfileCompatibilityFlags = reader.ReadBits(32); |
|||
|
|||
// The configuration record carries these 48 bits verbatim. Preserve their exact ordering so the
|
|||
// parameter set can be checked without reinterpreting profile-specific constraint layouts.
|
|||
this.ConstraintIndicatorFlags = ((ulong)reader.ReadBits(16) << 32) | reader.ReadBits(32); |
|||
this.LevelIdc = (byte)reader.ReadBits(8); |
|||
|
|||
Span<bool> subLayerProfilePresent = stackalloc bool[6]; |
|||
Span<bool> subLayerLevelPresent = stackalloc bool[6]; |
|||
for (int subLayer = 0; subLayer < maxSubLayersMinusOne; subLayer++) |
|||
{ |
|||
subLayerProfilePresent[subLayer] = reader.ReadFlag(); |
|||
subLayerLevelPresent[subLayer] = reader.ReadFlag(); |
|||
} |
|||
|
|||
if (maxSubLayersMinusOne > 0) |
|||
{ |
|||
for (int subLayer = maxSubLayersMinusOne; subLayer < 8; subLayer++) |
|||
{ |
|||
if (reader.ReadBits(2) != 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC profile-tier-level syntax has nonzero reserved bits."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
for (int subLayer = 0; subLayer < maxSubLayersMinusOne; subLayer++) |
|||
{ |
|||
if (subLayerProfilePresent[subLayer]) |
|||
{ |
|||
// A sublayer profile repeats the fixed 88-bit profile and constraint structure. It is consumed
|
|||
// for alignment but not retained because one still-image item has no temporal playback model.
|
|||
reader.ReadBits(2); |
|||
reader.ReadFlag(); |
|||
reader.ReadBits(5); |
|||
reader.ReadBits(32); |
|||
reader.ReadBits(16); |
|||
reader.ReadBits(32); |
|||
} |
|||
|
|||
if (subLayerLevelPresent[subLayer]) |
|||
{ |
|||
reader.ReadBits(8); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the namespace of the declared profile identifier.
|
|||
/// </summary>
|
|||
public byte ProfileSpace { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the high tier is declared.
|
|||
/// </summary>
|
|||
public bool TierFlag { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the five-bit profile identifier.
|
|||
/// </summary>
|
|||
public byte ProfileIdc { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the profile-compatibility flags.
|
|||
/// </summary>
|
|||
public uint ProfileCompatibilityFlags { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the 48-bit profile-constraint flags.
|
|||
/// </summary>
|
|||
public ulong ConstraintIndicatorFlags { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the eight-bit level identifier.
|
|||
/// </summary>
|
|||
public byte LevelIdc { get; } |
|||
|
|||
/// <summary>
|
|||
/// Determines whether this parameter-set description is compatible with an image item's codec-configuration
|
|||
/// property.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The associated HEVC codec configuration.</param>
|
|||
/// <returns><see langword="true"/> when the general profile, tier, compatibility, and level fields match.</returns>
|
|||
public bool Matches(HevcCodecConfiguration configuration) |
|||
=> this.ProfileSpace == configuration.GeneralProfileSpace |
|||
&& this.TierFlag == configuration.GeneralTierFlag |
|||
&& this.ProfileIdc == configuration.GeneralProfileIdc |
|||
&& this.ProfileCompatibilityFlags == configuration.GeneralProfileCompatibilityFlags |
|||
&& this.LevelIdc == configuration.GeneralLevelIdc; |
|||
} |
|||
@ -1,111 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the effective HEVC quantization parameters for one transform unit.
|
|||
/// </summary>
|
|||
internal readonly struct HevcQuantizationParameters |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcQuantizationParameters"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="lumaQuantizationParameter">The effective coding-unit luma quantization parameter before the luma bit-depth offset.</param>
|
|||
/// <param name="lumaBitDepth">The reconstructed luma precision.</param>
|
|||
/// <param name="chromaBitDepth">The reconstructed chroma precision.</param>
|
|||
/// <param name="chromaFormat">The sequence chroma-format identifier.</param>
|
|||
/// <param name="cbQuantizationParameterOffset">The combined picture, slice, and coding-unit Cb quantization-parameter offset.</param>
|
|||
/// <param name="crQuantizationParameterOffset">The combined picture, slice, and coding-unit Cr quantization-parameter offset.</param>
|
|||
public HevcQuantizationParameters( |
|||
int lumaQuantizationParameter, |
|||
int lumaBitDepth, |
|||
int chromaBitDepth, |
|||
byte chromaFormat, |
|||
int cbQuantizationParameterOffset, |
|||
int crQuantizationParameterOffset) |
|||
{ |
|||
int lumaBitDepthOffset = 6 * (lumaBitDepth - 8); |
|||
int chromaBitDepthOffset = 6 * (chromaBitDepth - 8); |
|||
this.CbOffset = cbQuantizationParameterOffset; |
|||
this.CrOffset = crQuantizationParameterOffset; |
|||
this.Luma = lumaQuantizationParameter + lumaBitDepthOffset; |
|||
this.Cb = GetChromaQuantizationParameter(lumaQuantizationParameter, cbQuantizationParameterOffset, chromaBitDepthOffset, chromaFormat); |
|||
this.Cr = GetChromaQuantizationParameter(lumaQuantizationParameter, crQuantizationParameterOffset, chromaBitDepthOffset, chromaFormat); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the effective nonnegative luma quantization parameter including its bit-depth offset.
|
|||
/// </summary>
|
|||
public int Luma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the effective nonnegative blue-difference chroma quantization parameter including its bit-depth offset.
|
|||
/// </summary>
|
|||
public int Cb { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the effective nonnegative red-difference chroma quantization parameter including its bit-depth offset.
|
|||
/// </summary>
|
|||
public int Cr { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the combined picture, slice, and coding-unit Cb quantization-parameter offset.
|
|||
/// </summary>
|
|||
public int CbOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the combined picture, slice, and coding-unit Cr quantization-parameter offset.
|
|||
/// </summary>
|
|||
public int CrOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the H.265 Table 8-10 chroma quantization-parameter mapping for 4:2:0 pictures.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> Chroma420QuantizationParameterMap => |
|||
[ |
|||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, |
|||
29, 30, 31, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the effective quantization parameter for the selected reconstruction plane.
|
|||
/// </summary>
|
|||
/// <param name="plane">The reconstruction plane.</param>
|
|||
/// <returns>The effective nonnegative quantization parameter including its bit-depth offset.</returns>
|
|||
public int Get(HevcPlane plane) => plane switch |
|||
{ |
|||
HevcPlane.Y => this.Luma, |
|||
HevcPlane.Cb => this.Cb, |
|||
_ => this.Cr, |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Derives an effective chroma quantization parameter from the luma value and combined component offset.
|
|||
/// </summary>
|
|||
/// <param name="lumaQuantizationParameter">The effective coding-unit luma quantization parameter before its bit-depth offset.</param>
|
|||
/// <param name="componentOffset">The combined picture, slice, and coding-unit component offset.</param>
|
|||
/// <param name="chromaBitDepthOffset">Six times the number of chroma bits above eight.</param>
|
|||
/// <param name="chromaFormat">The sequence chroma-format identifier.</param>
|
|||
/// <returns>The effective nonnegative chroma quantization parameter including its bit-depth offset.</returns>
|
|||
public static int GetChromaQuantizationParameter( |
|||
int lumaQuantizationParameter, |
|||
int componentOffset, |
|||
int chromaBitDepthOffset, |
|||
byte chromaFormat) |
|||
{ |
|||
int unscaled = Math.Clamp(lumaQuantizationParameter + componentOffset, -chromaBitDepthOffset, 57); |
|||
if (unscaled < 0) |
|||
{ |
|||
return unscaled + chromaBitDepthOffset; |
|||
} |
|||
|
|||
// H.265 section 8.6.1 maps nonnegative chroma QP before adding the bit-depth offset. The 4:2:0 table
|
|||
// contains plateaus above QP 29, whereas 4:2:2 and 4:4:4 remain linear through 51 and then saturate.
|
|||
int mapped = chromaFormat == 1 |
|||
? Chroma420QuantizationParameterMap[unscaled] |
|||
: Math.Min(unscaled, 51); |
|||
|
|||
return mapped + chromaBitDepthOffset; |
|||
} |
|||
} |
|||
@ -1,235 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Tracks reconstructed minimum prediction blocks for HEVC intra-reference availability.
|
|||
/// </summary>
|
|||
internal sealed class HevcReconstructionState : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The base-two logarithm of the minimum luma prediction-block side.
|
|||
/// </summary>
|
|||
private const int MinPredictionBlockLog2 = 2; |
|||
|
|||
/// <summary>
|
|||
/// The reconstruction-region identifiers for the three component planes.
|
|||
/// </summary>
|
|||
private readonly Buffer2D<int>[] regions; |
|||
|
|||
/// <summary>
|
|||
/// The horizontal chroma subsampling shift.
|
|||
/// </summary>
|
|||
private readonly int chromaSubsamplingX; |
|||
|
|||
/// <summary>
|
|||
/// The vertical chroma subsampling shift.
|
|||
/// </summary>
|
|||
private readonly int chromaSubsamplingY; |
|||
|
|||
/// <summary>
|
|||
/// The coded luma width used to reject padded right-edge units.
|
|||
/// </summary>
|
|||
private readonly int width; |
|||
|
|||
/// <summary>
|
|||
/// The coded luma height used to reject padded bottom-edge units.
|
|||
/// </summary>
|
|||
private readonly int height; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcReconstructionState"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration providing the image memory allocator.</param>
|
|||
/// <param name="sequenceParameterSet">The coded picture and chroma geometry.</param>
|
|||
public HevcReconstructionState(Configuration configuration, HevcSequenceParameterSet sequenceParameterSet) |
|||
{ |
|||
this.width = sequenceParameterSet.Width; |
|||
this.height = sequenceParameterSet.Height; |
|||
int widthInUnits = DivideCeilingByPowerOfTwo(this.width, MinPredictionBlockLog2); |
|||
int heightInUnits = DivideCeilingByPowerOfTwo(this.height, MinPredictionBlockLog2); |
|||
this.chromaSubsamplingX = !sequenceParameterSet.SeparateColorPlaneFlag && sequenceParameterSet.ChromaFormat is 1 or 2 ? 1 : 0; |
|||
this.chromaSubsamplingY = !sequenceParameterSet.SeparateColorPlaneFlag && sequenceParameterSet.ChromaFormat == 1 ? 1 : 0; |
|||
|
|||
Buffer2D<int>? lumaRegions = null; |
|||
Buffer2D<int>? chromaBlueRegions = null; |
|||
Buffer2D<int>? chromaRedRegions = null; |
|||
try |
|||
{ |
|||
// Region identifiers gate every reconstructed-neighbor read. A stale pooled identifier can match the first
|
|||
// region of a later picture, so these maps must begin at the reserved unavailable value zero.
|
|||
lumaRegions = configuration.MemoryAllocator.Allocate2D<int>(widthInUnits, heightInUnits, AllocationOptions.Clean); |
|||
chromaBlueRegions = configuration.MemoryAllocator.Allocate2D<int>(widthInUnits, heightInUnits, AllocationOptions.Clean); |
|||
chromaRedRegions = configuration.MemoryAllocator.Allocate2D<int>(widthInUnits, heightInUnits, AllocationOptions.Clean); |
|||
this.regions = [lumaRegions, chromaBlueRegions, chromaRedRegions]; |
|||
} |
|||
catch |
|||
{ |
|||
chromaRedRegions?.Dispose(); |
|||
chromaBlueRegions?.Dispose(); |
|||
lumaRegions?.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the horizontal availability-unit width for a component plane.
|
|||
/// </summary>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <returns>The availability-unit width in component samples.</returns>
|
|||
public int GetUnitWidth(HevcPlane plane) => 1 << (MinPredictionBlockLog2 - this.GetSubsamplingX(plane)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the vertical availability-unit height for a component plane.
|
|||
/// </summary>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <returns>The availability-unit height in component samples.</returns>
|
|||
public int GetUnitHeight(HevcPlane plane) => 1 << (MinPredictionBlockLog2 - this.GetSubsamplingY(plane)); |
|||
|
|||
/// <summary>
|
|||
/// Marks a reconstructed component rectangle as available within one slice-and-tile prediction region.
|
|||
/// </summary>
|
|||
/// <param name="plane">The reconstructed component plane.</param>
|
|||
/// <param name="x">The rectangle left coordinate in component samples.</param>
|
|||
/// <param name="y">The rectangle top coordinate in component samples.</param>
|
|||
/// <param name="width">The rectangle width in component samples.</param>
|
|||
/// <param name="height">The rectangle height in component samples.</param>
|
|||
/// <param name="regionId">The positive identifier shared by prediction blocks in the same slice segment and tile.</param>
|
|||
public void MarkReconstructed(HevcPlane plane, int x, int y, int width, int height, int regionId) |
|||
{ |
|||
DebugGuard.MustBeGreaterThan(regionId, 0, nameof(regionId)); |
|||
int subsamplingX = this.GetSubsamplingX(plane); |
|||
int subsamplingY = this.GetSubsamplingY(plane); |
|||
int unitX = (x << subsamplingX) >> MinPredictionBlockLog2; |
|||
int unitY = (y << subsamplingY) >> MinPredictionBlockLog2; |
|||
int endX = DivideCeilingByPowerOfTwo((x + width) << subsamplingX, MinPredictionBlockLog2); |
|||
int endY = DivideCeilingByPowerOfTwo((y + height) << subsamplingY, MinPredictionBlockLog2); |
|||
Buffer2D<int> map = this.regions[(int)plane]; |
|||
endX = Math.Min(endX, map.Width); |
|||
endY = Math.Min(endY, map.Height); |
|||
|
|||
// Chroma availability units map back to the same four-by-four luma grid used by HEVC neighbor derivation.
|
|||
// Filling the complete rectangle makes later sub-TUs observe only samples whose reconstruction has finished.
|
|||
for (int row = unitY; row < endY; row++) |
|||
{ |
|||
map.DangerousGetRowSpan(row)[unitX..endX].Fill(regionId); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Builds the ordered availability flags consumed by HEVC reference-sample substitution.
|
|||
/// </summary>
|
|||
/// <param name="plane">The component plane containing the prediction block.</param>
|
|||
/// <param name="x">The prediction-block left coordinate in component samples.</param>
|
|||
/// <param name="y">The prediction-block top coordinate in component samples.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the square prediction-block side.</param>
|
|||
/// <param name="regionId">The current slice-and-tile prediction-region identifier.</param>
|
|||
/// <param name="destination">
|
|||
/// The destination ordered from the bottom-most below-left unit through top-left and then the above-right units.
|
|||
/// </param>
|
|||
/// <returns>The number of flags written.</returns>
|
|||
public int BuildReferenceAvailability(HevcPlane plane, int x, int y, int log2Size, int regionId, Span<bool> destination) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
|||
DebugGuard.MustBeGreaterThan(regionId, 0, nameof(regionId)); |
|||
int size = 1 << log2Size; |
|||
int unitWidth = this.GetUnitWidth(plane); |
|||
int unitHeight = this.GetUnitHeight(plane); |
|||
int leftUnitCount = (size * 2) / unitHeight; |
|||
int aboveUnitCount = (size * 2) / unitWidth; |
|||
int flagCount = leftUnitCount + aboveUnitCount + 1; |
|||
Span<bool> availability = destination[..flagCount]; |
|||
|
|||
for (int unit = 0; unit < leftUnitCount; unit++) |
|||
{ |
|||
int unitY = y + ((leftUnitCount - unit - 1) * unitHeight); |
|||
availability[unit] = this.IsAvailable(plane, x - 1, unitY, regionId); |
|||
} |
|||
|
|||
availability[leftUnitCount] = this.IsAvailable(plane, x - 1, y - 1, regionId); |
|||
for (int unit = 0; unit < aboveUnitCount; unit++) |
|||
{ |
|||
availability[leftUnitCount + unit + 1] = this.IsAvailable(plane, x + (unit * unitWidth), y - 1, regionId); |
|||
} |
|||
|
|||
return flagCount; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets whether one component sample has already been reconstructed in the selected prediction region.
|
|||
/// </summary>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The component sample X coordinate.</param>
|
|||
/// <param name="y">The component sample Y coordinate.</param>
|
|||
/// <param name="regionId">The current slice-and-tile prediction-region identifier.</param>
|
|||
/// <returns><see langword="true"/> when the sample is available; otherwise, <see langword="false"/>.</returns>
|
|||
public bool IsReconstructed(HevcPlane plane, int x, int y, int regionId) => this.IsAvailable(plane, x, y, regionId); |
|||
|
|||
/// <summary>
|
|||
/// Releases the owned reconstruction-region maps.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
foreach (Buffer2D<int> map in this.regions) |
|||
{ |
|||
map.Dispose(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets whether a component sample belongs to an already reconstructed block in the selected prediction region.
|
|||
/// </summary>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The component sample X coordinate.</param>
|
|||
/// <param name="y">The component sample Y coordinate.</param>
|
|||
/// <param name="regionId">The current slice-and-tile prediction-region identifier.</param>
|
|||
/// <returns><see langword="true"/> when the sample is available; otherwise, <see langword="false"/>.</returns>
|
|||
private bool IsAvailable(HevcPlane plane, int x, int y, int regionId) |
|||
{ |
|||
if (x < 0 || y < 0) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
int subsamplingX = this.GetSubsamplingX(plane); |
|||
int subsamplingY = this.GetSubsamplingY(plane); |
|||
int planeWidth = DivideCeilingByPowerOfTwo(this.width, subsamplingX); |
|||
int planeHeight = DivideCeilingByPowerOfTwo(this.height, subsamplingY); |
|||
if (x >= planeWidth || y >= planeHeight) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
int unitX = (x << subsamplingX) >> MinPredictionBlockLog2; |
|||
int unitY = (y << subsamplingY) >> MinPredictionBlockLog2; |
|||
Buffer2D<int> map = this.regions[(int)plane]; |
|||
return (uint)unitX < (uint)map.Width |
|||
&& (uint)unitY < (uint)map.Height |
|||
&& map.DangerousGetRowSpan(unitY)[unitX] == regionId; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the horizontal chroma shift selected by a component plane.
|
|||
/// </summary>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <returns>Zero for luma and full-resolution planes; otherwise, the chroma shift.</returns>
|
|||
private int GetSubsamplingX(HevcPlane plane) => plane == HevcPlane.Y ? 0 : this.chromaSubsamplingX; |
|||
|
|||
/// <summary>
|
|||
/// Gets the vertical chroma shift selected by a component plane.
|
|||
/// </summary>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <returns>Zero for luma and full-resolution planes; otherwise, the chroma shift.</returns>
|
|||
private int GetSubsamplingY(HevcPlane plane) => plane == HevcPlane.Y ? 0 : this.chromaSubsamplingY; |
|||
|
|||
/// <summary>
|
|||
/// Divides a nonnegative sample count by a power of two with upward rounding.
|
|||
/// </summary>
|
|||
/// <param name="value">The sample count.</param>
|
|||
/// <param name="shift">The base-two divisor logarithm.</param>
|
|||
/// <returns>The upward-rounded quotient.</returns>
|
|||
private static int DivideCeilingByPowerOfTwo(int value, int shift) => (value + (1 << shift) - 1) >> shift; |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the differential pulse-code modulation applied to an HEVC residual block.
|
|||
/// </summary>
|
|||
internal enum HevcResidualDpcmMode : byte |
|||
{ |
|||
/// <summary>
|
|||
/// No residual differential pulse-code modulation is applied.
|
|||
/// </summary>
|
|||
None = 0, |
|||
|
|||
/// <summary>
|
|||
/// Residual differences accumulate from left to right within each row.
|
|||
/// </summary>
|
|||
Horizontal = 1, |
|||
|
|||
/// <summary>
|
|||
/// Residual differences accumulate from top to bottom within each column.
|
|||
/// </summary>
|
|||
Vertical = 2, |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
internal static partial class HevcResidualReconstructor |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the exact left shift used by high-bit-depth transform-skip reconstruction.
|
|||
/// </summary>
|
|||
private readonly struct LeftShiftTransformSkipOperator : ITransformSkipOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<int> Invoke(Vector512<int> values, int shift) => values << shift; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<int> Invoke(Vector256<int> values, int shift) => values << shift; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<int> Invoke(Vector128<int> values, int shift) => values << shift; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Invoke(int value, int shift) => value << shift; |
|||
} |
|||
} |
|||
@ -1,47 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
internal static partial class HevcResidualReconstructor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a closed transform-skip normalization operator for every SIMD width and the scalar tail.
|
|||
/// </summary>
|
|||
private interface ITransformSkipOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Normalizes sixteen transform-skipped coefficients.
|
|||
/// </summary>
|
|||
/// <param name="values">The dequantized coefficients.</param>
|
|||
/// <param name="shift">The nonnegative shift magnitude.</param>
|
|||
/// <returns>The reconstructed residuals.</returns>
|
|||
static abstract Vector512<int> Invoke(Vector512<int> values, int shift); |
|||
|
|||
/// <summary>
|
|||
/// Normalizes eight transform-skipped coefficients.
|
|||
/// </summary>
|
|||
/// <param name="values">The dequantized coefficients.</param>
|
|||
/// <param name="shift">The nonnegative shift magnitude.</param>
|
|||
/// <returns>The reconstructed residuals.</returns>
|
|||
static abstract Vector256<int> Invoke(Vector256<int> values, int shift); |
|||
|
|||
/// <summary>
|
|||
/// Normalizes four transform-skipped coefficients.
|
|||
/// </summary>
|
|||
/// <param name="values">The dequantized coefficients.</param>
|
|||
/// <param name="shift">The nonnegative shift magnitude.</param>
|
|||
/// <returns>The reconstructed residuals.</returns>
|
|||
static abstract Vector128<int> Invoke(Vector128<int> values, int shift); |
|||
|
|||
/// <summary>
|
|||
/// Normalizes one transform-skipped coefficient.
|
|||
/// </summary>
|
|||
/// <param name="value">The dequantized coefficient.</param>
|
|||
/// <param name="shift">The nonnegative shift magnitude.</param>
|
|||
/// <returns>The reconstructed residual.</returns>
|
|||
static abstract int Invoke(int value, int shift); |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
internal static partial class HevcResidualReconstructor |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the rounded right shift used by ordinary transform-skip reconstruction.
|
|||
/// </summary>
|
|||
private readonly struct RightShiftTransformSkipOperator : ITransformSkipOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<int> Invoke(Vector512<int> values, int shift) |
|||
=> shift == 0 ? values : (values + Vector512.Create(1 << (shift - 1))) >> shift; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<int> Invoke(Vector256<int> values, int shift) |
|||
=> shift == 0 ? values : (values + Vector256.Create(1 << (shift - 1))) >> shift; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<int> Invoke(Vector128<int> values, int shift) |
|||
=> shift == 0 ? values : (values + Vector128.Create(1 << (shift - 1))) >> shift; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Invoke(int value, int shift) => shift == 0 ? value : (value + (1 << (shift - 1))) >> shift; |
|||
} |
|||
} |
|||
@ -1,578 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs HEVC transform-skipped, bypassed, and differential residual blocks.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Consecutive residual samples are widened to signed 32-bit lanes for normalization and prediction addition. Closed
|
|||
/// static operators encode the selected transform-skip shift so the JIT specializes left-shift, rounded-right-shift,
|
|||
/// and identity cases outside the row loops. Saturation to the residual range and clipping to sample depth occur at the
|
|||
/// same stage in every vector width and in the scalar tail.
|
|||
/// </remarks>
|
|||
internal static partial class HevcResidualReconstructor |
|||
{ |
|||
/// <summary>
|
|||
/// The minimum residual sample represented by the decoder reconstruction pipeline.
|
|||
/// </summary>
|
|||
private const int ResidualMinimum = short.MinValue; |
|||
|
|||
/// <summary>
|
|||
/// The maximum residual sample represented by the decoder reconstruction pipeline.
|
|||
/// </summary>
|
|||
private const int ResidualMaximum = short.MaxValue; |
|||
|
|||
/// <summary>
|
|||
/// Copies one transquant-bypass coefficient block into residual sample order.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The decoded coefficients in raster order.</param>
|
|||
/// <param name="residual">The destination residual block in packed raster order.</param>
|
|||
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
|||
public static void CopyBypassed(ReadOnlySpan<int> coefficients, Span<int> residual, bool rotate) |
|||
{ |
|||
Span<int> destination = residual[..coefficients.Length]; |
|||
if (!rotate) |
|||
{ |
|||
coefficients.CopyTo(destination); |
|||
return; |
|||
} |
|||
|
|||
CopyReversed(coefficients, destination); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one transform-skipped residual block from dequantized coefficients.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The dequantized coefficients in raster order.</param>
|
|||
/// <param name="residual">The destination residual block in packed raster order.</param>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
|||
/// <param name="equivalentLog2TransformSize">The base-two logarithm of the equivalent square transform size.</param>
|
|||
/// <param name="extendedPrecisionProcessingEnabled">Whether transform-skip precision is extended by the sequence.</param>
|
|||
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
|||
public static void ApplyTransformSkip( |
|||
ReadOnlySpan<int> coefficients, |
|||
Span<int> residual, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
int maxTransformDynamicRange, |
|||
int equivalentLog2TransformSize, |
|||
bool extendedPrecisionProcessingEnabled, |
|||
bool rotate) |
|||
{ |
|||
int transformShift = maxTransformDynamicRange - bitDepth - equivalentLog2TransformSize; |
|||
if (extendedPrecisionProcessingEnabled) |
|||
{ |
|||
transformShift = Math.Max(0, transformShift); |
|||
} |
|||
|
|||
int coefficientCount = width * height; |
|||
if (transformShift >= 0) |
|||
{ |
|||
ApplyTransformSkip<RightShiftTransformSkipOperator>(coefficients[..coefficientCount], residual[..coefficientCount], transformShift, rotate); |
|||
} |
|||
else |
|||
{ |
|||
ApplyTransformSkip<LeftShiftTransformSkipOperator>(coefficients[..coefficientCount], residual[..coefficientCount], -transformShift, rotate); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets whether a non-transformed residual block uses the HEVC Range Extensions coefficient rotation.
|
|||
/// </summary>
|
|||
/// <param name="transformSkipRotationEnabled">Whether the sequence enables transform-skip rotation.</param>
|
|||
/// <param name="isIntraPredicted">Whether the transform unit belongs to an intra-predicted coding unit.</param>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <returns><see langword="true"/> when the complete coefficient order is reversed; otherwise, <see langword="false"/>.</returns>
|
|||
public static bool IsNonTransformedResidualRotated(bool transformSkipRotationEnabled, bool isIntraPredicted, int width) |
|||
=> transformSkipRotationEnabled && isIntraPredicted && width == 4; |
|||
|
|||
/// <summary>
|
|||
/// Gets the implicit residual differential mode selected by an intra-prediction direction.
|
|||
/// </summary>
|
|||
/// <param name="intraPredictionMode">The resolved luma or chroma intra-prediction mode.</param>
|
|||
/// <param name="remapChroma422">Whether the 4:2:2 chroma intra-angle remapping applies.</param>
|
|||
/// <returns>The residual differential mode selected by the prediction direction.</returns>
|
|||
public static HevcResidualDpcmMode GetImplicitResidualDpcmMode(int intraPredictionMode, bool remapChroma422) |
|||
{ |
|||
int predictionMode = remapChroma422 ? HevcIntraPredictionMode.RemapChroma422(intraPredictionMode) : intraPredictionMode; |
|||
return predictionMode switch |
|||
{ |
|||
HevcIntraPredictionMode.Horizontal => HevcResidualDpcmMode.Horizontal, |
|||
HevcIntraPredictionMode.Vertical => HevcResidualDpcmMode.Vertical, |
|||
_ => HevcResidualDpcmMode.None, |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies inverse residual differential pulse-code modulation to one packed residual block.
|
|||
/// </summary>
|
|||
/// <param name="residual">The residual block in packed raster order.</param>
|
|||
/// <param name="width">The residual-block width.</param>
|
|||
/// <param name="height">The residual-block height.</param>
|
|||
/// <param name="mode">The differential accumulation direction.</param>
|
|||
public static void ApplyResidualDpcm(Span<int> residual, int width, int height, HevcResidualDpcmMode mode) |
|||
{ |
|||
if (mode == HevcResidualDpcmMode.Vertical) |
|||
{ |
|||
ApplyVerticalResidualDpcm(residual, width, height); |
|||
} |
|||
else if (mode == HevcResidualDpcmMode.Horizontal) |
|||
{ |
|||
ApplyHorizontalResidualDpcm(residual, width, height); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds the scaled luma residual to one chroma residual block for inverse cross-component prediction.
|
|||
/// </summary>
|
|||
/// <param name="lumaResidual">The packed luma residual samples colocated with the chroma block.</param>
|
|||
/// <param name="chromaResidual">The packed chroma residual block updated in place.</param>
|
|||
/// <param name="sampleCount">The number of residual samples in each block.</param>
|
|||
/// <param name="alpha">The signed cross-component scale from minus eight through eight.</param>
|
|||
/// <param name="bitDepthDifference">The luma bit depth minus the chroma bit depth.</param>
|
|||
public static void ApplyCrossComponentPrediction( |
|||
ReadOnlySpan<int> lumaResidual, |
|||
Span<int> chromaResidual, |
|||
int sampleCount, |
|||
int alpha, |
|||
int bitDepthDifference) |
|||
{ |
|||
ref int lumaBase = ref MemoryMarshal.GetReference(lumaResidual); |
|||
ref int chromaBase = ref MemoryMarshal.GetReference(chromaResidual); |
|||
int index = 0; |
|||
|
|||
// The scale denominator is eight. Adjusting luma precision first preserves the normative arithmetic shift
|
|||
// for negative residuals before the signed alpha multiplication is applied independently to every lane.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
Vector512<int> alphaVector = Vector512.Create(alpha); |
|||
Vector512<int> minimum = Vector512.Create(ResidualMinimum); |
|||
Vector512<int> maximum = Vector512.Create(ResidualMaximum); |
|||
for (; index <= sampleCount - Vector512<int>.Count; index += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> luma = AdjustBitDepth(Vector512.LoadUnsafe(ref lumaBase, (nuint)index), bitDepthDifference); |
|||
Vector512<int> chroma = Vector512.LoadUnsafe(ref chromaBase, (nuint)index); |
|||
Vector512.Clamp(chroma + ((luma * alphaVector) >> 3), minimum, maximum).StoreUnsafe(ref chromaBase, (nuint)index); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<int> alphaVector = Vector256.Create(alpha); |
|||
Vector256<int> minimum = Vector256.Create(ResidualMinimum); |
|||
Vector256<int> maximum = Vector256.Create(ResidualMaximum); |
|||
for (; index <= sampleCount - Vector256<int>.Count; index += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> luma = AdjustBitDepth(Vector256.LoadUnsafe(ref lumaBase, (nuint)index), bitDepthDifference); |
|||
Vector256<int> chroma = Vector256.LoadUnsafe(ref chromaBase, (nuint)index); |
|||
Vector256.Clamp(chroma + ((luma * alphaVector) >> 3), minimum, maximum).StoreUnsafe(ref chromaBase, (nuint)index); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<int> alphaVector = Vector128.Create(alpha); |
|||
Vector128<int> minimum = Vector128.Create(ResidualMinimum); |
|||
Vector128<int> maximum = Vector128.Create(ResidualMaximum); |
|||
for (; index <= sampleCount - Vector128<int>.Count; index += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> luma = AdjustBitDepth(Vector128.LoadUnsafe(ref lumaBase, (nuint)index), bitDepthDifference); |
|||
Vector128<int> chroma = Vector128.LoadUnsafe(ref chromaBase, (nuint)index); |
|||
Vector128.Clamp(chroma + ((luma * alphaVector) >> 3), minimum, maximum).StoreUnsafe(ref chromaBase, (nuint)index); |
|||
} |
|||
} |
|||
|
|||
for (; index < sampleCount; index++) |
|||
{ |
|||
int luma = AdjustBitDepth(Unsafe.Add(ref lumaBase, index), bitDepthDifference); |
|||
int chroma = Unsafe.Add(ref chromaBase, index) + ((alpha * luma) >> 3); |
|||
Unsafe.Add(ref chromaBase, index) = Math.Clamp(chroma, ResidualMinimum, ResidualMaximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adjusts sixteen luma residuals to chroma precision.
|
|||
/// </summary>
|
|||
/// <param name="values">The luma residuals.</param>
|
|||
/// <param name="difference">The luma bit depth minus the chroma bit depth.</param>
|
|||
/// <returns>The precision-adjusted residuals.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> AdjustBitDepth(Vector512<int> values, int difference) |
|||
=> difference >= 0 ? values >> difference : values << -difference; |
|||
|
|||
/// <summary>
|
|||
/// Adjusts eight luma residuals to chroma precision.
|
|||
/// </summary>
|
|||
/// <param name="values">The luma residuals.</param>
|
|||
/// <param name="difference">The luma bit depth minus the chroma bit depth.</param>
|
|||
/// <returns>The precision-adjusted residuals.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> AdjustBitDepth(Vector256<int> values, int difference) |
|||
=> difference >= 0 ? values >> difference : values << -difference; |
|||
|
|||
/// <summary>
|
|||
/// Adjusts four luma residuals to chroma precision.
|
|||
/// </summary>
|
|||
/// <param name="values">The luma residuals.</param>
|
|||
/// <param name="difference">The luma bit depth minus the chroma bit depth.</param>
|
|||
/// <returns>The precision-adjusted residuals.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> AdjustBitDepth(Vector128<int> values, int difference) |
|||
=> difference >= 0 ? values >> difference : values << -difference; |
|||
|
|||
/// <summary>
|
|||
/// Adjusts one luma residual to chroma precision.
|
|||
/// </summary>
|
|||
/// <param name="value">The luma residual.</param>
|
|||
/// <param name="difference">The luma bit depth minus the chroma bit depth.</param>
|
|||
/// <returns>The precision-adjusted residual.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int AdjustBitDepth(int value, int difference) => difference >= 0 ? value >> difference : value << -difference; |
|||
|
|||
/// <summary>
|
|||
/// Applies one transform-skip normalization operator to a complete coefficient block.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The signed shift operator selected before entering the hot loop.</typeparam>
|
|||
/// <param name="coefficients">The dequantized coefficients in raster order.</param>
|
|||
/// <param name="residual">The destination residual block in packed raster order.</param>
|
|||
/// <param name="shift">The nonnegative shift magnitude.</param>
|
|||
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
|||
private static void ApplyTransformSkip<TOperator>(ReadOnlySpan<int> coefficients, Span<int> residual, int shift, bool rotate) |
|||
where TOperator : struct, ITransformSkipOperator |
|||
{ |
|||
ref int sourceBase = ref MemoryMarshal.GetReference(coefficients); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(residual); |
|||
int count = coefficients.Length; |
|||
int index = 0; |
|||
|
|||
// Rotation reverses the complete raster sequence, not the lanes of independently loaded forward chunks. Each
|
|||
// load therefore starts at the mirrored chunk and shuffles its lanes before the common destination traversal.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
for (; index <= count - Vector512<int>.Count; index += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> values = Load512(ref sourceBase, count, index, rotate); |
|||
TOperator.Invoke(values, shift).StoreUnsafe(ref destinationBase, (nuint)index); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
for (; index <= count - Vector256<int>.Count; index += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> values = Load256(ref sourceBase, count, index, rotate); |
|||
TOperator.Invoke(values, shift).StoreUnsafe(ref destinationBase, (nuint)index); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
for (; index <= count - Vector128<int>.Count; index += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> values = Load128(ref sourceBase, count, index, rotate); |
|||
TOperator.Invoke(values, shift).StoreUnsafe(ref destinationBase, (nuint)index); |
|||
} |
|||
} |
|||
|
|||
for (; index < count; index++) |
|||
{ |
|||
int sourceIndex = rotate ? count - 1 - index : index; |
|||
Unsafe.Add(ref destinationBase, index) = TOperator.Invoke(Unsafe.Add(ref sourceBase, sourceIndex), shift); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies one coefficient block while reversing its complete raster order.
|
|||
/// </summary>
|
|||
/// <param name="source">The source coefficient block.</param>
|
|||
/// <param name="destination">The destination residual block.</param>
|
|||
private static void CopyReversed(ReadOnlySpan<int> source, Span<int> destination) |
|||
{ |
|||
ref int sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
int count = source.Length; |
|||
int index = 0; |
|||
|
|||
// The descending source loads and ascending destination stores never overlap because callers provide distinct
|
|||
// coefficient and residual spans. The shared index permits a scalar tail for non-vector-sized blocks.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
for (; index <= count - Vector512<int>.Count; index += Vector512<int>.Count) |
|||
{ |
|||
Load512(ref sourceBase, count, index, true).StoreUnsafe(ref destinationBase, (nuint)index); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
for (; index <= count - Vector256<int>.Count; index += Vector256<int>.Count) |
|||
{ |
|||
Load256(ref sourceBase, count, index, true).StoreUnsafe(ref destinationBase, (nuint)index); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
for (; index <= count - Vector128<int>.Count; index += Vector128<int>.Count) |
|||
{ |
|||
Load128(ref sourceBase, count, index, true).StoreUnsafe(ref destinationBase, (nuint)index); |
|||
} |
|||
} |
|||
|
|||
for (; index < count; index++) |
|||
{ |
|||
Unsafe.Add(ref destinationBase, index) = Unsafe.Add(ref sourceBase, count - 1 - index); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads and optionally reverses sixteen source coefficients.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source coefficient.</param>
|
|||
/// <param name="count">The complete coefficient count.</param>
|
|||
/// <param name="index">The destination coefficient index.</param>
|
|||
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
|||
/// <returns>The source coefficients in destination order.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> Load512(ref int source, int count, int index, bool rotate) |
|||
{ |
|||
if (!rotate) |
|||
{ |
|||
return Vector512.LoadUnsafe(ref source, (nuint)index); |
|||
} |
|||
|
|||
Vector512<int> values = Vector512.LoadUnsafe(ref source, (nuint)(count - index - Vector512<int>.Count)); |
|||
return Vector512.Shuffle(values, Vector512.Create(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads and optionally reverses eight source coefficients.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source coefficient.</param>
|
|||
/// <param name="count">The complete coefficient count.</param>
|
|||
/// <param name="index">The destination coefficient index.</param>
|
|||
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
|||
/// <returns>The source coefficients in destination order.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> Load256(ref int source, int count, int index, bool rotate) |
|||
{ |
|||
if (!rotate) |
|||
{ |
|||
return Vector256.LoadUnsafe(ref source, (nuint)index); |
|||
} |
|||
|
|||
Vector256<int> values = Vector256.LoadUnsafe(ref source, (nuint)(count - index - Vector256<int>.Count)); |
|||
return Vector256.Shuffle(values, Vector256.Create(7, 6, 5, 4, 3, 2, 1, 0)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads and optionally reverses four source coefficients.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source coefficient.</param>
|
|||
/// <param name="count">The complete coefficient count.</param>
|
|||
/// <param name="index">The destination coefficient index.</param>
|
|||
/// <param name="rotate">Whether the complete coefficient order is reversed.</param>
|
|||
/// <returns>The source coefficients in destination order.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> Load128(ref int source, int count, int index, bool rotate) |
|||
{ |
|||
if (!rotate) |
|||
{ |
|||
return Vector128.LoadUnsafe(ref source, (nuint)index); |
|||
} |
|||
|
|||
Vector128<int> values = Vector128.LoadUnsafe(ref source, (nuint)(count - index - Vector128<int>.Count)); |
|||
return Vector128.Shuffle(values, Vector128.Create(3, 2, 1, 0)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Accumulates residual differences from top to bottom while processing independent columns in SIMD lanes.
|
|||
/// </summary>
|
|||
/// <param name="residual">The residual block in packed raster order.</param>
|
|||
/// <param name="width">The residual-block width.</param>
|
|||
/// <param name="height">The residual-block height.</param>
|
|||
private static void ApplyVerticalResidualDpcm(Span<int> residual, int width, int height) |
|||
{ |
|||
ref int residualBase = ref MemoryMarshal.GetReference(residual); |
|||
int x = 0; |
|||
|
|||
// Lanes are independent columns. Carrying the reconstructed row above in the accumulator removes the need for
|
|||
// a horizontal shuffle while preserving the top-to-bottom dependency of residual DPCM.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
Vector512<int> minimum = Vector512.Create(ResidualMinimum); |
|||
Vector512<int> maximum = Vector512.Create(ResidualMaximum); |
|||
for (; x <= width - Vector512<int>.Count; x += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> accumulator = Vector512.LoadUnsafe(ref residualBase, (nuint)x); |
|||
for (int y = 1; y < height; y++) |
|||
{ |
|||
int index = (y * width) + x; |
|||
accumulator += Vector512.LoadUnsafe(ref residualBase, (nuint)index); |
|||
Vector512.Clamp(accumulator, minimum, maximum).StoreUnsafe(ref residualBase, (nuint)index); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<int> minimum = Vector256.Create(ResidualMinimum); |
|||
Vector256<int> maximum = Vector256.Create(ResidualMaximum); |
|||
for (; x <= width - Vector256<int>.Count; x += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> accumulator = Vector256.LoadUnsafe(ref residualBase, (nuint)x); |
|||
for (int y = 1; y < height; y++) |
|||
{ |
|||
int index = (y * width) + x; |
|||
accumulator += Vector256.LoadUnsafe(ref residualBase, (nuint)index); |
|||
Vector256.Clamp(accumulator, minimum, maximum).StoreUnsafe(ref residualBase, (nuint)index); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<int> minimum = Vector128.Create(ResidualMinimum); |
|||
Vector128<int> maximum = Vector128.Create(ResidualMaximum); |
|||
for (; x <= width - Vector128<int>.Count; x += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> accumulator = Vector128.LoadUnsafe(ref residualBase, (nuint)x); |
|||
for (int y = 1; y < height; y++) |
|||
{ |
|||
int index = (y * width) + x; |
|||
accumulator += Vector128.LoadUnsafe(ref residualBase, (nuint)index); |
|||
Vector128.Clamp(accumulator, minimum, maximum).StoreUnsafe(ref residualBase, (nuint)index); |
|||
} |
|||
} |
|||
} |
|||
|
|||
for (; x < width; x++) |
|||
{ |
|||
int accumulator = Unsafe.Add(ref residualBase, x); |
|||
for (int y = 1; y < height; y++) |
|||
{ |
|||
int index = (y * width) + x; |
|||
accumulator += Unsafe.Add(ref residualBase, index); |
|||
Unsafe.Add(ref residualBase, index) = Math.Clamp(accumulator, ResidualMinimum, ResidualMaximum); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Accumulates residual differences from left to right using an inclusive SIMD prefix sum for each row.
|
|||
/// </summary>
|
|||
/// <param name="residual">The residual block in packed raster order.</param>
|
|||
/// <param name="width">The residual-block width.</param>
|
|||
/// <param name="height">The residual-block height.</param>
|
|||
private static void ApplyHorizontalResidualDpcm(Span<int> residual, int width, int height) |
|||
{ |
|||
ref int residualBase = ref MemoryMarshal.GetReference(residual); |
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
int rowOffset = y * width; |
|||
int x = 0; |
|||
int accumulator = 0; |
|||
|
|||
// PrefixSum resolves dependencies inside a vector. The final lane then seeds the next vector width or the
|
|||
// scalar tail, so changing SIMD width cannot change the left-to-right accumulation order.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
Vector512<int> minimum = Vector512.Create(ResidualMinimum); |
|||
Vector512<int> maximum = Vector512.Create(ResidualMaximum); |
|||
for (; x <= width - Vector512<int>.Count; x += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> values = Vector512.LoadUnsafe(ref residualBase, (nuint)(rowOffset + x)); |
|||
values = PrefixSum(values) + Vector512.Create(accumulator); |
|||
accumulator = values.GetElement(Vector512<int>.Count - 1); |
|||
Vector512.Clamp(values, minimum, maximum).StoreUnsafe(ref residualBase, (nuint)(rowOffset + x)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<int> minimum = Vector256.Create(ResidualMinimum); |
|||
Vector256<int> maximum = Vector256.Create(ResidualMaximum); |
|||
for (; x <= width - Vector256<int>.Count; x += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> values = Vector256.LoadUnsafe(ref residualBase, (nuint)(rowOffset + x)); |
|||
values = PrefixSum(values) + Vector256.Create(accumulator); |
|||
accumulator = values.GetElement(Vector256<int>.Count - 1); |
|||
Vector256.Clamp(values, minimum, maximum).StoreUnsafe(ref residualBase, (nuint)(rowOffset + x)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<int> minimum = Vector128.Create(ResidualMinimum); |
|||
Vector128<int> maximum = Vector128.Create(ResidualMaximum); |
|||
for (; x <= width - Vector128<int>.Count; x += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> values = Vector128.LoadUnsafe(ref residualBase, (nuint)(rowOffset + x)); |
|||
values = PrefixSum(values) + Vector128.Create(accumulator); |
|||
accumulator = values.GetElement(Vector128<int>.Count - 1); |
|||
Vector128.Clamp(values, minimum, maximum).StoreUnsafe(ref residualBase, (nuint)(rowOffset + x)); |
|||
} |
|||
} |
|||
|
|||
for (; x < width; x++) |
|||
{ |
|||
int index = rowOffset + x; |
|||
accumulator += Unsafe.Add(ref residualBase, index); |
|||
Unsafe.Add(ref residualBase, index) = x == 0 ? accumulator : Math.Clamp(accumulator, ResidualMinimum, ResidualMaximum); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes an inclusive prefix sum across sixteen signed lanes.
|
|||
/// </summary>
|
|||
/// <param name="values">The residual differences.</param>
|
|||
/// <returns>The accumulated residuals.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> PrefixSum(Vector512<int> values) |
|||
{ |
|||
// Out-of-range shuffle indices create zero lanes. Distances 1, 2, 4, and 8 form an inclusive Hillis-Steele
|
|||
// scan without carrying values backward across the start of the vector.
|
|||
values += Vector512.Shuffle(values, Vector512.Create(16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)); |
|||
values += Vector512.Shuffle(values, Vector512.Create(16, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)); |
|||
values += Vector512.Shuffle(values, Vector512.Create(16, 16, 16, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); |
|||
return values + Vector512.Shuffle(values, Vector512.Create(16, 16, 16, 16, 16, 16, 16, 16, 0, 1, 2, 3, 4, 5, 6, 7)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes an inclusive prefix sum across eight signed lanes.
|
|||
/// </summary>
|
|||
/// <param name="values">The residual differences.</param>
|
|||
/// <returns>The accumulated residuals.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> PrefixSum(Vector256<int> values) |
|||
{ |
|||
// Out-of-range index eight supplies the zero lanes needed at each doubling step.
|
|||
values += Vector256.Shuffle(values, Vector256.Create(8, 0, 1, 2, 3, 4, 5, 6)); |
|||
values += Vector256.Shuffle(values, Vector256.Create(8, 8, 0, 1, 2, 3, 4, 5)); |
|||
return values + Vector256.Shuffle(values, Vector256.Create(8, 8, 8, 8, 0, 1, 2, 3)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes an inclusive prefix sum across four signed lanes.
|
|||
/// </summary>
|
|||
/// <param name="values">The residual differences.</param>
|
|||
/// <returns>The accumulated residuals.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> PrefixSum(Vector128<int> values) |
|||
{ |
|||
// Out-of-range index four supplies the zero lanes needed at distances one and two.
|
|||
values += Vector128.Shuffle(values, Vector128.Create(4, 0, 1, 2)); |
|||
return values + Vector128.Shuffle(values, Vector128.Create(4, 4, 0, 1)); |
|||
} |
|||
} |
|||
@ -1,51 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
internal static partial class HevcSampleAdaptiveOffsetFilter |
|||
{ |
|||
/// <summary>
|
|||
/// Classifies samples by one of thirty-two most-significant-value bands.
|
|||
/// </summary>
|
|||
private readonly struct BandOperator : ISampleClassifier |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesNeighbors => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<short> Classify( |
|||
Vector512<short> current, |
|||
Vector512<short> neighbor0, |
|||
Vector512<short> neighbor1, |
|||
in KernelParameters kernel) |
|||
=> (Vector512.ShiftRightArithmetic(current, kernel.BandShift) - Vector512.Create(kernel.BandPosition)) & Vector512.Create((short)31); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<short> Classify( |
|||
Vector256<short> current, |
|||
Vector256<short> neighbor0, |
|||
Vector256<short> neighbor1, |
|||
in KernelParameters kernel) |
|||
=> (Vector256.ShiftRightArithmetic(current, kernel.BandShift) - Vector256.Create(kernel.BandPosition)) & Vector256.Create((short)31); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<short> Classify( |
|||
Vector128<short> current, |
|||
Vector128<short> neighbor0, |
|||
Vector128<short> neighbor1, |
|||
in KernelParameters kernel) |
|||
=> (Vector128.ShiftRightArithmetic(current, kernel.BandShift) - Vector128.Create(kernel.BandPosition)) & Vector128.Create((short)31); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel) |
|||
=> ((current >> kernel.BandShift) - kernel.BandPosition) & 31; |
|||
} |
|||
} |
|||
@ -1,68 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
internal static partial class HevcSampleAdaptiveOffsetFilter |
|||
{ |
|||
/// <summary>
|
|||
/// Classifies samples by the sum of their signs relative to two directional neighbors.
|
|||
/// </summary>
|
|||
private readonly struct EdgeOperator : ISampleClassifier |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesNeighbors => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<short> Classify( |
|||
Vector512<short> current, |
|||
Vector512<short> neighbor0, |
|||
Vector512<short> neighbor1, |
|||
in KernelParameters kernel) |
|||
{ |
|||
// Each comparison pair produces -1, 0, or 1. Adding two maps the normative edge classes onto the
|
|||
// contiguous zero-through-four offset-table indices used by the selection kernel.
|
|||
Vector512<short> one = Vector512.Create((short)1); |
|||
Vector512<short> sign0 = (Vector512.GreaterThan(current, neighbor0) & one) - (Vector512.LessThan(current, neighbor0) & one); |
|||
Vector512<short> sign1 = (Vector512.GreaterThan(current, neighbor1) & one) - (Vector512.LessThan(current, neighbor1) & one); |
|||
return sign0 + sign1 + Vector512.Create((short)2); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<short> Classify( |
|||
Vector256<short> current, |
|||
Vector256<short> neighbor0, |
|||
Vector256<short> neighbor1, |
|||
in KernelParameters kernel) |
|||
{ |
|||
Vector256<short> one = Vector256.Create((short)1); |
|||
Vector256<short> sign0 = (Vector256.GreaterThan(current, neighbor0) & one) - (Vector256.LessThan(current, neighbor0) & one); |
|||
Vector256<short> sign1 = (Vector256.GreaterThan(current, neighbor1) & one) - (Vector256.LessThan(current, neighbor1) & one); |
|||
return sign0 + sign1 + Vector256.Create((short)2); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<short> Classify( |
|||
Vector128<short> current, |
|||
Vector128<short> neighbor0, |
|||
Vector128<short> neighbor1, |
|||
in KernelParameters kernel) |
|||
{ |
|||
Vector128<short> one = Vector128.Create((short)1); |
|||
Vector128<short> sign0 = (Vector128.GreaterThan(current, neighbor0) & one) - (Vector128.LessThan(current, neighbor0) & one); |
|||
Vector128<short> sign1 = (Vector128.GreaterThan(current, neighbor1) & one) - (Vector128.LessThan(current, neighbor1) & one); |
|||
return sign0 + sign1 + Vector128.Create((short)2); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel) |
|||
=> Math.Sign(current - neighbor0) + Math.Sign(current - neighbor1) + 2; |
|||
} |
|||
} |
|||
@ -1,72 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
internal static partial class HevcSampleAdaptiveOffsetFilter |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the sample classifier shared by the SIMD row traversal and scalar tail.
|
|||
/// </summary>
|
|||
private interface ISampleClassifier |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether classification reads the two neighboring sample rows.
|
|||
/// </summary>
|
|||
public static abstract bool UsesNeighbors { get; } |
|||
|
|||
/// <summary>
|
|||
/// Classifies thirty-two current samples against their two classifier inputs.
|
|||
/// </summary>
|
|||
/// <param name="current">The current sample lanes.</param>
|
|||
/// <param name="neighbor0">The first neighboring sample lanes.</param>
|
|||
/// <param name="neighbor1">The second neighboring sample lanes.</param>
|
|||
/// <param name="kernel">The scaled offset and band-class state.</param>
|
|||
/// <returns>The zero-based offset-table indices.</returns>
|
|||
public static abstract Vector512<short> Classify( |
|||
Vector512<short> current, |
|||
Vector512<short> neighbor0, |
|||
Vector512<short> neighbor1, |
|||
in KernelParameters kernel); |
|||
|
|||
/// <summary>
|
|||
/// Classifies sixteen current samples against their two classifier inputs.
|
|||
/// </summary>
|
|||
/// <param name="current">The current sample lanes.</param>
|
|||
/// <param name="neighbor0">The first neighboring sample lanes.</param>
|
|||
/// <param name="neighbor1">The second neighboring sample lanes.</param>
|
|||
/// <param name="kernel">The scaled offset and band-class state.</param>
|
|||
/// <returns>The zero-based offset-table indices.</returns>
|
|||
public static abstract Vector256<short> Classify( |
|||
Vector256<short> current, |
|||
Vector256<short> neighbor0, |
|||
Vector256<short> neighbor1, |
|||
in KernelParameters kernel); |
|||
|
|||
/// <summary>
|
|||
/// Classifies eight current samples against their two classifier inputs.
|
|||
/// </summary>
|
|||
/// <param name="current">The current sample lanes.</param>
|
|||
/// <param name="neighbor0">The first neighboring sample lanes.</param>
|
|||
/// <param name="neighbor1">The second neighboring sample lanes.</param>
|
|||
/// <param name="kernel">The scaled offset and band-class state.</param>
|
|||
/// <returns>The zero-based offset-table indices.</returns>
|
|||
public static abstract Vector128<short> Classify( |
|||
Vector128<short> current, |
|||
Vector128<short> neighbor0, |
|||
Vector128<short> neighbor1, |
|||
in KernelParameters kernel); |
|||
|
|||
/// <summary>
|
|||
/// Classifies one current sample against its two classifier inputs.
|
|||
/// </summary>
|
|||
/// <param name="current">The current sample.</param>
|
|||
/// <param name="neighbor0">The first neighboring sample.</param>
|
|||
/// <param name="neighbor1">The second neighboring sample.</param>
|
|||
/// <param name="kernel">The scaled offset and band-class state.</param>
|
|||
/// <returns>The zero-based offset-table index.</returns>
|
|||
public static abstract int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel); |
|||
} |
|||
} |
|||
@ -1,581 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Applies HEVC sample-adaptive offsets to reconstructed component blocks.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Each SIMD lane classifies one reconstructed sample. Band-offset operators derive the class directly from the current
|
|||
/// value, while edge-offset operators compare aligned lanes from the two neighboring coordinates. The resulting class
|
|||
/// indices select one of the signaled offsets, after which addition and bit-depth clipping remain lane-wise. A scalar
|
|||
/// continuation handles only incomplete vectors at picture edges.
|
|||
/// </remarks>
|
|||
internal static partial class HevcSampleAdaptiveOffsetFilter |
|||
{ |
|||
/// <summary>
|
|||
/// Applies one resolved sample-adaptive-offset mode to a component coding-tree block.
|
|||
/// </summary>
|
|||
/// <param name="source">The immutable pre-SAO picture used for every classification.</param>
|
|||
/// <param name="destination">The picture receiving filtered samples.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The block's left coordinate in component samples.</param>
|
|||
/// <param name="y">The block's top coordinate in component samples.</param>
|
|||
/// <param name="width">The block width in component samples.</param>
|
|||
/// <param name="height">The block height in component samples.</param>
|
|||
/// <param name="parameters">The resolved coded offsets and classifier.</param>
|
|||
/// <param name="offsetScaleLog2">The component offset scale from the picture range-extension parameters.</param>
|
|||
/// <param name="leftAvailable">Whether classification may read the block immediately to the left.</param>
|
|||
/// <param name="rightAvailable">Whether classification may read the block immediately to the right.</param>
|
|||
/// <param name="aboveAvailable">Whether classification may read the block immediately above.</param>
|
|||
/// <param name="belowAvailable">Whether classification may read the block immediately below.</param>
|
|||
/// <param name="aboveLeftAvailable">Whether classification may read the upper-left diagonal block.</param>
|
|||
/// <param name="aboveRightAvailable">Whether classification may read the upper-right diagonal block.</param>
|
|||
/// <param name="belowLeftAvailable">Whether classification may read the lower-left diagonal block.</param>
|
|||
/// <param name="belowRightAvailable">Whether classification may read the lower-right diagonal block.</param>
|
|||
public static void ApplyBlock( |
|||
HevcPictureBuffer source, |
|||
HevcPictureBuffer destination, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int width, |
|||
int height, |
|||
in HevcSampleAdaptiveOffsetParameters parameters, |
|||
int offsetScaleLog2, |
|||
bool leftAvailable, |
|||
bool rightAvailable, |
|||
bool aboveAvailable, |
|||
bool belowAvailable, |
|||
bool aboveLeftAvailable, |
|||
bool aboveRightAvailable, |
|||
bool belowLeftAvailable, |
|||
bool belowRightAvailable) |
|||
{ |
|||
if (parameters.Type == HevcSampleAdaptiveOffsetType.Off) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
KernelParameters kernel = new(parameters, source.GetBitDepth(plane), offsetScaleLog2); |
|||
switch (parameters.Type) |
|||
{ |
|||
case HevcSampleAdaptiveOffsetType.Band: |
|||
ApplyBand(source, destination, plane, x, y, width, height, in kernel); |
|||
break; |
|||
case HevcSampleAdaptiveOffsetType.EdgeHorizontal: |
|||
ApplyHorizontalEdges(source, destination, plane, x, y, width, height, leftAvailable, rightAvailable, in kernel); |
|||
break; |
|||
case HevcSampleAdaptiveOffsetType.EdgeVertical: |
|||
ApplyVerticalEdges(source, destination, plane, x, y, width, height, aboveAvailable, belowAvailable, in kernel); |
|||
break; |
|||
case HevcSampleAdaptiveOffsetType.EdgeDescending: |
|||
ApplyDescendingEdges( |
|||
source, |
|||
destination, |
|||
plane, |
|||
x, |
|||
y, |
|||
width, |
|||
height, |
|||
leftAvailable, |
|||
rightAvailable, |
|||
aboveAvailable, |
|||
belowAvailable, |
|||
aboveLeftAvailable, |
|||
belowRightAvailable, |
|||
in kernel); |
|||
break; |
|||
case HevcSampleAdaptiveOffsetType.EdgeAscending: |
|||
ApplyAscendingEdges( |
|||
source, |
|||
destination, |
|||
plane, |
|||
x, |
|||
y, |
|||
width, |
|||
height, |
|||
leftAvailable, |
|||
rightAvailable, |
|||
aboveAvailable, |
|||
belowAvailable, |
|||
aboveRightAvailable, |
|||
belowLeftAvailable, |
|||
in kernel); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies band offsets to every sample in a component block.
|
|||
/// </summary>
|
|||
/// <param name="source">The immutable pre-SAO picture.</param>
|
|||
/// <param name="destination">The destination picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The block's left coordinate.</param>
|
|||
/// <param name="y">The block's top coordinate.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
/// <param name="kernel">The scaled offset and band-class state.</param>
|
|||
private static void ApplyBand( |
|||
HevcPictureBuffer source, |
|||
HevcPictureBuffer destination, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int width, |
|||
int height, |
|||
in KernelParameters kernel) |
|||
{ |
|||
for (int row = y; row < y + height; row++) |
|||
{ |
|||
ReadOnlySpan<ushort> sourceRow = source.GetRowSpan(plane, row).Slice(x, width); |
|||
Span<ushort> destinationRow = destination.GetRowSpan(plane, row).Slice(x, width); |
|||
|
|||
// Band classification depends only on the current sample. The closed classifier's UsesNeighbors value removes
|
|||
// the two neighbor loads when this generic traversal is specialized for BandOperator.
|
|||
ApplyRow<BandOperator>(sourceRow, sourceRow, sourceRow, destinationRow, in kernel); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies horizontal edge offsets within the available left and right boundaries.
|
|||
/// </summary>
|
|||
/// <param name="source">The immutable pre-SAO picture.</param>
|
|||
/// <param name="destination">The destination picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The block's left coordinate.</param>
|
|||
/// <param name="y">The block's top coordinate.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
/// <param name="leftAvailable">Whether the left neighboring block is available.</param>
|
|||
/// <param name="rightAvailable">Whether the right neighboring block is available.</param>
|
|||
/// <param name="kernel">The scaled offset state.</param>
|
|||
private static void ApplyHorizontalEdges( |
|||
HevcPictureBuffer source, |
|||
HevcPictureBuffer destination, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int width, |
|||
int height, |
|||
bool leftAvailable, |
|||
bool rightAvailable, |
|||
in KernelParameters kernel) |
|||
{ |
|||
int start = x + (leftAvailable ? 0 : 1); |
|||
int end = x + width - (rightAvailable ? 0 : 1); |
|||
int count = end - start; |
|||
if (count <= 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
for (int row = y; row < y + height; row++) |
|||
{ |
|||
ReadOnlySpan<ushort> sourceRow = source.GetRowSpan(plane, row); |
|||
ApplyRow<EdgeOperator>( |
|||
sourceRow.Slice(start, count), |
|||
sourceRow.Slice(start - 1, count), |
|||
sourceRow.Slice(start + 1, count), |
|||
destination.GetRowSpan(plane, row).Slice(start, count), |
|||
in kernel); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies vertical edge offsets within the available upper and lower boundaries.
|
|||
/// </summary>
|
|||
/// <param name="source">The immutable pre-SAO picture.</param>
|
|||
/// <param name="destination">The destination picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The block's left coordinate.</param>
|
|||
/// <param name="y">The block's top coordinate.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
/// <param name="aboveAvailable">Whether the upper neighboring block is available.</param>
|
|||
/// <param name="belowAvailable">Whether the lower neighboring block is available.</param>
|
|||
/// <param name="kernel">The scaled offset state.</param>
|
|||
private static void ApplyVerticalEdges( |
|||
HevcPictureBuffer source, |
|||
HevcPictureBuffer destination, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int width, |
|||
int height, |
|||
bool aboveAvailable, |
|||
bool belowAvailable, |
|||
in KernelParameters kernel) |
|||
{ |
|||
int start = y + (aboveAvailable ? 0 : 1); |
|||
int end = y + height - (belowAvailable ? 0 : 1); |
|||
for (int row = start; row < end; row++) |
|||
{ |
|||
ApplyRow<EdgeOperator>( |
|||
source.GetRowSpan(plane, row).Slice(x, width), |
|||
source.GetRowSpan(plane, row - 1).Slice(x, width), |
|||
source.GetRowSpan(plane, row + 1).Slice(x, width), |
|||
destination.GetRowSpan(plane, row).Slice(x, width), |
|||
in kernel); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies descending-diagonal edge offsets within the eight resolved block boundaries.
|
|||
/// </summary>
|
|||
/// <param name="source">The immutable pre-SAO picture.</param>
|
|||
/// <param name="destination">The destination picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The block's left coordinate.</param>
|
|||
/// <param name="y">The block's top coordinate.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
/// <param name="leftAvailable">Whether the left neighboring block is available.</param>
|
|||
/// <param name="rightAvailable">Whether the right neighboring block is available.</param>
|
|||
/// <param name="aboveAvailable">Whether the upper neighboring block is available.</param>
|
|||
/// <param name="belowAvailable">Whether the lower neighboring block is available.</param>
|
|||
/// <param name="aboveLeftAvailable">Whether the upper-left neighboring block is available.</param>
|
|||
/// <param name="belowRightAvailable">Whether the lower-right neighboring block is available.</param>
|
|||
/// <param name="kernel">The scaled offset state.</param>
|
|||
private static void ApplyDescendingEdges( |
|||
HevcPictureBuffer source, |
|||
HevcPictureBuffer destination, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int width, |
|||
int height, |
|||
bool leftAvailable, |
|||
bool rightAvailable, |
|||
bool aboveAvailable, |
|||
bool belowAvailable, |
|||
bool aboveLeftAvailable, |
|||
bool belowRightAvailable, |
|||
in KernelParameters kernel) |
|||
{ |
|||
int commonStart = x + (leftAvailable ? 0 : 1); |
|||
int commonEnd = x + width - (rightAvailable ? 0 : 1); |
|||
int lastRow = y + height - 1; |
|||
for (int row = y; row <= lastRow; row++) |
|||
{ |
|||
int start = commonStart; |
|||
int end = commonEnd; |
|||
if (row == y) |
|||
{ |
|||
start = aboveLeftAvailable ? x : x + 1; |
|||
end = aboveAvailable ? commonEnd : x + 1; |
|||
} |
|||
|
|||
if (row == lastRow) |
|||
{ |
|||
start = Math.Max(start, belowAvailable ? commonStart : x + width - 1); |
|||
end = Math.Min(end, belowRightAvailable ? x + width : x + width - 1); |
|||
} |
|||
|
|||
int count = end - start; |
|||
if (count <= 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
ApplyRow<EdgeOperator>( |
|||
source.GetRowSpan(plane, row).Slice(start, count), |
|||
source.GetRowSpan(plane, row - 1).Slice(start - 1, count), |
|||
source.GetRowSpan(plane, row + 1).Slice(start + 1, count), |
|||
destination.GetRowSpan(plane, row).Slice(start, count), |
|||
in kernel); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies ascending-diagonal edge offsets within the eight resolved block boundaries.
|
|||
/// </summary>
|
|||
/// <param name="source">The immutable pre-SAO picture.</param>
|
|||
/// <param name="destination">The destination picture.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="x">The block's left coordinate.</param>
|
|||
/// <param name="y">The block's top coordinate.</param>
|
|||
/// <param name="width">The block width.</param>
|
|||
/// <param name="height">The block height.</param>
|
|||
/// <param name="leftAvailable">Whether the left neighboring block is available.</param>
|
|||
/// <param name="rightAvailable">Whether the right neighboring block is available.</param>
|
|||
/// <param name="aboveAvailable">Whether the upper neighboring block is available.</param>
|
|||
/// <param name="belowAvailable">Whether the lower neighboring block is available.</param>
|
|||
/// <param name="aboveRightAvailable">Whether the upper-right neighboring block is available.</param>
|
|||
/// <param name="belowLeftAvailable">Whether the lower-left neighboring block is available.</param>
|
|||
/// <param name="kernel">The scaled offset state.</param>
|
|||
private static void ApplyAscendingEdges( |
|||
HevcPictureBuffer source, |
|||
HevcPictureBuffer destination, |
|||
HevcPlane plane, |
|||
int x, |
|||
int y, |
|||
int width, |
|||
int height, |
|||
bool leftAvailable, |
|||
bool rightAvailable, |
|||
bool aboveAvailable, |
|||
bool belowAvailable, |
|||
bool aboveRightAvailable, |
|||
bool belowLeftAvailable, |
|||
in KernelParameters kernel) |
|||
{ |
|||
int commonStart = x + (leftAvailable ? 0 : 1); |
|||
int commonEnd = x + width - (rightAvailable ? 0 : 1); |
|||
int lastRow = y + height - 1; |
|||
for (int row = y; row <= lastRow; row++) |
|||
{ |
|||
int start = commonStart; |
|||
int end = commonEnd; |
|||
if (row == y) |
|||
{ |
|||
start = aboveAvailable ? commonStart : x + width - 1; |
|||
end = aboveRightAvailable ? x + width : x + width - 1; |
|||
} |
|||
|
|||
if (row == lastRow) |
|||
{ |
|||
start = Math.Max(start, belowLeftAvailable ? x : x + 1); |
|||
end = Math.Min(end, belowAvailable ? commonEnd : x + 1); |
|||
} |
|||
|
|||
int count = end - start; |
|||
if (count <= 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
ApplyRow<EdgeOperator>( |
|||
source.GetRowSpan(plane, row).Slice(start, count), |
|||
source.GetRowSpan(plane, row - 1).Slice(start + 1, count), |
|||
source.GetRowSpan(plane, row + 1).Slice(start - 1, count), |
|||
destination.GetRowSpan(plane, row).Slice(start, count), |
|||
in kernel); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one closed classifier to a contiguous row range using every accelerated SIMD width before the scalar tail.
|
|||
/// </summary>
|
|||
/// <typeparam name="TClassifier">The band or edge classifier selected before entering the row.</typeparam>
|
|||
/// <param name="current">The current source samples.</param>
|
|||
/// <param name="neighbor0">The first classifier input samples.</param>
|
|||
/// <param name="neighbor1">The second classifier input samples.</param>
|
|||
/// <param name="destination">The destination samples.</param>
|
|||
/// <param name="kernel">The scaled offset and clamp state.</param>
|
|||
private static void ApplyRow<TClassifier>( |
|||
ReadOnlySpan<ushort> current, |
|||
ReadOnlySpan<ushort> neighbor0, |
|||
ReadOnlySpan<ushort> neighbor1, |
|||
Span<ushort> destination, |
|||
in KernelParameters kernel) |
|||
where TClassifier : struct, ISampleClassifier |
|||
{ |
|||
ref ushort currentBase = ref MemoryMarshal.GetReference(current); |
|||
ref ushort neighbor0Base = ref MemoryMarshal.GetReference(neighbor0); |
|||
ref ushort neighbor1Base = ref MemoryMarshal.GetReference(neighbor1); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
int index = 0; |
|||
|
|||
// HEVC's exposed 8/10/12-bit profiles keep every sample and scaled offset inside Int16. Signed lanes therefore
|
|||
// provide comparisons, addition, and saturation without the two widening stages an Int32 implementation needs.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
Vector512<short> minimum = Vector512<short>.Zero; |
|||
Vector512<short> maximum = Vector512.Create(kernel.Maximum); |
|||
for (; index <= current.Length - Vector512<ushort>.Count; index += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<short> value = Vector512.LoadUnsafe(ref currentBase, (nuint)index).AsInt16(); |
|||
Vector512<short> first = TClassifier.UsesNeighbors ? Vector512.LoadUnsafe(ref neighbor0Base, (nuint)index).AsInt16() : default; |
|||
Vector512<short> second = TClassifier.UsesNeighbors ? Vector512.LoadUnsafe(ref neighbor1Base, (nuint)index).AsInt16() : default; |
|||
Vector512<short> classes = TClassifier.Classify(value, first, second, in kernel); |
|||
Vector512<short> filtered = Vector512.Clamp(value + SelectOffset(classes, in kernel), minimum, maximum); |
|||
filtered.AsUInt16().StoreUnsafe(ref destinationBase, (nuint)index); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<short> minimum = Vector256<short>.Zero; |
|||
Vector256<short> maximum = Vector256.Create(kernel.Maximum); |
|||
for (; index <= current.Length - Vector256<ushort>.Count; index += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<short> value = Vector256.LoadUnsafe(ref currentBase, (nuint)index).AsInt16(); |
|||
Vector256<short> first = TClassifier.UsesNeighbors ? Vector256.LoadUnsafe(ref neighbor0Base, (nuint)index).AsInt16() : default; |
|||
Vector256<short> second = TClassifier.UsesNeighbors ? Vector256.LoadUnsafe(ref neighbor1Base, (nuint)index).AsInt16() : default; |
|||
Vector256<short> classes = TClassifier.Classify(value, first, second, in kernel); |
|||
Vector256<short> filtered = Vector256.Clamp(value + SelectOffset(classes, in kernel), minimum, maximum); |
|||
filtered.AsUInt16().StoreUnsafe(ref destinationBase, (nuint)index); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<short> minimum = Vector128<short>.Zero; |
|||
Vector128<short> maximum = Vector128.Create(kernel.Maximum); |
|||
for (; index <= current.Length - Vector128<ushort>.Count; index += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<short> value = Vector128.LoadUnsafe(ref currentBase, (nuint)index).AsInt16(); |
|||
Vector128<short> first = TClassifier.UsesNeighbors ? Vector128.LoadUnsafe(ref neighbor0Base, (nuint)index).AsInt16() : default; |
|||
Vector128<short> second = TClassifier.UsesNeighbors ? Vector128.LoadUnsafe(ref neighbor1Base, (nuint)index).AsInt16() : default; |
|||
Vector128<short> classes = TClassifier.Classify(value, first, second, in kernel); |
|||
Vector128<short> filtered = Vector128.Clamp(value + SelectOffset(classes, in kernel), minimum, maximum); |
|||
filtered.AsUInt16().StoreUnsafe(ref destinationBase, (nuint)index); |
|||
} |
|||
} |
|||
|
|||
for (; index < current.Length; index++) |
|||
{ |
|||
short currentValue = (short)Unsafe.Add(ref currentBase, index); |
|||
short first = TClassifier.UsesNeighbors ? (short)Unsafe.Add(ref neighbor0Base, index) : default; |
|||
short second = TClassifier.UsesNeighbors ? (short)Unsafe.Add(ref neighbor1Base, index) : default; |
|||
int offsetIndex = TClassifier.Classify(currentValue, first, second, in kernel); |
|||
|
|||
int filtered = Unsafe.Add(ref currentBase, index) + SelectOffset(offsetIndex, in kernel); |
|||
Unsafe.Add(ref destinationBase, index) = (ushort)Math.Clamp(filtered, 0, kernel.Maximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects one of five signed offsets for thirty-two classifier indices.
|
|||
/// </summary>
|
|||
/// <param name="classes">The zero-based classifier indices.</param>
|
|||
/// <param name="kernel">The five scaled offsets.</param>
|
|||
/// <returns>The selected signed offset in every lane.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<short> SelectOffset(Vector512<short> classes, in KernelParameters kernel) |
|||
{ |
|||
// The table contains only five values and there is no portable 16-bit gather. A comparison chain keeps every
|
|||
// class lane in registers and leaves unrecognized classes at the required zero offset.
|
|||
Vector512<short> selected = Vector512<short>.Zero; |
|||
selected = Vector512.ConditionalSelect(Vector512.Equals(classes, Vector512.Create((short)0)), Vector512.Create(kernel.Offset0), selected); |
|||
selected = Vector512.ConditionalSelect(Vector512.Equals(classes, Vector512.Create((short)1)), Vector512.Create(kernel.Offset1), selected); |
|||
selected = Vector512.ConditionalSelect(Vector512.Equals(classes, Vector512.Create((short)2)), Vector512.Create(kernel.Offset2), selected); |
|||
selected = Vector512.ConditionalSelect(Vector512.Equals(classes, Vector512.Create((short)3)), Vector512.Create(kernel.Offset3), selected); |
|||
return Vector512.ConditionalSelect(Vector512.Equals(classes, Vector512.Create((short)4)), Vector512.Create(kernel.Offset4), selected); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects one of five signed offsets for sixteen classifier indices.
|
|||
/// </summary>
|
|||
/// <param name="classes">The zero-based classifier indices.</param>
|
|||
/// <param name="kernel">The five scaled offsets.</param>
|
|||
/// <returns>The selected signed offset in every lane.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<short> SelectOffset(Vector256<short> classes, in KernelParameters kernel) |
|||
{ |
|||
Vector256<short> selected = Vector256<short>.Zero; |
|||
selected = Vector256.ConditionalSelect(Vector256.Equals(classes, Vector256.Create((short)0)), Vector256.Create(kernel.Offset0), selected); |
|||
selected = Vector256.ConditionalSelect(Vector256.Equals(classes, Vector256.Create((short)1)), Vector256.Create(kernel.Offset1), selected); |
|||
selected = Vector256.ConditionalSelect(Vector256.Equals(classes, Vector256.Create((short)2)), Vector256.Create(kernel.Offset2), selected); |
|||
selected = Vector256.ConditionalSelect(Vector256.Equals(classes, Vector256.Create((short)3)), Vector256.Create(kernel.Offset3), selected); |
|||
return Vector256.ConditionalSelect(Vector256.Equals(classes, Vector256.Create((short)4)), Vector256.Create(kernel.Offset4), selected); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects one of five signed offsets for eight classifier indices.
|
|||
/// </summary>
|
|||
/// <param name="classes">The zero-based classifier indices.</param>
|
|||
/// <param name="kernel">The five scaled offsets.</param>
|
|||
/// <returns>The selected signed offset in every lane.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<short> SelectOffset(Vector128<short> classes, in KernelParameters kernel) |
|||
{ |
|||
Vector128<short> selected = Vector128<short>.Zero; |
|||
selected = Vector128.ConditionalSelect(Vector128.Equals(classes, Vector128.Create((short)0)), Vector128.Create(kernel.Offset0), selected); |
|||
selected = Vector128.ConditionalSelect(Vector128.Equals(classes, Vector128.Create((short)1)), Vector128.Create(kernel.Offset1), selected); |
|||
selected = Vector128.ConditionalSelect(Vector128.Equals(classes, Vector128.Create((short)2)), Vector128.Create(kernel.Offset2), selected); |
|||
selected = Vector128.ConditionalSelect(Vector128.Equals(classes, Vector128.Create((short)3)), Vector128.Create(kernel.Offset3), selected); |
|||
return Vector128.ConditionalSelect(Vector128.Equals(classes, Vector128.Create((short)4)), Vector128.Create(kernel.Offset4), selected); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects one of five signed offsets for one classifier index.
|
|||
/// </summary>
|
|||
/// <param name="classification">The zero-based classifier index.</param>
|
|||
/// <param name="kernel">The five scaled offsets.</param>
|
|||
/// <returns>The selected signed offset, or zero for an unmodified class.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int SelectOffset(int classification, in KernelParameters kernel) |
|||
=> classification switch |
|||
{ |
|||
0 => kernel.Offset0, |
|||
1 => kernel.Offset1, |
|||
2 => kernel.Offset2, |
|||
3 => kernel.Offset3, |
|||
4 => kernel.Offset4, |
|||
_ => 0, |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Contains one block's scaled offsets and invariant classification values.
|
|||
/// </summary>
|
|||
private readonly struct KernelParameters |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="KernelParameters"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="parameters">The decoded signed offsets.</param>
|
|||
/// <param name="bitDepth">The component sample precision.</param>
|
|||
/// <param name="offsetScaleLog2">The component offset scale.</param>
|
|||
public KernelParameters(in HevcSampleAdaptiveOffsetParameters parameters, int bitDepth, int offsetScaleLog2) |
|||
{ |
|||
// Range Extensions scales each coded offset once before filtering. Hoisting the shifts here keeps the
|
|||
// classification loops to comparisons, table selection, one addition, and saturation.
|
|||
this.Offset0 = (short)(parameters.Offset0 << offsetScaleLog2); |
|||
this.Offset1 = (short)(parameters.Offset1 << offsetScaleLog2); |
|||
this.Offset2 = (short)(parameters.Offset2 << offsetScaleLog2); |
|||
this.Offset3 = (short)(parameters.Offset3 << offsetScaleLog2); |
|||
this.Offset4 = (short)(parameters.Offset4 << offsetScaleLog2); |
|||
this.BandPosition = (short)parameters.BandPosition; |
|||
this.BandShift = bitDepth - 5; |
|||
this.Maximum = (short)((1 << bitDepth) - 1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the first scaled class offset.
|
|||
/// </summary>
|
|||
public short Offset0 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the second scaled class offset.
|
|||
/// </summary>
|
|||
public short Offset1 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the third scaled class offset.
|
|||
/// </summary>
|
|||
public short Offset2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the fourth scaled class offset.
|
|||
/// </summary>
|
|||
public short Offset3 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the fifth scaled class offset.
|
|||
/// </summary>
|
|||
public short Offset4 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the first active band class.
|
|||
/// </summary>
|
|||
public short BandPosition { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of low sample bits discarded to form one of thirty-two band classes.
|
|||
/// </summary>
|
|||
public int BandShift { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the largest component sample value.
|
|||
/// </summary>
|
|||
public short Maximum { get; } |
|||
} |
|||
} |
|||
@ -1,464 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the HEVC sample-adaptive-offset classifier selected for one component coding-tree block.
|
|||
/// </summary>
|
|||
internal enum HevcSampleAdaptiveOffsetType : byte |
|||
{ |
|||
/// <summary>
|
|||
/// No sample-adaptive offset is applied.
|
|||
/// </summary>
|
|||
Off, |
|||
|
|||
/// <summary>
|
|||
/// Samples are classified by their most-significant sample-value band.
|
|||
/// </summary>
|
|||
Band, |
|||
|
|||
/// <summary>
|
|||
/// Samples are classified by horizontal neighboring samples.
|
|||
/// </summary>
|
|||
EdgeHorizontal, |
|||
|
|||
/// <summary>
|
|||
/// Samples are classified by vertical neighboring samples.
|
|||
/// </summary>
|
|||
EdgeVertical, |
|||
|
|||
/// <summary>
|
|||
/// Samples are classified by neighbors on the descending diagonal.
|
|||
/// </summary>
|
|||
EdgeDescending, |
|||
|
|||
/// <summary>
|
|||
/// Samples are classified by neighbors on the ascending diagonal.
|
|||
/// </summary>
|
|||
EdgeAscending, |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the resolved HEVC sample-adaptive offsets for one component coding-tree block.
|
|||
/// </summary>
|
|||
internal readonly struct HevcSampleAdaptiveOffsetParameters |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcSampleAdaptiveOffsetParameters"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="type">The sample classifier.</param>
|
|||
/// <param name="bandPosition">The first of four consecutive band classes.</param>
|
|||
/// <param name="offset0">The first band or full-valley offset.</param>
|
|||
/// <param name="offset1">The second band or half-valley offset.</param>
|
|||
/// <param name="offset2">The third band or plain-edge offset.</param>
|
|||
/// <param name="offset3">The fourth band or half-peak offset.</param>
|
|||
/// <param name="offset4">The full-peak offset.</param>
|
|||
public HevcSampleAdaptiveOffsetParameters( |
|||
HevcSampleAdaptiveOffsetType type, |
|||
int bandPosition, |
|||
int offset0, |
|||
int offset1, |
|||
int offset2, |
|||
int offset3, |
|||
int offset4) |
|||
{ |
|||
this.Type = type; |
|||
this.BandPosition = bandPosition; |
|||
this.Offset0 = offset0; |
|||
this.Offset1 = offset1; |
|||
this.Offset2 = offset2; |
|||
this.Offset3 = offset3; |
|||
this.Offset4 = offset4; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the sample classifier.
|
|||
/// </summary>
|
|||
public HevcSampleAdaptiveOffsetType Type { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the first of four consecutive band classes.
|
|||
/// </summary>
|
|||
public int BandPosition { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the first band or full-valley offset.
|
|||
/// </summary>
|
|||
public int Offset0 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the second band or half-valley offset.
|
|||
/// </summary>
|
|||
public int Offset1 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the third band or plain-edge offset.
|
|||
/// </summary>
|
|||
public int Offset2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the fourth band or half-peak offset.
|
|||
/// </summary>
|
|||
public int Offset3 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the full-peak offset.
|
|||
/// </summary>
|
|||
public int Offset4 { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Identifies the slice and tile governing in-loop filtering for one coding-tree block.
|
|||
/// </summary>
|
|||
internal readonly struct HevcLoopFilterRegion |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcLoopFilterRegion"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="sliceStartAddressInTileScan">The first coding-tree block of the independent slice in tile-scan order.</param>
|
|||
/// <param name="tileIndex">The zero-based tile index.</param>
|
|||
/// <param name="loopFilterAcrossSlicesEnabled">Whether the governing slice permits filtering across its slice boundary.</param>
|
|||
/// <param name="deblockingFilterDisabled">Whether the governing slice disables deblocking.</param>
|
|||
/// <param name="deblockingFilterBetaOffsetDiv2">Half the slice beta-threshold offset.</param>
|
|||
/// <param name="deblockingFilterTcOffsetDiv2">Half the slice clipping-threshold offset.</param>
|
|||
public HevcLoopFilterRegion( |
|||
int sliceStartAddressInTileScan, |
|||
int tileIndex, |
|||
bool loopFilterAcrossSlicesEnabled, |
|||
bool deblockingFilterDisabled, |
|||
int deblockingFilterBetaOffsetDiv2, |
|||
int deblockingFilterTcOffsetDiv2) |
|||
{ |
|||
this.SliceStartAddressInTileScan = sliceStartAddressInTileScan; |
|||
this.TileIndex = tileIndex; |
|||
this.LoopFilterAcrossSlicesEnabled = loopFilterAcrossSlicesEnabled; |
|||
this.DeblockingFilterDisabled = deblockingFilterDisabled; |
|||
this.DeblockingFilterBetaOffsetDiv2 = deblockingFilterBetaOffsetDiv2; |
|||
this.DeblockingFilterTcOffsetDiv2 = deblockingFilterTcOffsetDiv2; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the first coding-tree block of the independent slice in tile-scan order.
|
|||
/// </summary>
|
|||
public int SliceStartAddressInTileScan { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the zero-based tile index.
|
|||
/// </summary>
|
|||
public int TileIndex { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the governing slice permits filtering across its slice boundary.
|
|||
/// </summary>
|
|||
public bool LoopFilterAcrossSlicesEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the governing slice disables deblocking.
|
|||
/// </summary>
|
|||
public bool DeblockingFilterDisabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets half the governing slice's beta-threshold offset.
|
|||
/// </summary>
|
|||
public int DeblockingFilterBetaOffsetDiv2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets half the governing slice's clipping-threshold offset.
|
|||
/// </summary>
|
|||
public int DeblockingFilterTcOffsetDiv2 { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the eight coding-tree-block neighbor availability values used by HEVC in-loop filters.
|
|||
/// </summary>
|
|||
internal readonly struct HevcLoopFilterBoundaryAvailability |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcLoopFilterBoundaryAvailability"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="left">Whether the left block is available.</param>
|
|||
/// <param name="right">Whether the right block is available.</param>
|
|||
/// <param name="above">Whether the block above is available.</param>
|
|||
/// <param name="below">Whether the block below is available.</param>
|
|||
/// <param name="aboveLeft">Whether the upper-left block is available.</param>
|
|||
/// <param name="aboveRight">Whether the upper-right block is available.</param>
|
|||
/// <param name="belowLeft">Whether the lower-left block is available.</param>
|
|||
/// <param name="belowRight">Whether the lower-right block is available.</param>
|
|||
public HevcLoopFilterBoundaryAvailability( |
|||
bool left, |
|||
bool right, |
|||
bool above, |
|||
bool below, |
|||
bool aboveLeft, |
|||
bool aboveRight, |
|||
bool belowLeft, |
|||
bool belowRight) |
|||
{ |
|||
this.Left = left; |
|||
this.Right = right; |
|||
this.Above = above; |
|||
this.Below = below; |
|||
this.AboveLeft = aboveLeft; |
|||
this.AboveRight = aboveRight; |
|||
this.BelowLeft = belowLeft; |
|||
this.BelowRight = belowRight; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the left block is available.
|
|||
/// </summary>
|
|||
public bool Left { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the right block is available.
|
|||
/// </summary>
|
|||
public bool Right { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the block above is available.
|
|||
/// </summary>
|
|||
public bool Above { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the block below is available.
|
|||
/// </summary>
|
|||
public bool Below { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the upper-left block is available.
|
|||
/// </summary>
|
|||
public bool AboveLeft { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the upper-right block is available.
|
|||
/// </summary>
|
|||
public bool AboveRight { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the lower-left block is available.
|
|||
/// </summary>
|
|||
public bool BelowLeft { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the lower-right block is available.
|
|||
/// </summary>
|
|||
public bool BelowRight { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Owns resolved sample-adaptive-offset parameters and prediction and filter region identifiers for one picture.
|
|||
/// </summary>
|
|||
internal sealed class HevcSampleAdaptiveOffsetState : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Whether any decoded component block enables sample-adaptive offset.
|
|||
/// </summary>
|
|||
private bool hasEnabledParameters; |
|||
|
|||
/// <summary>
|
|||
/// The three component records for every raster-ordered coding-tree block.
|
|||
/// </summary>
|
|||
private readonly IMemoryOwner<HevcSampleAdaptiveOffsetParameters> parameters; |
|||
|
|||
/// <summary>
|
|||
/// The independent-slice and tile prediction region of every coding-tree block.
|
|||
/// </summary>
|
|||
private readonly IMemoryOwner<int> regions; |
|||
|
|||
/// <summary>
|
|||
/// The independent-slice and tile filter region of every coding-tree block and color plane.
|
|||
/// </summary>
|
|||
private readonly IMemoryOwner<HevcLoopFilterRegion> loopFilterRegions; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcSampleAdaptiveOffsetState"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration providing pooled picture state.</param>
|
|||
/// <param name="codingTreeBlockCount">The raster-ordered coding-tree-block count.</param>
|
|||
public HevcSampleAdaptiveOffsetState(Configuration configuration, int codingTreeBlockCount) |
|||
{ |
|||
IMemoryOwner<HevcSampleAdaptiveOffsetParameters>? parameters = null; |
|||
IMemoryOwner<int>? regions = null; |
|||
IMemoryOwner<HevcLoopFilterRegion>? loopFilterRegions = null; |
|||
try |
|||
{ |
|||
parameters = configuration.MemoryAllocator.Allocate<HevcSampleAdaptiveOffsetParameters>(codingTreeBlockCount * 3); |
|||
|
|||
// Slice headers can disable SAO independently for luma and chroma. Initialize every component record to Off
|
|||
// so an enabled component never causes untouched records from pooled memory to enter the picture-level pass.
|
|||
parameters.Memory.Span.Clear(); |
|||
regions = configuration.MemoryAllocator.Allocate<int>(codingTreeBlockCount * 3); |
|||
loopFilterRegions = configuration.MemoryAllocator.Allocate<HevcLoopFilterRegion>(codingTreeBlockCount * 3); |
|||
|
|||
this.parameters = parameters; |
|||
this.regions = regions; |
|||
this.loopFilterRegions = loopFilterRegions; |
|||
} |
|||
catch |
|||
{ |
|||
loopFilterRegions?.Dispose(); |
|||
regions?.Dispose(); |
|||
parameters?.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether any component block enables sample-adaptive offset.
|
|||
/// </summary>
|
|||
public bool HasEnabledParameters => this.hasEnabledParameters; |
|||
|
|||
/// <summary>
|
|||
/// Gets the resolved component parameters for one coding-tree block.
|
|||
/// </summary>
|
|||
/// <param name="rasterAddress">The raster-scan coding-tree-block address.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <returns>The resolved sample-adaptive-offset parameters.</returns>
|
|||
public HevcSampleAdaptiveOffsetParameters Get(int rasterAddress, HevcPlane plane) |
|||
=> this.parameters.Memory.Span[(rasterAddress * 3) + (int)plane]; |
|||
|
|||
/// <summary>
|
|||
/// Stores resolved component parameters for one coding-tree block.
|
|||
/// </summary>
|
|||
/// <param name="rasterAddress">The raster-scan coding-tree-block address.</param>
|
|||
/// <param name="plane">The component plane.</param>
|
|||
/// <param name="value">The resolved sample-adaptive-offset parameters.</param>
|
|||
public void Set(int rasterAddress, HevcPlane plane, HevcSampleAdaptiveOffsetParameters value) |
|||
{ |
|||
this.parameters.Memory.Span[(rasterAddress * 3) + (int)plane] = value; |
|||
this.hasEnabledParameters |= value.Type != HevcSampleAdaptiveOffsetType.Off; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets whether one coding-tree block belongs to the selected prediction region.
|
|||
/// </summary>
|
|||
/// <param name="rasterAddress">The raster-scan coding-tree-block address.</param>
|
|||
/// <param name="plane">The independently coded color plane, or luma for combined-plane coding.</param>
|
|||
/// <param name="regionId">The current independent-slice and tile prediction-region identifier.</param>
|
|||
/// <returns><see langword="true"/> when the block belongs to the region; otherwise, <see langword="false"/>.</returns>
|
|||
public bool IsInRegion(int rasterAddress, HevcPlane plane, int regionId) |
|||
=> this.regions.Memory.Span[(rasterAddress * 3) + (int)plane] == regionId; |
|||
|
|||
/// <summary>
|
|||
/// Records the prediction region after one coding-tree block's parameters are decoded.
|
|||
/// </summary>
|
|||
/// <param name="rasterAddress">The raster-scan coding-tree-block address.</param>
|
|||
/// <param name="plane">The independently coded color plane, or luma for combined-plane coding.</param>
|
|||
/// <param name="regionId">The positive prediction-region identifier.</param>
|
|||
public void SetRegion(int rasterAddress, HevcPlane plane, int regionId) |
|||
=> this.regions.Memory.Span[(rasterAddress * 3) + (int)plane] = regionId; |
|||
|
|||
/// <summary>
|
|||
/// Records the in-loop filter region for one coding-tree block.
|
|||
/// </summary>
|
|||
/// <param name="rasterAddress">The raster-scan coding-tree-block address.</param>
|
|||
/// <param name="plane">The independently coded color plane, or luma for combined-plane coding.</param>
|
|||
/// <param name="value">The governing independent-slice and tile state.</param>
|
|||
public void SetLoopFilterRegion(int rasterAddress, HevcPlane plane, HevcLoopFilterRegion value) |
|||
=> this.loopFilterRegions.Memory.Span[(rasterAddress * 3) + (int)plane] = value; |
|||
|
|||
/// <summary>
|
|||
/// Derives the picture, slice, and tile boundary availability used by the in-loop filters for one coding-tree block.
|
|||
/// </summary>
|
|||
/// <param name="rasterAddress">The raster-scan coding-tree-block address.</param>
|
|||
/// <param name="plane">The independently coded color plane, or luma for combined-plane coding.</param>
|
|||
/// <param name="pictureWidth">The picture width in coding-tree blocks.</param>
|
|||
/// <param name="pictureHeight">The picture height in coding-tree blocks.</param>
|
|||
/// <param name="loopFilterAcrossTilesEnabled">Whether the picture permits filtering across tile boundaries.</param>
|
|||
/// <returns>The availability of all eight neighboring coding-tree blocks.</returns>
|
|||
public HevcLoopFilterBoundaryAvailability GetLoopFilterBoundaryAvailability( |
|||
int rasterAddress, |
|||
HevcPlane plane, |
|||
int pictureWidth, |
|||
int pictureHeight, |
|||
bool loopFilterAcrossTilesEnabled) |
|||
{ |
|||
int x = rasterAddress % pictureWidth; |
|||
int y = rasterAddress / pictureWidth; |
|||
HevcLoopFilterRegion current = this.GetLoopFilterRegion(rasterAddress, plane); |
|||
|
|||
// H.265 assigns left, above, and upper-left boundaries to the current slice, while right, below, and
|
|||
// lower-right boundaries belong to the neighboring slice. This asymmetry makes filtering independent of CTB order.
|
|||
bool left = x > 0 |
|||
&& IsLoopFilterNeighborAvailable(current, this.GetLoopFilterRegion(rasterAddress - 1, plane), true, loopFilterAcrossTilesEnabled); |
|||
bool right = x + 1 < pictureWidth |
|||
&& IsLoopFilterNeighborAvailable(current, this.GetLoopFilterRegion(rasterAddress + 1, plane), false, loopFilterAcrossTilesEnabled); |
|||
bool above = y > 0 |
|||
&& IsLoopFilterNeighborAvailable(current, this.GetLoopFilterRegion(rasterAddress - pictureWidth, plane), true, loopFilterAcrossTilesEnabled); |
|||
bool below = y + 1 < pictureHeight |
|||
&& IsLoopFilterNeighborAvailable(current, this.GetLoopFilterRegion(rasterAddress + pictureWidth, plane), false, loopFilterAcrossTilesEnabled); |
|||
bool aboveLeft = x > 0 && y > 0 |
|||
&& IsLoopFilterNeighborAvailable(current, this.GetLoopFilterRegion(rasterAddress - pictureWidth - 1, plane), true, loopFilterAcrossTilesEnabled); |
|||
bool belowRight = x + 1 < pictureWidth && y + 1 < pictureHeight |
|||
&& IsLoopFilterNeighborAvailable(current, this.GetLoopFilterRegion(rasterAddress + pictureWidth + 1, plane), false, loopFilterAcrossTilesEnabled); |
|||
|
|||
// The crossed diagonals do not have a fixed owner in raster order. The later independent slice owns the
|
|||
// boundary flag, which is identified by its greater tile-scan start address.
|
|||
bool aboveRight = x + 1 < pictureWidth && y > 0 |
|||
&& IsLoopFilterDiagonalAvailable(current, this.GetLoopFilterRegion(rasterAddress - pictureWidth + 1, plane), loopFilterAcrossTilesEnabled); |
|||
bool belowLeft = x > 0 && y + 1 < pictureHeight |
|||
&& IsLoopFilterDiagonalAvailable(current, this.GetLoopFilterRegion(rasterAddress + pictureWidth - 1, plane), loopFilterAcrossTilesEnabled); |
|||
|
|||
return new HevcLoopFilterBoundaryAvailability(left, right, above, below, aboveLeft, aboveRight, belowLeft, belowRight); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the retained in-loop filter region for one coding-tree block.
|
|||
/// </summary>
|
|||
/// <param name="rasterAddress">The raster-scan coding-tree-block address.</param>
|
|||
/// <param name="plane">The independently coded color plane, or luma for combined-plane coding.</param>
|
|||
/// <returns>The retained slice and tile state.</returns>
|
|||
public HevcLoopFilterRegion GetLoopFilterRegion(int rasterAddress, HevcPlane plane) |
|||
=> this.loopFilterRegions.Memory.Span[(rasterAddress * 3) + (int)plane]; |
|||
|
|||
/// <summary>
|
|||
/// Determines availability across a boundary with a direction-selected slice owner.
|
|||
/// </summary>
|
|||
/// <param name="current">The current block's region.</param>
|
|||
/// <param name="neighbor">The neighboring block's region.</param>
|
|||
/// <param name="currentOwnsSliceBoundary">Whether the current slice controls a boundary between different slices.</param>
|
|||
/// <param name="loopFilterAcrossTilesEnabled">Whether tile boundaries permit filtering.</param>
|
|||
/// <returns><see langword="true"/> when both slice and tile rules permit filtering.</returns>
|
|||
private static bool IsLoopFilterNeighborAvailable( |
|||
HevcLoopFilterRegion current, |
|||
HevcLoopFilterRegion neighbor, |
|||
bool currentOwnsSliceBoundary, |
|||
bool loopFilterAcrossTilesEnabled) |
|||
{ |
|||
bool sameSlice = current.SliceStartAddressInTileScan == neighbor.SliceStartAddressInTileScan; |
|||
bool sliceAvailable = sameSlice |
|||
|| (currentOwnsSliceBoundary ? current.LoopFilterAcrossSlicesEnabled : neighbor.LoopFilterAcrossSlicesEnabled); |
|||
|
|||
return sliceAvailable && (loopFilterAcrossTilesEnabled || current.TileIndex == neighbor.TileIndex); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines availability across a crossed-diagonal boundary using the later slice as its owner.
|
|||
/// </summary>
|
|||
/// <param name="current">The current block's region.</param>
|
|||
/// <param name="neighbor">The diagonally neighboring block's region.</param>
|
|||
/// <param name="loopFilterAcrossTilesEnabled">Whether tile boundaries permit filtering.</param>
|
|||
/// <returns><see langword="true"/> when both slice and tile rules permit filtering.</returns>
|
|||
private static bool IsLoopFilterDiagonalAvailable( |
|||
HevcLoopFilterRegion current, |
|||
HevcLoopFilterRegion neighbor, |
|||
bool loopFilterAcrossTilesEnabled) |
|||
{ |
|||
bool currentOwnsSliceBoundary = current.SliceStartAddressInTileScan > neighbor.SliceStartAddressInTileScan; |
|||
return IsLoopFilterNeighborAvailable(current, neighbor, currentOwnsSliceBoundary, loopFilterAcrossTilesEnabled); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Releases the pooled sample-adaptive-offset picture state.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.loopFilterRegions.Dispose(); |
|||
this.regions.Dispose(); |
|||
this.parameters.Dispose(); |
|||
} |
|||
} |
|||
@ -1,352 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the HEVC quantization scaling matrices and their large-transform DC coefficients.
|
|||
/// </summary>
|
|||
internal sealed class HevcScalingList |
|||
{ |
|||
/// <summary>
|
|||
/// The number of matrix identifiers defined for each transform-size category.
|
|||
/// </summary>
|
|||
private const int MatrixCount = 6; |
|||
|
|||
/// <summary>
|
|||
/// The number of decoded scaling coefficients stored for every transform-size category.
|
|||
/// </summary>
|
|||
private const int CompactCoefficientCount = (16 * MatrixCount) + (64 * MatrixCount * 3); |
|||
|
|||
/// <summary>
|
|||
/// The number of separately coded DC coefficients.
|
|||
/// </summary>
|
|||
private const int DcCoefficientCount = 4 * MatrixCount; |
|||
|
|||
/// <summary>
|
|||
/// The first separately coded DC coefficient in the contiguous coefficient store.
|
|||
/// </summary>
|
|||
private const int DcCoefficientOffset = CompactCoefficientCount; |
|||
|
|||
/// <summary>
|
|||
/// The first transform-sized matrix in the contiguous coefficient store.
|
|||
/// </summary>
|
|||
private const int ExpandedCoefficientOffset = DcCoefficientOffset + DcCoefficientCount; |
|||
|
|||
/// <summary>
|
|||
/// The number of transform-sized coefficients stored across every size and matrix identifier.
|
|||
/// </summary>
|
|||
private const int ExpandedCoefficientCount = MatrixCount * ((4 * 4) + (8 * 8) + (16 * 16) + (32 * 32)); |
|||
|
|||
/// <summary>
|
|||
/// The compact syntax matrices, separately coded DC values, and transform-sized matrices.
|
|||
/// </summary>
|
|||
private readonly byte[] coefficients = new byte[ExpandedCoefficientOffset + ExpandedCoefficientCount]; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcScalingList"/> class with the normative default matrices.
|
|||
/// </summary>
|
|||
public HevcScalingList() |
|||
{ |
|||
for (int sizeId = 0; sizeId < 4; sizeId++) |
|||
{ |
|||
for (int matrixId = 0; matrixId < MatrixCount; matrixId++) |
|||
{ |
|||
ReadOnlySpan<byte> source = sizeId == 0 |
|||
? Default4x4 |
|||
: matrixId < 3 ? DefaultIntra8x8 : DefaultInter8x8; |
|||
|
|||
source.CopyTo(this.GetWritableMatrix(sizeId, matrixId)); |
|||
this.coefficients[GetDcCoefficientOffset(sizeId, matrixId)] = 16; |
|||
} |
|||
} |
|||
|
|||
this.ExpandMatrices(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the flat default matrix used by four-by-four transforms.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> Default4x4 => |
|||
[ |
|||
16, 16, 16, 16, |
|||
16, 16, 16, 16, |
|||
16, 16, 16, 16, |
|||
16, 16, 16, 16, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default intra-predicted matrix used by transforms of eight-by-eight and larger.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> DefaultIntra8x8 => |
|||
[ |
|||
16, 16, 16, 16, 17, 18, 21, 24, |
|||
16, 16, 16, 16, 17, 19, 22, 25, |
|||
16, 16, 17, 18, 20, 22, 25, 29, |
|||
16, 16, 18, 21, 24, 27, 31, 36, |
|||
17, 17, 20, 24, 30, 35, 41, 47, |
|||
18, 19, 22, 27, 35, 44, 54, 65, |
|||
21, 22, 25, 31, 41, 54, 70, 88, |
|||
24, 25, 29, 36, 47, 65, 88, 115, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default inter-predicted matrix used by transforms of eight-by-eight and larger.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> DefaultInter8x8 => |
|||
[ |
|||
16, 16, 16, 16, 17, 18, 20, 24, |
|||
16, 16, 16, 17, 18, 20, 24, 25, |
|||
16, 16, 17, 18, 20, 24, 25, 28, |
|||
16, 17, 18, 20, 24, 25, 28, 33, |
|||
17, 18, 20, 24, 25, 28, 33, 41, |
|||
18, 20, 24, 25, 28, 33, 41, 54, |
|||
20, 24, 25, 28, 33, 41, 54, 71, |
|||
24, 25, 28, 33, 41, 54, 71, 91, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the diagonal coefficient order for four-by-four matrices.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> DiagonalScan4x4 => |
|||
[ |
|||
0, 4, 1, 8, 5, 2, 12, 9, 6, 3, 13, 10, 7, 14, 11, 15 |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the diagonal coefficient order for matrices of eight-by-eight and larger.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> DiagonalScan8x8 => |
|||
[ |
|||
0, 8, 1, 16, 9, 2, 24, 17, 10, 3, 32, 25, 18, 11, 4, 40, |
|||
33, 26, 19, 12, 5, 48, 41, 34, 27, 20, 13, 6, 56, 49, 42, 35, |
|||
28, 21, 14, 7, 57, 50, 43, 36, 29, 22, 15, 58, 51, 44, 37, 30, |
|||
23, 59, 52, 45, 38, 31, 60, 53, 46, 39, 61, 54, 47, 62, 55, 63 |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Reads a complete scaling-list-data structure.
|
|||
/// </summary>
|
|||
/// <param name="reader">The parameter-set raw byte sequence payload reader.</param>
|
|||
/// <returns>The decoded scaling matrices.</returns>
|
|||
/// <exception cref="InvalidImageContentException">A prediction reference is outside its permitted matrix set.</exception>
|
|||
public static HevcScalingList Parse(ref HevcBitReader reader) |
|||
{ |
|||
HevcScalingList scalingList = new(); |
|||
for (int sizeId = 0; sizeId < 4; sizeId++) |
|||
{ |
|||
int matrixStep = sizeId == 3 ? 3 : 1; |
|||
for (int matrixId = 0; matrixId < MatrixCount; matrixId += matrixStep) |
|||
{ |
|||
bool predictionMode = reader.ReadFlag(); |
|||
if (!predictionMode) |
|||
{ |
|||
uint matrixIdDelta = reader.ReadUnsignedExpGolomb(); |
|||
if (sizeId == 3) |
|||
{ |
|||
if (matrixIdDelta > matrixId / 3) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC scaling list references an unavailable matrix."); |
|||
} |
|||
|
|||
matrixIdDelta *= 3; |
|||
} |
|||
|
|||
if (matrixIdDelta > matrixId) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC scaling list references an unavailable matrix."); |
|||
} |
|||
|
|||
int referenceMatrixId = matrixId - (int)matrixIdDelta; |
|||
if (referenceMatrixId != matrixId) |
|||
{ |
|||
// Span copying uses ImageSharp's runtime-optimized memory path and preserves one scalar
|
|||
// behavior model for these small, infrequently parsed coefficient tables.
|
|||
scalingList.GetMatrix(sizeId, referenceMatrixId).CopyTo(scalingList.GetWritableMatrix(sizeId, matrixId)); |
|||
byte dcCoefficient = scalingList.coefficients[GetDcCoefficientOffset(sizeId, referenceMatrixId)]; |
|||
|
|||
scalingList.coefficients[GetDcCoefficientOffset(sizeId, matrixId)] = dcCoefficient; |
|||
} |
|||
|
|||
continue; |
|||
} |
|||
|
|||
int nextCoefficient = 8; |
|||
if (sizeId > 1) |
|||
{ |
|||
nextCoefficient = (int)(((long)reader.ReadSignedExpGolomb() + 8) & 255); |
|||
scalingList.coefficients[GetDcCoefficientOffset(sizeId, matrixId)] = (byte)(nextCoefficient & 255); |
|||
} |
|||
|
|||
ReadOnlySpan<byte> scan = sizeId == 0 ? DiagonalScan4x4 : DiagonalScan8x8; |
|||
Span<byte> matrix = scalingList.GetWritableMatrix(sizeId, matrixId); |
|||
for (int coefficient = 0; coefficient < matrix.Length; coefficient++) |
|||
{ |
|||
nextCoefficient = (int)(((long)nextCoefficient + reader.ReadSignedExpGolomb()) & 255); |
|||
matrix[scan[coefficient]] = (byte)nextCoefficient; |
|||
} |
|||
} |
|||
|
|||
if (sizeId == 3) |
|||
{ |
|||
// HEVC signals only luma matrices at 32x32. Chroma uses the corresponding 16x16 matrices.
|
|||
for (int matrixId = 0; matrixId < MatrixCount; matrixId++) |
|||
{ |
|||
if (matrixId is 0 or 3) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
scalingList.GetMatrix(sizeId - 1, matrixId).CopyTo(scalingList.GetWritableMatrix(sizeId, matrixId)); |
|||
scalingList.coefficients[GetDcCoefficientOffset(sizeId, matrixId)] = scalingList.coefficients[GetDcCoefficientOffset(sizeId - 1, matrixId)]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
scalingList.ExpandMatrices(); |
|||
return scalingList; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a decoded scaling matrix.
|
|||
/// </summary>
|
|||
/// <param name="sizeId">The transform-size category from zero for 4x4 through three for 32x32.</param>
|
|||
/// <param name="matrixId">The prediction and color-component matrix identifier.</param>
|
|||
/// <returns>The 16 or 64 decoded scaling coefficients in raster order.</returns>
|
|||
public ReadOnlySpan<byte> GetMatrix(int sizeId, int matrixId) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(sizeId, 0, 3, nameof(sizeId)); |
|||
DebugGuard.MustBeBetweenOrEqualTo(matrixId, 0, MatrixCount - 1, nameof(matrixId)); |
|||
int coefficientCount = GetCompactMatrixLength(sizeId); |
|||
return this.coefficients.AsSpan(GetCompactMatrixOffset(sizeId, matrixId), coefficientCount); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a scaling matrix expanded to its transform dimensions.
|
|||
/// </summary>
|
|||
/// <param name="sizeId">The transform-size category from zero for 4x4 through three for 32x32.</param>
|
|||
/// <param name="matrixId">The prediction and color-component matrix identifier.</param>
|
|||
/// <returns>The transform-sized scaling coefficients in raster order.</returns>
|
|||
public ReadOnlySpan<byte> GetExpandedMatrix(int sizeId, int matrixId) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(sizeId, 0, 3, nameof(sizeId)); |
|||
DebugGuard.MustBeBetweenOrEqualTo(matrixId, 0, MatrixCount - 1, nameof(matrixId)); |
|||
int coefficientCount = GetExpandedMatrixLength(sizeId); |
|||
return this.coefficients.AsSpan(GetExpandedMatrixOffset(sizeId, matrixId), coefficientCount); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the DC scaling coefficient for a large transform matrix.
|
|||
/// </summary>
|
|||
/// <param name="sizeId">The transform-size category.</param>
|
|||
/// <param name="matrixId">The prediction and color-component matrix identifier.</param>
|
|||
/// <returns>The decoded DC coefficient.</returns>
|
|||
public byte GetDcCoefficient(int sizeId, int matrixId) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(sizeId, 0, 3, nameof(sizeId)); |
|||
DebugGuard.MustBeBetweenOrEqualTo(matrixId, 0, MatrixCount - 1, nameof(matrixId)); |
|||
return this.coefficients[GetDcCoefficientOffset(sizeId, matrixId)]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Expands every syntax matrix once so inverse quantization can consume consecutive weights without coordinate division.
|
|||
/// </summary>
|
|||
private void ExpandMatrices() |
|||
{ |
|||
for (int sizeId = 0; sizeId < 4; sizeId++) |
|||
{ |
|||
int size = 1 << (sizeId + 2); |
|||
int ratio = Math.Max(1, size >> 3); |
|||
int sourceSide = Math.Min(size, 8); |
|||
for (int matrixId = 0; matrixId < MatrixCount; matrixId++) |
|||
{ |
|||
ReadOnlySpan<byte> source = this.GetMatrix(sizeId, matrixId); |
|||
Span<byte> destination = this.coefficients.AsSpan(GetExpandedMatrixOffset(sizeId, matrixId), size * size); |
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
int sourceRowOffset = (y / ratio) * sourceSide; |
|||
int destinationRowOffset = y * size; |
|||
for (int x = 0; x < size; x++) |
|||
{ |
|||
destination[destinationRowOffset + x] = source[sourceRowOffset + (x / ratio)]; |
|||
} |
|||
} |
|||
|
|||
if (sizeId > 1) |
|||
{ |
|||
// Sixteen- and thirty-two-point matrices code their DC weight separately from the 8x8 body.
|
|||
destination[0] = this.coefficients[GetDcCoefficientOffset(sizeId, matrixId)]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a writable compact syntax matrix.
|
|||
/// </summary>
|
|||
/// <param name="sizeId">The transform-size category.</param>
|
|||
/// <param name="matrixId">The matrix identifier.</param>
|
|||
/// <returns>The writable compact matrix.</returns>
|
|||
private Span<byte> GetWritableMatrix(int sizeId, int matrixId) |
|||
=> this.coefficients.AsSpan(GetCompactMatrixOffset(sizeId, matrixId), GetCompactMatrixLength(sizeId)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of coefficients coded for one syntax matrix.
|
|||
/// </summary>
|
|||
/// <param name="sizeId">The transform-size category.</param>
|
|||
/// <returns>The compact coefficient count.</returns>
|
|||
private static int GetCompactMatrixLength(int sizeId) => sizeId == 0 ? 16 : 64; |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of coefficients in one transform-sized matrix.
|
|||
/// </summary>
|
|||
/// <param name="sizeId">The transform-size category.</param>
|
|||
/// <returns>The expanded coefficient count.</returns>
|
|||
private static int GetExpandedMatrixLength(int sizeId) => 1 << ((sizeId + 2) * 2); |
|||
|
|||
/// <summary>
|
|||
/// Gets the compact-matrix offset for a size and matrix identifier.
|
|||
/// </summary>
|
|||
/// <param name="sizeId">The transform-size category.</param>
|
|||
/// <param name="matrixId">The matrix identifier.</param>
|
|||
/// <returns>The compact-matrix offset.</returns>
|
|||
private static int GetCompactMatrixOffset(int sizeId, int matrixId) |
|||
{ |
|||
int sizeOffset = sizeId switch |
|||
{ |
|||
0 => 0, |
|||
1 => 16 * MatrixCount, |
|||
2 => (16 * MatrixCount) + (64 * MatrixCount), |
|||
_ => (16 * MatrixCount) + (64 * MatrixCount * 2), |
|||
}; |
|||
|
|||
return sizeOffset + (matrixId * GetCompactMatrixLength(sizeId)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the expanded-matrix offset for a size and matrix identifier.
|
|||
/// </summary>
|
|||
/// <param name="sizeId">The transform-size category.</param>
|
|||
/// <param name="matrixId">The matrix identifier.</param>
|
|||
/// <returns>The expanded-matrix offset.</returns>
|
|||
private static int GetExpandedMatrixOffset(int sizeId, int matrixId) |
|||
{ |
|||
int sizeOffset = sizeId switch |
|||
{ |
|||
0 => 0, |
|||
1 => 16 * MatrixCount, |
|||
2 => (16 * MatrixCount) + (64 * MatrixCount), |
|||
_ => (16 * MatrixCount) + (64 * MatrixCount) + (256 * MatrixCount), |
|||
}; |
|||
|
|||
return ExpandedCoefficientOffset + sizeOffset + (matrixId * GetExpandedMatrixLength(sizeId)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the separately coded DC-coefficient offset for a size and matrix identifier.
|
|||
/// </summary>
|
|||
/// <param name="sizeId">The transform-size category.</param>
|
|||
/// <param name="matrixId">The matrix identifier.</param>
|
|||
/// <returns>The DC-coefficient offset.</returns>
|
|||
private static int GetDcCoefficientOffset(int sizeId, int matrixId) => DcCoefficientOffset + (sizeId * MatrixCount) + matrixId; |
|||
} |
|||
@ -1,573 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the HEVC sequence fields required to reconstruct one independently coded still image.
|
|||
/// </summary>
|
|||
internal sealed class HevcSequenceParameterSet |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcSequenceParameterSet"/> class.
|
|||
/// </summary>
|
|||
/// <param name="nalUnit">The decoded sequence-parameter-set NAL unit.</param>
|
|||
/// <exception cref="InvalidImageContentException">The sequence parameter set is malformed or outside the still-image profile.</exception>
|
|||
public HevcSequenceParameterSet(HevcNalUnit nalUnit) |
|||
{ |
|||
const byte sequenceParameterSetNalUnitType = 33; |
|||
if (nalUnit.Header.NalUnitType != sequenceParameterSetNalUnitType |
|||
|| nalUnit.Header.LayerId != 0 |
|||
|| nalUnit.Header.TemporalId != 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC sequence parameter set has an invalid NAL-unit header."); |
|||
} |
|||
|
|||
HevcBitReader reader = new(nalUnit.Rbsp.Span); |
|||
this.VideoParameterSetId = (byte)reader.ReadBits(4); |
|||
int maxSubLayersMinusOne = (int)reader.ReadBits(3); |
|||
if (maxSubLayersMinusOne > 6) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC sequence parameter set declares too many temporal sublayers."); |
|||
} |
|||
|
|||
this.MaxSubLayers = maxSubLayersMinusOne + 1; |
|||
this.TemporalIdNestingFlag = reader.ReadFlag(); |
|||
if (maxSubLayersMinusOne == 0 && !this.TemporalIdNestingFlag) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC sequence parameter set has invalid temporal nesting."); |
|||
} |
|||
|
|||
this.ProfileTierLevel = new HevcProfileTierLevel(ref reader, maxSubLayersMinusOne); |
|||
uint sequenceParameterSetId = reader.ReadUnsignedExpGolomb(); |
|||
if (sequenceParameterSetId > 15) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC sequence parameter set identifier is invalid."); |
|||
} |
|||
|
|||
this.Id = (byte)sequenceParameterSetId; |
|||
uint chromaFormat = reader.ReadUnsignedExpGolomb(); |
|||
if (chromaFormat > 3) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC sequence parameter set has an invalid chroma format."); |
|||
} |
|||
|
|||
this.ChromaFormat = (byte)chromaFormat; |
|||
this.SeparateColorPlaneFlag = this.ChromaFormat == 3 && reader.ReadFlag(); |
|||
|
|||
uint width = reader.ReadUnsignedExpGolomb(); |
|||
uint height = reader.ReadUnsignedExpGolomb(); |
|||
if (width is 0 or > int.MaxValue || height is 0 or > int.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC sequence parameter set has invalid coded dimensions."); |
|||
} |
|||
|
|||
this.Width = (int)width; |
|||
this.Height = (int)height; |
|||
if (reader.ReadFlag()) |
|||
{ |
|||
int cropUnitWidth = HevcParameterSetSyntax.GetCropUnitWidth(this.ChromaFormat, this.SeparateColorPlaneFlag); |
|||
int cropUnitHeight = HevcParameterSetSyntax.GetCropUnitHeight(this.ChromaFormat, this.SeparateColorPlaneFlag); |
|||
this.ConformanceWindowLeftOffset = ReadScaledOffset(ref reader, cropUnitWidth); |
|||
this.ConformanceWindowRightOffset = ReadScaledOffset(ref reader, cropUnitWidth); |
|||
this.ConformanceWindowTopOffset = ReadScaledOffset(ref reader, cropUnitHeight); |
|||
this.ConformanceWindowBottomOffset = ReadScaledOffset(ref reader, cropUnitHeight); |
|||
} |
|||
|
|||
if ((long)this.ConformanceWindowLeftOffset + this.ConformanceWindowRightOffset >= this.Width |
|||
|| (long)this.ConformanceWindowTopOffset + this.ConformanceWindowBottomOffset >= this.Height) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC sequence parameter set has an invalid conformance window."); |
|||
} |
|||
|
|||
this.DisplayWidth = this.Width - this.ConformanceWindowLeftOffset - this.ConformanceWindowRightOffset; |
|||
this.DisplayHeight = this.Height - this.ConformanceWindowTopOffset - this.ConformanceWindowBottomOffset; |
|||
|
|||
this.BitDepthLuma = ReadBitDepth(ref reader); |
|||
this.BitDepthChroma = ReadBitDepth(ref reader); |
|||
uint log2MaxPictureOrderCountLsbMinusFour = reader.ReadUnsignedExpGolomb(); |
|||
if (log2MaxPictureOrderCountLsbMinusFour > 12) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC picture-order-count width is invalid."); |
|||
} |
|||
|
|||
this.PictureOrderCountLsbBits = (int)log2MaxPictureOrderCountLsbMinusFour + 4; |
|||
|
|||
bool subLayerOrderingInfoPresent = reader.ReadFlag(); |
|||
int firstOrderingSubLayer = subLayerOrderingInfoPresent ? 0 : maxSubLayersMinusOne; |
|||
for (int subLayer = firstOrderingSubLayer; subLayer <= maxSubLayersMinusOne; subLayer++) |
|||
{ |
|||
uint maxDecodedPictureBufferingMinusOne = reader.ReadUnsignedExpGolomb(); |
|||
uint maxNumReorderPictures = reader.ReadUnsignedExpGolomb(); |
|||
reader.ReadUnsignedExpGolomb(); |
|||
if (maxNumReorderPictures > maxDecodedPictureBufferingMinusOne) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC sequence parameter set has invalid sublayer ordering limits."); |
|||
} |
|||
} |
|||
|
|||
uint minCodingBlockLog2MinusThree = reader.ReadUnsignedExpGolomb(); |
|||
if (minCodingBlockLog2MinusThree > 3) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC minimum coding-block size is invalid."); |
|||
} |
|||
|
|||
this.MinCodingBlockLog2 = (int)minCodingBlockLog2MinusThree + 3; |
|||
uint codingBlockSizeDifference = reader.ReadUnsignedExpGolomb(); |
|||
if (codingBlockSizeDifference > 6 - this.MinCodingBlockLog2) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC coding-tree-block size is invalid."); |
|||
} |
|||
|
|||
this.CodingTreeBlockLog2 = this.MinCodingBlockLog2 + (int)codingBlockSizeDifference; |
|||
|
|||
uint minTransformBlockLog2MinusTwo = reader.ReadUnsignedExpGolomb(); |
|||
if (minTransformBlockLog2MinusTwo > this.MinCodingBlockLog2 - 3) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC minimum transform-block size is invalid."); |
|||
} |
|||
|
|||
this.MinTransformBlockLog2 = (int)minTransformBlockLog2MinusTwo + 2; |
|||
uint transformBlockSizeDifference = reader.ReadUnsignedExpGolomb(); |
|||
int maximumTransformBlockLog2 = Math.Min(5, this.CodingTreeBlockLog2); |
|||
if (transformBlockSizeDifference > maximumTransformBlockLog2 - this.MinTransformBlockLog2) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC maximum transform-block size is invalid."); |
|||
} |
|||
|
|||
this.MaxTransformBlockLog2 = this.MinTransformBlockLog2 + (int)transformBlockSizeDifference; |
|||
uint maxTransformHierarchyDepthInter = reader.ReadUnsignedExpGolomb(); |
|||
uint maxTransformHierarchyDepthIntra = reader.ReadUnsignedExpGolomb(); |
|||
uint maxHierarchyDepth = (uint)(this.CodingTreeBlockLog2 - this.MinTransformBlockLog2); |
|||
if (maxTransformHierarchyDepthInter > maxHierarchyDepth || maxTransformHierarchyDepthIntra > maxHierarchyDepth) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC transform hierarchy depth is invalid."); |
|||
} |
|||
|
|||
this.MaxTransformHierarchyDepthInter = (int)maxTransformHierarchyDepthInter + 1; |
|||
this.MaxTransformHierarchyDepthIntra = (int)maxTransformHierarchyDepthIntra + 1; |
|||
|
|||
this.ScalingListEnabled = reader.ReadFlag(); |
|||
this.ScalingList = new HevcScalingList(); |
|||
if (this.ScalingListEnabled && reader.ReadFlag()) |
|||
{ |
|||
this.ScalingList = HevcScalingList.Parse(ref reader); |
|||
} |
|||
|
|||
this.AsymmetricMotionPartitionsEnabled = reader.ReadFlag(); |
|||
this.SampleAdaptiveOffsetEnabled = reader.ReadFlag(); |
|||
this.PcmEnabled = reader.ReadFlag(); |
|||
if (this.PcmEnabled) |
|||
{ |
|||
this.PcmBitDepthLuma = (int)reader.ReadBits(4) + 1; |
|||
this.PcmBitDepthChroma = (int)reader.ReadBits(4) + 1; |
|||
if (this.PcmBitDepthLuma > this.BitDepthLuma || this.PcmBitDepthChroma > this.BitDepthChroma) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC PCM bit depth exceeds the coded sample precision."); |
|||
} |
|||
|
|||
uint minPcmCodingBlockLog2MinusThree = reader.ReadUnsignedExpGolomb(); |
|||
this.MinPcmCodingBlockLog2 = (int)minPcmCodingBlockLog2MinusThree + 3; |
|||
int maximumPcmCodingBlockLog2 = Math.Min(this.CodingTreeBlockLog2, 5); |
|||
if (this.MinPcmCodingBlockLog2 < Math.Min(this.MinCodingBlockLog2, 5) |
|||
|| this.MinPcmCodingBlockLog2 > maximumPcmCodingBlockLog2) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC minimum PCM coding-block size is invalid."); |
|||
} |
|||
|
|||
uint pcmCodingBlockSizeDifference = reader.ReadUnsignedExpGolomb(); |
|||
if (pcmCodingBlockSizeDifference > maximumPcmCodingBlockLog2 - this.MinPcmCodingBlockLog2) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC maximum PCM coding-block size is invalid."); |
|||
} |
|||
|
|||
this.MaxPcmCodingBlockLog2 = this.MinPcmCodingBlockLog2 + (int)pcmCodingBlockSizeDifference; |
|||
this.PcmLoopFilterDisabled = reader.ReadFlag(); |
|||
} |
|||
|
|||
uint shortTermReferencePictureSetCount = reader.ReadUnsignedExpGolomb(); |
|||
if (shortTermReferencePictureSetCount > 64) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC sequence parameter set declares too many short-term reference-picture sets."); |
|||
} |
|||
|
|||
List<HevcShortTermReferencePictureSet> shortTermReferencePictureSets = new((int)shortTermReferencePictureSetCount); |
|||
for (int referenceSet = 0; referenceSet < shortTermReferencePictureSetCount; referenceSet++) |
|||
{ |
|||
shortTermReferencePictureSets.Add( |
|||
HevcShortTermReferencePictureSet.Parse(ref reader, shortTermReferencePictureSets, referenceSet)); |
|||
} |
|||
|
|||
this.ShortTermReferencePictureSets = shortTermReferencePictureSets; |
|||
|
|||
if (reader.ReadFlag()) |
|||
{ |
|||
uint longTermReferencePictureCount = reader.ReadUnsignedExpGolomb(); |
|||
if (longTermReferencePictureCount > 32) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC sequence parameter set declares too many long-term reference pictures."); |
|||
} |
|||
|
|||
uint[] pictureOrderCounts = new uint[longTermReferencePictureCount]; |
|||
bool[] usedByCurrentPicture = new bool[longTermReferencePictureCount]; |
|||
for (int reference = 0; reference < pictureOrderCounts.Length; reference++) |
|||
{ |
|||
pictureOrderCounts[reference] = reader.ReadBits(this.PictureOrderCountLsbBits); |
|||
usedByCurrentPicture[reference] = reader.ReadFlag(); |
|||
} |
|||
|
|||
this.LongTermReferencePictureOrderCounts = pictureOrderCounts; |
|||
this.LongTermReferencePicturesUsedByCurrent = usedByCurrentPicture; |
|||
} |
|||
else |
|||
{ |
|||
this.LongTermReferencePictureOrderCounts = Array.Empty<uint>(); |
|||
this.LongTermReferencePicturesUsedByCurrent = Array.Empty<bool>(); |
|||
} |
|||
|
|||
this.TemporalMotionVectorPredictionEnabled = reader.ReadFlag(); |
|||
this.StrongIntraSmoothingEnabled = reader.ReadFlag(); |
|||
if (reader.ReadFlag()) |
|||
{ |
|||
this.VideoUsabilityInformation = new HevcVideoUsabilityInformation( |
|||
ref reader, |
|||
this.ChromaFormat, |
|||
this.SeparateColorPlaneFlag, |
|||
maxSubLayersMinusOne); |
|||
} |
|||
|
|||
if (reader.ReadFlag()) |
|||
{ |
|||
Span<bool> extensionFlags = stackalloc bool[8]; |
|||
for (int extensionFlag = 0; extensionFlag < extensionFlags.Length; extensionFlag++) |
|||
{ |
|||
extensionFlags[extensionFlag] = reader.ReadFlag(); |
|||
} |
|||
|
|||
if (extensionFlags[1]) |
|||
{ |
|||
throw new InvalidImageContentException("Layered HEVC sequence extensions are not supported for still-image items."); |
|||
} |
|||
|
|||
if (extensionFlags[0]) |
|||
{ |
|||
this.TransformSkipRotationEnabled = reader.ReadFlag(); |
|||
this.TransformSkipContextEnabled = reader.ReadFlag(); |
|||
this.ImplicitResidualDpcmEnabled = reader.ReadFlag(); |
|||
this.ExplicitResidualDpcmEnabled = reader.ReadFlag(); |
|||
this.ExtendedPrecisionProcessingEnabled = reader.ReadFlag(); |
|||
this.IntraSmoothingDisabled = reader.ReadFlag(); |
|||
this.HighPrecisionOffsetsEnabled = reader.ReadFlag(); |
|||
this.PersistentRiceAdaptationEnabled = reader.ReadFlag(); |
|||
this.CabacBypassAlignmentEnabled = reader.ReadFlag(); |
|||
} |
|||
|
|||
bool unknownExtensionPresent = false; |
|||
for (int extensionFlag = 2; extensionFlag < extensionFlags.Length; extensionFlag++) |
|||
{ |
|||
unknownExtensionPresent |= extensionFlags[extensionFlag]; |
|||
} |
|||
|
|||
if (unknownExtensionPresent) |
|||
{ |
|||
while (reader.HasMoreRbspData()) |
|||
{ |
|||
reader.ReadFlag(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
reader.ReadRbspTrailingBits(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the referenced video-parameter-set identifier.
|
|||
/// </summary>
|
|||
public byte VideoParameterSetId { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the sequence-parameter-set identifier.
|
|||
/// </summary>
|
|||
public byte Id { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the declared number of temporal sublayers.
|
|||
/// </summary>
|
|||
public int MaxSubLayers { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether temporal identifiers are nested.
|
|||
/// </summary>
|
|||
public bool TemporalIdNestingFlag { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the general profile, tier, constraint, and level description.
|
|||
/// </summary>
|
|||
public HevcProfileTierLevel ProfileTierLevel { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded chroma format, from monochrome through YUV 4:4:4.
|
|||
/// </summary>
|
|||
public byte ChromaFormat { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether 4:4:4 components are coded as separate color planes.
|
|||
/// </summary>
|
|||
public bool SeparateColorPlaneFlag { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded luma width before conformance cropping.
|
|||
/// </summary>
|
|||
public int Width { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded luma height before conformance cropping.
|
|||
/// </summary>
|
|||
public int Height { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the displayed width after conformance cropping.
|
|||
/// </summary>
|
|||
public int DisplayWidth { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the displayed height after conformance cropping.
|
|||
/// </summary>
|
|||
public int DisplayHeight { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the conformance-window left offset in luma samples.
|
|||
/// </summary>
|
|||
public int ConformanceWindowLeftOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the conformance-window right offset in luma samples.
|
|||
/// </summary>
|
|||
public int ConformanceWindowRightOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the conformance-window top offset in luma samples.
|
|||
/// </summary>
|
|||
public int ConformanceWindowTopOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the conformance-window bottom offset in luma samples.
|
|||
/// </summary>
|
|||
public int ConformanceWindowBottomOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma sample precision in bits.
|
|||
/// </summary>
|
|||
public int BitDepthLuma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma sample precision in bits.
|
|||
/// </summary>
|
|||
public int BitDepthChroma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded picture-order-count least-significant-bit width.
|
|||
/// </summary>
|
|||
public int PictureOrderCountLsbBits { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the minimum luma coding-block size.
|
|||
/// </summary>
|
|||
public int MinCodingBlockLog2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the coding-tree-block size.
|
|||
/// </summary>
|
|||
public int CodingTreeBlockLog2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the minimum luma transform-block size.
|
|||
/// </summary>
|
|||
public int MinTransformBlockLog2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the maximum luma transform-block size.
|
|||
/// </summary>
|
|||
public int MaxTransformBlockLog2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum inter-predicted transform hierarchy depth.
|
|||
/// </summary>
|
|||
public int MaxTransformHierarchyDepthInter { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum intra-predicted transform hierarchy depth.
|
|||
/// </summary>
|
|||
public int MaxTransformHierarchyDepthIntra { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether scaling lists affect inverse quantization.
|
|||
/// </summary>
|
|||
public bool ScalingListEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the effective quantization scaling matrices.
|
|||
/// </summary>
|
|||
public HevcScalingList ScalingList { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether asymmetric motion partitions are enabled.
|
|||
/// </summary>
|
|||
public bool AsymmetricMotionPartitionsEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether sample-adaptive offset filtering is enabled.
|
|||
/// </summary>
|
|||
public bool SampleAdaptiveOffsetEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether pulse-code-modulated coding blocks are enabled.
|
|||
/// </summary>
|
|||
public bool PcmEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the PCM luma sample precision in bits.
|
|||
/// </summary>
|
|||
public int PcmBitDepthLuma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the PCM chroma sample precision in bits.
|
|||
/// </summary>
|
|||
public int PcmBitDepthChroma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the minimum PCM coding-block size.
|
|||
/// </summary>
|
|||
public int MinPcmCodingBlockLog2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the maximum PCM coding-block size.
|
|||
/// </summary>
|
|||
public int MaxPcmCodingBlockLog2 { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether in-loop filtering is disabled for PCM blocks.
|
|||
/// </summary>
|
|||
public bool PcmLoopFilterDisabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the SPS short-term reference-picture sets.
|
|||
/// </summary>
|
|||
public IReadOnlyList<HevcShortTermReferencePictureSet> ShortTermReferencePictureSets { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the long-term reference picture-order-count values.
|
|||
/// </summary>
|
|||
public IReadOnlyList<uint> LongTermReferencePictureOrderCounts { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the long-term reference-picture current-usage flags.
|
|||
/// </summary>
|
|||
public IReadOnlyList<bool> LongTermReferencePicturesUsedByCurrent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether temporal motion-vector prediction is enabled.
|
|||
/// </summary>
|
|||
public bool TemporalMotionVectorPredictionEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether strong intra smoothing is enabled.
|
|||
/// </summary>
|
|||
public bool StrongIntraSmoothingEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the optional still-image VUI presentation description.
|
|||
/// </summary>
|
|||
public HevcVideoUsabilityInformation? VideoUsabilityInformation { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether transform-skip coefficient rotation is enabled.
|
|||
/// </summary>
|
|||
public bool TransformSkipRotationEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether transform-skip-specific entropy contexts are enabled.
|
|||
/// </summary>
|
|||
public bool TransformSkipContextEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether implicit residual DPCM is enabled.
|
|||
/// </summary>
|
|||
public bool ImplicitResidualDpcmEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether explicit residual DPCM is enabled.
|
|||
/// </summary>
|
|||
public bool ExplicitResidualDpcmEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether extended-precision processing is enabled.
|
|||
/// </summary>
|
|||
public bool ExtendedPrecisionProcessingEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether intra smoothing is disabled.
|
|||
/// </summary>
|
|||
public bool IntraSmoothingDisabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether high-precision prediction offsets are enabled.
|
|||
/// </summary>
|
|||
public bool HighPrecisionOffsetsEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether persistent Rice adaptation is enabled.
|
|||
/// </summary>
|
|||
public bool PersistentRiceAdaptationEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether CABAC bypass alignment is enabled.
|
|||
/// </summary>
|
|||
public bool CabacBypassAlignmentEnabled { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the transform dynamic range for the specified reconstructed plane.
|
|||
/// </summary>
|
|||
/// <param name="plane">The reconstructed plane.</param>
|
|||
/// <returns>The transform dynamic range excluding its sign bit.</returns>
|
|||
public int GetMaxTransformDynamicRange(HevcPlane plane) |
|||
{ |
|||
int bitDepth = plane == HevcPlane.Y ? this.BitDepthLuma : this.BitDepthChroma; |
|||
return this.ExtendedPrecisionProcessingEnabled ? Math.Max(15, bitDepth + 6) : 15; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a conformance-window offset and converts it to luma-sample units.
|
|||
/// </summary>
|
|||
/// <param name="reader">The sequence-parameter-set raw byte sequence payload reader.</param>
|
|||
/// <param name="unit">The chroma-dependent luma-sample unit.</param>
|
|||
/// <returns>The scaled offset.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The scaled offset exceeds the supported image dimension range.</exception>
|
|||
private static int ReadScaledOffset(ref HevcBitReader reader, int unit) |
|||
{ |
|||
uint offset = reader.ReadUnsignedExpGolomb(); |
|||
if (offset > int.MaxValue / unit) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC conformance-window offset is too large."); |
|||
} |
|||
|
|||
return (int)offset * unit; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads and validates a coded HEVC sample precision.
|
|||
/// </summary>
|
|||
/// <param name="reader">The sequence-parameter-set raw byte sequence payload reader.</param>
|
|||
/// <returns>The sample precision in bits.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The declared precision exceeds 16 bits.</exception>
|
|||
private static int ReadBitDepth(ref HevcBitReader reader) |
|||
{ |
|||
uint bitDepthMinusEight = reader.ReadUnsignedExpGolomb(); |
|||
if (bitDepthMinusEight > 8) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC sample bit depth is invalid."); |
|||
} |
|||
|
|||
return (int)bitDepthMinusEight + 8; |
|||
} |
|||
} |
|||
@ -1,188 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the bounded picture-order differences declared by one HEVC short-term reference-picture set.
|
|||
/// </summary>
|
|||
internal sealed class HevcShortTermReferencePictureSet |
|||
{ |
|||
/// <summary>
|
|||
/// Stores the bounded signed picture-order differences in HEVC reference order.
|
|||
/// </summary>
|
|||
private InlineArray16<int> deltaPictureOrders; |
|||
|
|||
/// <summary>
|
|||
/// Stores the bounded current-picture usage flags corresponding to <see cref="deltaPictureOrders"/>.
|
|||
/// </summary>
|
|||
private InlineArray16<bool> usedByCurrentPicture; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcShortTermReferencePictureSet"/> class.
|
|||
/// </summary>
|
|||
private HevcShortTermReferencePictureSet() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of pictures declared by the reference-picture set.
|
|||
/// </summary>
|
|||
public int Count { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a signed picture-order difference in HEVC reference order.
|
|||
/// </summary>
|
|||
/// <param name="index">The zero-based reference-picture index.</param>
|
|||
/// <returns>The signed picture-order difference.</returns>
|
|||
public int GetDeltaPictureOrder(int index) => this.deltaPictureOrders[index]; |
|||
|
|||
/// <summary>
|
|||
/// Gets whether a reference picture is used by the current picture.
|
|||
/// </summary>
|
|||
/// <param name="index">The zero-based reference-picture index.</param>
|
|||
/// <returns><see langword="true"/> when the reference is used by the current picture.</returns>
|
|||
public bool IsUsedByCurrentPicture(int index) => this.usedByCurrentPicture[index]; |
|||
|
|||
/// <summary>
|
|||
/// Reads one SPS short-term reference-picture set.
|
|||
/// </summary>
|
|||
/// <param name="reader">The sequence-parameter-set raw byte sequence payload reader.</param>
|
|||
/// <param name="previousSets">The previously decoded sets available for inter-set prediction.</param>
|
|||
/// <param name="index">The zero-based index of the set being decoded.</param>
|
|||
/// <returns>The decoded reference-picture set.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The set exceeds the HEVC decoded-picture-buffer bound.</exception>
|
|||
public static HevcShortTermReferencePictureSet Parse( |
|||
ref HevcBitReader reader, |
|||
IReadOnlyList<HevcShortTermReferencePictureSet> previousSets, |
|||
int index) |
|||
{ |
|||
HevcShortTermReferencePictureSet result = new(); |
|||
Span<int> deltaPictureOrders = result.deltaPictureOrders; |
|||
Span<bool> usedByCurrentPicture = result.usedByCurrentPicture; |
|||
int pictureCount = 0; |
|||
bool interSetPrediction = index > 0 && reader.ReadFlag(); |
|||
if (interSetPrediction) |
|||
{ |
|||
HevcShortTermReferencePictureSet referenceSet = previousSets[index - 1]; |
|||
bool deltaPictureOrderSign = reader.ReadFlag(); |
|||
uint absoluteDeltaPictureOrderMinusOne = reader.ReadUnsignedExpGolomb(); |
|||
if (absoluteDeltaPictureOrderMinusOne >= int.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC short-term reference-picture delta is too large."); |
|||
} |
|||
|
|||
int deltaReferencePictureSet = (deltaPictureOrderSign ? -1 : 1) |
|||
* ((int)absoluteDeltaPictureOrderMinusOne + 1); |
|||
|
|||
for (int referenceIndex = 0; referenceIndex <= referenceSet.Count; referenceIndex++) |
|||
{ |
|||
bool used = reader.ReadFlag(); |
|||
bool useDelta = used || reader.ReadFlag(); |
|||
if (!useDelta) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (pictureCount == deltaPictureOrders.Length) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC short-term reference-picture set is too large."); |
|||
} |
|||
|
|||
int referenceDelta = referenceIndex < referenceSet.Count |
|||
? referenceSet.GetDeltaPictureOrder(referenceIndex) |
|||
: 0; |
|||
|
|||
long deltaPictureOrder = (long)deltaReferencePictureSet + referenceDelta; |
|||
if (deltaPictureOrder is < int.MinValue or > int.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC short-term reference-picture delta is too large."); |
|||
} |
|||
|
|||
deltaPictureOrders[pictureCount] = (int)deltaPictureOrder; |
|||
usedByCurrentPicture[pictureCount] = used; |
|||
pictureCount++; |
|||
} |
|||
|
|||
// HEVC orders negative differences nearest-first, followed by positive differences nearest-first.
|
|||
for (int outer = 1; outer < pictureCount; outer++) |
|||
{ |
|||
int delta = deltaPictureOrders[outer]; |
|||
bool used = usedByCurrentPicture[outer]; |
|||
int inner = outer - 1; |
|||
while (inner >= 0 && delta < deltaPictureOrders[inner]) |
|||
{ |
|||
deltaPictureOrders[inner + 1] = deltaPictureOrders[inner]; |
|||
usedByCurrentPicture[inner + 1] = usedByCurrentPicture[inner]; |
|||
inner--; |
|||
} |
|||
|
|||
deltaPictureOrders[inner + 1] = delta; |
|||
usedByCurrentPicture[inner + 1] = used; |
|||
} |
|||
|
|||
int negativeCount = 0; |
|||
while (negativeCount < pictureCount && deltaPictureOrders[negativeCount] < 0) |
|||
{ |
|||
negativeCount++; |
|||
} |
|||
|
|||
deltaPictureOrders[..negativeCount].Reverse(); |
|||
usedByCurrentPicture[..negativeCount].Reverse(); |
|||
} |
|||
else |
|||
{ |
|||
uint negativePictureCount = reader.ReadUnsignedExpGolomb(); |
|||
uint positivePictureCount = reader.ReadUnsignedExpGolomb(); |
|||
if (negativePictureCount > 16 || positivePictureCount > 16 - negativePictureCount) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC short-term reference-picture set is too large."); |
|||
} |
|||
|
|||
int previousDelta = 0; |
|||
for (uint negativeIndex = 0; negativeIndex < negativePictureCount; negativeIndex++) |
|||
{ |
|||
uint deltaMinusOne = reader.ReadUnsignedExpGolomb(); |
|||
if (deltaMinusOne >= int.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC short-term reference-picture delta is too large."); |
|||
} |
|||
|
|||
long deltaPictureOrder = (long)previousDelta - deltaMinusOne - 1; |
|||
if (deltaPictureOrder < int.MinValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC short-term reference-picture delta is too large."); |
|||
} |
|||
|
|||
previousDelta = (int)deltaPictureOrder; |
|||
deltaPictureOrders[pictureCount] = previousDelta; |
|||
usedByCurrentPicture[pictureCount] = reader.ReadFlag(); |
|||
pictureCount++; |
|||
} |
|||
|
|||
previousDelta = 0; |
|||
for (uint positiveIndex = 0; positiveIndex < positivePictureCount; positiveIndex++) |
|||
{ |
|||
uint deltaMinusOne = reader.ReadUnsignedExpGolomb(); |
|||
if (deltaMinusOne >= int.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC short-term reference-picture delta is too large."); |
|||
} |
|||
|
|||
long deltaPictureOrder = (long)previousDelta + deltaMinusOne + 1; |
|||
if (deltaPictureOrder > int.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC short-term reference-picture delta is too large."); |
|||
} |
|||
|
|||
previousDelta = (int)deltaPictureOrder; |
|||
deltaPictureOrders[pictureCount] = previousDelta; |
|||
usedByCurrentPicture[pictureCount] = reader.ReadFlag(); |
|||
pictureCount++; |
|||
} |
|||
} |
|||
|
|||
result.Count = pictureCount; |
|||
return result; |
|||
} |
|||
} |
|||
@ -1,494 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the decoded header and entropy-coded payload of one HEVC still-picture slice segment.
|
|||
/// </summary>
|
|||
internal sealed class HevcSliceSegmentHeader |
|||
{ |
|||
/// <summary>
|
|||
/// The decoded-byte lengths preceding each tile or wavefront entropy entry point.
|
|||
/// </summary>
|
|||
private int[] entryPointOffsets = []; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcSliceSegmentHeader"/> class.
|
|||
/// </summary>
|
|||
/// <param name="nalUnit">The item-local instantaneous-decoder-refresh NAL unit.</param>
|
|||
/// <param name="pictureParameterSets">The picture parameter sets available to the coded image item.</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The NAL unit is not a base-layer IDR slice, references unavailable parameters, or contains malformed
|
|||
/// still-picture slice-header syntax.
|
|||
/// </exception>
|
|||
public HevcSliceSegmentHeader( |
|||
HevcNalUnit nalUnit, |
|||
IReadOnlyList<HevcPictureParameterSet> pictureParameterSets) |
|||
{ |
|||
if (!nalUnit.Header.IsInstantaneousDecoderRefresh |
|||
|| nalUnit.Header.LayerId != 0 |
|||
|| nalUnit.Header.TemporalId != 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC image item contains a non-IDR or layered coded slice."); |
|||
} |
|||
|
|||
this.NalUnit = nalUnit; |
|||
HevcBitReader reader = new(nalUnit.Rbsp.Span); |
|||
this.FirstSliceSegmentInPicture = reader.ReadFlag(); |
|||
|
|||
// An IDR item has no earlier picture whose output can affect the returned still image. Consume the required
|
|||
// random-access flag without retaining sequence-output state in the image decoder.
|
|||
reader.ReadFlag(); |
|||
|
|||
uint pictureParameterSetId = reader.ReadUnsignedExpGolomb(); |
|||
if (pictureParameterSetId > 63) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice segment has an invalid picture-parameter-set identifier."); |
|||
} |
|||
|
|||
HevcPictureParameterSet? pictureParameterSet = null; |
|||
foreach (HevcPictureParameterSet candidate in pictureParameterSets) |
|||
{ |
|||
if (candidate.Id == pictureParameterSetId) |
|||
{ |
|||
pictureParameterSet = candidate; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (pictureParameterSet is null) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice segment references an unavailable picture parameter set."); |
|||
} |
|||
|
|||
this.PictureParameterSet = pictureParameterSet; |
|||
HevcSequenceParameterSet sequenceParameterSet = pictureParameterSet.SequenceParameterSet; |
|||
if (pictureParameterSet.DependentSliceSegmentsEnabled && !this.FirstSliceSegmentInPicture) |
|||
{ |
|||
this.DependentSliceSegment = reader.ReadFlag(); |
|||
} |
|||
|
|||
int codingTreeBlockColumns = HevcParameterSetSyntax.GetCodingTreeBlockCount( |
|||
sequenceParameterSet.Width, |
|||
sequenceParameterSet.CodingTreeBlockLog2); |
|||
|
|||
int codingTreeBlockRows = HevcParameterSetSyntax.GetCodingTreeBlockCount( |
|||
sequenceParameterSet.Height, |
|||
sequenceParameterSet.CodingTreeBlockLog2); |
|||
|
|||
int codingTreeBlockCount = codingTreeBlockColumns * codingTreeBlockRows; |
|||
if (!this.FirstSliceSegmentInPicture) |
|||
{ |
|||
int addressBitCount = HevcParameterSetSyntax.GetCeilingLog2(codingTreeBlockCount); |
|||
uint address = reader.ReadBits(addressBitCount); |
|||
if (address >= codingTreeBlockCount) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice segment address is outside the coded picture."); |
|||
} |
|||
|
|||
this.SliceSegmentAddress = (int)address; |
|||
} |
|||
|
|||
if (!this.DependentSliceSegment) |
|||
{ |
|||
this.ReadIndependentHeader(ref reader); |
|||
} |
|||
|
|||
this.ReadEntryPoints(ref reader, codingTreeBlockCount); |
|||
if (pictureParameterSet.SliceSegmentHeaderExtensionPresent) |
|||
{ |
|||
uint extensionLength = reader.ReadUnsignedExpGolomb(); |
|||
if (extensionLength > int.MaxValue || extensionLength > reader.BitsRemaining / 8) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice-segment header extension is truncated."); |
|||
} |
|||
|
|||
for (int byteIndex = 0; byteIndex < extensionLength; byteIndex++) |
|||
{ |
|||
reader.ReadBits(8); |
|||
} |
|||
} |
|||
|
|||
reader.ReadByteAlignment(); |
|||
this.HeaderLength = reader.BitPosition / 8; |
|||
this.SliceData = nalUnit.Rbsp[this.HeaderLength..]; |
|||
if (this.SliceData.IsEmpty) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice segment contains no entropy-coded data."); |
|||
} |
|||
|
|||
int encodedHeaderLength = GetEncodedPayloadOffset( |
|||
this.HeaderLength, |
|||
nalUnit.EmulationPreventionBytePositions.Span); |
|||
|
|||
int availableEncodedData = nalUnit.EncodedPayloadLength - encodedHeaderLength; |
|||
int cumulativeEntryPointOffset = 0; |
|||
int previousDecodedBoundary = this.HeaderLength; |
|||
int[] entryPointOffsets = this.entryPointOffsets; |
|||
for (int index = 0; index < entryPointOffsets.Length; index++) |
|||
{ |
|||
int entryPointOffset = entryPointOffsets[index]; |
|||
if (cumulativeEntryPointOffset > availableEncodedData - entryPointOffset) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice entry point extends beyond its NAL unit."); |
|||
} |
|||
|
|||
cumulativeEntryPointOffset += entryPointOffset; |
|||
int encodedBoundary = encodedHeaderLength + cumulativeEntryPointOffset; |
|||
int decodedBoundary = GetDecodedPayloadOffset( |
|||
encodedBoundary, |
|||
nalUnit.EmulationPreventionBytePositions.Span); |
|||
|
|||
// entry_point_offset_minus1 counts encoded NAL bytes. The entropy decoder consumes the de-escaped RBSP,
|
|||
// so each retained substream length must exclude prevention bytes from its own encoded interval.
|
|||
entryPointOffsets[index] = decodedBoundary - previousDecodedBoundary; |
|||
previousDecodedBoundary = decodedBoundary; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the complete decoded NAL unit containing this slice segment.
|
|||
/// </summary>
|
|||
public HevcNalUnit NalUnit { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this is the first slice segment of the coded picture.
|
|||
/// </summary>
|
|||
public bool FirstSliceSegmentInPicture { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this segment inherits syntax from an earlier independent slice.
|
|||
/// </summary>
|
|||
public bool DependentSliceSegment { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the picture parameters selected by this slice segment.
|
|||
/// </summary>
|
|||
public HevcPictureParameterSet PictureParameterSet { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the raster-scan address of the first coding-tree block in this slice segment.
|
|||
/// </summary>
|
|||
public int SliceSegmentAddress { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the independent slice prediction type, or <see langword="null"/> for a dependent segment.
|
|||
/// </summary>
|
|||
public HevcSliceType? SliceType { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the selected color-plane identifier for separate-plane 4:4:4 coding.
|
|||
/// </summary>
|
|||
public byte ColorPlaneId { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether luma sample-adaptive offset filtering is enabled.
|
|||
/// </summary>
|
|||
public bool? SampleAdaptiveOffsetLumaEnabled { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether chroma sample-adaptive offset filtering is enabled.
|
|||
/// </summary>
|
|||
public bool? SampleAdaptiveOffsetChromaEnabled { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the effective luma quantization parameter, or <see langword="null"/> for a dependent segment.
|
|||
/// </summary>
|
|||
public int? QuantizationParameter { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the slice-level Cb quantization-parameter offset.
|
|||
/// </summary>
|
|||
public int ChromaCbQuantizationParameterOffset { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the slice-level Cr quantization-parameter offset.
|
|||
/// </summary>
|
|||
public int ChromaCrQuantizationParameterOffset { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether coding units can select the PPS chroma-offset list.
|
|||
/// </summary>
|
|||
public bool? ChromaQuantizationParameterOffsetListEnabled { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether deblocking is disabled for this independent slice.
|
|||
/// </summary>
|
|||
public bool? DeblockingFilterDisabled { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets half the effective deblocking beta-threshold offset.
|
|||
/// </summary>
|
|||
public int DeblockingFilterBetaOffsetDiv2 { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets half the effective deblocking clipping-threshold offset.
|
|||
/// </summary>
|
|||
public int DeblockingFilterTcOffsetDiv2 { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether in-loop filtering crosses slice boundaries.
|
|||
/// </summary>
|
|||
public bool? LoopFilterAcrossSlicesEnabled { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the decoded-byte lengths that separate tile or wavefront entropy substreams after the first substream.
|
|||
/// </summary>
|
|||
public IReadOnlyList<int> EntryPointOffsets => this.entryPointOffsets; |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of independently initialized tile or wavefront entropy substreams in this slice segment.
|
|||
/// </summary>
|
|||
public int EntropySubstreamCount => this.entryPointOffsets.Length + 1; |
|||
|
|||
/// <summary>
|
|||
/// Gets the slice-header length in decoded raw-byte-sequence payload bytes.
|
|||
/// </summary>
|
|||
public int HeaderLength { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the entropy-coded slice data following byte alignment.
|
|||
/// </summary>
|
|||
public ReadOnlyMemory<byte> SliceData { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets one bounded entropy substream in slice coding order.
|
|||
/// </summary>
|
|||
/// <param name="index">The zero-based entropy-substream index.</param>
|
|||
/// <returns>The decoded raw-byte-sequence payload bytes belonging to the selected substream.</returns>
|
|||
public ReadOnlyMemory<byte> GetEntropySubstream(int index) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(index, 0, this.entryPointOffsets.Length, nameof(index)); |
|||
int offset = 0; |
|||
for (int precedingIndex = 0; precedingIndex < index; precedingIndex++) |
|||
{ |
|||
offset += this.entryPointOffsets[precedingIndex]; |
|||
} |
|||
|
|||
int length = index < this.entryPointOffsets.Length |
|||
? this.entryPointOffsets[index] |
|||
: this.SliceData.Length - offset; |
|||
|
|||
return this.SliceData.Slice(offset, length); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads fields carried only by an independent slice-segment header.
|
|||
/// </summary>
|
|||
/// <param name="reader">The slice-segment raw byte sequence payload reader.</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The slice is not intra-coded or its quantization and filter fields are outside the governing parameter bounds.
|
|||
/// </exception>
|
|||
private void ReadIndependentHeader(ref HevcBitReader reader) |
|||
{ |
|||
HevcPictureParameterSet pictureParameterSet = this.PictureParameterSet; |
|||
HevcSequenceParameterSet sequenceParameterSet = pictureParameterSet.SequenceParameterSet; |
|||
for (int extraBit = 0; extraBit < pictureParameterSet.ExtraSliceHeaderBitCount; extraBit++) |
|||
{ |
|||
reader.ReadFlag(); |
|||
} |
|||
|
|||
uint sliceType = reader.ReadUnsignedExpGolomb(); |
|||
if (sliceType != (uint)HevcSliceType.Intra) |
|||
{ |
|||
throw new InvalidImageContentException("An independently coded HEVC image item must contain intra IDR slices."); |
|||
} |
|||
|
|||
this.SliceType = HevcSliceType.Intra; |
|||
if (pictureParameterSet.OutputFlagPresent && !reader.ReadFlag()) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC image-item slice is marked as unavailable for output."); |
|||
} |
|||
|
|||
if (sequenceParameterSet.SeparateColorPlaneFlag) |
|||
{ |
|||
this.ColorPlaneId = (byte)reader.ReadBits(2); |
|||
if (this.ColorPlaneId > 2) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice segment has an invalid separate color-plane identifier."); |
|||
} |
|||
} |
|||
|
|||
bool hasCombinedChromaPlanes = sequenceParameterSet.ChromaFormat != 0 |
|||
&& !sequenceParameterSet.SeparateColorPlaneFlag; |
|||
|
|||
if (sequenceParameterSet.SampleAdaptiveOffsetEnabled) |
|||
{ |
|||
this.SampleAdaptiveOffsetLumaEnabled = reader.ReadFlag(); |
|||
this.SampleAdaptiveOffsetChromaEnabled = hasCombinedChromaPlanes && reader.ReadFlag(); |
|||
} |
|||
|
|||
int sliceQuantizationParameterDelta = reader.ReadSignedExpGolomb(); |
|||
long quantizationParameter = 26L |
|||
+ pictureParameterSet.InitialQuantizationParameterMinus26 |
|||
+ sliceQuantizationParameterDelta; |
|||
|
|||
int minimumQuantizationParameter = -6 * (sequenceParameterSet.BitDepthLuma - 8); |
|||
if (quantizationParameter < minimumQuantizationParameter || quantizationParameter > 51) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice segment has an invalid luma quantization parameter."); |
|||
} |
|||
|
|||
this.QuantizationParameter = (int)quantizationParameter; |
|||
if (pictureParameterSet.SliceChromaQuantizationParameterOffsetsPresent && hasCombinedChromaPlanes) |
|||
{ |
|||
this.ChromaCbQuantizationParameterOffset = HevcParameterSetSyntax.ReadQuantizationParameterOffset(ref reader); |
|||
this.ChromaCrQuantizationParameterOffset = HevcParameterSetSyntax.ReadQuantizationParameterOffset(ref reader); |
|||
if (pictureParameterSet.ChromaCbQuantizationParameterOffset + this.ChromaCbQuantizationParameterOffset is < -12 or > 12 |
|||
|| pictureParameterSet.ChromaCrQuantizationParameterOffset + this.ChromaCrQuantizationParameterOffset is < -12 or > 12) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice and picture chroma quantization offsets have an invalid sum."); |
|||
} |
|||
} |
|||
|
|||
if (pictureParameterSet.ChromaQuantizationParameterOffsetsCb.Count != 0) |
|||
{ |
|||
this.ChromaQuantizationParameterOffsetListEnabled = reader.ReadFlag(); |
|||
} |
|||
|
|||
this.ReadDeblockingFilterFields(ref reader); |
|||
bool sampleAdaptiveOffsetEnabled = this.SampleAdaptiveOffsetLumaEnabled == true |
|||
|| this.SampleAdaptiveOffsetChromaEnabled == true; |
|||
|
|||
if (pictureParameterSet.LoopFilterAcrossSlicesEnabled |
|||
&& (sampleAdaptiveOffsetEnabled || this.DeblockingFilterDisabled == false)) |
|||
{ |
|||
this.LoopFilterAcrossSlicesEnabled = reader.ReadFlag(); |
|||
} |
|||
else |
|||
{ |
|||
this.LoopFilterAcrossSlicesEnabled = pictureParameterSet.LoopFilterAcrossSlicesEnabled; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resolves the independent slice's effective deblocking mode and threshold offsets.
|
|||
/// </summary>
|
|||
/// <param name="reader">The slice-segment raw byte sequence payload reader.</param>
|
|||
private void ReadDeblockingFilterFields(ref HevcBitReader reader) |
|||
{ |
|||
HevcPictureParameterSet pictureParameterSet = this.PictureParameterSet; |
|||
bool overrideFilter = false; |
|||
if (pictureParameterSet.DeblockingFilterControlPresent |
|||
&& pictureParameterSet.DeblockingFilterOverrideEnabled) |
|||
{ |
|||
overrideFilter = reader.ReadFlag(); |
|||
} |
|||
|
|||
if (overrideFilter) |
|||
{ |
|||
this.DeblockingFilterDisabled = reader.ReadFlag(); |
|||
if (this.DeblockingFilterDisabled == false) |
|||
{ |
|||
this.DeblockingFilterBetaOffsetDiv2 = HevcParameterSetSyntax.ReadDeblockingFilterOffset(ref reader); |
|||
this.DeblockingFilterTcOffsetDiv2 = HevcParameterSetSyntax.ReadDeblockingFilterOffset(ref reader); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
this.DeblockingFilterDisabled = pictureParameterSet.DeblockingFilterControlPresent |
|||
&& pictureParameterSet.DeblockingFilterDisabled; |
|||
|
|||
this.DeblockingFilterBetaOffsetDiv2 = pictureParameterSet.DeblockingFilterBetaOffsetDiv2; |
|||
this.DeblockingFilterTcOffsetDiv2 = pictureParameterSet.DeblockingFilterTcOffsetDiv2; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads tile or wavefront substream entry-point byte lengths.
|
|||
/// </summary>
|
|||
/// <param name="reader">The slice-segment raw byte sequence payload reader.</param>
|
|||
/// <param name="codingTreeBlockCount">The number of coding-tree blocks in the coded picture.</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The entry-point count, field width, or byte length exceeds the bounded picture or integer range.
|
|||
/// </exception>
|
|||
private void ReadEntryPoints(ref HevcBitReader reader, int codingTreeBlockCount) |
|||
{ |
|||
HevcPictureParameterSet pictureParameterSet = this.PictureParameterSet; |
|||
if (!pictureParameterSet.TilesEnabled && !pictureParameterSet.EntropyCodingSynchronizationEnabled) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
uint entryPointCount = reader.ReadUnsignedExpGolomb(); |
|||
if (entryPointCount >= codingTreeBlockCount) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice segment declares too many entropy entry points."); |
|||
} |
|||
|
|||
if (entryPointCount == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
uint offsetLengthMinusOne = reader.ReadUnsignedExpGolomb(); |
|||
if (offsetLengthMinusOne > 31) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice entry-point offset width is invalid."); |
|||
} |
|||
|
|||
int offsetBitCount = (int)offsetLengthMinusOne + 1; |
|||
int[] entryPointOffsets = new int[entryPointCount]; |
|||
for (int entryPoint = 0; entryPoint < entryPointOffsets.Length; entryPoint++) |
|||
{ |
|||
uint entryPointOffsetMinusOne = reader.ReadBits(offsetBitCount); |
|||
if (entryPointOffsetMinusOne >= int.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC slice entry-point byte length is too large."); |
|||
} |
|||
|
|||
entryPointOffsets[entryPoint] = (int)entryPointOffsetMinusOne + 1; |
|||
} |
|||
|
|||
this.entryPointOffsets = entryPointOffsets; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an RBSP byte boundary to its corresponding encoded-payload boundary.
|
|||
/// </summary>
|
|||
/// <param name="rbspOffset">The decoded raw-byte-sequence payload offset.</param>
|
|||
/// <param name="emulationPreventionBytePositions">The removed encoded-payload byte positions.</param>
|
|||
/// <returns>The encoded byte-sequence payload offset at the same syntax boundary.</returns>
|
|||
public static int GetEncodedPayloadOffset( |
|||
int rbspOffset, |
|||
ReadOnlySpan<int> emulationPreventionBytePositions) |
|||
{ |
|||
int encodedOffset = rbspOffset; |
|||
foreach (int preventionBytePosition in emulationPreventionBytePositions) |
|||
{ |
|||
if (preventionBytePosition >= encodedOffset) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
encodedOffset++; |
|||
} |
|||
|
|||
return encodedOffset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an encoded-payload byte boundary to its corresponding RBSP boundary.
|
|||
/// </summary>
|
|||
/// <param name="encodedOffset">The encoded byte-sequence payload offset.</param>
|
|||
/// <param name="emulationPreventionBytePositions">The removed encoded-payload byte positions.</param>
|
|||
/// <returns>The decoded raw-byte-sequence payload offset at the same syntax boundary.</returns>
|
|||
public static int GetDecodedPayloadOffset( |
|||
int encodedOffset, |
|||
ReadOnlySpan<int> emulationPreventionBytePositions) |
|||
{ |
|||
int decodedOffset = encodedOffset; |
|||
foreach (int preventionBytePosition in emulationPreventionBytePositions) |
|||
{ |
|||
if (preventionBytePosition >= encodedOffset) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
decodedOffset--; |
|||
} |
|||
|
|||
return decodedOffset; |
|||
} |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the prediction structure signaled for an HEVC slice segment.
|
|||
/// </summary>
|
|||
internal enum HevcSliceType |
|||
{ |
|||
/// <summary>
|
|||
/// The slice can use intra and bidirectional inter prediction.
|
|||
/// </summary>
|
|||
Bidirectional = 0, |
|||
|
|||
/// <summary>
|
|||
/// The slice can use intra and forward inter prediction.
|
|||
/// </summary>
|
|||
Predictive = 1, |
|||
|
|||
/// <summary>
|
|||
/// The slice uses only intra-picture prediction.
|
|||
/// </summary>
|
|||
Intra = 2 |
|||
} |
|||
@ -1,349 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Reads the presentation and exposed metadata carried by prefix SEI NAL units for one bounded still picture.
|
|||
/// </summary>
|
|||
internal sealed class HevcSupplementalEnhancementInformation |
|||
{ |
|||
private const int DisplayOrientationPayloadType = 47; |
|||
private const int MasteringDisplayColorVolumePayloadType = 137; |
|||
private const int NoDisplayPayloadType = 135; |
|||
private const int ContentLightLevelPayloadType = 144; |
|||
private const int AlternativeTransferCharacteristicsPayloadType = 147; |
|||
private const int AmbientViewingEnvironmentPayloadType = 148; |
|||
private const int ContentColorVolumePayloadType = 149; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the selected still picture is marked as unavailable for display.
|
|||
/// </summary>
|
|||
public bool NoDisplay { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether an active display-orientation message is present.
|
|||
/// </summary>
|
|||
public bool HasDisplayOrientation { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the cropped decoded picture is flipped horizontally before rotation.
|
|||
/// </summary>
|
|||
public bool HorizontalFlip { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the cropped decoded picture is flipped vertically before rotation.
|
|||
/// </summary>
|
|||
public bool VerticalFlip { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the unsigned fraction of one complete anticlockwise turn applied after flipping.
|
|||
/// </summary>
|
|||
public ushort AnticlockwiseRotation { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the preferred CICP transfer-characteristics code, when signaled.
|
|||
/// </summary>
|
|||
public byte? PreferredTransferCharacteristics { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the content light-level description, when signaled.
|
|||
/// </summary>
|
|||
public HeifContentLightLevel? ContentLightLevel { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the mastering-display color volume, when signaled.
|
|||
/// </summary>
|
|||
public HeifMasteringDisplayColorVolume? MasteringDisplayColorVolume { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the content color volume, when signaled and not cancelled.
|
|||
/// </summary>
|
|||
public HeifContentColorVolume? ContentColorVolume { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the ambient viewing environment, when signaled.
|
|||
/// </summary>
|
|||
public HeifAmbientViewingEnvironment? AmbientViewingEnvironment { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Reads every byte-aligned message from one prefix SEI RBSP in bitstream order.
|
|||
/// </summary>
|
|||
/// <param name="rbsp">The decoded NAL payload, including its RBSP trailing byte.</param>
|
|||
public void ReadPrefixNalUnit(ReadOnlySpan<byte> rbsp) |
|||
{ |
|||
if (rbsp.IsEmpty) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC prefix SEI NAL unit is missing RBSP trailing bits."); |
|||
} |
|||
|
|||
int offset = 0; |
|||
while (rbsp.Length - offset > 1) |
|||
{ |
|||
int payloadType = ReadExtendedValue(rbsp, ref offset, "payload type"); |
|||
int payloadSize = ReadExtendedValue(rbsp, ref offset, "payload size"); |
|||
if (payloadSize > rbsp.Length - offset) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC prefix SEI message payload is truncated."); |
|||
} |
|||
|
|||
ReadOnlySpan<byte> payload = rbsp.Slice(offset, payloadSize); |
|||
offset += payloadSize; |
|||
switch (payloadType) |
|||
{ |
|||
case DisplayOrientationPayloadType: |
|||
this.ReadDisplayOrientation(payload); |
|||
break; |
|||
case NoDisplayPayloadType: |
|||
this.ReadNoDisplay(payload); |
|||
break; |
|||
case MasteringDisplayColorVolumePayloadType: |
|||
this.ReadMasteringDisplayColorVolume(payload); |
|||
break; |
|||
case ContentLightLevelPayloadType: |
|||
this.ReadContentLightLevel(payload); |
|||
break; |
|||
case AlternativeTransferCharacteristicsPayloadType: |
|||
this.ReadAlternativeTransferCharacteristics(payload); |
|||
break; |
|||
case AmbientViewingEnvironmentPayloadType: |
|||
this.ReadAmbientViewingEnvironment(payload); |
|||
break; |
|||
case ContentColorVolumePayloadType: |
|||
this.ReadContentColorVolume(payload); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (offset != rbsp.Length - 1 || rbsp[offset] != 0x80) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC prefix SEI NAL unit has invalid RBSP trailing bits."); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the legacy HEVC display-orientation payload retained by pinned HM.
|
|||
/// </summary>
|
|||
private void ReadDisplayOrientation(ReadOnlySpan<byte> payload) |
|||
{ |
|||
HevcBitReader reader = new(payload); |
|||
bool cancel = reader.ReadFlag(); |
|||
if (cancel) |
|||
{ |
|||
this.HasDisplayOrientation = false; |
|||
this.HorizontalFlip = false; |
|||
this.VerticalFlip = false; |
|||
this.AnticlockwiseRotation = 0; |
|||
ValidatePayloadExtension(ref reader, "display orientation"); |
|||
return; |
|||
} |
|||
|
|||
this.HorizontalFlip = reader.ReadFlag(); |
|||
this.VerticalFlip = reader.ReadFlag(); |
|||
this.AnticlockwiseRotation = (ushort)reader.ReadBits(16); |
|||
_ = reader.ReadFlag(); |
|||
ValidatePayloadExtension(ref reader, "display orientation"); |
|||
this.HasDisplayOrientation = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Records that the selected picture is not intended for display.
|
|||
/// </summary>
|
|||
private void ReadNoDisplay(ReadOnlySpan<byte> payload) |
|||
{ |
|||
// Pinned HM writes no syntax bits for this message, producing a zero-byte payload. A nonempty payload can
|
|||
// contain only the generic reserved extension and payload-alignment marker handled by the shared validator.
|
|||
ValidateByteAlignedPayloadExtension(payload, "no-display"); |
|||
this.NoDisplay = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads mastering-display metadata in its HEVC fixed-point representation.
|
|||
/// </summary>
|
|||
private void ReadMasteringDisplayColorVolume(ReadOnlySpan<byte> payload) |
|||
{ |
|||
const int syntaxLength = 24; |
|||
if (payload.Length < syntaxLength) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC mastering-display color-volume SEI payload is truncated."); |
|||
} |
|||
|
|||
this.MasteringDisplayColorVolume = HeifPropertyParser.ParseMasteringDisplayColorVolume(payload[..syntaxLength]); |
|||
ValidateByteAlignedPayloadExtension(payload[syntaxLength..], "mastering-display color-volume"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads content light-level metadata in its HEVC fixed-width representation.
|
|||
/// </summary>
|
|||
private void ReadContentLightLevel(ReadOnlySpan<byte> payload) |
|||
{ |
|||
const int syntaxLength = 4; |
|||
if (payload.Length < syntaxLength) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC content light-level SEI payload is truncated."); |
|||
} |
|||
|
|||
this.ContentLightLevel = HeifPropertyParser.ParseContentLightLevel(payload[..syntaxLength]); |
|||
ValidateByteAlignedPayloadExtension(payload[syntaxLength..], "content light-level"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the preferred transfer function applied when the container does not provide one.
|
|||
/// </summary>
|
|||
private void ReadAlternativeTransferCharacteristics(ReadOnlySpan<byte> payload) |
|||
{ |
|||
if (payload.IsEmpty) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC alternative-transfer-characteristics SEI payload is truncated."); |
|||
} |
|||
|
|||
this.PreferredTransferCharacteristics = payload[0]; |
|||
ValidateByteAlignedPayloadExtension(payload[1..], "alternative transfer characteristics"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the nominal ambient viewing environment.
|
|||
/// </summary>
|
|||
private void ReadAmbientViewingEnvironment(ReadOnlySpan<byte> payload) |
|||
{ |
|||
const int syntaxLength = 8; |
|||
if (payload.Length < syntaxLength) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC ambient-viewing-environment SEI payload is truncated."); |
|||
} |
|||
|
|||
this.AmbientViewingEnvironment = HeifPropertyParser.ParseAmbientViewingEnvironment(payload[..syntaxLength]); |
|||
ValidateByteAlignedPayloadExtension(payload[syntaxLength..], "ambient viewing environment"); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the bit-packed content color-volume syntax and applies cancellation in message order.
|
|||
/// </summary>
|
|||
private void ReadContentColorVolume(ReadOnlySpan<byte> payload) |
|||
{ |
|||
HevcBitReader reader = new(payload); |
|||
bool cancel = reader.ReadFlag(); |
|||
if (cancel) |
|||
{ |
|||
this.ContentColorVolume = null; |
|||
ValidatePayloadExtension(ref reader, "content color-volume"); |
|||
return; |
|||
} |
|||
|
|||
_ = reader.ReadFlag(); |
|||
bool primariesPresent = reader.ReadFlag(); |
|||
bool minimumLuminancePresent = reader.ReadFlag(); |
|||
bool maximumLuminancePresent = reader.ReadFlag(); |
|||
bool averageLuminancePresent = reader.ReadFlag(); |
|||
RgbPrimariesChromaticityCoordinates? primaries = null; |
|||
if (primariesPresent) |
|||
{ |
|||
int greenX = unchecked((int)reader.ReadBits(32)); |
|||
int greenY = unchecked((int)reader.ReadBits(32)); |
|||
int blueX = unchecked((int)reader.ReadBits(32)); |
|||
int blueY = unchecked((int)reader.ReadBits(32)); |
|||
int redX = unchecked((int)reader.ReadBits(32)); |
|||
int redY = unchecked((int)reader.ReadBits(32)); |
|||
const int maximumChromaticityValue = 5_000_000; |
|||
if (greenX is < -maximumChromaticityValue or > maximumChromaticityValue |
|||
|| greenY is < -maximumChromaticityValue or > maximumChromaticityValue |
|||
|| blueX is < -maximumChromaticityValue or > maximumChromaticityValue |
|||
|| blueY is < -maximumChromaticityValue or > maximumChromaticityValue |
|||
|| redX is < -maximumChromaticityValue or > maximumChromaticityValue |
|||
|| redY is < -maximumChromaticityValue or > maximumChromaticityValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC content color-volume SEI payload has an out-of-range primary coordinate."); |
|||
} |
|||
|
|||
const float chromaticityScale = 1F / 50000F; |
|||
|
|||
// H.274 stores signed primary coordinates in G, B, R order. Reorder them once at the codec boundary so
|
|||
// the retained value has the same observable RGB coordinate contract as the equivalent item property.
|
|||
primaries = new RgbPrimariesChromaticityCoordinates( |
|||
new CieXyChromaticityCoordinates(redX * chromaticityScale, redY * chromaticityScale), |
|||
new CieXyChromaticityCoordinates(greenX * chromaticityScale, greenY * chromaticityScale), |
|||
new CieXyChromaticityCoordinates(blueX * chromaticityScale, blueY * chromaticityScale)); |
|||
} |
|||
|
|||
uint? minimumLuminance = minimumLuminancePresent ? reader.ReadBits(32) : null; |
|||
uint? maximumLuminance = maximumLuminancePresent ? reader.ReadBits(32) : null; |
|||
uint? averageLuminance = averageLuminancePresent ? reader.ReadBits(32) : null; |
|||
if ((minimumLuminance is not null && averageLuminance is not null && minimumLuminance.Value > averageLuminance.Value) |
|||
|| (averageLuminance is not null && maximumLuminance is not null && averageLuminance.Value > maximumLuminance.Value) |
|||
|| (minimumLuminance is not null && maximumLuminance is not null && minimumLuminance.Value > maximumLuminance.Value)) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC content color-volume SEI luminance values are not in ascending order."); |
|||
} |
|||
|
|||
ValidatePayloadExtension(ref reader, "content color-volume"); |
|||
const double luminanceScale = 1D / 10000000D; |
|||
|
|||
this.ContentColorVolume = new HeifContentColorVolume( |
|||
primaries, |
|||
minimumLuminance * luminanceScale, |
|||
maximumLuminance * luminanceScale, |
|||
averageLuminance * luminanceScale); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads an extended SEI payload type or size whose continuation bytes are all 255.
|
|||
/// </summary>
|
|||
private static int ReadExtendedValue(ReadOnlySpan<byte> data, ref int offset, string valueName) |
|||
{ |
|||
int value = 0; |
|||
while (true) |
|||
{ |
|||
if ((uint)offset >= (uint)data.Length) |
|||
{ |
|||
throw new InvalidImageContentException($"The HEVC prefix SEI {valueName} is truncated."); |
|||
} |
|||
|
|||
int current = data[offset++]; |
|||
if (value > int.MaxValue - current) |
|||
{ |
|||
throw new InvalidImageContentException($"The HEVC prefix SEI {valueName} is too large."); |
|||
} |
|||
|
|||
value += current; |
|||
if (current != byte.MaxValue) |
|||
{ |
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Validates an optional extension following fixed byte-aligned SEI syntax.
|
|||
/// </summary>
|
|||
private static void ValidateByteAlignedPayloadExtension(ReadOnlySpan<byte> extension, string payloadName) |
|||
{ |
|||
if (extension.IsEmpty) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
HevcBitReader reader = new(extension); |
|||
ValidatePayloadExtension(ref reader, payloadName); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Validates reserved payload-extension data followed by its final one bit and zero padding.
|
|||
/// </summary>
|
|||
private static void ValidatePayloadExtension(ref HevcBitReader reader, string payloadName) |
|||
{ |
|||
bool foundMarker = false; |
|||
while (reader.BitsRemaining > 0) |
|||
{ |
|||
foundMarker |= reader.ReadFlag(); |
|||
} |
|||
|
|||
// The final set bit is payload_bit_equal_to_one; any preceding bits are the reserved extension data that
|
|||
// pinned HM deliberately skips. An all-zero remainder has no marker and is therefore not a complete payload.
|
|||
if (!foundMarker) |
|||
{ |
|||
throw new InvalidImageContentException($"The HEVC {payloadName} SEI payload has invalid trailing bits."); |
|||
} |
|||
} |
|||
} |
|||
@ -1,196 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Maps HEVC coding-tree blocks between picture raster order and tile-scan order.
|
|||
/// </summary>
|
|||
internal readonly struct HevcTileLayout |
|||
{ |
|||
/// <summary>
|
|||
/// The tile widths in coding-tree blocks.
|
|||
/// </summary>
|
|||
private readonly IReadOnlyList<int> columnWidths; |
|||
|
|||
/// <summary>
|
|||
/// The tile heights in coding-tree blocks.
|
|||
/// </summary>
|
|||
private readonly IReadOnlyList<int> rowHeights; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcTileLayout"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="pictureParameterSet">The picture tile geometry.</param>
|
|||
public HevcTileLayout(HevcPictureParameterSet pictureParameterSet) |
|||
: this(pictureParameterSet.TileColumnWidths, pictureParameterSet.TileRowHeights) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcTileLayout"/> struct from validated tile dimensions.
|
|||
/// </summary>
|
|||
/// <param name="columnWidths">The tile-column widths in coding-tree blocks.</param>
|
|||
/// <param name="rowHeights">The tile-row heights in coding-tree blocks.</param>
|
|||
public HevcTileLayout(IReadOnlyList<int> columnWidths, IReadOnlyList<int> rowHeights) |
|||
{ |
|||
this.columnWidths = columnWidths; |
|||
this.rowHeights = rowHeights; |
|||
this.ColumnCount = this.columnWidths.Count; |
|||
this.RowCount = this.rowHeights.Count; |
|||
this.Width = Sum(this.columnWidths); |
|||
this.Height = Sum(this.rowHeights); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of tile columns.
|
|||
/// </summary>
|
|||
public int ColumnCount { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of tile rows.
|
|||
/// </summary>
|
|||
public int RowCount { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the picture width in coding-tree blocks.
|
|||
/// </summary>
|
|||
public int Width { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the picture height in coding-tree blocks.
|
|||
/// </summary>
|
|||
public int Height { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of tiles in the picture.
|
|||
/// </summary>
|
|||
public int TileCount => this.ColumnCount * this.RowCount; |
|||
|
|||
/// <summary>
|
|||
/// Converts a picture raster-scan address to tile-scan order.
|
|||
/// </summary>
|
|||
/// <param name="rasterAddress">The raster-scan coding-tree-block address.</param>
|
|||
/// <returns>The corresponding tile-scan address.</returns>
|
|||
public int GetTileScanAddress(int rasterAddress) |
|||
{ |
|||
int x = rasterAddress % this.Width; |
|||
int y = rasterAddress / this.Width; |
|||
this.FindTile(x, y, out int tileColumn, out int tileRow, out int tileStartX, out int tileStartY); |
|||
int address = 0; |
|||
for (int row = 0; row < tileRow; row++) |
|||
{ |
|||
address += this.rowHeights[row] * this.Width; |
|||
} |
|||
|
|||
for (int column = 0; column < tileColumn; column++) |
|||
{ |
|||
address += this.columnWidths[column] * this.rowHeights[tileRow]; |
|||
} |
|||
|
|||
return address + ((y - tileStartY) * this.columnWidths[tileColumn]) + x - tileStartX; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a tile-scan coding-tree-block address to picture raster order.
|
|||
/// </summary>
|
|||
/// <param name="tileScanAddress">The tile-scan address.</param>
|
|||
/// <returns>The corresponding raster-scan address.</returns>
|
|||
public int GetRasterAddress(int tileScanAddress) |
|||
{ |
|||
int remaining = tileScanAddress; |
|||
int tileStartY = 0; |
|||
for (int tileRow = 0; tileRow < this.RowCount; tileRow++) |
|||
{ |
|||
int tileStartX = 0; |
|||
for (int tileColumn = 0; tileColumn < this.ColumnCount; tileColumn++) |
|||
{ |
|||
int tileWidth = this.columnWidths[tileColumn]; |
|||
int tileHeight = this.rowHeights[tileRow]; |
|||
int tileArea = tileWidth * tileHeight; |
|||
if (remaining < tileArea) |
|||
{ |
|||
int x = tileStartX + (remaining % tileWidth); |
|||
int y = tileStartY + (remaining / tileWidth); |
|||
return (y * this.Width) + x; |
|||
} |
|||
|
|||
remaining -= tileArea; |
|||
tileStartX += tileWidth; |
|||
} |
|||
|
|||
tileStartY += this.rowHeights[tileRow]; |
|||
} |
|||
|
|||
return this.Width * this.Height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the tile and tile-local position of one raster-scan coding-tree block.
|
|||
/// </summary>
|
|||
/// <param name="rasterAddress">The raster-scan address.</param>
|
|||
/// <param name="tileIndex">The zero-based tile index.</param>
|
|||
/// <param name="columnInTile">The horizontal coding-tree-block offset within the tile.</param>
|
|||
/// <param name="rowInTile">The vertical coding-tree-block offset within the tile.</param>
|
|||
/// <param name="tileWidth">The tile width in coding-tree blocks.</param>
|
|||
/// <param name="tileHeight">The tile height in coding-tree blocks.</param>
|
|||
public void GetTilePosition( |
|||
int rasterAddress, |
|||
out int tileIndex, |
|||
out int columnInTile, |
|||
out int rowInTile, |
|||
out int tileWidth, |
|||
out int tileHeight) |
|||
{ |
|||
int x = rasterAddress % this.Width; |
|||
int y = rasterAddress / this.Width; |
|||
this.FindTile(x, y, out int tileColumn, out int tileRow, out int tileStartX, out int tileStartY); |
|||
tileIndex = (tileRow * this.ColumnCount) + tileColumn; |
|||
columnInTile = x - tileStartX; |
|||
rowInTile = y - tileStartY; |
|||
tileWidth = this.columnWidths[tileColumn]; |
|||
tileHeight = this.rowHeights[tileRow]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Locates the tile containing one coding-tree-block coordinate.
|
|||
/// </summary>
|
|||
/// <param name="x">The raster coding-tree-block X coordinate.</param>
|
|||
/// <param name="y">The raster coding-tree-block Y coordinate.</param>
|
|||
/// <param name="tileColumn">The containing tile column.</param>
|
|||
/// <param name="tileRow">The containing tile row.</param>
|
|||
/// <param name="tileStartX">The containing tile's left coding-tree-block coordinate.</param>
|
|||
/// <param name="tileStartY">The containing tile's top coding-tree-block coordinate.</param>
|
|||
private void FindTile(int x, int y, out int tileColumn, out int tileRow, out int tileStartX, out int tileStartY) |
|||
{ |
|||
tileStartX = 0; |
|||
tileColumn = 0; |
|||
while (x >= tileStartX + this.columnWidths[tileColumn]) |
|||
{ |
|||
tileStartX += this.columnWidths[tileColumn++]; |
|||
} |
|||
|
|||
tileStartY = 0; |
|||
tileRow = 0; |
|||
while (y >= tileStartY + this.rowHeights[tileRow]) |
|||
{ |
|||
tileStartY += this.rowHeights[tileRow++]; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sums one complete tile dimension.
|
|||
/// </summary>
|
|||
/// <param name="values">The tile widths or heights.</param>
|
|||
/// <returns>The complete picture dimension in coding-tree blocks.</returns>
|
|||
private static int Sum(IReadOnlyList<int> values) |
|||
{ |
|||
int sum = 0; |
|||
foreach (int value in values) |
|||
{ |
|||
sum += value; |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
} |
|||
@ -1,59 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Describes one component rectangle within an HEVC transform-tree node.
|
|||
/// </summary>
|
|||
internal readonly struct HevcTransformComponentGeometry |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcTransformComponentGeometry"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The component rectangle left coordinate.</param>
|
|||
/// <param name="y">The component rectangle top coordinate.</param>
|
|||
/// <param name="width">The component rectangle width.</param>
|
|||
/// <param name="height">The component rectangle height.</param>
|
|||
/// <param name="process">Whether this transform-tree section owns the component rectangle.</param>
|
|||
/// <param name="processesAllQuadrants">Whether every child section owns a distinct component rectangle.</param>
|
|||
public HevcTransformComponentGeometry(int x, int y, int width, int height, bool process, bool processesAllQuadrants) |
|||
{ |
|||
this.X = x; |
|||
this.Y = y; |
|||
this.Width = width; |
|||
this.Height = height; |
|||
this.Process = process; |
|||
this.ProcessesAllQuadrants = processesAllQuadrants; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the component rectangle left coordinate.
|
|||
/// </summary>
|
|||
public int X { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the component rectangle top coordinate.
|
|||
/// </summary>
|
|||
public int Y { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the component rectangle width.
|
|||
/// </summary>
|
|||
public int Width { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the component rectangle height.
|
|||
/// </summary>
|
|||
public int Height { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this transform-tree section owns the component rectangle.
|
|||
/// </summary>
|
|||
public bool Process { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether each child section owns a distinct component rectangle.
|
|||
/// </summary>
|
|||
public bool ProcessesAllQuadrants { get; } |
|||
} |
|||
@ -1,158 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Maps one luma transform-tree node to its primary and subsampled component rectangles.
|
|||
/// </summary>
|
|||
internal readonly struct HevcTransformUnitGeometry |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcTransformUnitGeometry"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="log2LumaSize">The base-two logarithm of the luma transform-node side.</param>
|
|||
/// <param name="primaryPlane">The primary plane coded with luma syntax.</param>
|
|||
/// <param name="primary">The primary component rectangle.</param>
|
|||
/// <param name="chromaBlue">The blue-difference chroma rectangle.</param>
|
|||
/// <param name="chromaRed">The red-difference chroma rectangle.</param>
|
|||
/// <param name="hasCombinedChroma">Whether chroma syntax accompanies the primary luma syntax.</param>
|
|||
private HevcTransformUnitGeometry( |
|||
int log2LumaSize, |
|||
HevcPlane primaryPlane, |
|||
HevcTransformComponentGeometry primary, |
|||
HevcTransformComponentGeometry chromaBlue, |
|||
HevcTransformComponentGeometry chromaRed, |
|||
bool hasCombinedChroma) |
|||
{ |
|||
this.Log2LumaSize = log2LumaSize; |
|||
this.PrimaryPlane = primaryPlane; |
|||
this.Primary = primary; |
|||
this.ChromaBlue = chromaBlue; |
|||
this.ChromaRed = chromaRed; |
|||
this.HasCombinedChroma = hasCombinedChroma; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the base-two logarithm of the luma transform-node side.
|
|||
/// </summary>
|
|||
public int Log2LumaSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the plane coded with luma transform syntax.
|
|||
/// </summary>
|
|||
public HevcPlane PrimaryPlane { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the primary component rectangle.
|
|||
/// </summary>
|
|||
public HevcTransformComponentGeometry Primary { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue-difference chroma rectangle.
|
|||
/// </summary>
|
|||
public HevcTransformComponentGeometry ChromaBlue { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the red-difference chroma rectangle.
|
|||
/// </summary>
|
|||
public HevcTransformComponentGeometry ChromaRed { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether combined chroma syntax accompanies the primary luma syntax.
|
|||
/// </summary>
|
|||
public bool HasCombinedChroma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Creates the root component geometry for one coding unit.
|
|||
/// </summary>
|
|||
/// <param name="x">The coding-unit left luma coordinate.</param>
|
|||
/// <param name="y">The coding-unit top luma coordinate.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the coding-unit side.</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>
|
|||
/// <param name="colorPlaneIndex">The selected separate-color plane, or zero for combined coding.</param>
|
|||
/// <returns>The root transform-unit geometry.</returns>
|
|||
public static HevcTransformUnitGeometry CreateRoot( |
|||
int x, |
|||
int y, |
|||
int log2Size, |
|||
byte chromaFormat, |
|||
bool separateColorPlane, |
|||
int colorPlaneIndex) |
|||
{ |
|||
int size = 1 << log2Size; |
|||
HevcPlane primaryPlane = separateColorPlane ? (HevcPlane)colorPlaneIndex : HevcPlane.Y; |
|||
HevcTransformComponentGeometry primary = new(x, y, size, size, true, true); |
|||
if (chromaFormat == 0 || separateColorPlane) |
|||
{ |
|||
return new HevcTransformUnitGeometry(log2Size, primaryPlane, primary, default, default, false); |
|||
} |
|||
|
|||
int subsamplingX = chromaFormat is 1 or 2 ? 1 : 0; |
|||
int subsamplingY = chromaFormat == 1 ? 1 : 0; |
|||
HevcTransformComponentGeometry chroma = new( |
|||
x >> subsamplingX, |
|||
y >> subsamplingY, |
|||
size >> subsamplingX, |
|||
size >> subsamplingY, |
|||
true, |
|||
true); |
|||
|
|||
return new HevcTransformUnitGeometry(log2Size, primaryPlane, primary, chroma, chroma, true); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates one of the four Z-ordered child transform nodes.
|
|||
/// </summary>
|
|||
/// <param name="section">The child section from zero through three.</param>
|
|||
/// <returns>The selected child geometry.</returns>
|
|||
public HevcTransformUnitGeometry CreateChild(int section) |
|||
=> new( |
|||
this.Log2LumaSize - 1, |
|||
this.PrimaryPlane, |
|||
SplitComponent(this.Primary, section), |
|||
SplitComponent(this.ChromaBlue, section), |
|||
SplitComponent(this.ChromaRed, section), |
|||
this.HasCombinedChroma); |
|||
|
|||
/// <summary>
|
|||
/// Splits one component rectangle while retaining sub-minimum chroma at the owning parent level.
|
|||
/// </summary>
|
|||
/// <param name="parent">The parent component rectangle.</param>
|
|||
/// <param name="section">The luma child section from zero through three.</param>
|
|||
/// <returns>The component rectangle visible from the selected child.</returns>
|
|||
private static HevcTransformComponentGeometry SplitComponent(HevcTransformComponentGeometry parent, int section) |
|||
{ |
|||
if (!parent.Process || parent.Width == 0) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
int width = parent.Width >> 1; |
|||
int height = parent.Height >> 1; |
|||
int sampleCount = width * height; |
|||
if ((width < 4 || height < 4) && sampleCount < 16) |
|||
{ |
|||
// A component transform cannot be smaller than four by four. Its parent rectangle is associated with
|
|||
// the final luma quadrant so CBF and coefficient syntax are consumed exactly once.
|
|||
return new HevcTransformComponentGeometry(parent.X, parent.Y, parent.Width, parent.Height, section == 3, false); |
|||
} |
|||
|
|||
if (width < 4) |
|||
{ |
|||
width = 4; |
|||
height = sampleCount / width; |
|||
} |
|||
else if (height < 4) |
|||
{ |
|||
height = 4; |
|||
width = sampleCount / height; |
|||
} |
|||
|
|||
int columns = parent.Width / width; |
|||
int x = parent.X + ((section % columns) * width); |
|||
int y = parent.Y + ((section / columns) * height); |
|||
return new HevcTransformComponentGeometry(x, y, width, height, true, true); |
|||
} |
|||
} |
|||
@ -1,156 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the bounded HEVC video-parameter-set fields required to validate and decode one still-image item.
|
|||
/// </summary>
|
|||
internal sealed class HevcVideoParameterSet |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcVideoParameterSet"/> class.
|
|||
/// </summary>
|
|||
/// <param name="nalUnit">The decoded video-parameter-set NAL unit.</param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The NAL unit is not a supported, conforming base-layer video parameter set.
|
|||
/// </exception>
|
|||
public HevcVideoParameterSet(HevcNalUnit nalUnit) |
|||
{ |
|||
const byte videoParameterSetNalUnitType = 32; |
|||
if (nalUnit.Header.NalUnitType != videoParameterSetNalUnitType |
|||
|| nalUnit.Header.LayerId != 0 |
|||
|| nalUnit.Header.TemporalId != 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC video parameter set has an invalid NAL-unit header."); |
|||
} |
|||
|
|||
HevcBitReader reader = new(nalUnit.Rbsp.Span); |
|||
this.Id = (byte)reader.ReadBits(4); |
|||
|
|||
bool baseLayerInternal = reader.ReadFlag(); |
|||
bool baseLayerAvailable = reader.ReadFlag(); |
|||
if (!baseLayerInternal || !baseLayerAvailable) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC video parameter set does not make its base layer available."); |
|||
} |
|||
|
|||
int maxLayersMinusOne = (int)reader.ReadBits(6); |
|||
if (maxLayersMinusOne != 0) |
|||
{ |
|||
// HEIF auxiliary images are separate image items. Importing an HEVC multilayer selection model would
|
|||
// exceed the one-presented-image contract and is not part of the exposed still-picture profiles.
|
|||
throw new InvalidImageContentException("Layered HEVC video parameter sets are not supported for still-image items."); |
|||
} |
|||
|
|||
int maxSubLayersMinusOne = (int)reader.ReadBits(3); |
|||
if (maxSubLayersMinusOne > 6) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC video parameter set declares too many temporal sublayers."); |
|||
} |
|||
|
|||
this.MaxSubLayers = maxSubLayersMinusOne + 1; |
|||
this.TemporalIdNestingFlag = reader.ReadFlag(); |
|||
if (maxSubLayersMinusOne == 0 && !this.TemporalIdNestingFlag) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC video parameter set has invalid temporal nesting."); |
|||
} |
|||
|
|||
if (reader.ReadBits(16) != ushort.MaxValue) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC video parameter set has invalid reserved bits."); |
|||
} |
|||
|
|||
this.ProfileTierLevel = new HevcProfileTierLevel(ref reader, maxSubLayersMinusOne); |
|||
|
|||
bool subLayerOrderingInfoPresent = reader.ReadFlag(); |
|||
int firstOrderingSubLayer = subLayerOrderingInfoPresent ? 0 : maxSubLayersMinusOne; |
|||
for (int subLayer = firstOrderingSubLayer; subLayer <= maxSubLayersMinusOne; subLayer++) |
|||
{ |
|||
uint maxDecodedPictureBufferingMinusOne = reader.ReadUnsignedExpGolomb(); |
|||
uint maxNumReorderPictures = reader.ReadUnsignedExpGolomb(); |
|||
reader.ReadUnsignedExpGolomb(); |
|||
if (maxNumReorderPictures > maxDecodedPictureBufferingMinusOne) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC video parameter set has invalid sublayer ordering limits."); |
|||
} |
|||
} |
|||
|
|||
uint maxLayerId = reader.ReadBits(6); |
|||
uint numLayerSetsMinusOne = reader.ReadUnsignedExpGolomb(); |
|||
if (maxLayerId != 0 || numLayerSetsMinusOne != 0) |
|||
{ |
|||
throw new InvalidImageContentException("HEVC layer sets are not supported for still-image items."); |
|||
} |
|||
|
|||
bool timingInfoPresent = reader.ReadFlag(); |
|||
if (timingInfoPresent) |
|||
{ |
|||
// Timing and hypothetical-reference-decoder values are required for bit alignment but do not describe
|
|||
// the pixels of the one image item, so they are deliberately consumed without retained playback state.
|
|||
reader.ReadBits(32); |
|||
reader.ReadBits(32); |
|||
if (reader.ReadFlag()) |
|||
{ |
|||
reader.ReadUnsignedExpGolomb(); |
|||
} |
|||
|
|||
uint hrdParameterCount = reader.ReadUnsignedExpGolomb(); |
|||
if (hrdParameterCount > 1024) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC video parameter set declares too many HRD parameter sets."); |
|||
} |
|||
|
|||
bool nalHrdParametersPresent = false; |
|||
bool vclHrdParametersPresent = false; |
|||
bool subPictureHrdParametersPresent = false; |
|||
for (uint hrdIndex = 0; hrdIndex < hrdParameterCount; hrdIndex++) |
|||
{ |
|||
uint layerSetIndex = reader.ReadUnsignedExpGolomb(); |
|||
if (layerSetIndex != 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC HRD parameters reference an unsupported layer set."); |
|||
} |
|||
|
|||
bool commonInformationPresent = hrdIndex == 0 || reader.ReadFlag(); |
|||
HevcParameterSetSyntax.SkipHrdParameters( |
|||
ref reader, |
|||
commonInformationPresent, |
|||
maxSubLayersMinusOne, |
|||
ref nalHrdParametersPresent, |
|||
ref vclHrdParametersPresent, |
|||
ref subPictureHrdParametersPresent); |
|||
} |
|||
} |
|||
|
|||
if (reader.ReadFlag()) |
|||
{ |
|||
while (reader.HasMoreRbspData()) |
|||
{ |
|||
reader.ReadFlag(); |
|||
} |
|||
} |
|||
|
|||
reader.ReadRbspTrailingBits(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the four-bit video-parameter-set identifier.
|
|||
/// </summary>
|
|||
public byte Id { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the declared number of temporal sublayers.
|
|||
/// </summary>
|
|||
public int MaxSubLayers { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether temporal identifiers are nested.
|
|||
/// </summary>
|
|||
public bool TemporalIdNestingFlag { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the general profile, tier, constraint, and level description.
|
|||
/// </summary>
|
|||
public HevcProfileTierLevel ProfileTierLevel { get; } |
|||
} |
|||
@ -1,247 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Contains the still-image presentation fields declared by HEVC video-usability information.
|
|||
/// </summary>
|
|||
internal sealed class HevcVideoUsabilityInformation |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HevcVideoUsabilityInformation"/> class.
|
|||
/// </summary>
|
|||
/// <param name="reader">The sequence-parameter-set raw byte sequence payload reader.</param>
|
|||
/// <param name="chromaFormat">The sequence chroma-format identifier.</param>
|
|||
/// <param name="separateColorPlane">Whether 4:4:4 components are coded as separate planes.</param>
|
|||
/// <param name="maxSubLayersMinusOne">The highest declared temporal sublayer index.</param>
|
|||
/// <exception cref="InvalidImageContentException">The VUI syntax is invalid for a still-image item.</exception>
|
|||
public HevcVideoUsabilityInformation( |
|||
ref HevcBitReader reader, |
|||
byte chromaFormat, |
|||
bool separateColorPlane, |
|||
int maxSubLayersMinusOne) |
|||
{ |
|||
this.AspectRatioInfoPresent = reader.ReadFlag(); |
|||
if (this.AspectRatioInfoPresent) |
|||
{ |
|||
this.AspectRatioIdc = (byte)reader.ReadBits(8); |
|||
if (this.AspectRatioIdc == byte.MaxValue) |
|||
{ |
|||
this.SarWidth = (ushort)reader.ReadBits(16); |
|||
this.SarHeight = (ushort)reader.ReadBits(16); |
|||
if (this.SarWidth == 0 || this.SarHeight == 0) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC VUI declares an invalid extended sample aspect ratio."); |
|||
} |
|||
} |
|||
else if (this.AspectRatioIdc > 16) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC VUI declares a reserved sample aspect ratio."); |
|||
} |
|||
} |
|||
|
|||
if (reader.ReadFlag()) |
|||
{ |
|||
reader.ReadFlag(); |
|||
} |
|||
|
|||
this.VideoSignalTypePresent = reader.ReadFlag(); |
|||
if (this.VideoSignalTypePresent) |
|||
{ |
|||
reader.ReadBits(3); |
|||
this.FullRange = reader.ReadFlag(); |
|||
this.ColorDescriptionPresent = reader.ReadFlag(); |
|||
if (this.ColorDescriptionPresent) |
|||
{ |
|||
this.ColorPrimaries = (byte)reader.ReadBits(8); |
|||
this.TransferCharacteristics = (byte)reader.ReadBits(8); |
|||
this.MatrixCoefficients = (byte)reader.ReadBits(8); |
|||
} |
|||
} |
|||
|
|||
this.ChromaLocationInfoPresent = reader.ReadFlag(); |
|||
if (this.ChromaLocationInfoPresent) |
|||
{ |
|||
uint topFieldLocation = reader.ReadUnsignedExpGolomb(); |
|||
uint bottomFieldLocation = reader.ReadUnsignedExpGolomb(); |
|||
if (topFieldLocation > 5 || bottomFieldLocation > 5) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC VUI declares an invalid chroma sample location."); |
|||
} |
|||
|
|||
this.ChromaSampleLocationTopField = (HevcChromaSampleLocation)topFieldLocation; |
|||
this.ChromaSampleLocationBottomField = (HevcChromaSampleLocation)bottomFieldLocation; |
|||
} |
|||
|
|||
reader.ReadFlag(); |
|||
if (reader.ReadFlag()) |
|||
{ |
|||
// A field sequence requires paired-field presentation state and is not a single HEIF image item.
|
|||
throw new InvalidImageContentException("Interlaced HEVC field sequences are not supported as still-image items."); |
|||
} |
|||
|
|||
reader.ReadFlag(); |
|||
|
|||
this.DefaultDisplayWindowPresent = reader.ReadFlag(); |
|||
if (this.DefaultDisplayWindowPresent) |
|||
{ |
|||
int cropUnitWidth = HevcParameterSetSyntax.GetCropUnitWidth(chromaFormat, separateColorPlane); |
|||
int cropUnitHeight = HevcParameterSetSyntax.GetCropUnitHeight(chromaFormat, separateColorPlane); |
|||
this.DefaultDisplayWindowLeftOffset = ReadScaledOffset(ref reader, cropUnitWidth); |
|||
this.DefaultDisplayWindowRightOffset = ReadScaledOffset(ref reader, cropUnitWidth); |
|||
this.DefaultDisplayWindowTopOffset = ReadScaledOffset(ref reader, cropUnitHeight); |
|||
this.DefaultDisplayWindowBottomOffset = ReadScaledOffset(ref reader, cropUnitHeight); |
|||
} |
|||
|
|||
if (reader.ReadFlag()) |
|||
{ |
|||
// VUI timing and HRD fields affect scheduling, not the reconstructed still-image samples.
|
|||
reader.ReadBits(32); |
|||
reader.ReadBits(32); |
|||
if (reader.ReadFlag()) |
|||
{ |
|||
reader.ReadUnsignedExpGolomb(); |
|||
} |
|||
|
|||
if (reader.ReadFlag()) |
|||
{ |
|||
bool nalHrdParametersPresent = false; |
|||
bool vclHrdParametersPresent = false; |
|||
bool subPictureHrdParametersPresent = false; |
|||
HevcParameterSetSyntax.SkipHrdParameters( |
|||
ref reader, |
|||
true, |
|||
maxSubLayersMinusOne, |
|||
ref nalHrdParametersPresent, |
|||
ref vclHrdParametersPresent, |
|||
ref subPictureHrdParametersPresent); |
|||
} |
|||
} |
|||
|
|||
if (reader.ReadFlag()) |
|||
{ |
|||
reader.ReadFlag(); |
|||
reader.ReadFlag(); |
|||
reader.ReadFlag(); |
|||
uint minimumSpatialSegmentation = reader.ReadUnsignedExpGolomb(); |
|||
if (minimumSpatialSegmentation >= 4096) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC VUI spatial-segmentation value is invalid."); |
|||
} |
|||
|
|||
reader.ReadUnsignedExpGolomb(); |
|||
reader.ReadUnsignedExpGolomb(); |
|||
reader.ReadUnsignedExpGolomb(); |
|||
reader.ReadUnsignedExpGolomb(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether sample-aspect-ratio information is present.
|
|||
/// </summary>
|
|||
public bool AspectRatioInfoPresent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the registered sample-aspect-ratio identifier.
|
|||
/// </summary>
|
|||
public byte AspectRatioIdc { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the explicit horizontal sample spacing when <see cref="AspectRatioIdc"/> is 255.
|
|||
/// </summary>
|
|||
public ushort SarWidth { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the explicit vertical sample spacing when <see cref="AspectRatioIdc"/> is 255.
|
|||
/// </summary>
|
|||
public ushort SarHeight { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether video-signal-type information is present.
|
|||
/// </summary>
|
|||
public bool VideoSignalTypePresent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether component samples use the full numeric range.
|
|||
/// </summary>
|
|||
public bool FullRange { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether color-description fields are present.
|
|||
/// </summary>
|
|||
public bool ColorDescriptionPresent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded color-primary identifier.
|
|||
/// </summary>
|
|||
public byte ColorPrimaries { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded transfer-characteristic identifier.
|
|||
/// </summary>
|
|||
public byte TransferCharacteristics { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coded matrix-coefficient identifier.
|
|||
/// </summary>
|
|||
public byte MatrixCoefficients { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether chroma sample-location information is present.
|
|||
/// </summary>
|
|||
public bool ChromaLocationInfoPresent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the top-field chroma sample-location identifier.
|
|||
/// </summary>
|
|||
public HevcChromaSampleLocation ChromaSampleLocationTopField { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the bottom-field chroma sample-location identifier.
|
|||
/// </summary>
|
|||
public HevcChromaSampleLocation ChromaSampleLocationBottomField { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether a default display window is present.
|
|||
/// </summary>
|
|||
public bool DefaultDisplayWindowPresent { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the default display-window left offset in luma samples.
|
|||
/// </summary>
|
|||
public int DefaultDisplayWindowLeftOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the default display-window right offset in luma samples.
|
|||
/// </summary>
|
|||
public int DefaultDisplayWindowRightOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the default display-window top offset in luma samples.
|
|||
/// </summary>
|
|||
public int DefaultDisplayWindowTopOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the default display-window bottom offset in luma samples.
|
|||
/// </summary>
|
|||
public int DefaultDisplayWindowBottomOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Reads a conformance-window offset and converts it to luma-sample units.
|
|||
/// </summary>
|
|||
/// <param name="reader">The sequence-parameter-set raw byte sequence payload reader.</param>
|
|||
/// <param name="unit">The chroma-dependent luma-sample unit.</param>
|
|||
/// <returns>The scaled offset.</returns>
|
|||
/// <exception cref="InvalidImageContentException">The scaled offset exceeds the supported image dimension range.</exception>
|
|||
private static int ReadScaledOffset(ref HevcBitReader reader, int unit) |
|||
{ |
|||
uint offset = reader.ReadUnsignedExpGolomb(); |
|||
if (offset > int.MaxValue / unit) |
|||
{ |
|||
throw new InvalidImageContentException("The HEVC VUI display-window offset is too large."); |
|||
} |
|||
|
|||
return (int)offset * unit; |
|||
} |
|||
} |
|||
@ -1,337 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Components.Alpha; |
|||
using SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
using SixLabors.ImageSharp.Formats.Heif.Hevc.Color; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Cicp; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif; |
|||
|
|||
/// <summary>
|
|||
/// Decodes a single HEVC-coded HEIF image item.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
|
|||
internal sealed class HevcHeifItemDecoder<TPixel> : IHeifItemDecoder<TPixel>, IHeifAlphaItemDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
private HevcSupplementalEnhancementInformation? supplementalEnhancementInformation; |
|||
|
|||
/// <summary>
|
|||
/// Gets the HEVC-coded image item type.
|
|||
/// </summary>
|
|||
public Heif4CharCode Type => Heif4CharCode.Hvc1; |
|||
|
|||
/// <summary>
|
|||
/// Gets the HEVC compression method.
|
|||
/// </summary>
|
|||
public HeifCompressionMethod CompressionMethod => HeifCompressionMethod.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Decodes the encoded HEVC payload of an image item.
|
|||
/// </summary>
|
|||
/// <param name="options">The general options governing the containing HEIF decode.</param>
|
|||
/// <param name="item">The HEIF item whose encoded payload is being decoded.</param>
|
|||
/// <param name="data">The encoded HEVC payload.</param>
|
|||
/// <param name="colorProfile">The container color description that takes precedence over bitstream color information.</param>
|
|||
/// <param name="cancellationToken">The token used to cancel the payload decode.</param>
|
|||
/// <returns>The decoded image.</returns>
|
|||
public Image<TPixel> DecodeItemData( |
|||
DecoderOptions options, |
|||
HeifItem item, |
|||
Span<byte> data, |
|||
CicpProfile? colorProfile, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
this.supplementalEnhancementInformation = null; |
|||
using HevcPictureDecoder decoder = DecodePicture( |
|||
options, |
|||
item, |
|||
data, |
|||
colorProfile, |
|||
cancellationToken, |
|||
out HevcCodecConfiguration codecConfiguration, |
|||
out HevcSequenceParameterSet sequenceParameterSet, |
|||
out CicpProfile effectiveColorProfile, |
|||
out HevcChromaSampleLocation chromaSampleLocation, |
|||
out HevcSupplementalEnhancementInformation supplementalEnhancementInformation); |
|||
|
|||
if (supplementalEnhancementInformation.NoDisplay) |
|||
{ |
|||
throw new InvalidImageContentException($"HEVC image item {item.Id} is marked as unavailable for display."); |
|||
} |
|||
|
|||
ValidateSupplementalMetadata(item, supplementalEnhancementInformation); |
|||
this.supplementalEnhancementInformation = supplementalEnhancementInformation; |
|||
|
|||
ImageFrame<TPixel>? frame = null; |
|||
Image<TPixel>? image = null; |
|||
try |
|||
{ |
|||
frame = new ImageFrame<TPixel>(options.Configuration, sequenceParameterSet.DisplayWidth, sequenceParameterSet.DisplayHeight); |
|||
HevcYuvConverter.ConvertToRgb( |
|||
options.Configuration, |
|||
decoder.Picture, |
|||
frame, |
|||
effectiveColorProfile, |
|||
chromaSampleLocation, |
|||
sequenceParameterSet.ConformanceWindowLeftOffset, |
|||
sequenceParameterSet.ConformanceWindowTopOffset); |
|||
|
|||
ImageMetadata metadata = new() |
|||
{ |
|||
CicpProfile = effectiveColorProfile.DeepClone() |
|||
}; |
|||
|
|||
HeifMetadata heifMetadata = metadata.GetHeifMetadata(); |
|||
heifMetadata.CompressionMethod = this.CompressionMethod; |
|||
heifMetadata.BitDepth = codecConfiguration.BitDepth; |
|||
heifMetadata.IsMonochrome = codecConfiguration.IsMonochrome; |
|||
heifMetadata.ContentLightLevel = supplementalEnhancementInformation.ContentLightLevel; |
|||
heifMetadata.MasteringDisplayColorVolume = supplementalEnhancementInformation.MasteringDisplayColorVolume; |
|||
heifMetadata.ContentColorVolume = supplementalEnhancementInformation.ContentColorVolume; |
|||
heifMetadata.AmbientViewingEnvironment = supplementalEnhancementInformation.AmbientViewingEnvironment; |
|||
|
|||
image = new Image<TPixel>(options.Configuration, metadata, [frame]); |
|||
frame = null; |
|||
return image; |
|||
} |
|||
catch |
|||
{ |
|||
// Before the image constructor succeeds the frame remains locally owned. Afterwards the image owns it and
|
|||
// every processor-created replacement buffer, so unwind exactly one of those two ownership states.
|
|||
image?.Dispose(); |
|||
frame?.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the active HEVC display-orientation message to the complete presented image.
|
|||
/// </summary>
|
|||
/// <param name="image">The decoded image after item scaling and auxiliary-alpha composition.</param>
|
|||
public void ApplySupplementalPresentation(Image<TPixel> image) |
|||
{ |
|||
HevcSupplementalEnhancementInformation supplementalEnhancementInformation |
|||
= this.supplementalEnhancementInformation!; |
|||
|
|||
if (!supplementalEnhancementInformation.HasDisplayOrientation) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
image.Mutate(context => |
|||
{ |
|||
// H.265 applies both flips to the cropped decoded picture before its anticlockwise rotation.
|
|||
// ImageSharp's positive rotation is clockwise, so quarter turns use the exact optimized modes and
|
|||
// all other coded angles use the equivalent positive clockwise angle.
|
|||
if (supplementalEnhancementInformation.HorizontalFlip) |
|||
{ |
|||
context.Flip(FlipMode.Horizontal); |
|||
} |
|||
|
|||
if (supplementalEnhancementInformation.VerticalFlip) |
|||
{ |
|||
context.Flip(FlipMode.Vertical); |
|||
} |
|||
|
|||
ushort rotation = supplementalEnhancementInformation.AnticlockwiseRotation; |
|||
switch (rotation) |
|||
{ |
|||
case 0: |
|||
break; |
|||
case 16384: |
|||
context.Rotate(RotateMode.Rotate270); |
|||
break; |
|||
case 32768: |
|||
context.Rotate(RotateMode.Rotate180); |
|||
break; |
|||
case 49152: |
|||
context.Rotate(RotateMode.Rotate90); |
|||
break; |
|||
default: |
|||
context.Rotate(360F - ((360F * rotation) / 65536F)); |
|||
break; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void DecodeAlphaItemData( |
|||
DecoderOptions options, |
|||
HeifItem item, |
|||
Span<byte> data, |
|||
ImageFrame<TPixel> destination, |
|||
Size outputSize, |
|||
Rectangle destinationRectangle, |
|||
bool premultiplied, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
using HevcPictureDecoder decoder = DecodePicture( |
|||
options, |
|||
item, |
|||
data, |
|||
item.CicpProfile, |
|||
cancellationToken, |
|||
out _, |
|||
out HevcSequenceParameterSet sequenceParameterSet, |
|||
out CicpProfile effectiveColorProfile, |
|||
out HevcChromaSampleLocation chromaSampleLocation, |
|||
out _); |
|||
|
|||
Rectangle sourceRectangle = new( |
|||
sequenceParameterSet.ConformanceWindowLeftOffset, |
|||
sequenceParameterSet.ConformanceWindowTopOffset, |
|||
sequenceParameterSet.DisplayWidth, |
|||
sequenceParameterSet.DisplayHeight); |
|||
|
|||
if (decoder.Picture.ChromaFormat != 0) |
|||
{ |
|||
throw new InvalidImageContentException($"HEVC alpha image item {item.Id} is not monochrome."); |
|||
} |
|||
|
|||
HevcYuvConverter.ComposeAlpha( |
|||
options.Configuration, |
|||
decoder.Picture, |
|||
destination, |
|||
effectiveColorProfile, |
|||
chromaSampleLocation, |
|||
sourceRectangle, |
|||
outputSize, |
|||
destinationRectangle, |
|||
premultiplied); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Validates and reconstructs one HEVC image item while retaining the native picture for its caller.
|
|||
/// </summary>
|
|||
/// <param name="options">The general options governing the containing HEIF decode.</param>
|
|||
/// <param name="item">The HEVC image item being decoded.</param>
|
|||
/// <param name="data">The encoded HEVC payload.</param>
|
|||
/// <param name="colorProfile">The container color description that takes precedence over bitstream color information.</param>
|
|||
/// <param name="cancellationToken">The token used to cancel the payload decode.</param>
|
|||
/// <param name="codecConfiguration">Receives the validated HEVC codec configuration.</param>
|
|||
/// <param name="sequenceParameterSet">Receives the sequence parameters describing the visible picture.</param>
|
|||
/// <param name="effectiveColorProfile">Receives the effective CICP description used for presentation.</param>
|
|||
/// <param name="chromaSampleLocation">Receives the progressive-frame chroma sample location.</param>
|
|||
/// <param name="supplementalEnhancementInformation">Receives the bounded presentation and metadata SEI state.</param>
|
|||
/// <returns>The decoder owning the reconstructed native picture. Ownership transfers to the caller.</returns>
|
|||
private static HevcPictureDecoder DecodePicture( |
|||
DecoderOptions options, |
|||
HeifItem item, |
|||
ReadOnlySpan<byte> data, |
|||
CicpProfile? colorProfile, |
|||
CancellationToken cancellationToken, |
|||
out HevcCodecConfiguration codecConfiguration, |
|||
out HevcSequenceParameterSet sequenceParameterSet, |
|||
out CicpProfile effectiveColorProfile, |
|||
out HevcChromaSampleLocation chromaSampleLocation, |
|||
out HevcSupplementalEnhancementInformation supplementalEnhancementInformation) |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
codecConfiguration = item.HevcCodecConfiguration |
|||
?? throw new InvalidImageContentException($"HEVC image item {item.Id} has no codec configuration property."); |
|||
|
|||
if (item.ChannelBitDepths is not null) |
|||
{ |
|||
codecConfiguration.ValidateChannelBitDepths(item.ChannelBitDepths); |
|||
} |
|||
|
|||
HevcImageItemBitstream bitstream = new(data, codecConfiguration); |
|||
supplementalEnhancementInformation = bitstream.SupplementalEnhancementInformation; |
|||
HevcPictureParameterSet pictureParameterSet = bitstream.SliceSegments[0].PictureParameterSet; |
|||
sequenceParameterSet = pictureParameterSet.SequenceParameterSet; |
|||
HevcVideoUsabilityInformation? vui = sequenceParameterSet.VideoUsabilityInformation; |
|||
byte transferCharacteristics = vui?.ColorDescriptionPresent == true |
|||
? vui.TransferCharacteristics |
|||
: (byte)CicpTransferCharacteristics.Unspecified; |
|||
|
|||
byte? preferredTransferCharacteristics = supplementalEnhancementInformation.PreferredTransferCharacteristics; |
|||
if (colorProfile is null && preferredTransferCharacteristics is not null) |
|||
{ |
|||
transferCharacteristics = preferredTransferCharacteristics.Value; |
|||
} |
|||
|
|||
// ISO BMFF color information takes precedence when both the container and HEVC VUI describe the image.
|
|||
// Otherwise, retain the VUI values and the SEI-preferred transfer function used by conversion so bitstream-only
|
|||
// color information reaches metadata.
|
|||
effectiveColorProfile = colorProfile is not null |
|||
? new CicpProfile( |
|||
(byte)colorProfile.ColorPrimaries, |
|||
(byte)colorProfile.TransferCharacteristics, |
|||
(byte)colorProfile.MatrixCoefficients, |
|||
colorProfile.FullRange) |
|||
: new CicpProfile( |
|||
vui?.ColorDescriptionPresent == true ? vui.ColorPrimaries : (byte)CicpColorPrimaries.Unspecified, |
|||
transferCharacteristics, |
|||
vui?.ColorDescriptionPresent == true ? vui.MatrixCoefficients : (byte)CicpMatrixCoefficients.Unspecified, |
|||
vui?.VideoSignalTypePresent == true && vui.FullRange); |
|||
|
|||
chromaSampleLocation = vui?.ChromaLocationInfoPresent == true |
|||
? vui.ChromaSampleLocationTopField |
|||
: HevcChromaSampleLocation.Left; |
|||
|
|||
HevcPictureDecoder decoder = new(options.Configuration, pictureParameterSet); |
|||
try |
|||
{ |
|||
decoder.Decode(bitstream); |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
return decoder; |
|||
} |
|||
catch |
|||
{ |
|||
decoder.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Validates equivalent codec and item-property HDR metadata before either representation is exposed.
|
|||
/// </summary>
|
|||
private static void ValidateSupplementalMetadata( |
|||
HeifItem item, |
|||
HevcSupplementalEnhancementInformation supplementalEnhancementInformation) |
|||
{ |
|||
HeifContentLightLevel? supplementalContentLightLevel = supplementalEnhancementInformation.ContentLightLevel; |
|||
HeifContentLightLevel? itemContentLightLevel = item.ContentLightLevel; |
|||
if (supplementalContentLightLevel is not null |
|||
&& itemContentLightLevel is not null |
|||
&& (supplementalContentLightLevel.Value.MaximumContentLightLevel != itemContentLightLevel.Value.MaximumContentLightLevel |
|||
|| supplementalContentLightLevel.Value.MaximumPictureAverageLightLevel |
|||
!= itemContentLightLevel.Value.MaximumPictureAverageLightLevel)) |
|||
{ |
|||
throw new InvalidImageContentException($"HEVC image item {item.Id} has conflicting content light-level metadata."); |
|||
} |
|||
|
|||
HeifMasteringDisplayColorVolume? supplementalMasteringDisplayColorVolume |
|||
= supplementalEnhancementInformation.MasteringDisplayColorVolume; |
|||
|
|||
if (supplementalMasteringDisplayColorVolume is not null |
|||
&& item.MasteringDisplayColorVolume is not null |
|||
&& supplementalMasteringDisplayColorVolume.Value != item.MasteringDisplayColorVolume.Value) |
|||
{ |
|||
throw new InvalidImageContentException($"HEVC image item {item.Id} has conflicting mastering-display metadata."); |
|||
} |
|||
|
|||
HeifContentColorVolume? supplementalContentColorVolume = supplementalEnhancementInformation.ContentColorVolume; |
|||
if (supplementalContentColorVolume is not null |
|||
&& item.ContentColorVolume is not null |
|||
&& supplementalContentColorVolume.Value != item.ContentColorVolume.Value) |
|||
{ |
|||
throw new InvalidImageContentException($"HEVC image item {item.Id} has conflicting content color-volume metadata."); |
|||
} |
|||
|
|||
HeifAmbientViewingEnvironment? supplementalAmbientViewingEnvironment |
|||
= supplementalEnhancementInformation.AmbientViewingEnvironment; |
|||
|
|||
if (supplementalAmbientViewingEnvironment is not null |
|||
&& item.AmbientViewingEnvironment is not null |
|||
&& supplementalAmbientViewingEnvironment.Value != item.AmbientViewingEnvironment.Value) |
|||
{ |
|||
throw new InvalidImageContentException($"HEVC image item {item.Id} has conflicting ambient-viewing metadata."); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue