mirror of https://github.com/SixLabors/ImageSharp
16 changed files with 1224 additions and 1282 deletions
@ -1,455 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.Advanced; |
|
||||
using SixLabors.ImageSharp.Common.Helpers; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Color; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using static SixLabors.ImageSharp.Formats.Heif.Color.HeifColorConverterBase; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|
||||
|
|
||||
/// <content>
|
|
||||
/// Provides the pooled row converters used by AV1 color conversion.
|
|
||||
/// </content>
|
|
||||
internal static partial class Av1YuvConverter |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Converts one reconstructed AV1 row using frame-scoped pooled component, chroma, and packed-pixel storage.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
|
|
||||
/// <typeparam name="TSample">The reconstructed sample type.</typeparam>
|
|
||||
/// <typeparam name="TLoader">The SIMD widening operations for the sample type.</typeparam>
|
|
||||
private readonly struct YuvToRgbRowConverter<TPixel, TSample, TLoader> |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
where TSample : unmanaged |
|
||||
where TLoader : struct, IHeifSampleLoader<TSample> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The configuration used by bulk pixel conversion.
|
|
||||
/// </summary>
|
|
||||
private readonly Configuration configuration; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The reconstructed AV1 frame containing the source planes.
|
|
||||
/// </summary>
|
|
||||
private readonly Av1FrameBuffer<byte> frameBuffer; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The destination image frame.
|
|
||||
/// </summary>
|
|
||||
private readonly ImageFrame<TPixel> image; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The full-resolution luma plane.
|
|
||||
/// </summary>
|
|
||||
private readonly Buffer2DRegion<byte> yPlane; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The blue-difference plane when the frame contains chroma.
|
|
||||
/// </summary>
|
|
||||
private readonly Buffer2DRegion<byte> uPlane; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The red-difference plane when the frame contains chroma.
|
|
||||
/// </summary>
|
|
||||
private readonly Buffer2DRegion<byte> vPlane; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The frame-scoped color-model converter.
|
|
||||
/// </summary>
|
|
||||
private readonly HeifColorConverterBase colorConverter; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The signaled chroma sample position used for reconstruction.
|
|
||||
/// </summary>
|
|
||||
private readonly ObuChromoSamplePosition chromaSamplePosition; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Whether the source contains only a luma plane.
|
|
||||
/// </summary>
|
|
||||
private readonly bool isMonochrome; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The horizontal chroma subsampling shift.
|
|
||||
/// </summary>
|
|
||||
private readonly int subX; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The vertical chroma subsampling shift.
|
|
||||
/// </summary>
|
|
||||
private readonly int subY; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="YuvToRgbRowConverter{TPixel, TSample, TLoader}"/> struct.
|
|
||||
/// </summary>
|
|
||||
/// <param name="configuration">The configuration used for packed-pixel conversion.</param>
|
|
||||
/// <param name="frameBuffer">The reconstructed AV1 frame.</param>
|
|
||||
/// <param name="image">The destination image frame.</param>
|
|
||||
/// <param name="colorConverter">The selected H.273 color converter.</param>
|
|
||||
public YuvToRgbRowConverter( |
|
||||
Configuration configuration, |
|
||||
Av1FrameBuffer<byte> frameBuffer, |
|
||||
ImageFrame<TPixel> image, |
|
||||
HeifColorConverterBase colorConverter) |
|
||||
{ |
|
||||
this.configuration = configuration; |
|
||||
this.frameBuffer = frameBuffer; |
|
||||
this.image = image; |
|
||||
this.colorConverter = colorConverter; |
|
||||
this.isMonochrome = frameBuffer.ColorFormat == Av1ColorFormat.Yuv400; |
|
||||
this.subX = frameBuffer.ColorConfig.SubSamplingX ? 1 : 0; |
|
||||
this.subY = frameBuffer.ColorConfig.SubSamplingY ? 1 : 0; |
|
||||
this.chromaSamplePosition = frameBuffer.ColorConfig.ChromaSamplePosition; |
|
||||
this.yPlane = frameBuffer.DeriveBlockPointer(Av1Plane.Y, 0, 0); |
|
||||
this.uPlane = this.isMonochrome ? default : frameBuffer.DeriveBlockPointer(Av1Plane.U, this.subX, this.subY); |
|
||||
this.vPlane = this.isMonochrome ? default : frameBuffer.DeriveBlockPointer(Av1Plane.V, this.subX, this.subY); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the number of float elements required by the reusable conversion buffer.
|
|
||||
/// </summary>
|
|
||||
public int BufferLength |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
// Three float component rows are converted in place. Color input adds two reusable chroma scratch
|
|
||||
// rows, and the final one or two float-sized slots per pixel back RGB byte planes or an Rgba64 row.
|
|
||||
int rowCount = this.isMonochrome ? 3 : 5; |
|
||||
int packedRowCount = typeof(TSample) == typeof(byte) ? 1 : 2; |
|
||||
return this.image.Width * (rowCount + packedRowCount); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts one reconstructed AV1 row to packed pixels.
|
|
||||
/// </summary>
|
|
||||
/// <param name="y">The row index.</param>
|
|
||||
/// <param name="span">The reusable conversion buffer.</param>
|
|
||||
public void Convert(int y, Span<float> span) |
|
||||
{ |
|
||||
int width = this.image.Width; |
|
||||
Span<float> red = span[..width]; |
|
||||
Span<float> green = span.Slice(width, width); |
|
||||
Span<float> blue = span.Slice(width * 2, width); |
|
||||
|
|
||||
ReadOnlySpan<TSample> ySource; |
|
||||
if (typeof(TSample) == typeof(byte)) |
|
||||
{ |
|
||||
ySource = MemoryMarshal.Cast<byte, TSample>(this.yPlane.DangerousGetRowSpan(y)); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
ySource = MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.Y, y, 0, 0)); |
|
||||
} |
|
||||
|
|
||||
ConvertSamplesToFloat<TSample, TLoader>(ySource, red); |
|
||||
|
|
||||
int packedOffset = width * 3; |
|
||||
if (!this.isMonochrome) |
|
||||
{ |
|
||||
GetChromaCoordinates( |
|
||||
y, |
|
||||
this.subY, |
|
||||
this.subY != 0 && this.chromaSamplePosition != ObuChromoSamplePosition.Colocated, |
|
||||
this.uPlane.Height - 1, |
|
||||
out int y0, |
|
||||
out int y1, |
|
||||
out int y1Weight); |
|
||||
|
|
||||
ReadOnlySpan<TSample> uRow0; |
|
||||
ReadOnlySpan<TSample> uRow1; |
|
||||
ReadOnlySpan<TSample> vRow0; |
|
||||
ReadOnlySpan<TSample> vRow1; |
|
||||
if (typeof(TSample) == typeof(byte)) |
|
||||
{ |
|
||||
uRow0 = MemoryMarshal.Cast<byte, TSample>(this.uPlane.DangerousGetRowSpan(y0)); |
|
||||
uRow1 = MemoryMarshal.Cast<byte, TSample>(this.uPlane.DangerousGetRowSpan(y1)); |
|
||||
vRow0 = MemoryMarshal.Cast<byte, TSample>(this.vPlane.DangerousGetRowSpan(y0)); |
|
||||
vRow1 = MemoryMarshal.Cast<byte, TSample>(this.vPlane.DangerousGetRowSpan(y1)); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
uRow0 = MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.U, y0, this.subX, this.subY)); |
|
||||
uRow1 = MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.U, y1, this.subX, this.subY)); |
|
||||
vRow0 = MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.V, y0, this.subX, this.subY)); |
|
||||
vRow1 = MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.V, y1, this.subX, this.subY)); |
|
||||
} |
|
||||
|
|
||||
Span<float> scratch0 = span.Slice(width * 3, width); |
|
||||
Span<float> scratch1 = span.Slice(width * 4, width); |
|
||||
bool isCenteredX = this.subX != 0 && (this.subY == 0 || this.chromaSamplePosition == ObuChromoSamplePosition.Unknown); |
|
||||
ReconstructChromaRow<TSample, TLoader>(uRow0, uRow1, y1Weight, this.subX, isCenteredX, green, scratch0, scratch1); |
|
||||
ReconstructChromaRow<TSample, TLoader>(vRow0, vRow1, y1Weight, this.subX, isCenteredX, blue, scratch0, scratch1); |
|
||||
packedOffset = width * 5; |
|
||||
} |
|
||||
|
|
||||
this.colorConverter.ConvertToRgbInPlace(red, green, blue); |
|
||||
Span<float> packedStorage = span[packedOffset..]; |
|
||||
Span<TPixel> destination = this.image.PixelBuffer.DangerousGetRowSpan(y); |
|
||||
if (typeof(TSample) == typeof(byte)) |
|
||||
{ |
|
||||
// Match JPEG's byte-plane packing contract so optimized pixel types use the existing RGB packer.
|
|
||||
// The final float row provides enough byte storage for three tightly packed component planes.
|
|
||||
Span<byte> byteStorage = MemoryMarshal.AsBytes(packedStorage)[..(width * 3)]; |
|
||||
Span<byte> redBytes = byteStorage[..width]; |
|
||||
Span<byte> greenBytes = byteStorage.Slice(width, width); |
|
||||
Span<byte> blueBytes = byteStorage.Slice(width * 2, width); |
|
||||
SimdUtils.NormalizedFloatToByteSaturate(red, redBytes); |
|
||||
SimdUtils.NormalizedFloatToByteSaturate(green, greenBytes); |
|
||||
SimdUtils.NormalizedFloatToByteSaturate(blue, blueBytes); |
|
||||
|
|
||||
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(redBytes, greenBytes, blueBytes, destination); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
Span<Rgba64> packed = MemoryMarshal.Cast<float, Rgba64>(packedStorage)[..width]; |
|
||||
PackRgba64(red, green, blue, packed); |
|
||||
PixelOperations<TPixel>.Instance.FromRgba64(this.configuration, packed, destination); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts one source row, or one vertically subsampled row pair, using reusable pooled component storage.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The source pixel type.</typeparam>
|
|
||||
/// <typeparam name="TSample">The encoded sample type.</typeparam>
|
|
||||
/// <typeparam name="TStorer">The SIMD narrowing and storage operations for the sample type.</typeparam>
|
|
||||
private readonly struct RgbToYuvRowConverter<TPixel, TSample, TStorer> |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
where TSample : unmanaged |
|
||||
where TStorer : struct, IHeifSampleStorer<TSample> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The configuration used by bulk pixel conversion.
|
|
||||
/// </summary>
|
|
||||
private readonly Configuration configuration; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The destination AV1 frame.
|
|
||||
/// </summary>
|
|
||||
private readonly Av1FrameBuffer<byte> frameBuffer; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The source image frame.
|
|
||||
/// </summary>
|
|
||||
private readonly ImageFrame<TPixel> image; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The full-resolution luma plane.
|
|
||||
/// </summary>
|
|
||||
private readonly Buffer2DRegion<byte> yPlane; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The blue-difference plane when the frame contains chroma.
|
|
||||
/// </summary>
|
|
||||
private readonly Buffer2DRegion<byte> uPlane; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The red-difference plane when the frame contains chroma.
|
|
||||
/// </summary>
|
|
||||
private readonly Buffer2DRegion<byte> vPlane; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The frame-scoped color-model converter.
|
|
||||
/// </summary>
|
|
||||
private readonly HeifColorConverterBase colorConverter; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The largest value represented by the encoded AV1 bit depth.
|
|
||||
/// </summary>
|
|
||||
private readonly float sampleMaximum; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Whether the destination contains only a luma plane.
|
|
||||
/// </summary>
|
|
||||
private readonly bool isMonochrome; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The horizontal chroma subsampling shift.
|
|
||||
/// </summary>
|
|
||||
private readonly int subX; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The vertical chroma subsampling shift.
|
|
||||
/// </summary>
|
|
||||
private readonly int rowShift; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="RgbToYuvRowConverter{TPixel, TSample, TStorer}"/> struct.
|
|
||||
/// </summary>
|
|
||||
/// <param name="configuration">The configuration used for packed-pixel conversion.</param>
|
|
||||
/// <param name="frameBuffer">The destination AV1 frame.</param>
|
|
||||
/// <param name="image">The source image frame.</param>
|
|
||||
/// <param name="colorConverter">The selected H.273 color converter.</param>
|
|
||||
/// <param name="sampleMaximum">The largest encoded sample value.</param>
|
|
||||
public RgbToYuvRowConverter( |
|
||||
Configuration configuration, |
|
||||
Av1FrameBuffer<byte> frameBuffer, |
|
||||
ImageFrame<TPixel> image, |
|
||||
HeifColorConverterBase colorConverter, |
|
||||
float sampleMaximum) |
|
||||
{ |
|
||||
this.configuration = configuration; |
|
||||
this.frameBuffer = frameBuffer; |
|
||||
this.image = image; |
|
||||
this.colorConverter = colorConverter; |
|
||||
this.sampleMaximum = sampleMaximum; |
|
||||
this.isMonochrome = frameBuffer.ColorFormat == Av1ColorFormat.Yuv400; |
|
||||
this.subX = frameBuffer.ColorConfig.SubSamplingX ? 1 : 0; |
|
||||
this.rowShift = !this.isMonochrome && frameBuffer.ColorConfig.SubSamplingY ? 1 : 0; |
|
||||
this.yPlane = frameBuffer.DeriveBlockPointer(Av1Plane.Y, 0, 0); |
|
||||
this.uPlane = this.isMonochrome ? default : frameBuffer.DeriveBlockPointer(Av1Plane.U, this.subX, this.rowShift); |
|
||||
this.vPlane = this.isMonochrome ? default : frameBuffer.DeriveBlockPointer(Av1Plane.V, this.subX, this.rowShift); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the number of float elements required by the reusable conversion buffer.
|
|
||||
/// </summary>
|
|
||||
public int ComponentBufferLength |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
// Three planar rows hold the converted YUV values, and 4:2:0 keeps a second set until its
|
|
||||
// chroma has been averaged with the first.
|
|
||||
int componentRowCount = this.rowShift == 0 ? 3 : 6; |
|
||||
return this.image.Width * componentRowCount; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts one source row, or one vertically subsampled row pair, to AV1 planes.
|
|
||||
/// </summary>
|
|
||||
/// <param name="y">The conversion iteration index.</param>
|
|
||||
/// <param name="packed">The reusable high-bit-depth RGB staging row.</param>
|
|
||||
/// <param name="components">The reusable planar component buffer.</param>
|
|
||||
public void Convert(int y, Span<Rgb48> packed, Span<float> components) |
|
||||
{ |
|
||||
int width = this.image.Width; |
|
||||
int sourceY = y << this.rowShift; |
|
||||
Span<float> yRow0 = components[..width]; |
|
||||
Span<float> cbRow0 = components.Slice(width, width); |
|
||||
Span<float> crRow0 = components.Slice(width * 2, width); |
|
||||
this.ConvertSourceRow(sourceY, packed, yRow0, cbRow0, crRow0); |
|
||||
WriteSamples<TSample, TStorer>( |
|
||||
yRow0, |
|
||||
this.GetPlaneRow(Av1Plane.Y, sourceY), |
|
||||
this.colorConverter.LumaScale, |
|
||||
this.colorConverter.LumaBias, |
|
||||
this.sampleMaximum); |
|
||||
|
|
||||
bool hasSecondSourceRow = this.rowShift != 0 && sourceY + 1 < this.image.Height; |
|
||||
Span<float> yRow1 = Span<float>.Empty; |
|
||||
Span<float> cbRow1 = Span<float>.Empty; |
|
||||
Span<float> crRow1 = Span<float>.Empty; |
|
||||
if (hasSecondSourceRow) |
|
||||
{ |
|
||||
yRow1 = components.Slice(width * 3, width); |
|
||||
cbRow1 = components.Slice(width * 4, width); |
|
||||
crRow1 = components.Slice(width * 5, width); |
|
||||
this.ConvertSourceRow(sourceY + 1, packed, yRow1, cbRow1, crRow1); |
|
||||
WriteSamples<TSample, TStorer>( |
|
||||
yRow1, |
|
||||
this.GetPlaneRow(Av1Plane.Y, sourceY + 1), |
|
||||
this.colorConverter.LumaScale, |
|
||||
this.colorConverter.LumaBias, |
|
||||
this.sampleMaximum); |
|
||||
} |
|
||||
|
|
||||
if (this.isMonochrome) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
Span<TSample> uDestination = this.GetPlaneRow(Av1Plane.U, y); |
|
||||
Span<TSample> vDestination = this.GetPlaneRow(Av1Plane.V, y); |
|
||||
float chromaScale = this.colorConverter.ChromaScale; |
|
||||
float chromaBias = this.colorConverter.ChromaBias; |
|
||||
if (this.subX == 0) |
|
||||
{ |
|
||||
WriteSamples<TSample, TStorer>(cbRow0, uDestination, chromaScale, chromaBias, this.sampleMaximum); |
|
||||
WriteSamples<TSample, TStorer>(crRow0, vDestination, chromaScale, chromaBias, this.sampleMaximum); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
float secondRowWeight = hasSecondSourceRow ? 0.5F : 0F; |
|
||||
WriteSubsampledSamples<TSample, TStorer>( |
|
||||
cbRow0, |
|
||||
cbRow1, |
|
||||
uDestination, |
|
||||
true, |
|
||||
secondRowWeight, |
|
||||
chromaScale, |
|
||||
chromaBias, |
|
||||
this.sampleMaximum); |
|
||||
|
|
||||
WriteSubsampledSamples<TSample, TStorer>( |
|
||||
crRow0, |
|
||||
crRow1, |
|
||||
vDestination, |
|
||||
true, |
|
||||
secondRowWeight, |
|
||||
chromaScale, |
|
||||
chromaBias, |
|
||||
this.sampleMaximum); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts one source row into normalized planar AV1 components.
|
|
||||
/// </summary>
|
|
||||
/// <param name="y">The source row index.</param>
|
|
||||
/// <param name="packed">The high-bit-depth RGB staging row.</param>
|
|
||||
/// <param name="luma">The destination luma values.</param>
|
|
||||
/// <param name="chromaBlue">The destination blue-difference values.</param>
|
|
||||
/// <param name="chromaRed">The destination red-difference values.</param>
|
|
||||
private void ConvertSourceRow(int y, Span<Rgb48> packed, Span<float> luma, Span<float> chromaBlue, Span<float> chromaRed) |
|
||||
{ |
|
||||
ReadOnlySpan<TPixel> source = this.image.PixelBuffer.DangerousGetRowSpan(y); |
|
||||
if (typeof(TSample) == typeof(byte)) |
|
||||
{ |
|
||||
// This is the same planar input contract used by JPEG encoding. Pixel types with optimized
|
|
||||
// unpackers reach their existing SIMD path before the AV1 operator consumes the planes.
|
|
||||
PixelOperations<TPixel>.Instance.UnpackIntoRgbPlanes(luma, chromaBlue, chromaRed, source); |
|
||||
this.colorConverter.ConvertFromRgbInPlace(luma, chromaBlue, chromaRed, ByteMaximum); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
// JPEG's planar unpack contract is eight-bit. AV1 10/12-bit encoding stages Rgb48 instead
|
|
||||
// so high-precision source pixels are not truncated before the color transform.
|
|
||||
PixelOperations<TPixel>.Instance.ToRgb48(this.configuration, source, packed); |
|
||||
DeinterleaveRgb48(packed, luma, chromaBlue, chromaRed); |
|
||||
this.colorConverter.ConvertFromRgbInPlace(luma, chromaBlue, chromaRed, UShortMaximum); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets a writable row from an eight-bit or high-bit-depth AV1 plane.
|
|
||||
/// </summary>
|
|
||||
/// <param name="plane">The requested plane.</param>
|
|
||||
/// <param name="y">The row index in the requested plane.</param>
|
|
||||
/// <returns>The writable sample row.</returns>
|
|
||||
private Span<TSample> GetPlaneRow(Av1Plane plane, int y) |
|
||||
{ |
|
||||
if (typeof(TSample) == typeof(byte)) |
|
||||
{ |
|
||||
Buffer2DRegion<byte> region = plane switch |
|
||||
{ |
|
||||
Av1Plane.Y => this.yPlane, |
|
||||
Av1Plane.U => this.uPlane, |
|
||||
_ => this.vPlane, |
|
||||
}; |
|
||||
|
|
||||
return MemoryMarshal.Cast<byte, TSample>(region.DangerousGetRowSpan(y)); |
|
||||
} |
|
||||
|
|
||||
int subX = plane == Av1Plane.Y ? 0 : this.subX; |
|
||||
int subY = plane == Av1Plane.Y ? 0 : this.rowShift; |
|
||||
return MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(plane, y, subX, subY)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,174 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Buffers; |
|
||||
using SixLabors.ImageSharp.Advanced; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Color; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Cicp; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using static SixLabors.ImageSharp.Formats.Heif.Color.HeifColorConverterBase; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts between reconstructed AV1 YUV planes and packed ImageSharp pixels.
|
|
||||
/// </summary>
|
|
||||
internal static partial class Av1YuvConverter |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The largest value represented by an eight-bit packed RGB component.
|
|
||||
/// </summary>
|
|
||||
private const float ByteMaximum = byte.MaxValue; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The largest value represented by a 16-bit packed RGB component.
|
|
||||
/// </summary>
|
|
||||
private const float UShortMaximum = ushort.MaxValue; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts the reconstructed YUV 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="frameBuffer">The reconstructed AV1 frame.</param>
|
|
||||
/// <param name="image">The destination image frame.</param>
|
|
||||
public static void ConvertToRgb<TPixel>(Configuration configuration, Av1FrameBuffer<byte> frameBuffer, ImageFrame<TPixel> image) |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer, out HeifColorConversionMode mode); |
|
||||
|
|
||||
HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, frameBuffer.ColorFormat == Av1ColorFormat.Yuv400); |
|
||||
if (frameBuffer.BitDepth == Av1BitDepth.EightBit) |
|
||||
{ |
|
||||
YuvToRgbRowConverter<TPixel, byte, HeifByteSampleLoader> converter = new(configuration, frameBuffer, image, colorConverter); |
|
||||
using IMemoryOwner<float> scratchOwner = configuration.MemoryAllocator.Allocate<float>(converter.BufferLength); |
|
||||
Span<float> scratch = scratchOwner.GetSpan(); |
|
||||
for (int y = 0; y < image.Height; y++) |
|
||||
{ |
|
||||
converter.Convert(y, scratch); |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
YuvToRgbRowConverter<TPixel, ushort, HeifUShortSampleLoader> converter = new(configuration, frameBuffer, image, colorConverter); |
|
||||
using IMemoryOwner<float> owner = configuration.MemoryAllocator.Allocate<float>(converter.BufferLength); |
|
||||
Span<float> scratch = owner.GetSpan(); |
|
||||
for (int y = 0; y < image.Height; y++) |
|
||||
{ |
|
||||
converter.Convert(y, scratch); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Converts packed pixels to the configured monochrome or YUV planes used by the AV1 encoder.
|
|
||||
/// </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="frameBuffer">The destination AV1 frame.</param>
|
|
||||
public static void ConvertFromRgb<TPixel>(Configuration configuration, ImageFrame<TPixel> image, Av1FrameBuffer<byte> frameBuffer) |
|
||||
where TPixel : unmanaged, IPixel<TPixel> |
|
||||
{ |
|
||||
HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer, out HeifColorConversionMode mode); |
|
||||
float sampleMaximum = parameters.LumaSampleMaximum; |
|
||||
|
|
||||
bool isMonochrome = frameBuffer.ColorFormat == Av1ColorFormat.Yuv400; |
|
||||
HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, isMonochrome); |
|
||||
int rowShift = !isMonochrome && frameBuffer.ColorConfig.SubSamplingY ? 1 : 0; |
|
||||
int iterationCount = (image.Height + rowShift) >> rowShift; |
|
||||
if (frameBuffer.BitDepth == Av1BitDepth.EightBit) |
|
||||
{ |
|
||||
RgbToYuvRowConverter<TPixel, byte, HeifByteSampleStorer> converter = new(configuration, frameBuffer, image, colorConverter, sampleMaximum); |
|
||||
using IMemoryOwner<float> componentOwner = configuration.MemoryAllocator.Allocate<float>(converter.ComponentBufferLength); |
|
||||
Span<float> components = componentOwner.GetSpan(); |
|
||||
for (int y = 0; y < iterationCount; y++) |
|
||||
{ |
|
||||
converter.Convert(y, Span<Rgb48>.Empty, components); |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
RgbToYuvRowConverter<TPixel, ushort, HeifUShortSampleStorer> converter = new(configuration, frameBuffer, image, colorConverter, sampleMaximum); |
|
||||
using IMemoryOwner<Rgb48> packedOwner = configuration.MemoryAllocator.Allocate<Rgb48>(image.Width); |
|
||||
using IMemoryOwner<float> componentOwner = configuration.MemoryAllocator.Allocate<float>(converter.ComponentBufferLength); |
|
||||
Span<Rgb48> packed = packedOwner.GetSpan()[..image.Width]; |
|
||||
Span<float> components = componentOwner.GetSpan(); |
|
||||
for (int y = 0; y < iterationCount; y++) |
|
||||
{ |
|
||||
converter.Convert(y, packed, components); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Resolves the H.273 conversion mode, matrix coefficients, and sample range for a frame.
|
|
||||
/// </summary>
|
|
||||
/// <param name="frameBuffer">The AV1 frame containing the signaled color configuration.</param>
|
|
||||
/// <param name="mode">The resolved conversion mode.</param>
|
|
||||
/// <returns>The resolved conversion parameters.</returns>
|
|
||||
private static HeifColorConversionParameters GetConversionParameters(Av1FrameBuffer<byte> frameBuffer, out HeifColorConversionMode mode) |
|
||||
{ |
|
||||
if (frameBuffer.ColorConfig.ChromaSamplePosition == ObuChromoSamplePosition.Reserved) |
|
||||
{ |
|
||||
throw new InvalidImageContentException("The reserved AV1 chroma sample position is invalid."); |
|
||||
} |
|
||||
|
|
||||
bool isMonochrome = frameBuffer.ColorFormat == Av1ColorFormat.Yuv400; |
|
||||
|
|
||||
return HeifColorConversionParameters.Create( |
|
||||
(CicpColorPrimaries)(byte)frameBuffer.ColorConfig.ColorPrimaries, |
|
||||
(CicpTransferCharacteristics)(byte)frameBuffer.ColorConfig.TransferCharacteristics, |
|
||||
(CicpMatrixCoefficients)(byte)frameBuffer.ColorConfig.MatrixCoefficients, |
|
||||
frameBuffer.ColorConfig.ColorRange, |
|
||||
frameBuffer.BitDepth.GetBitCount(), |
|
||||
frameBuffer.BitDepth.GetBitCount(), |
|
||||
isMonochrome, |
|
||||
frameBuffer.ColorFormat == Av1ColorFormat.Yuv444, |
|
||||
out mode); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Resolves the two chroma samples and quarter-sample weight surrounding a luma coordinate.
|
|
||||
/// </summary>
|
|
||||
/// <param name="coordinate">The luma coordinate.</param>
|
|
||||
/// <param name="subsampling">The chroma subsampling shift.</param>
|
|
||||
/// <param name="isCentered">Whether chroma lies between neighboring luma samples.</param>
|
|
||||
/// <param name="maximum">The last available chroma coordinate.</param>
|
|
||||
/// <param name="lower">The lower chroma coordinate.</param>
|
|
||||
/// <param name="upper">The upper chroma coordinate.</param>
|
|
||||
/// <param name="upperWeight">The upper-coordinate weight with a denominator of four.</param>
|
|
||||
private static void GetChromaCoordinates( |
|
||||
int coordinate, |
|
||||
int subsampling, |
|
||||
bool isCentered, |
|
||||
int maximum, |
|
||||
out int lower, |
|
||||
out int upper, |
|
||||
out int upperWeight) |
|
||||
{ |
|
||||
if (subsampling == 0) |
|
||||
{ |
|
||||
lower = coordinate; |
|
||||
upper = coordinate; |
|
||||
upperWeight = 0; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
int sample = coordinate >> 1; |
|
||||
bool isOdd = (coordinate & 1) != 0; |
|
||||
if (isCentered) |
|
||||
{ |
|
||||
lower = isOdd ? sample : Math.Max(sample - 1, 0); |
|
||||
upper = isOdd ? Math.Min(sample + 1, maximum) : sample; |
|
||||
upperWeight = isOdd ? 1 : 3; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
lower = sample; |
|
||||
upper = isOdd ? Math.Min(sample + 1, maximum) : sample; |
|
||||
upperWeight = isOdd ? 2 : 0; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,133 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.InteropServices; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Color; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Color; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adapts reconstructed AV1 planes to the shared HEIF planar color pipeline.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TSample">The native unsigned sample storage type.</typeparam>
|
||||
|
internal struct Av1PlanarSampleBuffer<TSample> : IHeifPlanarSampleBuffer<TSample> |
||||
|
where TSample : unmanaged |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The reconstructed AV1 frame containing the component planes.
|
||||
|
/// </summary>
|
||||
|
private readonly Av1FrameBuffer<byte> frameBuffer; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The visible luma plane in byte-backed storage.
|
||||
|
/// </summary>
|
||||
|
private readonly Buffer2DRegion<byte> luma; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The visible blue-difference plane in byte-backed storage.
|
||||
|
/// </summary>
|
||||
|
private readonly Buffer2DRegion<byte> chromaBlue; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The visible red-difference plane in byte-backed storage.
|
||||
|
/// </summary>
|
||||
|
private readonly Buffer2DRegion<byte> chromaRed; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="Av1PlanarSampleBuffer{TSample}"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="frameBuffer">The reconstructed AV1 frame.</param>
|
||||
|
public Av1PlanarSampleBuffer(Av1FrameBuffer<byte> frameBuffer) |
||||
|
{ |
||||
|
this.frameBuffer = frameBuffer; |
||||
|
this.luma = frameBuffer.DeriveBlockPointer(Av1Plane.Y, 0, 0); |
||||
|
this.chromaBlue = this.IsMonochrome |
||||
|
? default |
||||
|
: frameBuffer.DeriveBlockPointer(Av1Plane.U, this.ChromaSubsamplingX, this.ChromaSubsamplingY); |
||||
|
|
||||
|
this.chromaRed = this.IsMonochrome |
||||
|
? default |
||||
|
: frameBuffer.DeriveBlockPointer(Av1Plane.V, this.ChromaSubsamplingX, this.ChromaSubsamplingY); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public readonly int Width => this.frameBuffer.Width; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public readonly int Height => this.frameBuffer.Height; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public readonly int LumaBitDepth => this.frameBuffer.BitDepth.GetBitCount(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public readonly int ChromaBitDepth => this.frameBuffer.BitDepth.GetBitCount(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public readonly bool IsMonochrome => this.frameBuffer.ColorFormat == Av1ColorFormat.Yuv400; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public readonly int ChromaSubsamplingX => this.frameBuffer.ColorConfig.SubSamplingX ? 1 : 0; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public readonly int ChromaSubsamplingY => this.frameBuffer.ColorConfig.SubSamplingY ? 1 : 0; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public readonly int ChromaPositionX |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (this.ChromaSubsamplingX == 0) |
||||
|
{ |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
// AV1 4:2:2 chroma is centered horizontally. For 4:2:0, CSP_UNKNOWN is centered while the two
|
||||
|
// explicitly positioned layouts are co-sited with the left luma sample.
|
||||
|
bool isCentered = this.ChromaSubsamplingY == 0 |
||||
|
|| this.frameBuffer.ColorConfig.ChromaSamplePosition == ObuChromoSamplePosition.Unknown; |
||||
|
|
||||
|
return isCentered ? 1 : 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public readonly int ChromaPositionY |
||||
|
=> this.ChromaSubsamplingY != 0 && this.frameBuffer.ColorConfig.ChromaSamplePosition != ObuChromoSamplePosition.Colocated ? 1 : 0; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Span<TSample> GetLumaRowSpan(int row) |
||||
|
{ |
||||
|
if (typeof(TSample) == typeof(byte)) |
||||
|
{ |
||||
|
return MemoryMarshal.Cast<byte, TSample>(this.luma.DangerousGetRowSpan(row)); |
||||
|
} |
||||
|
|
||||
|
return MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.Y, row, 0, 0)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Span<TSample> GetChromaBlueRowSpan(int row) |
||||
|
{ |
||||
|
if (typeof(TSample) == typeof(byte)) |
||||
|
{ |
||||
|
return MemoryMarshal.Cast<byte, TSample>(this.chromaBlue.DangerousGetRowSpan(row)); |
||||
|
} |
||||
|
|
||||
|
return MemoryMarshal.Cast<ushort, TSample>( |
||||
|
this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.U, row, this.ChromaSubsamplingX, this.ChromaSubsamplingY)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Span<TSample> GetChromaRedRowSpan(int row) |
||||
|
{ |
||||
|
if (typeof(TSample) == typeof(byte)) |
||||
|
{ |
||||
|
return MemoryMarshal.Cast<byte, TSample>(this.chromaRed.DangerousGetRowSpan(row)); |
||||
|
} |
||||
|
|
||||
|
return MemoryMarshal.Cast<ushort, TSample>( |
||||
|
this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.V, row, this.ChromaSubsamplingX, this.ChromaSubsamplingY)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Color; |
||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Cicp; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using static SixLabors.ImageSharp.Formats.Heif.Color.HeifColorConverterBase; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Color; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adapts AV1 color signaling and reconstructed planes to the shared HEIF color pipeline.
|
||||
|
/// </summary>
|
||||
|
internal static class Av1YuvConverter |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Converts the reconstructed 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="frameBuffer">The reconstructed AV1 frame.</param>
|
||||
|
/// <param name="image">The destination image frame.</param>
|
||||
|
public static void ConvertToRgb<TPixel>(Configuration configuration, Av1FrameBuffer<byte> frameBuffer, ImageFrame<TPixel> image) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer, out HeifColorConversionMode mode); |
||||
|
if (frameBuffer.BitDepth == Av1BitDepth.EightBit) |
||||
|
{ |
||||
|
Av1PlanarSampleBuffer<byte> buffer = new(frameBuffer); |
||||
|
HeifPlanarColorConverter.ConvertToRgb<TPixel, Av1PlanarSampleBuffer<byte>, byte, HeifByteSampleLoader>( |
||||
|
configuration, |
||||
|
buffer, |
||||
|
image, |
||||
|
in parameters, |
||||
|
mode); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Av1PlanarSampleBuffer<ushort> highBitDepthBuffer = new(frameBuffer); |
||||
|
HeifPlanarColorConverter.ConvertToRgb<TPixel, Av1PlanarSampleBuffer<ushort>>( |
||||
|
configuration, |
||||
|
highBitDepthBuffer, |
||||
|
image, |
||||
|
in parameters, |
||||
|
mode); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts packed pixels to the configured monochrome or component planes used by the AV1 encoder.
|
||||
|
/// </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="frameBuffer">The destination AV1 frame.</param>
|
||||
|
public static void ConvertFromRgb<TPixel>(Configuration configuration, ImageFrame<TPixel> image, Av1FrameBuffer<byte> frameBuffer) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
{ |
||||
|
HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer, out HeifColorConversionMode mode); |
||||
|
if (frameBuffer.BitDepth == Av1BitDepth.EightBit) |
||||
|
{ |
||||
|
Av1PlanarSampleBuffer<byte> buffer = new(frameBuffer); |
||||
|
HeifPlanarColorConverter.ConvertFromRgb<TPixel, Av1PlanarSampleBuffer<byte>, byte, HeifByteSampleStorer>( |
||||
|
configuration, |
||||
|
image, |
||||
|
buffer, |
||||
|
in parameters, |
||||
|
mode); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Av1PlanarSampleBuffer<ushort> highBitDepthBuffer = new(frameBuffer); |
||||
|
HeifPlanarColorConverter.ConvertFromRgb<TPixel, Av1PlanarSampleBuffer<ushort>, ushort, HeifUShortSampleStorer>( |
||||
|
configuration, |
||||
|
image, |
||||
|
highBitDepthBuffer, |
||||
|
in parameters, |
||||
|
mode); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resolves the H.273 conversion mode, matrix coefficients, and sample range for a frame.
|
||||
|
/// </summary>
|
||||
|
/// <param name="frameBuffer">The AV1 frame containing the signaled color configuration.</param>
|
||||
|
/// <param name="mode">The resolved conversion mode.</param>
|
||||
|
/// <returns>The resolved conversion parameters.</returns>
|
||||
|
private static HeifColorConversionParameters GetConversionParameters(Av1FrameBuffer<byte> frameBuffer, out HeifColorConversionMode mode) |
||||
|
{ |
||||
|
if (frameBuffer.ColorConfig.ChromaSamplePosition == ObuChromoSamplePosition.Reserved) |
||||
|
{ |
||||
|
throw new InvalidImageContentException("The reserved AV1 chroma sample position is invalid."); |
||||
|
} |
||||
|
|
||||
|
bool isMonochrome = frameBuffer.ColorFormat == Av1ColorFormat.Yuv400; |
||||
|
|
||||
|
return HeifColorConversionParameters.Create( |
||||
|
(CicpColorPrimaries)(byte)frameBuffer.ColorConfig.ColorPrimaries, |
||||
|
(CicpTransferCharacteristics)(byte)frameBuffer.ColorConfig.TransferCharacteristics, |
||||
|
(CicpMatrixCoefficients)(byte)frameBuffer.ColorConfig.MatrixCoefficients, |
||||
|
frameBuffer.ColorConfig.ColorRange, |
||||
|
frameBuffer.BitDepth.GetBitCount(), |
||||
|
frameBuffer.BitDepth.GetBitCount(), |
||||
|
isMonochrome, |
||||
|
frameBuffer.ColorFormat == Av1ColorFormat.Yuv444, |
||||
|
out mode); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,738 @@ |
|||||
|
// 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.Common.Helpers; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using static SixLabors.ImageSharp.Formats.Heif.Color.HeifColorConverterBase; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Color; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts between native HEIF component planes and packed ImageSharp pixels.
|
||||
|
/// </summary>
|
||||
|
internal static class HeifPlanarColorConverter |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The largest value represented by an eight-bit packed RGB component.
|
||||
|
/// </summary>
|
||||
|
private const float ByteMaximum = byte.MaxValue; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The largest value represented by a 16-bit packed RGB component.
|
||||
|
/// </summary>
|
||||
|
private const float UShortMaximum = ushort.MaxValue; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts native unsigned 16-bit component storage to packed pixels and selects eligible exact integer kernels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
|
||||
|
/// <typeparam name="TBuffer">The codec adapter exposing the native component planes.</typeparam>
|
||||
|
/// <param name="configuration">The configuration used for allocation and pixel conversion.</param>
|
||||
|
/// <param name="buffer">The native component-plane buffer.</param>
|
||||
|
/// <param name="image">The destination image frame.</param>
|
||||
|
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
||||
|
/// <param name="mode">The resolved H.273 conversion mode.</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, TBuffer>( |
||||
|
Configuration configuration, |
||||
|
TBuffer buffer, |
||||
|
ImageFrame<TPixel> image, |
||||
|
in HeifColorConversionParameters parameters, |
||||
|
HeifColorConversionMode mode, |
||||
|
int sourceX = 0, |
||||
|
int sourceY = 0) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
where TBuffer : struct, IHeifPlanarSampleBuffer<ushort> |
||||
|
{ |
||||
|
if (HeifYuv420ToRgb8Converter.IsSupported( |
||||
|
buffer.ChromaSubsamplingX, |
||||
|
buffer.ChromaSubsamplingY, |
||||
|
buffer.LumaBitDepth, |
||||
|
buffer.ChromaBitDepth, |
||||
|
parameters.IsFullRange, |
||||
|
parameters.MatrixCoefficients, |
||||
|
mode)) |
||||
|
{ |
||||
|
// The fixed-point operator preserves the exact code-value rounding used by the verified eight-bit
|
||||
|
// presentation path. Selection belongs here so no codec can acquire a private color-conversion route.
|
||||
|
HeifYuv420ToRgb8Converter.Convert(configuration, buffer, image, in parameters, sourceX, sourceY); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
ConvertToRgb<TPixel, TBuffer, ushort, HeifUShortSampleLoader>( |
||||
|
configuration, |
||||
|
buffer, |
||||
|
image, |
||||
|
in parameters, |
||||
|
mode, |
||||
|
sourceX, |
||||
|
sourceY); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts native component planes to packed pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
|
||||
|
/// <typeparam name="TBuffer">The codec adapter exposing the native component planes.</typeparam>
|
||||
|
/// <typeparam name="TSample">The native unsigned sample storage type.</typeparam>
|
||||
|
/// <typeparam name="TLoader">The SIMD widening operations for the sample type.</typeparam>
|
||||
|
/// <param name="configuration">The configuration used for allocation and pixel conversion.</param>
|
||||
|
/// <param name="buffer">The native component-plane buffer.</param>
|
||||
|
/// <param name="image">The destination image frame.</param>
|
||||
|
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
||||
|
/// <param name="mode">The resolved H.273 conversion mode.</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, TBuffer, TSample, TLoader>( |
||||
|
Configuration configuration, |
||||
|
TBuffer buffer, |
||||
|
ImageFrame<TPixel> image, |
||||
|
in HeifColorConversionParameters parameters, |
||||
|
HeifColorConversionMode mode, |
||||
|
int sourceX = 0, |
||||
|
int sourceY = 0) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
where TBuffer : struct, IHeifPlanarSampleBuffer<TSample> |
||||
|
where TSample : unmanaged |
||||
|
where TLoader : struct, IHeifSampleLoader<TSample> |
||||
|
{ |
||||
|
HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, buffer.IsMonochrome); |
||||
|
YuvToRgbRowConverter<TPixel, TBuffer, TSample, TLoader> converter = new( |
||||
|
configuration, |
||||
|
buffer, |
||||
|
image, |
||||
|
colorConverter, |
||||
|
sourceX, |
||||
|
sourceY); |
||||
|
|
||||
|
using IMemoryOwner<float> scratchOwner = configuration.MemoryAllocator.Allocate<float>(converter.BufferLength); |
||||
|
Span<float> scratch = scratchOwner.GetSpan(); |
||||
|
for (int y = 0; y < image.Height; y++) |
||||
|
{ |
||||
|
converter.Convert(y, scratch); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts packed pixels to native component planes.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The source pixel type.</typeparam>
|
||||
|
/// <typeparam name="TBuffer">The codec adapter exposing the native component planes.</typeparam>
|
||||
|
/// <typeparam name="TSample">The native unsigned sample storage type.</typeparam>
|
||||
|
/// <typeparam name="TStorer">The SIMD narrowing and storage operations for the sample type.</typeparam>
|
||||
|
/// <param name="configuration">The configuration used for allocation and pixel conversion.</param>
|
||||
|
/// <param name="image">The source image frame.</param>
|
||||
|
/// <param name="buffer">The destination component-plane buffer.</param>
|
||||
|
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
||||
|
/// <param name="mode">The resolved H.273 conversion mode.</param>
|
||||
|
public static void ConvertFromRgb<TPixel, TBuffer, TSample, TStorer>( |
||||
|
Configuration configuration, |
||||
|
ImageFrame<TPixel> image, |
||||
|
TBuffer buffer, |
||||
|
in HeifColorConversionParameters parameters, |
||||
|
HeifColorConversionMode mode) |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
where TBuffer : struct, IHeifPlanarSampleBuffer<TSample> |
||||
|
where TSample : unmanaged |
||||
|
where TStorer : struct, IHeifSampleStorer<TSample> |
||||
|
{ |
||||
|
HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, buffer.IsMonochrome); |
||||
|
RgbToYuvRowConverter<TPixel, TBuffer, TSample, TStorer> converter = new( |
||||
|
configuration, |
||||
|
buffer, |
||||
|
image, |
||||
|
colorConverter, |
||||
|
in parameters); |
||||
|
|
||||
|
using IMemoryOwner<float> componentOwner = configuration.MemoryAllocator.Allocate<float>(converter.ComponentBufferLength); |
||||
|
Span<float> components = componentOwner.GetSpan(); |
||||
|
if (converter.UsesByteInput) |
||||
|
{ |
||||
|
converter.Convert(Span<Rgb48>.Empty, components); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
using IMemoryOwner<Rgb48> packedOwner = configuration.MemoryAllocator.Allocate<Rgb48>(image.Width); |
||||
|
converter.Convert(packedOwner.GetSpan()[..image.Width], components); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resolves the two chroma rows and quarter-sample weight surrounding a luma row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="coordinate">The luma row coordinate.</param>
|
||||
|
/// <param name="subsampling">The vertical chroma subsampling shift.</param>
|
||||
|
/// <param name="position">The chroma offset in half-luma-sample units.</param>
|
||||
|
/// <param name="maximum">The last available chroma row.</param>
|
||||
|
/// <param name="lower">The lower chroma row.</param>
|
||||
|
/// <param name="upper">The upper chroma row.</param>
|
||||
|
/// <param name="upperWeight">The upper-row weight with a denominator of four.</param>
|
||||
|
private static void GetChromaCoordinates( |
||||
|
int coordinate, |
||||
|
int subsampling, |
||||
|
int position, |
||||
|
int maximum, |
||||
|
out int lower, |
||||
|
out int upper, |
||||
|
out int upperWeight) |
||||
|
{ |
||||
|
if (subsampling == 0) |
||||
|
{ |
||||
|
lower = coordinate; |
||||
|
upper = coordinate; |
||||
|
upperWeight = 0; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Multiplication by two expresses the luma coordinate in half-sample units, while division by four
|
||||
|
// addresses the subsampled plane. Floor division is required before the first centered chroma sample.
|
||||
|
int quarterCoordinate = (coordinate * 2) - position; |
||||
|
int unclampedLower = quarterCoordinate >= 0 ? quarterCoordinate >> 2 : -((-quarterCoordinate + 3) >> 2); |
||||
|
int fraction = quarterCoordinate - (unclampedLower * 4); |
||||
|
lower = Numerics.Clamp(unclampedLower, 0, maximum); |
||||
|
upper = Numerics.Clamp(unclampedLower + 1, 0, maximum); |
||||
|
upperWeight = lower == upper ? 0 : fraction; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts one native component row using pooled planar and packed-pixel storage.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
|
||||
|
/// <typeparam name="TBuffer">The codec adapter exposing the native component planes.</typeparam>
|
||||
|
/// <typeparam name="TSample">The native unsigned sample storage type.</typeparam>
|
||||
|
/// <typeparam name="TLoader">The SIMD widening operations for the sample type.</typeparam>
|
||||
|
private struct YuvToRgbRowConverter<TPixel, TBuffer, TSample, TLoader> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
where TBuffer : struct, IHeifPlanarSampleBuffer<TSample> |
||||
|
where TSample : unmanaged |
||||
|
where TLoader : struct, IHeifSampleLoader<TSample> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The configuration used for packed-pixel conversion.
|
||||
|
/// </summary>
|
||||
|
private readonly Configuration configuration; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The codec adapter exposing the reconstructed component planes.
|
||||
|
/// </summary>
|
||||
|
private TBuffer buffer; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The destination image frame.
|
||||
|
/// </summary>
|
||||
|
private readonly ImageFrame<TPixel> image; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The selected H.273 color converter.
|
||||
|
/// </summary>
|
||||
|
private readonly HeifColorConverterBase colorConverter; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The horizontal luma-sample offset of the output window.
|
||||
|
/// </summary>
|
||||
|
private readonly int sourceX; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The vertical luma-sample offset of the output window.
|
||||
|
/// </summary>
|
||||
|
private readonly int sourceY; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The full coded luma-plane width.
|
||||
|
/// </summary>
|
||||
|
private readonly int bufferWidth; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The chroma-plane width in samples.
|
||||
|
/// </summary>
|
||||
|
private readonly int chromaWidth; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The chroma-plane height in samples.
|
||||
|
/// </summary>
|
||||
|
private readonly int chromaHeight; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The horizontal chroma subsampling shift.
|
||||
|
/// </summary>
|
||||
|
private readonly int subsamplingX; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The vertical chroma subsampling shift.
|
||||
|
/// </summary>
|
||||
|
private readonly int subsamplingY; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The horizontal chroma position in half-luma-sample units.
|
||||
|
/// </summary>
|
||||
|
private readonly int chromaPositionX; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The vertical chroma position in half-luma-sample units.
|
||||
|
/// </summary>
|
||||
|
private readonly int chromaPositionY; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Whether the buffer contains only the luma plane.
|
||||
|
/// </summary>
|
||||
|
private readonly bool isMonochrome; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Whether the destination uses JPEG-compatible eight-bit RGB plane packing.
|
||||
|
/// </summary>
|
||||
|
private readonly bool usesBytePacking; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Whether chroma reconstruction must retain the complete coded row before selecting the output window.
|
||||
|
/// </summary>
|
||||
|
private readonly bool reconstructCompleteRow; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="YuvToRgbRowConverter{TPixel, TBuffer, TSample, TLoader}"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The configuration used for pixel conversion.</param>
|
||||
|
/// <param name="buffer">The codec adapter exposing the reconstructed component planes.</param>
|
||||
|
/// <param name="image">The destination image frame.</param>
|
||||
|
/// <param name="colorConverter">The selected H.273 color converter.</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 YuvToRgbRowConverter( |
||||
|
Configuration configuration, |
||||
|
TBuffer buffer, |
||||
|
ImageFrame<TPixel> image, |
||||
|
HeifColorConverterBase colorConverter, |
||||
|
int sourceX, |
||||
|
int sourceY) |
||||
|
{ |
||||
|
this.configuration = configuration; |
||||
|
this.buffer = buffer; |
||||
|
this.image = image; |
||||
|
this.colorConverter = colorConverter; |
||||
|
this.sourceX = sourceX; |
||||
|
this.sourceY = sourceY; |
||||
|
this.bufferWidth = buffer.Width; |
||||
|
this.subsamplingX = buffer.ChromaSubsamplingX; |
||||
|
this.subsamplingY = buffer.ChromaSubsamplingY; |
||||
|
this.chromaPositionX = buffer.ChromaPositionX; |
||||
|
this.chromaPositionY = buffer.ChromaPositionY; |
||||
|
this.isMonochrome = buffer.IsMonochrome; |
||||
|
this.usesBytePacking = buffer.LumaBitDepth == 8 && (buffer.IsMonochrome || buffer.ChromaBitDepth == 8); |
||||
|
this.chromaWidth = (buffer.Width + (1 << this.subsamplingX) - 1) >> this.subsamplingX; |
||||
|
this.chromaHeight = (buffer.Height + (1 << this.subsamplingY) - 1) >> this.subsamplingY; |
||||
|
this.reconstructCompleteRow = sourceX != 0 || image.Width != buffer.Width; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the destination uses JPEG-compatible eight-bit RGB plane packing.
|
||||
|
/// </summary>
|
||||
|
public readonly bool UsesBytePacking => this.usesBytePacking; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of float elements required by the reusable row buffer.
|
||||
|
/// </summary>
|
||||
|
public readonly int BufferLength |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
int componentLength = this.image.Width * 3; |
||||
|
if (!this.isMonochrome && this.subsamplingX != 0) |
||||
|
{ |
||||
|
// Full-image conversion reconstructs directly into the component rows. Cropped conversion
|
||||
|
// retains the complete coded row so interpolation phase is preserved at the window boundary.
|
||||
|
componentLength += (this.chromaWidth * 2) + (this.reconstructCompleteRow ? this.bufferWidth : 0); |
||||
|
} |
||||
|
|
||||
|
int packedRowCount = this.UsesBytePacking ? 1 : 2; |
||||
|
return componentLength + (this.image.Width * packedRowCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts one reconstructed row to packed pixels.
|
||||
|
/// </summary>
|
||||
|
/// <param name="y">The zero-based output row.</param>
|
||||
|
/// <param name="scratch">The reusable pooled row buffer.</param>
|
||||
|
public void Convert(int y, Span<float> scratch) |
||||
|
{ |
||||
|
int width = this.image.Width; |
||||
|
Span<float> red = scratch[..width]; |
||||
|
Span<float> green = scratch.Slice(width, width); |
||||
|
Span<float> blue = scratch.Slice(width * 2, width); |
||||
|
int sourceY = y + this.sourceY; |
||||
|
ReadOnlySpan<TSample> luma = this.buffer.GetLumaRowSpan(sourceY).Slice(this.sourceX, width); |
||||
|
ConvertSamplesToFloat<TSample, TLoader>(luma, red); |
||||
|
|
||||
|
int packedOffset = width * 3; |
||||
|
if (!this.isMonochrome) |
||||
|
{ |
||||
|
GetChromaCoordinates( |
||||
|
sourceY, |
||||
|
this.subsamplingY, |
||||
|
this.chromaPositionY, |
||||
|
this.chromaHeight - 1, |
||||
|
out int y0, |
||||
|
out int y1, |
||||
|
out int y1Weight); |
||||
|
|
||||
|
ReadOnlySpan<TSample> cb0 = this.buffer.GetChromaBlueRowSpan(y0); |
||||
|
ReadOnlySpan<TSample> cb1 = this.buffer.GetChromaBlueRowSpan(y1); |
||||
|
ReadOnlySpan<TSample> cr0 = this.buffer.GetChromaRedRowSpan(y0); |
||||
|
ReadOnlySpan<TSample> cr1 = this.buffer.GetChromaRedRowSpan(y1); |
||||
|
if (this.subsamplingX == 0) |
||||
|
{ |
||||
|
ConvertSamplesToFloat<TSample, TLoader>(cb0.Slice(this.sourceX, width), green); |
||||
|
ConvertSamplesToFloat<TSample, TLoader>(cr0.Slice(this.sourceX, width), blue); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Span<float> chroma0 = scratch.Slice(packedOffset, this.chromaWidth); |
||||
|
Span<float> chroma1 = scratch.Slice(packedOffset + this.chromaWidth, this.chromaWidth); |
||||
|
Span<float> reconstructed = this.reconstructCompleteRow |
||||
|
? scratch.Slice(packedOffset + (this.chromaWidth * 2), this.bufferWidth) |
||||
|
: green; |
||||
|
|
||||
|
bool isCenteredX = this.chromaPositionX == 1; |
||||
|
|
||||
|
ReconstructChromaRow<TSample, TLoader>( |
||||
|
cb0, |
||||
|
cb1, |
||||
|
y1Weight, |
||||
|
this.subsamplingX, |
||||
|
isCenteredX, |
||||
|
reconstructed, |
||||
|
chroma0, |
||||
|
chroma1); |
||||
|
|
||||
|
if (this.reconstructCompleteRow) |
||||
|
{ |
||||
|
reconstructed.Slice(this.sourceX, width).CopyTo(green); |
||||
|
} |
||||
|
|
||||
|
reconstructed = this.reconstructCompleteRow ? reconstructed : blue; |
||||
|
ReconstructChromaRow<TSample, TLoader>( |
||||
|
cr0, |
||||
|
cr1, |
||||
|
y1Weight, |
||||
|
this.subsamplingX, |
||||
|
isCenteredX, |
||||
|
reconstructed, |
||||
|
chroma0, |
||||
|
chroma1); |
||||
|
|
||||
|
if (this.reconstructCompleteRow) |
||||
|
{ |
||||
|
reconstructed.Slice(this.sourceX, width).CopyTo(blue); |
||||
|
} |
||||
|
|
||||
|
packedOffset += (this.chromaWidth * 2) + (this.reconstructCompleteRow ? this.bufferWidth : 0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.colorConverter.ConvertToRgbInPlace(red, green, blue); |
||||
|
Span<TPixel> destination = this.image.PixelBuffer.DangerousGetRowSpan(y); |
||||
|
Span<float> packedStorage = scratch[packedOffset..]; |
||||
|
if (this.UsesBytePacking) |
||||
|
{ |
||||
|
// This is JPEG's planar packing contract. Existing pixel-specific SIMD packers therefore own the
|
||||
|
// final RGB-to-TPixel conversion rather than a HEIF-codec-specific per-pixel implementation.
|
||||
|
Span<byte> byteStorage = MemoryMarshal.AsBytes(packedStorage)[..(width * 3)]; |
||||
|
Span<byte> redBytes = byteStorage[..width]; |
||||
|
Span<byte> greenBytes = byteStorage.Slice(width, width); |
||||
|
Span<byte> blueBytes = byteStorage.Slice(width * 2, width); |
||||
|
SimdUtils.NormalizedFloatToByteSaturate(red, redBytes); |
||||
|
SimdUtils.NormalizedFloatToByteSaturate(green, greenBytes); |
||||
|
SimdUtils.NormalizedFloatToByteSaturate(blue, blueBytes); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(redBytes, greenBytes, blueBytes, destination); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Span<Rgba64> packed = MemoryMarshal.Cast<float, Rgba64>(packedStorage)[..width]; |
||||
|
PackRgba64(red, green, blue, packed); |
||||
|
PixelOperations<TPixel>.Instance.FromRgba64(this.configuration, packed, destination); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts packed image rows to native component planes using pooled planar storage.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The source pixel type.</typeparam>
|
||||
|
/// <typeparam name="TBuffer">The codec adapter exposing the native component planes.</typeparam>
|
||||
|
/// <typeparam name="TSample">The native unsigned sample storage type.</typeparam>
|
||||
|
/// <typeparam name="TStorer">The SIMD narrowing and storage operations for the sample type.</typeparam>
|
||||
|
private struct RgbToYuvRowConverter<TPixel, TBuffer, TSample, TStorer> |
||||
|
where TPixel : unmanaged, IPixel<TPixel> |
||||
|
where TBuffer : struct, IHeifPlanarSampleBuffer<TSample> |
||||
|
where TSample : unmanaged |
||||
|
where TStorer : struct, IHeifSampleStorer<TSample> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The configuration used for packed-pixel conversion.
|
||||
|
/// </summary>
|
||||
|
private readonly Configuration configuration; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The codec adapter exposing the destination component planes.
|
||||
|
/// </summary>
|
||||
|
private TBuffer buffer; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The source image frame.
|
||||
|
/// </summary>
|
||||
|
private readonly ImageFrame<TPixel> image; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The selected H.273 color converter.
|
||||
|
/// </summary>
|
||||
|
private readonly HeifColorConverterBase colorConverter; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The largest encoded luma sample value.
|
||||
|
/// </summary>
|
||||
|
private readonly float lumaMaximum; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The largest encoded chroma sample value.
|
||||
|
/// </summary>
|
||||
|
private readonly float chromaMaximum; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The horizontal chroma subsampling shift.
|
||||
|
/// </summary>
|
||||
|
private readonly int subsamplingX; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The vertical chroma subsampling shift.
|
||||
|
/// </summary>
|
||||
|
private readonly int subsamplingY; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The horizontal chroma position in half-luma-sample units.
|
||||
|
/// </summary>
|
||||
|
private readonly int chromaPositionX; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The vertical chroma position in half-luma-sample units.
|
||||
|
/// </summary>
|
||||
|
private readonly int chromaPositionY; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Whether the destination buffer contains only the luma plane.
|
||||
|
/// </summary>
|
||||
|
private readonly bool isMonochrome; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Whether source pixels use JPEG-compatible eight-bit RGB plane unpacking.
|
||||
|
/// </summary>
|
||||
|
private readonly bool usesByteInput; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="RgbToYuvRowConverter{TPixel, TBuffer, TSample, TStorer}"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The configuration used for pixel conversion.</param>
|
||||
|
/// <param name="buffer">The codec adapter exposing the destination component planes.</param>
|
||||
|
/// <param name="image">The source image frame.</param>
|
||||
|
/// <param name="colorConverter">The selected H.273 color converter.</param>
|
||||
|
/// <param name="parameters">The resolved H.273 component ranges.</param>
|
||||
|
public RgbToYuvRowConverter( |
||||
|
Configuration configuration, |
||||
|
TBuffer buffer, |
||||
|
ImageFrame<TPixel> image, |
||||
|
HeifColorConverterBase colorConverter, |
||||
|
in HeifColorConversionParameters parameters) |
||||
|
{ |
||||
|
this.configuration = configuration; |
||||
|
this.buffer = buffer; |
||||
|
this.image = image; |
||||
|
this.colorConverter = colorConverter; |
||||
|
this.lumaMaximum = parameters.LumaSampleMaximum; |
||||
|
this.chromaMaximum = parameters.ChromaSampleMaximum; |
||||
|
this.subsamplingX = buffer.ChromaSubsamplingX; |
||||
|
this.subsamplingY = buffer.ChromaSubsamplingY; |
||||
|
this.chromaPositionX = buffer.ChromaPositionX; |
||||
|
this.chromaPositionY = buffer.ChromaPositionY; |
||||
|
this.isMonochrome = buffer.IsMonochrome; |
||||
|
this.usesByteInput = buffer.LumaBitDepth == 8 && (buffer.IsMonochrome || buffer.ChromaBitDepth == 8); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether source pixels use JPEG-compatible eight-bit RGB plane unpacking.
|
||||
|
/// </summary>
|
||||
|
public readonly bool UsesByteInput => this.usesByteInput; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of float elements required by the reusable component buffer.
|
||||
|
/// </summary>
|
||||
|
public readonly int ComponentBufferLength => this.image.Width * (this.subsamplingY == 0 || this.isMonochrome ? 3 : 6); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts every packed source row to the destination component planes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="packed">The reusable high-bit-depth RGB staging row.</param>
|
||||
|
/// <param name="components">The reusable planar component buffer.</param>
|
||||
|
public void Convert(Span<Rgb48> packed, Span<float> components) |
||||
|
{ |
||||
|
int width = this.image.Width; |
||||
|
Span<float> luma0 = components[..width]; |
||||
|
Span<float> blue0 = components.Slice(width, width); |
||||
|
Span<float> red0 = components.Slice(width * 2, width); |
||||
|
if (this.subsamplingY == 0) |
||||
|
{ |
||||
|
for (int y = 0; y < this.image.Height; y++) |
||||
|
{ |
||||
|
this.ConvertSourceRow(y, packed, luma0, blue0, red0); |
||||
|
WriteSamples<TSample, TStorer>( |
||||
|
luma0, |
||||
|
this.buffer.GetLumaRowSpan(y), |
||||
|
this.colorConverter.LumaScale, |
||||
|
this.colorConverter.LumaBias, |
||||
|
this.lumaMaximum); |
||||
|
|
||||
|
if (!this.isMonochrome) |
||||
|
{ |
||||
|
this.WriteChromaRows(y, blue0, red0, Span<float>.Empty, Span<float>.Empty, 0F); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Span<float> luma1 = this.isMonochrome ? luma0 : components.Slice(width * 3, width); |
||||
|
Span<float> blue1 = this.isMonochrome ? blue0 : components.Slice(width * 4, width); |
||||
|
Span<float> red1 = this.isMonochrome ? red0 : components.Slice(width * 5, width); |
||||
|
int chromaHeight = (this.image.Height + 1) >> 1; |
||||
|
for (int destinationY = 0; destinationY < chromaHeight; destinationY++) |
||||
|
{ |
||||
|
// A vertically subsampled chroma row is owned by one two-row luma cell. Processing that cell as a
|
||||
|
// unit removes per-row state and lets the selected chroma position choose or average the two rows.
|
||||
|
int sourceY = destinationY << 1; |
||||
|
this.ConvertSourceRow(sourceY, packed, luma0, blue0, red0); |
||||
|
WriteSamples<TSample, TStorer>( |
||||
|
luma0, |
||||
|
this.buffer.GetLumaRowSpan(sourceY), |
||||
|
this.colorConverter.LumaScale, |
||||
|
this.colorConverter.LumaBias, |
||||
|
this.lumaMaximum); |
||||
|
|
||||
|
bool hasSecondRow = sourceY + 1 < this.image.Height; |
||||
|
if (hasSecondRow) |
||||
|
{ |
||||
|
this.ConvertSourceRow(sourceY + 1, packed, luma1, blue1, red1); |
||||
|
WriteSamples<TSample, TStorer>( |
||||
|
luma1, |
||||
|
this.buffer.GetLumaRowSpan(sourceY + 1), |
||||
|
this.colorConverter.LumaScale, |
||||
|
this.colorConverter.LumaBias, |
||||
|
this.lumaMaximum); |
||||
|
} |
||||
|
|
||||
|
if (this.isMonochrome) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (this.chromaPositionY == 0 || !hasSecondRow) |
||||
|
{ |
||||
|
this.WriteChromaRows(destinationY, blue0, red0, Span<float>.Empty, Span<float>.Empty, 0F); |
||||
|
} |
||||
|
else if (this.chromaPositionY == 1) |
||||
|
{ |
||||
|
this.WriteChromaRows(destinationY, blue0, red0, blue1, red1, 0.5F); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.WriteChromaRows(destinationY, blue1, red1, Span<float>.Empty, Span<float>.Empty, 0F); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Converts one packed source row into normalized planar components.
|
||||
|
/// </summary>
|
||||
|
/// <param name="y">The source row index.</param>
|
||||
|
/// <param name="packed">The high-bit-depth RGB staging row.</param>
|
||||
|
/// <param name="luma">The destination luma or first component values.</param>
|
||||
|
/// <param name="chromaBlue">The destination blue-difference or second component values.</param>
|
||||
|
/// <param name="chromaRed">The destination red-difference or third component values.</param>
|
||||
|
private void ConvertSourceRow(int y, Span<Rgb48> packed, Span<float> luma, Span<float> chromaBlue, Span<float> chromaRed) |
||||
|
{ |
||||
|
ReadOnlySpan<TPixel> source = this.image.PixelBuffer.DangerousGetRowSpan(y); |
||||
|
if (this.UsesByteInput) |
||||
|
{ |
||||
|
// JPEG's planar unpack contract reaches the existing pixel-specific SIMD implementation before
|
||||
|
// the closed H.273 operator transforms the component rows in place.
|
||||
|
PixelOperations<TPixel>.Instance.UnpackIntoRgbPlanes(luma, chromaBlue, chromaRed, source); |
||||
|
this.colorConverter.ConvertFromRgbInPlace(luma, chromaBlue, chromaRed, ByteMaximum); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToRgb48(this.configuration, source, packed); |
||||
|
DeinterleaveRgb48(packed, luma, chromaBlue, chromaRed); |
||||
|
this.colorConverter.ConvertFromRgbInPlace(luma, chromaBlue, chromaRed, UShortMaximum); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Filters and writes one pair of chroma component rows.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destinationY">The destination chroma row.</param>
|
||||
|
/// <param name="blue0">The first blue-difference source row.</param>
|
||||
|
/// <param name="red0">The first red-difference source row.</param>
|
||||
|
/// <param name="blue1">The optional second blue-difference source row.</param>
|
||||
|
/// <param name="red1">The optional second red-difference source row.</param>
|
||||
|
/// <param name="row1Weight">The second-row contribution.</param>
|
||||
|
private void WriteChromaRows( |
||||
|
int destinationY, |
||||
|
ReadOnlySpan<float> blue0, |
||||
|
ReadOnlySpan<float> red0, |
||||
|
ReadOnlySpan<float> blue1, |
||||
|
ReadOnlySpan<float> red1, |
||||
|
float row1Weight) |
||||
|
{ |
||||
|
Span<TSample> blueDestination = this.buffer.GetChromaBlueRowSpan(destinationY); |
||||
|
Span<TSample> redDestination = this.buffer.GetChromaRedRowSpan(destinationY); |
||||
|
if (this.subsamplingX == 0) |
||||
|
{ |
||||
|
WriteSamples<TSample, TStorer>( |
||||
|
blue0, |
||||
|
blueDestination, |
||||
|
this.colorConverter.ChromaScale, |
||||
|
this.colorConverter.ChromaBias, |
||||
|
this.chromaMaximum); |
||||
|
|
||||
|
WriteSamples<TSample, TStorer>( |
||||
|
red0, |
||||
|
redDestination, |
||||
|
this.colorConverter.ChromaScale, |
||||
|
this.colorConverter.ChromaBias, |
||||
|
this.chromaMaximum); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
bool isCenteredX = this.chromaPositionX == 1; |
||||
|
WriteSubsampledSamples<TSample, TStorer>( |
||||
|
blue0, |
||||
|
blue1, |
||||
|
blueDestination, |
||||
|
isCenteredX, |
||||
|
row1Weight, |
||||
|
this.colorConverter.ChromaScale, |
||||
|
this.colorConverter.ChromaBias, |
||||
|
this.chromaMaximum); |
||||
|
|
||||
|
WriteSubsampledSamples<TSample, TStorer>( |
||||
|
red0, |
||||
|
red1, |
||||
|
redDestination, |
||||
|
isCenteredX, |
||||
|
row1Weight, |
||||
|
this.colorConverter.ChromaScale, |
||||
|
this.colorConverter.ChromaBias, |
||||
|
this.chromaMaximum); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Color; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Exposes the native planar sample layout shared by HEIF image codecs.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TSample">The native unsigned sample storage type.</typeparam>
|
||||
|
internal interface IHeifPlanarSampleBuffer<TSample> |
||||
|
where TSample : unmanaged |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the full luma-plane width in samples.
|
||||
|
/// </summary>
|
||||
|
public int Width { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the full luma-plane height in samples.
|
||||
|
/// </summary>
|
||||
|
public int Height { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the luma sample precision in bits.
|
||||
|
/// </summary>
|
||||
|
public int LumaBitDepth { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the chroma sample precision in bits.
|
||||
|
/// </summary>
|
||||
|
public int ChromaBitDepth { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the buffer contains only the luma plane.
|
||||
|
/// </summary>
|
||||
|
public bool IsMonochrome { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the horizontal chroma subsampling shift.
|
||||
|
/// </summary>
|
||||
|
public int ChromaSubsamplingX { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the vertical chroma subsampling shift.
|
||||
|
/// </summary>
|
||||
|
public int ChromaSubsamplingY { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the horizontal chroma position in half-luma-sample units.
|
||||
|
/// </summary>
|
||||
|
public int ChromaPositionX { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the vertical chroma position in half-luma-sample units.
|
||||
|
/// </summary>
|
||||
|
public int ChromaPositionY { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets one complete writable row from the luma, green, or intensity plane.
|
||||
|
/// </summary>
|
||||
|
/// <param name="row">The zero-based row index in plane samples.</param>
|
||||
|
/// <returns>The native luma, green, or intensity samples.</returns>
|
||||
|
public Span<TSample> GetLumaRowSpan(int row); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets one complete writable row from the blue-difference, blue, or first opponent-color plane.
|
||||
|
/// </summary>
|
||||
|
/// <param name="row">The zero-based row index in plane samples.</param>
|
||||
|
/// <returns>The native blue-difference, blue, or first opponent-color samples.</returns>
|
||||
|
public Span<TSample> GetChromaBlueRowSpan(int row); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets one complete writable row from the red-difference, red, or second opponent-color plane.
|
||||
|
/// </summary>
|
||||
|
/// <param name="row">The zero-based row index in plane samples.</param>
|
||||
|
/// <returns>The native red-difference, red, or second opponent-color samples.</returns>
|
||||
|
public Span<TSample> GetChromaRedRowSpan(int row); |
||||
|
} |
||||
@ -1,31 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Color; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Exposes reconstructed eight-bit 4:2:0 component rows to the shared fixed-point HEIF converter.
|
|
||||
/// </summary>
|
|
||||
internal interface IHeifYuv420ToRgb8Source |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Gets one complete luma row in native sample storage.
|
|
||||
/// </summary>
|
|
||||
/// <param name="row">The zero-based luma row.</param>
|
|
||||
/// <returns>The reconstructed luma samples.</returns>
|
|
||||
public ReadOnlySpan<ushort> GetLumaRow(int row); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets one complete blue-difference chroma row in native sample storage.
|
|
||||
/// </summary>
|
|
||||
/// <param name="row">The zero-based chroma row.</param>
|
|
||||
/// <returns>The reconstructed blue-difference samples.</returns>
|
|
||||
public ReadOnlySpan<ushort> GetChromaBlueRow(int row); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets one complete red-difference chroma row in native sample storage.
|
|
||||
/// </summary>
|
|
||||
/// <param name="row">The zero-based chroma row.</param>
|
|
||||
/// <returns>The reconstructed red-difference samples.</returns>
|
|
||||
public ReadOnlySpan<ushort> GetChromaRedRow(int row); |
|
||||
} |
|
||||
@ -0,0 +1,81 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Color; |
||||
|
|
||||
|
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,32 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using SixLabors.ImageSharp.Formats.Heif.Color; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc.Color; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Adapts reconstructed HEVC planes to the shared fixed-point HEIF color converter.
|
|
||||
/// </summary>
|
|
||||
internal readonly struct HevcYuv420ToRgb8Source : IHeifYuv420ToRgb8Source |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The reconstructed HEVC picture containing the component planes.
|
|
||||
/// </summary>
|
|
||||
private readonly HevcPictureBuffer picture; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="HevcYuv420ToRgb8Source"/> struct.
|
|
||||
/// </summary>
|
|
||||
/// <param name="picture">The reconstructed HEVC picture.</param>
|
|
||||
public HevcYuv420ToRgb8Source(HevcPictureBuffer picture) => this.picture = picture; |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public ReadOnlySpan<ushort> GetLumaRow(int row) => this.picture.GetRowSpan(HevcPlane.Y, row); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public ReadOnlySpan<ushort> GetChromaBlueRow(int row) => this.picture.GetRowSpan(HevcPlane.Cb, row); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public ReadOnlySpan<ushort> GetChromaRedRow(int row) => this.picture.GetRowSpan(HevcPlane.Cr, row); |
|
||||
} |
|
||||
Loading…
Reference in new issue