mirror of https://github.com/SixLabors/ImageSharp
Browse Source
# Conflicts: # src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs # tests/ImageSharp.Tests/TestImages.cspull/2899/head
769 changed files with 54210 additions and 21350 deletions
@ -0,0 +1,3 @@ |
|||
# GitHub Copilot Instructions |
|||
|
|||
Read and follow [AGENTS.md](../AGENTS.md) as the repository-wide source of coding, performance, and verification requirements. Prefer existing local patterns and repository configuration whenever generated code or suggestions are accepted. |
|||
@ -0,0 +1,41 @@ |
|||
# Six Labors AI Coding Guidelines |
|||
|
|||
These instructions apply to the entire repository. More-specific `AGENTS.md` files may add to or override them for their directory tree. |
|||
|
|||
## Working Practices |
|||
|
|||
- Inspect the relevant implementation, tests, benchmarks, project files, and nearby code before proposing or making changes. Do not infer current behavior when the source is available. |
|||
- Make the smallest complete change that solves the requested problem. Avoid unrelated cleanup, speculative abstractions, and formatting churn. |
|||
- Match established architecture, naming, formatting, documentation, and test patterns. Treat `.editorconfig`, analyzers, and repository build settings as authoritative. |
|||
- Preserve public API and observable behavior unless the task explicitly requires a change. Public API documentation must describe observable behavior, not implementation details. |
|||
- Do not use reflection against built assemblies, ad hoc assembly loading, or temporary probe projects unless explicitly requested. |
|||
- Build .NET projects in Release configuration unless explicitly instructed otherwise. |
|||
|
|||
## Performance |
|||
|
|||
- Treat throughput, latency, memory use, and binary size as design constraints, especially in pixel-processing, drawing, parsing, encoding, and other hot paths. |
|||
- Avoid unnecessary allocations, copies, boxing, closures, interface dispatch, repeated enumeration, and extra passes over data. |
|||
- Reuse the repository's existing memory ownership, pooling, span, vectorization, and parallelization patterns. Do not introduce a new mechanism when an established one fits. |
|||
- Keep hot loops simple and bounds-check-friendly. Hoist invariant work, preserve locality, and use the narrowest suitable data types without sacrificing correctness. |
|||
- Do not trade correctness or maintainability for assumed speed. Support non-obvious optimizations with measurements or clear evidence, and add or update benchmarks when performance is the purpose of the change. |
|||
- Consider all supported target frameworks and runtime capabilities. Do not regress fallback paths while optimizing newer runtimes. |
|||
|
|||
## C# Conventions |
|||
|
|||
- Follow the existing code around the change; local patterns take precedence over generic preferences. |
|||
- Do not use `record` or `record struct` types. |
|||
- Prefer established invariants over redundant guards. Validate at real external boundaries and do not add defensive checks for internally controlled states. |
|||
- Do not extract single-use helpers merely to name a block. Extract only for genuine reuse, an established local pattern, or meaningful complexity reduction. |
|||
- Add vertical whitespace after multi-line statements and declarations and between distinct logical stages. Never add trailing whitespace. |
|||
- Document every method, constructor, and property, regardless of whether it is public, internal, protected, or private. Keep public API documentation limited to observable behavior; use private and internal documentation to capture the contract and intent needed to maintain the code. |
|||
- Add inline comments throughout complex code. Explain algorithms, formulas, invariants, ownership, compatibility behavior, and performance tradeoffs at the operations and decisions they govern. Explain why the code is shaped that way rather than narrating the syntax. |
|||
- Document SIMD code especially thoroughly. Explain the vector layout, lane meaning, widening or narrowing, masks, shuffles, constants, alignment or remainder handling, supported instruction paths, scalar equivalence, and the reason each non-obvious operation is correct. |
|||
- Write algorithm and SIMD comments for a maintainer who is unfamiliar with the implementation. The reader should not need to reconstruct intent from external documentation, issue history, or benchmark results. |
|||
|
|||
## Verification |
|||
|
|||
- Add or update focused tests when behavior changes, following the test framework and conventions already used by the project. |
|||
- Never hack, weaken, skip, conditionally bypass, or otherwise manipulate a test to make it pass. Fix the production defect or the genuine test defect while preserving the test's intended coverage and sensitivity. |
|||
- Do not update golden files, reference images, snapshots, baselines, or expected-output artifacts to resolve a test failure. Treat a mismatch as evidence to investigate and correct the implementation. |
|||
- Run the narrowest relevant formatting, test, and Release build commands, then expand verification in proportion to the risk and scope of the change. |
|||
- Report what changed, the verification performed, and any remaining risks or unverified assumptions. |
|||
@ -0,0 +1,3 @@ |
|||
# Claude Code Instructions |
|||
|
|||
Read and follow [AGENTS.md](AGENTS.md) as the repository-wide source of coding, performance, and verification requirements. Apply any more-specific `AGENTS.md` or `CLAUDE.md` found below the files being changed. |
|||
@ -0,0 +1,3 @@ |
|||
# Gemini CLI Instructions |
|||
|
|||
Read and follow [AGENTS.md](AGENTS.md) as the repository-wide source of coding, performance, and verification requirements. Apply any more-specific `AGENTS.md` or `GEMINI.md` found below the files being changed. |
|||
@ -0,0 +1,96 @@ |
|||
# Security Policy |
|||
|
|||
## Supported Versions |
|||
|
|||
Six Labors provides security fixes only for the latest major version of each library. |
|||
|
|||
Older major versions are end-of-life and do not receive security fixes. |
|||
|
|||
Users must upgrade to the latest major version to receive security fixes. |
|||
|
|||
| Version | Supported | |
|||
| -------------------- | --------- | |
|||
| Latest major version | Yes | |
|||
| Older major versions | No | |
|||
|
|||
Security fixes, if any, are provided at Six Labors' discretion. |
|||
|
|||
This policy does not create any obligation to provide support, maintenance services, SLAs, custom fixes, hosted services, managed services, operational monitoring, professional services, consulting, or certification of customer products. |
|||
|
|||
## Reporting a Vulnerability |
|||
|
|||
Please report suspected security vulnerabilities using GitHub private vulnerability reporting for the relevant Six Labors repository, where available. |
|||
|
|||
If GitHub private vulnerability reporting is not available for a repository, please report suspected security vulnerabilities by contacting Six Labors through the contact details published on the Six Labors website. |
|||
|
|||
Do not report security vulnerabilities through public GitHub issues. |
|||
|
|||
When reporting a vulnerability, please include as much relevant information as possible: |
|||
|
|||
* affected package and version |
|||
* target framework and runtime |
|||
* operating system |
|||
* input file or minimal reproduction, if safe to share |
|||
* expected and actual behavior |
|||
* potential security impact |
|||
* whether you believe the issue is being actively exploited |
|||
|
|||
Six Labors may review reported vulnerabilities and determine whether they are security issues affecting a supported version. |
|||
|
|||
A report may be declined or closed without action if, in Six Labors' opinion, it: |
|||
|
|||
* is not reproducible |
|||
* does not affect a supported version |
|||
* affects only an unsupported or end-of-life version |
|||
* is not a security vulnerability |
|||
* depends on unsafe, unsupported, or unintended use |
|||
* depends on a vulnerable application, environment, dependency, configuration, or deployment outside the Six Labors library itself |
|||
* lacks sufficient information for assessment |
|||
* is duplicative |
|||
* has already been fixed |
|||
* is otherwise outside the scope of this policy |
|||
|
|||
If a vulnerability is accepted, Six Labors may handle it through GitHub Security Advisories and, where appropriate, CVE assignment. |
|||
|
|||
Six Labors does not guarantee any response time, fix time, release date, advisory publication date, CVE assignment, workaround, mitigation, or particular outcome for any report. |
|||
|
|||
## Scope |
|||
|
|||
This policy applies only to security vulnerabilities in Six Labors libraries themselves. |
|||
|
|||
This policy does not apply to: |
|||
|
|||
* customer applications |
|||
* customer products |
|||
* customer deployments |
|||
* customer infrastructure |
|||
* customer data |
|||
* third-party services |
|||
* unsupported versions |
|||
* end-of-life versions |
|||
* forks or modified versions |
|||
* usage outside the documented or intended behavior of the relevant library |
|||
|
|||
Organizations using Six Labors libraries are responsible for assessing, securing, testing, monitoring, updating, and maintaining their own applications, products, deployments, infrastructure, and supply chains. |
|||
|
|||
## Cyber Resilience Act |
|||
|
|||
Six Labors libraries are general-purpose software libraries. |
|||
|
|||
They are not cybersecurity products, identity or access management systems, password managers, operating systems, browsers, firewalls, network management tools, SIEM tools, hypervisors, container runtimes, or other Cyber Resilience Act important or critical product classes. |
|||
|
|||
If a Six Labors library is treated as a product with digital elements under the Cyber Resilience Act, Six Labors assesses it as an ordinary software component. |
|||
|
|||
Organizations incorporating Six Labors libraries into products made available on the EU market are responsible for assessing and meeting their own regulatory obligations for those products, including any obligations under the Cyber Resilience Act. |
|||
|
|||
Six Labors does not provide support, maintenance services, SLAs, managed services, hosted services, operational monitoring, custom fixes, professional services, consulting, or certification of customer products. |
|||
|
|||
Security vulnerabilities in supported Six Labors libraries are handled through the GitHub Security Advisory process for the relevant repository, where appropriate. |
|||
|
|||
From 11 September 2026, if Six Labors becomes aware of credible active exploitation of a vulnerability in a supported Six Labors library, or a severe security incident affecting a supported Six Labors library, Six Labors may report the matter through the applicable Cyber Resilience Act reporting mechanism where legally required. |
|||
|
|||
## No Warranty |
|||
|
|||
Six Labors libraries are provided in accordance with their applicable license terms. |
|||
|
|||
Nothing in this policy creates any warranty, representation, guarantee, support obligation, maintenance obligation, service commitment, regulatory certification, or assumption of responsibility for any customer product, customer deployment, customer compliance obligation, or third-party system. |
|||
@ -1 +1 @@ |
|||
Subproject commit a1d3ac20494631e3cc13132897573796b0e4ee6d |
|||
Subproject commit 74b7f32b8e41fdf8fe2f3eda54fd5a82ebbedfbc |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Advanced; |
|||
|
|||
/// <summary>
|
|||
/// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations
|
|||
/// on non-generic <see cref="ImageFrame"/> instances.
|
|||
/// </summary>
|
|||
public interface IImageFrameVisitor |
|||
{ |
|||
/// <summary>
|
|||
/// Provides a pixel-specific implementation for a given operation.
|
|||
/// </summary>
|
|||
/// <param name="frame">The image frame.</param>
|
|||
/// <typeparam name="TPixel">The pixel type.</typeparam>
|
|||
public void Visit<TPixel>(ImageFrame<TPixel> frame) |
|||
where TPixel : unmanaged, IPixel<TPixel>; |
|||
} |
|||
@ -1,36 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
// The JIT can detect and optimize rotation idioms ROTL (Rotate Left)
|
|||
// and ROTR (Rotate Right) emitting efficient CPU instructions:
|
|||
// https://github.com/dotnet/coreclr/pull/1830
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp; |
|||
|
|||
/// <summary>
|
|||
/// Defines the contract for methods that allow the shuffling of pixel components.
|
|||
/// Used for shuffling on platforms that do not support Hardware Intrinsics.
|
|||
/// Defines a stateless operation over packed pixel components.
|
|||
/// </summary>
|
|||
internal interface IComponentShuffle |
|||
{ |
|||
/// <summary>
|
|||
/// Shuffles then slices 8-bit integers in <paramref name="source"/>
|
|||
/// using a byte control and store the results in <paramref name="destination"/>.
|
|||
/// If successful, this method will reduce the length of <paramref name="source"/> length
|
|||
/// by the shuffle amount.
|
|||
/// Reorders one packed pixel.
|
|||
/// </summary>
|
|||
/// <param name="source">The source span of bytes.</param>
|
|||
/// <param name="destination">The destination span of bytes.</param>
|
|||
void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination); |
|||
/// <param name="source">The source components, with the first component in the least-significant byte.</param>
|
|||
/// <returns>The reordered packed components.</returns>
|
|||
public static abstract uint Invoke(uint source); |
|||
|
|||
/// <summary>
|
|||
/// Shuffle 8-bit integers in <paramref name="source"/>
|
|||
/// using the control and store the results in <paramref name="destination"/>.
|
|||
/// Reorders the packed pixels in a 128-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="source">The source span of bytes.</param>
|
|||
/// <param name="destination">The destination span of bytes.</param>
|
|||
/// <remarks>
|
|||
/// Implementation can assume that source.Length is less or equal than destination.Length.
|
|||
/// Loops should iterate using source.Length.
|
|||
/// </remarks>
|
|||
void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination); |
|||
/// <param name="source">The source pixels.</param>
|
|||
/// <returns>The reordered pixels.</returns>
|
|||
public static abstract Vector128<byte> Invoke(Vector128<byte> source); |
|||
} |
|||
|
|||
@ -1,85 +1,95 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using static SixLabors.ImageSharp.SimdUtils; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp; |
|||
|
|||
/// <inheritdoc/>
|
|||
/// <summary>
|
|||
/// Defines a stateless operation that reorders a three-component pixel after adding opaque alpha.
|
|||
/// </summary>
|
|||
internal interface IPad3Shuffle4 : IComponentShuffle |
|||
{ |
|||
} |
|||
|
|||
internal readonly struct DefaultPad3Shuffle4([ConstantExpected] byte control) : IPad3Shuffle4 |
|||
/// <summary>
|
|||
/// Preserves XYZ order and appends opaque W.
|
|||
/// </summary>
|
|||
internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4 |
|||
{ |
|||
public byte Control { get; } = control; |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination) |
|||
#pragma warning disable CA1857 // A constant is expected for the parameter
|
|||
=> HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref destination, this.Control); |
|||
#pragma warning restore CA1857 // A constant is expected for the parameter
|
|||
public static uint Invoke(uint source) => source; |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Invoke(Vector128<byte> source) => source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reorders padded XYZW components to WXYZ.
|
|||
/// </summary>
|
|||
internal readonly struct WXYZPad3Shuffle4 : IPad3Shuffle4 |
|||
{ |
|||
/// <inheritdoc />
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination) |
|||
{ |
|||
ref byte sBase = ref MemoryMarshal.GetReference(source); |
|||
ref byte dBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
SimdUtils.Shuffle.InverseMMShuffle(this.Control, out uint p3, out uint p2, out uint p1, out uint p0); |
|||
|
|||
Span<byte> temp = stackalloc byte[4]; |
|||
ref byte t = ref MemoryMarshal.GetReference(temp); |
|||
ref uint tu = ref Unsafe.As<byte, uint>(ref t); |
|||
|
|||
for (nuint i = 0, j = 0; i < (uint)source.Length; i += 3, j += 4) |
|||
{ |
|||
ref byte s = ref Unsafe.Add(ref sBase, i); |
|||
tu = Unsafe.As<byte, uint>(ref s) | 0xFF000000; |
|||
|
|||
Unsafe.Add(ref dBase, j + 0) = Unsafe.Add(ref t, p0); |
|||
Unsafe.Add(ref dBase, j + 1) = Unsafe.Add(ref t, p1); |
|||
Unsafe.Add(ref dBase, j + 2) = Unsafe.Add(ref t, p2); |
|||
Unsafe.Add(ref dBase, j + 3) = Unsafe.Add(ref t, p3); |
|||
} |
|||
} |
|||
public static uint Invoke(uint source) |
|||
|
|||
// The scalar pipeline has already appended opaque W, so the four-component
|
|||
// WXYZ operator performs the complete remaining permutation.
|
|||
=> WXYZShuffle4.Invoke(source); |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Invoke(Vector128<byte> source) |
|||
|
|||
// Each four-byte group is an XYZW pixel with opaque W. Selecting [3, 0, 1, 2]
|
|||
// produces WXYZ, and offsets 4, 8, and 12 repeat that rotation for the next pixels.
|
|||
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14)); |
|||
} |
|||
|
|||
internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4 |
|||
/// <summary>
|
|||
/// Reorders padded XYZW components to WZYX.
|
|||
/// </summary>
|
|||
internal readonly struct WZYXPad3Shuffle4 : IPad3Shuffle4 |
|||
{ |
|||
/// <inheritdoc />
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination) |
|||
=> HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref destination, SimdUtils.Shuffle.MMShuffle3210); |
|||
public static uint Invoke(uint source) |
|||
|
|||
// The scalar pipeline has already appended opaque W, so the four-component
|
|||
// WZYX operator performs the complete remaining permutation.
|
|||
=> WZYXShuffle4.Invoke(source); |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Invoke(Vector128<byte> source) |
|||
|
|||
// Each four-byte group is an XYZW pixel with opaque W. Selecting [3, 2, 1, 0]
|
|||
// produces WZYX, and offsets 4, 8, and 12 repeat that reversal for the next pixels.
|
|||
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reorders padded XYZW components to ZYXW.
|
|||
/// </summary>
|
|||
internal readonly struct ZYXWPad3Shuffle4 : IPad3Shuffle4 |
|||
{ |
|||
/// <inheritdoc />
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination) |
|||
{ |
|||
ref byte sBase = ref MemoryMarshal.GetReference(source); |
|||
ref byte dBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
ref byte sEnd = ref Unsafe.Add(ref sBase, (uint)source.Length); |
|||
ref byte sLoopEnd = ref Unsafe.Subtract(ref sEnd, 4); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd)) |
|||
{ |
|||
Unsafe.As<byte, uint>(ref dBase) = Unsafe.As<byte, uint>(ref sBase) | 0xFF000000; |
|||
|
|||
sBase = ref Unsafe.Add(ref sBase, 3); |
|||
dBase = ref Unsafe.Add(ref dBase, 4); |
|||
} |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd)) |
|||
{ |
|||
Unsafe.Add(ref dBase, 0) = Unsafe.Add(ref sBase, 0); |
|||
Unsafe.Add(ref dBase, 1) = Unsafe.Add(ref sBase, 1); |
|||
Unsafe.Add(ref dBase, 2) = Unsafe.Add(ref sBase, 2); |
|||
Unsafe.Add(ref dBase, 3) = byte.MaxValue; |
|||
|
|||
sBase = ref Unsafe.Add(ref sBase, 3); |
|||
dBase = ref Unsafe.Add(ref dBase, 4); |
|||
} |
|||
} |
|||
public static uint Invoke(uint source) |
|||
|
|||
// The scalar pipeline has already appended opaque W, so the four-component
|
|||
// ZYXW operator performs the complete remaining permutation.
|
|||
=> ZYXWShuffle4.Invoke(source); |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Invoke(Vector128<byte> source) |
|||
|
|||
// Each four-byte group is an XYZW pixel with opaque W. Selecting [2, 1, 0, 3]
|
|||
// exchanges X and Z to produce ZYXW, with offsets 4, 8, and 12 covering the next pixels.
|
|||
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15)); |
|||
} |
|||
|
|||
@ -1,41 +1,38 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using static SixLabors.ImageSharp.SimdUtils; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp; |
|||
|
|||
/// <inheritdoc/>
|
|||
/// <summary>
|
|||
/// Identifies a stateless three-component shuffle operator.
|
|||
/// </summary>
|
|||
internal interface IShuffle3 : IComponentShuffle |
|||
{ |
|||
} |
|||
|
|||
internal readonly struct DefaultShuffle3([ConstantExpected] byte control) : IShuffle3 |
|||
/// <summary>
|
|||
/// Reorders XYZ components to ZYX.
|
|||
/// </summary>
|
|||
internal readonly struct ZYXShuffle3 : IShuffle3 |
|||
{ |
|||
public byte Control { get; } = control; |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public void ShuffleReduce(ref ReadOnlySpan<byte> source, ref Span<byte> destination) |
|||
#pragma warning disable CA1857 // A constant is expected for the parameter
|
|||
=> HwIntrinsics.Shuffle3Reduce(ref source, ref destination, this.Control); |
|||
#pragma warning restore CA1857 // A constant is expected for the parameter
|
|||
public static uint Invoke(uint source) |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public void Shuffle(ReadOnlySpan<byte> source, Span<byte> destination) |
|||
{ |
|||
ref byte sBase = ref MemoryMarshal.GetReference(source); |
|||
ref byte dBase = ref MemoryMarshal.GetReference(destination); |
|||
// The scalar tail is staged as XYZW with an unused W byte. Reusing the four-component
|
|||
// ZYXW operator produces ZYX in the low three bytes consumed by the caller.
|
|||
=> ZYXWShuffle4.Invoke(source); |
|||
|
|||
SimdUtils.Shuffle.InverseMMShuffle(this.Control, out _, out uint p2, out uint p1, out uint p0); |
|||
/// <inheritdoc />
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<byte> Invoke(Vector128<byte> source) |
|||
|
|||
for (nuint i = 0; i < (uint)source.Length; i += 3) |
|||
{ |
|||
Unsafe.Add(ref dBase, i + 0) = Unsafe.Add(ref sBase, p0 + i); |
|||
Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); |
|||
Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); |
|||
} |
|||
} |
|||
// Each four-byte group is a temporary XYZW pixel created by the shuffle pipeline.
|
|||
// Selecting [2, 1, 0, 3] produces ZYXW, and offsets 4, 8, and 12 repeat that
|
|||
// permutation for the next pixels. The pipeline subsequently discards every W byte.
|
|||
=> Vector128_.ShuffleNative(source, Vector128.Create((byte)2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15)); |
|||
} |
|||
|
|||
@ -0,0 +1,93 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
internal static partial class TensorPrimitives_ |
|||
{ |
|||
/// <summary>
|
|||
/// Computes the element-wise sum of the values in <paramref name="x"/> and <paramref name="y"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="x">The first addends.</param>
|
|||
/// <param name="y">The second addends.</param>
|
|||
/// <param name="destination">The destination for the sums.</param>
|
|||
/// <exception cref="ArgumentException"><paramref name="x"/> and <paramref name="y"/> do not have the same length.</exception>
|
|||
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than the input spans.</exception>
|
|||
/// <exception cref="ArgumentException">
|
|||
/// An input and <paramref name="destination"/> overlap without beginning at the same memory location.
|
|||
/// </exception>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Add<T>(ReadOnlySpan<T> x, ReadOnlySpan<T> y, Span<T> destination) |
|||
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T> |
|||
=> InvokeSpanSpanIntoSpan<T, AddOperator<T>>(x, y, destination); |
|||
|
|||
/// <summary>
|
|||
/// Computes the element-wise sum of the values in <paramref name="x"/> and the scalar <paramref name="y"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="x">The first addends.</param>
|
|||
/// <param name="y">The scalar second addend.</param>
|
|||
/// <param name="destination">The destination for the sums.</param>
|
|||
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
|
|||
/// <exception cref="ArgumentException">
|
|||
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
|
|||
/// </exception>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Add<T>(ReadOnlySpan<T> x, T y, Span<T> destination) |
|||
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T> |
|||
=> InvokeSpanScalarIntoSpan<T, AddOperator<T>>(x, y, destination); |
|||
|
|||
/// <summary>
|
|||
/// Adds corresponding values.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
private readonly struct AddOperator<T> : IBinaryOperator<T> |
|||
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether this operation supports vector execution.
|
|||
/// </summary>
|
|||
public static bool Vectorizable => true; |
|||
|
|||
/// <summary>
|
|||
/// Adds scalar values.
|
|||
/// </summary>
|
|||
/// <param name="x">The first addend.</param>
|
|||
/// <param name="y">The second addend.</param>
|
|||
/// <returns>The sum.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static T Invoke(T x, T y) => x + y; |
|||
|
|||
/// <summary>
|
|||
/// Adds 128-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The first addends.</param>
|
|||
/// <param name="y">The second addends.</param>
|
|||
/// <returns>The sums.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> y) => x + y; |
|||
|
|||
/// <summary>
|
|||
/// Adds 256-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The first addends.</param>
|
|||
/// <param name="y">The second addends.</param>
|
|||
/// <returns>The sums.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> y) => x + y; |
|||
|
|||
/// <summary>
|
|||
/// Adds 512-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The first addends.</param>
|
|||
/// <param name="y">The second addends.</param>
|
|||
/// <returns>The sums.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> y) => x + y; |
|||
} |
|||
} |
|||
@ -0,0 +1,322 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
internal static partial class TensorPrimitives_ |
|||
{ |
|||
/// <summary>
|
|||
/// Computes the element-wise result of clamping <paramref name="x"/> to the inclusive range specified
|
|||
/// by <paramref name="min"/> and <paramref name="max"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="x">The values to clamp.</param>
|
|||
/// <param name="min">The inclusive lower bound.</param>
|
|||
/// <param name="max">The inclusive upper bound.</param>
|
|||
/// <param name="destination">The destination for the clamped values.</param>
|
|||
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
|
|||
/// <exception cref="ArgumentException">
|
|||
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
|
|||
/// </exception>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Clamp<T>(ReadOnlySpan<T> x, T min, T max, Span<T> destination) |
|||
where T : INumber<T> |
|||
=> InvokeSpanScalarScalarIntoSpan<T, ClampOperator<T>>(x, min, max, destination); |
|||
|
|||
/// <summary>
|
|||
/// Clamps single-precision values with the normalized runtime semantics.
|
|||
/// </summary>
|
|||
/// <param name="value">The values to clamp.</param>
|
|||
/// <param name="min">The inclusive lower bounds.</param>
|
|||
/// <param name="max">The inclusive upper bounds.</param>
|
|||
/// <returns>The clamped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<float> ClampSingle( |
|||
Vector128<float> value, |
|||
Vector128<float> min, |
|||
Vector128<float> max) |
|||
{ |
|||
// Unlike the native x86 min/max instructions, the normalized runtime operations propagate a NaN in the
|
|||
// first operand and select negative zero when equal values have different signs.
|
|||
Vector128<float> maximum = Vector128.ConditionalSelect( |
|||
Vector128.LessThan(min, value) |
|||
| ~Vector128.Equals(value, value) |
|||
| (Vector128.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), |
|||
value, |
|||
min); |
|||
|
|||
return Vector128.ConditionalSelect( |
|||
Vector128.LessThan(maximum, max) |
|||
| ~Vector128.Equals(maximum, maximum) |
|||
| (Vector128.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), |
|||
maximum, |
|||
max); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clamps single-precision values with the normalized runtime semantics.
|
|||
/// </summary>
|
|||
/// <param name="value">The values to clamp.</param>
|
|||
/// <param name="min">The inclusive lower bounds.</param>
|
|||
/// <param name="max">The inclusive upper bounds.</param>
|
|||
/// <returns>The clamped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<float> ClampSingle( |
|||
Vector256<float> value, |
|||
Vector256<float> min, |
|||
Vector256<float> max) |
|||
{ |
|||
Vector256<float> maximum = Vector256.ConditionalSelect( |
|||
Vector256.LessThan(min, value) |
|||
| ~Vector256.Equals(value, value) |
|||
| (Vector256.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), |
|||
value, |
|||
min); |
|||
|
|||
return Vector256.ConditionalSelect( |
|||
Vector256.LessThan(maximum, max) |
|||
| ~Vector256.Equals(maximum, maximum) |
|||
| (Vector256.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), |
|||
maximum, |
|||
max); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clamps single-precision values with the normalized runtime semantics.
|
|||
/// </summary>
|
|||
/// <param name="value">The values to clamp.</param>
|
|||
/// <param name="min">The inclusive lower bounds.</param>
|
|||
/// <param name="max">The inclusive upper bounds.</param>
|
|||
/// <returns>The clamped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<float> ClampSingle( |
|||
Vector512<float> value, |
|||
Vector512<float> min, |
|||
Vector512<float> max) |
|||
{ |
|||
Vector512<float> maximum = Vector512.ConditionalSelect( |
|||
Vector512.LessThan(min, value) |
|||
| ~Vector512.Equals(value, value) |
|||
| (Vector512.Equals(value, min) & (min.AsInt32() >> 31).AsSingle()), |
|||
value, |
|||
min); |
|||
|
|||
return Vector512.ConditionalSelect( |
|||
Vector512.LessThan(maximum, max) |
|||
| ~Vector512.Equals(maximum, maximum) |
|||
| (Vector512.Equals(maximum, max) & (maximum.AsInt32() >> 31).AsSingle()), |
|||
maximum, |
|||
max); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clamps double-precision values with the normalized runtime semantics.
|
|||
/// </summary>
|
|||
/// <param name="value">The values to clamp.</param>
|
|||
/// <param name="min">The inclusive lower bounds.</param>
|
|||
/// <param name="max">The inclusive upper bounds.</param>
|
|||
/// <returns>The clamped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<double> ClampDouble( |
|||
Vector128<double> value, |
|||
Vector128<double> min, |
|||
Vector128<double> max) |
|||
{ |
|||
Vector128<double> maximum = Vector128.ConditionalSelect( |
|||
Vector128.LessThan(min, value) |
|||
| ~Vector128.Equals(value, value) |
|||
| (Vector128.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), |
|||
value, |
|||
min); |
|||
|
|||
return Vector128.ConditionalSelect( |
|||
Vector128.LessThan(maximum, max) |
|||
| ~Vector128.Equals(maximum, maximum) |
|||
| (Vector128.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), |
|||
maximum, |
|||
max); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clamps double-precision values with the normalized runtime semantics.
|
|||
/// </summary>
|
|||
/// <param name="value">The values to clamp.</param>
|
|||
/// <param name="min">The inclusive lower bounds.</param>
|
|||
/// <param name="max">The inclusive upper bounds.</param>
|
|||
/// <returns>The clamped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<double> ClampDouble( |
|||
Vector256<double> value, |
|||
Vector256<double> min, |
|||
Vector256<double> max) |
|||
{ |
|||
Vector256<double> maximum = Vector256.ConditionalSelect( |
|||
Vector256.LessThan(min, value) |
|||
| ~Vector256.Equals(value, value) |
|||
| (Vector256.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), |
|||
value, |
|||
min); |
|||
|
|||
return Vector256.ConditionalSelect( |
|||
Vector256.LessThan(maximum, max) |
|||
| ~Vector256.Equals(maximum, maximum) |
|||
| (Vector256.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), |
|||
maximum, |
|||
max); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clamps double-precision values with the normalized runtime semantics.
|
|||
/// </summary>
|
|||
/// <param name="value">The values to clamp.</param>
|
|||
/// <param name="min">The inclusive lower bounds.</param>
|
|||
/// <param name="max">The inclusive upper bounds.</param>
|
|||
/// <returns>The clamped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<double> ClampDouble( |
|||
Vector512<double> value, |
|||
Vector512<double> min, |
|||
Vector512<double> max) |
|||
{ |
|||
Vector512<double> maximum = Vector512.ConditionalSelect( |
|||
Vector512.LessThan(min, value) |
|||
| ~Vector512.Equals(value, value) |
|||
| (Vector512.Equals(value, min) & (min.AsInt64() >> 63).AsDouble()), |
|||
value, |
|||
min); |
|||
|
|||
return Vector512.ConditionalSelect( |
|||
Vector512.LessThan(maximum, max) |
|||
| ~Vector512.Equals(maximum, maximum) |
|||
| (Vector512.Equals(maximum, max) & (maximum.AsInt64() >> 63).AsDouble()), |
|||
maximum, |
|||
max); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clamps values using the complete runtime tensor contract, including signed-zero correction.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
private readonly struct ClampOperator<T> : ITernaryOperator<T> |
|||
where T : INumber<T> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether this operation supports vector execution.
|
|||
/// </summary>
|
|||
public static bool Vectorizable => true; |
|||
|
|||
/// <summary>
|
|||
/// Clamps a scalar value.
|
|||
/// </summary>
|
|||
/// <param name="x">The value.</param>
|
|||
/// <param name="min">The inclusive lower bound.</param>
|
|||
/// <param name="max">The inclusive upper bound.</param>
|
|||
/// <returns>The clamped value.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static T Invoke(T x, T min, T max) |
|||
=> Vector128<T>.IsSupported ? T.Min(T.Max(x, min), max) : T.Clamp(x, min, max); |
|||
|
|||
/// <summary>
|
|||
/// Clamps a 128-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="x">The values.</param>
|
|||
/// <param name="min">The inclusive lower bounds.</param>
|
|||
/// <param name="max">The inclusive upper bounds.</param>
|
|||
/// <returns>The clamped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> min, Vector128<T> max) |
|||
{ |
|||
if (typeof(T) == typeof(float)) |
|||
{ |
|||
Vector128<float> result = ClampSingle( |
|||
Unsafe.As<Vector128<T>, Vector128<float>>(ref x), |
|||
Unsafe.As<Vector128<T>, Vector128<float>>(ref min), |
|||
Unsafe.As<Vector128<T>, Vector128<float>>(ref max)); |
|||
|
|||
return Unsafe.As<Vector128<float>, Vector128<T>>(ref result); |
|||
} |
|||
|
|||
if (typeof(T) == typeof(double)) |
|||
{ |
|||
Vector128<double> result = ClampDouble( |
|||
Unsafe.As<Vector128<T>, Vector128<double>>(ref x), |
|||
Unsafe.As<Vector128<T>, Vector128<double>>(ref min), |
|||
Unsafe.As<Vector128<T>, Vector128<double>>(ref max)); |
|||
|
|||
return Unsafe.As<Vector128<double>, Vector128<T>>(ref result); |
|||
} |
|||
|
|||
return Vector128_.Clamp(x, min, max); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clamps a 256-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="x">The values.</param>
|
|||
/// <param name="min">The inclusive lower bounds.</param>
|
|||
/// <param name="max">The inclusive upper bounds.</param>
|
|||
/// <returns>The clamped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> min, Vector256<T> max) |
|||
{ |
|||
if (typeof(T) == typeof(float)) |
|||
{ |
|||
Vector256<float> result = ClampSingle( |
|||
Unsafe.As<Vector256<T>, Vector256<float>>(ref x), |
|||
Unsafe.As<Vector256<T>, Vector256<float>>(ref min), |
|||
Unsafe.As<Vector256<T>, Vector256<float>>(ref max)); |
|||
|
|||
return Unsafe.As<Vector256<float>, Vector256<T>>(ref result); |
|||
} |
|||
|
|||
if (typeof(T) == typeof(double)) |
|||
{ |
|||
Vector256<double> result = ClampDouble( |
|||
Unsafe.As<Vector256<T>, Vector256<double>>(ref x), |
|||
Unsafe.As<Vector256<T>, Vector256<double>>(ref min), |
|||
Unsafe.As<Vector256<T>, Vector256<double>>(ref max)); |
|||
|
|||
return Unsafe.As<Vector256<double>, Vector256<T>>(ref result); |
|||
} |
|||
|
|||
return Vector256_.Clamp(x, min, max); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clamps a 512-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="x">The values.</param>
|
|||
/// <param name="min">The inclusive lower bounds.</param>
|
|||
/// <param name="max">The inclusive upper bounds.</param>
|
|||
/// <returns>The clamped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> min, Vector512<T> max) |
|||
{ |
|||
if (typeof(T) == typeof(float)) |
|||
{ |
|||
Vector512<float> result = ClampSingle( |
|||
Unsafe.As<Vector512<T>, Vector512<float>>(ref x), |
|||
Unsafe.As<Vector512<T>, Vector512<float>>(ref min), |
|||
Unsafe.As<Vector512<T>, Vector512<float>>(ref max)); |
|||
|
|||
return Unsafe.As<Vector512<float>, Vector512<T>>(ref result); |
|||
} |
|||
|
|||
if (typeof(T) == typeof(double)) |
|||
{ |
|||
Vector512<double> result = ClampDouble( |
|||
Unsafe.As<Vector512<T>, Vector512<double>>(ref x), |
|||
Unsafe.As<Vector512<T>, Vector512<double>>(ref min), |
|||
Unsafe.As<Vector512<T>, Vector512<double>>(ref max)); |
|||
|
|||
return Unsafe.As<Vector512<double>, Vector512<T>>(ref result); |
|||
} |
|||
|
|||
return Vector512_.Clamp(x, min, max); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
internal static partial class TensorPrimitives_ |
|||
{ |
|||
/// <summary>
|
|||
/// Computes the element-wise result of dividing the values in <paramref name="x"/> by <paramref name="y"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="x">The dividend values.</param>
|
|||
/// <param name="y">The divisor.</param>
|
|||
/// <param name="destination">The destination for the quotient values.</param>
|
|||
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
|
|||
/// <exception cref="ArgumentException">
|
|||
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
|
|||
/// </exception>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Divide<T>(ReadOnlySpan<T> x, T y, Span<T> destination) |
|||
where T : IDivisionOperators<T, T, T> |
|||
=> InvokeSpanScalarIntoSpanForDivision<T, DivideOperator<T>>(x, y, destination); |
|||
|
|||
/// <summary>
|
|||
/// Determines whether <typeparamref name="T"/> has the same vector division support as <see cref="int"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <returns><see langword="true"/> when <typeparamref name="T"/> is a 32-bit signed native integer type.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static bool IsInt32Like<T>() |
|||
=> typeof(T) == typeof(int) || (IntPtr.Size == 4 && typeof(T) == typeof(nint)); |
|||
|
|||
/// <summary>
|
|||
/// Divides values by a scalar.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
private readonly struct DivideOperator<T> : IBinaryOperator<T> |
|||
where T : IDivisionOperators<T, T, T> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether this operation supports vector execution.
|
|||
/// </summary>
|
|||
public static bool Vectorizable => typeof(T) == typeof(float) |
|||
|| typeof(T) == typeof(double) |
|||
|| (Vector256.IsHardwareAccelerated && IsInt32Like<T>()); |
|||
|
|||
/// <summary>
|
|||
/// Divides scalar values.
|
|||
/// </summary>
|
|||
/// <param name="x">The dividend.</param>
|
|||
/// <param name="y">The divisor.</param>
|
|||
/// <returns>The quotient.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static T Invoke(T x, T y) => x / y; |
|||
|
|||
/// <summary>
|
|||
/// Divides 128-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The dividends.</param>
|
|||
/// <param name="y">The divisors.</param>
|
|||
/// <returns>The quotients.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> y) => x / y; |
|||
|
|||
/// <summary>
|
|||
/// Divides 256-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The dividends.</param>
|
|||
/// <param name="y">The divisors.</param>
|
|||
/// <returns>The quotients.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> y) => x / y; |
|||
|
|||
/// <summary>
|
|||
/// Divides 512-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The dividends.</param>
|
|||
/// <param name="y">The divisors.</param>
|
|||
/// <returns>The quotients.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> y) => x / y; |
|||
} |
|||
} |
|||
@ -0,0 +1,900 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
/// <summary>
|
|||
/// Provides compatibility implementations for tensor operations that are not available on every target framework.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The API shape follows <c>System.Numerics.Tensors.TensorPrimitives</c> so call sites can move to the runtime
|
|||
/// implementation when ImageSharp no longer supports target frameworks that predate it.
|
|||
/// </remarks>
|
|||
internal static partial class TensorPrimitives_ |
|||
{ |
|||
/// <summary>
|
|||
/// Defines an element-wise binary operation.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
private interface IBinaryOperator<T> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether the operation supports vector execution.
|
|||
/// </summary>
|
|||
public static abstract bool Vectorizable { get; } |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to scalar values.
|
|||
/// </summary>
|
|||
/// <param name="x">The first value.</param>
|
|||
/// <param name="y">The second value.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract T Invoke(T x, T y); |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to 128-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The first vector.</param>
|
|||
/// <param name="y">The second vector.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract Vector128<T> Invoke(Vector128<T> x, Vector128<T> y); |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to 256-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The first vector.</param>
|
|||
/// <param name="y">The second vector.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract Vector256<T> Invoke(Vector256<T> x, Vector256<T> y); |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to 512-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The first vector.</param>
|
|||
/// <param name="y">The second vector.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract Vector512<T> Invoke(Vector512<T> x, Vector512<T> y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines an element-wise ternary operation.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
private interface ITernaryOperator<T> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether the operation supports vector execution.
|
|||
/// </summary>
|
|||
public static abstract bool Vectorizable { get; } |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to scalar values.
|
|||
/// </summary>
|
|||
/// <param name="x">The first value.</param>
|
|||
/// <param name="y">The second value.</param>
|
|||
/// <param name="z">The third value.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract T Invoke(T x, T y, T z); |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to 128-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The first vector.</param>
|
|||
/// <param name="y">The second vector.</param>
|
|||
/// <param name="z">The third vector.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract Vector128<T> Invoke(Vector128<T> x, Vector128<T> y, Vector128<T> z); |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to 256-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The first vector.</param>
|
|||
/// <param name="y">The second vector.</param>
|
|||
/// <param name="z">The third vector.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract Vector256<T> Invoke(Vector256<T> x, Vector256<T> y, Vector256<T> z); |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to 512-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The first vector.</param>
|
|||
/// <param name="y">The second vector.</param>
|
|||
/// <param name="z">The third vector.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract Vector512<T> Invoke(Vector512<T> x, Vector512<T> y, Vector512<T> z); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Validates that an input and destination are either disjoint or begin at the same memory location.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="input">The input values.</param>
|
|||
/// <param name="destination">The destination values.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void ValidateInputOutputSpanNonOverlapping<T>(ReadOnlySpan<T> input, Span<T> destination) |
|||
{ |
|||
// Runtime TensorPrimitives permits exact same-start overlap for in-place operation. A shifted overlap is
|
|||
// rejected because forward SIMD stores could overwrite input elements before a later load consumes them.
|
|||
if (!Unsafe.AreSame(ref MemoryMarshal.GetReference(input), ref MemoryMarshal.GetReference(destination)) |
|||
&& input.Overlaps(destination)) |
|||
{ |
|||
ThrowInputAndDestinationSpanMustNotOverlap(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Throws when input spans do not have the same length.
|
|||
/// </summary>
|
|||
[DoesNotReturn] |
|||
private static void ThrowSpansMustHaveSameLength() |
|||
=> throw new ArgumentException("Input span arguments must all have the same length."); |
|||
|
|||
/// <summary>
|
|||
/// Throws when the destination cannot hold every result.
|
|||
/// </summary>
|
|||
[DoesNotReturn] |
|||
private static void ThrowDestinationTooShort() |
|||
=> throw new ArgumentException("Destination is too short.", "destination"); |
|||
|
|||
/// <summary>
|
|||
/// Throws when an input and destination overlap without beginning at the same memory location.
|
|||
/// </summary>
|
|||
[DoesNotReturn] |
|||
private static void ThrowInputAndDestinationSpanMustNotOverlap() |
|||
=> throw new ArgumentException( |
|||
"The destination span may only overlap with an input span if the two spans start at the same memory location.", |
|||
"destination"); |
|||
|
|||
/// <summary>
|
|||
/// Performs an element-wise binary operation between two spans.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="x">The first input values.</param>
|
|||
/// <param name="y">The second input values.</param>
|
|||
/// <param name="destination">The destination values.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeSpanSpanIntoSpan<T, TOperator>( |
|||
ReadOnlySpan<T> x, |
|||
ReadOnlySpan<T> y, |
|||
Span<T> destination) |
|||
where TOperator : struct, IBinaryOperator<T> |
|||
{ |
|||
if (x.Length != y.Length) |
|||
{ |
|||
ThrowSpansMustHaveSameLength(); |
|||
} |
|||
|
|||
if (x.Length > destination.Length) |
|||
{ |
|||
ThrowDestinationTooShort(); |
|||
} |
|||
|
|||
ValidateInputOutputSpanNonOverlapping(x, destination); |
|||
ValidateInputOutputSpanNonOverlapping(y, destination); |
|||
|
|||
ref T xRef = ref MemoryMarshal.GetReference(x); |
|||
ref T yRef = ref MemoryMarshal.GetReference(y); |
|||
ref T destinationRef = ref MemoryMarshal.GetReference(destination); |
|||
nuint length = (uint)x.Length; |
|||
|
|||
// Runtime main selects the widest supported pipeline once one complete vector is available.
|
|||
// Each pipeline preloads its final inputs when a tail overlaps so same-start in-place operation remains correct.
|
|||
if (TOperator.Vectorizable |
|||
&& Vector512.IsHardwareAccelerated |
|||
&& Vector512<T>.IsSupported |
|||
&& length >= (uint)Vector512<T>.Count) |
|||
{ |
|||
InvokeVectorized512<T, TOperator>(ref xRef, ref yRef, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count) |
|||
{ |
|||
InvokeVectorized256<T, TOperator>(ref xRef, ref yRef, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count) |
|||
{ |
|||
InvokeVectorized128<T, TOperator>(ref xRef, ref yRef, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
for (nuint i = 0; i < length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), Unsafe.Add(ref yRef, i)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Performs an element-wise binary operation between a span and a scalar.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="x">The input values.</param>
|
|||
/// <param name="y">The scalar input.</param>
|
|||
/// <param name="destination">The destination values.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeSpanScalarIntoSpan<T, TOperator>( |
|||
ReadOnlySpan<T> x, |
|||
T y, |
|||
Span<T> destination) |
|||
where TOperator : struct, IBinaryOperator<T> |
|||
{ |
|||
if (x.Length > destination.Length) |
|||
{ |
|||
ThrowDestinationTooShort(); |
|||
} |
|||
|
|||
ValidateInputOutputSpanNonOverlapping(x, destination); |
|||
|
|||
ref T xRef = ref MemoryMarshal.GetReference(x); |
|||
ref T destinationRef = ref MemoryMarshal.GetReference(destination); |
|||
nuint length = (uint)x.Length; |
|||
|
|||
// Runtime main selects the widest supported pipeline once one complete vector is available.
|
|||
if (TOperator.Vectorizable |
|||
&& Vector512.IsHardwareAccelerated |
|||
&& Vector512<T>.IsSupported |
|||
&& length >= (uint)Vector512<T>.Count) |
|||
{ |
|||
InvokeVectorized512<T, TOperator>(ref xRef, y, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count) |
|||
{ |
|||
InvokeVectorized256<T, TOperator>(ref xRef, y, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count) |
|||
{ |
|||
InvokeVectorized128<T, TOperator>(ref xRef, y, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
for (nuint i = 0; i < length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Performs element-wise division using the runtime tensor width-selection order.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The division operation to apply.</typeparam>
|
|||
/// <param name="x">The input values.</param>
|
|||
/// <param name="y">The scalar divisor.</param>
|
|||
/// <param name="destination">The destination values.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeSpanScalarIntoSpanForDivision<T, TOperator>( |
|||
ReadOnlySpan<T> x, |
|||
T y, |
|||
Span<T> destination) |
|||
where TOperator : struct, IBinaryOperator<T> |
|||
{ |
|||
if (x.Length > destination.Length) |
|||
{ |
|||
ThrowDestinationTooShort(); |
|||
} |
|||
|
|||
ValidateInputOutputSpanNonOverlapping(x, destination); |
|||
|
|||
ref T xRef = ref MemoryMarshal.GetReference(x); |
|||
ref T destinationRef = ref MemoryMarshal.GetReference(destination); |
|||
nuint length = (uint)x.Length; |
|||
|
|||
// Runtime main selects the widest supported pipeline once one complete vector is available.
|
|||
if (TOperator.Vectorizable |
|||
&& Vector512.IsHardwareAccelerated |
|||
&& Vector512<T>.IsSupported |
|||
&& length >= (uint)Vector512<T>.Count) |
|||
{ |
|||
InvokeVectorized512<T, TOperator>(ref xRef, y, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count) |
|||
{ |
|||
InvokeVectorized256<T, TOperator>(ref xRef, y, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
// Four values fill one 128-bit float vector. Processing exactly one packed prefix before the scalar
|
|||
// remainder avoids the overlapping second vector that regresses the common seven-element normalization.
|
|||
if (TOperator.Vectorizable |
|||
&& Vector128.IsHardwareAccelerated |
|||
&& Vector128<T>.IsSupported |
|||
&& length >= (uint)Vector128<T>.Count) |
|||
{ |
|||
nuint vectorCount = (uint)Vector128<T>.Count; |
|||
Vector128<T> yVector = Vector128.Create(y); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef), yVector).StoreUnsafe(ref destinationRef); |
|||
|
|||
for (nuint i = vectorCount; i < length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (nuint i = 0; i < length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Performs an element-wise ternary operation between a span and two scalars.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="x">The input values.</param>
|
|||
/// <param name="y">The first scalar input.</param>
|
|||
/// <param name="z">The second scalar input.</param>
|
|||
/// <param name="destination">The destination values.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeSpanScalarScalarIntoSpan<T, TOperator>( |
|||
ReadOnlySpan<T> x, |
|||
T y, |
|||
T z, |
|||
Span<T> destination) |
|||
where TOperator : struct, ITernaryOperator<T> |
|||
{ |
|||
if (x.Length > destination.Length) |
|||
{ |
|||
ThrowDestinationTooShort(); |
|||
} |
|||
|
|||
ValidateInputOutputSpanNonOverlapping(x, destination); |
|||
|
|||
ref T xRef = ref MemoryMarshal.GetReference(x); |
|||
ref T destinationRef = ref MemoryMarshal.GetReference(destination); |
|||
nuint length = (uint)x.Length; |
|||
|
|||
// This dispatch mirrors the runtime pipeline: large inputs use the widest available registers while
|
|||
// short inputs fall through to a width that fits, keeping the operator contract identical at every length.
|
|||
if (TOperator.Vectorizable && Vector512.IsHardwareAccelerated && Vector512<T>.IsSupported && length >= (uint)Vector512<T>.Count) |
|||
{ |
|||
InvokeVectorized512<T, TOperator>(ref xRef, y, z, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count) |
|||
{ |
|||
InvokeVectorized256<T, TOperator>(ref xRef, y, z, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count) |
|||
{ |
|||
InvokeVectorized128<T, TOperator>(ref xRef, y, z, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
for (nuint i = 0; i < length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i), y, z); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a binary operation between two spans with 128-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first element of the first input.</param>
|
|||
/// <param name="yRef">The first element of the second input.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeVectorized128<T, TOperator>( |
|||
ref T xRef, |
|||
ref T yRef, |
|||
ref T destinationRef, |
|||
nuint length) |
|||
where TOperator : struct, IBinaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector128<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
|
|||
// When a tail exists, both final inputs are loaded before any stores. This permits either source to also
|
|||
// be the destination when the tail starts inside the range written by the preceding full vector.
|
|||
Vector128<T> end = default; |
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke( |
|||
Vector128.LoadUnsafe(ref xRef, length - vectorCount), |
|||
Vector128.LoadUnsafe(ref yRef, length - vectorCount)); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7)), Vector128.LoadUnsafe(ref yRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index), Vector128.LoadUnsafe(ref yRef, index)).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a binary operation between two spans with 256-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first element of the first input.</param>
|
|||
/// <param name="yRef">The first element of the second input.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeVectorized256<T, TOperator>( |
|||
ref T xRef, |
|||
ref T yRef, |
|||
ref T destinationRef, |
|||
nuint length) |
|||
where TOperator : struct, IBinaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector256<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
Vector256<T> end = default; |
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke( |
|||
Vector256.LoadUnsafe(ref xRef, length - vectorCount), |
|||
Vector256.LoadUnsafe(ref yRef, length - vectorCount)); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7)), Vector256.LoadUnsafe(ref yRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index), Vector256.LoadUnsafe(ref yRef, index)).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a binary operation between two spans with 512-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first element of the first input.</param>
|
|||
/// <param name="yRef">The first element of the second input.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeVectorized512<T, TOperator>( |
|||
ref T xRef, |
|||
ref T yRef, |
|||
ref T destinationRef, |
|||
nuint length) |
|||
where TOperator : struct, IBinaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector512<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
Vector512<T> end = default; |
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke( |
|||
Vector512.LoadUnsafe(ref xRef, length - vectorCount), |
|||
Vector512.LoadUnsafe(ref yRef, length - vectorCount)); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7)), Vector512.LoadUnsafe(ref yRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index), Vector512.LoadUnsafe(ref yRef, index)).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a binary operation between a span and a scalar with 128-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first input element.</param>
|
|||
/// <param name="y">The scalar input.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeVectorized128<T, TOperator>( |
|||
ref T xRef, |
|||
T y, |
|||
ref T destinationRef, |
|||
nuint length) |
|||
where TOperator : struct, IBinaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector128<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
Vector128<T> yVector = Vector128.Create(y); |
|||
|
|||
// When a tail exists, preloading its final vector is required for in-place operation because it must
|
|||
// observe the original values before an earlier overlapping store writes them.
|
|||
Vector128<T> end = default; |
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke( |
|||
Vector128.LoadUnsafe(ref xRef, length - vectorCount), |
|||
yVector); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index), yVector).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a binary operation with 256-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first input element.</param>
|
|||
/// <param name="y">The scalar input.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeVectorized256<T, TOperator>( |
|||
ref T xRef, |
|||
T y, |
|||
ref T destinationRef, |
|||
nuint length) |
|||
where TOperator : struct, IBinaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector256<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
Vector256<T> yVector = Vector256.Create(y); |
|||
Vector256<T> end = default; |
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke( |
|||
Vector256.LoadUnsafe(ref xRef, length - vectorCount), |
|||
yVector); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index), yVector).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a binary operation with 512-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first input element.</param>
|
|||
/// <param name="y">The scalar input.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeVectorized512<T, TOperator>( |
|||
ref T xRef, |
|||
T y, |
|||
ref T destinationRef, |
|||
nuint length) |
|||
where TOperator : struct, IBinaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector512<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
Vector512<T> yVector = Vector512.Create(y); |
|||
Vector512<T> end = default; |
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke( |
|||
Vector512.LoadUnsafe(ref xRef, length - vectorCount), |
|||
yVector); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index), yVector).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a ternary operation with 128-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first input element.</param>
|
|||
/// <param name="y">The first scalar input.</param>
|
|||
/// <param name="z">The second scalar input.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeVectorized128<T, TOperator>( |
|||
ref T xRef, |
|||
T y, |
|||
T z, |
|||
ref T destinationRef, |
|||
nuint length) |
|||
where TOperator : struct, ITernaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector128<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
Vector128<T> yVector = Vector128.Create(y); |
|||
Vector128<T> zVector = Vector128.Create(z); |
|||
Vector128<T> end = default; |
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke( |
|||
Vector128.LoadUnsafe(ref xRef, length - vectorCount), |
|||
yVector, |
|||
zVector); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index), yVector, zVector).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a ternary operation with 256-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first input element.</param>
|
|||
/// <param name="y">The first scalar input.</param>
|
|||
/// <param name="z">The second scalar input.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeVectorized256<T, TOperator>( |
|||
ref T xRef, |
|||
T y, |
|||
T z, |
|||
ref T destinationRef, |
|||
nuint length) |
|||
where TOperator : struct, ITernaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector256<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
Vector256<T> yVector = Vector256.Create(y); |
|||
Vector256<T> zVector = Vector256.Create(z); |
|||
Vector256<T> end = default; |
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke( |
|||
Vector256.LoadUnsafe(ref xRef, length - vectorCount), |
|||
yVector, |
|||
zVector); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index), yVector, zVector).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a ternary operation with 512-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first input element.</param>
|
|||
/// <param name="y">The first scalar input.</param>
|
|||
/// <param name="z">The second scalar input.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeVectorized512<T, TOperator>( |
|||
ref T xRef, |
|||
T y, |
|||
T z, |
|||
ref T destinationRef, |
|||
nuint length) |
|||
where TOperator : struct, ITernaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector512<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
Vector512<T> yVector = Vector512.Create(y); |
|||
Vector512<T> zVector = Vector512.Create(z); |
|||
Vector512<T> end = default; |
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke( |
|||
Vector512.LoadUnsafe(ref xRef, length - vectorCount), |
|||
yVector, |
|||
zVector); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7)), yVector, zVector).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index), yVector, zVector).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,249 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
internal static partial class TensorPrimitives_ |
|||
{ |
|||
/// <summary>
|
|||
/// Computes the element-wise maximum of the values in <paramref name="x"/> and <paramref name="y"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="x">The values to compare.</param>
|
|||
/// <param name="y">The value to compare with each element.</param>
|
|||
/// <param name="destination">The destination for the maximum values.</param>
|
|||
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
|
|||
/// <exception cref="ArgumentException">
|
|||
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
|
|||
/// </exception>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Max<T>(ReadOnlySpan<T> x, T y, Span<T> destination) |
|||
where T : INumber<T> |
|||
=> InvokeSpanScalarIntoSpan<T, MaxOperator<T>>(x, y, destination); |
|||
|
|||
/// <summary>
|
|||
/// Selects maximum single-precision values with the normalized runtime semantics.
|
|||
/// </summary>
|
|||
/// <param name="x">The first values.</param>
|
|||
/// <param name="y">The second values.</param>
|
|||
/// <returns>The maximum values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<float> MaxSingle(Vector128<float> x, Vector128<float> y) |
|||
{ |
|||
// The .NET 8 operation already handles ordered unequal values. Correct its second-operand result for a
|
|||
// first-operand NaN, then use bitwise AND for equal values so positive zero wins regardless of operand order.
|
|||
Vector128<float> result = Vector128.Max(x, y); |
|||
result = Vector128.ConditionalSelect(~Vector128.Equals(x, x), x, result); |
|||
|
|||
return Vector128.ConditionalSelect( |
|||
Vector128.Equals(x, y), |
|||
x & y, |
|||
result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects maximum single-precision values with the normalized runtime semantics.
|
|||
/// </summary>
|
|||
/// <param name="x">The first values.</param>
|
|||
/// <param name="y">The second values.</param>
|
|||
/// <returns>The maximum values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<float> MaxSingle(Vector256<float> x, Vector256<float> y) |
|||
{ |
|||
Vector256<float> result = Vector256.Max(x, y); |
|||
result = Vector256.ConditionalSelect(~Vector256.Equals(x, x), x, result); |
|||
|
|||
return Vector256.ConditionalSelect( |
|||
Vector256.Equals(x, y), |
|||
x & y, |
|||
result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects maximum single-precision values with the normalized runtime semantics.
|
|||
/// </summary>
|
|||
/// <param name="x">The first values.</param>
|
|||
/// <param name="y">The second values.</param>
|
|||
/// <returns>The maximum values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<float> MaxSingle(Vector512<float> x, Vector512<float> y) |
|||
{ |
|||
Vector512<float> result = Vector512.Max(x, y); |
|||
result = Vector512.ConditionalSelect(~Vector512.Equals(x, x), x, result); |
|||
|
|||
return Vector512.ConditionalSelect( |
|||
Vector512.Equals(x, y), |
|||
x & y, |
|||
result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects maximum double-precision values with the normalized runtime semantics.
|
|||
/// </summary>
|
|||
/// <param name="x">The first values.</param>
|
|||
/// <param name="y">The second values.</param>
|
|||
/// <returns>The maximum values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<double> MaxDouble(Vector128<double> x, Vector128<double> y) |
|||
{ |
|||
Vector128<double> result = Vector128.Max(x, y); |
|||
result = Vector128.ConditionalSelect(~Vector128.Equals(x, x), x, result); |
|||
|
|||
return Vector128.ConditionalSelect( |
|||
Vector128.Equals(x, y), |
|||
x & y, |
|||
result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects maximum double-precision values with the normalized runtime semantics.
|
|||
/// </summary>
|
|||
/// <param name="x">The first values.</param>
|
|||
/// <param name="y">The second values.</param>
|
|||
/// <returns>The maximum values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<double> MaxDouble(Vector256<double> x, Vector256<double> y) |
|||
{ |
|||
Vector256<double> result = Vector256.Max(x, y); |
|||
result = Vector256.ConditionalSelect(~Vector256.Equals(x, x), x, result); |
|||
|
|||
return Vector256.ConditionalSelect( |
|||
Vector256.Equals(x, y), |
|||
x & y, |
|||
result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects maximum double-precision values with the normalized runtime semantics.
|
|||
/// </summary>
|
|||
/// <param name="x">The first values.</param>
|
|||
/// <param name="y">The second values.</param>
|
|||
/// <returns>The maximum values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<double> MaxDouble(Vector512<double> x, Vector512<double> y) |
|||
{ |
|||
Vector512<double> result = Vector512.Max(x, y); |
|||
result = Vector512.ConditionalSelect(~Vector512.Equals(x, x), x, result); |
|||
|
|||
return Vector512.ConditionalSelect( |
|||
Vector512.Equals(x, y), |
|||
x & y, |
|||
result); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects the maximum corresponding values.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
private readonly struct MaxOperator<T> : IBinaryOperator<T> |
|||
where T : INumber<T> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether this operation supports vector execution.
|
|||
/// </summary>
|
|||
public static bool Vectorizable => true; |
|||
|
|||
/// <summary>
|
|||
/// Selects the maximum scalar value.
|
|||
/// </summary>
|
|||
/// <param name="x">The first value.</param>
|
|||
/// <param name="y">The second value.</param>
|
|||
/// <returns>The maximum value.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static T Invoke(T x, T y) => T.Max(x, y); |
|||
|
|||
/// <summary>
|
|||
/// Selects the maximum values from 128-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The first values.</param>
|
|||
/// <param name="y">The second values.</param>
|
|||
/// <returns>The maximum values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> y) |
|||
{ |
|||
if (typeof(T) == typeof(float)) |
|||
{ |
|||
Vector128<float> result = MaxSingle( |
|||
Unsafe.As<Vector128<T>, Vector128<float>>(ref x), |
|||
Unsafe.As<Vector128<T>, Vector128<float>>(ref y)); |
|||
|
|||
return Unsafe.As<Vector128<float>, Vector128<T>>(ref result); |
|||
} |
|||
|
|||
if (typeof(T) == typeof(double)) |
|||
{ |
|||
Vector128<double> result = MaxDouble( |
|||
Unsafe.As<Vector128<T>, Vector128<double>>(ref x), |
|||
Unsafe.As<Vector128<T>, Vector128<double>>(ref y)); |
|||
|
|||
return Unsafe.As<Vector128<double>, Vector128<T>>(ref result); |
|||
} |
|||
|
|||
return Vector128.Max(x, y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects the maximum values from 256-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The first values.</param>
|
|||
/// <param name="y">The second values.</param>
|
|||
/// <returns>The maximum values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> y) |
|||
{ |
|||
if (typeof(T) == typeof(float)) |
|||
{ |
|||
Vector256<float> result = MaxSingle( |
|||
Unsafe.As<Vector256<T>, Vector256<float>>(ref x), |
|||
Unsafe.As<Vector256<T>, Vector256<float>>(ref y)); |
|||
|
|||
return Unsafe.As<Vector256<float>, Vector256<T>>(ref result); |
|||
} |
|||
|
|||
if (typeof(T) == typeof(double)) |
|||
{ |
|||
Vector256<double> result = MaxDouble( |
|||
Unsafe.As<Vector256<T>, Vector256<double>>(ref x), |
|||
Unsafe.As<Vector256<T>, Vector256<double>>(ref y)); |
|||
|
|||
return Unsafe.As<Vector256<double>, Vector256<T>>(ref result); |
|||
} |
|||
|
|||
return Vector256.Max(x, y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects the maximum values from 512-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The first values.</param>
|
|||
/// <param name="y">The second values.</param>
|
|||
/// <returns>The maximum values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> y) |
|||
{ |
|||
if (typeof(T) == typeof(float)) |
|||
{ |
|||
Vector512<float> result = MaxSingle( |
|||
Unsafe.As<Vector512<T>, Vector512<float>>(ref x), |
|||
Unsafe.As<Vector512<T>, Vector512<float>>(ref y)); |
|||
|
|||
return Unsafe.As<Vector512<float>, Vector512<T>>(ref result); |
|||
} |
|||
|
|||
if (typeof(T) == typeof(double)) |
|||
{ |
|||
Vector512<double> result = MaxDouble( |
|||
Unsafe.As<Vector512<T>, Vector512<double>>(ref x), |
|||
Unsafe.As<Vector512<T>, Vector512<double>>(ref y)); |
|||
|
|||
return Unsafe.As<Vector512<double>, Vector512<T>>(ref result); |
|||
} |
|||
|
|||
return Vector512.Max(x, y); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
internal static partial class TensorPrimitives_ |
|||
{ |
|||
/// <summary>
|
|||
/// Computes the element-wise product of the values in <paramref name="x"/> and <paramref name="y"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="x">The multiplicands.</param>
|
|||
/// <param name="y">The multiplier.</param>
|
|||
/// <param name="destination">The destination for the products.</param>
|
|||
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
|
|||
/// <exception cref="ArgumentException">
|
|||
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
|
|||
/// </exception>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Multiply<T>(ReadOnlySpan<T> x, T y, Span<T> destination) |
|||
where T : IMultiplyOperators<T, T, T>, IMultiplicativeIdentity<T, T> |
|||
=> InvokeSpanScalarIntoSpan<T, MultiplyOperator<T>>(x, y, destination); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies corresponding values.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
private readonly struct MultiplyOperator<T> : IBinaryOperator<T> |
|||
where T : IMultiplyOperators<T, T, T>, IMultiplicativeIdentity<T, T> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether this operation supports vector execution.
|
|||
/// </summary>
|
|||
public static bool Vectorizable => true; |
|||
|
|||
/// <summary>
|
|||
/// Multiplies scalar values.
|
|||
/// </summary>
|
|||
/// <param name="x">The multiplicand.</param>
|
|||
/// <param name="y">The multiplier.</param>
|
|||
/// <returns>The product.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static T Invoke(T x, T y) => x * y; |
|||
|
|||
/// <summary>
|
|||
/// Multiplies 128-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The multiplicands.</param>
|
|||
/// <param name="y">The multipliers.</param>
|
|||
/// <returns>The products.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector128<T> Invoke(Vector128<T> x, Vector128<T> y) => x * y; |
|||
|
|||
/// <summary>
|
|||
/// Multiplies 256-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The multiplicands.</param>
|
|||
/// <param name="y">The multipliers.</param>
|
|||
/// <returns>The products.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector256<T> Invoke(Vector256<T> x, Vector256<T> y) => x * y; |
|||
|
|||
/// <summary>
|
|||
/// Multiplies 512-bit vectors.
|
|||
/// </summary>
|
|||
/// <param name="x">The multiplicands.</param>
|
|||
/// <param name="y">The multipliers.</param>
|
|||
/// <returns>The products.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector512<T> Invoke(Vector512<T> x, Vector512<T> y) => x * y; |
|||
} |
|||
} |
|||
@ -0,0 +1,332 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
internal static partial class TensorPrimitives_ |
|||
{ |
|||
/// <summary>
|
|||
/// Defines an element-wise unary operation.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
private interface IUnaryOperator<T> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether the operation supports vector execution.
|
|||
/// </summary>
|
|||
public static abstract bool Vectorizable { get; } |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to a scalar value.
|
|||
/// </summary>
|
|||
/// <param name="x">The input value.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract T Invoke(T x); |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to a 128-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="x">The input vector.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract Vector128<T> Invoke(Vector128<T> x); |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to a 256-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="x">The input vector.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract Vector256<T> Invoke(Vector256<T> x); |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to a 512-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="x">The input vector.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract Vector512<T> Invoke(Vector512<T> x); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the element-wise negation of the values in <paramref name="x"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="x">The values to negate.</param>
|
|||
/// <param name="destination">The destination for the negated values.</param>
|
|||
/// <exception cref="ArgumentException"><paramref name="destination"/> is shorter than <paramref name="x"/>.</exception>
|
|||
/// <exception cref="ArgumentException">
|
|||
/// <paramref name="x"/> and <paramref name="destination"/> overlap without beginning at the same memory location.
|
|||
/// </exception>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Negate<T>(ReadOnlySpan<T> x, Span<T> destination) |
|||
where T : IUnaryNegationOperators<T, T> |
|||
=> InvokeSpanIntoSpan<T, NegateOperator<T>>(x, destination); |
|||
|
|||
/// <summary>
|
|||
/// Performs an element-wise unary operation over a span.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="x">The input values.</param>
|
|||
/// <param name="destination">The destination values.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeSpanIntoSpan<T, TOperator>(ReadOnlySpan<T> x, Span<T> destination) |
|||
where TOperator : struct, IUnaryOperator<T> |
|||
{ |
|||
if (x.Length > destination.Length) |
|||
{ |
|||
ThrowDestinationTooShort(); |
|||
} |
|||
|
|||
ValidateInputOutputSpanNonOverlapping(x, destination); |
|||
|
|||
ref T xRef = ref MemoryMarshal.GetReference(x); |
|||
ref T destinationRef = ref MemoryMarshal.GetReference(destination); |
|||
nuint length = (uint)x.Length; |
|||
|
|||
// Runtime main selects the widest supported pipeline once one complete vector is available.
|
|||
if (TOperator.Vectorizable |
|||
&& Vector512.IsHardwareAccelerated |
|||
&& Vector512<T>.IsSupported |
|||
&& length >= (uint)Vector512<T>.Count) |
|||
{ |
|||
InvokeUnaryVectorized512<T, TOperator>(ref xRef, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count) |
|||
{ |
|||
InvokeUnaryVectorized256<T, TOperator>(ref xRef, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count) |
|||
{ |
|||
InvokeUnaryVectorized128<T, TOperator>(ref xRef, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
for (nuint i = 0; i < length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a unary operation with 128-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first input element.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeUnaryVectorized128<T, TOperator>(ref T xRef, ref T destinationRef, nuint length) |
|||
where TOperator : struct, IUnaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector128<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
|
|||
// The final vector overlaps the preceding store when the length is not a vector multiple. Loading it
|
|||
// before any stores preserves same-start in-place operation because it captures the original tail.
|
|||
Vector128<T> end = default; |
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, length - vectorCount)); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a unary operation with 256-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first input element.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeUnaryVectorized256<T, TOperator>(ref T xRef, ref T destinationRef, nuint length) |
|||
where TOperator : struct, IUnaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector256<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
Vector256<T> end = default; |
|||
|
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, length - vectorCount)); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a unary operation with 512-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first input element.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeUnaryVectorized512<T, TOperator>(ref T xRef, ref T destinationRef, nuint length) |
|||
where TOperator : struct, IUnaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector512<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
Vector512<T> end = default; |
|||
|
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, length - vectorCount)); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements element-wise negation for scalar and SIMD inputs.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
private readonly struct NegateOperator<T> : IUnaryOperator<T> |
|||
where T : IUnaryNegationOperators<T, T> |
|||
{ |
|||
/// <inheritdoc />
|
|||
public static bool Vectorizable => true; |
|||
|
|||
/// <inheritdoc />
|
|||
public static T Invoke(T x) => -x; |
|||
|
|||
/// <inheritdoc />
|
|||
public static Vector128<T> Invoke(Vector128<T> x) |
|||
{ |
|||
if (typeof(T) == typeof(float)) |
|||
{ |
|||
// IEEE-754 negation toggles the sign bit. Expressing that operation explicitly avoids the
|
|||
// subtraction-based ARM64 code generated by .NET 8 for generic vector negation, which loses
|
|||
// the sign when +0F is negated and therefore differs from both scalar and runtime-main behavior.
|
|||
return x ^ Vector128.Create(-0F).As<float, T>(); |
|||
} |
|||
|
|||
if (typeof(T) == typeof(double)) |
|||
{ |
|||
// Double-precision values use the same sign-bit representation, with the sign in bit 63.
|
|||
return x ^ Vector128.Create(-0D).As<double, T>(); |
|||
} |
|||
|
|||
return -x; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public static Vector256<T> Invoke(Vector256<T> x) |
|||
{ |
|||
if (typeof(T) == typeof(float)) |
|||
{ |
|||
// Keep the operation bitwise at every width so ARM64 preserves signed zero exactly.
|
|||
return x ^ Vector256.Create(-0F).As<float, T>(); |
|||
} |
|||
|
|||
if (typeof(T) == typeof(double)) |
|||
{ |
|||
return x ^ Vector256.Create(-0D).As<double, T>(); |
|||
} |
|||
|
|||
return -x; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public static Vector512<T> Invoke(Vector512<T> x) |
|||
{ |
|||
if (typeof(T) == typeof(float)) |
|||
{ |
|||
// Vector512 can be hardware accelerated directly or decomposed by the runtime; the explicit
|
|||
// bit operation provides identical IEEE-754 behavior in either case.
|
|||
return x ^ Vector512.Create(-0F).As<float, T>(); |
|||
} |
|||
|
|||
if (typeof(T) == typeof(double)) |
|||
{ |
|||
return x ^ Vector512.Create(-0D).As<double, T>(); |
|||
} |
|||
|
|||
return -x; |
|||
} |
|||
} |
|||
} |
|||
@ -1,72 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Tuples; |
|||
|
|||
/// <summary>
|
|||
/// Contains 8 element value tuples of various types.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
internal struct Octet<T> |
|||
where T : unmanaged |
|||
{ |
|||
public T V0; |
|||
public T V1; |
|||
public T V2; |
|||
public T V3; |
|||
public T V4; |
|||
public T V5; |
|||
public T V6; |
|||
public T V7; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override readonly string ToString() |
|||
{ |
|||
return $"Octet<{typeof(T)}>({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})"; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Extension methods for the <see cref="Octet{T}"/> type.
|
|||
/// </summary>
|
|||
internal static class OctetExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Loads the fields in a target <see cref="Octet{T}"/> of <see cref="uint"/> from one of <see cref="byte"/> type.
|
|||
/// </summary>
|
|||
/// <param name="destination">The target <see cref="Octet{T}"/> of <see cref="uint"/> instance.</param>
|
|||
/// <param name="source">The source <see cref="Octet{T}"/> of <see cref="byte"/> instance.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static void LoadFrom(ref this Octet<uint> destination, ref Octet<byte> source) |
|||
{ |
|||
destination.V0 = source.V0; |
|||
destination.V1 = source.V1; |
|||
destination.V2 = source.V2; |
|||
destination.V3 = source.V3; |
|||
destination.V4 = source.V4; |
|||
destination.V5 = source.V5; |
|||
destination.V6 = source.V6; |
|||
destination.V7 = source.V7; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Loads the fields in a target <see cref="Octet{T}"/> of <see cref="byte"/> from one of <see cref="uint"/> type.
|
|||
/// </summary>
|
|||
/// <param name="destination">The target <see cref="Octet{T}"/> of <see cref="byte"/> instance.</param>
|
|||
/// <param name="source">The source <see cref="Octet{T}"/> of <see cref="uint"/> instance.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static void LoadFrom(ref this Octet<byte> destination, ref Octet<uint> source) |
|||
{ |
|||
destination.V0 = (byte)source.V0; |
|||
destination.V1 = (byte)source.V1; |
|||
destination.V2 = (byte)source.V2; |
|||
destination.V3 = (byte)source.V3; |
|||
destination.V4 = (byte)source.V4; |
|||
destination.V5 = (byte)source.V5; |
|||
destination.V6 = (byte)source.V6; |
|||
destination.V7 = (byte)source.V7; |
|||
} |
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Compression.Zlib; |
|||
|
|||
/// <summary>
|
|||
/// A read-only stream over a sequence of length-delimited segments. Bytes are
|
|||
/// pulled from the inner stream up to the current segment's remaining length;
|
|||
/// when the segment is exhausted the supplied delegate is invoked to advance
|
|||
/// to the next segment and return its length. The inner stream is not owned
|
|||
/// and is not disposed.
|
|||
/// </summary>
|
|||
internal sealed class ChunkedReadStream : Stream |
|||
{ |
|||
private static readonly Func<int> GetDataNoOp = () => 0; |
|||
|
|||
private readonly BufferedReadStream innerStream; |
|||
private readonly Func<int> getData; |
|||
private int currentDataRemaining; |
|||
|
|||
public ChunkedReadStream(BufferedReadStream innerStream) |
|||
: this(innerStream, GetDataNoOp) |
|||
{ |
|||
} |
|||
|
|||
public ChunkedReadStream(BufferedReadStream innerStream, Func<int> getData) |
|||
{ |
|||
this.innerStream = innerStream; |
|||
this.getData = getData; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool CanRead => this.innerStream.CanRead; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool CanSeek => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool CanWrite => throw new NotSupportedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override long Length => throw new NotSupportedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } |
|||
|
|||
/// <summary>
|
|||
/// Sets the number of bytes available to read from the current segment.
|
|||
/// Must be called before reading each segment.
|
|||
/// </summary>
|
|||
public void SetCurrentSegmentLength(int bytes) => this.currentDataRemaining = bytes; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Flush() => throw new NotSupportedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int ReadByte() |
|||
{ |
|||
if (this.currentDataRemaining is 0) |
|||
{ |
|||
this.currentDataRemaining = this.getData(); |
|||
if (this.currentDataRemaining is 0) |
|||
{ |
|||
return -1; |
|||
} |
|||
} |
|||
|
|||
int value = this.innerStream.ReadByte(); |
|||
if (value is not -1) |
|||
{ |
|||
this.currentDataRemaining--; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int Read(byte[] buffer, int offset, int count) |
|||
{ |
|||
// Decrement currentDataRemaining only by bytes actually returned by
|
|||
// innerStream.Read; a short read otherwise underflows the segment
|
|||
// counter and triggers getData() before the segment is truly drained.
|
|||
int totalBytesRead = 0; |
|||
while (totalBytesRead < count) |
|||
{ |
|||
if (this.currentDataRemaining is 0) |
|||
{ |
|||
this.currentDataRemaining = this.getData(); |
|||
if (this.currentDataRemaining is 0) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
|
|||
int bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining); |
|||
int bytesRead = this.innerStream.Read(buffer, offset + totalBytesRead, bytesToRead); |
|||
if (bytesRead is 0) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
this.currentDataRemaining -= bytesRead; |
|||
totalBytesRead += bytesRead; |
|||
} |
|||
|
|||
return totalBytesRead; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void SetLength(long value) => throw new NotSupportedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); |
|||
} |
|||
@ -0,0 +1,125 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.IO.Compression; |
|||
using SixLabors.ImageSharp.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Compression.Zlib; |
|||
|
|||
/// <summary>
|
|||
/// Reads chunked input, parses the zlib CMF/FLG header, and exposes a
|
|||
/// <see cref="DeflateStream"/> over the remaining DEFLATE payload. The
|
|||
/// Adler-32 trailer is not validated.
|
|||
/// </summary>
|
|||
internal sealed class ZlibInflateReader : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Used to read the Adler-32 and Crc-32 checksums.
|
|||
/// We don't actually use this for anything so it doesn't
|
|||
/// have to be threadsafe.
|
|||
/// </summary>
|
|||
private static readonly byte[] ChecksumBuffer = new byte[4]; |
|||
|
|||
private readonly ChunkedReadStream segmentStream; |
|||
|
|||
public ZlibInflateReader(BufferedReadStream innerStream) |
|||
=> this.segmentStream = new ChunkedReadStream(innerStream); |
|||
|
|||
public ZlibInflateReader(BufferedReadStream innerStream, Func<int> getData) |
|||
=> this.segmentStream = new ChunkedReadStream(innerStream, getData); |
|||
|
|||
/// <summary>
|
|||
/// Gets the compressed stream over the deframed inner stream.
|
|||
/// </summary>
|
|||
public DeflateStream? CompressedStream { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Sets the length of the next segment of compressed input and, on first
|
|||
/// call, parses the zlib header.
|
|||
/// </summary>
|
|||
/// <param name="bytes">The remaining data length for the current segment.</param>
|
|||
/// <param name="isCriticalChunk">Whether to throw on a malformed zlib header.</param>
|
|||
/// <returns>The <see cref="bool"/>.</returns>
|
|||
[MemberNotNullWhen(true, nameof(CompressedStream))] |
|||
public bool AllocateNewBytes(int bytes, bool isCriticalChunk) |
|||
{ |
|||
this.segmentStream.SetCurrentSegmentLength(bytes); |
|||
if (this.CompressedStream is null) |
|||
{ |
|||
return this.InitializeInflateStream(isCriticalChunk); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
this.CompressedStream?.Dispose(); |
|||
this.segmentStream?.Dispose(); |
|||
} |
|||
|
|||
[MemberNotNullWhen(true, nameof(CompressedStream))] |
|||
private bool InitializeInflateStream(bool isCriticalChunk) |
|||
{ |
|||
// Read the zlib header : http://tools.ietf.org/html/rfc1950
|
|||
// CMF(Compression Method and flags)
|
|||
// This byte is divided into a 4 - bit compression method and a
|
|||
// 4-bit information field depending on the compression method.
|
|||
// bits 0 to 3 CM Compression method
|
|||
// bits 4 to 7 CINFO Compression info
|
|||
//
|
|||
// 0 1
|
|||
// +---+---+
|
|||
// |CMF|FLG|
|
|||
// +---+---+
|
|||
int cmf = this.segmentStream.ReadByte(); |
|||
int flag = this.segmentStream.ReadByte(); |
|||
if (cmf == -1 || flag == -1) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if ((cmf & 0x0F) == 8) |
|||
{ |
|||
// CINFO is the base-2 logarithm of the LZ77 window size, minus eight.
|
|||
int cinfo = (cmf & 0xF0) >> 4; |
|||
|
|||
if (cinfo > 7) |
|||
{ |
|||
if (isCriticalChunk) |
|||
{ |
|||
// Values of CINFO above 7 are not allowed in RFC1950.
|
|||
// CINFO is not defined in this specification for CM not equal to 8.
|
|||
throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}"); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
else if (isCriticalChunk) |
|||
{ |
|||
throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}"); |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
// The preset dictionary.
|
|||
bool fdict = (flag & 32) != 0; |
|||
if (fdict) |
|||
{ |
|||
// We don't need this for inflate so simply skip by the next four bytes.
|
|||
// https://tools.ietf.org/html/rfc1950#page-6
|
|||
if (this.segmentStream.Read(ChecksumBuffer, 0, 4) != 4) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
this.CompressedStream = new DeflateStream(this.segmentStream, CompressionMode.Decompress, leaveOpen: true); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -1,277 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.IO.Compression; |
|||
using SixLabors.ImageSharp.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.Compression.Zlib; |
|||
|
|||
/// <summary>
|
|||
/// Provides methods and properties for deframing streams from PNGs.
|
|||
/// </summary>
|
|||
internal sealed class ZlibInflateStream : Stream |
|||
{ |
|||
/// <summary>
|
|||
/// Used to read the Adler-32 and Crc-32 checksums.
|
|||
/// We don't actually use this for anything so it doesn't
|
|||
/// have to be threadsafe.
|
|||
/// </summary>
|
|||
private static readonly byte[] ChecksumBuffer = new byte[4]; |
|||
|
|||
/// <summary>
|
|||
/// A default delegate to get more data from the inner stream.
|
|||
/// </summary>
|
|||
private static readonly Func<int> GetDataNoOp = () => 0; |
|||
|
|||
/// <summary>
|
|||
/// The inner raw memory stream.
|
|||
/// </summary>
|
|||
private readonly BufferedReadStream innerStream; |
|||
|
|||
/// <summary>
|
|||
/// A value indicating whether this instance of the given entity has been disposed.
|
|||
/// </summary>
|
|||
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
|
|||
/// <remarks>
|
|||
/// If the entity is disposed, it must not be disposed a second
|
|||
/// time. The isDisposed field is set the first time the entity
|
|||
/// is disposed. If the isDisposed field is true, then the Dispose()
|
|||
/// method will not dispose again. This help not to prolong the entity's
|
|||
/// life in the Garbage Collector.
|
|||
/// </remarks>
|
|||
private bool isDisposed; |
|||
|
|||
/// <summary>
|
|||
/// The current data remaining to be read.
|
|||
/// </summary>
|
|||
private int currentDataRemaining; |
|||
|
|||
/// <summary>
|
|||
/// Delegate to get more data once we've exhausted the current data remaining.
|
|||
/// </summary>
|
|||
private readonly Func<int> getData; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
|
|||
/// </summary>
|
|||
/// <param name="innerStream">The inner raw stream.</param>
|
|||
public ZlibInflateStream(BufferedReadStream innerStream) |
|||
: this(innerStream, GetDataNoOp) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
|
|||
/// </summary>
|
|||
/// <param name="innerStream">The inner raw stream.</param>
|
|||
/// <param name="getData">A delegate to get more data from the inner stream.</param>
|
|||
public ZlibInflateStream(BufferedReadStream innerStream, Func<int> getData) |
|||
{ |
|||
this.innerStream = innerStream; |
|||
this.getData = getData; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool CanRead => this.innerStream.CanRead; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool CanSeek => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool CanWrite => throw new NotSupportedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override long Length => throw new NotSupportedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } |
|||
|
|||
/// <summary>
|
|||
/// Gets the compressed stream over the deframed inner stream.
|
|||
/// </summary>
|
|||
public DeflateStream? CompressedStream { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Adds new bytes from a frame found in the original stream.
|
|||
/// </summary>
|
|||
/// <param name="bytes">The current remaining data according to the chunk length.</param>
|
|||
/// <param name="isCriticalChunk">Whether the chunk to be inflated is a critical chunk.</param>
|
|||
/// <returns>The <see cref="bool"/>.</returns>
|
|||
[MemberNotNullWhen(true, nameof(CompressedStream))] |
|||
public bool AllocateNewBytes(int bytes, bool isCriticalChunk) |
|||
{ |
|||
this.currentDataRemaining = bytes; |
|||
if (this.CompressedStream is null) |
|||
{ |
|||
return this.InitializeInflateStream(isCriticalChunk); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Flush() => throw new NotSupportedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int ReadByte() |
|||
{ |
|||
this.currentDataRemaining--; |
|||
return this.innerStream.ReadByte(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int Read(byte[] buffer, int offset, int count) |
|||
{ |
|||
if (this.currentDataRemaining is 0) |
|||
{ |
|||
// Last buffer was read in its entirety, let's make sure we don't actually have more in additional IDAT chunks.
|
|||
this.currentDataRemaining = this.getData(); |
|||
|
|||
if (this.currentDataRemaining is 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
} |
|||
|
|||
int bytesToRead = Math.Min(count, this.currentDataRemaining); |
|||
this.currentDataRemaining -= bytesToRead; |
|||
int totalBytesRead = this.innerStream.Read(buffer, offset, bytesToRead); |
|||
long innerStreamLength = this.innerStream.Length; |
|||
|
|||
// Keep reading data until we've reached the end of the stream or filled the buffer.
|
|||
int bytesRead = 0; |
|||
offset += totalBytesRead; |
|||
while (this.currentDataRemaining is 0 && totalBytesRead < count) |
|||
{ |
|||
this.currentDataRemaining = this.getData(); |
|||
|
|||
if (this.currentDataRemaining is 0) |
|||
{ |
|||
return totalBytesRead; |
|||
} |
|||
|
|||
offset += bytesRead; |
|||
|
|||
if (offset >= innerStreamLength || offset >= count) |
|||
{ |
|||
return totalBytesRead; |
|||
} |
|||
|
|||
bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining); |
|||
this.currentDataRemaining -= bytesToRead; |
|||
bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); |
|||
if (bytesRead == 0) |
|||
{ |
|||
return totalBytesRead; |
|||
} |
|||
|
|||
totalBytesRead += bytesRead; |
|||
} |
|||
|
|||
return totalBytesRead; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void SetLength(long value) => throw new NotSupportedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
if (this.isDisposed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (disposing) |
|||
{ |
|||
// Dispose managed resources.
|
|||
if (this.CompressedStream != null) |
|||
{ |
|||
this.CompressedStream.Dispose(); |
|||
this.CompressedStream = null; |
|||
} |
|||
} |
|||
|
|||
base.Dispose(disposing); |
|||
|
|||
// Call the appropriate methods to clean up
|
|||
// unmanaged resources here.
|
|||
// Note disposing is done.
|
|||
this.isDisposed = true; |
|||
} |
|||
|
|||
[MemberNotNullWhen(true, nameof(CompressedStream))] |
|||
private bool InitializeInflateStream(bool isCriticalChunk) |
|||
{ |
|||
// Read the zlib header : http://tools.ietf.org/html/rfc1950
|
|||
// CMF(Compression Method and flags)
|
|||
// This byte is divided into a 4 - bit compression method and a
|
|||
// 4-bit information field depending on the compression method.
|
|||
// bits 0 to 3 CM Compression method
|
|||
// bits 4 to 7 CINFO Compression info
|
|||
//
|
|||
// 0 1
|
|||
// +---+---+
|
|||
// |CMF|FLG|
|
|||
// +---+---+
|
|||
int cmf = this.innerStream.ReadByte(); |
|||
int flag = this.innerStream.ReadByte(); |
|||
this.currentDataRemaining -= 2; |
|||
if (cmf == -1 || flag == -1) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if ((cmf & 0x0F) == 8) |
|||
{ |
|||
// CINFO is the base-2 logarithm of the LZ77 window size, minus eight.
|
|||
int cinfo = (cmf & 0xF0) >> 4; |
|||
|
|||
if (cinfo > 7) |
|||
{ |
|||
if (isCriticalChunk) |
|||
{ |
|||
// Values of CINFO above 7 are not allowed in RFC1950.
|
|||
// CINFO is not defined in this specification for CM not equal to 8.
|
|||
throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}"); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
else if (isCriticalChunk) |
|||
{ |
|||
throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}"); |
|||
} |
|||
else |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
// The preset dictionary.
|
|||
bool fdict = (flag & 32) != 0; |
|||
if (fdict) |
|||
{ |
|||
// We don't need this for inflate so simply skip by the next four bytes.
|
|||
// https://tools.ietf.org/html/rfc1950#page-6
|
|||
if (this.innerStream.Read(ChecksumBuffer, 0, 4) != 4) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
this.currentDataRemaining -= 4; |
|||
} |
|||
|
|||
// Initialize the deflate BufferedReadStream.
|
|||
this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; |
|||
|
|||
/// <summary>
|
|||
/// Compressor for EXR image data which does not use any compression method.
|
|||
/// </summary>
|
|||
internal class NoneExrCompressor : ExrBaseCompressor |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="NoneExrCompressor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="output">The output stream to write the compressed image data to.</param>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">Bytes per row block.</param>
|
|||
/// <param name="bytesPerRow">Bytes per pixel row.</param>
|
|||
/// <param name="rowsPerBlock">The pixel rows per block.</param>
|
|||
/// <param name="width">The witdh of one row in pixels.</param>
|
|||
public NoneExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) |
|||
: base(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override uint CompressRowBlock(Span<byte> rows, int rowCount) |
|||
{ |
|||
this.Output.Write(rows); |
|||
return (uint)rows.Length; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Compression.Zlib; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; |
|||
|
|||
/// <summary>
|
|||
/// Compressor for EXR image data using the ZIP compression.
|
|||
/// </summary>
|
|||
internal class ZipExrCompressor : ExrBaseCompressor |
|||
{ |
|||
private readonly DeflateCompressionLevel compressionLevel; |
|||
|
|||
private readonly MemoryStream memoryStream; |
|||
|
|||
private readonly System.Buffers.IMemoryOwner<byte> buffer; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ZipExrCompressor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="output">The stream to write the compressed data to.</param>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
/// <param name="rowsPerBlock">The pixel rows per block.</param>
|
|||
/// <param name="width">The witdh of one row in pixels.</param>
|
|||
/// <param name="compressionLevel">The compression level for deflate compression.</param>
|
|||
public ZipExrCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, DeflateCompressionLevel compressionLevel) |
|||
: base(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) |
|||
{ |
|||
this.compressionLevel = compressionLevel; |
|||
this.buffer = allocator.Allocate<byte>((int)bytesPerBlock); |
|||
this.memoryStream = new(); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override uint CompressRowBlock(Span<byte> rows, int rowCount) |
|||
{ |
|||
// Re-oder pixel values.
|
|||
Span<byte> reordered = this.buffer.GetSpan()[..(int)(rowCount * this.BytesPerRow)]; |
|||
int n = reordered.Length; |
|||
int t1 = 0; |
|||
int t2 = (n + 1) >> 1; |
|||
for (int i = 0; i < n; i++) |
|||
{ |
|||
bool isOdd = (i & 1) == 1; |
|||
reordered[isOdd ? t2++ : t1++] = rows[i]; |
|||
} |
|||
|
|||
// Predictor.
|
|||
Span<byte> predicted = reordered; |
|||
byte p = predicted[0]; |
|||
for (int i = 1; i < predicted.Length; i++) |
|||
{ |
|||
int d = (predicted[i] - p + 128 + 256) & 255; |
|||
p = predicted[i]; |
|||
predicted[i] = (byte)d; |
|||
} |
|||
|
|||
this.memoryStream.Seek(0, SeekOrigin.Begin); |
|||
using (ZlibDeflateStream stream = new(this.Allocator, this.memoryStream, this.compressionLevel)) |
|||
{ |
|||
stream.Write(predicted); |
|||
stream.Flush(); |
|||
} |
|||
|
|||
int size = (int)this.memoryStream.Position; |
|||
byte[] buffer = this.memoryStream.GetBuffer(); |
|||
this.Output.Write(buffer, 0, size); |
|||
|
|||
// Reset memory stream for next pixel row.
|
|||
this.memoryStream.Seek(0, SeekOrigin.Begin); |
|||
this.memoryStream.SetLength(0); |
|||
|
|||
return (uint)size; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
this.buffer.Dispose(); |
|||
this.memoryStream?.Dispose(); |
|||
} |
|||
} |
|||
@ -0,0 +1,205 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; |
|||
|
|||
/// <summary>
|
|||
/// Implementation of B44 decompressor for EXR image data.
|
|||
/// </summary>
|
|||
internal class B44ExrCompression : ExrBaseDecompressor |
|||
{ |
|||
private readonly int channelCount; |
|||
|
|||
private readonly byte[] scratch = new byte[14]; |
|||
|
|||
private readonly ushort[] s = new ushort[16]; |
|||
|
|||
private readonly IMemoryOwner<ushort> tmpBuffer; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="B44ExrCompression" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
/// <param name="rowsPerBlock">The pixel rows per block.</param>
|
|||
/// <param name="width">The width of a pixel row in pixels.</param>
|
|||
/// <param name="channelCount">The number of channels of the image.</param>
|
|||
public B44ExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount) |
|||
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) |
|||
{ |
|||
this.channelCount = channelCount; |
|||
this.tmpBuffer = allocator.Allocate<ushort>((int)(width * rowsPerBlock * channelCount)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer) |
|||
{ |
|||
Span<ushort> outputBuffer = MemoryMarshal.Cast<byte, ushort>(buffer); |
|||
Span<ushort> decompressed = this.tmpBuffer.GetSpan(); |
|||
int outputOffset = 0; |
|||
int bytesLeft = (int)compressedBytes; |
|||
for (int i = 0; i < this.channelCount && bytesLeft > 0; i++) |
|||
{ |
|||
for (int y = 0; y < this.RowsPerBlock; y += 4) |
|||
{ |
|||
Span<ushort> row0 = decompressed.Slice(outputOffset, this.Width); |
|||
outputOffset += this.Width; |
|||
Span<ushort> row1 = decompressed.Slice(outputOffset, this.Width); |
|||
outputOffset += this.Width; |
|||
Span<ushort> row2 = decompressed.Slice(outputOffset, this.Width); |
|||
outputOffset += this.Width; |
|||
Span<ushort> row3 = decompressed.Slice(outputOffset, this.Width); |
|||
outputOffset += this.Width; |
|||
|
|||
int rowOffset = 0; |
|||
for (int x = 0; x < this.Width && bytesLeft > 0; x += 4) |
|||
{ |
|||
int bytesRead = stream.Read(this.scratch, 0, 3); |
|||
if (bytesRead == 0) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); |
|||
} |
|||
|
|||
// Check if 3-byte encoded flat field.
|
|||
if (this.scratch[2] >= 13 << 2) |
|||
{ |
|||
Unpack3(this.scratch, this.s); |
|||
bytesLeft -= 3; |
|||
} |
|||
else |
|||
{ |
|||
bytesRead = stream.Read(this.scratch, 3, 11); |
|||
if (bytesRead == 0) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data from the stream!"); |
|||
} |
|||
|
|||
Unpack14(this.scratch, this.s); |
|||
bytesLeft -= 14; |
|||
} |
|||
|
|||
int n = x + 3 < this.Width ? 4 : this.Width - x; |
|||
if (y + 3 < this.RowsPerBlock) |
|||
{ |
|||
this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]); |
|||
this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]); |
|||
this.s.AsSpan(8, n).CopyTo(row2[rowOffset..]); |
|||
this.s.AsSpan(12, n).CopyTo(row3[rowOffset..]); |
|||
} |
|||
else |
|||
{ |
|||
this.s.AsSpan(0, n).CopyTo(row0[rowOffset..]); |
|||
if (y + 1 < this.RowsPerBlock) |
|||
{ |
|||
this.s.AsSpan(4, n).CopyTo(row1[rowOffset..]); |
|||
} |
|||
|
|||
if (y + 2 < this.RowsPerBlock) |
|||
{ |
|||
this.s.AsSpan(8, n).CopyTo(row2[rowOffset..]); |
|||
} |
|||
} |
|||
|
|||
rowOffset += 4; |
|||
} |
|||
|
|||
if (bytesLeft <= 0) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Rearrange the decompressed data such that the data for each scan line form a contiguous block.
|
|||
int offsetDecompressed = 0; |
|||
int offsetOutput = 0; |
|||
int blockSize = (int)(this.Width * this.RowsPerBlock); |
|||
for (int y = 0; y < this.RowsPerBlock; y++) |
|||
{ |
|||
for (int i = 0; i < this.channelCount; i++) |
|||
{ |
|||
decompressed.Slice(offsetDecompressed + (i * blockSize), this.Width).CopyTo(outputBuffer[offsetOutput..]); |
|||
offsetOutput += this.Width; |
|||
} |
|||
|
|||
offsetDecompressed += this.Width; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Unpack a 14-byte block into 4 by 4 16-bit pixels.
|
|||
/// </summary>
|
|||
/// <param name="b">The source byte data to unpack.</param>
|
|||
/// <param name="s">Destintation buffer.</param>
|
|||
private static void Unpack14(Span<byte> b, Span<ushort> s) |
|||
{ |
|||
s[0] = (ushort)((b[0] << 8) | b[1]); |
|||
|
|||
ushort shift = (ushort)(b[2] >> 2); |
|||
ushort bias = (ushort)(0x20u << shift); |
|||
|
|||
s[4] = (ushort)(s[0] + ((((b[2] << 4) | (b[3] >> 4)) & 0x3fu) << shift) - bias); |
|||
s[8] = (ushort)(s[4] + ((((b[3] << 2) | (b[4] >> 6)) & 0x3fu) << shift) - bias); |
|||
s[12] = (ushort)(s[8] + ((b[4] & 0x3fu) << shift) - bias); |
|||
|
|||
s[1] = (ushort)(s[0] + ((uint)(b[5] >> 2) << shift) - bias); |
|||
s[5] = (ushort)(s[4] + ((((b[5] << 4) | (b[6] >> 4)) & 0x3fu) << shift) - bias); |
|||
s[9] = (ushort)(s[8] + ((((b[6] << 2) | (b[7] >> 6)) & 0x3fu) << shift) - bias); |
|||
s[13] = (ushort)(s[12] + ((b[7] & 0x3fu) << shift) - bias); |
|||
|
|||
s[2] = (ushort)(s[1] + ((uint)(b[8] >> 2) << shift) - bias); |
|||
s[6] = (ushort)(s[5] + ((((b[8] << 4) | (b[9] >> 4)) & 0x3fu) << shift) - bias); |
|||
s[10] = (ushort)(s[9] + ((((b[9] << 2) | (b[10] >> 6)) & 0x3fu) << shift) - bias); |
|||
s[14] = (ushort)(s[13] + ((b[10] & 0x3fu) << shift) - bias); |
|||
|
|||
s[3] = (ushort)(s[2] + ((uint)(b[11] >> 2) << shift) - bias); |
|||
s[7] = (ushort)(s[6] + ((((b[11] << 4) | (b[12] >> 4)) & 0x3fu) << shift) - bias); |
|||
s[11] = (ushort)(s[10] + ((((b[12] << 2) | (b[13] >> 6)) & 0x3fu) << shift) - bias); |
|||
s[15] = (ushort)(s[14] + ((b[13] & 0x3fu) << shift) - bias); |
|||
|
|||
for (int i = 0; i < 16; ++i) |
|||
{ |
|||
if ((s[i] & 0x8000) != 0) |
|||
{ |
|||
s[i] &= 0x7fff; |
|||
} |
|||
else |
|||
{ |
|||
s[i] = (ushort)~s[i]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// // Unpack a 3-byte block into 4 by 4 identical 16-bit pixels.
|
|||
/// </summary>
|
|||
/// <param name="b">The source byte data to unpack.</param>
|
|||
/// <param name="s">The destination buffer.</param>
|
|||
private static void Unpack3(Span<byte> b, Span<ushort> s) |
|||
{ |
|||
s[0] = (ushort)((b[0] << 8) | b[1]); |
|||
|
|||
if ((s[0] & 0x8000) != 0) |
|||
{ |
|||
s[0] &= 0x7fff; |
|||
} |
|||
else |
|||
{ |
|||
s[0] = (ushort)~s[0]; |
|||
} |
|||
|
|||
for (int i = 1; i < 16; ++i) |
|||
{ |
|||
s[i] = s[0]; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; |
|||
|
|||
/// <summary>
|
|||
/// Decompressor for EXR image data which do not use any compression.
|
|||
/// </summary>
|
|||
internal class NoneExrCompression : ExrBaseDecompressor |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="NoneExrCompression" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per pixel row.</param>
|
|||
/// <param name="rowsPerBlock">The pixel rows per block.</param>
|
|||
/// <param name="width">The number of pixels per row.</param>
|
|||
public NoneExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) |
|||
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer) |
|||
{ |
|||
int bytesRead = stream.Read(buffer, 0, Math.Min(buffer.Length, (int)this.BytesPerBlock)); |
|||
if (bytesRead != (int)this.BytesPerBlock) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough pixel data from the stream!"); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,153 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; |
|||
|
|||
/// <summary>
|
|||
/// Implementation of PXR24 decompressor for EXR image data.
|
|||
/// </summary>
|
|||
internal class Pxr24Compression : ExrBaseDecompressor |
|||
{ |
|||
private readonly IMemoryOwner<byte> tmpBuffer; |
|||
|
|||
private readonly int channelCount; |
|||
|
|||
private readonly ExrPixelType pixelType; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Pxr24Compression" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per pixel row.</param>
|
|||
/// <param name="rowsPerBlock">The pixel rows per block.</param>
|
|||
/// <param name="width">The witdh of one row in pixels.</param>
|
|||
/// <param name="channelCount">The number of channels for a pixel.</param>
|
|||
/// <param name="pixelType">The pixel type.</param>
|
|||
public Pxr24Compression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width, int channelCount, ExrPixelType pixelType) |
|||
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) |
|||
{ |
|||
this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock); |
|||
this.channelCount = channelCount; |
|||
this.pixelType = pixelType; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer) |
|||
{ |
|||
Span<byte> uncompressed = this.tmpBuffer.GetSpan(); |
|||
Span<ushort> outputBufferHalf = MemoryMarshal.Cast<byte, ushort>(buffer); |
|||
Span<uint> outputBufferFloat = MemoryMarshal.Cast<byte, uint>(buffer); |
|||
Span<uint> outputBufferUint = MemoryMarshal.Cast<byte, uint>(buffer); |
|||
|
|||
uint uncompressedBytes = this.BytesPerBlock; |
|||
UndoZipCompression(stream, compressedBytes, uncompressed, uncompressedBytes); |
|||
|
|||
int lastIn = 0; |
|||
int outputOffset = 0; |
|||
for (int y = 0; y < this.RowsPerBlock; y++) |
|||
{ |
|||
for (int c = 0; c < this.channelCount; c++) |
|||
{ |
|||
switch (this.pixelType) |
|||
{ |
|||
case ExrPixelType.UnsignedInt: |
|||
{ |
|||
int offsetT0 = lastIn; |
|||
lastIn += this.Width; |
|||
int offsetT1 = lastIn; |
|||
lastIn += this.Width; |
|||
int offsetT2 = lastIn; |
|||
lastIn += this.Width; |
|||
int offsetT3 = lastIn; |
|||
lastIn += this.Width; |
|||
|
|||
uint pixel = 0; |
|||
for (int x = 0; x < this.Width; x++) |
|||
{ |
|||
uint t0 = uncompressed[offsetT0]; |
|||
uint t1 = uncompressed[offsetT1]; |
|||
uint t2 = uncompressed[offsetT2]; |
|||
uint t3 = uncompressed[offsetT3]; |
|||
uint diff = (t0 << 24) | (t1 << 16) | (t2 << 8) | t3; |
|||
|
|||
pixel += diff; |
|||
outputBufferUint[outputOffset] = pixel; |
|||
|
|||
offsetT0++; |
|||
offsetT1++; |
|||
offsetT2++; |
|||
offsetT3++; |
|||
outputOffset++; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case ExrPixelType.Half: |
|||
{ |
|||
int offsetT0 = lastIn; |
|||
lastIn += this.Width; |
|||
int offsetT1 = lastIn; |
|||
lastIn += this.Width; |
|||
|
|||
uint pixel = 0; |
|||
for (int x = 0; x < this.Width; x++) |
|||
{ |
|||
uint t0 = uncompressed[offsetT0]; |
|||
uint t1 = uncompressed[offsetT1]; |
|||
uint diff = (t0 << 8) | t1; |
|||
|
|||
pixel += diff; |
|||
outputBufferHalf[outputOffset] = (ushort)pixel; |
|||
|
|||
offsetT0++; |
|||
offsetT1++; |
|||
outputOffset++; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case ExrPixelType.Float: |
|||
{ |
|||
int offsetT0 = lastIn; |
|||
lastIn += this.Width; |
|||
int offsetT1 = lastIn; |
|||
lastIn += this.Width; |
|||
int offsetT2 = lastIn; |
|||
lastIn += this.Width; |
|||
|
|||
uint pixel = 0; |
|||
for (int x = 0; x < this.Width; x++) |
|||
{ |
|||
uint t0 = uncompressed[offsetT0]; |
|||
uint t1 = uncompressed[offsetT1]; |
|||
uint t2 = uncompressed[offsetT2]; |
|||
uint diff = (t0 << 24) | (t1 << 16) | (t2 << 8); |
|||
|
|||
pixel += diff; |
|||
outputBufferFloat[outputOffset] = pixel; |
|||
|
|||
offsetT0++; |
|||
offsetT1++; |
|||
offsetT2++; |
|||
outputOffset++; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; |
|||
|
|||
/// <summary>
|
|||
/// Implementation of RLE decompressor for EXR images.
|
|||
/// </summary>
|
|||
internal class RunLengthExrCompression : ExrBaseDecompressor |
|||
{ |
|||
private readonly IMemoryOwner<byte> tmpBuffer; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RunLengthExrCompression" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
/// <param name="rowsPerBlock">The pixel rows per block.</param>
|
|||
/// <param name="width">The witdh of one row in pixels.</param>
|
|||
public RunLengthExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) |
|||
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) => this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer) |
|||
{ |
|||
Span<byte> uncompressed = this.tmpBuffer.GetSpan(); |
|||
int maxLength = (int)this.BytesPerBlock; |
|||
int offset = 0; |
|||
while (compressedBytes > 0) |
|||
{ |
|||
byte nextByte = ReadNextByte(stream); |
|||
|
|||
sbyte input = (sbyte)nextByte; |
|||
if (input < 0) |
|||
{ |
|||
int count = -input; |
|||
compressedBytes -= (uint)(count + 1); |
|||
|
|||
if ((maxLength -= count) < 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
uncompressed[offset + i] = ReadNextByte(stream); |
|||
} |
|||
|
|||
offset += count; |
|||
} |
|||
else |
|||
{ |
|||
int count = input; |
|||
byte value = ReadNextByte(stream); |
|||
compressedBytes -= 2; |
|||
|
|||
if ((maxLength -= count + 1) < 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
for (int i = 0; i < count + 1; i++) |
|||
{ |
|||
uncompressed[offset + i] = value; |
|||
} |
|||
|
|||
offset += count + 1; |
|||
} |
|||
} |
|||
|
|||
Reconstruct(uncompressed, this.BytesPerBlock); |
|||
Interleave(uncompressed, this.BytesPerBlock, buffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the next byte from the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream.</param>
|
|||
/// <returns>The next byte.</returns>
|
|||
private static byte ReadNextByte(BufferedReadStream stream) |
|||
{ |
|||
int nextByte = stream.ReadByte(); |
|||
if (nextByte == -1) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Not enough data to decompress RLE encoded EXR image!"); |
|||
} |
|||
|
|||
return (byte)nextByte; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; |
|||
|
|||
/// <summary>
|
|||
/// Implementation of zhe Zip decompressor for EXR image data.
|
|||
/// </summary>
|
|||
internal class ZipExrCompression : ExrBaseDecompressor |
|||
{ |
|||
private readonly IMemoryOwner<byte> tmpBuffer; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ZipExrCompression" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per pixel row block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per pixel row.</param>
|
|||
/// <param name="rowsPerBlock">The pixel rows per block.</param>
|
|||
/// <param name="width">The witdh of one row in pixels.</param>
|
|||
public ZipExrCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) |
|||
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) => this.tmpBuffer = allocator.Allocate<byte>((int)bytesPerBlock); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer) |
|||
{ |
|||
Span<byte> uncompressed = this.tmpBuffer.GetSpan(); |
|||
|
|||
uint uncompressedBytes = (uint)buffer.Length; |
|||
int totalRead = UndoZipCompression(stream, compressedBytes, uncompressed, uncompressedBytes); |
|||
|
|||
Reconstruct(uncompressed, (uint)totalRead); |
|||
Interleave(uncompressed, (uint)totalRead, buffer); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Dispose(bool disposing) => this.tmpBuffer.Dispose(); |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
|
|||
/// <summary>
|
|||
/// Base class for EXR compression.
|
|||
/// </summary>
|
|||
internal abstract class ExrBaseCompression : IDisposable |
|||
{ |
|||
private bool isDisposed; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrBaseCompression" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
/// <param name="rowsPerBlock">The number of pixel rows per block.</param>
|
|||
/// <param name="width">The number of pixels of a row.</param>
|
|||
protected ExrBaseCompression(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) |
|||
{ |
|||
this.Allocator = allocator; |
|||
this.BytesPerBlock = bytesPerBlock; |
|||
this.BytesPerRow = bytesPerRow; |
|||
this.RowsPerBlock = rowsPerBlock; |
|||
this.Width = width; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the memory allocator.
|
|||
/// </summary>
|
|||
protected MemoryAllocator Allocator { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the bits per pixel.
|
|||
/// </summary>
|
|||
public int BitsPerPixel { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the bytes per row.
|
|||
/// </summary>
|
|||
public uint BytesPerRow { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the uncompressed bytes per block.
|
|||
/// </summary>
|
|||
public uint BytesPerBlock { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of pixel rows per block.
|
|||
/// </summary>
|
|||
public uint RowsPerBlock { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the image width.
|
|||
/// </summary>
|
|||
public int Width { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public void Dispose() |
|||
{ |
|||
if (this.isDisposed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
this.isDisposed = true; |
|||
this.Dispose(true); |
|||
} |
|||
|
|||
protected abstract void Dispose(bool disposing); |
|||
} |
|||
@ -0,0 +1,112 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.IO.Compression; |
|||
using SixLabors.ImageSharp.Compression.Zlib; |
|||
using SixLabors.ImageSharp.IO; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
|
|||
/// <summary>
|
|||
/// The base EXR decompressor class.
|
|||
/// </summary>
|
|||
internal abstract class ExrBaseDecompressor : ExrBaseCompression |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrBaseDecompressor" /> class.
|
|||
/// </summary>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per row block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
/// <param name="rowsPerBlock">The pixel rows per block.</param>
|
|||
/// <param name="width">The number of pixels per row.</param>
|
|||
protected ExrBaseDecompressor(MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) |
|||
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Decompresses the specified stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The buffered stream to decompress.</param>
|
|||
/// <param name="compressedBytes">The compressed bytes.</param>
|
|||
/// <param name="buffer">The buffer to write the decompressed data to.</param>
|
|||
public abstract void Decompress(BufferedReadStream stream, uint compressedBytes, Span<byte> buffer); |
|||
|
|||
/// <summary>
|
|||
/// Decompresses zip compressed data.
|
|||
/// </summary>
|
|||
/// <param name="stream">The buffered stream to decompress.</param>
|
|||
/// <param name="compressedBytes">The compressed bytes.</param>
|
|||
/// <param name="uncompressed">The buffer to write the uncompressed data to.</param>
|
|||
/// <param name="uncompressedBytes">The uncompressed bytes.</param>
|
|||
/// <returns>The total bytes read from the stream.</returns>
|
|||
protected static int UndoZipCompression(BufferedReadStream stream, uint compressedBytes, Span<byte> uncompressed, uint uncompressedBytes) |
|||
{ |
|||
long pos = stream.Position; |
|||
using ZlibInflateReader inflateStream = new( |
|||
stream, |
|||
() => |
|||
{ |
|||
int left = (int)(compressedBytes - (stream.Position - pos)); |
|||
return left > 0 ? left : 0; |
|||
}); |
|||
inflateStream.AllocateNewBytes((int)compressedBytes, true); |
|||
using DeflateStream dataStream = inflateStream.CompressedStream!; |
|||
|
|||
int totalRead = 0; |
|||
while (totalRead < uncompressedBytes) |
|||
{ |
|||
int bytesRead = dataStream.Read(uncompressed, totalRead, (int)uncompressedBytes - totalRead); |
|||
if (bytesRead <= 0) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
totalRead += bytesRead; |
|||
} |
|||
|
|||
if (totalRead == 0) |
|||
{ |
|||
ExrThrowHelper.ThrowInvalidImageContentException("Could not read enough data for zip compressed EXR image data!"); |
|||
} |
|||
|
|||
return totalRead; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Integrate over all differences to the previous value in order to
|
|||
/// reconstruct sample values.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer with the data.</param>
|
|||
/// <param name="unCompressedBytes">The un compressed bytes.</param>
|
|||
protected static void Reconstruct(Span<byte> buffer, uint unCompressedBytes) |
|||
{ |
|||
int offset = 0; |
|||
for (int i = 0; i < unCompressedBytes - 1; i++) |
|||
{ |
|||
byte d = (byte)(buffer[offset] + (buffer[offset + 1] - 128)); |
|||
buffer[offset + 1] = d; |
|||
offset++; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Interleaves the input data.
|
|||
/// </summary>
|
|||
/// <param name="source">The source data.</param>
|
|||
/// <param name="unCompressedBytes">The uncompressed bytes.</param>
|
|||
/// <param name="output">The output to write to.</param>
|
|||
protected static void Interleave(Span<byte> source, uint unCompressedBytes, Span<byte> output) |
|||
{ |
|||
int sourceOffset = 0; |
|||
int offset0 = 0; |
|||
int offset1 = (int)((unCompressedBytes + 1) / 2); |
|||
while (sourceOffset < unCompressedBytes) |
|||
{ |
|||
output[sourceOffset++] = source[offset0++]; |
|||
output[sourceOffset++] = source[offset1++]; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Compression.Zlib; |
|||
using SixLabors.ImageSharp.Formats.Exr.Compression.Compressors; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
|
|||
/// <summary>
|
|||
/// Factory class for creating a compressor for EXR image data.
|
|||
/// </summary>
|
|||
internal static class ExrCompressorFactory |
|||
{ |
|||
/// <summary>
|
|||
/// Creates the specified exr data compressor.
|
|||
/// </summary>
|
|||
/// <param name="method">The compression method.</param>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="output">The output stream.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
/// <param name="rowsPerBlock">The pixel rows per block.</param>
|
|||
/// <param name="width">The witdh of one row in pixels.</param>
|
|||
/// <param name="compressionLevel">The deflate compression level.</param>
|
|||
/// <returns>A compressor for EXR image data.</returns>
|
|||
public static ExrBaseCompressor Create( |
|||
ExrCompression method, |
|||
MemoryAllocator allocator, |
|||
Stream output, |
|||
uint bytesPerBlock, |
|||
uint bytesPerRow, |
|||
uint rowsPerBlock, |
|||
int width, |
|||
DeflateCompressionLevel compressionLevel = DeflateCompressionLevel.DefaultCompression) => method switch |
|||
{ |
|||
ExrCompression.None => new NoneExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width), |
|||
ExrCompression.Zips => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, compressionLevel), |
|||
ExrCompression.Zip => new ZipExrCompressor(output, allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, compressionLevel), |
|||
_ => throw ExrThrowHelper.NotSupportedCompressor(method.ToString()), |
|||
}; |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Exr.Compression.Decompressors; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
|
|||
/// <summary>
|
|||
/// The Factory class for creating a EXR data decompressor.
|
|||
/// </summary>
|
|||
internal static class ExrDecompressorFactory |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a decomprssor for a specific EXR compression type.
|
|||
/// </summary>
|
|||
/// <param name="method">The compression method.</param>
|
|||
/// <param name="memoryAllocator">The memory allocator.</param>
|
|||
/// <param name="width">The width in pixels of the image.</param>
|
|||
/// <param name="bytesPerBlock">The bytes per block.</param>
|
|||
/// <param name="bytesPerRow">The bytes per row.</param>
|
|||
/// <param name="rowsPerBlock">The rows per block.</param>
|
|||
/// <param name="channelCount">The number of image channels.</param>
|
|||
/// <param name="pixelType">The pixel type.</param>
|
|||
/// <returns>Decompressor for EXR image data.</returns>
|
|||
public static ExrBaseDecompressor Create( |
|||
ExrCompression method, |
|||
MemoryAllocator memoryAllocator, |
|||
int width, |
|||
uint bytesPerBlock, |
|||
uint bytesPerRow, |
|||
uint rowsPerBlock, |
|||
int channelCount, |
|||
ExrPixelType pixelType) => method switch |
|||
{ |
|||
ExrCompression.None => new NoneExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width), |
|||
ExrCompression.Zips => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width), |
|||
ExrCompression.Zip => new ZipExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width), |
|||
ExrCompression.RunLengthEncoded => new RunLengthExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width), |
|||
ExrCompression.B44 => new B44ExrCompression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount), |
|||
ExrCompression.Pxr24 => new Pxr24Compression(memoryAllocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width, channelCount, pixelType), |
|||
_ => throw ExrThrowHelper.NotSupportedDecompressor(nameof(method)), |
|||
}; |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
/// <summary>
|
|||
/// Enumeration representing the compression formats defined by the EXR file-format.
|
|||
/// </summary>
|
|||
public enum ExrCompression |
|||
{ |
|||
/// <summary>
|
|||
/// Pixel data is not compressed.
|
|||
/// </summary>
|
|||
None = 0, |
|||
|
|||
/// <summary>
|
|||
/// Differences between horizontally adjacent pixels are run-length encoded.
|
|||
/// This method is fast, and works well for images with large flat areas, but for photographic images,
|
|||
/// the compressed file size is usually between 60 and 75 percent of the uncompressed size.
|
|||
/// Compression is lossless.
|
|||
/// </summary>
|
|||
RunLengthEncoded = 1, |
|||
|
|||
/// <summary>
|
|||
/// Uses the open source zlib library for compression. Unlike ZIP compression, this operates one scan line at a time.
|
|||
/// Compression is lossless.
|
|||
/// </summary>
|
|||
Zips = 2, |
|||
|
|||
/// <summary>
|
|||
/// Differences between horizontally adjacent pixels are compressed using the open source zlib library.
|
|||
/// Unlike ZIPS compression, this operates in in blocks of 16 scan lines.
|
|||
/// Compression is lossless.
|
|||
/// </summary>
|
|||
Zip = 3, |
|||
|
|||
/// <summary>
|
|||
/// A wavelet transform is applied to the pixel data, and the result is Huffman-encoded.
|
|||
/// Compression is lossless.
|
|||
/// </summary>
|
|||
Piz = 4, |
|||
|
|||
/// <summary>
|
|||
/// After reducing 32-bit floating-point data to 24 bits by rounding, differences between horizontally adjacent pixels are compressed with zlib,
|
|||
/// similar to ZIP. PXR24 compression preserves image channels of type HALF and UINT exactly, but the relative error of FLOAT data increases to about 3×10-5.
|
|||
/// Compression is lossy.
|
|||
/// </summary>
|
|||
Pxr24 = 5, |
|||
|
|||
/// <summary>
|
|||
/// Channels of type HALF are split into blocks of four by four pixels or 32 bytes. Each block is then packed into 14 bytes,
|
|||
/// reducing the data to 44 percent of their uncompressed size.
|
|||
/// Compression is lossy.
|
|||
/// </summary>
|
|||
B44 = 6, |
|||
|
|||
/// <summary>
|
|||
/// Like B44, except for blocks of four by four pixels where all pixels have the same value, which are packed into 3 instead of 14 bytes.
|
|||
/// For images with large uniform areas, B44A produces smaller files than B44 compression.
|
|||
/// Compression is lossy.
|
|||
/// </summary>
|
|||
B44A = 7 |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
/// <summary>
|
|||
/// This enum represents the type of pixel data in the EXR image.
|
|||
/// </summary>
|
|||
public enum ExrImageDataType |
|||
{ |
|||
/// <summary>
|
|||
/// The pixel data is unknown.
|
|||
/// </summary>
|
|||
Unknown = 0, |
|||
|
|||
/// <summary>
|
|||
/// The pixel data has 3 channels: red, green and blue.
|
|||
/// </summary>
|
|||
Rgb = 1, |
|||
|
|||
/// <summary>
|
|||
/// The pixel data has four channels: red, green, blue and a alpha channel.
|
|||
/// </summary>
|
|||
Rgba = 2, |
|||
|
|||
/// <summary>
|
|||
/// There is only one channel with the luminance.
|
|||
/// </summary>
|
|||
Gray = 3, |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
/// <summary>
|
|||
/// Enum for the differnt exr image type.
|
|||
/// </summary>
|
|||
internal enum ExrImageType |
|||
{ |
|||
/// <summary>
|
|||
/// The image data is stored in scan lines.
|
|||
/// </summary>
|
|||
ScanLine = 0, |
|||
|
|||
/// <summary>
|
|||
/// The image data is stored in tile.
|
|||
/// This is not yet supported.
|
|||
/// </summary>
|
|||
Tiled = 1 |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
/// <summary>
|
|||
/// Enum for the different scan line ordering.
|
|||
/// </summary>
|
|||
internal enum ExrLineOrder : byte |
|||
{ |
|||
/// <summary>
|
|||
/// The scan lines are written from top-to-bottom.
|
|||
/// </summary>
|
|||
IncreasingY = 0, |
|||
|
|||
/// <summary>
|
|||
/// The scan lines are written from bottom-to-top.
|
|||
/// </summary>
|
|||
DecreasingY = 1, |
|||
|
|||
/// <summary>
|
|||
/// The Scan lines are written in no particular oder.
|
|||
/// </summary>
|
|||
RandomY = 2 |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
/// <summary>
|
|||
/// The different pixel formats for a OpenEXR image.
|
|||
/// </summary>
|
|||
public enum ExrPixelType |
|||
{ |
|||
/// <summary>
|
|||
/// unsigned int (32 bit).
|
|||
/// </summary>
|
|||
UnsignedInt = 0, |
|||
|
|||
/// <summary>
|
|||
/// half (16 bit floating point).
|
|||
/// </summary>
|
|||
Half = 1, |
|||
|
|||
/// <summary>
|
|||
/// float (32 bit floating point).
|
|||
/// </summary>
|
|||
Float = 2 |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Repressents an exr image attribute.
|
|||
/// </summary>
|
|||
[DebuggerDisplay("Name: {Name}, Type: {Type}, Length: {Length}")] |
|||
internal class ExrAttribute |
|||
{ |
|||
public static readonly ExrAttribute EmptyAttribute = new(string.Empty, string.Empty, 0); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrAttribute"/> class.
|
|||
/// </summary>
|
|||
/// <param name="name">The name of the attribute.</param>
|
|||
/// <param name="type">The type of the attribute.</param>
|
|||
/// <param name="length">The length in bytes.</param>
|
|||
public ExrAttribute(string name, string type, int length) |
|||
{ |
|||
this.Name = name; |
|||
this.Type = type; |
|||
this.Length = length; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the name of the attribute.
|
|||
/// </summary>
|
|||
public string Name { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the type of the attribute.
|
|||
/// </summary>
|
|||
public string Type { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the length in bytes of the attribute.
|
|||
/// </summary>
|
|||
public int Length { get; } |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
|
|||
internal abstract class ExrBaseCompressor : ExrBaseCompression |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrBaseCompressor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="output">The output stream to write the compressed image to.</param>
|
|||
/// <param name="allocator">The memory allocator.</param>
|
|||
/// <param name="bytesPerBlock">Bytes per row block.</param>
|
|||
/// <param name="bytesPerRow">Bytes per pixel row.</param>
|
|||
/// <param name="rowsPerBlock">The pixel rows per block.</param>
|
|||
/// <param name="width">The number of pixels per row.</param>
|
|||
protected ExrBaseCompressor(Stream output, MemoryAllocator allocator, uint bytesPerBlock, uint bytesPerRow, uint rowsPerBlock, int width) |
|||
: base(allocator, bytesPerBlock, bytesPerRow, rowsPerBlock, width) |
|||
=> this.Output = output; |
|||
|
|||
/// <summary>
|
|||
/// Gets the output stream to write the compressed image to.
|
|||
/// </summary>
|
|||
public Stream Output { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compresses a block of rows of the image.
|
|||
/// </summary>
|
|||
/// <param name="rows">Image rows to compress.</param>
|
|||
/// <param name="rowCount">The number of rows to compress.</param>
|
|||
/// <returns>Number of bytes of of the compressed data.</returns>
|
|||
public abstract uint CompressRowBlock(Span<byte> rows, int rowCount); |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Integer region definition.
|
|||
/// </summary>
|
|||
[DebuggerDisplay("xMin: {XMin}, yMin: {YMin}, xMax: {XMax}, yMax: {YMax}")] |
|||
internal readonly struct ExrBox2i |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrBox2i"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="xMin">The minimum x value.</param>
|
|||
/// <param name="yMin">The minimum y value.</param>
|
|||
/// <param name="xMax">The maximum x value.</param>
|
|||
/// <param name="yMax">The maximum y value.</param>
|
|||
public ExrBox2i(int xMin, int yMin, int xMax, int yMax) |
|||
{ |
|||
this.XMin = xMin; |
|||
this.YMin = yMin; |
|||
this.XMax = xMax; |
|||
this.YMax = yMax; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the minimum x value.
|
|||
/// </summary>
|
|||
public int XMin { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the minimum y value.
|
|||
/// </summary>
|
|||
public int YMin { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum x value.
|
|||
/// </summary>
|
|||
public int XMax { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the maximum y value.
|
|||
/// </summary>
|
|||
public int YMax { get; } |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics; |
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Information about a pixel channel.
|
|||
/// </summary>
|
|||
[DebuggerDisplay("Name: {ChannelName}, PixelType: {PixelType}")] |
|||
[StructLayout(LayoutKind.Sequential, Pack = 1)] |
|||
internal readonly struct ExrChannelInfo |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrChannelInfo" /> struct.
|
|||
/// </summary>
|
|||
/// <param name="channelName">Name of the channel.</param>
|
|||
/// <param name="pixelType">The type of the pixel data.</param>
|
|||
/// <param name="linear">Linear flag, possible values are 0 and 1.</param>
|
|||
/// <param name="xSampling">X sampling.</param>
|
|||
/// <param name="ySampling">Y sampling.</param>
|
|||
public ExrChannelInfo(string channelName, ExrPixelType pixelType, byte linear, int xSampling, int ySampling) |
|||
{ |
|||
this.ChannelName = channelName; |
|||
this.PixelType = pixelType; |
|||
this.Linear = linear; |
|||
this.XSampling = xSampling; |
|||
this.YSampling = ySampling; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the channel name.
|
|||
/// </summary>
|
|||
public string ChannelName { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the type of the pixel data.
|
|||
/// </summary>
|
|||
public ExrPixelType PixelType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the linear flag. Hint to lossy compression methods that indicates whether
|
|||
/// human perception of the quantity represented by this channel
|
|||
/// is closer to linear or closer to logarithmic.
|
|||
/// </summary>
|
|||
public byte Linear { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the x sampling value.
|
|||
/// </summary>
|
|||
public int XSampling { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the y sampling value.
|
|||
/// </summary>
|
|||
public int YSampling { get; } |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the OpenExr format.
|
|||
/// </summary>
|
|||
public sealed class ExrConfigurationModule : IImageFormatConfigurationModule |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public void Configure(Configuration configuration) |
|||
{ |
|||
configuration.ImageFormatsManager.SetEncoder(ExrFormat.Instance, new ExrEncoder()); |
|||
configuration.ImageFormatsManager.SetDecoder(ExrFormat.Instance, ExrDecoder.Instance); |
|||
configuration.ImageFormatsManager.AddImageFormatDetector(new ExrImageFormatDetector()); |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Defines constants relating to OpenExr images.
|
|||
/// </summary>
|
|||
internal static class ExrConstants |
|||
{ |
|||
/// <summary>
|
|||
/// The list of mimetypes that equate to a OpenExr image.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> MimeTypes = new[] { "image/x-exr" }; |
|||
|
|||
/// <summary>
|
|||
/// The list of file extensions that equate to a OpenExr image.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> FileExtensions = new[] { "exr" }; |
|||
|
|||
/// <summary>
|
|||
/// The magick bytes identifying an OpenExr image.
|
|||
/// </summary>
|
|||
public static readonly int MagickBytes = 20000630; |
|||
|
|||
/// <summary>
|
|||
/// EXR attribute names.
|
|||
/// </summary>
|
|||
internal static class AttributeNames |
|||
{ |
|||
public const string Channels = "channels"; |
|||
|
|||
public const string Compression = "compression"; |
|||
|
|||
public const string DataWindow = "dataWindow"; |
|||
|
|||
public const string DisplayWindow = "displayWindow"; |
|||
|
|||
public const string LineOrder = "lineOrder"; |
|||
|
|||
public const string PixelAspectRatio = "pixelAspectRatio"; |
|||
|
|||
public const string ScreenWindowCenter = "screenWindowCenter"; |
|||
|
|||
public const string ScreenWindowWidth = "screenWindowWidth"; |
|||
|
|||
public const string Tiles = "tiles"; |
|||
|
|||
public const string ChunkCount = "chunkCount"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// EXR attribute types.
|
|||
/// </summary>
|
|||
internal static class AttibuteTypes |
|||
{ |
|||
public const string ChannelList = "chlist"; |
|||
|
|||
public const string Compression = "compression"; |
|||
|
|||
public const string Float = "float"; |
|||
|
|||
public const string LineOrder = "lineOrder"; |
|||
|
|||
public const string TwoFloat = "v2f"; |
|||
|
|||
public const string BoxInt = "box2i"; |
|||
} |
|||
|
|||
internal static class ChannelNames |
|||
{ |
|||
public const string Red = "R"; |
|||
|
|||
public const string Green = "G"; |
|||
|
|||
public const string Blue = "B"; |
|||
|
|||
public const string Alpha = "A"; |
|||
|
|||
public const string Luminance = "Y"; |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Image decoder for generating an image out of a OpenExr stream.
|
|||
/// </summary>
|
|||
public class ExrDecoder : ImageDecoder |
|||
{ |
|||
private ExrDecoder() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the shared instance.
|
|||
/// </summary>
|
|||
public static ExrDecoder Instance { get; } = new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
Guard.NotNull(options, nameof(options)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
return new ExrDecoderCore(new ExrDecoderOptions { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
Guard.NotNull(options, nameof(options)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
ExrDecoderCore decoder = new(new ExrDecoderOptions { GeneralOptions = options }); |
|||
Image<TPixel> image = decoder.Decode<TPixel>(options.Configuration, stream, cancellationToken); |
|||
|
|||
ScaleToTargetSize(options, image); |
|||
|
|||
return image; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) |
|||
=> this.Decode<Rgba32>(options, stream, cancellationToken); |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,13 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Image decoder options for decoding OpenExr streams.
|
|||
/// </summary>
|
|||
public sealed class ExrDecoderOptions : ISpecializedDecoderOptions |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public DecoderOptions GeneralOptions { get; init; } = new(); |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Image encoder for writing an image to a stream in the OpenExr Format.
|
|||
/// </summary>
|
|||
public sealed class ExrEncoder : ImageEncoder |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the pixel type of the image.
|
|||
/// </summary>
|
|||
public ExrPixelType? PixelType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the compression type to use.
|
|||
/// </summary>
|
|||
public ExrCompression? Compression { get; init; } |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) |
|||
{ |
|||
ExrEncoderCore encoder = new(this, image.Configuration, image.Configuration.MemoryAllocator); |
|||
encoder.Encode(image, stream, cancellationToken); |
|||
} |
|||
} |
|||
@ -0,0 +1,710 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Buffers.Binary; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Formats.Exr.Compression; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Image encoder for writing an image to a stream in the OpenExr format.
|
|||
/// </summary>
|
|||
internal sealed class ExrEncoderCore |
|||
{ |
|||
/// <summary>
|
|||
/// Reusable buffer.
|
|||
/// </summary>
|
|||
private readonly byte[] buffer = new byte[8]; |
|||
|
|||
/// <summary>
|
|||
/// Used for allocating memory during processing operations.
|
|||
/// </summary>
|
|||
private readonly MemoryAllocator memoryAllocator; |
|||
|
|||
/// <summary>
|
|||
/// The global configuration.
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// The encoder with options.
|
|||
/// </summary>
|
|||
private readonly ExrEncoder encoder; |
|||
|
|||
/// <summary>
|
|||
/// The pixel type of the image.
|
|||
/// </summary>
|
|||
private ExrPixelType? pixelType; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrEncoderCore"/> class.
|
|||
/// </summary>
|
|||
/// <param name="encoder">The encoder with options.</param>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="memoryAllocator">The memory manager.</param>
|
|||
public ExrEncoderCore(ExrEncoder encoder, Configuration configuration, MemoryAllocator memoryAllocator) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.encoder = encoder; |
|||
this.memoryAllocator = memoryAllocator; |
|||
this.Compression = encoder.Compression ?? ExrCompression.None; |
|||
this.pixelType = encoder.PixelType; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the compression implementation to use when encoding the image.
|
|||
/// </summary>
|
|||
internal ExrCompression Compression { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
|
|||
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
|
|||
/// <param name="cancellationToken">The token to request cancellation.</param>
|
|||
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(image, nameof(image)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
Buffer2D<TPixel> pixels = image.Frames.RootFrame.PixelBuffer; |
|||
|
|||
ImageMetadata metadata = image.Metadata; |
|||
ExrMetadata exrMetadata = metadata.GetExrMetadata(); |
|||
this.pixelType ??= exrMetadata.PixelType; |
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
float aspectRatio = 1.0f; |
|||
ExrBox2i dataWindow = new(0, 0, width - 1, height - 1); |
|||
ExrBox2i displayWindow = new(0, 0, width - 1, height - 1); |
|||
ExrLineOrder lineOrder = ExrLineOrder.IncreasingY; |
|||
PointF screenWindowCenter = new(0.0f, 0.0f); |
|||
int screenWindowWidth = 1; |
|||
List<ExrChannelInfo> channels = |
|||
[ |
|||
new(ExrConstants.ChannelNames.Alpha, this.pixelType.Value, 0, 1, 1), |
|||
new(ExrConstants.ChannelNames.Blue, this.pixelType.Value, 0, 1, 1), |
|||
new(ExrConstants.ChannelNames.Green, this.pixelType.Value, 0, 1, 1), |
|||
new(ExrConstants.ChannelNames.Red, this.pixelType.Value, 0, 1, 1), |
|||
]; |
|||
ExrHeaderAttributes header = new( |
|||
channels, |
|||
this.Compression, |
|||
dataWindow, |
|||
displayWindow, |
|||
lineOrder, |
|||
aspectRatio, |
|||
screenWindowWidth, |
|||
screenWindowCenter); |
|||
|
|||
// Write magick bytes.
|
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, ExrConstants.MagickBytes); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
// Version number.
|
|||
this.buffer[0] = 2; |
|||
|
|||
// Second, third and fourth bytes store info about the image, set all to default: zero.
|
|||
this.buffer[1] = 0; |
|||
this.buffer[2] = 0; |
|||
this.buffer[3] = 0; |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
// Write EXR header.
|
|||
this.WriteHeader(stream, header); |
|||
|
|||
// Next is offsets table to each pixel row, which will be written after the pixel data was written.
|
|||
ulong startOfRowOffsetData = (ulong)stream.Position; |
|||
stream.Position += 8 * height; |
|||
|
|||
// Write pixel data.
|
|||
switch (this.pixelType) |
|||
{ |
|||
case ExrPixelType.Half: |
|||
case ExrPixelType.Float: |
|||
{ |
|||
ulong[] rowOffsets = this.EncodeFloatingPointPixelData(stream, pixels, width, height, channels, this.Compression, cancellationToken); |
|||
stream.Position = (long)startOfRowOffsetData; |
|||
this.WriteRowOffsets(stream, height, rowOffsets); |
|||
break; |
|||
} |
|||
|
|||
case ExrPixelType.UnsignedInt: |
|||
{ |
|||
ulong[] rowOffsets = this.EncodeUnsignedIntPixelData(stream, pixels, width, height, channels, this.Compression, cancellationToken); |
|||
stream.Position = (long)startOfRowOffsetData; |
|||
this.WriteRowOffsets(stream, height, rowOffsets); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes and writes pixel data with float pixel data to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the pixels.</typeparam>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="pixels">The pixel bufer.</param>
|
|||
/// <param name="width">The width of the image in pixels.</param>
|
|||
/// <param name="height">The height of the image in pixels.</param>
|
|||
/// <param name="channels">The imagechannels.</param>
|
|||
/// <param name="compression">The compression to use.</param>
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
|||
/// <returns>The array of pixel row offsets.</returns>
|
|||
private ulong[] EncodeFloatingPointPixelData<TPixel>( |
|||
Stream stream, |
|||
Buffer2D<TPixel> pixels, |
|||
int width, |
|||
int height, |
|||
List<ExrChannelInfo> channels, |
|||
ExrCompression compression, |
|||
CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
ulong bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); |
|||
uint rowsPerBlock = ExrUtils.RowsPerBlock(compression); |
|||
ulong bytesPerBlock = bytesPerRow * rowsPerBlock; |
|||
if (bytesPerRow > uint.MaxValue || bytesPerBlock > int.MaxValue) |
|||
{ |
|||
throw new ImageFormatException("Image is too large to encode in EXR format."); |
|||
} |
|||
|
|||
using IMemoryOwner<float> rgbBuffer = this.memoryAllocator.Allocate<float>(width * 4, AllocationOptions.Clean); |
|||
using IMemoryOwner<byte> rowBlockBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock, AllocationOptions.Clean); |
|||
Span<float> redBuffer = rgbBuffer.GetSpan()[..width]; |
|||
Span<float> greenBuffer = rgbBuffer.GetSpan().Slice(width, width); |
|||
Span<float> blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); |
|||
Span<float> alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width); |
|||
|
|||
using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, (uint)bytesPerBlock, (uint)bytesPerRow, rowsPerBlock, width); |
|||
|
|||
ulong[] rowOffsets = new ulong[height]; |
|||
for (uint y = 0; y < height; y += rowsPerBlock) |
|||
{ |
|||
rowOffsets[y] = (ulong)stream.Position; |
|||
|
|||
// Write row index.
|
|||
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, y); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
// At this point, it is not yet known how much bytes the compressed data will take up, keep stream position.
|
|||
long pixelDataSizePos = stream.Position; |
|||
stream.Position = pixelDataSizePos + 4; |
|||
|
|||
uint rowsInBlockCount = 0; |
|||
for (uint rowIndex = y; rowIndex < y + rowsPerBlock && rowIndex < height; rowIndex++) |
|||
{ |
|||
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan((int)rowIndex); |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
// OpenEXR stores RGB associated with alpha. Use the native vector domain so floating-point and HDR component
|
|||
// ranges are preserved instead of being clamped through the scaled [0, 1] representation.
|
|||
Vector4 vector4 = pixelRowSpan[x].ToAssociatedVector4(); |
|||
redBuffer[x] = vector4.X; |
|||
greenBuffer[x] = vector4.Y; |
|||
blueBuffer[x] = vector4.Z; |
|||
alphaBuffer[x] = vector4.W; |
|||
} |
|||
|
|||
// Write pixel data to row block buffer.
|
|||
Span<byte> rowBlockSpan = rowBlockBuffer.GetSpan().Slice((int)(rowsInBlockCount * bytesPerRow), (int)bytesPerRow); |
|||
switch (this.pixelType) |
|||
{ |
|||
case ExrPixelType.Float: |
|||
WriteSingleRow(rowBlockSpan, width, alphaBuffer, blueBuffer, greenBuffer, redBuffer); |
|||
break; |
|||
case ExrPixelType.Half: |
|||
WriteHalfSingleRow(rowBlockSpan, width, alphaBuffer, blueBuffer, greenBuffer, redBuffer); |
|||
break; |
|||
} |
|||
|
|||
rowsInBlockCount++; |
|||
} |
|||
|
|||
// Write compressed pixel row data to the stream.
|
|||
uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), (int)rowsInBlockCount); |
|||
long positionAfterPixelData = stream.Position; |
|||
|
|||
// Write pixel row data size.
|
|||
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, compressedBytes); |
|||
stream.Position = pixelDataSizePos; |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
stream.Position = positionAfterPixelData; |
|||
|
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
} |
|||
|
|||
return rowOffsets; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes and writes pixel data with the unsigned int pixel type to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the pixels.</typeparam>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="pixels">The pixel bufer.</param>
|
|||
/// <param name="width">The width of the image in pixels.</param>
|
|||
/// <param name="height">The height of the image in pixels.</param>
|
|||
/// <param name="channels">The imagechannels.</param>
|
|||
/// <param name="compression">The compression to use.</param>
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
|||
/// <returns>The array of pixel row offsets.</returns>
|
|||
private ulong[] EncodeUnsignedIntPixelData<TPixel>( |
|||
Stream stream, |
|||
Buffer2D<TPixel> pixels, |
|||
int width, |
|||
int height, |
|||
List<ExrChannelInfo> channels, |
|||
ExrCompression compression, |
|||
CancellationToken cancellationToken) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
ulong bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width); |
|||
uint rowsPerBlock = ExrUtils.RowsPerBlock(compression); |
|||
ulong bytesPerBlock = bytesPerRow * rowsPerBlock; |
|||
if (bytesPerRow > uint.MaxValue || bytesPerBlock > int.MaxValue) |
|||
{ |
|||
throw new ImageFormatException("Image is too large to encode in EXR format."); |
|||
} |
|||
|
|||
using IMemoryOwner<uint> rgbBuffer = this.memoryAllocator.Allocate<uint>(width * 4, AllocationOptions.Clean); |
|||
using IMemoryOwner<byte> rowBlockBuffer = this.memoryAllocator.Allocate<byte>((int)bytesPerBlock, AllocationOptions.Clean); |
|||
Span<uint> redBuffer = rgbBuffer.GetSpan()[..width]; |
|||
Span<uint> greenBuffer = rgbBuffer.GetSpan().Slice(width, width); |
|||
Span<uint> blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width); |
|||
Span<uint> alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width); |
|||
|
|||
using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, (uint)bytesPerBlock, (uint)bytesPerRow, rowsPerBlock, width); |
|||
|
|||
Rgba128 rgb = default; |
|||
ulong[] rowOffsets = new ulong[height]; |
|||
for (uint y = 0; y < height; y += rowsPerBlock) |
|||
{ |
|||
rowOffsets[y] = (ulong)stream.Position; |
|||
|
|||
// Write row index.
|
|||
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, y); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
// At this point, it is not yet known how much bytes the compressed data will take up, keep stream position.
|
|||
long pixelDataSizePos = stream.Position; |
|||
stream.Position = pixelDataSizePos + 4; |
|||
|
|||
uint rowsInBlockCount = 0; |
|||
for (uint rowIndex = y; rowIndex < y + rowsPerBlock && rowIndex < height; rowIndex++) |
|||
{ |
|||
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan((int)rowIndex); |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
// OpenEXR channels use associated alpha; the native vector conversion also preserves the integer channel range.
|
|||
Vector4 vector4 = pixelRowSpan[x].ToAssociatedVector4(); |
|||
rgb = Rgba128.FromVector4(vector4); |
|||
|
|||
redBuffer[x] = rgb.R; |
|||
greenBuffer[x] = rgb.G; |
|||
blueBuffer[x] = rgb.B; |
|||
alphaBuffer[x] = rgb.A; |
|||
} |
|||
|
|||
// Write row data to row block buffer.
|
|||
Span<byte> rowBlockSpan = rowBlockBuffer.GetSpan().Slice((int)(rowsInBlockCount * bytesPerRow), (int)bytesPerRow); |
|||
WriteUnsignedIntRow(rowBlockSpan, width, alphaBuffer, blueBuffer, greenBuffer, redBuffer); |
|||
rowsInBlockCount++; |
|||
} |
|||
|
|||
// Write pixel row data compressed to the stream.
|
|||
uint compressedBytes = compressor.CompressRowBlock(rowBlockBuffer.GetSpan(), (int)rowsInBlockCount); |
|||
long positionAfterPixelData = stream.Position; |
|||
|
|||
// Write pixel row data size.
|
|||
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, compressedBytes); |
|||
stream.Position = pixelDataSizePos; |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
stream.Position = positionAfterPixelData; |
|||
|
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
} |
|||
|
|||
return rowOffsets; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the image header to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="header">The header.</param>
|
|||
private void WriteHeader(Stream stream, ExrHeaderAttributes header) |
|||
{ |
|||
this.WriteChannels(stream, header.Channels); |
|||
this.WriteCompression(stream, header.Compression); |
|||
this.WriteDataWindow(stream, header.DataWindow); |
|||
this.WriteDisplayWindow(stream, header.DisplayWindow); |
|||
this.WritePixelAspectRatio(stream, header.AspectRatio); |
|||
this.WriteLineOrder(stream, header.LineOrder); |
|||
this.WriteScreenWindowCenter(stream, header.ScreenWindowCenter); |
|||
this.WriteScreenWindowWidth(stream, header.ScreenWindowWidth); |
|||
stream.WriteByte(0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a row of pixels with the FLOAT pixel type to a buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write to.</param>
|
|||
/// <param name="width">The width of a row in pixels.</param>
|
|||
/// <param name="alphaBuffer">The alpha channel buffer.</param>
|
|||
/// <param name="blueBuffer">The blue channel buffer.</param>
|
|||
/// <param name="greenBuffer">The green channel buffer.</param>
|
|||
/// <param name="redBuffer">The red channel buffer.</param>
|
|||
private static void WriteSingleRow(Span<byte> buffer, int width, Span<float> alphaBuffer, Span<float> blueBuffer, Span<float> greenBuffer, Span<float> redBuffer) |
|||
{ |
|||
int offset = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteSingleToBuffer(buffer.Slice(offset, 4), alphaBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteSingleToBuffer(buffer.Slice(offset, 4), blueBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteSingleToBuffer(buffer.Slice(offset, 4), greenBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteSingleToBuffer(buffer.Slice(offset, 4), redBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a row of pixels with the HALF pixel type to a buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write to.</param>
|
|||
/// <param name="width">The width of a row in pixels.</param>
|
|||
/// <param name="alphaBuffer">The alpha channel buffer.</param>
|
|||
/// <param name="blueBuffer">The blue channel buffer.</param>
|
|||
/// <param name="greenBuffer">The green channel buffer.</param>
|
|||
/// <param name="redBuffer">The red channel buffer.</param>
|
|||
private static void WriteHalfSingleRow(Span<byte> buffer, int width, Span<float> alphaBuffer, Span<float> blueBuffer, Span<float> greenBuffer, Span<float> redBuffer) |
|||
{ |
|||
int offset = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteHalfSingleToBuffer(buffer.Slice(offset, 2), alphaBuffer[x]); |
|||
offset += 2; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteHalfSingleToBuffer(buffer.Slice(offset, 2), blueBuffer[x]); |
|||
offset += 2; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteHalfSingleToBuffer(buffer.Slice(offset, 2), greenBuffer[x]); |
|||
offset += 2; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteHalfSingleToBuffer(buffer.Slice(offset, 2), redBuffer[x]); |
|||
offset += 2; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a row of pixels with unsigned int pixel data to a buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write to.</param>
|
|||
/// <param name="width">The width of the row in pixels.</param>
|
|||
/// <param name="alphaBuffer">The alpha channel buffer.</param>
|
|||
/// <param name="blueBuffer">The blue channel buffer.</param>
|
|||
/// <param name="greenBuffer">The green channel buffer.</param>
|
|||
/// <param name="redBuffer">The red channel buffer.</param>
|
|||
private static void WriteUnsignedIntRow(Span<byte> buffer, int width, Span<uint> alphaBuffer, Span<uint> blueBuffer, Span<uint> greenBuffer, Span<uint> redBuffer) |
|||
{ |
|||
int offset = 0; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), alphaBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), blueBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), greenBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
WriteUnsignedIntToBuffer(buffer.Slice(offset, 4), redBuffer[x]); |
|||
offset += 4; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the row offsets to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="height">The height in pixels of the image.</param>
|
|||
/// <param name="rowOffsets">The row offsets.</param>
|
|||
private void WriteRowOffsets(Stream stream, int height, ulong[] rowOffsets) |
|||
{ |
|||
for (int i = 0; i < height; i++) |
|||
{ |
|||
BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, rowOffsets[i]); |
|||
stream.Write(this.buffer); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the channel infos to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="channels">The channels.</param>
|
|||
private void WriteChannels(Stream stream, IList<ExrChannelInfo> channels) |
|||
{ |
|||
int attributeSize = 0; |
|||
foreach (ExrChannelInfo channelInfo in channels) |
|||
{ |
|||
attributeSize += channelInfo.ChannelName.Length + 1; |
|||
attributeSize += 16; |
|||
} |
|||
|
|||
// Last zero byte.
|
|||
attributeSize++; |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Channels, ExrConstants.AttibuteTypes.ChannelList, attributeSize); |
|||
|
|||
foreach (ExrChannelInfo channelInfo in channels) |
|||
{ |
|||
this.WriteChannelInfo(stream, channelInfo); |
|||
} |
|||
|
|||
// Last byte should be zero.
|
|||
stream.WriteByte(0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes info about a single channel to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="channelInfo">The channel information.</param>
|
|||
private void WriteChannelInfo(Stream stream, ExrChannelInfo channelInfo) |
|||
{ |
|||
WriteString(stream, channelInfo.ChannelName); |
|||
|
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, (int)channelInfo.PixelType); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
stream.WriteByte(channelInfo.Linear); |
|||
|
|||
// Next 3 bytes are reserved and will set to zero.
|
|||
stream.WriteByte(0); |
|||
stream.WriteByte(0); |
|||
stream.WriteByte(0); |
|||
|
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.XSampling); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, channelInfo.YSampling); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the compression type to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="compression">The compression type.</param>
|
|||
private void WriteCompression(Stream stream, ExrCompression compression) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.Compression, ExrConstants.AttibuteTypes.Compression, 1); |
|||
stream.WriteByte((byte)compression); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the pixel aspect ratio to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="aspectRatio">The aspect ratio.</param>
|
|||
private void WritePixelAspectRatio(Stream stream, float aspectRatio) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.PixelAspectRatio, ExrConstants.AttibuteTypes.Float, 4); |
|||
this.WriteSingle(stream, aspectRatio); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the line order to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="lineOrder">The line order.</param>
|
|||
private void WriteLineOrder(Stream stream, ExrLineOrder lineOrder) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.LineOrder, ExrConstants.AttibuteTypes.LineOrder, 1); |
|||
stream.WriteByte((byte)lineOrder); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the screen window center to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="screenWindowCenter">The screen window center.</param>
|
|||
private void WriteScreenWindowCenter(Stream stream, PointF screenWindowCenter) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowCenter, ExrConstants.AttibuteTypes.TwoFloat, 8); |
|||
this.WriteSingle(stream, screenWindowCenter.X); |
|||
this.WriteSingle(stream, screenWindowCenter.Y); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the screen width to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="screenWindowWidth">Width of the screen window.</param>
|
|||
private void WriteScreenWindowWidth(Stream stream, float screenWindowWidth) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.ScreenWindowWidth, ExrConstants.AttibuteTypes.Float, 4); |
|||
this.WriteSingle(stream, screenWindowWidth); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the data window to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="dataWindow">The data window.</param>
|
|||
private void WriteDataWindow(Stream stream, ExrBox2i dataWindow) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DataWindow, ExrConstants.AttibuteTypes.BoxInt, 16); |
|||
this.WriteBoxInteger(stream, dataWindow); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the display window to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="displayWindow">The display window.</param>
|
|||
private void WriteDisplayWindow(Stream stream, ExrBox2i displayWindow) |
|||
{ |
|||
this.WriteAttributeInformation(stream, ExrConstants.AttributeNames.DisplayWindow, ExrConstants.AttibuteTypes.BoxInt, 16); |
|||
this.WriteBoxInteger(stream, displayWindow); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes attribute information to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="name">The name of the attribute.</param>
|
|||
/// <param name="type">The type of the attribute.</param>
|
|||
/// <param name="size">The size in bytes of the attribute.</param>
|
|||
private void WriteAttributeInformation(Stream stream, string name, string type, int size) |
|||
{ |
|||
// Write attribute name.
|
|||
WriteString(stream, name); |
|||
|
|||
// Write attribute type.
|
|||
WriteString(stream, type); |
|||
|
|||
// Write attribute size.
|
|||
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, (uint)size); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a string to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="str">The string to write.</param>
|
|||
private static void WriteString(Stream stream, string str) |
|||
{ |
|||
foreach (char c in str) |
|||
{ |
|||
stream.WriteByte((byte)c); |
|||
} |
|||
|
|||
// Write termination byte.
|
|||
stream.WriteByte(0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes box struct with xmin, xmax, ymin and y max to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="box">The box to write.</param>
|
|||
private void WriteBoxInteger(Stream stream, ExrBox2i box) |
|||
{ |
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMin); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMin); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.XMax); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
|
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, box.YMax); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes 32 bit float value to the stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream to write to.</param>
|
|||
/// <param name="value">The float value to write.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private unsafe void WriteSingle(Stream stream, float value) |
|||
{ |
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, *(int*)&value); |
|||
stream.Write(this.buffer.AsSpan(0, 4)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a 32 bit float value to a buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write to.</param>
|
|||
/// <param name="value">The float value to write.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static unsafe void WriteSingleToBuffer(Span<byte> buffer, float value) => BinaryPrimitives.WriteInt32LittleEndian(buffer, *(int*)&value); |
|||
|
|||
/// <summary>
|
|||
/// Writes a 16 bit float value to a buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write to.</param>
|
|||
/// <param name="value">The float value to write.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static void WriteHalfSingleToBuffer(Span<byte> buffer, float value) |
|||
{ |
|||
ushort valueAsShort = HalfTypeHelper.Pack(value); |
|||
BinaryPrimitives.WriteUInt16LittleEndian(buffer, valueAsShort); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes one unsigned int to a buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write to.</param>
|
|||
/// <param name="value">The uint value to write.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
private static void WriteUnsignedIntToBuffer(Span<byte> buffer, uint value) => BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the OpenExr format.
|
|||
/// </summary>
|
|||
public sealed class ExrFormat : IImageFormat<ExrMetadata> |
|||
{ |
|||
private ExrFormat() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the current instance.
|
|||
/// </summary>
|
|||
public static ExrFormat Instance { get; } = new(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public string Name => "EXR"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public string DefaultMimeType => "image/x-exr"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> MimeTypes => ExrConstants.MimeTypes; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> FileExtensions => ExrConstants.FileExtensions; |
|||
|
|||
/// <inheritdoc/>
|
|||
public ExrMetadata CreateDefaultFormatMetadata() => new(); |
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// The header of an EXR image.
|
|||
/// <see href="https://openexr.com/en/latest/TechnicalIntroduction.html#header"/>
|
|||
/// </summary>
|
|||
internal class ExrHeaderAttributes |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrHeaderAttributes" /> class.
|
|||
/// </summary>
|
|||
/// <param name="channels">The image channels.</param>
|
|||
/// <param name="compression">The compression used.</param>
|
|||
/// <param name="dataWindow">The data window.</param>
|
|||
/// <param name="displayWindow">The display window.</param>
|
|||
/// <param name="lineOrder">The line order.</param>
|
|||
/// <param name="aspectRatio">The aspect ratio.</param>
|
|||
/// <param name="screenWindowWidth">Width of the screen window.</param>
|
|||
/// <param name="screenWindowCenter">The screen window center.</param>
|
|||
/// <param name="tileXSize">Size of the tile in x dimension.</param>
|
|||
/// <param name="tileYSize">Size of the tile in y dimension.</param>
|
|||
/// <param name="chunkCount">The chunk count.</param>
|
|||
public ExrHeaderAttributes( |
|||
IList<ExrChannelInfo> channels, |
|||
ExrCompression compression, |
|||
ExrBox2i dataWindow, |
|||
ExrBox2i displayWindow, |
|||
ExrLineOrder lineOrder, |
|||
float aspectRatio, |
|||
float screenWindowWidth, |
|||
PointF screenWindowCenter, |
|||
uint? tileXSize = null, |
|||
uint? tileYSize = null, |
|||
int? chunkCount = null) |
|||
{ |
|||
this.Channels = channels; |
|||
this.Compression = compression; |
|||
this.DataWindow = dataWindow; |
|||
this.DisplayWindow = displayWindow; |
|||
this.LineOrder = lineOrder; |
|||
this.AspectRatio = aspectRatio; |
|||
this.ScreenWindowWidth = screenWindowWidth; |
|||
this.ScreenWindowCenter = screenWindowCenter; |
|||
this.TileXSize = tileXSize; |
|||
this.TileYSize = tileYSize; |
|||
this.ChunkCount = chunkCount; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a description of the image channels stored in the file.
|
|||
/// </summary>
|
|||
public IList<ExrChannelInfo> Channels { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the compression method applied to the pixel data of all channels in the file.
|
|||
/// </summary>
|
|||
public ExrCompression Compression { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the image’s data window.
|
|||
/// </summary>
|
|||
public ExrBox2i DataWindow { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the image’s display window.
|
|||
/// </summary>
|
|||
public ExrBox2i DisplayWindow { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets in what order the scan lines in the file are stored in the file (increasing Y, decreasing Y, or, for tiled images, also random Y).
|
|||
/// </summary>
|
|||
public ExrLineOrder LineOrder { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the aspect ratio of the image.
|
|||
/// </summary>
|
|||
public float AspectRatio { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the screen width.
|
|||
/// </summary>
|
|||
public float ScreenWindowWidth { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the screen window center.
|
|||
/// </summary>
|
|||
public PointF ScreenWindowCenter { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of horizontal tiles.
|
|||
/// </summary>
|
|||
public uint? TileXSize { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of vertical tiles.
|
|||
/// </summary>
|
|||
public uint? TileYSize { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the chunk count. Indicates the number of chunks in this part. Required if the multipart bit (12) is set.
|
|||
/// </summary>
|
|||
public int? ChunkCount { get; set; } |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers.Binary; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Detects OpenExr file headers.
|
|||
/// </summary>
|
|||
public sealed class ExrImageFormatDetector : IImageFormatDetector |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public int HeaderSize => 4; |
|||
|
|||
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) |
|||
{ |
|||
if (header.Length >= this.HeaderSize) |
|||
{ |
|||
int fileTypeMarker = BinaryPrimitives.ReadInt32LittleEndian(header); |
|||
return fileTypeMarker == ExrConstants.MagickBytes; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format) |
|||
{ |
|||
format = this.IsSupportedFileFormat(header) ? ExrFormat.Instance : null; |
|||
return format != null; |
|||
} |
|||
} |
|||
@ -0,0 +1,157 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Provides OpenExr specific metadata information for the image.
|
|||
/// </summary>
|
|||
public class ExrMetadata : IFormatMetadata<ExrMetadata> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrMetadata"/> class.
|
|||
/// </summary>
|
|||
public ExrMetadata() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ExrMetadata"/> class.
|
|||
/// </summary>
|
|||
/// <param name="other">The metadata to create an instance from.</param>
|
|||
private ExrMetadata(ExrMetadata other) => this.PixelType = other.PixelType; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the pixel format.
|
|||
/// </summary>
|
|||
public ExrPixelType PixelType { get; set; } = ExrPixelType.Half; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the image data type, either RGB, RGBA or gray.
|
|||
/// </summary>
|
|||
public ExrImageDataType ImageDataType { get; set; } = ExrImageDataType.Unknown; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the compression method.
|
|||
/// </summary>
|
|||
public ExrCompression Compression { get; set; } = ExrCompression.None; |
|||
|
|||
/// <inheritdoc/>
|
|||
public PixelTypeInfo GetPixelTypeInfo() |
|||
{ |
|||
bool hasAlpha = this.ImageDataType is ExrImageDataType.Rgba; |
|||
|
|||
int bitsPerComponent = 32; |
|||
int bitsPerPixel = hasAlpha ? bitsPerComponent * 4 : bitsPerComponent * 3; |
|||
if (this.PixelType == ExrPixelType.Half) |
|||
{ |
|||
bitsPerComponent = 16; |
|||
bitsPerPixel = hasAlpha ? bitsPerComponent * 4 : bitsPerComponent * 3; |
|||
} |
|||
|
|||
// OpenEXR defines RGBA color channels as premultiplied by alpha, so expose the association stored by the format.
|
|||
PixelAlphaRepresentation alpha = hasAlpha ? PixelAlphaRepresentation.Associated : PixelAlphaRepresentation.None; |
|||
PixelColorType color = PixelColorType.RGB; |
|||
|
|||
int componentsCount = 0; |
|||
int[] precision = []; |
|||
switch (this.ImageDataType) |
|||
{ |
|||
case ExrImageDataType.Rgb: |
|||
color = PixelColorType.RGB; |
|||
componentsCount = 3; |
|||
precision = new int[componentsCount]; |
|||
precision[0] = bitsPerComponent; |
|||
precision[1] = bitsPerComponent; |
|||
precision[2] = bitsPerComponent; |
|||
break; |
|||
case ExrImageDataType.Rgba: |
|||
color = PixelColorType.RGB | PixelColorType.Alpha; |
|||
componentsCount = 4; |
|||
precision = new int[componentsCount]; |
|||
precision[0] = bitsPerComponent; |
|||
precision[1] = bitsPerComponent; |
|||
precision[2] = bitsPerComponent; |
|||
precision[3] = bitsPerComponent; |
|||
break; |
|||
case ExrImageDataType.Gray: |
|||
color = PixelColorType.Luminance; |
|||
componentsCount = 1; |
|||
precision = new int[componentsCount]; |
|||
precision[0] = bitsPerComponent; |
|||
break; |
|||
} |
|||
|
|||
PixelComponentInfo info = PixelComponentInfo.Create(componentsCount, bitsPerPixel, precision); |
|||
return new PixelTypeInfo(bitsPerPixel) |
|||
{ |
|||
AlphaRepresentation = alpha, |
|||
ComponentInfo = info, |
|||
ColorType = color |
|||
}; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public FormatConnectingMetadata ToFormatConnectingMetadata() |
|||
{ |
|||
EncodingType type = this.Compression is ExrCompression.B44 or ExrCompression.B44A or ExrCompression.Pxr24 |
|||
? EncodingType.Lossy |
|||
: EncodingType.Lossless; |
|||
|
|||
return new() |
|||
{ |
|||
EncodingType = type, |
|||
PixelTypeInfo = this.GetPixelTypeInfo() |
|||
}; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ExrMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) |
|||
{ |
|||
PixelTypeInfo pixelTypeInfo = metadata.PixelTypeInfo; |
|||
PixelComponentInfo? info = pixelTypeInfo.ComponentInfo; |
|||
PixelColorType colorType = pixelTypeInfo.ColorType; |
|||
|
|||
int bitsPerComponent = info?.GetMaximumComponentPrecision() |
|||
?? (pixelTypeInfo.BitsPerPixel <= 16 ? 16 : 32); |
|||
|
|||
int componentCount = info?.ComponentCount ?? 0; |
|||
ExrImageDataType imageDataType = colorType switch |
|||
{ |
|||
PixelColorType.Luminance => ExrImageDataType.Gray, |
|||
PixelColorType.RGB or PixelColorType.BGR => ExrImageDataType.Rgb, |
|||
PixelColorType.RGB | PixelColorType.Alpha |
|||
or PixelColorType.BGR | PixelColorType.Alpha |
|||
or PixelColorType.Luminance | PixelColorType.Alpha => ExrImageDataType.Rgba, |
|||
_ => componentCount switch |
|||
{ |
|||
>= 4 => ExrImageDataType.Rgba, |
|||
>= 3 => ExrImageDataType.Rgb, |
|||
1 => ExrImageDataType.Gray, |
|||
_ => ExrImageDataType.Unknown, |
|||
} |
|||
}; |
|||
|
|||
return new() |
|||
{ |
|||
PixelType = bitsPerComponent <= 16 ? ExrPixelType.Half : ExrPixelType.Float, |
|||
ImageDataType = imageDataType, |
|||
}; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
ExrMetadata IDeepCloneable<ExrMetadata>.DeepClone() => new(this); |
|||
|
|||
/// <inheritdoc/>
|
|||
public IDeepCloneable DeepClone() => new ExrMetadata(this); |
|||
|
|||
/// <inheritdoc/>
|
|||
public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
/// <summary>
|
|||
/// Cold path optimizations for throwing exr format based exceptions.
|
|||
/// </summary>
|
|||
internal static class ExrThrowHelper |
|||
{ |
|||
[DoesNotReturn] |
|||
public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); |
|||
|
|||
[DoesNotReturn] |
|||
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); |
|||
|
|||
[DoesNotReturn] |
|||
public static void ThrowNotSupportedVersion() => throw new NotSupportedException("Unsupported EXR version"); |
|||
|
|||
[DoesNotReturn] |
|||
public static void ThrowNotSupported(string msg) => throw new NotSupportedException(msg); |
|||
|
|||
[DoesNotReturn] |
|||
public static void ThrowInvalidImageHeader() => throw new InvalidImageContentException("Invalid EXR image header"); |
|||
|
|||
[DoesNotReturn] |
|||
public static void ThrowInvalidImageHeader(string msg) => throw new InvalidImageContentException(msg); |
|||
|
|||
[DoesNotReturn] |
|||
public static Exception NotSupportedCompressor(string compressionType) => throw new NotSupportedException($"Not supported encoder compression method: {compressionType}"); |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Exr.Constants; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Exr; |
|||
|
|||
internal static class ExrUtils |
|||
{ |
|||
/// <summary>
|
|||
/// Calcualtes the required bytes for a pixel row.
|
|||
/// </summary>
|
|||
/// <param name="channels">The image channels array.</param>
|
|||
/// <param name="width">The width in pixels of a row.</param>
|
|||
/// <returns>The number of bytes per row.</returns>
|
|||
public static ulong CalculateBytesPerRow(IList<ExrChannelInfo> channels, uint width) |
|||
{ |
|||
ulong bytesPerRow = 0; |
|||
foreach (ExrChannelInfo channelInfo in channels) |
|||
{ |
|||
if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal) |
|||
|| channelInfo.ChannelName.Equals("R", StringComparison.Ordinal) |
|||
|| channelInfo.ChannelName.Equals("G", StringComparison.Ordinal) |
|||
|| channelInfo.ChannelName.Equals("B", StringComparison.Ordinal) |
|||
|| channelInfo.ChannelName.Equals("Y", StringComparison.Ordinal)) |
|||
{ |
|||
if (channelInfo.PixelType == ExrPixelType.Half) |
|||
{ |
|||
bytesPerRow += 2UL * width; |
|||
} |
|||
else |
|||
{ |
|||
bytesPerRow += 4UL * width; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return bytesPerRow; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines how many pixel rows there are in a block. This varies depending on the compression used.
|
|||
/// </summary>
|
|||
/// <param name="compression">The compression used.</param>
|
|||
/// <returns>Pixel rows in a block.</returns>
|
|||
public static uint RowsPerBlock(ExrCompression compression) => compression switch |
|||
{ |
|||
ExrCompression.Zip or ExrCompression.Pxr24 => 16, |
|||
ExrCompression.B44 or ExrCompression.B44A or ExrCompression.Piz => 32, |
|||
_ => 1, |
|||
}; |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
### Some useful links for documentation about the OpenEXR format: |
|||
|
|||
- [Technical Introduction](https://openexr.readthedocs.io/en/latest/TechnicalIntroduction.html) |
|||
- [OpenExr file layout](https://openexr.readthedocs.io/en/latest/OpenEXRFileLayout.html) |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue