Browse Source

Match HEVC full-range color presentation

pull/2633/head
James Jackson-South 1 week ago
parent
commit
24ffa64096
  1. 262
      src/ImageSharp/Formats/Heif/Hevc/Color/HevcYuv420ToRgb8Converter.Parameters.cs
  2. 293
      src/ImageSharp/Formats/Heif/Hevc/Color/HevcYuv420ToRgb8Converter.Simd.cs
  3. 92
      src/ImageSharp/Formats/Heif/Hevc/Color/HevcYuv420ToRgb8Converter.cs
  4. 32
      src/ImageSharp/Formats/Heif/Hevc/Color/HevcYuvConverter.cs
  5. 1
      src/ImageSharp/Formats/Heif/HevcHeifItemDecoder.cs
  6. 1
      tests/ImageSharp.Benchmarks/Codecs/Heif/HevcColorConversionBenchmarks.cs
  7. 5
      tests/ImageSharp.Tests/Formats/Heif/HeifDecoderTests.cs
  8. 1
      tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcYuvConverterTests.cs

262
src/ImageSharp/Formats/Heif/Hevc/Color/HevcYuv420ToRgb8Converter.Parameters.cs

@ -0,0 +1,262 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Formats.Heif.Color;
namespace SixLabors.ImageSharp.Formats.Heif.Hevc.Color;
/// <content>
/// Provides fixed-point scalar and SIMD coefficient storage for eight-bit 4:2:0 conversion.
/// </content>
internal static partial class HevcYuv420ToRgb8Converter
{
/// <summary>
/// Stores every scalar and SIMD coefficient representation resolved once for an image.
/// </summary>
private readonly struct ConversionParameters
{
/// <summary>
/// The scalar fixed-point coefficients.
/// </summary>
public readonly FixedPointParameters Scalar;
/// <summary>
/// The four-lane SIMD coefficients.
/// </summary>
public readonly Vector128Parameters FourLane;
/// <summary>
/// The eight-lane SIMD coefficients.
/// </summary>
public readonly Vector256Parameters EightLane;
/// <summary>
/// The sixteen-lane SIMD coefficients.
/// </summary>
public readonly Vector512Parameters SixteenLane;
/// <summary>
/// Initializes a new instance of the <see cref="ConversionParameters"/> struct.
/// </summary>
/// <param name="parameters">The shared floating-point conversion parameters.</param>
public ConversionParameters(in HeifColorConversionParameters parameters)
{
FixedPointParameters scalar = new(in parameters);
this.Scalar = scalar;
this.FourLane = new(in scalar);
this.EightLane = new(in scalar);
this.SixteenLane = new(in scalar);
}
}
/// <summary>
/// Stores the scalar fixed-point coefficients resolved for one image.
/// </summary>
private readonly struct FixedPointParameters
{
/// <summary>
/// Initializes a new instance of the <see cref="FixedPointParameters"/> struct.
/// </summary>
/// <param name="parameters">The shared floating-point conversion parameters.</param>
public FixedPointParameters(in HeifColorConversionParameters parameters)
{
float scale = 1 << CoefficientShift;
// Rounding each image-invariant coefficient once gives the integer kernel eight fractional bits.
// The signed green coefficients retain the exact addition and rounding order used by every SIMD lane.
this.RedCr = (int)MathF.Round(parameters.RedChromaScale * scale, MidpointRounding.AwayFromZero);
this.GreenCb = -(int)MathF.Round(parameters.GreenBlueChromaScale * scale, MidpointRounding.AwayFromZero);
this.GreenCr = -(int)MathF.Round(parameters.GreenRedChromaScale * scale, MidpointRounding.AwayFromZero);
this.BlueCb = (int)MathF.Round(parameters.BlueChromaScale * scale, MidpointRounding.AwayFromZero);
}
/// <summary>
/// Gets the red contribution from centered Cr.
/// </summary>
public int RedCr { get; }
/// <summary>
/// Gets the green contribution from centered Cb.
/// </summary>
public int GreenCb { get; }
/// <summary>
/// Gets the green contribution from centered Cr.
/// </summary>
public int GreenCr { get; }
/// <summary>
/// Gets the blue contribution from centered Cb.
/// </summary>
public int BlueCb { get; }
}
/// <summary>
/// Broadcasts the fixed-point 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 fixed-point coefficients.</param>
public Vector128Parameters(in FixedPointParameters parameters)
{
this.ChromaMidpoint = Vector128.Create(HevcYuv420ToRgb8Converter.ChromaMidpoint);
this.RoundingBias = Vector128.Create(HevcYuv420ToRgb8Converter.RoundingBias);
this.Maximum = Vector128.Create((int)byte.MaxValue);
this.RedCr = Vector128.Create(parameters.RedCr);
this.GreenCb = Vector128.Create(parameters.GreenCb);
this.GreenCr = Vector128.Create(parameters.GreenCr);
this.BlueCb = Vector128.Create(parameters.BlueCb);
}
/// <summary>
/// Gets the neutral chroma code-value lanes.
/// </summary>
public Vector128<int> ChromaMidpoint { get; }
/// <summary>
/// Gets the fixed-point rounding-bias lanes.
/// </summary>
public Vector128<int> RoundingBias { get; }
/// <summary>
/// Gets the maximum eight-bit sample lanes.
/// </summary>
public Vector128<int> Maximum { get; }
/// <summary>
/// Gets the red Cr coefficient lanes.
/// </summary>
public Vector128<int> RedCr { get; }
/// <summary>
/// Gets the green Cb coefficient lanes.
/// </summary>
public Vector128<int> GreenCb { get; }
/// <summary>
/// Gets the green Cr coefficient lanes.
/// </summary>
public Vector128<int> GreenCr { get; }
/// <summary>
/// Gets the blue Cb coefficient lanes.
/// </summary>
public Vector128<int> BlueCb { get; }
}
/// <summary>
/// Broadcasts the fixed-point 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 fixed-point coefficients.</param>
public Vector256Parameters(in FixedPointParameters parameters)
{
this.ChromaMidpoint = Vector256.Create(HevcYuv420ToRgb8Converter.ChromaMidpoint);
this.RoundingBias = Vector256.Create(HevcYuv420ToRgb8Converter.RoundingBias);
this.Maximum = Vector256.Create((int)byte.MaxValue);
this.RedCr = Vector256.Create(parameters.RedCr);
this.GreenCb = Vector256.Create(parameters.GreenCb);
this.GreenCr = Vector256.Create(parameters.GreenCr);
this.BlueCb = Vector256.Create(parameters.BlueCb);
}
/// <summary>
/// Gets the neutral chroma code-value lanes.
/// </summary>
public Vector256<int> ChromaMidpoint { get; }
/// <summary>
/// Gets the fixed-point rounding-bias lanes.
/// </summary>
public Vector256<int> RoundingBias { get; }
/// <summary>
/// Gets the maximum eight-bit sample lanes.
/// </summary>
public Vector256<int> Maximum { get; }
/// <summary>
/// Gets the red Cr coefficient lanes.
/// </summary>
public Vector256<int> RedCr { get; }
/// <summary>
/// Gets the green Cb coefficient lanes.
/// </summary>
public Vector256<int> GreenCb { get; }
/// <summary>
/// Gets the green Cr coefficient lanes.
/// </summary>
public Vector256<int> GreenCr { get; }
/// <summary>
/// Gets the blue Cb coefficient lanes.
/// </summary>
public Vector256<int> BlueCb { get; }
}
/// <summary>
/// Broadcasts the fixed-point 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 fixed-point coefficients.</param>
public Vector512Parameters(in FixedPointParameters parameters)
{
this.ChromaMidpoint = Vector512.Create(HevcYuv420ToRgb8Converter.ChromaMidpoint);
this.RoundingBias = Vector512.Create(HevcYuv420ToRgb8Converter.RoundingBias);
this.Maximum = Vector512.Create((int)byte.MaxValue);
this.RedCr = Vector512.Create(parameters.RedCr);
this.GreenCb = Vector512.Create(parameters.GreenCb);
this.GreenCr = Vector512.Create(parameters.GreenCr);
this.BlueCb = Vector512.Create(parameters.BlueCb);
}
/// <summary>
/// Gets the neutral chroma code-value lanes.
/// </summary>
public Vector512<int> ChromaMidpoint { get; }
/// <summary>
/// Gets the fixed-point rounding-bias lanes.
/// </summary>
public Vector512<int> RoundingBias { get; }
/// <summary>
/// Gets the maximum eight-bit sample lanes.
/// </summary>
public Vector512<int> Maximum { get; }
/// <summary>
/// Gets the red Cr coefficient lanes.
/// </summary>
public Vector512<int> RedCr { get; }
/// <summary>
/// Gets the green Cb coefficient lanes.
/// </summary>
public Vector512<int> GreenCb { get; }
/// <summary>
/// Gets the green Cr coefficient lanes.
/// </summary>
public Vector512<int> GreenCr { get; }
/// <summary>
/// Gets the blue Cb coefficient lanes.
/// </summary>
public Vector512<int> BlueCb { get; }
}
}

293
src/ImageSharp/Formats/Heif/Hevc/Color/HevcYuv420ToRgb8Converter.Simd.cs

@ -0,0 +1,293 @@
// 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;
using static SixLabors.ImageSharp.Formats.Heif.Color.HeifColorConverterBase;
namespace SixLabors.ImageSharp.Formats.Heif.Hevc.Color;
/// <content>
/// Provides the fixed-point scalar and SIMD row kernels for eight-bit 4:2:0 conversion.
/// </content>
internal static partial class HevcYuv420ToRgb8Converter
{
/// <summary>
/// Converts one luma row and its nearest native chroma row to planar eight-bit RGB.
/// </summary>
/// <param name="luma">The full-resolution luma samples.</param>
/// <param name="chromaBlue">The half-width blue-difference samples.</param>
/// <param name="chromaRed">The half-width 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="parameters">The fixed-point matrix coefficients.</param>
private static void ConvertRow(
ReadOnlySpan<ushort> luma,
ReadOnlySpan<ushort> chromaBlue,
ReadOnlySpan<ushort> chromaRed,
Span<byte> red,
Span<byte> green,
Span<byte> blue,
in ConversionParameters parameters)
{
ref ushort lumaBase = ref MemoryMarshal.GetReference(luma);
ref ushort chromaBlueBase = ref MemoryMarshal.GetReference(chromaBlue);
ref ushort chromaRedBase = ref MemoryMarshal.GetReference(chromaRed);
ref byte redBase = ref MemoryMarshal.GetReference(red);
ref byte greenBase = ref MemoryMarshal.GetReference(green);
ref byte blueBase = ref MemoryMarshal.GetReference(blue);
int x = 0;
// The shared offset lets the widest supported register consume the row first. Narrower widths then
// handle the complete remainder, leaving at most three pixels for the scalar fallback.
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 = LoadRepeatedVector512(ref Unsafe.Add(ref chromaBlueBase, x >> 1));
Vector512<int> cr = LoadRepeatedVector512(ref Unsafe.Add(ref chromaRedBase, x >> 1));
Convert(y, cb, cr, in parameters.SixteenLane, out Vector512<int> r, out Vector512<int> g, out Vector512<int> b);
HeifByteSampleStorer.Store(r, ref Unsafe.Add(ref redBase, x));
HeifByteSampleStorer.Store(g, ref Unsafe.Add(ref greenBase, x));
HeifByteSampleStorer.Store(b, 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 = LoadRepeatedVector256(ref Unsafe.Add(ref chromaBlueBase, x >> 1));
Vector256<int> cr = LoadRepeatedVector256(ref Unsafe.Add(ref chromaRedBase, x >> 1));
Convert(y, cb, cr, in parameters.EightLane, out Vector256<int> r, out Vector256<int> g, out Vector256<int> b);
HeifByteSampleStorer.Store(r, ref Unsafe.Add(ref redBase, x));
HeifByteSampleStorer.Store(g, ref Unsafe.Add(ref greenBase, x));
HeifByteSampleStorer.Store(b, 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 = LoadRepeatedVector128(ref Unsafe.Add(ref chromaBlueBase, x >> 1));
Vector128<int> cr = LoadRepeatedVector128(ref Unsafe.Add(ref chromaRedBase, x >> 1));
Convert(y, cb, cr, in parameters.FourLane, out Vector128<int> r, out Vector128<int> g, out Vector128<int> b);
HeifByteSampleStorer.Store(r, ref Unsafe.Add(ref redBase, x));
HeifByteSampleStorer.Store(g, ref Unsafe.Add(ref greenBase, x));
HeifByteSampleStorer.Store(b, ref Unsafe.Add(ref blueBase, x));
}
}
for (; x < luma.Length; x++)
{
Convert(
Unsafe.Add(ref lumaBase, x),
Unsafe.Add(ref chromaBlueBase, x >> 1),
Unsafe.Add(ref chromaRedBase, x >> 1),
in parameters.Scalar,
out Unsafe.Add(ref redBase, x),
out Unsafe.Add(ref greenBase, x),
out Unsafe.Add(ref blueBase, x));
}
}
/// <summary>
/// Loads sixteen luma samples as signed 32-bit SIMD lanes.
/// </summary>
/// <param name="source">The first native luma sample.</param>
/// <returns>The widened luma 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 luma samples as signed 32-bit SIMD lanes.
/// </summary>
/// <param name="source">The first native luma sample.</param>
/// <returns>The widened luma 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 luma samples as signed 32-bit SIMD lanes.
/// </summary>
/// <param name="source">The first native luma sample.</param>
/// <returns>The widened luma 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();
}
/// <summary>
/// Converts one coefficient-based H.273 YCbCr sample to eight-bit 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 fixed-point matrix coefficients.</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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Convert(ushort y, ushort cb, ushort cr, in FixedPointParameters parameters, out byte r, out byte g, out byte b)
{
int centeredBlue = cb - ChromaMidpoint;
int centeredRed = cr - ChromaMidpoint;
int red = y + (((parameters.RedCr * centeredRed) + RoundingBias) >> CoefficientShift);
int green = y + (((parameters.GreenCb * centeredBlue) + (parameters.GreenCr * centeredRed) + RoundingBias) >> CoefficientShift);
int blue = y + (((parameters.BlueCb * centeredBlue) + RoundingBias) >> CoefficientShift);
r = (byte)Numerics.Clamp(red, 0, byte.MaxValue);
g = (byte)Numerics.Clamp(green, 0, byte.MaxValue);
b = (byte)Numerics.Clamp(blue, 0, byte.MaxValue);
}
/// <summary>
/// Converts four coefficient-based H.273 YCbCr samples to eight-bit 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 fixed-point matrix coefficient lanes.</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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Convert(
Vector128<int> y,
Vector128<int> cb,
Vector128<int> cr,
in Vector128Parameters parameters,
out Vector128<int> r,
out Vector128<int> g,
out Vector128<int> b)
{
cb -= parameters.ChromaMidpoint;
cr -= parameters.ChromaMidpoint;
r = Vector128.Clamp(y + (((parameters.RedCr * cr) + parameters.RoundingBias) >> CoefficientShift), Vector128<int>.Zero, parameters.Maximum);
g = Vector128.Clamp(y + (((parameters.GreenCb * cb) + (parameters.GreenCr * cr) + parameters.RoundingBias) >> CoefficientShift), Vector128<int>.Zero, parameters.Maximum);
b = Vector128.Clamp(y + (((parameters.BlueCb * cb) + parameters.RoundingBias) >> CoefficientShift), Vector128<int>.Zero, parameters.Maximum);
}
/// <summary>
/// Converts eight coefficient-based H.273 YCbCr samples to eight-bit 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 fixed-point matrix coefficient lanes.</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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Convert(
Vector256<int> y,
Vector256<int> cb,
Vector256<int> cr,
in Vector256Parameters parameters,
out Vector256<int> r,
out Vector256<int> g,
out Vector256<int> b)
{
cb -= parameters.ChromaMidpoint;
cr -= parameters.ChromaMidpoint;
r = Vector256.Clamp(y + (((parameters.RedCr * cr) + parameters.RoundingBias) >> CoefficientShift), Vector256<int>.Zero, parameters.Maximum);
g = Vector256.Clamp(y + (((parameters.GreenCb * cb) + (parameters.GreenCr * cr) + parameters.RoundingBias) >> CoefficientShift), Vector256<int>.Zero, parameters.Maximum);
b = Vector256.Clamp(y + (((parameters.BlueCb * cb) + parameters.RoundingBias) >> CoefficientShift), Vector256<int>.Zero, parameters.Maximum);
}
/// <summary>
/// Converts sixteen coefficient-based H.273 YCbCr samples to eight-bit 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 fixed-point matrix coefficient lanes.</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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Convert(
Vector512<int> y,
Vector512<int> cb,
Vector512<int> cr,
in Vector512Parameters parameters,
out Vector512<int> r,
out Vector512<int> g,
out Vector512<int> b)
{
cb -= parameters.ChromaMidpoint;
cr -= parameters.ChromaMidpoint;
r = Vector512.Clamp(y + (((parameters.RedCr * cr) + parameters.RoundingBias) >> CoefficientShift), Vector512<int>.Zero, parameters.Maximum);
g = Vector512.Clamp(y + (((parameters.GreenCb * cb) + (parameters.GreenCr * cr) + parameters.RoundingBias) >> CoefficientShift), Vector512<int>.Zero, parameters.Maximum);
b = Vector512.Clamp(y + (((parameters.BlueCb * cb) + parameters.RoundingBias) >> CoefficientShift), Vector512<int>.Zero, parameters.Maximum);
}
}

92
src/ImageSharp/Formats/Heif/Hevc/Color/HevcYuv420ToRgb8Converter.cs

@ -0,0 +1,92 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Heif.Color;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Heif.Hevc.Color;
/// <summary>
/// Converts full-range eight-bit HEVC 4:2:0 planes with an unspecified matrix to packed RGB pixels.
/// </summary>
internal static partial class HevcYuv420ToRgb8Converter
{
/// <summary>
/// The fixed-point precision used for H.273 matrix coefficients.
/// </summary>
private const int CoefficientShift = 8;
/// <summary>
/// The half-unit bias used before fixed-point coefficient results are shifted to integer samples.
/// </summary>
private const int RoundingBias = 1 << (CoefficientShift - 1);
/// <summary>
/// The neutral code value for full-range eight-bit chroma.
/// </summary>
private const int ChromaMidpoint = 128;
/// <summary>
/// Determines whether the specialized integer conversion supports the supplied picture and color description.
/// </summary>
/// <param name="picture">The reconstructed HEVC picture.</param>
/// <param name="colorProfile">The effective H.273 color description.</param>
/// <param name="mode">The resolved H.273 conversion operation.</param>
/// <returns><see langword="true"/> when the picture can use this converter; otherwise, <see langword="false"/>.</returns>
public static bool IsSupported(HevcPictureBuffer picture, CicpProfile colorProfile, HeifColorConversionMode mode)
=> picture.ChromaFormat == 1
&& !picture.SeparateColorPlane
&& picture.BitDepthLuma == 8
&& picture.BitDepthChroma == 8
&& colorProfile.FullRange
&& colorProfile.MatrixCoefficients == CicpMatrixCoefficients.Unspecified
&& mode == HeifColorConversionMode.Coefficients;
/// <summary>
/// Converts a supported HEVC picture to packed pixels using integer SIMD with a scalar tail.
/// </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="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>(
Configuration configuration,
HevcPictureBuffer picture,
ImageFrame<TPixel> image,
in HeifColorConversionParameters parameters,
int sourceX,
int sourceY)
where TPixel : unmanaged, IPixel<TPixel>
{
ConversionParameters conversionParameters = new(in parameters);
using IMemoryOwner<byte> componentOwner = configuration.MemoryAllocator.Allocate<byte>(image.Width * 3);
Span<byte> components = componentOwner.GetSpan();
Span<byte> red = components[..image.Width];
Span<byte> green = components.Slice(image.Width, image.Width);
Span<byte> blue = components.Slice(image.Width * 2, image.Width);
for (int y = 0; y < image.Height; y++)
{
int lumaY = sourceY + y;
// HEVC expresses 4:2:0 conformance-window offsets in complete chroma sample units, so both source
// offsets are even here. The unspecified-matrix presentation replicates each native chroma sample
// across its 2x2 luma cell before applying the default BT.601 coefficients.
ReadOnlySpan<ushort> luma = picture.GetRowSpan(HevcPlane.Y, lumaY).Slice(sourceX, image.Width);
ReadOnlySpan<ushort> chromaBlue = picture.GetRowSpan(HevcPlane.Cb, lumaY >> 1).Slice(sourceX >> 1);
ReadOnlySpan<ushort> chromaRed = picture.GetRowSpan(HevcPlane.Cr, lumaY >> 1).Slice(sourceX >> 1);
ConvertRow(luma, chromaBlue, chromaRed, red, green, blue, in conversionParameters);
Span<TPixel> destination = image.PixelBuffer.DangerousGetRowSpan(y);
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(red, green, blue, destination);
}
}
}

32
src/ImageSharp/Formats/Heif/Hevc/HevcYuvConverter.cs → src/ImageSharp/Formats/Heif/Hevc/Color/HevcYuvConverter.cs

@ -11,12 +11,12 @@ using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.PixelFormats;
using static SixLabors.ImageSharp.Formats.Heif.Color.HeifColorConverterBase;
namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
namespace SixLabors.ImageSharp.Formats.Heif.Hevc.Color;
/// <summary>
/// Converts between reconstructed HEVC component planes and packed ImageSharp pixels.
/// </summary>
internal static class HevcYuvConverter
internal static partial class HevcYuvConverter
{
/// <summary>
/// The largest value represented by an eight-bit packed RGB component.
@ -60,6 +60,15 @@ internal static class HevcYuvConverter
where TPixel : unmanaged, IPixel<TPixel>
{
HeifColorConversionParameters parameters = GetConversionParameters(picture, colorProfile, out HeifColorConversionMode mode);
// H.273 resolves an unspecified matrix to BT.601 coefficients. The common full-range eight-bit 4:2:0
// presentation can therefore remain in the integer sample domain and avoid float staging and rounding.
if (HevcYuv420ToRgb8Converter.IsSupported(picture, colorProfile, mode))
{
HevcYuv420ToRgb8Converter.Convert(configuration, picture, image, in parameters, sourceX, sourceY);
return;
}
HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, picture.ChromaFormat == 0);
YuvToRgbRowConverter<TPixel> converter = new(configuration, picture, image, colorConverter, chromaSampleLocation, sourceX, sourceY);
using IMemoryOwner<float> scratchOwner = configuration.MemoryAllocator.Allocate<float>(converter.BufferLength);
@ -67,11 +76,9 @@ internal static class HevcYuvConverter
if (converter.UsesBytePacking)
{
using IMemoryOwner<TPixel> proxyOwner = configuration.MemoryAllocator.Allocate<TPixel>(image.Width + 3);
Span<TPixel> proxy = proxyOwner.GetSpan()[..(image.Width + 3)];
for (int y = 0; y < image.Height; y++)
{
converter.Convert(y, scratch, proxy);
converter.Convert(y, scratch);
}
return;
@ -79,7 +86,7 @@ internal static class HevcYuvConverter
for (int y = 0; y < image.Height; y++)
{
converter.Convert(y, scratch, Span<TPixel>.Empty);
converter.Convert(y, scratch);
}
}
@ -317,8 +324,7 @@ internal static class HevcYuvConverter
/// </summary>
/// <param name="y">The zero-based luma row.</param>
/// <param name="scratch">The reusable pooled row buffer.</param>
/// <param name="proxy">The padded byte-packing destination when the image row has insufficient padding.</param>
public void Convert(int y, Span<float> scratch, Span<TPixel> proxy)
public void Convert(int y, Span<float> scratch)
{
int width = this.image.Width;
Span<float> red = scratch[..width];
@ -391,15 +397,7 @@ internal static class HevcYuvConverter
SimdUtils.NormalizedFloatToByteSaturate(green, greenBytes);
SimdUtils.NormalizedFloatToByteSaturate(blue, blueBytes);
if (this.image.PixelBuffer.DangerousTryGetPaddedRowSpan(y, 3, out Span<TPixel> paddedDestination))
{
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(redBytes, greenBytes, blueBytes, paddedDestination);
}
else
{
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(redBytes, greenBytes, blueBytes, proxy);
proxy[..width].CopyTo(destination);
}
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(redBytes, greenBytes, blueBytes, destination);
return;
}

1
src/ImageSharp/Formats/Heif/HevcHeifItemDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
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;

1
tests/ImageSharp.Benchmarks/Codecs/Heif/HevcColorConversionBenchmarks.cs

@ -3,6 +3,7 @@
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Heif.Hevc;
using SixLabors.ImageSharp.Formats.Heif.Hevc.Color;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.PixelFormats;

5
tests/ImageSharp.Tests/Formats/Heif/HeifDecoderTests.cs

@ -84,10 +84,7 @@ public class HeifDecoderTests
HeifMetadata metadata = image.Metadata.GetHeifMetadata();
image.DebugSave(provider);
// The extracted native YUV tiles have byte-exact HM coverage. Pinned libheif 1.23.1 selects its cheaper fused
// nearest-neighbor RGB path by default, so the full-image oracle permits the bounded difference from our
// bilinear reconstruction while still covering grids, alpha composition, color conversion, and presentation.
image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.6F), provider);
image.CompareToReferenceOutput(ImageComparer.Exact, provider);
Assert.Equal(new Size(width, height), image.Size);
Assert.Equal(HeifCompressionMethod.Hevc, metadata.CompressionMethod);

1
tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcYuvConverterTests.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Hevc;
using SixLabors.ImageSharp.Formats.Heif.Hevc.Color;
using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
using SixLabors.ImageSharp.PixelFormats;

Loading…
Cancel
Save