mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
13 KiB
247 lines
13 KiB
// Copyright (c) Six Labors.
|
|
// Licensed under the Six Labors Split License.
|
|
|
|
using System.Buffers;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using SixLabors.ImageSharp.Formats;
|
|
using SixLabors.ImageSharp.Memory;
|
|
|
|
namespace SixLabors.ImageSharp.PixelFormats;
|
|
|
|
/// <summary>
|
|
/// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations
|
|
/// for pixel buffers of type <typeparamref name="TPixel"/>.
|
|
/// </summary>
|
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
public partial class PixelOperations<TPixel>
|
|
where TPixel : unmanaged, IPixel<TPixel>
|
|
{
|
|
private static readonly Lazy<PixelTypeInfo> LazyInfo = new(() => PixelTypeInfo.Create<TPixel>(), true);
|
|
private static readonly Lazy<PixelOperations<TPixel>> LazyInstance = new(() => default(TPixel).CreatePixelOperations(), true);
|
|
|
|
/// <summary>
|
|
/// Gets the global <see cref="PixelOperations{TPixel}"/> instance for the pixel type <typeparamref name="TPixel"/>
|
|
/// </summary>
|
|
#pragma warning disable CA1000 // Do not declare static members on generic types
|
|
public static PixelOperations<TPixel> Instance => LazyInstance.Value;
|
|
#pragma warning restore CA1000 // Do not declare static members on generic types
|
|
|
|
/// <summary>
|
|
/// Gets the pixel type info for the given <typeparamref name="TPixel"/>.
|
|
/// </summary>
|
|
/// <returns>The <see cref="PixelTypeInfo"/>.</returns>
|
|
public virtual PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value;
|
|
|
|
/// <summary>
|
|
/// Bulk version of <see cref="IPixel.FromVector4"/> converting 'sourceVectors.Length' pixels into 'destinationColors'.
|
|
/// The method is DESTRUCTIVE altering the contents of <paramref name="sourceVectors"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The destructive behavior is a design choice for performance reasons.
|
|
/// In a typical use case the contents of <paramref name="sourceVectors"/> are abandoned after the conversion.
|
|
/// </remarks>
|
|
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations</param>
|
|
/// <param name="sourceVectors">The <see cref="Span{T}"/> to the source vectors.</param>
|
|
/// <param name="destinationPixels">The <see cref="Span{T}"/> to the destination colors.</param>
|
|
/// <param name="modifiers">The <see cref="PixelConversionModifiers"/> to apply during the conversion</param>
|
|
public virtual void FromVector4Destructive(
|
|
Configuration configuration,
|
|
Span<Vector4> sourceVectors,
|
|
Span<TPixel> destinationPixels,
|
|
PixelConversionModifiers modifiers)
|
|
{
|
|
Guard.NotNull(configuration, nameof(configuration));
|
|
|
|
Utils.Vector4Converters.Default.FromVector4(sourceVectors, destinationPixels, modifiers);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulk version of <see cref="IPixel.FromVector4"/> converting 'sourceVectors.Length' pixels into 'destinationColors'.
|
|
/// The method is DESTRUCTIVE altering the contents of <paramref name="sourceVectors"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The destructive behavior is a design choice for performance reasons.
|
|
/// In a typical use case the contents of <paramref name="sourceVectors"/> are abandoned after the conversion.
|
|
/// </remarks>
|
|
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations</param>
|
|
/// <param name="sourceVectors">The <see cref="Span{T}"/> to the source vectors.</param>
|
|
/// <param name="destinationPixels">The <see cref="Span{T}"/> to the destination colors.</param>
|
|
public void FromVector4Destructive(
|
|
Configuration configuration,
|
|
Span<Vector4> sourceVectors,
|
|
Span<TPixel> destinationPixels)
|
|
=> this.FromVector4Destructive(configuration, sourceVectors, destinationPixels, PixelConversionModifiers.None);
|
|
|
|
/// <summary>
|
|
/// Bulk version of <see cref="IPixel.ToVector4()"/> converting 'sourceColors.Length' pixels into 'destinationVectors'.
|
|
/// </summary>
|
|
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations</param>
|
|
/// <param name="sourcePixels">The <see cref="Span{T}"/> to the source colors.</param>
|
|
/// <param name="destinationVectors">The <see cref="Span{T}"/> to the destination vectors.</param>
|
|
/// <param name="modifiers">The <see cref="PixelConversionModifiers"/> to apply during the conversion</param>
|
|
public virtual void ToVector4(
|
|
Configuration configuration,
|
|
ReadOnlySpan<TPixel> sourcePixels,
|
|
Span<Vector4> destinationVectors,
|
|
PixelConversionModifiers modifiers)
|
|
{
|
|
Guard.NotNull(configuration, nameof(configuration));
|
|
|
|
Utils.Vector4Converters.Default.ToVector4(sourcePixels, destinationVectors, modifiers);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulk version of <see cref="IPixel.ToVector4()"/> converting 'sourceColors.Length' pixels into 'destinationVectors'.
|
|
/// </summary>
|
|
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations</param>
|
|
/// <param name="sourcePixels">The <see cref="Span{T}"/> to the source colors.</param>
|
|
/// <param name="destinationVectors">The <see cref="Span{T}"/> to the destination vectors.</param>
|
|
public void ToVector4(
|
|
Configuration configuration,
|
|
ReadOnlySpan<TPixel> sourcePixels,
|
|
Span<Vector4> destinationVectors)
|
|
=> this.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.None);
|
|
|
|
/// <summary>
|
|
/// Bulk operation that copies the <paramref name="sourcePixels"/> to <paramref name="destinationPixels"/> in
|
|
/// <typeparamref name="TSourcePixel"/> format.
|
|
/// </summary>
|
|
/// <typeparam name="TSourcePixel">The destination pixel type.</typeparam>
|
|
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations.</param>
|
|
/// <param name="sourcePixels">The <see cref="ReadOnlySpan{TSourcePixel}"/> to the source pixels.</param>
|
|
/// <param name="destinationPixels">The <see cref="Span{TPixel}"/> to the destination pixels.</param>
|
|
public virtual void From<TSourcePixel>(
|
|
Configuration configuration,
|
|
ReadOnlySpan<TSourcePixel> sourcePixels,
|
|
Span<TPixel> destinationPixels)
|
|
where TSourcePixel : unmanaged, IPixel<TSourcePixel>
|
|
{
|
|
const int sliceLength = 1024;
|
|
int numberOfSlices = sourcePixels.Length / sliceLength;
|
|
|
|
using IMemoryOwner<Vector4> tempVectors = configuration.MemoryAllocator.Allocate<Vector4>(sliceLength);
|
|
Span<Vector4> vectorSpan = tempVectors.GetSpan();
|
|
for (int i = 0; i < numberOfSlices; i++)
|
|
{
|
|
int start = i * sliceLength;
|
|
ReadOnlySpan<TSourcePixel> s = sourcePixels.Slice(start, sliceLength);
|
|
Span<TPixel> d = destinationPixels.Slice(start, sliceLength);
|
|
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale);
|
|
this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale);
|
|
}
|
|
|
|
int endOfCompleteSlices = numberOfSlices * sliceLength;
|
|
int remainder = sourcePixels.Length - endOfCompleteSlices;
|
|
if (remainder > 0)
|
|
{
|
|
ReadOnlySpan<TSourcePixel> s = sourcePixels[endOfCompleteSlices..];
|
|
Span<TPixel> d = destinationPixels[endOfCompleteSlices..];
|
|
vectorSpan = vectorSpan[..remainder];
|
|
PixelOperations<TSourcePixel>.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale);
|
|
this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulk operation that copies the <paramref name="sourcePixels"/> to <paramref name="destinationPixels"/> in
|
|
/// <typeparamref name="TDestinationPixel"/> format.
|
|
/// </summary>
|
|
/// <typeparam name="TDestinationPixel">The destination pixel type.</typeparam>
|
|
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations.</param>
|
|
/// <param name="sourcePixels">The <see cref="ReadOnlySpan{TPixel}"/> to the source pixels.</param>
|
|
/// <param name="destinationPixels">The <see cref="Span{TDestinationPixel}"/> to the destination pixels.</param>
|
|
public virtual void To<TDestinationPixel>(
|
|
Configuration configuration,
|
|
ReadOnlySpan<TPixel> sourcePixels,
|
|
Span<TDestinationPixel> destinationPixels)
|
|
where TDestinationPixel : unmanaged, IPixel<TDestinationPixel>
|
|
{
|
|
Guard.NotNull(configuration, nameof(configuration));
|
|
Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels));
|
|
|
|
PixelOperations<TDestinationPixel>.Instance.From(configuration, sourcePixels, destinationPixels);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulk operation that packs 3 separate RGB channels to <paramref name="destination"/>.
|
|
/// The destination must have a padding of 3.
|
|
/// </summary>
|
|
/// <param name="redChannel">A <see cref="ReadOnlySpan{T}"/> to the red values.</param>
|
|
/// <param name="greenChannel">A <see cref="ReadOnlySpan{T}"/> to the green values.</param>
|
|
/// <param name="blueChannel">A <see cref="ReadOnlySpan{T}"/> to the blue values.</param>
|
|
/// <param name="destination">A <see cref="Span{T}"/> to the destination pixels.</param>
|
|
internal virtual void PackFromRgbPlanes(
|
|
ReadOnlySpan<byte> redChannel,
|
|
ReadOnlySpan<byte> greenChannel,
|
|
ReadOnlySpan<byte> blueChannel,
|
|
Span<TPixel> destination)
|
|
{
|
|
int count = redChannel.Length;
|
|
GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count);
|
|
|
|
Rgb24 rgb24 = default;
|
|
ref byte r = ref MemoryMarshal.GetReference(redChannel);
|
|
ref byte g = ref MemoryMarshal.GetReference(greenChannel);
|
|
ref byte b = ref MemoryMarshal.GetReference(blueChannel);
|
|
ref TPixel d = ref MemoryMarshal.GetReference(destination);
|
|
|
|
for (nuint i = 0; i < (uint)count; i++)
|
|
{
|
|
rgb24.R = Unsafe.Add(ref r, i);
|
|
rgb24.G = Unsafe.Add(ref g, i);
|
|
rgb24.B = Unsafe.Add(ref b, i);
|
|
Unsafe.Add(ref d, i).FromRgb24(rgb24);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bulk operation that unpacks pixels from <paramref name="source"/>
|
|
/// into 3 separate RGB channels.
|
|
/// </summary>
|
|
/// <param name="redChannel">A <see cref="ReadOnlySpan{T}"/> to the red values.</param>
|
|
/// <param name="greenChannel">A <see cref="ReadOnlySpan{T}"/> to the green values.</param>
|
|
/// <param name="blueChannel">A <see cref="ReadOnlySpan{T}"/> to the blue values.</param>
|
|
/// <param name="source">A <see cref="Span{T}"/> to the destination pixels.</param>
|
|
internal virtual void UnpackIntoRgbPlanes(
|
|
Span<float> redChannel,
|
|
Span<float> greenChannel,
|
|
Span<float> blueChannel,
|
|
ReadOnlySpan<TPixel> source)
|
|
{
|
|
GuardUnpackIntoRgbPlanes(redChannel, greenChannel, blueChannel, source);
|
|
|
|
int count = source.Length;
|
|
|
|
Rgba32 rgba32 = default;
|
|
|
|
ref float r = ref MemoryMarshal.GetReference(redChannel);
|
|
ref float g = ref MemoryMarshal.GetReference(greenChannel);
|
|
ref float b = ref MemoryMarshal.GetReference(blueChannel);
|
|
ref TPixel src = ref MemoryMarshal.GetReference(source);
|
|
for (nuint i = 0; i < (uint)count; i++)
|
|
{
|
|
Unsafe.Add(ref src, i).ToRgba32(ref rgba32);
|
|
Unsafe.Add(ref r, i) = rgba32.R;
|
|
Unsafe.Add(ref g, i) = rgba32.G;
|
|
Unsafe.Add(ref b, i) = rgba32.B;
|
|
}
|
|
}
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)]
|
|
internal static void GuardUnpackIntoRgbPlanes(Span<float> redChannel, Span<float> greenChannel, Span<float> blueChannel, ReadOnlySpan<TPixel> source)
|
|
{
|
|
Guard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!");
|
|
Guard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!");
|
|
Guard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!");
|
|
}
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)]
|
|
internal static void GuardPackFromRgbPlanes(ReadOnlySpan<byte> greenChannel, ReadOnlySpan<byte> blueChannel, Span<TPixel> destination, int count)
|
|
{
|
|
Guard.IsTrue(greenChannel.Length == count, nameof(greenChannel), "Channels must be of same size!");
|
|
Guard.IsTrue(blueChannel.Length == count, nameof(blueChannel), "Channels must be of same size!");
|
|
Guard.IsTrue(destination.Length > count + 2, nameof(destination), "'destination' must contain a padding of 3 elements!");
|
|
}
|
|
}
|
|
|