mirror of https://github.com/SixLabors/ImageSharp
61 changed files with 6387 additions and 589 deletions
@ -0,0 +1,173 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Numerics.Tensors; |
|||
using SixLabors.ImageSharp.Formats.Heif.Components; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Components.Alpha; |
|||
|
|||
/// <summary>
|
|||
/// Composes a reconstructed HEIF luma plane directly into the alpha channel of a packed destination frame.
|
|||
/// </summary>
|
|||
internal static class HeifPlanarAlphaCompositor |
|||
{ |
|||
/// <summary>
|
|||
/// Composes a native codec luma plane into a destination image region without materializing an intermediate image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
|
|||
/// <typeparam name="TBuffer">The codec adapter exposing the reconstructed 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 pooled allocation and pixel conversion.</param>
|
|||
/// <param name="buffer">The native reconstructed component planes.</param>
|
|||
/// <param name="destination">The packed destination frame receiving alpha values.</param>
|
|||
/// <param name="parameters">The resolved H.273 component-range parameters.</param>
|
|||
/// <param name="sourceRectangle">The visible luma rectangle within the reconstructed plane.</param>
|
|||
/// <param name="outputSize">The complete presented size of the auxiliary image or grid tile.</param>
|
|||
/// <param name="destinationRectangle">The destination region receiving the top-left portion of the presented alpha image.</param>
|
|||
/// <param name="premultiplied">Whether stored color samples must be converted to unassociated alpha.</param>
|
|||
public static void Compose<TPixel, TBuffer, TSample, TLoader>( |
|||
Configuration configuration, |
|||
TBuffer buffer, |
|||
ImageFrame<TPixel> destination, |
|||
in HeifColorConversionParameters parameters, |
|||
Rectangle sourceRectangle, |
|||
Size outputSize, |
|||
Rectangle destinationRectangle, |
|||
bool premultiplied) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
where TBuffer : struct, IHeifPlanarSampleBuffer<TSample> |
|||
where TSample : unmanaged |
|||
where TLoader : struct, IHeifSampleConverter<TSample> |
|||
{ |
|||
int sourceWidth = sourceRectangle.Width; |
|||
int sourceHeight = sourceRectangle.Height; |
|||
int outputWidth = outputSize.Width; |
|||
int outputHeight = outputSize.Height; |
|||
int composedWidth = destinationRectangle.Width; |
|||
int composedHeight = destinationRectangle.Height; |
|||
|
|||
if (sourceWidth == outputWidth && sourceHeight == outputHeight) |
|||
{ |
|||
using IMemoryOwner<float> componentOwner = configuration.MemoryAllocator.Allocate<float>(composedWidth); |
|||
using IMemoryOwner<L16> alphaOwner = configuration.MemoryAllocator.Allocate<L16>(composedWidth); |
|||
using IMemoryOwner<Rgba64> colorOwner = configuration.MemoryAllocator.Allocate<Rgba64>(composedWidth); |
|||
Span<float> alpha = componentOwner.GetSpan()[..composedWidth]; |
|||
Span<L16> packedAlpha = alphaOwner.GetSpan()[..composedWidth]; |
|||
Span<Rgba64> packedColor = colorOwner.GetSpan()[..composedWidth]; |
|||
|
|||
// The overwhelmingly common path reads the codec plane once and immediately packs the corresponding
|
|||
// destination row. No resize maps or full-plane staging are required.
|
|||
for (int y = 0; y < composedHeight; y++) |
|||
{ |
|||
ReadOnlySpan<TSample> source = buffer.GetLumaRowSpan(sourceRectangle.Y + y).Slice(sourceRectangle.X, composedWidth); |
|||
NormalizeAlphaRow<TSample, TLoader>(source, alpha, in parameters); |
|||
ApplyAlphaRow(configuration, destination, destinationRectangle.X, destinationRectangle.Y + y, alpha, packedAlpha, packedColor, premultiplied); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
// Alpha scaling must match KnownResamplers.Box. That public instance is exposed as IResampler, while
|
|||
// ResizeKernelMap requires the concrete struct so Radius and GetValue remain statically dispatched.
|
|||
// BoxResampler is stateless, making its default value behaviorally identical to the known instance.
|
|||
BoxResampler boxResampler = default; |
|||
using ResizeKernelMap horizontalKernels = ResizeKernelMap.Calculate(in boxResampler, outputWidth, sourceWidth, configuration.MemoryAllocator); |
|||
using ResizeKernelMap verticalKernels = ResizeKernelMap.Calculate(in boxResampler, outputHeight, sourceHeight, configuration.MemoryAllocator); |
|||
using HeifPlanarAlphaResizeWorker<TPixel, TBuffer, TSample, TLoader> worker = new( |
|||
configuration, |
|||
buffer, |
|||
destination, |
|||
in parameters, |
|||
sourceRectangle, |
|||
destinationRectangle, |
|||
horizontalKernels, |
|||
verticalKernels, |
|||
premultiplied); |
|||
|
|||
worker.Compose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Widens and normalizes one native luma row to unbounded alpha values before packing or resampling.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSample">The native unsigned sample storage type.</typeparam>
|
|||
/// <typeparam name="TLoader">The SIMD widening operations for the sample type.</typeparam>
|
|||
/// <param name="source">The native luma samples.</param>
|
|||
/// <param name="destination">The normalized alpha samples.</param>
|
|||
/// <param name="parameters">The resolved H.273 component-range parameters.</param>
|
|||
public static void NormalizeAlphaRow<TSample, TLoader>( |
|||
ReadOnlySpan<TSample> source, |
|||
Span<float> destination, |
|||
in HeifColorConversionParameters parameters) |
|||
where TSample : unmanaged |
|||
where TLoader : struct, IHeifSampleConverter<TSample> |
|||
{ |
|||
HeifSampleConversion.ConvertSamplesToFloat<TSample, TLoader>(source, destination); |
|||
|
|||
// Alpha auxiliaries use the luma code-value range but no color matrix. TensorPrimitives keeps this bulk
|
|||
// normalization SIMD-first on every supported architecture and clamps before resampling, matching the
|
|||
// established conversion to a bounded L16 plane.
|
|||
TensorPrimitives.Subtract(destination, parameters.LumaBias, destination); |
|||
TensorPrimitives.Multiply(destination, 1F / parameters.LumaScale, destination); |
|||
TensorPrimitives.Clamp(destination, 0F, 1F, destination); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Packs and composes one normalized alpha row into the destination frame.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
|
|||
/// <param name="configuration">The configuration used for pixel conversion.</param>
|
|||
/// <param name="destination">The packed destination frame receiving alpha values.</param>
|
|||
/// <param name="destinationX">The horizontal start of the destination region.</param>
|
|||
/// <param name="destinationY">The destination row receiving alpha values.</param>
|
|||
/// <param name="alpha">The normalized alpha samples.</param>
|
|||
/// <param name="packedAlpha">The reusable 16-bit alpha packing row.</param>
|
|||
/// <param name="packedColor">The reusable high-bit-depth destination color row.</param>
|
|||
/// <param name="premultiplied">Whether stored color samples must be converted to unassociated alpha.</param>
|
|||
public static void ApplyAlphaRow<TPixel>( |
|||
Configuration configuration, |
|||
ImageFrame<TPixel> destination, |
|||
int destinationX, |
|||
int destinationY, |
|||
ReadOnlySpan<float> alpha, |
|||
Span<L16> packedAlpha, |
|||
Span<Rgba64> packedColor, |
|||
bool premultiplied) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
int width = alpha.Length; |
|||
Span<TPixel> destinationRow = destination.PixelBuffer.DangerousGetRowSpan(destinationY).Slice(destinationX, width); |
|||
PixelOperations<TPixel> pixelOperations = PixelOperations<TPixel>.Instance; |
|||
|
|||
HeifSampleConversion.PackL16(alpha, packedAlpha); |
|||
pixelOperations.ToRgba64(configuration, destinationRow, packedColor); |
|||
if (premultiplied) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
Rgba64 pixel = packedColor[x]; |
|||
pixel.A = packedAlpha[x].PackedValue; |
|||
|
|||
// Transparent associated samples have no recoverable color. Nonzero samples use the pixel type's
|
|||
// established conversion so unassociation retains ImageSharp's clamping and rounding behavior.
|
|||
packedColor[x] = pixel.A == 0 |
|||
? new Rgba64(0, 0, 0, 0) |
|||
: Rgba64.FromAssociatedScaledVector4(pixel.ToScaledVector4()); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
packedColor[x].A = packedAlpha[x].PackedValue; |
|||
} |
|||
} |
|||
|
|||
pixelOperations.FromRgba64(configuration, packedColor, destinationRow); |
|||
} |
|||
} |
|||
@ -0,0 +1,323 @@ |
|||
// 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.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing.Processors.Transforms; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Components.Alpha; |
|||
|
|||
/// <summary>
|
|||
/// Resizes a native HEIF luma plane and composes the result as alpha using a bounded sliding window.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
|
|||
/// <typeparam name="TBuffer">The codec adapter exposing the reconstructed 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>
|
|||
internal sealed class HeifPlanarAlphaResizeWorker<TPixel, TBuffer, TSample, TLoader> : IDisposable |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
where TBuffer : struct, IHeifPlanarSampleBuffer<TSample> |
|||
where TSample : unmanaged |
|||
where TLoader : struct, IHeifSampleConverter<TSample> |
|||
{ |
|||
/// <summary>
|
|||
/// The configuration used for pooled allocation and pixel conversion.
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// The codec-native component planes.
|
|||
/// </summary>
|
|||
private readonly TBuffer buffer; |
|||
|
|||
/// <summary>
|
|||
/// The packed color frame receiving alpha values.
|
|||
/// </summary>
|
|||
private readonly ImageFrame<TPixel> destination; |
|||
|
|||
/// <summary>
|
|||
/// The resolved H.273 component-range parameters.
|
|||
/// </summary>
|
|||
private readonly HeifColorConversionParameters parameters; |
|||
|
|||
/// <summary>
|
|||
/// The visible luma rectangle within the reconstructed plane.
|
|||
/// </summary>
|
|||
private readonly Rectangle sourceRectangle; |
|||
|
|||
/// <summary>
|
|||
/// The destination region receiving the resized alpha plane.
|
|||
/// </summary>
|
|||
private readonly Rectangle destinationRectangle; |
|||
|
|||
/// <summary>
|
|||
/// The horizontal box-filter kernels for the full presented width.
|
|||
/// </summary>
|
|||
private readonly ResizeKernelMap horizontalKernels; |
|||
|
|||
/// <summary>
|
|||
/// The vertical box-filter kernels for the full presented height.
|
|||
/// </summary>
|
|||
private readonly ResizeKernelMap verticalKernels; |
|||
|
|||
/// <summary>
|
|||
/// The transposed horizontally filtered rows retained by the sliding window.
|
|||
/// </summary>
|
|||
private readonly Buffer2D<Vector4> transposedFirstPassBuffer; |
|||
|
|||
/// <summary>
|
|||
/// The reusable normalized source or resized destination row.
|
|||
/// </summary>
|
|||
private readonly IMemoryOwner<float> componentOwner; |
|||
|
|||
/// <summary>
|
|||
/// The reusable replicated source row consumed by the shared resize kernels.
|
|||
/// </summary>
|
|||
private readonly IMemoryOwner<Vector4> sourceVectorOwner; |
|||
|
|||
/// <summary>
|
|||
/// The reusable 16-bit source and destination alpha packing row.
|
|||
/// </summary>
|
|||
private readonly IMemoryOwner<L16> alphaOwner; |
|||
|
|||
/// <summary>
|
|||
/// The reusable high-bit-depth destination color row.
|
|||
/// </summary>
|
|||
private readonly IMemoryOwner<Rgba64> colorOwner; |
|||
|
|||
/// <summary>
|
|||
/// Whether stored color samples must be converted to unassociated alpha.
|
|||
/// </summary>
|
|||
private readonly bool premultiplied; |
|||
|
|||
/// <summary>
|
|||
/// The number of source rows retained when the window advances.
|
|||
/// </summary>
|
|||
private readonly int windowBandHeight; |
|||
|
|||
/// <summary>
|
|||
/// The total number of source rows retained by the bounded working window.
|
|||
/// </summary>
|
|||
private readonly int workerHeight; |
|||
|
|||
/// <summary>
|
|||
/// The source-row interval currently represented by the transposed first-pass buffer.
|
|||
/// </summary>
|
|||
private RowInterval currentWindow; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HeifPlanarAlphaResizeWorker{TPixel, TBuffer, TSample, TLoader}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration used for pooled allocation and pixel conversion.</param>
|
|||
/// <param name="buffer">The codec-native component planes.</param>
|
|||
/// <param name="destination">The packed color frame receiving alpha values.</param>
|
|||
/// <param name="parameters">The resolved H.273 component-range parameters.</param>
|
|||
/// <param name="sourceRectangle">The visible luma rectangle within the reconstructed plane.</param>
|
|||
/// <param name="destinationRectangle">The destination region receiving the top-left portion of the presented alpha plane.</param>
|
|||
/// <param name="horizontalKernels">The horizontal box-filter kernels for the full presented width.</param>
|
|||
/// <param name="verticalKernels">The vertical box-filter kernels for the full presented height.</param>
|
|||
/// <param name="premultiplied">Whether stored color samples must be converted to unassociated alpha.</param>
|
|||
public HeifPlanarAlphaResizeWorker( |
|||
Configuration configuration, |
|||
TBuffer buffer, |
|||
ImageFrame<TPixel> destination, |
|||
in HeifColorConversionParameters parameters, |
|||
Rectangle sourceRectangle, |
|||
Rectangle destinationRectangle, |
|||
ResizeKernelMap horizontalKernels, |
|||
ResizeKernelMap verticalKernels, |
|||
bool premultiplied) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.buffer = buffer; |
|||
this.destination = destination; |
|||
this.parameters = parameters; |
|||
this.sourceRectangle = sourceRectangle; |
|||
this.destinationRectangle = destinationRectangle; |
|||
this.premultiplied = premultiplied; |
|||
|
|||
this.horizontalKernels = horizontalKernels; |
|||
this.verticalKernels = verticalKernels; |
|||
|
|||
// Retaining one complete maximum-diameter band is sufficient for every vertical kernel that crosses a
|
|||
// window boundary. Those first-pass rows can be copied forward instead of normalized and filtered again.
|
|||
this.windowBandHeight = this.verticalKernels.MaxDiameter; |
|||
|
|||
// As in ResizeWorker, the first pass is stored transposed as [destination X][source Y]. Bounding the source-Y
|
|||
// dimension by the configured working-buffer limit keeps memory independent of the complete alpha-plane size.
|
|||
int workingBufferLimitInBytes = Math.Min( |
|||
configuration.WorkingBufferSizeHintInBytes, |
|||
configuration.MemoryAllocator.GetBufferCapacityInBytes()); |
|||
|
|||
int windowBandCount = ResizeHelper.CalculateResizeWorkerHeightInWindowBands( |
|||
this.windowBandHeight, |
|||
destinationRectangle.Width, |
|||
workingBufferLimitInBytes); |
|||
|
|||
// A whole number of bands lets Slide retain exactly one overlap band and fill the remaining window with rows
|
|||
// that have not entered the first pass before.
|
|||
this.workerHeight = Math.Min(sourceRectangle.Height, windowBandCount * this.windowBandHeight); |
|||
this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D<Vector4>( |
|||
this.workerHeight, |
|||
destinationRectangle.Width, |
|||
preferContiguosImageBuffers: true, |
|||
options: AllocationOptions.Clean); |
|||
|
|||
this.componentOwner = configuration.MemoryAllocator.Allocate<float>(Math.Max(sourceRectangle.Width, destinationRectangle.Width)); |
|||
this.sourceVectorOwner = configuration.MemoryAllocator.Allocate<Vector4>(sourceRectangle.Width); |
|||
this.alphaOwner = configuration.MemoryAllocator.Allocate<L16>(Math.Max(sourceRectangle.Width, destinationRectangle.Width)); |
|||
this.colorOwner = configuration.MemoryAllocator.Allocate<Rgba64>(destinationRectangle.Width); |
|||
this.currentWindow = new RowInterval(0, this.workerHeight); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Releases all allocator-owned working buffers.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.transposedFirstPassBuffer.Dispose(); |
|||
this.componentOwner.Dispose(); |
|||
this.sourceVectorOwner.Dispose(); |
|||
this.alphaOwner.Dispose(); |
|||
this.colorOwner.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resizes and composes the complete requested destination rectangle.
|
|||
/// </summary>
|
|||
public void Compose() |
|||
{ |
|||
// Populate the horizontal first pass for the initial bounded source-row interval. Later windows retain their
|
|||
// overlap and calculate only newly entering rows.
|
|||
this.CalculateFirstPassValues(this.currentWindow); |
|||
|
|||
Span<Vector4> transposed = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); |
|||
Span<float> resizedAlpha = this.componentOwner.GetSpan()[..this.destinationRectangle.Width]; |
|||
Span<L16> packedAlpha = this.alphaOwner.GetSpan()[..this.destinationRectangle.Width]; |
|||
Span<Rgba64> packedColor = this.colorOwner.GetSpan()[..this.destinationRectangle.Width]; |
|||
ReadOnlySpan<ResizeKernel> verticalKernelSpan = this.verticalKernels.GetKernelSpan(); |
|||
ref ResizeKernel verticalKernelBase = ref MemoryMarshal.GetReference(verticalKernelSpan); |
|||
ref float resizedAlphaBase = ref MemoryMarshal.GetReference(resizedAlpha); |
|||
int currentWindowMin = this.currentWindow.Min; |
|||
int currentWindowMax = this.currentWindow.Max; |
|||
nuint width = (uint)this.destinationRectangle.Width; |
|||
nuint workerHeight = (uint)this.workerHeight; |
|||
nuint twoWorkerHeights = workerHeight * 2; |
|||
|
|||
for (int y = 0; y < this.destinationRectangle.Height; y++) |
|||
{ |
|||
ref ResizeKernel kernel = ref Unsafe.Add(ref verticalKernelBase, y); |
|||
int kernelEnd = kernel.StartIndex + kernel.Length; |
|||
|
|||
// Destination kernels advance monotonically through source Y. Slide until the complete kernel lies in
|
|||
// the cached first-pass interval; the retained overlap prevents any shared source row being recalculated.
|
|||
while (kernelEnd > currentWindowMax) |
|||
{ |
|||
this.Slide(); |
|||
currentWindowMin = this.currentWindow.Min; |
|||
currentWindowMax = this.currentWindow.Max; |
|||
} |
|||
|
|||
// Values for one destination X are contiguous along source Y in the transposed buffer. ConvolveCore
|
|||
// therefore reads the vertical kernel without gathers, while workerHeight advances to the next X column.
|
|||
ref Vector4 column = ref transposed[kernel.StartIndex - currentWindowMin]; |
|||
nuint x = 0; |
|||
for (; x + 1 < width; x += 2) |
|||
{ |
|||
Unsafe.Add(ref resizedAlphaBase, x) = kernel.ConvolveCore(ref column).X; |
|||
ref Vector4 nextColumn = ref Unsafe.Add(ref column, workerHeight); |
|||
Unsafe.Add(ref resizedAlphaBase, x + 1) = kernel.ConvolveCore(ref nextColumn).X; |
|||
column = ref Unsafe.Add(ref column, twoWorkerHeights); |
|||
} |
|||
|
|||
if (x < width) |
|||
{ |
|||
Unsafe.Add(ref resizedAlphaBase, x) = kernel.ConvolveCore(ref column).X; |
|||
} |
|||
|
|||
HeifPlanarAlphaCompositor.ApplyAlphaRow( |
|||
this.configuration, |
|||
this.destination, |
|||
this.destinationRectangle.X, |
|||
this.destinationRectangle.Y + y, |
|||
resizedAlpha, |
|||
packedAlpha, |
|||
packedColor, |
|||
this.premultiplied); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Advances the bounded working window while preserving its overlapping source-row band.
|
|||
/// </summary>
|
|||
private void Slide() |
|||
{ |
|||
// The old bottom band is the only set of first-pass rows that a future kernel can share with the new window.
|
|||
// Its height equals the largest vertical-kernel diameter, covering the maximum possible overlap.
|
|||
int minimumY = this.currentWindow.Max - this.windowBandHeight; |
|||
int maximumY = Math.Min(minimumY + this.workerHeight, this.sourceRectangle.Height); |
|||
|
|||
// Buffer2D columns represent source Y because the first pass is transposed. Move the retained bottom band to
|
|||
// offset zero for every destination-X column before replacing the remainder of the window.
|
|||
this.transposedFirstPassBuffer.DangerousCopyColumns( |
|||
this.workerHeight - this.windowBandHeight, |
|||
0, |
|||
this.windowBandHeight); |
|||
|
|||
this.currentWindow = new RowInterval(minimumY, maximumY); |
|||
|
|||
// The retained band already contains normalized and horizontally filtered values. Only rows below it are new.
|
|||
this.CalculateFirstPassValues(this.currentWindow.Slice(this.windowBandHeight)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Normalizes and horizontally filters the source rows entering the current working window.
|
|||
/// </summary>
|
|||
/// <param name="interval">The source-row interval requiring first-pass values.</param>
|
|||
private void CalculateFirstPassValues(RowInterval interval) |
|||
{ |
|||
int sourceWidth = this.sourceRectangle.Width; |
|||
int destinationWidth = this.destinationRectangle.Width; |
|||
Span<float> normalized = this.componentOwner.GetSpan()[..sourceWidth]; |
|||
Span<L16> sourceAlpha = this.alphaOwner.GetSpan()[..sourceWidth]; |
|||
Span<Vector4> sourceVectors = this.sourceVectorOwner.GetSpan()[..sourceWidth]; |
|||
Span<Vector4> transposed = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); |
|||
ReadOnlySpan<ResizeKernel> horizontalKernelSpan = this.horizontalKernels.GetKernelSpan(); |
|||
ref ResizeKernel horizontalKernelBase = ref MemoryMarshal.GetReference(horizontalKernelSpan); |
|||
nuint workerHeight = (uint)this.workerHeight; |
|||
|
|||
for (int y = interval.Min; y < interval.Max; y++) |
|||
{ |
|||
ReadOnlySpan<TSample> source = this.buffer.GetLumaRowSpan(this.sourceRectangle.Y + y).Slice(this.sourceRectangle.X, sourceWidth); |
|||
HeifPlanarAlphaCompositor.NormalizeAlphaRow<TSample, TLoader>(source, normalized, in this.parameters); |
|||
|
|||
// ResizeKernel is the same SIMD convolution primitive used by the general image resizer. Replicating alpha
|
|||
// into Vector4 lets that kernel operate on the planar row, while the L16 round trip preserves the result of
|
|||
// the removed Image<L16> path without materializing the complete alpha image.
|
|||
HeifSampleConversion.PackL16(normalized, sourceAlpha); |
|||
PixelOperations<L16>.Instance.ToVector4(this.configuration, sourceAlpha, sourceVectors, PixelConversionModifiers.Scale); |
|||
|
|||
// The source row is horizontally filtered once for every destination X and stored at [X][window Y]. A
|
|||
// vertical kernel can then reuse this first-pass row wherever adjacent destination kernels overlap it.
|
|||
ref Vector4 firstPass = ref transposed[y - this.currentWindow.Min]; |
|||
int x = 0; |
|||
for (; x + 1 < destinationWidth; x += 2) |
|||
{ |
|||
ref ResizeKernel kernel0 = ref Unsafe.Add(ref horizontalKernelBase, x); |
|||
ref ResizeKernel kernel1 = ref Unsafe.Add(ref horizontalKernelBase, x + 1); |
|||
Unsafe.Add(ref firstPass, (nuint)x * workerHeight) = kernel0.Convolve(sourceVectors); |
|||
Unsafe.Add(ref firstPass, (nuint)(x + 1) * workerHeight) = kernel1.Convolve(sourceVectors); |
|||
} |
|||
|
|||
if (x < destinationWidth) |
|||
{ |
|||
ref ResizeKernel kernel = ref Unsafe.Add(ref horizontalKernelBase, x); |
|||
Unsafe.Add(ref firstPass, (nuint)x * workerHeight) = kernel.Convolve(sourceVectors); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Components.Alpha; |
|||
|
|||
/// <summary>
|
|||
/// Decodes one coded HEIF auxiliary alpha item directly into a packed color frame.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The destination color pixel type.</typeparam>
|
|||
internal interface IHeifAlphaItemDecoder<TPixel> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes and composes one coded auxiliary alpha item.
|
|||
/// </summary>
|
|||
/// <param name="options">The general options governing the containing HEIF decode.</param>
|
|||
/// <param name="item">The auxiliary image item whose encoded payload is being decoded.</param>
|
|||
/// <param name="data">The encoded auxiliary payload.</param>
|
|||
/// <param name="destination">The packed color frame receiving alpha values.</param>
|
|||
/// <param name="outputSize">The complete presented size of the auxiliary image or grid tile.</param>
|
|||
/// <param name="destinationRectangle">The destination region receiving the top-left portion of the presented alpha image.</param>
|
|||
/// <param name="premultiplied">Whether stored color samples must be converted to unassociated alpha.</param>
|
|||
/// <param name="cancellationToken">The token used to cancel the payload decode.</param>
|
|||
public void DecodeAlphaItemData( |
|||
DecoderOptions options, |
|||
HeifItem item, |
|||
Span<byte> data, |
|||
ImageFrame<TPixel> destination, |
|||
Size outputSize, |
|||
Rectangle destinationRectangle, |
|||
bool premultiplied, |
|||
CancellationToken cancellationToken); |
|||
} |
|||
@ -0,0 +1,167 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Components; |
|||
|
|||
/// <summary>
|
|||
/// Defines SIMD widening and narrowing operations for one native HEIF sample representation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSample">The native sample type.</typeparam>
|
|||
internal interface IHeifSampleConverter<TSample> |
|||
where TSample : unmanaged |
|||
{ |
|||
/// <summary>
|
|||
/// Loads and widens four samples to single-precision lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <returns>The widened samples.</returns>
|
|||
public static abstract Vector128<float> LoadVector128(ref TSample source); |
|||
|
|||
/// <summary>
|
|||
/// Loads and widens eight samples to single-precision lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <returns>The widened samples.</returns>
|
|||
public static abstract Vector256<float> LoadVector256(ref TSample source); |
|||
|
|||
/// <summary>
|
|||
/// Loads and widens sixteen samples to single-precision lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <returns>The widened samples.</returns>
|
|||
public static abstract Vector512<float> LoadVector512(ref TSample source); |
|||
|
|||
/// <summary>
|
|||
/// Narrows and stores four integer samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The integer samples.</param>
|
|||
/// <param name="destination">The first destination sample.</param>
|
|||
public static abstract void Store(Vector128<int> source, ref TSample destination); |
|||
|
|||
/// <summary>
|
|||
/// Narrows and stores eight integer samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The integer samples.</param>
|
|||
/// <param name="destination">The first destination sample.</param>
|
|||
public static abstract void Store(Vector256<int> source, ref TSample destination); |
|||
|
|||
/// <summary>
|
|||
/// Narrows and stores sixteen integer samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The integer samples.</param>
|
|||
/// <param name="destination">The first destination sample.</param>
|
|||
public static abstract void Store(Vector512<int> source, ref TSample destination); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts between eight-bit native samples and the planar conversion pipeline.
|
|||
/// </summary>
|
|||
internal readonly struct HeifByteSampleConverter : IHeifSampleConverter<byte> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> LoadVector128(ref byte source) |
|||
{ |
|||
uint packed = Unsafe.ReadUnaligned<uint>(ref source); |
|||
Vector128<ushort> samples16 = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()); |
|||
return Vector128.ConvertToSingle(Vector128.WidenLower(samples16)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> LoadVector256(ref byte source) |
|||
{ |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref source); |
|||
Vector128<ushort> samples16 = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()); |
|||
Vector256<uint> samples32 = Vector256.Create(Vector128.WidenLower(samples16), Vector128.WidenUpper(samples16)); |
|||
return Vector256.ConvertToSingle(samples32); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> LoadVector512(ref byte source) |
|||
{ |
|||
Vector128<byte> packed = Unsafe.ReadUnaligned<Vector128<byte>>(ref source); |
|||
(Vector128<ushort> lower16, Vector128<ushort> upper16) = Vector128.Widen(packed); |
|||
Vector256<uint> lower32 = Vector256.Create(Vector128.WidenLower(lower16), Vector128.WidenUpper(lower16)); |
|||
Vector256<uint> upper32 = Vector256.Create(Vector128.WidenLower(upper16), Vector128.WidenUpper(upper16)); |
|||
return Vector512.ConvertToSingle(Vector512.Create(lower32, upper32)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Store(Vector128<int> source, ref byte destination) |
|||
{ |
|||
Vector128<ushort> samples16 = Vector128.Narrow(source.AsUInt32(), Vector128<uint>.Zero); |
|||
Vector128<byte> samples8 = Vector128.Narrow(samples16, Vector128<ushort>.Zero); |
|||
|
|||
// The lower four bytes contain the four source lanes after the two narrowing stages.
|
|||
Unsafe.WriteUnaligned(ref destination, samples8.AsUInt32().ToScalar()); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Store(Vector256<int> source, ref byte destination) |
|||
{ |
|||
Store(source.GetLower(), ref destination); |
|||
Store(source.GetUpper(), ref Unsafe.Add(ref destination, Vector128<int>.Count)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Store(Vector512<int> source, ref byte destination) |
|||
{ |
|||
Store(source.GetLower(), ref destination); |
|||
Store(source.GetUpper(), ref Unsafe.Add(ref destination, Vector256<int>.Count)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts between unsigned 16-bit native samples and the planar conversion pipeline.
|
|||
/// </summary>
|
|||
internal readonly struct HeifUShortSampleConverter : IHeifSampleConverter<ushort> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> LoadVector128(ref ushort source) |
|||
{ |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<ushort, byte>(ref source)); |
|||
Vector128<ushort> samples16 = Vector128.CreateScalarUnsafe(packed).AsUInt16(); |
|||
return Vector128.ConvertToSingle(Vector128.WidenLower(samples16)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> LoadVector256(ref ushort source) |
|||
{ |
|||
Vector128<ushort> samples16 = Unsafe.ReadUnaligned<Vector128<ushort>>(ref Unsafe.As<ushort, byte>(ref source)); |
|||
Vector256<uint> samples32 = Vector256.Create(Vector128.WidenLower(samples16), Vector128.WidenUpper(samples16)); |
|||
return Vector256.ConvertToSingle(samples32); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> LoadVector512(ref ushort source) |
|||
{ |
|||
Vector256<ushort> samples16 = Unsafe.ReadUnaligned<Vector256<ushort>>(ref Unsafe.As<ushort, byte>(ref source)); |
|||
(Vector256<uint> lower32, Vector256<uint> upper32) = Vector256.Widen(samples16); |
|||
return Vector512.ConvertToSingle(Vector512.Create(lower32, upper32)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Store(Vector128<int> source, ref ushort destination) |
|||
{ |
|||
Vector128<ushort> samples = Vector128.Narrow(source.AsUInt32(), Vector128<uint>.Zero); |
|||
|
|||
// The lower four UInt16 values are contiguous and can be committed with one unaligned store.
|
|||
Unsafe.WriteUnaligned(ref Unsafe.As<ushort, byte>(ref destination), samples.AsUInt64().ToScalar()); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Store(Vector256<int> source, ref ushort destination) |
|||
{ |
|||
Store(source.GetLower(), ref destination); |
|||
Store(source.GetUpper(), ref Unsafe.Add(ref destination, Vector128<int>.Count)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Store(Vector512<int> source, ref ushort destination) |
|||
{ |
|||
Store(source.GetLower(), ref destination); |
|||
Store(source.GetUpper(), ref Unsafe.Add(ref destination, Vector256<int>.Count)); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue