mirror of https://github.com/SixLabors/ImageSharp
Browse Source
# Conflicts: # src/ImageSharp/Configuration.cs # src/ImageSharp/Formats/ImageDecoderUtilities.cs # tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cspull/3096/head
2396 changed files with 73294 additions and 51970 deletions
@ -1,56 +0,0 @@ |
|||
name: "Commercial License : Bug Report" |
|||
description: | |
|||
Create a report to help us improve the project. For Commercial License holders only. |
|||
Please contact help@sixlabors.com for issues requiring private support. |
|||
labels: ["commercial", "needs triage"] |
|||
body: |
|||
- type: checkboxes |
|||
attributes: |
|||
label: Prerequisites |
|||
options: |
|||
- label: I have bought a Commercial License |
|||
required: true |
|||
- label: I have written a descriptive issue title |
|||
required: true |
|||
- label: I have verified that I am running the latest version of ImageSharp |
|||
required: true |
|||
- label: I have verified if the problem exist in both `DEBUG` and `RELEASE` mode |
|||
required: true |
|||
- label: I have searched [open](https://github.com/SixLabors/ImageSharp/issues) and [closed](https://github.com/SixLabors/ImageSharp/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported |
|||
required: true |
|||
- type: input |
|||
attributes: |
|||
label: ImageSharp version |
|||
validations: |
|||
required: true |
|||
- type: input |
|||
attributes: |
|||
label: Other ImageSharp packages and versions |
|||
validations: |
|||
required: true |
|||
- type: input |
|||
attributes: |
|||
label: Environment (Operating system, version and so on) |
|||
validations: |
|||
required: true |
|||
- type: input |
|||
attributes: |
|||
label: .NET Framework version |
|||
validations: |
|||
required: true |
|||
- type: textarea |
|||
attributes: |
|||
label: Description |
|||
description: A description of the bug |
|||
validations: |
|||
required: true |
|||
- type: textarea |
|||
attributes: |
|||
label: Steps to Reproduce |
|||
description: List of steps, sample code, failing test or link to a project that reproduces the behavior. Make sure you place a stack trace inside a code (```) block to avoid linking unrelated issues. |
|||
validations: |
|||
required: true |
|||
- type: textarea |
|||
attributes: |
|||
label: Images |
|||
description: Please upload images that can be used to reproduce issues in the area below. If the file type is not supported the file can be zipped and then uploaded instead. |
|||
@ -1 +1 @@ |
|||
Subproject commit 9a6cf00d9a3d482bb08211dd8309f4724a2735cb |
|||
Subproject commit a1d3ac20494631e3cc13132897573796b0e4ee6d |
|||
@ -1,7 +1,7 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<RuleSet Name="ImageSharp" ToolsVersion="17.0"> |
|||
<Include Path="..\shared-infrastructure\sixlabors.ruleset" Action="Default" /> |
|||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers"> |
|||
<Rule Id="SA1011" Action="None" /> |
|||
<Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp.NetAnalyzers" RuleNamespace="Microsoft.CodeAnalysis.CSharp.NetAnalyzers"> |
|||
<Rule Id="CA2022" Action="Info" /> |
|||
</Rules> |
|||
</RuleSet> |
|||
@ -1,240 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp; |
|||
|
|||
/// <content>
|
|||
/// Contains constructors and implicit conversion methods.
|
|||
/// </content>
|
|||
public readonly partial struct Color |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Color"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The <see cref="Rgba64"/> containing the color information.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public Color(Rgba64 pixel) |
|||
{ |
|||
this.data = pixel; |
|||
this.boxedHighPrecisionPixel = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Color"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The <see cref="Rgb48"/> containing the color information.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public Color(Rgb48 pixel) |
|||
{ |
|||
this.data = new Rgba64(pixel.R, pixel.G, pixel.B, ushort.MaxValue); |
|||
this.boxedHighPrecisionPixel = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Color"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The <see cref="La32"/> containing the color information.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public Color(La32 pixel) |
|||
{ |
|||
this.data = new Rgba64(pixel.L, pixel.L, pixel.L, pixel.A); |
|||
this.boxedHighPrecisionPixel = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Color"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The <see cref="L16"/> containing the color information.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public Color(L16 pixel) |
|||
{ |
|||
this.data = new Rgba64(pixel.PackedValue, pixel.PackedValue, pixel.PackedValue, ushort.MaxValue); |
|||
this.boxedHighPrecisionPixel = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Color"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The <see cref="Rgba32"/> containing the color information.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public Color(Rgba32 pixel) |
|||
{ |
|||
this.data = new Rgba64(pixel); |
|||
this.boxedHighPrecisionPixel = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Color"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The <see cref="Argb32"/> containing the color information.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public Color(Argb32 pixel) |
|||
{ |
|||
this.data = new Rgba64(pixel); |
|||
this.boxedHighPrecisionPixel = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Color"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The <see cref="Bgra32"/> containing the color information.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public Color(Bgra32 pixel) |
|||
{ |
|||
this.data = new Rgba64(pixel); |
|||
this.boxedHighPrecisionPixel = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Color"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The <see cref="Abgr32"/> containing the color information.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public Color(Abgr32 pixel) |
|||
{ |
|||
this.data = new Rgba64(pixel); |
|||
this.boxedHighPrecisionPixel = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Color"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The <see cref="Rgb24"/> containing the color information.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public Color(Rgb24 pixel) |
|||
{ |
|||
this.data = new Rgba64(pixel); |
|||
this.boxedHighPrecisionPixel = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Color"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The <see cref="Bgr24"/> containing the color information.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public Color(Bgr24 pixel) |
|||
{ |
|||
this.data = new Rgba64(pixel); |
|||
this.boxedHighPrecisionPixel = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Color"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The <see cref="Vector4"/> containing the color information.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public Color(Vector4 vector) |
|||
{ |
|||
vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); |
|||
this.boxedHighPrecisionPixel = new RgbaVector(vector.X, vector.Y, vector.Z, vector.W); |
|||
this.data = default; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a <see cref="Color"/> to <see cref="Vector4"/>.
|
|||
/// </summary>
|
|||
/// <param name="color">The <see cref="Color"/>.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public static explicit operator Vector4(Color color) => color.ToVector4(); |
|||
|
|||
/// <summary>
|
|||
/// Converts an <see cref="Vector4"/> to <see cref="Color"/>.
|
|||
/// </summary>
|
|||
/// <param name="source">The <see cref="Vector4"/>.</param>
|
|||
/// <returns>The <see cref="Color"/>.</returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static explicit operator Color(Vector4 source) => new(source); |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal Rgba32 ToRgba32() |
|||
{ |
|||
if (this.boxedHighPrecisionPixel is null) |
|||
{ |
|||
return this.data.ToRgba32(); |
|||
} |
|||
|
|||
Rgba32 value = default; |
|||
this.boxedHighPrecisionPixel.ToRgba32(ref value); |
|||
return value; |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal Bgra32 ToBgra32() |
|||
{ |
|||
if (this.boxedHighPrecisionPixel is null) |
|||
{ |
|||
return this.data.ToBgra32(); |
|||
} |
|||
|
|||
Bgra32 value = default; |
|||
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); |
|||
return value; |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal Argb32 ToArgb32() |
|||
{ |
|||
if (this.boxedHighPrecisionPixel is null) |
|||
{ |
|||
return this.data.ToArgb32(); |
|||
} |
|||
|
|||
Argb32 value = default; |
|||
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); |
|||
return value; |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal Abgr32 ToAbgr32() |
|||
{ |
|||
if (this.boxedHighPrecisionPixel is null) |
|||
{ |
|||
return this.data.ToAbgr32(); |
|||
} |
|||
|
|||
Abgr32 value = default; |
|||
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); |
|||
return value; |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal Rgb24 ToRgb24() |
|||
{ |
|||
if (this.boxedHighPrecisionPixel is null) |
|||
{ |
|||
return this.data.ToRgb24(); |
|||
} |
|||
|
|||
Rgb24 value = default; |
|||
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); |
|||
return value; |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal Bgr24 ToBgr24() |
|||
{ |
|||
if (this.boxedHighPrecisionPixel is null) |
|||
{ |
|||
return this.data.ToBgr24(); |
|||
} |
|||
|
|||
Bgr24 value = default; |
|||
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); |
|||
return value; |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
internal Vector4 ToVector4() |
|||
{ |
|||
if (this.boxedHighPrecisionPixel is null) |
|||
{ |
|||
return this.data.ToScaledVector4(); |
|||
} |
|||
|
|||
return this.boxedHighPrecisionPixel.ToScaledVector4(); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp; |
|||
|
|||
/// <summary>
|
|||
/// Specifies the channel order when formatting or parsing a color as a hexadecimal string.
|
|||
/// </summary>
|
|||
public enum ColorHexFormat |
|||
{ |
|||
/// <summary>
|
|||
/// Uses <c>RRGGBBAA</c> channel order where the red, green, and blue components come first,
|
|||
/// followed by the alpha component. This matches the CSS Color Module Level 4 and common web standards.
|
|||
/// <para>
|
|||
/// When parsing, supports the following formats:
|
|||
/// <list type="bullet">
|
|||
/// <item><description><c>#RGB</c> expands to <c>RRGGBBFF</c> (fully opaque)</description></item>
|
|||
/// <item><description><c>#RGBA</c> expands to <c>RRGGBBAA</c></description></item>
|
|||
/// <item><description><c>#RRGGBB</c> expands to <c>RRGGBBFF</c> (fully opaque)</description></item>
|
|||
/// <item><description><c>#RRGGBBAA</c> used as-is</description></item>
|
|||
/// </list>
|
|||
/// </para>
|
|||
/// When formatting, outputs an 8-digit hex string in <c>RRGGBBAA</c> order.
|
|||
/// </summary>
|
|||
Rgba, |
|||
|
|||
/// <summary>
|
|||
/// Uses <c>AARRGGBB</c> channel order where the alpha component comes first,
|
|||
/// followed by the red, green, and blue components. This matches the Microsoft/XAML convention.
|
|||
/// <para>
|
|||
/// When parsing, supports the following formats:
|
|||
/// <list type="bullet">
|
|||
/// <item><description><c>#ARGB</c> expands to <c>AARRGGBB</c></description></item>
|
|||
/// <item><description><c>#AARRGGBB</c> used as-is</description></item>
|
|||
/// </list>
|
|||
/// </para>
|
|||
/// When formatting, outputs an 8-digit hex string in <c>AARRGGBB</c> order.
|
|||
/// </summary>
|
|||
Argb |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Enumerate the possible sources of the white point used in chromatic adaptation.
|
|||
/// </summary>
|
|||
public enum ChromaticAdaptionWhitePointSource |
|||
{ |
|||
/// <summary>
|
|||
/// The white point of the source color space.
|
|||
/// </summary>
|
|||
WhitePoint, |
|||
|
|||
/// <summary>
|
|||
/// The white point of the source working space.
|
|||
/// </summary>
|
|||
RgbWorkingSpace |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Constants use for Cie conversion calculations
|
|||
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html"/>
|
|||
/// </summary>
|
|||
internal static class CieConstants |
|||
{ |
|||
/// <summary>
|
|||
/// 216F / 24389F
|
|||
/// </summary>
|
|||
public const float Epsilon = 216f / 24389f; |
|||
|
|||
/// <summary>
|
|||
/// 24389F / 27F
|
|||
/// </summary>
|
|||
public const float Kappa = 24389f / 27f; |
|||
} |
|||
@ -0,0 +1,220 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents a CIE L*a*b* 1976 color.
|
|||
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct CieLab : IProfileConnectingSpace<CieLab, CieXyz> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLab"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="l">The lightness dimension.</param>
|
|||
/// <param name="a">The a (green - magenta) component.</param>
|
|||
/// <param name="b">The b (blue - yellow) component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieLab(float l, float a, float b) |
|||
{ |
|||
// Not clamping as documentation about this space only indicates "usual" ranges
|
|||
this.L = l; |
|||
this.A = a; |
|||
this.B = b; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLab"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the l, a, b components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieLab(Vector3 vector) |
|||
{ |
|||
this.L = vector.X; |
|||
this.A = vector.Y; |
|||
this.B = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the lightness dimension.
|
|||
/// <remarks>A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
|
|||
/// </summary>
|
|||
public float L { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the a color component.
|
|||
/// <remarks>A value usually ranging from -100 to 100. Negative is green, positive magenta.</remarks>
|
|||
/// </summary>
|
|||
public float A { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the b color component.
|
|||
/// <remarks>A value usually ranging from -100 to 100. Negative is blue, positive is yellow</remarks>
|
|||
/// </summary>
|
|||
public float B { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieLab"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieLab"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieLab"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(CieLab left, CieLab right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieLab"/> objects for inequality
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieLab"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieLab"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(CieLab left, CieLab right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() |
|||
{ |
|||
Vector3 v3 = default; |
|||
v3 += this.AsVector3Unsafe(); |
|||
v3 += new Vector3(0, 128F, 128F); |
|||
v3 /= new Vector3(100F, 255F, 255F); |
|||
return new Vector4(v3, 1F); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static CieLab FromScaledVector4(Vector4 source) |
|||
{ |
|||
Vector3 v3 = source.AsVector3(); |
|||
v3 *= new Vector3(100F, 255, 255); |
|||
v3 -= new Vector3(0, 128F, 128F); |
|||
return new CieLab(v3); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<CieLab> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToScaledVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<CieLab> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = FromScaledVector4(source[i]); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static CieLab FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) |
|||
{ |
|||
// Conversion algorithm described here:
|
|||
// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
|
|||
CieXyz whitePoint = options.TargetWhitePoint; |
|||
float wx = whitePoint.X, wy = whitePoint.Y, wz = whitePoint.Z; |
|||
|
|||
float xr = source.X / wx, yr = source.Y / wy, zr = source.Z / wz; |
|||
|
|||
const float inv116 = 1 / 116F; |
|||
|
|||
float fx = xr > CieConstants.Epsilon ? MathF.Pow(xr, 0.3333333F) : ((CieConstants.Kappa * xr) + 16F) * inv116; |
|||
float fy = yr > CieConstants.Epsilon ? MathF.Pow(yr, 0.3333333F) : ((CieConstants.Kappa * yr) + 16F) * inv116; |
|||
float fz = zr > CieConstants.Epsilon ? MathF.Pow(zr, 0.3333333F) : ((CieConstants.Kappa * zr) + 16F) * inv116; |
|||
|
|||
float l = (116F * fy) - 16F; |
|||
float a = 500F * (fx - fy); |
|||
float b = 200F * (fy - fz); |
|||
|
|||
return new CieLab(l, a, b); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<CieLab> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieXyz xyz = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in xyz); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
// Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
|
|||
float l = this.L, a = this.A, b = this.B; |
|||
float fy = (l + 16) / 116F; |
|||
float fx = (a / 500F) + fy; |
|||
float fz = fy - (b / 200F); |
|||
|
|||
float fx3 = Numerics.Pow3(fx); |
|||
float fz3 = Numerics.Pow3(fz); |
|||
|
|||
float xr = fx3 > CieConstants.Epsilon ? fx3 : ((116F * fx) - 16F) / CieConstants.Kappa; |
|||
float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? Numerics.Pow3((l + 16F) / 116F) : l / CieConstants.Kappa; |
|||
float zr = fz3 > CieConstants.Epsilon ? fz3 : ((116F * fz) - 16F) / CieConstants.Kappa; |
|||
|
|||
CieXyz whitePoint = options.SourceWhitePoint; |
|||
Vector3 wxyz = new(whitePoint.X, whitePoint.Y, whitePoint.Z); |
|||
Vector3 xyzr = new(xr, yr, zr); |
|||
|
|||
return new CieXyz(xyzr * wxyz); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieLab> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieLab lab = source[i]; |
|||
destination[i] = lab.ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.WhitePoint; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"CieLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is CieLab other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(CieLab other) |
|||
=> this.AsVector3Unsafe() == other.AsVector3Unsafe(); |
|||
|
|||
private Vector3 AsVector3Unsafe() => Unsafe.As<CieLab, Vector3>(ref Unsafe.AsRef(in this)); |
|||
} |
|||
@ -0,0 +1,220 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color.
|
|||
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC"/>
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct CieLch : IColorProfile<CieLch, CieLab> |
|||
{ |
|||
private static readonly Vector3 Min = new(0, -200, 0); |
|||
private static readonly Vector3 Max = new(100, 200, 360); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLch"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="l">The lightness dimension.</param>
|
|||
/// <param name="c">The chroma, relative saturation.</param>
|
|||
/// <param name="h">The hue in degrees.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieLch(float l, float c, float h) |
|||
: this(new Vector3(l, c, h)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLch"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the l, c, h components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieLch(Vector3 vector) |
|||
{ |
|||
vector = Vector3.Clamp(vector, Min, Max); |
|||
this.L = vector.X; |
|||
this.C = vector.Y; |
|||
this.H = vector.Z; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
|||
private CieLch(Vector3 vector, bool _) |
|||
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
|
|||
{ |
|||
vector = Vector3.Clamp(vector, Min, Max); |
|||
this.L = vector.X; |
|||
this.C = vector.Y; |
|||
this.H = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the lightness dimension.
|
|||
/// <remarks>A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
|
|||
/// </summary>
|
|||
public float L { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the a chroma component.
|
|||
/// <remarks>A value ranging from -200 to 200.</remarks>
|
|||
/// </summary>
|
|||
public float C { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the h° hue component in degrees.
|
|||
/// <remarks>A value ranging from 0 to 360.</remarks>
|
|||
/// </summary>
|
|||
public float H { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieLch"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieLch"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieLch"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(CieLch left, CieLch right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieLch"/> objects for inequality
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieLch"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieLch"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(CieLch left, CieLch right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() |
|||
{ |
|||
Vector3 v3 = default; |
|||
v3 += this.AsVector3Unsafe(); |
|||
v3 += new Vector3(0, 200, 0); |
|||
v3 /= new Vector3(100, 400, 360); |
|||
return new Vector4(v3, 1F); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static CieLch FromScaledVector4(Vector4 source) |
|||
{ |
|||
Vector3 v3 = source.AsVector3(); |
|||
v3 *= new Vector3(100, 400, 360); |
|||
v3 -= new Vector3(0, 200, 0); |
|||
return new CieLch(v3, true); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<CieLch> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToScaledVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<CieLch> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = FromScaledVector4(source[i]); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static CieLch FromProfileConnectingSpace(ColorConversionOptions options, in CieLab source) |
|||
{ |
|||
// Conversion algorithm described here:
|
|||
// https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC
|
|||
float l = source.L, a = source.A, b = source.B; |
|||
float c = MathF.Sqrt((a * a) + (b * b)); |
|||
float hRadians = MathF.Atan2(b, a); |
|||
float hDegrees = GeometryUtilities.RadianToDegree(hRadians); |
|||
|
|||
// Wrap the angle round at 360.
|
|||
hDegrees %= 360; |
|||
|
|||
// Make sure it's not negative.
|
|||
while (hDegrees < 0) |
|||
{ |
|||
hDegrees += 360; |
|||
} |
|||
|
|||
return new CieLch(l, c, hDegrees); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieLab> source, Span<CieLch> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieLab lab = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in lab); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public CieLab ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
// Conversion algorithm described here:
|
|||
// https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC
|
|||
float l = this.L, c = this.C, hDegrees = this.H; |
|||
float hRadians = GeometryUtilities.DegreeToRadian(hDegrees); |
|||
|
|||
float a = c * MathF.Cos(hRadians); |
|||
float b = c * MathF.Sin(hRadians); |
|||
|
|||
return new CieLab(l, a, b); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieLch> source, Span<CieLab> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieLch lch = source[i]; |
|||
destination[i] = lch.ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.WhitePoint; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
=> HashCode.Combine(this.L, this.C, this.H); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"CieLch({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public override bool Equals(object? obj) => obj is CieLch other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(CieLch other) |
|||
=> this.AsVector3Unsafe() == other.AsVector3Unsafe(); |
|||
|
|||
private Vector3 AsVector3Unsafe() => Unsafe.As<CieLch, Vector3>(ref Unsafe.AsRef(in this)); |
|||
} |
|||
@ -0,0 +1,219 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color.
|
|||
/// <see href="https://en.wikipedia.org/wiki/CIELAB_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC"/>
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct CieLchuv : IColorProfile<CieLchuv, CieXyz> |
|||
{ |
|||
private static readonly Vector3 Min = new(0, -200, 0); |
|||
private static readonly Vector3 Max = new(100, 200, 360); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLchuv"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="l">The lightness dimension.</param>
|
|||
/// <param name="c">The chroma, relative saturation.</param>
|
|||
/// <param name="h">The hue in degrees.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieLchuv(float l, float c, float h) |
|||
: this(new Vector3(l, c, h)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLchuv"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the l, c, h components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieLchuv(Vector3 vector) |
|||
{ |
|||
vector = Vector3.Clamp(vector, Min, Max); |
|||
this.L = vector.X; |
|||
this.C = vector.Y; |
|||
this.H = vector.Z; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
|||
private CieLchuv(Vector3 vector, bool _) |
|||
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
|
|||
{ |
|||
this.L = vector.X; |
|||
this.C = vector.Y; |
|||
this.H = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the lightness dimension.
|
|||
/// <remarks>A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
|
|||
/// </summary>
|
|||
public float L { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the a chroma component.
|
|||
/// <remarks>A value ranging from -200 to 200.</remarks>
|
|||
/// </summary>
|
|||
public float C { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the h° hue component in degrees.
|
|||
/// <remarks>A value ranging from 0 to 360.</remarks>
|
|||
/// </summary>
|
|||
public float H { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieLchuv"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieLchuv"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieLchuv"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
public static bool operator ==(CieLchuv left, CieLchuv right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieLchuv"/> objects for inequality
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieLchuv"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieLchuv"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
public static bool operator !=(CieLchuv left, CieLchuv right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() |
|||
{ |
|||
Vector3 v3 = default; |
|||
v3 += this.AsVector3Unsafe(); |
|||
v3 += new Vector3(0, 200, 0); |
|||
v3 /= new Vector3(100, 400, 360); |
|||
return new Vector4(v3, 1F); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static CieLchuv FromScaledVector4(Vector4 source) |
|||
{ |
|||
Vector3 v3 = source.AsVector3(); |
|||
v3 *= new Vector3(100, 400, 360); |
|||
v3 -= new Vector3(0, 200, 0); |
|||
return new CieLchuv(v3, true); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<CieLchuv> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToScaledVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<CieLchuv> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = FromScaledVector4(source[i]); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static CieLchuv FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) |
|||
{ |
|||
CieLuv luv = CieLuv.FromProfileConnectingSpace(options, source); |
|||
|
|||
// Conversion algorithm described here:
|
|||
// https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29
|
|||
float l = luv.L, u = luv.U, v = luv.V; |
|||
float c = MathF.Sqrt((u * u) + (v * v)); |
|||
float hRadians = MathF.Atan2(v, u); |
|||
float hDegrees = GeometryUtilities.RadianToDegree(hRadians); |
|||
|
|||
// Wrap the angle round at 360.
|
|||
hDegrees %= 360; |
|||
|
|||
// Make sure it's not negative.
|
|||
while (hDegrees < 0) |
|||
{ |
|||
hDegrees += 360; |
|||
} |
|||
|
|||
return new CieLchuv(l, c, hDegrees); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<CieLchuv> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieXyz xyz = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in xyz); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
// Conversion algorithm described here:
|
|||
// https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29
|
|||
float l = this.L, c = this.C, hDegrees = this.H; |
|||
float hRadians = GeometryUtilities.DegreeToRadian(hDegrees); |
|||
|
|||
float u = c * MathF.Cos(hRadians); |
|||
float v = c * MathF.Sin(hRadians); |
|||
|
|||
CieLuv luv = new(l, u, v); |
|||
return luv.ToProfileConnectingSpace(options); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieLchuv> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieLchuv lch = source[i]; |
|||
destination[i] = lch.ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.WhitePoint; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
=> HashCode.Combine(this.L, this.C, this.H); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
=> FormattableString.Invariant($"CieLchuv({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) |
|||
=> obj is CieLchuv other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(CieLchuv other) |
|||
=> this.AsVector3Unsafe() == other.AsVector3Unsafe(); |
|||
|
|||
private Vector3 AsVector3Unsafe() => Unsafe.As<CieLchuv, Vector3>(ref Unsafe.AsRef(in this)); |
|||
} |
|||
@ -0,0 +1,232 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// The CIE 1976 (L*, u*, v*) color space, commonly known by its abbreviation CIELUV, is a color space adopted by the International
|
|||
/// Commission on Illumination (CIE) in 1976, as a simple-to-compute transformation of the 1931 CIE XYZ color space, but which
|
|||
/// attempted perceptual uniformity
|
|||
/// <see href="https://en.wikipedia.org/wiki/CIELUV"/>
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct CieLuv : IColorProfile<CieLuv, CieXyz> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLuv"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="l">The lightness dimension.</param>
|
|||
/// <param name="u">The blue-yellow chromaticity coordinate of the given white point.</param>
|
|||
/// <param name="v">The red-green chromaticity coordinate of the given white point.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieLuv(float l, float u, float v) |
|||
{ |
|||
// Not clamping as documentation about this space only indicates "usual" ranges
|
|||
this.L = l; |
|||
this.U = u; |
|||
this.V = v; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLuv"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the l, u, v components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieLuv(Vector3 vector) |
|||
{ |
|||
this.L = vector.X; |
|||
this.U = vector.Y; |
|||
this.V = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the lightness dimension
|
|||
/// <remarks>A value usually ranging between 0 and 100.</remarks>
|
|||
/// </summary>
|
|||
public float L { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue-yellow chromaticity coordinate of the given white point.
|
|||
/// <remarks>A value usually ranging between -100 and 100.</remarks>
|
|||
/// </summary>
|
|||
public float U { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the red-green chromaticity coordinate of the given white point.
|
|||
/// <remarks>A value usually ranging between -100 and 100.</remarks>
|
|||
/// </summary>
|
|||
public float V { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieLuv"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieLuv"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieLuv"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(CieLuv left, CieLuv right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieLuv"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieLuv"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieLuv"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(CieLuv left, CieLuv right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() => throw new NotImplementedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static CieLuv FromScaledVector4(Vector4 source) => throw new NotImplementedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<CieLuv> source, Span<Vector4> destination) => throw new NotImplementedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<CieLuv> destination) => throw new NotImplementedException(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static CieLuv FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) |
|||
{ |
|||
// Use doubles here for accuracy.
|
|||
// Conversion algorithm described here:
|
|||
// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Luv.html
|
|||
CieXyz whitePoint = options.TargetWhitePoint; |
|||
|
|||
double yr = source.Y / whitePoint.Y; |
|||
|
|||
double den = source.X + (15 * source.Y) + (3 * source.Z); |
|||
double up = den > 0 ? ComputeU(in source) : 0; |
|||
double vp = den > 0 ? ComputeV(in source) : 0; |
|||
double upr = ComputeU(in whitePoint); |
|||
double vpr = ComputeV(in whitePoint); |
|||
|
|||
const double e = 1 / 3d; |
|||
double l = yr > CieConstants.Epsilon |
|||
? ((116 * Math.Pow(yr, e)) - 16d) |
|||
: (CieConstants.Kappa * yr); |
|||
|
|||
if (double.IsNaN(l) || l == -0d) |
|||
{ |
|||
l = 0; |
|||
} |
|||
|
|||
double u = 13 * l * (up - upr); |
|||
double v = 13 * l * (vp - vpr); |
|||
|
|||
if (double.IsNaN(u) || u == -0d) |
|||
{ |
|||
u = 0; |
|||
} |
|||
|
|||
if (double.IsNaN(v) || v == -0d) |
|||
{ |
|||
v = 0; |
|||
} |
|||
|
|||
return new CieLuv((float)l, (float)u, (float)v); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<CieLuv> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieXyz xyz = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in xyz); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
// Use doubles here for accuracy.
|
|||
// Conversion algorithm described here:
|
|||
// http://www.brucelindbloom.com/index.html?Eqn_Luv_to_XYZ.html
|
|||
CieXyz whitePoint = options.SourceWhitePoint; |
|||
|
|||
double l = this.L, u = this.U, v = this.V; |
|||
|
|||
double u0 = ComputeU(in whitePoint); |
|||
double v0 = ComputeV(in whitePoint); |
|||
|
|||
double y = l > CieConstants.Kappa * CieConstants.Epsilon |
|||
? Numerics.Pow3((l + 16) / 116d) |
|||
: l / CieConstants.Kappa; |
|||
|
|||
double a = ((52 * l / (u + (13 * l * u0))) - 1) / 3; |
|||
double b = -5 * y; |
|||
const double c = -1 / 3d; |
|||
double d = y * ((39 * l / (v + (13 * l * v0))) - 5); |
|||
|
|||
double x = (d - b) / (a - c); |
|||
double z = (x * a) + b; |
|||
|
|||
if (double.IsNaN(x) || x == -0d) |
|||
{ |
|||
x = 0; |
|||
} |
|||
|
|||
if (double.IsNaN(y) || y == -0d) |
|||
{ |
|||
y = 0; |
|||
} |
|||
|
|||
if (double.IsNaN(z) || z == -0d) |
|||
{ |
|||
z = 0; |
|||
} |
|||
|
|||
return new CieXyz((float)x, (float)y, (float)z); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieLuv> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieLuv luv = source[i]; |
|||
destination[i] = luv.ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.WhitePoint; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.L, this.U, this.V); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"CieLuv({this.L:#0.##}, {this.U:#0.##}, {this.V:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is CieLuv other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(CieLuv other) |
|||
=> this.AsVector3Unsafe() == other.AsVector3Unsafe(); |
|||
|
|||
private Vector3 AsVector3Unsafe() => Unsafe.As<CieLuv, Vector3>(ref Unsafe.AsRef(in this)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static double ComputeU(in CieXyz source) |
|||
=> (4 * source.X) / (source.X + (15 * source.Y) + (3 * source.Z)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static double ComputeV(in CieXyz source) |
|||
=> (9 * source.Y) / (source.X + (15 * source.Y) + (3 * source.Z)); |
|||
} |
|||
@ -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.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents the coordinates of CIEXY chromaticity space.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct CieXyChromaticityCoordinates : IEquatable<CieXyChromaticityCoordinates> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieXyChromaticityCoordinates"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">Chromaticity coordinate x (usually from 0 to 1)</param>
|
|||
/// <param name="y">Chromaticity coordinate y (usually from 0 to 1)</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public CieXyChromaticityCoordinates(float x, float y) |
|||
{ |
|||
this.X = x; |
|||
this.Y = y; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the chromaticity X-coordinate.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Ranges usually from 0 to 1.
|
|||
/// </remarks>
|
|||
public float X { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chromaticity Y-coordinate
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Ranges usually from 0 to 1.
|
|||
/// </remarks>
|
|||
public float Y { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieXyChromaticityCoordinates"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieXyChromaticityCoordinates"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieXyChromaticityCoordinates"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static bool operator ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) |
|||
=> left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieXyChromaticityCoordinates"/> objects for inequality
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieXyChromaticityCoordinates"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieXyChromaticityCoordinates"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static bool operator !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) |
|||
=> !left.Equals(right); |
|||
|
|||
/// <inheritdoc />
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public override int GetHashCode() |
|||
=> HashCode.Combine(this.X, this.Y); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
=> FormattableString.Invariant($"CieXyChromaticityCoordinates({this.X:#0.##}, {this.Y:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) |
|||
=> obj is CieXyChromaticityCoordinates other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public bool Equals(CieXyChromaticityCoordinates other) |
|||
=> this.AsVector2Unsafe() == other.AsVector2Unsafe(); |
|||
|
|||
private Vector2 AsVector2Unsafe() => Unsafe.As<CieXyChromaticityCoordinates, Vector2>(ref Unsafe.AsRef(in this)); |
|||
} |
|||
@ -0,0 +1,189 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents an CIE xyY 1931 color
|
|||
/// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space"/>
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct CieXyy : IColorProfile<CieXyy, CieXyz> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieXyy"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">The x chroma component.</param>
|
|||
/// <param name="y">The y chroma component.</param>
|
|||
/// <param name="yl">The y luminance component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieXyy(float x, float y, float yl) |
|||
{ |
|||
// Not clamping as documentation about this space only indicates "usual" ranges
|
|||
this.X = x; |
|||
this.Y = y; |
|||
this.Yl = yl; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieXyy"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the x, y, Y components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieXyy(Vector3 vector) |
|||
{ |
|||
// Not clamping as documentation about this space only indicates "usual" ranges
|
|||
this.X = vector.X; |
|||
this.Y = vector.Y; |
|||
this.Yl = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the X chrominance component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float X { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Y chrominance component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Y { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Y luminance component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Yl { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieXyy"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieXyy"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieXyy"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(CieXyy left, CieXyy right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieXyy"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieXyy"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieXyy"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(CieXyy left, CieXyy right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() |
|||
=> new(this.AsVector3Unsafe(), 1F); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static CieXyy FromScaledVector4(Vector4 source) |
|||
=> new(source.AsVector3()); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<CieXyy> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToScaledVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<CieXyy> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = FromScaledVector4(source[i]); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static CieXyy FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) |
|||
{ |
|||
float x = source.X / (source.X + source.Y + source.Z); |
|||
float y = source.Y / (source.X + source.Y + source.Z); |
|||
|
|||
if (float.IsNaN(x) || float.IsNaN(y)) |
|||
{ |
|||
return new CieXyy(0, 0, source.Y); |
|||
} |
|||
|
|||
return new CieXyy(x, y, source.Y); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<CieXyy> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieXyz xyz = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in xyz); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
if (MathF.Abs(this.Y) < Constants.Epsilon) |
|||
{ |
|||
return new CieXyz(0, 0, this.Yl); |
|||
} |
|||
|
|||
float x = (this.X * this.Yl) / this.Y; |
|||
float y = this.Yl; |
|||
float z = ((1 - this.X - this.Y) * y) / this.Y; |
|||
|
|||
return new CieXyz(x, y, z); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyy> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieXyy xyz = source[i]; |
|||
destination[i] = xyz.ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.WhitePoint; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
=> HashCode.Combine(this.X, this.Y, this.Yl); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
=> FormattableString.Invariant($"CieXyy({this.X:#0.##}, {this.Y:#0.##}, {this.Yl:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is CieXyy other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(CieXyy other) |
|||
=> this.AsVector3Unsafe() == other.AsVector3Unsafe(); |
|||
|
|||
private Vector3 AsVector3Unsafe() => Unsafe.As<CieXyy, Vector3>(ref Unsafe.AsRef(in this)); |
|||
} |
|||
@ -0,0 +1,203 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents an CIE XYZ 1931 color
|
|||
/// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space#Definition_of_the_CIE_XYZ_color_space"/>
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct CieXyz : IProfileConnectingSpace<CieXyz, CieXyz> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieXyz"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="x">X is a mix (a linear combination) of cone response curves chosen to be nonnegative</param>
|
|||
/// <param name="y">The y luminance component.</param>
|
|||
/// <param name="z">Z is quasi-equal to blue stimulation, or the S cone of the human eye.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public CieXyz(float x, float y, float z) |
|||
{ |
|||
// Not clamping as documentation about this space only indicates "usual" ranges
|
|||
this.X = x; |
|||
this.Y = y; |
|||
this.Z = z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieXyz"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the x, y, z components.</param>
|
|||
public CieXyz(Vector3 vector) |
|||
{ |
|||
this.X = vector.X; |
|||
this.Y = vector.Y; |
|||
this.Z = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the X component. A mix (a linear combination) of cone response curves chosen to be nonnegative.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float X { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Y luminance component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Y { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Z component. Quasi-equal to blue stimulation, or the S cone response.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Z { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieXyz"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieXyz"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieXyz"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(CieXyz left, CieXyz right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieXyz"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieXyz"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieXyz"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(CieXyz left, CieXyz right) => !left.Equals(right); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal Vector3 ToVector3() => new(this.X, this.Y, this.Z); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal Vector4 ToVector4() |
|||
{ |
|||
Vector3 v3 = default; |
|||
v3 += this.AsVector3Unsafe(); |
|||
return new Vector4(v3, 1F); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() |
|||
{ |
|||
Vector3 v3 = default; |
|||
v3 += this.AsVector3Unsafe(); |
|||
v3 *= 32768F / 65535; |
|||
return new Vector4(v3, 1F); |
|||
} |
|||
|
|||
internal static CieXyz FromVector4(Vector4 source) |
|||
{ |
|||
Vector3 v3 = source.AsVector3(); |
|||
return new CieXyz(v3); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static CieXyz FromScaledVector4(Vector4 source) |
|||
{ |
|||
Vector3 v3 = source.AsVector3(); |
|||
v3 *= 65535 / 32768F; |
|||
return new CieXyz(v3); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<CieXyz> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToScaledVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = FromScaledVector4(source[i]); |
|||
} |
|||
} |
|||
|
|||
internal static void FromVector4(ReadOnlySpan<Vector4> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = FromVector4(source[i]); |
|||
} |
|||
} |
|||
|
|||
internal static void ToVector4(ReadOnlySpan<CieXyz> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static CieXyz FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) |
|||
=> new(source.X, source.Y, source.Z); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
source.CopyTo(destination[..source.Length]); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) |
|||
=> new(this.X, this.Y, this.Z); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
source.CopyTo(destination[..source.Length]); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.WhitePoint; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Z); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"CieXyz({this.X:#0.##}, {this.Y:#0.##}, {this.Z:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is CieXyz other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(CieXyz other) |
|||
=> this.AsVector3Unsafe() == other.AsVector3Unsafe(); |
|||
|
|||
internal Vector3 AsVector3Unsafe() => Unsafe.As<CieXyz, Vector3>(ref Unsafe.AsRef(in this)); |
|||
} |
|||
@ -0,0 +1,202 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents an CMYK (cyan, magenta, yellow, keyline) color.
|
|||
/// <see href="https://en.wikipedia.org/wiki/CMYK_color_model"/>
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct Cmyk : IColorProfile<Cmyk, Rgb> |
|||
{ |
|||
private static readonly Vector4 Min = Vector4.Zero; |
|||
private static readonly Vector4 Max = Vector4.One; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Cmyk"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="c">The cyan component.</param>
|
|||
/// <param name="m">The magenta component.</param>
|
|||
/// <param name="y">The yellow component.</param>
|
|||
/// <param name="k">The keyline black component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Cmyk(float c, float m, float y, float k) |
|||
: this(new Vector4(c, m, y, k)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Cmyk"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the c, m, y, k components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Cmyk(Vector4 vector) |
|||
{ |
|||
vector = Vector4.Clamp(vector, Min, Max); |
|||
this.C = vector.X; |
|||
this.M = vector.Y; |
|||
this.Y = vector.Z; |
|||
this.K = vector.W; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
|||
private Cmyk(Vector4 vector, bool _) |
|||
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
|
|||
{ |
|||
this.C = vector.X; |
|||
this.M = vector.Y; |
|||
this.Y = vector.Z; |
|||
this.K = vector.W; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the cyan color component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float C { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the magenta color component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float M { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the yellow color component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Y { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the keyline black color component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float K { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Cmyk"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Cmyk"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Cmyk"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(Cmyk left, Cmyk right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Cmyk"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Cmyk"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Cmyk"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(Cmyk left, Cmyk right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() |
|||
{ |
|||
Vector4 v4 = default; |
|||
v4 += this.AsVector4Unsafe(); |
|||
return v4; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Cmyk FromScaledVector4(Vector4 source) |
|||
=> new(source, true); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<Cmyk> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
MemoryMarshal.Cast<Cmyk, Vector4>(source).CopyTo(destination); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<Cmyk> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
MemoryMarshal.Cast<Vector4, Cmyk>(source).CopyTo(destination); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Cmyk FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) |
|||
{ |
|||
// To CMY
|
|||
Vector3 cmy = Vector3.One - source.AsVector3Unsafe(); |
|||
|
|||
// To CMYK
|
|||
Vector3 k = new(MathF.Min(cmy.X, MathF.Min(cmy.Y, cmy.Z))); |
|||
|
|||
if (k.X >= 1F - Constants.Epsilon) |
|||
{ |
|||
return new Cmyk(0, 0, 0, 1F); |
|||
} |
|||
|
|||
cmy = (cmy - k) / (Vector3.One - k); |
|||
|
|||
return new Cmyk(cmy.X, cmy.Y, cmy.Z, k.X); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<Cmyk> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: We can optimize this by using SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Rgb rgb = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in rgb); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Rgb ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
Vector3 rgb = (Vector3.One - new Vector3(this.C, this.M, this.Y)) * (1F - this.K); |
|||
return Rgb.FromScaledVector3(rgb); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Cmyk> source, Span<Rgb> destination) |
|||
{ |
|||
// TODO: We can possibly optimize this by using SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.RgbWorkingSpace; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public override int GetHashCode() |
|||
=> HashCode.Combine(this.C, this.M, this.Y, this.K); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
=> FormattableString.Invariant($"Cmyk({this.C:#0.##}, {this.M:#0.##}, {this.Y:#0.##}, {this.K:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) |
|||
=> obj is Cmyk other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(Cmyk other) |
|||
=> this.AsVector4Unsafe() == other.AsVector4Unsafe(); |
|||
|
|||
private Vector4 AsVector4Unsafe() => Unsafe.As<Cmyk, Vector4>(ref Unsafe.AsRef(in this)); |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Provides options for color profile conversion.
|
|||
/// </summary>
|
|||
public class ColorConversionOptions |
|||
{ |
|||
private Matrix4x4 adaptationMatrix; |
|||
private YCbCrTransform yCbCrTransform; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ColorConversionOptions"/> class.
|
|||
/// </summary>
|
|||
public ColorConversionOptions() |
|||
{ |
|||
this.AdaptationMatrix = KnownChromaticAdaptationMatrices.Bradford; |
|||
this.YCbCrTransform = KnownYCbCrMatrices.BT601; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the memory allocator.
|
|||
/// </summary>
|
|||
public MemoryAllocator MemoryAllocator { get; init; } = MemoryAllocator.Default; |
|||
|
|||
/// <summary>
|
|||
/// Gets the source white point used for chromatic adaptation in conversions from/to XYZ color space.
|
|||
/// </summary>
|
|||
public CieXyz SourceWhitePoint { get; init; } = KnownIlluminants.D50; |
|||
|
|||
/// <summary>
|
|||
/// Gets the destination white point used for chromatic adaptation in conversions from/to XYZ color space.
|
|||
/// </summary>
|
|||
public CieXyz TargetWhitePoint { get; init; } = KnownIlluminants.D50; |
|||
|
|||
/// <summary>
|
|||
/// Gets the source working space used for companding in conversions from/to XYZ color space.
|
|||
/// </summary>
|
|||
public RgbWorkingSpace SourceRgbWorkingSpace { get; init; } = KnownRgbWorkingSpaces.SRgb; |
|||
|
|||
/// <summary>
|
|||
/// Gets the destination working space used for companding in conversions from/to XYZ color space.
|
|||
/// </summary>
|
|||
public RgbWorkingSpace TargetRgbWorkingSpace { get; init; } = KnownRgbWorkingSpaces.SRgb; |
|||
|
|||
/// <summary>
|
|||
/// Gets the YCbCr matrix to used to perform conversions from/to RGB.
|
|||
/// </summary>
|
|||
public YCbCrTransform YCbCrTransform |
|||
{ |
|||
get => this.yCbCrTransform; |
|||
init |
|||
{ |
|||
this.yCbCrTransform = value; |
|||
this.TransposedYCbCrTransform = value.Transpose(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the source ICC profile.
|
|||
/// </summary>
|
|||
public IccProfile? SourceIccProfile { get; init; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the target ICC profile.
|
|||
/// </summary>
|
|||
public IccProfile? TargetIccProfile { get; init; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the transformation matrix used in conversion to perform chromatic adaptation.
|
|||
/// <see cref="KnownChromaticAdaptationMatrices"/> for further information. Default is Bradford.
|
|||
/// </summary>
|
|||
public Matrix4x4 AdaptationMatrix |
|||
{ |
|||
get => this.adaptationMatrix; |
|||
init |
|||
{ |
|||
this.adaptationMatrix = value; |
|||
_ = Matrix4x4.Invert(value, out Matrix4x4 inverted); |
|||
this.InverseAdaptationMatrix = inverted; |
|||
} |
|||
} |
|||
|
|||
internal YCbCrTransform TransposedYCbCrTransform { get; private set; } |
|||
|
|||
internal Matrix4x4 InverseAdaptationMatrix { get; private set; } |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Allows the conversion of color profiles.
|
|||
/// </summary>
|
|||
public class ColorProfileConverter |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ColorProfileConverter"/> class.
|
|||
/// </summary>
|
|||
public ColorProfileConverter() |
|||
: this(new ColorConversionOptions()) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ColorProfileConverter"/> class.
|
|||
/// </summary>
|
|||
/// <param name="options">The color profile conversion options.</param>
|
|||
public ColorProfileConverter(ColorConversionOptions options) |
|||
=> this.Options = options; |
|||
|
|||
/// <summary>
|
|||
/// Gets the color profile conversion options.
|
|||
/// </summary>
|
|||
public ColorConversionOptions Options { get; } |
|||
|
|||
internal (CieXyz From, CieXyz To) GetChromaticAdaptionWhitePoints<TFrom, TTo>() |
|||
where TFrom : struct, IColorProfile |
|||
where TTo : struct, IColorProfile |
|||
{ |
|||
CieXyz sourceWhitePoint = TFrom.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint |
|||
? this.Options.SourceWhitePoint |
|||
: this.Options.SourceRgbWorkingSpace.WhitePoint; |
|||
|
|||
CieXyz targetWhitePoint = TTo.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint |
|||
? this.Options.TargetWhitePoint |
|||
: this.Options.TargetRgbWorkingSpace.WhitePoint; |
|||
|
|||
return (sourceWhitePoint, targetWhitePoint); |
|||
} |
|||
|
|||
internal bool ShouldUseIccProfiles() |
|||
=> this.Options.SourceIccProfile != null && this.Options.TargetIccProfile != null; |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Allows conversion between two color profiles based on the CIE Lab color space.
|
|||
/// </summary>
|
|||
public static class ColorProfileConverterExtensionsCieLabCieLab |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a color value from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The conversion process may use ICC profiles if available; otherwise, it performs a manual
|
|||
/// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
|
|||
/// both source and target types to be value types implementing the appropriate color profile interface.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The source color profile type. Must implement <see cref="IColorProfile{TFrom, CieLab}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The target color profile type. Must implement <see cref="IColorProfile{TTo, CieLab}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion.</param>
|
|||
/// <param name="source">The source color value to convert.</param>
|
|||
/// <returns>A value of type <typeparamref name="TTo"/> representing the converted color in the target color profile.</returns>
|
|||
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, in TFrom source) |
|||
where TFrom : struct, IColorProfile<TFrom, CieLab> |
|||
where TTo : struct, IColorProfile<TTo, CieLab> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
return converter.ConvertUsingIccProfile<TFrom, TTo>(source); |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS
|
|||
CieLab pcsFromA = source.ToProfileConnectingSpace(options); |
|||
CieXyz pcsFromB = pcsFromA.ToProfileConnectingSpace(options); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert between PCS
|
|||
CieLab pcsTo = CieLab.FromProfileConnectingSpace(options, in pcsFromB); |
|||
|
|||
// Convert to output from PCS
|
|||
return TTo.FromProfileConnectingSpace(options, in pcsTo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a span of color values from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method performs color conversion between two color profiles, handling necessary
|
|||
/// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
|
|||
/// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
|
|||
/// for the destination; the caller is responsible for providing a suitably sized span.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The type representing the source color profile. Must implement <see cref="IColorProfile{TFrom, CieLab}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The type representing the destination color profile. Must implement <see cref="IColorProfile{TTo, CieLab}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion operation.</param>
|
|||
/// <param name="source">A read-only span containing the source color values to convert.</param>
|
|||
/// <param name="destination">A span that receives the converted color values. Must be at least as long as the source span.</param>
|
|||
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
|||
where TFrom : struct, IColorProfile<TFrom, CieLab> |
|||
where TTo : struct, IColorProfile<TTo, CieLab> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
converter.ConvertUsingIccProfile(source, destination); |
|||
return; |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS.
|
|||
using IMemoryOwner<CieLab> pcsFromToOwner = options.MemoryAllocator.Allocate<CieLab>(source.Length); |
|||
Span<CieLab> pcsFromTo = pcsFromToOwner.GetSpan(); |
|||
TFrom.ToProfileConnectionSpace(options, source, pcsFromTo); |
|||
|
|||
using IMemoryOwner<CieXyz> pcsFromOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
|||
Span<CieXyz> pcsFrom = pcsFromOwner.GetSpan(); |
|||
CieLab.ToProfileConnectionSpace(options, pcsFromTo, pcsFrom); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert between PCS.
|
|||
CieLab.FromProfileConnectionSpace(options, pcsFrom, pcsFromTo); |
|||
|
|||
// Convert to output from PCS
|
|||
TTo.FromProfileConnectionSpace(options, pcsFromTo, destination); |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Allows conversion between two color profiles based on the CIE Lab and CIE XYZ color spaces.
|
|||
/// </summary>
|
|||
public static class ColorProfileConverterExtensionsCieLabCieXyz |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a color value from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The conversion process may use ICC profiles if available; otherwise, it performs a manual
|
|||
/// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
|
|||
/// both source and target types to be value types implementing the appropriate color profile interface.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The source color profile type. Must implement <see cref="IColorProfile{TFrom, CieLab}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The target color profile type. Must implement <see cref="IColorProfile{TTo, CieXyz}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion.</param>
|
|||
/// <param name="source">The source color value to convert.</param>
|
|||
/// <returns>A value of type <typeparamref name="TTo"/> representing the converted color in the target color profile.</returns>
|
|||
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, in TFrom source) |
|||
where TFrom : struct, IColorProfile<TFrom, CieLab> |
|||
where TTo : struct, IColorProfile<TTo, CieXyz> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
return converter.ConvertUsingIccProfile<TFrom, TTo>(source); |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS
|
|||
CieLab pcsFrom = source.ToProfileConnectingSpace(options); |
|||
|
|||
// Convert between PCS
|
|||
CieXyz pcsTo = pcsFrom.ToProfileConnectingSpace(options); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
pcsTo = VonKriesChromaticAdaptation.Transform(in pcsTo, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert to output from PCS
|
|||
return TTo.FromProfileConnectingSpace(options, in pcsTo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a span of color values from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method performs color conversion between two color profiles, handling necessary
|
|||
/// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
|
|||
/// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
|
|||
/// for the destination; the caller is responsible for providing a suitably sized span.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The type representing the source color profile. Must implement <see cref="IColorProfile{TFrom, CieLab}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The type representing the destination color profile. Must implement <see cref="IColorProfile{TTo, CieXyz}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion operation.</param>
|
|||
/// <param name="source">A read-only span containing the source color values to convert.</param>
|
|||
/// <param name="destination">A span that receives the converted color values. Must be at least as long as the source span.</param>
|
|||
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
|||
where TFrom : struct, IColorProfile<TFrom, CieLab> |
|||
where TTo : struct, IColorProfile<TTo, CieXyz> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
converter.ConvertUsingIccProfile(source, destination); |
|||
return; |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS.
|
|||
using IMemoryOwner<CieLab> pcsFromOwner = options.MemoryAllocator.Allocate<CieLab>(source.Length); |
|||
Span<CieLab> pcsFrom = pcsFromOwner.GetSpan(); |
|||
TFrom.ToProfileConnectionSpace(options, source, pcsFrom); |
|||
|
|||
// Convert between PCS.
|
|||
using IMemoryOwner<CieXyz> pcsToOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
|||
Span<CieXyz> pcsTo = pcsToOwner.GetSpan(); |
|||
CieLab.ToProfileConnectionSpace(options, pcsFrom, pcsTo); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
VonKriesChromaticAdaptation.Transform(pcsTo, pcsTo, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert to output from PCS
|
|||
TTo.FromProfileConnectionSpace(options, pcsTo, destination); |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Allows conversion between two color profiles based on the CIE Lab and RGB color spaces.
|
|||
/// </summary>
|
|||
public static class ColorProfileConverterExtensionsCieLabRgb |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a color value from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The conversion process may use ICC profiles if available; otherwise, it performs a manual
|
|||
/// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
|
|||
/// both source and target types to be value types implementing the appropriate color profile interface.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The source color profile type. Must implement <see cref="IColorProfile{TFrom, CieLab}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The target color profile type. Must implement <see cref="IColorProfile{TTo, Rgb}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion.</param>
|
|||
/// <param name="source">The source color value to convert.</param>
|
|||
/// <returns>A value of type <typeparamref name="TTo"/> representing the converted color in the target color profile.</returns>
|
|||
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, in TFrom source) |
|||
where TFrom : struct, IColorProfile<TFrom, CieLab> |
|||
where TTo : struct, IColorProfile<TTo, Rgb> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
return converter.ConvertUsingIccProfile<TFrom, TTo>(source); |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS
|
|||
CieLab pcsFromA = source.ToProfileConnectingSpace(options); |
|||
CieXyz pcsFromB = pcsFromA.ToProfileConnectingSpace(options); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert between PCS
|
|||
Rgb pcsTo = Rgb.FromProfileConnectingSpace(options, in pcsFromB); |
|||
|
|||
// Convert to output from PCS
|
|||
return TTo.FromProfileConnectingSpace(options, in pcsTo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a span of color values from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method performs color conversion between two color profiles, handling necessary
|
|||
/// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
|
|||
/// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
|
|||
/// for the destination; the caller is responsible for providing a suitably sized span.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The type representing the source color profile. Must implement <see cref="IColorProfile{TFrom, CieLab}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The type representing the destination color profile. Must implement <see cref="IColorProfile{TTo, Rgb}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion operation.</param>
|
|||
/// <param name="source">A read-only span containing the source color values to convert.</param>
|
|||
/// <param name="destination">A span that receives the converted color values. Must be at least as long as the source span.</param>
|
|||
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
|||
where TFrom : struct, IColorProfile<TFrom, CieLab> |
|||
where TTo : struct, IColorProfile<TTo, Rgb> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
converter.ConvertUsingIccProfile(source, destination); |
|||
return; |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS.
|
|||
using IMemoryOwner<CieLab> pcsFromAOwner = options.MemoryAllocator.Allocate<CieLab>(source.Length); |
|||
Span<CieLab> pcsFromA = pcsFromAOwner.GetSpan(); |
|||
TFrom.ToProfileConnectionSpace(options, source, pcsFromA); |
|||
|
|||
using IMemoryOwner<CieXyz> pcsFromBOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
|||
Span<CieXyz> pcsFromB = pcsFromBOwner.GetSpan(); |
|||
CieLab.ToProfileConnectionSpace(options, pcsFromA, pcsFromB); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert between PCS.
|
|||
using IMemoryOwner<Rgb> pcsToOwner = options.MemoryAllocator.Allocate<Rgb>(source.Length); |
|||
Span<Rgb> pcsTo = pcsToOwner.GetSpan(); |
|||
Rgb.FromProfileConnectionSpace(options, pcsFromB, pcsTo); |
|||
|
|||
// Convert to output from PCS
|
|||
TTo.FromProfileConnectionSpace(options, pcsTo, destination); |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Allows conversion between two color profiles based on the CIE XYZ and CIE Lab color spaces.
|
|||
/// </summary>
|
|||
public static class ColorProfileConverterExtensionsCieXyzCieLab |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a color value from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The conversion process may use ICC profiles if available; otherwise, it performs a manual
|
|||
/// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
|
|||
/// both source and target types to be value types implementing the appropriate color profile interface.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The source color profile type. Must implement <see cref="IColorProfile{TFrom, CieXyz}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The target color profile type. Must implement <see cref="IColorProfile{TTo, CieLab}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion.</param>
|
|||
/// <param name="source">The source color value to convert.</param>
|
|||
/// <returns>A value of type <typeparamref name="TTo"/> representing the converted color in the target color profile.</returns>
|
|||
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, in TFrom source) |
|||
where TFrom : struct, IColorProfile<TFrom, CieXyz> |
|||
where TTo : struct, IColorProfile<TTo, CieLab> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
return converter.ConvertUsingIccProfile<TFrom, TTo>(source); |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS
|
|||
CieXyz pcsFrom = source.ToProfileConnectingSpace(options); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert between PCS
|
|||
CieLab pcsTo = CieLab.FromProfileConnectingSpace(options, in pcsFrom); |
|||
|
|||
// Convert to output from PCS
|
|||
return TTo.FromProfileConnectingSpace(options, in pcsTo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a span of color values from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method performs color conversion between two color profiles, handling necessary
|
|||
/// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
|
|||
/// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
|
|||
/// for the destination; the caller is responsible for providing a suitably sized span.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The type representing the source color profile. Must implement <see cref="IColorProfile{TFrom, CieXyz}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The type representing the destination color profile. Must implement <see cref="IColorProfile{TTo, CieLab}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion operation.</param>
|
|||
/// <param name="source">A read-only span containing the source color values to convert.</param>
|
|||
/// <param name="destination">A span that receives the converted color values. Must be at least as long as the source span.</param>
|
|||
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
|||
where TFrom : struct, IColorProfile<TFrom, CieXyz> |
|||
where TTo : struct, IColorProfile<TTo, CieLab> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
converter.ConvertUsingIccProfile(source, destination); |
|||
return; |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS.
|
|||
using IMemoryOwner<CieXyz> pcsFromOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
|||
Span<CieXyz> pcsFrom = pcsFromOwner.GetSpan(); |
|||
TFrom.ToProfileConnectionSpace(options, source, pcsFrom); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert between PCS.
|
|||
using IMemoryOwner<CieLab> pcsToOwner = options.MemoryAllocator.Allocate<CieLab>(source.Length); |
|||
Span<CieLab> pcsTo = pcsToOwner.GetSpan(); |
|||
CieLab.FromProfileConnectionSpace(options, pcsFrom, pcsTo); |
|||
|
|||
// Convert to output from PCS
|
|||
TTo.FromProfileConnectionSpace(options, pcsTo, destination); |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Allows conversion between two color profiles based on the CIE XYZ color space.
|
|||
/// </summary>
|
|||
public static class ColorProfileConverterExtensionsCieXyzCieXyz |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a color value from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The conversion process may use ICC profiles if available; otherwise, it performs a manual
|
|||
/// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
|
|||
/// both source and target types to be value types implementing the appropriate color profile interface.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The source color profile type. Must implement <see cref="IColorProfile{TFrom, CieXyz}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The target color profile type. Must implement <see cref="IColorProfile{TTo, CieXyz}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion.</param>
|
|||
/// <param name="source">The source color value to convert.</param>
|
|||
/// <returns>A value of type <typeparamref name="TTo"/> representing the converted color in the target color profile.</returns>
|
|||
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, in TFrom source) |
|||
where TFrom : struct, IColorProfile<TFrom, CieXyz> |
|||
where TTo : struct, IColorProfile<TTo, CieXyz> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
return converter.ConvertUsingIccProfile<TFrom, TTo>(source); |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS
|
|||
CieXyz pcsFrom = source.ToProfileConnectingSpace(options); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert to output from PCS
|
|||
return TTo.FromProfileConnectingSpace(options, in pcsFrom); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a span of color values from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method performs color conversion between two color profiles, handling necessary
|
|||
/// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
|
|||
/// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
|
|||
/// for the destination; the caller is responsible for providing a suitably sized span.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The type representing the source color profile. Must implement <see cref="IColorProfile{TFrom, CieXyz}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The type representing the destination color profile. Must implement <see cref="IColorProfile{TTo, CieXyz}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion operation.</param>
|
|||
/// <param name="source">A read-only span containing the source color values to convert.</param>
|
|||
/// <param name="destination">A span that receives the converted color values. Must be at least as long as the source span.</param>
|
|||
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
|||
where TFrom : struct, IColorProfile<TFrom, CieXyz> |
|||
where TTo : struct, IColorProfile<TTo, CieXyz> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
converter.ConvertUsingIccProfile(source, destination); |
|||
return; |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS.
|
|||
using IMemoryOwner<CieXyz> pcsFromOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
|||
Span<CieXyz> pcsFrom = pcsFromOwner.GetSpan(); |
|||
TFrom.ToProfileConnectionSpace(options, source, pcsFrom); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert to output from PCS
|
|||
TTo.FromProfileConnectionSpace(options, pcsFrom, destination); |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Allows conversion between two color profiles based on the CIE XYZ and RGB color spaces.
|
|||
/// </summary>
|
|||
public static class ColorProfileConverterExtensionsCieXyzRgb |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a color value from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The conversion process may use ICC profiles if available; otherwise, it performs a manual
|
|||
/// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
|
|||
/// both source and target types to be value types implementing the appropriate color profile interface.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The source color profile type. Must implement <see cref="IColorProfile{TFrom, CieXyz}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The target color profile type. Must implement <see cref="IColorProfile{TTo, Rgb}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion.</param>
|
|||
/// <param name="source">The source color value to convert.</param>
|
|||
/// <returns>A value of type <typeparamref name="TTo"/> representing the converted color in the target color profile.</returns>
|
|||
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, in TFrom source) |
|||
where TFrom : struct, IColorProfile<TFrom, CieXyz> |
|||
where TTo : struct, IColorProfile<TTo, Rgb> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
return converter.ConvertUsingIccProfile<TFrom, TTo>(source); |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS
|
|||
CieXyz pcsFrom = source.ToProfileConnectingSpace(options); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert between PCS
|
|||
Rgb pcsTo = Rgb.FromProfileConnectingSpace(options, in pcsFrom); |
|||
|
|||
// Convert to output from PCS
|
|||
return TTo.FromProfileConnectingSpace(options, in pcsTo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a span of color values from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method performs color conversion between two color profiles, handling necessary
|
|||
/// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
|
|||
/// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
|
|||
/// for the destination; the caller is responsible for providing a suitably sized span.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The type representing the source color profile. Must implement <see cref="IColorProfile{TFrom, CieXyz}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The type representing the destination color profile. Must implement <see cref="IColorProfile{TTo, Rgb}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion operation.</param>
|
|||
/// <param name="source">A read-only span containing the source color values to convert.</param>
|
|||
/// <param name="destination">A span that receives the converted color values. Must be at least as long as the source span.</param>
|
|||
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
|||
where TFrom : struct, IColorProfile<TFrom, CieXyz> |
|||
where TTo : struct, IColorProfile<TTo, Rgb> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
converter.ConvertUsingIccProfile(source, destination); |
|||
return; |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS.
|
|||
using IMemoryOwner<CieXyz> pcsFromOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
|||
Span<CieXyz> pcsFrom = pcsFromOwner.GetSpan(); |
|||
TFrom.ToProfileConnectionSpace(options, source, pcsFrom); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert between PCS.
|
|||
using IMemoryOwner<Rgb> pcsToOwner = options.MemoryAllocator.Allocate<Rgb>(source.Length); |
|||
Span<Rgb> pcsTo = pcsToOwner.GetSpan(); |
|||
Rgb.FromProfileConnectionSpace(options, pcsFrom, pcsTo); |
|||
|
|||
// Convert to output from PCS
|
|||
TTo.FromProfileConnectionSpace(options, pcsTo, destination); |
|||
} |
|||
} |
|||
@ -0,0 +1,772 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
using SixLabors.ImageSharp.ColorProfiles.Icc; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
internal static class ColorProfileConverterExtensionsIcc |
|||
{ |
|||
private static readonly float[] PcsV2FromBlackPointScale = |
|||
[0.9965153F, 0.9965269F, 0.9965208F, 1F, |
|||
0.9965153F, 0.9965269F, 0.9965208F, 1F, |
|||
0.9965153F, 0.9965269F, 0.9965208F, 1F, |
|||
0.9965153F, 0.9965269F, 0.9965208F, 1F]; |
|||
|
|||
private static readonly float[] PcsV2FromBlackPointOffset = |
|||
[0.00336F, 0.0034731F, 0.00287F, 0F, |
|||
0.00336F, 0.0034731F, 0.00287F, 0F, |
|||
0.00336F, 0.0034731F, 0.00287F, 0F, |
|||
0.00336F, 0.0034731F, 0.00287F, 0F]; |
|||
|
|||
private static readonly float[] PcsV2ToBlackPointScale = |
|||
[1.0034969F, 1.0034852F, 1.0034913F, 1F, |
|||
1.0034969F, 1.0034852F, 1.0034913F, 1F, |
|||
1.0034969F, 1.0034852F, 1.0034913F, 1F, |
|||
1.0034969F, 1.0034852F, 1.0034913F, 1F]; |
|||
|
|||
private static readonly float[] PcsV2ToBlackPointOffset = |
|||
[0.0033717495F, 0.0034852044F, 0.0028800198F, 0F, |
|||
0.0033717495F, 0.0034852044F, 0.0028800198F, 0F, |
|||
0.0033717495F, 0.0034852044F, 0.0028800198F, 0F, |
|||
0.0033717495F, 0.0034852044F, 0.0028800198F, 0F]; |
|||
|
|||
/// <summary>
|
|||
/// Converts a color value from one ICC color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method performs color conversion using ICC profiles, ensuring accurate color mapping
|
|||
/// between different color spaces. Both the source and target ICC profiles must be provided in the converter's
|
|||
/// options. The method supports perceptual adjustments when required by the profiles.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The type representing the source color profile. Must implement <see cref="IColorProfile{TFrom}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The type representing the destination color profile. Must implement <see cref="IColorProfile{TTo}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter configured with source and target ICC profiles.</param>
|
|||
/// <param name="source">The color value to convert, defined in the source color profile.</param>
|
|||
/// <returns>
|
|||
/// A color value in the target color profile, resulting from the ICC profile-based conversion of the source value.
|
|||
/// </returns>
|
|||
/// <exception cref="InvalidOperationException">
|
|||
/// Thrown if either the source or target ICC profile is missing from the converter options.
|
|||
/// </exception>
|
|||
internal static TTo ConvertUsingIccProfile<TFrom, TTo>(this ColorProfileConverter converter, in TFrom source) |
|||
where TFrom : struct, IColorProfile<TFrom> |
|||
where TTo : struct, IColorProfile<TTo> |
|||
{ |
|||
// TODO: Validation of ICC Profiles against color profile. Is this possible?
|
|||
if (converter.Options.SourceIccProfile is null) |
|||
{ |
|||
throw new InvalidOperationException("Source ICC profile is missing."); |
|||
} |
|||
|
|||
if (converter.Options.TargetIccProfile is null) |
|||
{ |
|||
throw new InvalidOperationException("Target ICC profile is missing."); |
|||
} |
|||
|
|||
ConversionParams sourceParams = new(converter.Options.SourceIccProfile, toPcs: true); |
|||
ConversionParams targetParams = new(converter.Options.TargetIccProfile, toPcs: false); |
|||
|
|||
ColorProfileConverter pcsConverter = new(new ColorConversionOptions |
|||
{ |
|||
MemoryAllocator = converter.Options.MemoryAllocator, |
|||
SourceWhitePoint = KnownIlluminants.D50Icc, |
|||
TargetWhitePoint = KnownIlluminants.D50Icc |
|||
}); |
|||
|
|||
// Normalize the source, then convert to the PCS space.
|
|||
Vector4 sourcePcs = sourceParams.Converter.Calculate(source.ToScaledVector4()); |
|||
|
|||
// If both profiles need PCS adjustment, they both share the same unadjusted PCS space
|
|||
// cancelling out the need to make the adjustment
|
|||
// except if using TRC transforms, which always requires perceptual handling
|
|||
// TODO: this does not include adjustment for absolute intent, which would double existing complexity, suggest throwing exception and addressing in future update
|
|||
bool anyProfileNeedsPerceptualAdjustment = sourceParams.HasNoPerceptualHandling || targetParams.HasNoPerceptualHandling; |
|||
bool oneProfileHasV2PerceptualAdjustment = sourceParams.HasV2PerceptualHandling ^ targetParams.HasV2PerceptualHandling; |
|||
|
|||
Vector4 targetPcs = anyProfileNeedsPerceptualAdjustment || oneProfileHasV2PerceptualAdjustment |
|||
? GetTargetPcsWithPerceptualAdjustment(sourcePcs, sourceParams, targetParams, pcsConverter) |
|||
: GetTargetPcsWithoutAdjustment(sourcePcs, sourceParams, targetParams, pcsConverter); |
|||
|
|||
return TTo.FromScaledVector4(targetParams.Converter.Calculate(targetPcs)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a span of color values from a source color profile to a destination color profile using ICC profiles.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method performs color conversion by transforming the input values through the Profile
|
|||
/// Connection Space (PCS) as defined by the provided ICC profiles. Perceptual adjustments are applied as required
|
|||
/// by the profiles. The method does not support absolute colorimetric intent and will not perform such
|
|||
/// conversions.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The type representing the source color profile. Must implement <see cref="IColorProfile{TFrom}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The type representing the destination color profile. Must implement <see cref="IColorProfile{TTo}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter that provides conversion options and ICC profiles.</param>
|
|||
/// <param name="source">
|
|||
/// A read-only span containing the source color values to convert. The values must conform to the source color
|
|||
/// profile.
|
|||
/// </param>
|
|||
/// <param name="destination">
|
|||
/// A span to receive the converted color values in the destination color profile. Must be at least as large as the
|
|||
/// source span.
|
|||
/// </param>
|
|||
/// <exception cref="InvalidOperationException">
|
|||
/// Thrown if the source or target ICC profile is missing from the converter options.
|
|||
/// </exception>
|
|||
internal static void ConvertUsingIccProfile<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
|||
where TFrom : struct, IColorProfile<TFrom> |
|||
where TTo : struct, IColorProfile<TTo> |
|||
{ |
|||
// TODO: Validation of ICC Profiles against color profile. Is this possible?
|
|||
if (converter.Options.SourceIccProfile is null) |
|||
{ |
|||
throw new InvalidOperationException("Source ICC profile is missing."); |
|||
} |
|||
|
|||
if (converter.Options.TargetIccProfile is null) |
|||
{ |
|||
throw new InvalidOperationException("Target ICC profile is missing."); |
|||
} |
|||
|
|||
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(destination)); |
|||
|
|||
ConversionParams sourceParams = new(converter.Options.SourceIccProfile, toPcs: true); |
|||
ConversionParams targetParams = new(converter.Options.TargetIccProfile, toPcs: false); |
|||
|
|||
ColorProfileConverter pcsConverter = new(new ColorConversionOptions |
|||
{ |
|||
MemoryAllocator = converter.Options.MemoryAllocator, |
|||
SourceWhitePoint = KnownIlluminants.D50Icc, |
|||
TargetWhitePoint = KnownIlluminants.D50Icc |
|||
}); |
|||
|
|||
using IMemoryOwner<Vector4> pcsBuffer = converter.Options.MemoryAllocator.Allocate<Vector4>(source.Length); |
|||
Span<Vector4> pcs = pcsBuffer.GetSpan(); |
|||
|
|||
// Normalize the source, then convert to the PCS space.
|
|||
TFrom.ToScaledVector4(source, pcs); |
|||
sourceParams.Converter.Calculate(pcs, pcs); |
|||
|
|||
// If both profiles need PCS adjustment, they both share the same unadjusted PCS space
|
|||
// cancelling out the need to make the adjustment
|
|||
// except if using TRC transforms, which always requires perceptual handling
|
|||
// TODO: this does not include adjustment for absolute intent, which would double existing complexity, suggest throwing exception and addressing in future update
|
|||
bool anyProfileNeedsPerceptualAdjustment = sourceParams.HasNoPerceptualHandling || targetParams.HasNoPerceptualHandling; |
|||
bool oneProfileHasV2PerceptualAdjustment = sourceParams.HasV2PerceptualHandling ^ targetParams.HasV2PerceptualHandling; |
|||
|
|||
if (anyProfileNeedsPerceptualAdjustment || oneProfileHasV2PerceptualAdjustment) |
|||
{ |
|||
GetTargetPcsWithPerceptualAdjustment(pcs, sourceParams, targetParams, pcsConverter); |
|||
} |
|||
else |
|||
{ |
|||
GetTargetPcsWithoutAdjustment(pcs, sourceParams, targetParams, pcsConverter); |
|||
} |
|||
|
|||
// Convert to the target space.
|
|||
targetParams.Converter.Calculate(pcs, pcs); |
|||
TTo.FromScaledVector4(pcs, destination); |
|||
} |
|||
|
|||
private static Vector4 GetTargetPcsWithoutAdjustment( |
|||
Vector4 sourcePcs, |
|||
ConversionParams sourceParams, |
|||
ConversionParams targetParams, |
|||
ColorProfileConverter pcsConverter) |
|||
{ |
|||
// Profile connecting spaces can only be Lab, XYZ.
|
|||
// 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version
|
|||
// so ensure that Lab is using the correct encoding when a 16-bit LUT is used
|
|||
switch (sourceParams.PcsType) |
|||
{ |
|||
// Convert from Lab to XYZ.
|
|||
case IccColorSpaceType.CieLab when targetParams.PcsType is IccColorSpaceType.CieXyz: |
|||
{ |
|||
sourcePcs = sourceParams.Is16BitLutEntry ? LabV2ToLab(sourcePcs) : sourcePcs; |
|||
CieLab lab = CieLab.FromScaledVector4(sourcePcs); |
|||
CieXyz xyz = pcsConverter.Convert<CieLab, CieXyz>(in lab); |
|||
return xyz.ToScaledVector4(); |
|||
} |
|||
|
|||
// Convert from XYZ to Lab.
|
|||
case IccColorSpaceType.CieXyz when targetParams.PcsType is IccColorSpaceType.CieLab: |
|||
{ |
|||
CieXyz xyz = CieXyz.FromScaledVector4(sourcePcs); |
|||
CieLab lab = pcsConverter.Convert<CieXyz, CieLab>(in xyz); |
|||
Vector4 targetPcs = lab.ToScaledVector4(); |
|||
return targetParams.Is16BitLutEntry ? LabToLabV2(targetPcs) : targetPcs; |
|||
} |
|||
|
|||
// Convert from XYZ to XYZ.
|
|||
case IccColorSpaceType.CieXyz when targetParams.PcsType is IccColorSpaceType.CieXyz: |
|||
{ |
|||
CieXyz xyz = CieXyz.FromScaledVector4(sourcePcs); |
|||
CieXyz targetXyz = pcsConverter.Convert<CieXyz, CieXyz>(in xyz); |
|||
return targetXyz.ToScaledVector4(); |
|||
} |
|||
|
|||
// Convert from Lab to Lab.
|
|||
case IccColorSpaceType.CieLab when targetParams.PcsType is IccColorSpaceType.CieLab: |
|||
{ |
|||
// if both source and target LUT use same v2 LAB encoding, no need to correct them
|
|||
if (sourceParams.Is16BitLutEntry && targetParams.Is16BitLutEntry) |
|||
{ |
|||
CieLab sourceLab = CieLab.FromScaledVector4(sourcePcs); |
|||
CieLab targetLab = pcsConverter.Convert<CieLab, CieLab>(in sourceLab); |
|||
return targetLab.ToScaledVector4(); |
|||
} |
|||
else |
|||
{ |
|||
sourcePcs = sourceParams.Is16BitLutEntry ? LabV2ToLab(sourcePcs) : sourcePcs; |
|||
CieLab sourceLab = CieLab.FromScaledVector4(sourcePcs); |
|||
CieLab targetLab = pcsConverter.Convert<CieLab, CieLab>(in sourceLab); |
|||
Vector4 targetPcs = targetLab.ToScaledVector4(); |
|||
return targetParams.Is16BitLutEntry ? LabToLabV2(targetPcs) : targetPcs; |
|||
} |
|||
} |
|||
|
|||
default: |
|||
throw new ArgumentOutOfRangeException($"Source PCS {sourceParams.PcsType} to target PCS {targetParams.PcsType} is not supported"); |
|||
} |
|||
} |
|||
|
|||
private static void GetTargetPcsWithoutAdjustment( |
|||
Span<Vector4> pcs, |
|||
ConversionParams sourceParams, |
|||
ConversionParams targetParams, |
|||
ColorProfileConverter pcsConverter) |
|||
{ |
|||
// Profile connecting spaces can only be Lab, XYZ.
|
|||
// 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version
|
|||
// so ensure that Lab is using the correct encoding when a 16-bit LUT is used
|
|||
switch (sourceParams.PcsType) |
|||
{ |
|||
// Convert from Lab to XYZ.
|
|||
case IccColorSpaceType.CieLab when targetParams.PcsType is IccColorSpaceType.CieXyz: |
|||
{ |
|||
if (sourceParams.Is16BitLutEntry) |
|||
{ |
|||
LabV2ToLab(pcs, pcs); |
|||
} |
|||
|
|||
using IMemoryOwner<CieLab> pcsFromBuffer = pcsConverter.Options.MemoryAllocator.Allocate<CieLab>(pcs.Length); |
|||
Span<CieLab> pcsFrom = pcsFromBuffer.GetSpan(); |
|||
|
|||
using IMemoryOwner<CieXyz> pcsToBuffer = pcsConverter.Options.MemoryAllocator.Allocate<CieXyz>(pcs.Length); |
|||
Span<CieXyz> pcsTo = pcsToBuffer.GetSpan(); |
|||
|
|||
CieLab.FromScaledVector4(pcs, pcsFrom); |
|||
pcsConverter.Convert<CieLab, CieXyz>(pcsFrom, pcsTo); |
|||
|
|||
CieXyz.ToScaledVector4(pcsTo, pcs); |
|||
break; |
|||
} |
|||
|
|||
// Convert from XYZ to Lab.
|
|||
case IccColorSpaceType.CieXyz when targetParams.PcsType is IccColorSpaceType.CieLab: |
|||
{ |
|||
using IMemoryOwner<CieXyz> pcsFromBuffer = pcsConverter.Options.MemoryAllocator.Allocate<CieXyz>(pcs.Length); |
|||
Span<CieXyz> pcsFrom = pcsFromBuffer.GetSpan(); |
|||
|
|||
using IMemoryOwner<CieLab> pcsToBuffer = pcsConverter.Options.MemoryAllocator.Allocate<CieLab>(pcs.Length); |
|||
Span<CieLab> pcsTo = pcsToBuffer.GetSpan(); |
|||
|
|||
CieXyz.FromScaledVector4(pcs, pcsFrom); |
|||
pcsConverter.Convert<CieXyz, CieLab>(pcsFrom, pcsTo); |
|||
|
|||
CieLab.ToScaledVector4(pcsTo, pcs); |
|||
|
|||
if (targetParams.Is16BitLutEntry) |
|||
{ |
|||
LabToLabV2(pcs, pcs); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
// Convert from XYZ to XYZ.
|
|||
case IccColorSpaceType.CieXyz when targetParams.PcsType is IccColorSpaceType.CieXyz: |
|||
{ |
|||
using IMemoryOwner<CieXyz> pcsFromToBuffer = pcsConverter.Options.MemoryAllocator.Allocate<CieXyz>(pcs.Length); |
|||
Span<CieXyz> pcsFromTo = pcsFromToBuffer.GetSpan(); |
|||
|
|||
CieXyz.FromScaledVector4(pcs, pcsFromTo); |
|||
pcsConverter.Convert<CieXyz, CieXyz>(pcsFromTo, pcsFromTo); |
|||
|
|||
CieXyz.ToScaledVector4(pcsFromTo, pcs); |
|||
break; |
|||
} |
|||
|
|||
// Convert from Lab to Lab.
|
|||
case IccColorSpaceType.CieLab when targetParams.PcsType is IccColorSpaceType.CieLab: |
|||
{ |
|||
using IMemoryOwner<CieLab> pcsFromToBuffer = pcsConverter.Options.MemoryAllocator.Allocate<CieLab>(pcs.Length); |
|||
Span<CieLab> pcsFromTo = pcsFromToBuffer.GetSpan(); |
|||
|
|||
// if both source and target LUT use same v2 LAB encoding, no need to correct them
|
|||
if (sourceParams.Is16BitLutEntry && targetParams.Is16BitLutEntry) |
|||
{ |
|||
CieLab.FromScaledVector4(pcs, pcsFromTo); |
|||
pcsConverter.Convert<CieLab, CieLab>(pcsFromTo, pcsFromTo); |
|||
CieLab.ToScaledVector4(pcsFromTo, pcs); |
|||
} |
|||
else |
|||
{ |
|||
if (sourceParams.Is16BitLutEntry) |
|||
{ |
|||
LabV2ToLab(pcs, pcs); |
|||
} |
|||
|
|||
CieLab.FromScaledVector4(pcs, pcsFromTo); |
|||
pcsConverter.Convert<CieLab, CieLab>(pcsFromTo, pcsFromTo); |
|||
CieLab.ToScaledVector4(pcsFromTo, pcs); |
|||
|
|||
if (targetParams.Is16BitLutEntry) |
|||
{ |
|||
LabToLabV2(pcs, pcs); |
|||
} |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
default: |
|||
throw new ArgumentOutOfRangeException($"Source PCS {sourceParams.PcsType} to target PCS {targetParams.PcsType} is not supported"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Effectively this is <see cref="GetTargetPcsWithoutAdjustment(Vector4, ConversionParams, ConversionParams, ColorProfileConverter)"/> with an extra step in the middle.
|
|||
/// It adjusts PCS by compensating for the black point used for perceptual intent in v2 profiles.
|
|||
/// The adjustment needs to be performed in XYZ space, potentially an overhead of 2 more conversions.
|
|||
/// Not required if both spaces need V2 correction, since they both have the same understanding of the PCS.
|
|||
/// Not compatible with PCS adjustment for absolute intent.
|
|||
/// </summary>
|
|||
/// <param name="sourcePcs">The source PCS values.</param>
|
|||
/// <param name="sourceParams">The source profile parameters.</param>
|
|||
/// <param name="targetParams">The target profile parameters.</param>
|
|||
/// <param name="pcsConverter">The converter to use for the PCS adjustments.</param>
|
|||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the source or target PCS is not supported.</exception>
|
|||
private static Vector4 GetTargetPcsWithPerceptualAdjustment( |
|||
Vector4 sourcePcs, |
|||
ConversionParams sourceParams, |
|||
ConversionParams targetParams, |
|||
ColorProfileConverter pcsConverter) |
|||
{ |
|||
// all conversions are funneled through XYZ in case PCS adjustments need to be made
|
|||
CieXyz xyz; |
|||
|
|||
switch (sourceParams.PcsType) |
|||
{ |
|||
// 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version
|
|||
// so convert Lab to modern v4 encoding when returned from a 16-bit LUT
|
|||
case IccColorSpaceType.CieLab: |
|||
sourcePcs = sourceParams.Is16BitLutEntry ? LabV2ToLab(sourcePcs) : sourcePcs; |
|||
CieLab lab = CieLab.FromScaledVector4(sourcePcs); |
|||
xyz = pcsConverter.Convert<CieLab, CieXyz>(in lab); |
|||
break; |
|||
case IccColorSpaceType.CieXyz: |
|||
xyz = CieXyz.FromScaledVector4(sourcePcs); |
|||
break; |
|||
default: |
|||
throw new ArgumentOutOfRangeException($"Source PCS {sourceParams.PcsType} is not supported"); |
|||
} |
|||
|
|||
bool oneProfileHasV2PerceptualAdjustment = sourceParams.HasV2PerceptualHandling ^ targetParams.HasV2PerceptualHandling; |
|||
|
|||
// when converting from device to PCS with v2 perceptual intent
|
|||
// the black point needs to be adjusted to v4 after converting the PCS values
|
|||
if (sourceParams.HasNoPerceptualHandling || |
|||
(oneProfileHasV2PerceptualAdjustment && sourceParams.HasV2PerceptualHandling)) |
|||
{ |
|||
Vector3 vector = xyz.ToVector3(); |
|||
|
|||
// when using LAB PCS, negative values are clipped before PCS adjustment (in DemoIccMAX)
|
|||
if (sourceParams.PcsType == IccColorSpaceType.CieLab) |
|||
{ |
|||
vector = Vector3.Max(vector, Vector3.Zero); |
|||
} |
|||
|
|||
xyz = new CieXyz(AdjustPcsFromV2BlackPoint(vector)); |
|||
} |
|||
|
|||
// when converting from PCS to device with v2 perceptual intent
|
|||
// the black point needs to be adjusted to v2 before converting the PCS values
|
|||
if (targetParams.HasNoPerceptualHandling || |
|||
(oneProfileHasV2PerceptualAdjustment && targetParams.HasV2PerceptualHandling)) |
|||
{ |
|||
Vector3 vector = AdjustPcsToV2BlackPoint(xyz.AsVector3Unsafe()); |
|||
|
|||
// when using XYZ PCS, negative values are clipped after PCS adjustment (in DemoIccMAX)
|
|||
if (targetParams.PcsType == IccColorSpaceType.CieXyz) |
|||
{ |
|||
vector = Vector3.Max(vector, Vector3.Zero); |
|||
} |
|||
|
|||
xyz = new CieXyz(vector); |
|||
} |
|||
|
|||
switch (targetParams.PcsType) |
|||
{ |
|||
// 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version
|
|||
// so convert Lab back to legacy encoding before using in a 16-bit LUT
|
|||
case IccColorSpaceType.CieLab: |
|||
CieLab lab = pcsConverter.Convert<CieXyz, CieLab>(in xyz); |
|||
Vector4 targetPcs = lab.ToScaledVector4(); |
|||
return targetParams.Is16BitLutEntry ? LabToLabV2(targetPcs) : targetPcs; |
|||
case IccColorSpaceType.CieXyz: |
|||
return xyz.ToScaledVector4(); |
|||
default: |
|||
throw new ArgumentOutOfRangeException($"Target PCS {targetParams.PcsType} is not supported"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Effectively this is <see cref="GetTargetPcsWithoutAdjustment(Span{Vector4}, ConversionParams, ConversionParams, ColorProfileConverter)"/> with an extra step in the middle.
|
|||
/// It adjusts PCS by compensating for the black point used for perceptual intent in v2 profiles.
|
|||
/// The adjustment needs to be performed in XYZ space, potentially an overhead of 2 more conversions.
|
|||
/// Not required if both spaces need V2 correction, since they both have the same understanding of the PCS.
|
|||
/// Not compatible with PCS adjustment for absolute intent.
|
|||
/// </summary>
|
|||
/// <param name="pcs">The PCS values from the source.</param>
|
|||
/// <param name="sourceParams">The source profile parameters.</param>
|
|||
/// <param name="targetParams">The target profile parameters.</param>
|
|||
/// <param name="pcsConverter">The converter to use for the PCS adjustments.</param>
|
|||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the source or target PCS is not supported.</exception>
|
|||
private static void GetTargetPcsWithPerceptualAdjustment( |
|||
Span<Vector4> pcs, |
|||
ConversionParams sourceParams, |
|||
ConversionParams targetParams, |
|||
ColorProfileConverter pcsConverter) |
|||
{ |
|||
// All conversions are funneled through XYZ in case PCS adjustments need to be made
|
|||
using IMemoryOwner<CieXyz> xyzBuffer = pcsConverter.Options.MemoryAllocator.Allocate<CieXyz>(pcs.Length); |
|||
Span<CieXyz> xyz = xyzBuffer.GetSpan(); |
|||
|
|||
switch (sourceParams.PcsType) |
|||
{ |
|||
// 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version
|
|||
// so convert Lab to modern v4 encoding when returned from a 16-bit LUT
|
|||
case IccColorSpaceType.CieLab: |
|||
{ |
|||
if (sourceParams.Is16BitLutEntry) |
|||
{ |
|||
LabV2ToLab(pcs, pcs); |
|||
} |
|||
|
|||
using IMemoryOwner<CieLab> pcsFromBuffer = pcsConverter.Options.MemoryAllocator.Allocate<CieLab>(pcs.Length); |
|||
Span<CieLab> pcsFrom = pcsFromBuffer.GetSpan(); |
|||
CieLab.FromScaledVector4(pcs, pcsFrom); |
|||
pcsConverter.Convert<CieLab, CieXyz>(pcsFrom, xyz); |
|||
break; |
|||
} |
|||
|
|||
case IccColorSpaceType.CieXyz: |
|||
CieXyz.FromScaledVector4(pcs, xyz); |
|||
break; |
|||
default: |
|||
throw new ArgumentOutOfRangeException($"Source PCS {sourceParams.PcsType} is not supported"); |
|||
} |
|||
|
|||
bool oneProfileHasV2PerceptualAdjustment = sourceParams.HasV2PerceptualHandling ^ targetParams.HasV2PerceptualHandling; |
|||
|
|||
using IMemoryOwner<Vector4> vectorBuffer = pcsConverter.Options.MemoryAllocator.Allocate<Vector4>(pcs.Length); |
|||
Span<Vector4> vector = vectorBuffer.GetSpan(); |
|||
|
|||
// When converting from device to PCS with v2 perceptual intent
|
|||
// the black point needs to be adjusted to v4 after converting the PCS values
|
|||
if (sourceParams.HasNoPerceptualHandling || |
|||
(oneProfileHasV2PerceptualAdjustment && sourceParams.HasV2PerceptualHandling)) |
|||
{ |
|||
CieXyz.ToVector4(xyz, vector); |
|||
|
|||
// When using LAB PCS, negative values are clipped before PCS adjustment (in DemoIccMAX)
|
|||
if (sourceParams.PcsType == IccColorSpaceType.CieLab) |
|||
{ |
|||
ClipNegative(vector); |
|||
} |
|||
|
|||
AdjustPcsFromV2BlackPoint(vector, vector); |
|||
CieXyz.FromVector4(vector, xyz); |
|||
} |
|||
|
|||
// When converting from PCS to device with v2 perceptual intent
|
|||
// the black point needs to be adjusted to v2 before converting the PCS values
|
|||
if (targetParams.HasNoPerceptualHandling || |
|||
(oneProfileHasV2PerceptualAdjustment && targetParams.HasV2PerceptualHandling)) |
|||
{ |
|||
CieXyz.ToVector4(xyz, vector); |
|||
AdjustPcsToV2BlackPoint(vector, vector); |
|||
|
|||
// When using XYZ PCS, negative values are clipped after PCS adjustment (in DemoIccMAX)
|
|||
if (targetParams.PcsType == IccColorSpaceType.CieXyz) |
|||
{ |
|||
ClipNegative(vector); |
|||
} |
|||
|
|||
CieXyz.FromVector4(vector, xyz); |
|||
} |
|||
|
|||
switch (targetParams.PcsType) |
|||
{ |
|||
// 16-bit Lab encodings changed from v2 to v4, but 16-bit LUTs always use the legacy encoding regardless of version
|
|||
// so convert Lab back to legacy encoding before using in a 16-bit LUT
|
|||
case IccColorSpaceType.CieLab: |
|||
{ |
|||
using IMemoryOwner<CieLab> pcsToBuffer = pcsConverter.Options.MemoryAllocator.Allocate<CieLab>(pcs.Length); |
|||
Span<CieLab> pcsTo = pcsToBuffer.GetSpan(); |
|||
pcsConverter.Convert<CieXyz, CieLab>(xyz, pcsTo); |
|||
|
|||
CieLab.ToScaledVector4(pcsTo, pcs); |
|||
|
|||
if (targetParams.Is16BitLutEntry) |
|||
{ |
|||
LabToLabV2(pcs, pcs); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case IccColorSpaceType.CieXyz: |
|||
CieXyz.ToScaledVector4(xyz, pcs); |
|||
break; |
|||
default: |
|||
throw new ArgumentOutOfRangeException($"Target PCS {targetParams.PcsType} is not supported"); |
|||
} |
|||
} |
|||
|
|||
// as per DemoIccMAX icPerceptual values in IccCmm.h
|
|||
// refBlack = 0.00336F, 0.0034731F, 0.00287F
|
|||
// refWhite = 0.9642F, 1.0000F, 0.8249F
|
|||
// scale = 1 - (refBlack / refWhite)
|
|||
// offset = refBlack
|
|||
private static Vector3 AdjustPcsFromV2BlackPoint(Vector3 xyz) |
|||
=> (xyz * new Vector3(0.9965153F, 0.9965269F, 0.9965208F)) + new Vector3(0.00336F, 0.0034731F, 0.00287F); |
|||
|
|||
// as per DemoIccMAX icPerceptual values in IccCmm.h
|
|||
// refBlack = 0.00336F, 0.0034731F, 0.00287F
|
|||
// refWhite = 0.9642F, 1.0000F, 0.8249F
|
|||
// scale = 1 / (1 - (refBlack / refWhite))
|
|||
// offset = -refBlack * scale
|
|||
private static Vector3 AdjustPcsToV2BlackPoint(Vector3 xyz) |
|||
=> (xyz * new Vector3(1.0034969F, 1.0034852F, 1.0034913F)) - new Vector3(0.0033717495F, 0.0034852044F, 0.0028800198F); |
|||
|
|||
private static void AdjustPcsFromV2BlackPoint(Span<Vector4> source, Span<Vector4> destination) |
|||
{ |
|||
if (Vector.IsHardwareAccelerated && Vector<float>.IsSupported && |
|||
Vector<float>.Count <= Vector512<float>.Count && |
|||
source.Length * 4 >= Vector<float>.Count) |
|||
{ |
|||
// TODO: Check our constants. They may require scaling.
|
|||
Vector<float> vScale = new(PcsV2FromBlackPointScale.AsSpan()[..Vector<float>.Count]); |
|||
Vector<float> vOffset = new(PcsV2FromBlackPointOffset.AsSpan()[..Vector<float>.Count]); |
|||
|
|||
// SIMD loop
|
|||
int i = 0; |
|||
int simdBatchSize = Vector<float>.Count / 4; // Number of Vector4 elements per SIMD batch
|
|||
for (; i <= source.Length - simdBatchSize; i += simdBatchSize) |
|||
{ |
|||
// Load the vector from source span
|
|||
Vector<float> v = Unsafe.ReadUnaligned<Vector<float>>(ref Unsafe.As<Vector4, byte>(ref source[i])); |
|||
|
|||
// Scale and offset the vector
|
|||
v *= vScale; |
|||
v += vOffset; |
|||
|
|||
// Write the vector to the destination span
|
|||
Unsafe.WriteUnaligned(ref Unsafe.As<Vector4, byte>(ref destination[i]), v); |
|||
} |
|||
|
|||
// Scalar fallback for remaining elements
|
|||
for (; i < source.Length; i++) |
|||
{ |
|||
Vector4 s = source[i]; |
|||
s *= new Vector4(0.9965153F, 0.9965269F, 0.9965208F, 1F); |
|||
s += new Vector4(0.00336F, 0.0034731F, 0.00287F, 0F); |
|||
destination[i] = s; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Scalar fallback if SIMD is not supported
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Vector4 s = source[i]; |
|||
s *= new Vector4(0.9965153F, 0.9965269F, 0.9965208F, 1F); |
|||
s += new Vector4(0.00336F, 0.0034731F, 0.00287F, 0F); |
|||
destination[i] = s; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void AdjustPcsToV2BlackPoint(Span<Vector4> source, Span<Vector4> destination) |
|||
{ |
|||
if (Vector.IsHardwareAccelerated && Vector<float>.IsSupported && |
|||
Vector<float>.Count <= Vector512<float>.Count && |
|||
source.Length * 4 >= Vector<float>.Count) |
|||
{ |
|||
// TODO: Check our constants. They may require scaling.
|
|||
Vector<float> vScale = new(PcsV2ToBlackPointScale.AsSpan()[..Vector<float>.Count]); |
|||
Vector<float> vOffset = new(PcsV2ToBlackPointOffset.AsSpan()[..Vector<float>.Count]); |
|||
|
|||
// SIMD loop
|
|||
int i = 0; |
|||
int simdBatchSize = Vector<float>.Count / 4; // Number of Vector4 elements per SIMD batch
|
|||
for (; i <= source.Length - simdBatchSize; i += simdBatchSize) |
|||
{ |
|||
// Load the vector from source span
|
|||
Vector<float> v = Unsafe.ReadUnaligned<Vector<float>>(ref Unsafe.As<Vector4, byte>(ref source[i])); |
|||
|
|||
// Scale and offset the vector
|
|||
v *= vScale; |
|||
v -= vOffset; |
|||
|
|||
// Write the vector to the destination span
|
|||
Unsafe.WriteUnaligned(ref Unsafe.As<Vector4, byte>(ref destination[i]), v); |
|||
} |
|||
|
|||
// Scalar fallback for remaining elements
|
|||
for (; i < source.Length; i++) |
|||
{ |
|||
Vector4 s = source[i]; |
|||
s *= new Vector4(1.0034969F, 1.0034852F, 1.0034913F, 1F); |
|||
s -= new Vector4(0.0033717495F, 0.0034852044F, 0.0028800198F, 0F); |
|||
destination[i] = s; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Scalar fallback if SIMD is not supported
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Vector4 s = source[i]; |
|||
s *= new Vector4(1.0034969F, 1.0034852F, 1.0034913F, 1F); |
|||
s -= new Vector4(0.0033717495F, 0.0034852044F, 0.0028800198F, 0F); |
|||
destination[i] = s; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void ClipNegative(Span<Vector4> source) |
|||
{ |
|||
if (Vector.IsHardwareAccelerated && Vector<float>.IsSupported && Vector<float>.Count >= source.Length * 4) |
|||
{ |
|||
// SIMD loop
|
|||
int i = 0; |
|||
int simdBatchSize = Vector<float>.Count / 4; // Number of Vector4 elements per SIMD batch
|
|||
for (; i <= source.Length - simdBatchSize; i += simdBatchSize) |
|||
{ |
|||
// Load the vector from source span
|
|||
Vector<float> v = Unsafe.ReadUnaligned<Vector<float>>(ref Unsafe.As<Vector4, byte>(ref source[i])); |
|||
|
|||
v = Vector.Max(v, Vector<float>.Zero); |
|||
|
|||
// Write the vector to the destination span
|
|||
Unsafe.WriteUnaligned(ref Unsafe.As<Vector4, byte>(ref source[i]), v); |
|||
} |
|||
|
|||
// Scalar fallback for remaining elements
|
|||
for (; i < source.Length; i++) |
|||
{ |
|||
ref Vector4 s = ref source[i]; |
|||
s = Vector4.Max(s, Vector4.Zero); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Scalar fallback if SIMD is not supported
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
ref Vector4 s = ref source[i]; |
|||
s = Vector4.Max(s, Vector4.Zero); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 LabToLabV2(Vector4 input) |
|||
=> input * 65280F / 65535F; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 LabV2ToLab(Vector4 input) |
|||
=> input * 65535F / 65280F; |
|||
|
|||
private static void LabToLabV2(Span<Vector4> source, Span<Vector4> destination) |
|||
=> LabToLab(source, destination, 65280F / 65535F); |
|||
|
|||
private static void LabV2ToLab(Span<Vector4> source, Span<Vector4> destination) |
|||
=> LabToLab(source, destination, 65535F / 65280F); |
|||
|
|||
private static void LabToLab(Span<Vector4> source, Span<Vector4> destination, [ConstantExpected] float scale) |
|||
{ |
|||
if (Vector.IsHardwareAccelerated && Vector<float>.IsSupported) |
|||
{ |
|||
Vector<float> vScale = new(scale); |
|||
int i = 0; |
|||
|
|||
// SIMD loop
|
|||
int simdBatchSize = Vector<float>.Count / 4; // Number of Vector4 elements per SIMD batch
|
|||
for (; i <= source.Length - simdBatchSize; i += simdBatchSize) |
|||
{ |
|||
// Load the vector from source span
|
|||
Vector<float> v = Unsafe.ReadUnaligned<Vector<float>>(ref Unsafe.As<Vector4, byte>(ref source[i])); |
|||
|
|||
// Scale the vector
|
|||
v *= vScale; |
|||
|
|||
// Write the scaled vector to the destination span
|
|||
Unsafe.WriteUnaligned(ref Unsafe.As<Vector4, byte>(ref destination[i]), v); |
|||
} |
|||
|
|||
// Scalar fallback for remaining elements
|
|||
for (; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i] * scale; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Scalar fallback if SIMD is not supported
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i] * scale; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private class ConversionParams |
|||
{ |
|||
private readonly IccProfile profile; |
|||
|
|||
internal ConversionParams(IccProfile profile, bool toPcs) |
|||
{ |
|||
this.profile = profile; |
|||
this.Converter = toPcs ? new IccDataToPcsConverter(profile) : new IccPcsToDataConverter(profile); |
|||
} |
|||
|
|||
internal IccConverterBase Converter { get; } |
|||
|
|||
internal IccProfileHeader Header => this.profile.Header; |
|||
|
|||
internal IccRenderingIntent Intent => this.Header.RenderingIntent; |
|||
|
|||
internal IccColorSpaceType PcsType => this.Header.ProfileConnectionSpace; |
|||
|
|||
internal IccVersion Version => this.Header.Version; |
|||
|
|||
internal bool HasV2PerceptualHandling => this.Intent == IccRenderingIntent.Perceptual && this.Version.Major == 2; |
|||
|
|||
internal bool HasNoPerceptualHandling => this.Intent == IccRenderingIntent.Perceptual && this.Converter.IsTrc; |
|||
|
|||
internal bool Is16BitLutEntry => this.Converter.Is16BitLutEntry; |
|||
} |
|||
} |
|||
@ -0,0 +1,192 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using System.Runtime.Intrinsics.X86; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
internal static class ColorProfileConverterExtensionsPixelCompatible |
|||
{ |
|||
/// <summary>
|
|||
/// Converts the pixel data of the specified image from the source color profile to the target color profile using
|
|||
/// the provided color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method modifies the source image in place by converting its pixel data according to the
|
|||
/// color profiles specified in the converter. The method does not verify whether the profiles are RGB compatible;
|
|||
/// if they are not, the conversion may produce incorrect results. Ensure that both the source and target ICC
|
|||
/// profiles are set on the converter before calling this method.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="converter">The color profile converter configured with source and target ICC profiles.</param>
|
|||
/// <param name="source">
|
|||
/// The image whose pixel data will be converted. The conversion is performed in place, modifying the original
|
|||
/// image.
|
|||
/// </param>
|
|||
/// <exception cref="InvalidOperationException">
|
|||
/// Thrown if the converter's source or target ICC profile is not specified.
|
|||
/// </exception>
|
|||
public static void Convert<TPixel>(this ColorProfileConverter converter, Image<TPixel> source) |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
{ |
|||
// These checks actually take place within the converter, but we want to fail fast here.
|
|||
// Note. we do not check to see whether the profiles themselves are RGB compatible,
|
|||
// if they are not, then the converter will simply produce incorrect results.
|
|||
if (converter.Options.SourceIccProfile is null) |
|||
{ |
|||
throw new InvalidOperationException("Source ICC profile is missing."); |
|||
} |
|||
|
|||
if (converter.Options.TargetIccProfile is null) |
|||
{ |
|||
throw new InvalidOperationException("Target ICC profile is missing."); |
|||
} |
|||
|
|||
// Process the rows in parallel chunks, the converter itself is thread safe.
|
|||
source.Mutate(o => o.ProcessPixelRowsAsVector4( |
|||
row => |
|||
{ |
|||
// Gather and convert the pixels in the row to Rgb.
|
|||
using IMemoryOwner<Rgb> rgbBuffer = converter.Options.MemoryAllocator.Allocate<Rgb>(row.Length); |
|||
Span<Rgb> rgbSpan = rgbBuffer.Memory.Span; |
|||
Rgb.FromScaledVector4(row, rgbSpan); |
|||
|
|||
// Perform the actual color conversion.
|
|||
converter.ConvertUsingIccProfile<Rgb, Rgb>(rgbSpan, rgbSpan); |
|||
|
|||
// Copy the converted Rgb pixels back to the row as TPixel.
|
|||
// Important: Preserve alpha from the existing row Vector4 values.
|
|||
// We merge RGB from rgbSpan into row, leaving W untouched.
|
|||
ref float srcRgb = ref Unsafe.As<Rgb, float>(ref MemoryMarshal.GetReference(rgbSpan)); |
|||
ref float dstRow = ref Unsafe.As<Vector4, float>(ref MemoryMarshal.GetReference(row)); |
|||
|
|||
int count = rgbSpan.Length; |
|||
int i = 0; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
static Vector512<float> ReadVector512(ref float f) |
|||
{ |
|||
ref byte b = ref Unsafe.As<float, byte>(ref f); |
|||
return Unsafe.ReadUnaligned<Vector512<float>>(ref b); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
static void WriteVector512(ref float f, Vector512<float> v) |
|||
{ |
|||
ref byte b = ref Unsafe.As<float, byte>(ref f); |
|||
Unsafe.WriteUnaligned(ref b, v); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
static Vector256<float> ReadVector256(ref float f) |
|||
{ |
|||
ref byte b = ref Unsafe.As<float, byte>(ref f); |
|||
return Unsafe.ReadUnaligned<Vector256<float>>(ref b); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
static void WriteVector256(ref float f, Vector256<float> v) |
|||
{ |
|||
ref byte b = ref Unsafe.As<float, byte>(ref f); |
|||
Unsafe.WriteUnaligned(ref b, v); |
|||
} |
|||
|
|||
if (Avx512F.IsSupported) |
|||
{ |
|||
// 4 pixels per iteration.
|
|||
//
|
|||
// Source layout (Rgb float stream, 12 floats):
|
|||
// [r0 g0 b0 r1 g1 b1 r2 g2 b2 r3 g3 b3]
|
|||
//
|
|||
// Destination layout (row Vector4 float stream, 16 floats):
|
|||
// [r0 g0 b0 a0 r1 g1 b1 a1 r2 g2 b2 a2 r3 g3 b3 a3]
|
|||
//
|
|||
// We use an overlapped load (16 floats) from the 3-float stride source.
|
|||
// The permute selects the RGB we need and inserts placeholders for alpha lanes.
|
|||
//
|
|||
// Then we blend RGB lanes into the existing destination, preserving alpha lanes.
|
|||
Vector512<int> rgbPerm = Vector512.Create(0, 1, 2, 0, 3, 4, 5, 0, 6, 7, 8, 0, 9, 10, 11, 0); |
|||
|
|||
// BlendVariable selects from the second operand where the sign bit of the mask lane is set.
|
|||
// We want to overwrite lanes 0,1,2 then 4,5,6 then 8,9,10 then 12,13,14, and preserve lanes 3,7,11,15 (alpha).
|
|||
Vector512<float> rgbSelect = Vector512.Create(-0F, -0F, -0F, 0F, -0F, -0F, -0F, 0F, -0F, -0F, -0F, 0F, -0F, -0F, -0F, 0F); |
|||
|
|||
int quads = count >> 2; |
|||
int simdQuads = quads - 1; // Leave the last quad for the scalar tail to avoid the final overlapped load reading past the end.
|
|||
|
|||
for (int q = 0; q < simdQuads; q++) |
|||
{ |
|||
Vector512<float> dst = ReadVector512(ref dstRow); |
|||
Vector512<float> src = ReadVector512(ref srcRgb); |
|||
|
|||
Vector512<float> rgbx = Avx512F.PermuteVar16x32(src, rgbPerm); |
|||
Vector512<float> merged = Avx512F.BlendVariable(dst, rgbx, rgbSelect); |
|||
|
|||
WriteVector512(ref dstRow, merged); |
|||
|
|||
// Advance input by 4 pixels (4 * 3 = 12 floats)
|
|||
srcRgb = ref Unsafe.Add(ref srcRgb, 12); |
|||
|
|||
// Advance output by 4 pixels (4 * 4 = 16 floats)
|
|||
dstRow = ref Unsafe.Add(ref dstRow, 16); |
|||
|
|||
i += 4; |
|||
} |
|||
} |
|||
else if (Avx2.IsSupported) |
|||
{ |
|||
// 2 pixels per iteration.
|
|||
//
|
|||
// Same idea as AVX-512, but on 256-bit vectors.
|
|||
// We permute packed RGB into rgbx layout and blend into the existing destination,
|
|||
// preserving alpha lanes.
|
|||
Vector256<int> rgbPerm = Vector256.Create(0, 1, 2, 0, 3, 4, 5, 0); |
|||
|
|||
Vector256<float> rgbSelect = Vector256.Create(-0F, -0F, -0F, 0F, -0F, -0F, -0F, 0F); |
|||
|
|||
int pairs = count >> 1; |
|||
int simdPairs = pairs - 1; // Leave the last pair for the scalar tail to avoid the final overlapped load reading past the end.
|
|||
|
|||
for (int p = 0; p < simdPairs; p++) |
|||
{ |
|||
Vector256<float> dst = ReadVector256(ref dstRow); |
|||
Vector256<float> src = ReadVector256(ref srcRgb); |
|||
|
|||
Vector256<float> rgbx = Avx2.PermuteVar8x32(src, rgbPerm); |
|||
Vector256<float> merged = Avx.BlendVariable(dst, rgbx, rgbSelect); |
|||
|
|||
WriteVector256(ref dstRow, merged); |
|||
|
|||
// Advance input by 2 pixels (2 * 3 = 6 floats)
|
|||
srcRgb = ref Unsafe.Add(ref srcRgb, 6); |
|||
|
|||
// Advance output by 2 pixels (2 * 4 = 8 floats)
|
|||
dstRow = ref Unsafe.Add(ref dstRow, 8); |
|||
|
|||
i += 2; |
|||
} |
|||
} |
|||
|
|||
// Scalar tail.
|
|||
// Handles:
|
|||
// - the last skipped SIMD block (quad or pair)
|
|||
// - any remainder
|
|||
//
|
|||
// Preserve alpha by writing Vector3 into the Vector4 storage.
|
|||
ref Vector4 rowRef = ref MemoryMarshal.GetReference(row); |
|||
for (; i < count; i++) |
|||
{ |
|||
Vector3 rgb = rgbSpan[i].AsVector3Unsafe(); |
|||
Unsafe.As<Vector4, Vector3>(ref Unsafe.Add(ref rowRef, (uint)i)) = rgb; |
|||
} |
|||
}, |
|||
PixelConversionModifiers.Scale)); |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Allows conversion between two color profiles based on the RGB and CIE Lab color spaces.
|
|||
/// </summary>
|
|||
public static class ColorProfileConverterExtensionsRgbCieLab |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a color value from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The conversion process may use ICC profiles if available; otherwise, it performs a manual
|
|||
/// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
|
|||
/// both source and target types to be value types implementing the appropriate color profile interface.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The source color profile type. Must implement <see cref="IColorProfile{TFrom, Rgb}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The target color profile type. Must implement <see cref="IColorProfile{TTo, CieLab}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion.</param>
|
|||
/// <param name="source">The source color value to convert.</param>
|
|||
/// <returns>A value of type <typeparamref name="TTo"/> representing the converted color in the target color profile.</returns>
|
|||
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, in TFrom source) |
|||
where TFrom : struct, IColorProfile<TFrom, Rgb> |
|||
where TTo : struct, IColorProfile<TTo, CieLab> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
return converter.ConvertUsingIccProfile<TFrom, TTo>(source); |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS
|
|||
Rgb pcsFromA = source.ToProfileConnectingSpace(options); |
|||
CieXyz pcsFromB = pcsFromA.ToProfileConnectingSpace(options); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert between PCS
|
|||
CieLab pcsTo = CieLab.FromProfileConnectingSpace(options, in pcsFromB); |
|||
|
|||
// Convert to output from PCS
|
|||
return TTo.FromProfileConnectingSpace(options, in pcsTo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a span of color values from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method performs color conversion between two color profiles, handling necessary
|
|||
/// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
|
|||
/// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
|
|||
/// for the destination; the caller is responsible for providing a suitably sized span.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The type representing the source color profile. Must implement <see cref="IColorProfile{TFrom, Rgb}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The type representing the destination color profile. Must implement <see cref="IColorProfile{TTo, CieLab}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion operation.</param>
|
|||
/// <param name="source">A read-only span containing the source color values to convert.</param>
|
|||
/// <param name="destination">A span that receives the converted color values. Must be at least as long as the source span.</param>
|
|||
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
|||
where TFrom : struct, IColorProfile<TFrom, Rgb> |
|||
where TTo : struct, IColorProfile<TTo, CieLab> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
converter.ConvertUsingIccProfile(source, destination); |
|||
return; |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS.
|
|||
using IMemoryOwner<Rgb> pcsFromAOwner = options.MemoryAllocator.Allocate<Rgb>(source.Length); |
|||
Span<Rgb> pcsFromA = pcsFromAOwner.GetSpan(); |
|||
TFrom.ToProfileConnectionSpace(options, source, pcsFromA); |
|||
|
|||
using IMemoryOwner<CieXyz> pcsFromBOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
|||
Span<CieXyz> pcsFromB = pcsFromBOwner.GetSpan(); |
|||
Rgb.ToProfileConnectionSpace(options, pcsFromA, pcsFromB); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert between PCS.
|
|||
using IMemoryOwner<CieLab> pcsToOwner = options.MemoryAllocator.Allocate<CieLab>(source.Length); |
|||
Span<CieLab> pcsTo = pcsToOwner.GetSpan(); |
|||
CieLab.FromProfileConnectionSpace(options, pcsFromB, pcsTo); |
|||
|
|||
// Convert to output from PCS
|
|||
TTo.FromProfileConnectionSpace(options, pcsTo, destination); |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Allows conversion between two color profiles based on the RGB and CIE XYZ color spaces.
|
|||
/// </summary>
|
|||
public static class ColorProfileConverterExtensionsRgbCieXyz |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a color value from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The conversion process may use ICC profiles if available; otherwise, it performs a manual
|
|||
/// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
|
|||
/// both source and target types to be value types implementing the appropriate color profile interface.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The source color profile type. Must implement <see cref="IColorProfile{TFrom, Rgb}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The target color profile type. Must implement <see cref="IColorProfile{TTo, CieXyz}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion.</param>
|
|||
/// <param name="source">The source color value to convert.</param>
|
|||
/// <returns>A value of type <typeparamref name="TTo"/> representing the converted color in the target color profile.</returns>
|
|||
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, in TFrom source) |
|||
where TFrom : struct, IColorProfile<TFrom, Rgb> |
|||
where TTo : struct, IColorProfile<TTo, CieXyz> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
return converter.ConvertUsingIccProfile<TFrom, TTo>(source); |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS
|
|||
Rgb pcsFrom = source.ToProfileConnectingSpace(options); |
|||
|
|||
// Convert between PCS
|
|||
CieXyz pcsTo = pcsFrom.ToProfileConnectingSpace(options); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
pcsTo = VonKriesChromaticAdaptation.Transform(in pcsTo, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert to output from PCS
|
|||
return TTo.FromProfileConnectingSpace(options, in pcsTo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a span of color values from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method performs color conversion between two color profiles, handling necessary
|
|||
/// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
|
|||
/// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
|
|||
/// for the destination; the caller is responsible for providing a suitably sized span.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The type representing the source color profile. Must implement <see cref="IColorProfile{TFrom, Rgb}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The type representing the destination color profile. Must implement <see cref="IColorProfile{TTo, CieXyz}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion operation.</param>
|
|||
/// <param name="source">A read-only span containing the source color values to convert.</param>
|
|||
/// <param name="destination">A span that receives the converted color values. Must be at least as long as the source span.</param>
|
|||
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
|||
where TFrom : struct, IColorProfile<TFrom, Rgb> |
|||
where TTo : struct, IColorProfile<TTo, CieXyz> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
converter.ConvertUsingIccProfile(source, destination); |
|||
return; |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS.
|
|||
using IMemoryOwner<Rgb> pcsFromOwner = options.MemoryAllocator.Allocate<Rgb>(source.Length); |
|||
Span<Rgb> pcsFrom = pcsFromOwner.GetSpan(); |
|||
TFrom.ToProfileConnectionSpace(options, source, pcsFrom); |
|||
|
|||
// Convert between PCS.
|
|||
using IMemoryOwner<CieXyz> pcsToOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
|||
Span<CieXyz> pcsTo = pcsToOwner.GetSpan(); |
|||
Rgb.ToProfileConnectionSpace(options, pcsFrom, pcsTo); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
VonKriesChromaticAdaptation.Transform(pcsTo, pcsTo, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert to output from PCS
|
|||
TTo.FromProfileConnectionSpace(options, pcsTo, destination); |
|||
} |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Allows conversion between two color profiles based on the RGB color space.
|
|||
/// </summary>
|
|||
public static class ColorProfileConverterExtensionsRgbRgb |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a color value from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The conversion process may use ICC profiles if available; otherwise, it performs a manual
|
|||
/// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
|
|||
/// both source and target types to be value types implementing the appropriate color profile interface.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The source color profile type. Must implement <see cref="IColorProfile{TFrom, Rgb}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The target color profile type. Must implement <see cref="IColorProfile{TTo, Rgb}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion.</param>
|
|||
/// <param name="source">The source color value to convert.</param>
|
|||
/// <returns>A value of type <typeparamref name="TTo"/> representing the converted color in the target color profile.</returns>
|
|||
public static TTo Convert<TFrom, TTo>(this ColorProfileConverter converter, in TFrom source) |
|||
where TFrom : struct, IColorProfile<TFrom, Rgb> |
|||
where TTo : struct, IColorProfile<TTo, Rgb> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
return converter.ConvertUsingIccProfile<TFrom, TTo>(source); |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS
|
|||
Rgb pcsFromA = source.ToProfileConnectingSpace(options); |
|||
CieXyz pcsFromB = pcsFromA.ToProfileConnectingSpace(options); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
pcsFromB = VonKriesChromaticAdaptation.Transform(in pcsFromB, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert between PCS
|
|||
Rgb pcsTo = Rgb.FromProfileConnectingSpace(options, in pcsFromB); |
|||
|
|||
// Convert to output from PCS
|
|||
return TTo.FromProfileConnectingSpace(options, in pcsTo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts a span of color values from one color profile to another using the specified color profile converter.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This method performs color conversion between two color profiles, handling necessary
|
|||
/// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
|
|||
/// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
|
|||
/// for the destination; the caller is responsible for providing a suitably sized span.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TFrom">The type representing the source color profile. Must implement <see cref="IColorProfile{TFrom, Rgb}"/>.</typeparam>
|
|||
/// <typeparam name="TTo">The type representing the destination color profile. Must implement <see cref="IColorProfile{TTo, Rgb}"/>.</typeparam>
|
|||
/// <param name="converter">The color profile converter to use for the conversion operation.</param>
|
|||
/// <param name="source">A read-only span containing the source color values to convert.</param>
|
|||
/// <param name="destination">A span that receives the converted color values. Must be at least as long as the source span.</param>
|
|||
public static void Convert<TFrom, TTo>(this ColorProfileConverter converter, ReadOnlySpan<TFrom> source, Span<TTo> destination) |
|||
where TFrom : struct, IColorProfile<TFrom, Rgb> |
|||
where TTo : struct, IColorProfile<TTo, Rgb> |
|||
{ |
|||
if (converter.ShouldUseIccProfiles()) |
|||
{ |
|||
converter.ConvertUsingIccProfile(source, destination); |
|||
return; |
|||
} |
|||
|
|||
ColorConversionOptions options = converter.Options; |
|||
|
|||
// Convert to input PCS.
|
|||
using IMemoryOwner<Rgb> pcsFromToOwner = options.MemoryAllocator.Allocate<Rgb>(source.Length); |
|||
Span<Rgb> pcsFromTo = pcsFromToOwner.GetSpan(); |
|||
TFrom.ToProfileConnectionSpace(options, source, pcsFromTo); |
|||
|
|||
using IMemoryOwner<CieXyz> pcsFromOwner = options.MemoryAllocator.Allocate<CieXyz>(source.Length); |
|||
Span<CieXyz> pcsFrom = pcsFromOwner.GetSpan(); |
|||
Rgb.ToProfileConnectionSpace(options, pcsFromTo, pcsFrom); |
|||
|
|||
// Adapt to target white point
|
|||
(CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints<TFrom, TTo>(); |
|||
VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix); |
|||
|
|||
// Convert between PCS.
|
|||
Rgb.FromProfileConnectionSpace(options, pcsFrom, pcsFromTo); |
|||
|
|||
// Convert to output from PCS
|
|||
TTo.FromProfileConnectionSpace(options, pcsFromTo, destination); |
|||
} |
|||
} |
|||
@ -0,0 +1,182 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Collections.Concurrent; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using System.Runtime.Intrinsics.X86; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Companding; |
|||
|
|||
/// <summary>
|
|||
/// Companding utilities that allow the accelerated compression-expansion of color channels.
|
|||
/// </summary>
|
|||
public static class CompandingUtilities |
|||
{ |
|||
private const int Length = Scale + 2; // 256kb @ 16bit precision.
|
|||
private const int Scale = (1 << 16) - 1; |
|||
private static readonly ConcurrentDictionary<(Type, double), float[]> CompressLookupTables = new(); |
|||
private static readonly ConcurrentDictionary<(Type, double), float[]> ExpandLookupTables = new(); |
|||
|
|||
/// <summary>
|
|||
/// Lazily creates and stores a companding compression lookup table using the given function and modifier.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of companding function.</typeparam>
|
|||
/// <param name="compandingFunction">The companding function.</param>
|
|||
/// <param name="modifier">A modifier to pass to the function.</param>
|
|||
/// <returns>The <see cref="float"/> array.</returns>
|
|||
public static float[] GetCompressLookupTable<T>(Func<double, double, double> compandingFunction, double modifier = 0) |
|||
=> CompressLookupTables.GetOrAdd((typeof(T), modifier), args => CreateLookupTableImpl(compandingFunction, args.Item2)); |
|||
|
|||
/// <summary>
|
|||
/// Lazily creates and stores a companding expanding lookup table using the given function and modifier.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of companding function.</typeparam>
|
|||
/// <param name="compandingFunction">The companding function.</param>
|
|||
/// <param name="modifier">A modifier to pass to the function.</param>
|
|||
/// <returns>The <see cref="float"/> array.</returns>
|
|||
public static float[] GetExpandLookupTable<T>(Func<double, double, double> compandingFunction, double modifier = 0) |
|||
=> ExpandLookupTables.GetOrAdd((typeof(T), modifier), args => CreateLookupTableImpl(compandingFunction, args.Item2)); |
|||
|
|||
/// <summary>
|
|||
/// Creates a companding lookup table using the given function.
|
|||
/// </summary>
|
|||
/// <param name="compandingFunction">The companding function.</param>
|
|||
/// <param name="modifier">A modifier to pass to the function.</param>
|
|||
/// <returns>The <see cref="float"/> array.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static float[] CreateLookupTableImpl(Func<double, double, double> compandingFunction, double modifier = 0) |
|||
{ |
|||
float[] result = new float[Length]; |
|||
|
|||
for (int i = 0; i < result.Length; i++) |
|||
{ |
|||
double d = (double)i / Scale; |
|||
d = compandingFunction(d, modifier); |
|||
result[i] = (float)d; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Performs the companding operation on the given vectors using the given table.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
/// <param name="table">The lookup table.</param>
|
|||
public static void Compand(Span<Vector4> vectors, float[] table) |
|||
{ |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(table.Length, Length, nameof(table)); |
|||
|
|||
if (Avx2.IsSupported && vectors.Length >= 2) |
|||
{ |
|||
CompandAvx2(vectors, table); |
|||
|
|||
if (Numerics.Modulo2(vectors.Length) != 0) |
|||
{ |
|||
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
|
|||
ref Vector4 last = ref MemoryMarshal.GetReference(vectors[^1..]); |
|||
last = Compand(last, table); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
CompandScalar(vectors, table); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Performs the companding operation on the given vector using the given table.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <param name="table">The lookup table.</param>
|
|||
/// <returns>The <see cref="Vector4"/></returns>
|
|||
public static Vector4 Compand(Vector4 vector, float[] table) |
|||
{ |
|||
DebugGuard.MustBeGreaterThanOrEqualTo(table.Length, Length, nameof(table)); |
|||
|
|||
Vector4 zero = Vector4.Zero; |
|||
Vector4 scale = new(Scale); |
|||
|
|||
Vector4 multiplied = Numerics.Clamp(vector * Scale, zero, scale); |
|||
|
|||
float f0 = multiplied.X; |
|||
float f1 = multiplied.Y; |
|||
float f2 = multiplied.Z; |
|||
|
|||
uint i0 = (uint)f0; |
|||
uint i1 = (uint)f1; |
|||
uint i2 = (uint)f2; |
|||
|
|||
// Alpha is already a linear representation of opacity so we do not want to convert it.
|
|||
vector.X = Numerics.Lerp(table[i0], table[i0 + 1], f0 - (int)i0); |
|||
vector.Y = Numerics.Lerp(table[i1], table[i1 + 1], f1 - (int)i1); |
|||
vector.Z = Numerics.Lerp(table[i2], table[i2 + 1], f2 - (int)i2); |
|||
|
|||
return vector; |
|||
} |
|||
|
|||
private static unsafe void CompandAvx2(Span<Vector4> vectors, float[] table) |
|||
{ |
|||
fixed (float* tablePointer = &MemoryMarshal.GetArrayDataReference(table)) |
|||
{ |
|||
Vector256<float> scale = Vector256.Create((float)Scale); |
|||
Vector256<float> zero = Vector256<float>.Zero; |
|||
Vector256<int> offset = Vector256.Create(1); |
|||
|
|||
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
|
|||
ref Vector256<float> vectorsBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(vectors)); |
|||
ref Vector256<float> vectorsLast = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length / 2u); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) |
|||
{ |
|||
Vector256<float> multiplied = Avx.Multiply(scale, vectorsBase); |
|||
multiplied = Avx.Min(Avx.Max(zero, multiplied), scale); |
|||
|
|||
Vector256<int> truncated = Avx.ConvertToVector256Int32WithTruncation(multiplied); |
|||
Vector256<float> truncatedF = Avx.ConvertToVector256Single(truncated); |
|||
|
|||
Vector256<float> low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float)); |
|||
Vector256<float> high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), sizeof(float)); |
|||
|
|||
// Alpha is already a linear representation of opacity so we do not want to convert it.
|
|||
Vector256<float> companded = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF)); |
|||
vectorsBase = Avx.Blend(companded, vectorsBase, Numerics.BlendAlphaControl); |
|||
vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static unsafe void CompandScalar(Span<Vector4> vectors, float[] table) |
|||
{ |
|||
fixed (float* tablePointer = &MemoryMarshal.GetArrayDataReference(table)) |
|||
{ |
|||
Vector4 zero = Vector4.Zero; |
|||
Vector4 scale = new(Scale); |
|||
ref Vector4 vectorsBase = ref MemoryMarshal.GetReference(vectors); |
|||
ref Vector4 vectorsLast = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length); |
|||
|
|||
while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) |
|||
{ |
|||
Vector4 multiplied = Numerics.Clamp(vectorsBase * Scale, zero, scale); |
|||
|
|||
float f0 = multiplied.X; |
|||
float f1 = multiplied.Y; |
|||
float f2 = multiplied.Z; |
|||
|
|||
uint i0 = (uint)f0; |
|||
uint i1 = (uint)f1; |
|||
uint i2 = (uint)f2; |
|||
|
|||
// Alpha is already a linear representation of opacity so we do not want to convert it.
|
|||
vectorsBase.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0); |
|||
vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1); |
|||
vectorsBase.Z = Numerics.Lerp(tablePointer[i2], tablePointer[i2 + 1], f2 - (int)i2); |
|||
|
|||
vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Companding; |
|||
|
|||
/// <summary>
|
|||
/// Implements gamma companding.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
|
|||
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
|
|||
/// </remarks>
|
|||
public static class GammaCompanding |
|||
{ |
|||
private static Func<double, double, double> CompressFunction => (d, m) => Math.Pow(d, 1 / m); |
|||
|
|||
private static Func<double, double, double> ExpandFunction => Math.Pow; |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
/// <param name="gamma">The gamma value.</param>
|
|||
public static void Compress(Span<Vector4> vectors, double gamma) |
|||
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable<GammaCompandingKey>(CompressFunction, gamma)); |
|||
|
|||
/// <summary>
|
|||
/// Expands the nonlinear vectors to their linear equivalents with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
/// <param name="gamma">The gamma value.</param>
|
|||
public static void Expand(Span<Vector4> vectors, double gamma) |
|||
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable<GammaCompandingKey>(ExpandFunction, gamma)); |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <param name="gamma">The gamma value.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public static Vector4 Compress(Vector4 vector, double gamma) |
|||
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable<GammaCompandingKey>(CompressFunction, gamma)); |
|||
|
|||
/// <summary>
|
|||
/// Expands the nonlinear vector to its linear equivalent with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <param name="gamma">The gamma value.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public static Vector4 Expand(Vector4 vector, double gamma) |
|||
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable<GammaCompandingKey>(ExpandFunction, gamma)); |
|||
|
|||
private class GammaCompandingKey; |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Companding; |
|||
|
|||
/// <summary>
|
|||
/// Implements L* companding.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// For more info see:
|
|||
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
|
|||
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
|
|||
/// </remarks>
|
|||
public static class LCompanding |
|||
{ |
|||
private static Func<double, double, double> CompressFunction |
|||
=> (d, _) => |
|||
{ |
|||
if (d <= CieConstants.Epsilon) |
|||
{ |
|||
return (d * CieConstants.Kappa) / 100; |
|||
} |
|||
|
|||
return (1.16 * Math.Pow(d, 0.3333333)) - 0.16; |
|||
}; |
|||
|
|||
private static Func<double, double, double> ExpandFunction |
|||
=> (d, _) => |
|||
{ |
|||
if (d <= 0.08) |
|||
{ |
|||
return (100 * d) / CieConstants.Kappa; |
|||
} |
|||
|
|||
return Numerics.Pow3(((float)(d + 0.16f)) / 1.16f); |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
public static void Compress(Span<Vector4> vectors) |
|||
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable<LCompandingKey>(CompressFunction)); |
|||
|
|||
/// <summary>
|
|||
/// Expands the nonlinear vectors to their linear equivalents with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
public static void Expand(Span<Vector4> vectors) |
|||
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable<LCompandingKey>(ExpandFunction)); |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public static Vector4 Compress(Vector4 vector) |
|||
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable<LCompandingKey>(CompressFunction)); |
|||
|
|||
/// <summary>
|
|||
/// Expands the nonlinear vector to its linear equivalent with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public static Vector4 Expand(Vector4 vector) |
|||
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable<LCompandingKey>(ExpandFunction)); |
|||
|
|||
private class LCompandingKey; |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Companding; |
|||
|
|||
/// <summary>
|
|||
/// Implements Rec. 2020 companding function.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// <see href="http://en.wikipedia.org/wiki/Rec._2020"/>
|
|||
/// </remarks>
|
|||
public static class Rec2020Companding |
|||
{ |
|||
private const double Alpha = 1.09929682680944; |
|||
private const double AlphaMinusOne = Alpha - 1; |
|||
private const double Beta = 0.018053968510807; |
|||
private const double InverseBeta = Beta * 4.5; |
|||
private const double Epsilon = 1 / 0.45; |
|||
|
|||
private static Func<double, double, double> CompressFunction |
|||
=> (d, _) => |
|||
{ |
|||
if (d < Beta) |
|||
{ |
|||
return 4.5 * d; |
|||
} |
|||
|
|||
return (Alpha * Math.Pow(d, 0.45)) - AlphaMinusOne; |
|||
}; |
|||
|
|||
private static Func<double, double, double> ExpandFunction |
|||
=> (d, _) => |
|||
{ |
|||
if (d < InverseBeta) |
|||
{ |
|||
return d / 4.5; |
|||
} |
|||
|
|||
return Math.Pow((d + AlphaMinusOne) / Alpha, Epsilon); |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
public static void Compress(Span<Vector4> vectors) |
|||
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable<Rec2020CompandingKey>(CompressFunction)); |
|||
|
|||
/// <summary>
|
|||
/// Expands the nonlinear vectors to their linear equivalents with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
public static void Expand(Span<Vector4> vectors) |
|||
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable<Rec2020CompandingKey>(ExpandFunction)); |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public static Vector4 Compress(Vector4 vector) |
|||
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable<Rec2020CompandingKey>(CompressFunction)); |
|||
|
|||
/// <summary>
|
|||
/// Expands the nonlinear vector to its linear equivalent with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public static Vector4 Expand(Vector4 vector) |
|||
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable<Rec2020CompandingKey>(ExpandFunction)); |
|||
|
|||
private class Rec2020CompandingKey; |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Companding; |
|||
|
|||
/// <summary>
|
|||
/// Implements the Rec. 709 companding function.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// http://en.wikipedia.org/wiki/Rec._709
|
|||
/// </remarks>
|
|||
public static class Rec709Companding |
|||
{ |
|||
private const double Epsilon = 1 / 0.45; |
|||
|
|||
private static Func<double, double, double> CompressFunction |
|||
=> (d, _) => |
|||
{ |
|||
if (d < 0.018) |
|||
{ |
|||
return 4.5 * d; |
|||
} |
|||
|
|||
return (1.099 * Math.Pow(d, 0.45)) - 0.099; |
|||
}; |
|||
|
|||
private static Func<double, double, double> ExpandFunction |
|||
=> (d, _) => |
|||
{ |
|||
if (d < 0.081) |
|||
{ |
|||
return d / 4.5; |
|||
} |
|||
|
|||
return Math.Pow((d + 0.099) / 1.099, Epsilon); |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
public static void Compress(Span<Vector4> vectors) |
|||
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable<Rec2020CompandingKey>(CompressFunction)); |
|||
|
|||
/// <summary>
|
|||
/// Expands the nonlinear vectors to their linear equivalents with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
public static void Expand(Span<Vector4> vectors) |
|||
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable<Rec2020CompandingKey>(ExpandFunction)); |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public static Vector4 Compress(Vector4 vector) |
|||
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable<Rec2020CompandingKey>(CompressFunction)); |
|||
|
|||
/// <summary>
|
|||
/// Expands the nonlinear vector to its linear equivalent with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public static Vector4 Expand(Vector4 vector) |
|||
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable<Rec2020CompandingKey>(ExpandFunction)); |
|||
|
|||
private class Rec2020CompandingKey; |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Companding; |
|||
|
|||
/// <summary>
|
|||
/// Implements sRGB companding.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// For more info see:
|
|||
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html"/>
|
|||
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html"/>
|
|||
/// </remarks>
|
|||
public static class SRgbCompanding |
|||
{ |
|||
private static Func<double, double, double> CompressFunction |
|||
=> (d, _) => |
|||
{ |
|||
if (d <= (0.04045 / 12.92)) |
|||
{ |
|||
return d * 12.92; |
|||
} |
|||
|
|||
return (1.055 * Math.Pow(d, 1.0 / 2.4)) - 0.055; |
|||
}; |
|||
|
|||
private static Func<double, double, double> ExpandFunction |
|||
=> (d, _) => |
|||
{ |
|||
if (d <= 0.04045) |
|||
{ |
|||
return d / 12.92; |
|||
} |
|||
|
|||
return Math.Pow((d + 0.055) / 1.055, 2.4); |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
public static void Compress(Span<Vector4> vectors) |
|||
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetCompressLookupTable<SRgbCompandingKey>(CompressFunction)); |
|||
|
|||
/// <summary>
|
|||
/// Expands the nonlinear vectors to their linear equivalents with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
public static void Expand(Span<Vector4> vectors) |
|||
=> CompandingUtilities.Compand(vectors, CompandingUtilities.GetExpandLookupTable<SRgbCompandingKey>(ExpandFunction)); |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public static Vector4 Compress(Vector4 vector) |
|||
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetCompressLookupTable<SRgbCompandingKey>(CompressFunction)); |
|||
|
|||
/// <summary>
|
|||
/// Expands the nonlinear vector to its linear equivalent with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public static Vector4 Expand(Vector4 vector) |
|||
=> CompandingUtilities.Compand(vector, CompandingUtilities.GetExpandLookupTable<SRgbCompandingKey>(ExpandFunction)); |
|||
|
|||
private class SRgbCompandingKey; |
|||
} |
|||
@ -0,0 +1,287 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents a Hsl (hue, saturation, lightness) color.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct Hsl : IColorProfile<Hsl, Rgb> |
|||
{ |
|||
private static readonly Vector3 Min = Vector3.Zero; |
|||
private static readonly Vector3 Max = new(360, 1, 1); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Hsl"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="h">The h hue component.</param>
|
|||
/// <param name="s">The s saturation component.</param>
|
|||
/// <param name="l">The l value (lightness) component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Hsl(float h, float s, float l) |
|||
: this(new Vector3(h, s, l)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Hsl"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the h, s, l components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Hsl(Vector3 vector) |
|||
{ |
|||
vector = Vector3.Clamp(vector, Min, Max); |
|||
this.H = vector.X; |
|||
this.S = vector.Y; |
|||
this.L = vector.Z; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
|||
private Hsl(Vector3 vector, bool _) |
|||
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
|
|||
{ |
|||
this.H = vector.X; |
|||
this.S = vector.Y; |
|||
this.L = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the hue component.
|
|||
/// <remarks>A value ranging between 0 and 360.</remarks>
|
|||
/// </summary>
|
|||
public float H { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the saturation component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float S { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the lightness component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float L { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Hsl"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="Hsl"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">The <see cref="Hsl"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(Hsl left, Hsl right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Hsl"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Hsl"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Hsl"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(Hsl left, Hsl right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() |
|||
=> new(this.AsVector3Unsafe() / 360F, 1F); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Hsl FromScaledVector4(Vector4 source) |
|||
=> new(source.AsVector3() * 360F, true); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<Hsl> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToScaledVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<Hsl> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = FromScaledVector4(source[i]); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Hsl FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) |
|||
{ |
|||
float r = source.R; |
|||
float g = source.G; |
|||
float b = source.B; |
|||
|
|||
float max = MathF.Max(r, MathF.Max(g, b)); |
|||
float min = MathF.Min(r, MathF.Min(g, b)); |
|||
float chroma = max - min; |
|||
float h = 0F; |
|||
float s = 0F; |
|||
float l = (max + min) / 2F; |
|||
|
|||
if (MathF.Abs(chroma) < Constants.Epsilon) |
|||
{ |
|||
return new Hsl(0F, s, l); |
|||
} |
|||
|
|||
if (MathF.Abs(r - max) < Constants.Epsilon) |
|||
{ |
|||
h = (g - b) / chroma; |
|||
} |
|||
else if (MathF.Abs(g - max) < Constants.Epsilon) |
|||
{ |
|||
h = 2F + ((b - r) / chroma); |
|||
} |
|||
else if (MathF.Abs(b - max) < Constants.Epsilon) |
|||
{ |
|||
h = 4F + ((r - g) / chroma); |
|||
} |
|||
|
|||
h *= 60F; |
|||
if (h < -Constants.Epsilon) |
|||
{ |
|||
h += 360F; |
|||
} |
|||
|
|||
if (l <= .5F) |
|||
{ |
|||
s = chroma / (max + min); |
|||
} |
|||
else |
|||
{ |
|||
s = chroma / (2F - max - min); |
|||
} |
|||
|
|||
return new Hsl(h, s, l); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<Hsl> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Rgb rgb = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in rgb); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Rgb ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
float rangedH = this.H / 360F; |
|||
float r = 0; |
|||
float g = 0; |
|||
float b = 0; |
|||
float s = this.S; |
|||
float l = this.L; |
|||
|
|||
if (MathF.Abs(l) > Constants.Epsilon) |
|||
{ |
|||
if (MathF.Abs(s) < Constants.Epsilon) |
|||
{ |
|||
r = g = b = l; |
|||
} |
|||
else |
|||
{ |
|||
float temp2 = (l < .5F) ? l * (1F + s) : l + s - (l * s); |
|||
float temp1 = (2F * l) - temp2; |
|||
|
|||
r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F); |
|||
g = GetColorComponent(temp1, temp2, rangedH); |
|||
b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F); |
|||
} |
|||
} |
|||
|
|||
return new Rgb(r, g, b); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Hsl> source, Span<Rgb> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Hsl hsl = source[i]; |
|||
destination[i] = hsl.ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.RgbWorkingSpace; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public override int GetHashCode() => HashCode.Combine(this.H, this.S, this.L); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"Hsl({this.H:#0.##}, {this.S:#0.##}, {this.L:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is Hsl other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(Hsl other) |
|||
=> this.AsVector3Unsafe() == other.AsVector3Unsafe(); |
|||
|
|||
private Vector3 AsVector3Unsafe() => Unsafe.As<Hsl, Vector3>(ref Unsafe.AsRef(in this)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static float GetColorComponent(float first, float second, float third) |
|||
{ |
|||
third = MoveIntoRange(third); |
|||
if (third < 0.1666667F) |
|||
{ |
|||
return first + ((second - first) * 6F * third); |
|||
} |
|||
|
|||
if (third < .5F) |
|||
{ |
|||
return second; |
|||
} |
|||
|
|||
if (third < 0.6666667F) |
|||
{ |
|||
return first + ((second - first) * (0.6666667F - third) * 6F); |
|||
} |
|||
|
|||
return first; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static float MoveIntoRange(float value) |
|||
{ |
|||
if (value < 0F) |
|||
{ |
|||
value++; |
|||
} |
|||
else if (value > 1F) |
|||
{ |
|||
value--; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
} |
|||
@ -0,0 +1,273 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness).
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct Hsv : IColorProfile<Hsv, Rgb> |
|||
{ |
|||
private static readonly Vector3 Min = Vector3.Zero; |
|||
private static readonly Vector3 Max = new(360, 1, 1); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Hsv"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="h">The h hue component.</param>
|
|||
/// <param name="s">The s saturation component.</param>
|
|||
/// <param name="v">The v value (brightness) component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Hsv(float h, float s, float v) |
|||
: this(new Vector3(h, s, v)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Hsv"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the h, s, v components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Hsv(Vector3 vector) |
|||
{ |
|||
vector = Vector3.Clamp(vector, Min, Max); |
|||
this.H = vector.X; |
|||
this.S = vector.Y; |
|||
this.V = vector.Z; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
|||
private Hsv(Vector3 vector, bool _) |
|||
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
|
|||
{ |
|||
this.H = vector.X; |
|||
this.S = vector.Y; |
|||
this.V = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the hue component.
|
|||
/// <remarks>A value ranging between 0 and 360.</remarks>
|
|||
/// </summary>
|
|||
public float H { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the saturation component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float S { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the value (brightness) component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float V { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Hsv"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Hsv"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Hsv"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(Hsv left, Hsv right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Hsv"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Hsv"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Hsv"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(Hsv left, Hsv right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() |
|||
=> new(this.AsVector3Unsafe() / 360F, 1F); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Hsv FromScaledVector4(Vector4 source) |
|||
=> new(source.AsVector3() * 360F, true); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<Hsv> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToScaledVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<Hsv> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = FromScaledVector4(source[i]); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Hsv FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) |
|||
{ |
|||
float r = source.R; |
|||
float g = source.G; |
|||
float b = source.B; |
|||
|
|||
float max = MathF.Max(r, MathF.Max(g, b)); |
|||
float min = MathF.Min(r, MathF.Min(g, b)); |
|||
float chroma = max - min; |
|||
float h = 0; |
|||
float s = 0; |
|||
float v = max; |
|||
|
|||
if (MathF.Abs(chroma) < Constants.Epsilon) |
|||
{ |
|||
return new Hsv(0, s, v); |
|||
} |
|||
|
|||
if (MathF.Abs(r - max) < Constants.Epsilon) |
|||
{ |
|||
h = (g - b) / chroma; |
|||
} |
|||
else if (MathF.Abs(g - max) < Constants.Epsilon) |
|||
{ |
|||
h = 2 + ((b - r) / chroma); |
|||
} |
|||
else if (MathF.Abs(b - max) < Constants.Epsilon) |
|||
{ |
|||
h = 4 + ((r - g) / chroma); |
|||
} |
|||
|
|||
h *= 60F; |
|||
if (h < -Constants.Epsilon) |
|||
{ |
|||
h += 360F; |
|||
} |
|||
|
|||
s = chroma / v; |
|||
|
|||
return new Hsv(h, s, v); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<Hsv> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Rgb rgb = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in rgb); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Rgb ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
float s = this.S; |
|||
float v = this.V; |
|||
|
|||
if (MathF.Abs(s) < Constants.Epsilon) |
|||
{ |
|||
return new Rgb(v, v, v); |
|||
} |
|||
|
|||
float h = (MathF.Abs(this.H - 360) < Constants.Epsilon) ? 0 : this.H / 60; |
|||
int i = (int)Math.Truncate(h); |
|||
float f = h - i; |
|||
|
|||
float p = v * (1F - s); |
|||
float q = v * (1F - (s * f)); |
|||
float t = v * (1F - (s * (1F - f))); |
|||
|
|||
float r, g, b; |
|||
switch (i) |
|||
{ |
|||
case 0: |
|||
r = v; |
|||
g = t; |
|||
b = p; |
|||
break; |
|||
|
|||
case 1: |
|||
r = q; |
|||
g = v; |
|||
b = p; |
|||
break; |
|||
|
|||
case 2: |
|||
r = p; |
|||
g = v; |
|||
b = t; |
|||
break; |
|||
|
|||
case 3: |
|||
r = p; |
|||
g = q; |
|||
b = v; |
|||
break; |
|||
|
|||
case 4: |
|||
r = t; |
|||
g = p; |
|||
b = v; |
|||
break; |
|||
|
|||
default: |
|||
r = v; |
|||
g = p; |
|||
b = q; |
|||
break; |
|||
} |
|||
|
|||
return new Rgb(r, g, b); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Hsv> source, Span<Rgb> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Hsv hsv = source[i]; |
|||
destination[i] = hsv.ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.RgbWorkingSpace; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public override int GetHashCode() => HashCode.Combine(this.H, this.S, this.V); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"Hsv({this.H:#0.##}, {this.S:#0.##}, {this.V:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is Hsv other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(Hsv other) |
|||
=> this.AsVector3Unsafe() == other.AsVector3Unsafe(); |
|||
|
|||
private Vector3 AsVector3Unsafe() => Unsafe.As<Hsv, Vector3>(ref Unsafe.AsRef(in this)); |
|||
} |
|||
@ -0,0 +1,243 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents an Hunter LAB color.
|
|||
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct HunterLab : IColorProfile<HunterLab, CieXyz> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HunterLab"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="l">The lightness dimension.</param>
|
|||
/// <param name="a">The a (green - magenta) component.</param>
|
|||
/// <param name="b">The b (blue - yellow) component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public HunterLab(float l, float a, float b) |
|||
{ |
|||
this.L = l; |
|||
this.A = a; |
|||
this.B = b; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="HunterLab"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the l a b components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public HunterLab(Vector3 vector) |
|||
{ |
|||
// Not clamping as documentation about this space only indicates "usual" ranges
|
|||
this.L = vector.X; |
|||
this.A = vector.Y; |
|||
this.B = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the lightness dimension.
|
|||
/// <remarks>A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
|
|||
/// </summary>
|
|||
public float L { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the a color component.
|
|||
/// <remarks>A value usually ranging from -100 to 100. Negative is green, positive magenta.</remarks>
|
|||
/// </summary>
|
|||
public float A { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the b color component.
|
|||
/// <remarks>A value usually ranging from -100 to 100. Negative is blue, positive is yellow</remarks>
|
|||
/// </summary>
|
|||
public float B { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="HunterLab"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="HunterLab"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="HunterLab"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
public static bool operator ==(HunterLab left, HunterLab right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="HunterLab"/> objects for inequality
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="HunterLab"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="HunterLab"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(HunterLab left, HunterLab right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() |
|||
{ |
|||
Vector3 v3 = default; |
|||
v3 += this.AsVector3Unsafe(); |
|||
v3 += new Vector3(0, 128F, 128F); |
|||
v3 /= new Vector3(100F, 255F, 255F); |
|||
return new Vector4(v3, 1F); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static HunterLab FromScaledVector4(Vector4 source) |
|||
{ |
|||
Vector3 v3 = source.AsVector3(); |
|||
v3 *= new Vector3(100F, 255, 255); |
|||
v3 -= new Vector3(0, 128F, 128F); |
|||
return new HunterLab(v3); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<HunterLab> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToScaledVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<HunterLab> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = FromScaledVector4(source[i]); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static HunterLab FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) |
|||
{ |
|||
// Conversion algorithm described here:
|
|||
// http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab
|
|||
CieXyz whitePoint = options.TargetWhitePoint; |
|||
float x = source.X, y = source.Y, z = source.Z; |
|||
float xn = whitePoint.X, yn = whitePoint.Y, zn = whitePoint.Z; |
|||
|
|||
float ka = ComputeKa(in whitePoint); |
|||
float kb = ComputeKb(in whitePoint); |
|||
|
|||
float yByYn = y / yn; |
|||
float sqrtYbyYn = MathF.Sqrt(yByYn); |
|||
float l = 100 * sqrtYbyYn; |
|||
float a = ka * (((x / xn) - yByYn) / sqrtYbyYn); |
|||
float b = kb * ((yByYn - (z / zn)) / sqrtYbyYn); |
|||
|
|||
if (float.IsNaN(a)) |
|||
{ |
|||
a = 0; |
|||
} |
|||
|
|||
if (float.IsNaN(b)) |
|||
{ |
|||
b = 0; |
|||
} |
|||
|
|||
return new HunterLab(l, a, b); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<HunterLab> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieXyz xyz = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in xyz); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
// Conversion algorithm described here:
|
|||
// http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab
|
|||
CieXyz whitePoint = options.SourceWhitePoint; |
|||
float l = this.L, a = this.A, b = this.B; |
|||
float xn = whitePoint.X, yn = whitePoint.Y, zn = whitePoint.Z; |
|||
|
|||
float ka = ComputeKa(in whitePoint); |
|||
float kb = ComputeKb(in whitePoint); |
|||
|
|||
float pow = Numerics.Pow2(l / 100F); |
|||
float sqrtPow = MathF.Sqrt(pow); |
|||
float y = pow * yn; |
|||
|
|||
float x = (((a / ka) * sqrtPow) + pow) * xn; |
|||
float z = (((b / kb) * sqrtPow) - pow) * (-zn); |
|||
|
|||
return new CieXyz(x, y, z); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<HunterLab> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
HunterLab lab = source[i]; |
|||
destination[i] = lab.ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.WhitePoint; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"HunterLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is HunterLab other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(HunterLab other) |
|||
=> this.AsVector3Unsafe() == other.AsVector3Unsafe(); |
|||
|
|||
private Vector3 AsVector3Unsafe() => Unsafe.As<HunterLab, Vector3>(ref Unsafe.AsRef(in this)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static float ComputeKa(in CieXyz whitePoint) |
|||
{ |
|||
if (whitePoint.Equals(KnownIlluminants.C)) |
|||
{ |
|||
return 175F; |
|||
} |
|||
|
|||
return 100F * (175F / 198.04F) * (whitePoint.X + whitePoint.Y); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static float ComputeKb(in CieXyz whitePoint) |
|||
{ |
|||
if (whitePoint == KnownIlluminants.C) |
|||
{ |
|||
return 70F; |
|||
} |
|||
|
|||
return 100F * (70F / 218.11F) * (whitePoint.Y + whitePoint.Z); |
|||
} |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Defines the contract for all color profiles.
|
|||
/// </summary>
|
|||
public interface IColorProfile |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the chromatic adaption white point source.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="ChromaticAdaptionWhitePointSource"/>.</returns>
|
|||
public static abstract ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines the contract for all color profiles.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSelf">The type of color profile.</typeparam>
|
|||
public interface IColorProfile<TSelf> : IColorProfile, IEquatable<TSelf> |
|||
where TSelf : IColorProfile<TSelf> |
|||
{ |
|||
/// <summary>
|
|||
/// Expands the pixel into a generic ("scaled") <see cref="Vector4"/> representation
|
|||
/// with values scaled and clamped between <value>0</value> and <value>1</value>.
|
|||
/// The vector components are typically expanded in least to greatest significance order.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public Vector4 ToScaledVector4(); |
|||
|
|||
#pragma warning disable CA1000 // Do not declare static members on generic types
|
|||
/// <summary>
|
|||
/// Initializes the color instance from a generic a generic ("scaled") <see cref="Vector4"/> representation
|
|||
/// with values scaled and clamped between <value>0</value> and <value>1</value>.
|
|||
/// </summary>
|
|||
/// <param name="source">The vector to load the pixel from.</param>
|
|||
/// <returns>The <typeparamref name="TSelf"/>.</returns>
|
|||
public static abstract TSelf FromScaledVector4(Vector4 source); |
|||
|
|||
/// <summary>
|
|||
/// Converts the span of colors to a generic ("scaled") <see cref="Vector4"/> representation
|
|||
/// with values scaled and clamped between <value>0</value> and <value>1</value>.
|
|||
/// </summary>
|
|||
/// <param name="source">The color span to convert from.</param>
|
|||
/// <param name="destination">The vector span to write the results to.</param>
|
|||
public static abstract void ToScaledVector4(ReadOnlySpan<TSelf> source, Span<Vector4> destination); |
|||
|
|||
/// <summary>
|
|||
/// Converts the span of colors from a generic ("scaled") <see cref="Vector4"/> representation
|
|||
/// with values scaled and clamped between <value>0</value> and <value>1</value>.
|
|||
/// </summary>
|
|||
/// <param name="source">The vector span to convert from.</param>
|
|||
/// <param name="destination">The color span to write the results to.</param>
|
|||
public static abstract void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<TSelf> destination); |
|||
#pragma warning restore CA1000 // Do not declare static members on generic types
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Defines the contract for all color profiles.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSelf">The type of color profile.</typeparam>
|
|||
/// <typeparam name="TProfileSpace">The type of color profile connecting space.</typeparam>
|
|||
public interface IColorProfile<TSelf, TProfileSpace> : IColorProfile<TSelf> |
|||
where TSelf : IColorProfile<TSelf, TProfileSpace> |
|||
where TProfileSpace : struct, IProfileConnectingSpace |
|||
{ |
|||
#pragma warning disable CA1000 // Do not declare static members on generic types
|
|||
/// <summary>
|
|||
/// Initializes the color instance from the profile connection space.
|
|||
/// </summary>
|
|||
/// <param name="options">The color profile conversion options.</param>
|
|||
/// <param name="source">The color profile connecting space.</param>
|
|||
/// <returns>The <typeparamref name="TSelf"/>.</returns>
|
|||
public static abstract TSelf FromProfileConnectingSpace(ColorConversionOptions options, in TProfileSpace source); |
|||
|
|||
/// <summary>
|
|||
/// Converts the span of colors from the profile connection space.
|
|||
/// </summary>
|
|||
/// <param name="options">The color profile conversion options.</param>
|
|||
/// <param name="source">The color profile span to convert from.</param>
|
|||
/// <param name="destination">The color span to write the results to.</param>
|
|||
public static abstract void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<TProfileSpace> source, Span<TSelf> destination); |
|||
|
|||
/// <summary>
|
|||
/// Converts the color to the profile connection space.
|
|||
/// </summary>
|
|||
/// <param name="options">The color profile conversion options.</param>
|
|||
/// <returns>The <typeparamref name="TProfileSpace"/>.</returns>
|
|||
public TProfileSpace ToProfileConnectingSpace(ColorConversionOptions options); |
|||
|
|||
/// <summary>
|
|||
/// Converts the span of colors to the profile connection space.
|
|||
/// </summary>
|
|||
/// <param name="options">The color profile conversion options.</param>
|
|||
/// <param name="source">The color span to convert from.</param>
|
|||
/// <param name="destination">The color profile span to write the results to.</param>
|
|||
public static abstract void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<TSelf> source, Span<TProfileSpace> destination); |
|||
#pragma warning restore CA1000 // Do not declare static members on generic types
|
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Defines the contract for all color profile connection spaces.
|
|||
/// </summary>
|
|||
public interface IProfileConnectingSpace; |
|||
|
|||
/// <summary>
|
|||
/// Defines the contract for all color profile connection spaces.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSelf">The type of color profile.</typeparam>
|
|||
/// <typeparam name="TProfileSpace">The type of color profile connecting space.</typeparam>
|
|||
public interface IProfileConnectingSpace<TSelf, TProfileSpace> : IColorProfile<TSelf, TProfileSpace>, IProfileConnectingSpace |
|||
where TSelf : struct, IColorProfile<TSelf, TProfileSpace>, IProfileConnectingSpace |
|||
where TProfileSpace : struct, IProfileConnectingSpace; |
|||
@ -0,0 +1,506 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
|
|||
/// <summary>
|
|||
/// Implements interpolation methods for color profile lookup tables.
|
|||
/// Adapted from ICC Reference implementation:
|
|||
/// https://github.com/InternationalColorConsortium/DemoIccMAX/blob/79ecb74135ad47bac7d42692905a079839b7e105/IccProfLib/IccTagLut.cpp
|
|||
/// </summary>
|
|||
internal class ClutCalculator : IVector4Calculator |
|||
{ |
|||
private readonly int inputCount; |
|||
private readonly int outputCount; |
|||
private readonly float[] lut; |
|||
private readonly byte[] gridPointCount; |
|||
private readonly byte[] maxGridPoint; |
|||
private readonly int[] indexFactor; |
|||
private readonly int[] dimSize; |
|||
private readonly int nodeCount; |
|||
private readonly float[][] nodes; |
|||
private readonly float[] g; |
|||
private readonly uint[] ig; |
|||
private readonly float[] s; |
|||
private readonly float[] df; |
|||
private readonly uint[] nPower; |
|||
private int n000; |
|||
private int n001; |
|||
private int n010; |
|||
private int n011; |
|||
private int n100; |
|||
private int n101; |
|||
private int n110; |
|||
private int n111; |
|||
private int n1000; |
|||
|
|||
public ClutCalculator(IccClut clut) |
|||
{ |
|||
Guard.NotNull(clut, nameof(clut)); |
|||
Guard.MustBeGreaterThan(clut.InputChannelCount, 0, nameof(clut.InputChannelCount)); |
|||
Guard.MustBeGreaterThan(clut.OutputChannelCount, 0, nameof(clut.OutputChannelCount)); |
|||
|
|||
this.inputCount = clut.InputChannelCount; |
|||
this.outputCount = clut.OutputChannelCount; |
|||
this.g = new float[this.inputCount]; |
|||
this.ig = new uint[this.inputCount]; |
|||
this.s = new float[this.inputCount]; |
|||
this.nPower = new uint[16]; |
|||
this.lut = clut.Values; |
|||
this.nodeCount = (int)Math.Pow(2, clut.InputChannelCount); |
|||
this.df = new float[this.nodeCount]; |
|||
this.nodes = new float[this.nodeCount][]; |
|||
this.dimSize = new int[this.inputCount]; |
|||
this.gridPointCount = clut.GridPointCount; |
|||
this.maxGridPoint = new byte[this.inputCount]; |
|||
for (int i = 0; i < this.inputCount; i++) |
|||
{ |
|||
this.maxGridPoint[i] = (byte)(this.gridPointCount[i] - 1); |
|||
} |
|||
|
|||
this.dimSize[this.inputCount - 1] = this.outputCount; |
|||
for (int i = this.inputCount - 2; i >= 0; i--) |
|||
{ |
|||
this.dimSize[i] = this.dimSize[i + 1] * this.gridPointCount[i + 1]; |
|||
} |
|||
|
|||
this.indexFactor = this.CalculateIndexFactor(); |
|||
} |
|||
|
|||
public unsafe Vector4 Calculate(Vector4 value) |
|||
{ |
|||
Vector4 result = default; |
|||
switch (this.inputCount) |
|||
{ |
|||
case 1: |
|||
this.Interpolate1d((float*)&value, (float*)&result); |
|||
break; |
|||
case 2: |
|||
this.Interpolate2d((float*)&value, (float*)&result); |
|||
break; |
|||
case 3: |
|||
this.Interpolate3d((float*)&value, (float*)&result); |
|||
break; |
|||
case 4: |
|||
this.Interpolate4d((float*)&value, (float*)&result); |
|||
break; |
|||
default: |
|||
this.InterpolateNd((float*)&value, (float*)&result); |
|||
break; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private int[] CalculateIndexFactor() |
|||
{ |
|||
int[] factors = new int[16]; |
|||
switch (this.inputCount) |
|||
{ |
|||
case 1: |
|||
factors[0] = this.n000 = 0; |
|||
factors[1] = this.n001 = this.dimSize[0]; |
|||
break; |
|||
case 2: |
|||
factors[0] = this.n000 = 0; |
|||
factors[1] = this.n001 = this.dimSize[0]; |
|||
factors[2] = this.n010 = this.dimSize[1]; |
|||
factors[3] = this.n011 = this.n001 + this.n010; |
|||
break; |
|||
case 3: |
|||
factors[0] = this.n000 = 0; |
|||
factors[1] = this.n001 = this.dimSize[0]; |
|||
factors[2] = this.n010 = this.dimSize[1]; |
|||
factors[3] = this.n011 = this.n001 + this.n010; |
|||
factors[4] = this.n100 = this.dimSize[2]; |
|||
factors[5] = this.n101 = this.n100 + this.n001; |
|||
factors[6] = this.n110 = this.n100 + this.n010; |
|||
factors[7] = this.n111 = this.n110 + this.n001; |
|||
break; |
|||
case 4: |
|||
factors[0] = 0; |
|||
factors[1] = this.n001 = this.dimSize[0]; |
|||
factors[2] = this.n010 = this.dimSize[1]; |
|||
factors[3] = factors[2] + factors[1]; |
|||
factors[4] = this.n100 = this.dimSize[2]; |
|||
factors[5] = factors[4] + factors[1]; |
|||
factors[6] = factors[4] + factors[2]; |
|||
factors[7] = factors[4] + factors[3]; |
|||
factors[8] = this.n1000 = this.dimSize[3]; |
|||
factors[9] = factors[8] + factors[1]; |
|||
factors[10] = factors[8] + factors[2]; |
|||
factors[11] = factors[8] + factors[3]; |
|||
factors[12] = factors[8] + factors[4]; |
|||
factors[13] = factors[8] + factors[5]; |
|||
factors[14] = factors[8] + factors[6]; |
|||
factors[15] = factors[8] + factors[7]; |
|||
break; |
|||
default: |
|||
// Initialize ND interpolation variables.
|
|||
factors[0] = 0; |
|||
int count; |
|||
for (count = 0; count < this.inputCount; count++) |
|||
{ |
|||
this.nPower[count] = (uint)(1 << (this.inputCount - 1 - count)); |
|||
} |
|||
|
|||
uint[] nPower = [0, 1]; |
|||
count = 0; |
|||
int nFlag = 1; |
|||
for (uint j = 1; j < this.nodeCount; j++) |
|||
{ |
|||
if (j == nPower[1]) |
|||
{ |
|||
factors[j] = this.dimSize[count]; |
|||
nPower[0] = (uint)(1 << count); |
|||
count++; |
|||
nPower[1] = (uint)(1 << count); |
|||
nFlag = 1; |
|||
} |
|||
else |
|||
{ |
|||
factors[j] = factors[nPower[0]] + factors[nFlag]; |
|||
nFlag++; |
|||
} |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
return factors; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// One dimensional interpolation function.
|
|||
/// </summary>
|
|||
/// <param name="srcPixel">The input pixel values, which will be interpolated.</param>
|
|||
/// <param name="destPixel">The interpolated output pixels.</param>
|
|||
private unsafe void Interpolate1d(float* srcPixel, float* destPixel) |
|||
{ |
|||
byte mx = this.maxGridPoint[0]; |
|||
|
|||
float x = UnitClip(srcPixel[0]) * mx; |
|||
|
|||
uint ix = (uint)x; |
|||
|
|||
float u = x - ix; |
|||
|
|||
if (ix == mx) |
|||
{ |
|||
ix--; |
|||
u = 1.0f; |
|||
} |
|||
|
|||
float nu = (float)(1.0 - u); |
|||
|
|||
int i; |
|||
Span<float> p = this.lut.AsSpan((int)(ix * this.n001)); |
|||
|
|||
// Normalize grid units.
|
|||
float dF0 = nu; |
|||
float dF1 = u; |
|||
|
|||
int offset = 0; |
|||
for (i = 0; i < this.outputCount; i++) |
|||
{ |
|||
destPixel[i] = (float)((p[offset + this.n000] * dF0) + (p[offset + this.n001] * dF1)); |
|||
offset++; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Two dimensional interpolation function.
|
|||
/// </summary>
|
|||
/// <param name="srcPixel">The input pixel values, which will be interpolated.</param>
|
|||
/// <param name="destPixel">The interpolated output pixels.</param>
|
|||
private unsafe void Interpolate2d(float* srcPixel, float* destPixel) |
|||
{ |
|||
byte mx = this.maxGridPoint[0]; |
|||
byte my = this.maxGridPoint[1]; |
|||
|
|||
float x = UnitClip(srcPixel[0]) * mx; |
|||
float y = UnitClip(srcPixel[1]) * my; |
|||
|
|||
uint ix = (uint)x; |
|||
uint iy = (uint)y; |
|||
|
|||
float u = x - ix; |
|||
float t = y - iy; |
|||
|
|||
if (ix == mx) |
|||
{ |
|||
ix--; |
|||
u = 1.0f; |
|||
} |
|||
|
|||
if (iy == my) |
|||
{ |
|||
iy--; |
|||
t = 1.0f; |
|||
} |
|||
|
|||
float nt = (float)(1.0 - t); |
|||
float nu = (float)(1.0 - u); |
|||
|
|||
int i; |
|||
Span<float> p = this.lut.AsSpan((int)((ix * this.n001) + (iy * this.n010))); |
|||
|
|||
// Normalize grid units.
|
|||
float dF0 = nt * nu; |
|||
float dF1 = nt * u; |
|||
float dF2 = t * nu; |
|||
float dF3 = t * u; |
|||
|
|||
int offset = 0; |
|||
for (i = 0; i < this.outputCount; i++) |
|||
{ |
|||
destPixel[i] = (float)((p[offset + this.n000] * dF0) + (p[offset + this.n001] * dF1) + (p[offset + this.n010] * dF2) + (p[offset + this.n011] * dF3)); |
|||
offset++; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Three dimensional interpolation function.
|
|||
/// </summary>
|
|||
/// <param name="srcPixel">The input pixel values, which will be interpolated.</param>
|
|||
/// <param name="destPixel">The interpolated output pixels.</param>
|
|||
private unsafe void Interpolate3d(float* srcPixel, float* destPixel) |
|||
{ |
|||
byte mx = this.maxGridPoint[0]; |
|||
byte my = this.maxGridPoint[1]; |
|||
byte mz = this.maxGridPoint[2]; |
|||
|
|||
float x = UnitClip(srcPixel[0]) * mx; |
|||
float y = UnitClip(srcPixel[1]) * my; |
|||
float z = UnitClip(srcPixel[2]) * mz; |
|||
|
|||
uint ix = (uint)x; |
|||
uint iy = (uint)y; |
|||
uint iz = (uint)z; |
|||
|
|||
float u = x - ix; |
|||
float t = y - iy; |
|||
float s = z - iz; |
|||
|
|||
if (ix == mx) |
|||
{ |
|||
ix--; |
|||
u = 1.0f; |
|||
} |
|||
|
|||
if (iy == my) |
|||
{ |
|||
iy--; |
|||
t = 1.0f; |
|||
} |
|||
|
|||
if (iz == mz) |
|||
{ |
|||
iz--; |
|||
s = 1.0f; |
|||
} |
|||
|
|||
float ns = (float)(1.0 - s); |
|||
float nt = (float)(1.0 - t); |
|||
float nu = (float)(1.0 - u); |
|||
|
|||
Span<float> p = this.lut.AsSpan((int)((ix * this.n001) + (iy * this.n010) + (iz * this.n100))); |
|||
|
|||
// Normalize grid units
|
|||
float dF0 = ns * nt * nu; |
|||
float dF1 = ns * nt * u; |
|||
float dF2 = ns * t * nu; |
|||
float dF3 = ns * t * u; |
|||
float dF4 = s * nt * nu; |
|||
float dF5 = s * nt * u; |
|||
float dF6 = s * t * nu; |
|||
float dF7 = s * t * u; |
|||
|
|||
int offset = 0; |
|||
for (int i = 0; i < this.outputCount; i++) |
|||
{ |
|||
destPixel[i] = (float)((p[offset + this.n000] * dF0) + |
|||
(p[offset + this.n001] * dF1) + |
|||
(p[offset + this.n010] * dF2) + |
|||
(p[offset + this.n011] * dF3) + |
|||
(p[offset + this.n100] * dF4) + |
|||
(p[offset + this.n101] * dF5) + |
|||
(p[offset + this.n110] * dF6) + |
|||
(p[offset + this.n111] * dF7)); |
|||
offset++; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Four dimensional interpolation function.
|
|||
/// </summary>
|
|||
/// <param name="srcPixel">The input pixel values, which will be interpolated.</param>
|
|||
/// <param name="destPixel">The interpolated output pixels.</param>
|
|||
private unsafe void Interpolate4d(float* srcPixel, float* destPixel) |
|||
{ |
|||
byte mw = this.maxGridPoint[0]; |
|||
byte mx = this.maxGridPoint[1]; |
|||
byte my = this.maxGridPoint[2]; |
|||
byte mz = this.maxGridPoint[3]; |
|||
|
|||
float w = UnitClip(srcPixel[0]) * mw; |
|||
float x = UnitClip(srcPixel[1]) * mx; |
|||
float y = UnitClip(srcPixel[2]) * my; |
|||
float z = UnitClip(srcPixel[3]) * mz; |
|||
|
|||
uint iw = (uint)w; |
|||
uint ix = (uint)x; |
|||
uint iy = (uint)y; |
|||
uint iz = (uint)z; |
|||
|
|||
float v = w - iw; |
|||
float u = x - ix; |
|||
float t = y - iy; |
|||
float s = z - iz; |
|||
|
|||
if (iw == mw) |
|||
{ |
|||
iw--; |
|||
v = 1.0f; |
|||
} |
|||
|
|||
if (ix == mx) |
|||
{ |
|||
ix--; |
|||
u = 1.0f; |
|||
} |
|||
|
|||
if (iy == my) |
|||
{ |
|||
iy--; |
|||
t = 1.0f; |
|||
} |
|||
|
|||
if (iz == mz) |
|||
{ |
|||
iz--; |
|||
s = 1.0f; |
|||
} |
|||
|
|||
float ns = (float)(1.0 - s); |
|||
float nt = (float)(1.0 - t); |
|||
float nu = (float)(1.0 - u); |
|||
float nv = (float)(1.0 - v); |
|||
|
|||
Span<float> p = this.lut.AsSpan((int)((iw * this.n001) + (ix * this.n010) + (iy * this.n100) + (iz * this.n1000))); |
|||
|
|||
// Normalize grid units.
|
|||
float[] dF = |
|||
[ |
|||
ns * nt * nu * nv, |
|||
ns * nt * nu * v, |
|||
ns * nt * u * nv, |
|||
ns * nt * u * v, |
|||
ns * t * nu * nv, |
|||
ns * t * nu * v, |
|||
ns * t * u * nv, |
|||
ns * t * u * v, |
|||
s * nt * nu * nv, |
|||
s * nt * nu * v, |
|||
s * nt * u * nv, |
|||
s * nt * u * v, |
|||
s * t * nu * nv, |
|||
s * t * nu * v, |
|||
s * t * u * nv, |
|||
s * t * u * v, |
|||
]; |
|||
|
|||
int offset = 0; |
|||
for (int i = 0; i < this.outputCount; i++) |
|||
{ |
|||
float pv = 0.0f; |
|||
for (int j = 0; j < 16; j++) |
|||
{ |
|||
pv += p[offset + this.indexFactor[j]] * dF[j]; |
|||
} |
|||
|
|||
destPixel[i] = pv; |
|||
offset++; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Generic N-dimensional interpolation function.
|
|||
/// </summary>
|
|||
/// <param name="srcPixel">The input pixel values, which will be interpolated.</param>
|
|||
/// <param name="destPixel">The interpolated output pixels.</param>
|
|||
private unsafe void InterpolateNd(float* srcPixel, float* destPixel) |
|||
{ |
|||
int index = 0; |
|||
for (int i = 0; i < this.inputCount; i++) |
|||
{ |
|||
this.g[i] = UnitClip(srcPixel[i]) * this.maxGridPoint[i]; |
|||
this.ig[i] = (uint)this.g[i]; |
|||
this.s[this.inputCount - 1 - i] = this.g[i] - this.ig[i]; |
|||
if (this.ig[i] == this.maxGridPoint[i]) |
|||
{ |
|||
this.ig[i]--; |
|||
this.s[this.inputCount - 1 - i] = 1.0f; |
|||
} |
|||
|
|||
index += (int)this.ig[i] * this.dimSize[i]; |
|||
} |
|||
|
|||
Span<float> p = this.lut.AsSpan(index); |
|||
float[] temp = new float[2]; |
|||
bool nFlag = false; |
|||
|
|||
for (int i = 0; i < this.nodeCount; i++) |
|||
{ |
|||
this.df[i] = 1.0f; |
|||
} |
|||
|
|||
for (int i = 0; i < this.inputCount; i++) |
|||
{ |
|||
temp[0] = 1.0f - this.s[i]; |
|||
temp[1] = this.s[i]; |
|||
index = (int)this.nPower[i]; |
|||
for (int j = 0; j < this.nodeCount; j++) |
|||
{ |
|||
this.df[j] *= temp[nFlag ? 1 : 0]; |
|||
if ((j + 1) % index == 0) |
|||
{ |
|||
nFlag = !nFlag; |
|||
} |
|||
} |
|||
|
|||
nFlag = false; |
|||
} |
|||
|
|||
int offset = 0; |
|||
for (int i = 0; i < this.outputCount; i++) |
|||
{ |
|||
float pv = 0; |
|||
for (int j = 0; j < this.nodeCount; j++) |
|||
{ |
|||
pv += p[offset + this.indexFactor[j]] * this.df[j]; |
|||
} |
|||
|
|||
destPixel[i] = pv; |
|||
offset++; |
|||
} |
|||
} |
|||
|
|||
private static float UnitClip(float v) |
|||
{ |
|||
if (v < 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
if (v > 1.0) |
|||
{ |
|||
return 1.0f; |
|||
} |
|||
|
|||
return v; |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
|
|||
internal class ColorTrcCalculator : IVector4Calculator |
|||
{ |
|||
private readonly TrcCalculator curveCalculator; |
|||
private readonly Matrix4x4 matrix; |
|||
private readonly bool toPcs; |
|||
|
|||
public ColorTrcCalculator( |
|||
IccXyzTagDataEntry redMatrixColumn, |
|||
IccXyzTagDataEntry greenMatrixColumn, |
|||
IccXyzTagDataEntry blueMatrixColumn, |
|||
IccTagDataEntry redTrc, |
|||
IccTagDataEntry greenTrc, |
|||
IccTagDataEntry blueTrc, |
|||
bool toPcs) |
|||
{ |
|||
this.toPcs = toPcs; |
|||
this.curveCalculator = new TrcCalculator([redTrc, greenTrc, blueTrc], !toPcs); |
|||
|
|||
Vector3 mr = redMatrixColumn.Data[0]; |
|||
Vector3 mg = greenMatrixColumn.Data[0]; |
|||
Vector3 mb = blueMatrixColumn.Data[0]; |
|||
this.matrix = new Matrix4x4(mr.X, mr.Y, mr.Z, 0, mg.X, mg.Y, mg.Z, 0, mb.X, mb.Y, mb.Z, 0, 0, 0, 0, 1); |
|||
|
|||
if (!toPcs) |
|||
{ |
|||
Matrix4x4.Invert(this.matrix, out this.matrix); |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector4 Calculate(Vector4 value) |
|||
{ |
|||
if (this.toPcs) |
|||
{ |
|||
// input is always linear RGB
|
|||
value = this.curveCalculator.Calculate(value); |
|||
CieXyz xyz = new(Vector4.Transform(value, this.matrix).AsVector3()); |
|||
|
|||
// when data to PCS, output from calculator is descaled XYZ
|
|||
// but downstream process requires scaled XYZ
|
|||
// (see DemoMaxICC IccCmm.cpp : CIccXformMatrixTRC::Apply)
|
|||
return xyz.ToScaledVector4(); |
|||
} |
|||
else |
|||
{ |
|||
// input is always XYZ
|
|||
Vector4 xyz = Vector4.Transform(value, this.matrix); |
|||
|
|||
// when data to PCS, upstream process provides scaled XYZ
|
|||
// but input to calculator is descaled XYZ
|
|||
// (see DemoMaxICC IccCmm.cpp : CIccXformMatrixTRC::Apply)
|
|||
xyz = new Vector4(CieXyz.FromScaledVector4(xyz).AsVector3Unsafe(), 1); |
|||
return this.curveCalculator.Calculate(xyz); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
|
|||
internal partial class CurveCalculator |
|||
{ |
|||
private enum CalculationType |
|||
{ |
|||
Identity, |
|||
Gamma, |
|||
Lut, |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
#nullable disable |
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
|
|||
internal partial class CurveCalculator : ISingleCalculator |
|||
{ |
|||
private readonly LutCalculator lutCalculator; |
|||
private readonly float gamma; |
|||
private readonly CalculationType type; |
|||
|
|||
public CurveCalculator(IccCurveTagDataEntry entry, bool inverted) |
|||
{ |
|||
if (entry.IsIdentityResponse) |
|||
{ |
|||
this.type = CalculationType.Identity; |
|||
} |
|||
else if (entry.IsGamma) |
|||
{ |
|||
this.gamma = entry.Gamma; |
|||
if (inverted) |
|||
{ |
|||
this.gamma = 1f / this.gamma; |
|||
} |
|||
|
|||
this.type = CalculationType.Gamma; |
|||
} |
|||
else |
|||
{ |
|||
this.lutCalculator = new LutCalculator(entry.CurveData, inverted); |
|||
this.type = CalculationType.Lut; |
|||
} |
|||
} |
|||
|
|||
public float Calculate(float value) |
|||
=> this.type switch |
|||
{ |
|||
CalculationType.Identity => value, |
|||
CalculationType.Gamma => MathF.Pow(value, this.gamma), // TODO: This could be optimized using a LUT. See SrgbCompanding
|
|||
CalculationType.Lut => this.lutCalculator.Calculate(value), |
|||
_ => throw new InvalidOperationException("Invalid calculation type"), |
|||
}; |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
|
|||
internal class GrayTrcCalculator : IVector4Calculator |
|||
{ |
|||
private readonly TrcCalculator calculator; |
|||
|
|||
public GrayTrcCalculator(IccTagDataEntry grayTrc, bool toPcs) |
|||
=> this.calculator = new TrcCalculator(new IccTagDataEntry[] { grayTrc }, !toPcs); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector4 Calculate(Vector4 value) => this.calculator.Calculate(value); |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
|
|||
/// <summary>
|
|||
/// Represents an ICC calculator with a single floating point value and result
|
|||
/// </summary>
|
|||
internal interface ISingleCalculator |
|||
{ |
|||
/// <summary>
|
|||
/// Calculates a result from the given value
|
|||
/// </summary>
|
|||
/// <param name="value">The input value</param>
|
|||
/// <returns>The calculated result</returns>
|
|||
float Calculate(float value); |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
|
|||
/// <summary>
|
|||
/// Represents an ICC calculator with <see cref="Vector4"/> values and results
|
|||
/// </summary>
|
|||
internal interface IVector4Calculator |
|||
{ |
|||
/// <summary>
|
|||
/// Calculates a result from the given values
|
|||
/// </summary>
|
|||
/// <param name="value">The input values</param>
|
|||
/// <returns>The calculated result</returns>
|
|||
Vector4 Calculate(Vector4 value); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
|
|||
internal partial class LutABCalculator |
|||
{ |
|||
/// <summary>
|
|||
/// Identifies the transform direction for the configured LUT calculator.
|
|||
/// </summary>
|
|||
private enum CalculationType |
|||
{ |
|||
/// <summary>
|
|||
/// Converts from device space to PCS using ICC <c>mAB</c> stage order.
|
|||
/// </summary>
|
|||
AtoB, |
|||
|
|||
/// <summary>
|
|||
/// Converts from PCS to device space using ICC <c>mBA</c> stage order.
|
|||
/// </summary>
|
|||
BtoA, |
|||
} |
|||
} |
|||
@ -0,0 +1,158 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
#nullable disable |
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
|
|||
internal partial class LutABCalculator : IVector4Calculator |
|||
{ |
|||
private CalculationType type; |
|||
private TrcCalculator curveACalculator; |
|||
private TrcCalculator curveBCalculator; |
|||
private TrcCalculator curveMCalculator; |
|||
private MatrixCalculator matrixCalculator; |
|||
private ClutCalculator clutCalculator; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LutABCalculator"/> class for an ICC <c>mAB</c> transform.
|
|||
/// </summary>
|
|||
/// <param name="entry">The parsed A-to-B LUT entry.</param>
|
|||
public LutABCalculator(IccLutAToBTagDataEntry entry) |
|||
{ |
|||
Guard.NotNull(entry, nameof(entry)); |
|||
this.Init(entry.CurveA, entry.CurveB, entry.CurveM, entry.Matrix3x1, entry.Matrix3x3, entry.ClutValues); |
|||
this.type = CalculationType.AtoB; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LutABCalculator"/> class for an ICC <c>mBA</c> transform.
|
|||
/// </summary>
|
|||
/// <param name="entry">The parsed B-to-A LUT entry.</param>
|
|||
public LutABCalculator(IccLutBToATagDataEntry entry) |
|||
{ |
|||
Guard.NotNull(entry, nameof(entry)); |
|||
this.Init(entry.CurveA, entry.CurveB, entry.CurveM, entry.Matrix3x1, entry.Matrix3x3, entry.ClutValues); |
|||
this.type = CalculationType.BtoA; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates the transformed value by applying the configured ICC LUT stages in specification order.
|
|||
/// </summary>
|
|||
/// <param name="value">The input value.</param>
|
|||
/// <returns>The transformed value.</returns>
|
|||
public Vector4 Calculate(Vector4 value) |
|||
{ |
|||
switch (this.type) |
|||
{ |
|||
case CalculationType.AtoB: |
|||
// ICC mAB order: A, CLUT, M, Matrix, B.
|
|||
if (this.curveACalculator != null) |
|||
{ |
|||
value = this.curveACalculator.Calculate(value); |
|||
} |
|||
|
|||
if (this.clutCalculator != null) |
|||
{ |
|||
value = this.clutCalculator.Calculate(value); |
|||
} |
|||
|
|||
if (this.curveMCalculator != null) |
|||
{ |
|||
value = this.curveMCalculator.Calculate(value); |
|||
} |
|||
|
|||
if (this.matrixCalculator != null) |
|||
{ |
|||
value = this.matrixCalculator.Calculate(value); |
|||
} |
|||
|
|||
if (this.curveBCalculator != null) |
|||
{ |
|||
value = this.curveBCalculator.Calculate(value); |
|||
} |
|||
|
|||
return value; |
|||
|
|||
case CalculationType.BtoA: |
|||
// ICC mBA order: B, Matrix, M, CLUT, A.
|
|||
if (this.curveBCalculator != null) |
|||
{ |
|||
value = this.curveBCalculator.Calculate(value); |
|||
} |
|||
|
|||
if (this.matrixCalculator != null) |
|||
{ |
|||
value = this.matrixCalculator.Calculate(value); |
|||
} |
|||
|
|||
if (this.curveMCalculator != null) |
|||
{ |
|||
value = this.curveMCalculator.Calculate(value); |
|||
} |
|||
|
|||
if (this.clutCalculator != null) |
|||
{ |
|||
value = this.clutCalculator.Calculate(value); |
|||
} |
|||
|
|||
if (this.curveACalculator != null) |
|||
{ |
|||
value = this.curveACalculator.Calculate(value); |
|||
} |
|||
|
|||
return value; |
|||
|
|||
default: |
|||
throw new InvalidOperationException("Invalid calculation type"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates calculators for the processing stages present in the LUT entry.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The tag entry classes already validate channel continuity, so this method only materializes the available stages.
|
|||
/// </remarks>
|
|||
private void Init(IccTagDataEntry[] curveA, IccTagDataEntry[] curveB, IccTagDataEntry[] curveM, Vector3? matrix3x1, Matrix4x4? matrix3x3, IccClut clut) |
|||
{ |
|||
bool hasACurve = curveA != null; |
|||
bool hasBCurve = curveB != null; |
|||
bool hasMCurve = curveM != null; |
|||
bool hasMatrix = matrix3x1 != null && matrix3x3 != null; |
|||
bool hasClut = clut != null; |
|||
|
|||
Guard.IsTrue( |
|||
hasACurve || hasBCurve || hasMCurve || hasMatrix || hasClut, |
|||
"entry", |
|||
"AToB or BToA tag must contain at least one processing element"); |
|||
|
|||
if (hasACurve) |
|||
{ |
|||
this.curveACalculator = new TrcCalculator(curveA, false); |
|||
} |
|||
|
|||
if (hasBCurve) |
|||
{ |
|||
this.curveBCalculator = new TrcCalculator(curveB, false); |
|||
} |
|||
|
|||
if (hasMCurve) |
|||
{ |
|||
this.curveMCalculator = new TrcCalculator(curveM, false); |
|||
} |
|||
|
|||
if (hasMatrix) |
|||
{ |
|||
this.matrixCalculator = new MatrixCalculator(matrix3x3.Value, matrix3x1.Value); |
|||
} |
|||
|
|||
if (hasClut) |
|||
{ |
|||
this.clutCalculator = new ClutCalculator(clut); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
|
|||
internal class LutCalculator : ISingleCalculator |
|||
{ |
|||
private readonly float[] lut; |
|||
private readonly bool inverse; |
|||
|
|||
public LutCalculator(float[] lut, bool inverse) |
|||
{ |
|||
Guard.NotNull(lut, nameof(lut)); |
|||
|
|||
this.lut = lut; |
|||
this.inverse = inverse; |
|||
} |
|||
|
|||
public float Calculate(float value) |
|||
{ |
|||
if (this.inverse) |
|||
{ |
|||
return this.LookupInverse(value); |
|||
} |
|||
|
|||
return this.Lookup(value); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private float Lookup(float value) |
|||
{ |
|||
value = Math.Max(value, 0); |
|||
|
|||
float factor = value * (this.lut.Length - 1); |
|||
int index = (int)factor; |
|||
float low = this.lut[index]; |
|||
|
|||
float high = 1F; |
|||
if (index < this.lut.Length - 1) |
|||
{ |
|||
high = this.lut[index + 1]; |
|||
} |
|||
|
|||
return low + ((high - low) * (factor - index)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private float LookupInverse(float value) |
|||
{ |
|||
int index = Array.BinarySearch(this.lut, value); |
|||
if (index >= 0) |
|||
{ |
|||
return index / (float)(this.lut.Length - 1); |
|||
} |
|||
|
|||
index = ~index; |
|||
if (index == 0) |
|||
{ |
|||
return 0; |
|||
} |
|||
else if (index == this.lut.Length) |
|||
{ |
|||
return 1; |
|||
} |
|||
|
|||
float high = this.lut[index]; |
|||
float low = this.lut[index - 1]; |
|||
|
|||
float valuePercent = (value - low) / (high - low); |
|||
float lutRange = 1 / (float)(this.lut.Length - 1); |
|||
float lutLow = (index - 1) / (float)(this.lut.Length - 1); |
|||
|
|||
return lutLow + (valuePercent * lutRange); |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
#nullable disable |
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
|
|||
internal class LutEntryCalculator : IVector4Calculator |
|||
{ |
|||
private LutCalculator[] inputCurve; |
|||
private LutCalculator[] outputCurve; |
|||
private ClutCalculator clutCalculator; |
|||
private Matrix4x4 matrix; |
|||
private bool doTransform; |
|||
|
|||
public LutEntryCalculator(IccLut8TagDataEntry lut) |
|||
{ |
|||
Guard.NotNull(lut, nameof(lut)); |
|||
this.Init(lut.InputValues, lut.OutputValues, lut.ClutValues, lut.Matrix); |
|||
this.Is16Bit = false; |
|||
} |
|||
|
|||
public LutEntryCalculator(IccLut16TagDataEntry lut) |
|||
{ |
|||
Guard.NotNull(lut, nameof(lut)); |
|||
this.Init(lut.InputValues, lut.OutputValues, lut.ClutValues, lut.Matrix); |
|||
this.Is16Bit = true; |
|||
} |
|||
|
|||
internal bool Is16Bit { get; } |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector4 Calculate(Vector4 value) |
|||
{ |
|||
if (this.doTransform) |
|||
{ |
|||
value = Vector4.Transform(value, this.matrix); |
|||
} |
|||
|
|||
value = CalculateLut(this.inputCurve, value); |
|||
value = this.clutCalculator.Calculate(value); |
|||
return CalculateLut(this.outputCurve, value); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 CalculateLut(LutCalculator[] lut, Vector4 value) |
|||
{ |
|||
ref float f = ref Unsafe.As<Vector4, float>(ref value); |
|||
for (int i = 0; i < lut.Length; i++) |
|||
{ |
|||
Unsafe.Add(ref f, i) = lut[i].Calculate(Unsafe.Add(ref f, i)); |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
private void Init(IccLut[] inputCurve, IccLut[] outputCurve, IccClut clut, Matrix4x4 matrix) |
|||
{ |
|||
this.inputCurve = InitLut(inputCurve); |
|||
this.outputCurve = InitLut(outputCurve); |
|||
this.clutCalculator = new ClutCalculator(clut); |
|||
this.matrix = matrix; |
|||
|
|||
this.doTransform = !matrix.IsIdentity && inputCurve.Length == 3; |
|||
} |
|||
|
|||
private static LutCalculator[] InitLut(IccLut[] curves) |
|||
{ |
|||
LutCalculator[] calculators = new LutCalculator[curves.Length]; |
|||
for (int i = 0; i < curves.Length; i++) |
|||
{ |
|||
calculators[i] = new LutCalculator(curves[i].Values, false); |
|||
} |
|||
|
|||
return calculators; |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
|
|||
internal class MatrixCalculator : IVector4Calculator |
|||
{ |
|||
private Matrix4x4 matrix2D; |
|||
private Vector4 matrix1D; |
|||
|
|||
public MatrixCalculator(Matrix4x4 matrix3x3, Vector3 matrix3x1) |
|||
{ |
|||
this.matrix2D = matrix3x3; |
|||
this.matrix1D = new Vector4(matrix3x1, 0); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector4 Calculate(Vector4 value) |
|||
{ |
|||
Vector4 transformed = Vector4.Transform(value, this.matrix2D); |
|||
return Vector4.Add(this.matrix1D, transformed); |
|||
} |
|||
} |
|||
@ -0,0 +1,130 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
|
|||
internal class ParametricCurveCalculator : ISingleCalculator |
|||
{ |
|||
private readonly IccParametricCurve curve; |
|||
private readonly IccParametricCurveType type; |
|||
private const IccParametricCurveType InvertedFlag = (IccParametricCurveType)(1 << 3); |
|||
|
|||
public ParametricCurveCalculator(IccParametricCurveTagDataEntry entry, bool inverted) |
|||
{ |
|||
Guard.NotNull(entry, nameof(entry)); |
|||
this.curve = entry.Curve; |
|||
this.type = entry.Curve.Type; |
|||
|
|||
if (inverted) |
|||
{ |
|||
this.type |= InvertedFlag; |
|||
} |
|||
} |
|||
|
|||
public float Calculate(float value) |
|||
=> this.type switch |
|||
{ |
|||
IccParametricCurveType.Type1 => this.CalculateGamma(value), |
|||
IccParametricCurveType.Cie122_1996 => this.CalculateCie122(value), |
|||
IccParametricCurveType.Iec61966_3 => this.CalculateIec61966(value), |
|||
IccParametricCurveType.SRgb => this.CalculateSRgb(value), |
|||
IccParametricCurveType.Type5 => this.CalculateType5(value), |
|||
IccParametricCurveType.Type1 | InvertedFlag => this.CalculateInvertedGamma(value), |
|||
IccParametricCurveType.Cie122_1996 | InvertedFlag => this.CalculateInvertedCie122(value), |
|||
IccParametricCurveType.Iec61966_3 | InvertedFlag => this.CalculateInvertedIec61966(value), |
|||
IccParametricCurveType.SRgb | InvertedFlag => this.CalculateInvertedSRgb(value), |
|||
IccParametricCurveType.Type5 | InvertedFlag => this.CalculateInvertedType5(value), |
|||
_ => throw new InvalidIccProfileException("ParametricCurve"), |
|||
}; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private float CalculateGamma(float value) => MathF.Pow(value, this.curve.G); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private float CalculateCie122(float value) |
|||
{ |
|||
if (value >= -this.curve.B / this.curve.A) |
|||
{ |
|||
return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G); |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private float CalculateIec61966(float value) |
|||
{ |
|||
if (value >= -this.curve.B / this.curve.A) |
|||
{ |
|||
return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G) + this.curve.C; |
|||
} |
|||
|
|||
return this.curve.C; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private float CalculateSRgb(float value) |
|||
{ |
|||
if (value >= this.curve.D) |
|||
{ |
|||
return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G); |
|||
} |
|||
|
|||
return this.curve.C * value; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private float CalculateType5(float value) |
|||
{ |
|||
if (value >= this.curve.D) |
|||
{ |
|||
return MathF.Pow((this.curve.A * value) + this.curve.B, this.curve.G) + this.curve.E; |
|||
} |
|||
|
|||
return (this.curve.C * value) + this.curve.F; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private float CalculateInvertedGamma(float value) |
|||
=> MathF.Pow(value, 1 / this.curve.G); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private float CalculateInvertedCie122(float value) |
|||
=> (MathF.Pow(value, 1 / this.curve.G) - this.curve.B) / this.curve.A; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private float CalculateInvertedIec61966(float value) |
|||
{ |
|||
if (value >= this.curve.C) |
|||
{ |
|||
return (MathF.Pow(value - this.curve.C, 1 / this.curve.G) - this.curve.B) / this.curve.A; |
|||
} |
|||
|
|||
return -this.curve.B / this.curve.A; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private float CalculateInvertedSRgb(float value) |
|||
{ |
|||
if (value >= MathF.Pow((this.curve.A * this.curve.D) + this.curve.B, this.curve.G)) |
|||
{ |
|||
return (MathF.Pow(value, 1 / this.curve.G) - this.curve.B) / this.curve.A; |
|||
} |
|||
|
|||
return value / this.curve.C; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private float CalculateInvertedType5(float value) |
|||
{ |
|||
if (value >= (this.curve.C * this.curve.D) + this.curve.F) |
|||
{ |
|||
return (MathF.Pow(value - this.curve.E, 1 / this.curve.G) - this.curve.B) / this.curve.A; |
|||
} |
|||
|
|||
return (value - this.curve.F) / this.curve.C; |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
|
|||
internal class TrcCalculator : IVector4Calculator |
|||
{ |
|||
private readonly ISingleCalculator[] calculators; |
|||
|
|||
public TrcCalculator(IccTagDataEntry[] entries, bool inverted) |
|||
{ |
|||
Guard.NotNull(entries, nameof(entries)); |
|||
|
|||
this.calculators = new ISingleCalculator[entries.Length]; |
|||
for (int i = 0; i < entries.Length; i++) |
|||
{ |
|||
this.calculators[i] = entries[i] switch |
|||
{ |
|||
IccCurveTagDataEntry curve => new CurveCalculator(curve, inverted), |
|||
IccParametricCurveTagDataEntry parametricCurve => new ParametricCurveCalculator(parametricCurve, inverted), |
|||
_ => throw new InvalidIccProfileException("Invalid Entry."), |
|||
}; |
|||
} |
|||
} |
|||
|
|||
public unsafe Vector4 Calculate(Vector4 value) |
|||
{ |
|||
ref float f = ref Unsafe.As<Vector4, float>(ref value); |
|||
for (int i = 0; i < this.calculators.Length; i++) |
|||
{ |
|||
Unsafe.Add(ref f, i) = this.calculators[i].Calculate(Unsafe.Add(ref f, i)); |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc; |
|||
|
|||
internal static class CompactSrgbV4Profile |
|||
{ |
|||
private static readonly Lazy<IccProfile> LazyIccProfile = new(GetIccProfile); |
|||
|
|||
// Generated using the sRGB-v4.icc profile found at https://github.com/saucecontrol/Compact-ICC-Profiles
|
|||
private static ReadOnlySpan<byte> Data => |
|||
[ |
|||
0, 0, 1, 224, 108, 99, 109, 115, 4, 32, 0, 0, 109, 110, 116, 114, 82, 71, 66, 32, 88, 89, 90, 32, 7, 226, 0, 3, 0, |
|||
20, 0, 9, 0, 14, 0, 29, 97, 99, 115, 112, 77, 83, 70, 84, 0, 0, 0, 0, 115, 97, 119, 115, 99, 116, 114, 108, 0, 0, |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 214, 0, 1, 0, 0, 0, 0, 211, 45, 104, 97, 110, 100, 163, 178, 171, |
|||
223, 92, 167, 3, 18, 168, 85, 164, 236, 53, 122, 209, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 100, 101, 115, 99, 0, 0, 0, 252, 0, 0, 0, 36, 99, |
|||
112, 114, 116, 0, 0, 1, 32, 0, 0, 0, 34, 119, 116, 112, 116, 0, 0, 1, 68, 0, 0, 0, 20, 99, 104, 97, 100, 0, 0, |
|||
1, 88, 0, 0, 0, 44, 114, 88, 89, 90, 0, 0, 1, 132, 0, 0, 0, 20, 103, 88, 89, 90, 0, 0, 1, 152, 0, 0, 0, |
|||
20, 98, 88, 89, 90, 0, 0, 1, 172, 0, 0, 0, 20, 114, 84, 82, 67, 0, 0, 1, 192, 0, 0, 0, 32, 103, 84, 82, 67, |
|||
0, 0, 1, 192, 0, 0, 0, 32, 98, 84, 82, 67, 0, 0, 1, 192, 0, 0, 0, 32, 109, 108, 117, 99, 0, 0, 0, 0, 0, |
|||
0, 0, 1, 0, 0, 0, 12, 101, 110, 85, 83, 0, 0, 0, 8, 0, 0, 0, 28, 0, 115, 0, 82, 0, 71, 0, 66, 109, 108, |
|||
117, 99, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 12, 101, 110, 85, 83, 0, 0, 0, 6, 0, 0, 0, 28, 0, 67, 0, |
|||
67, 0, 48, 0, 33, 88, 89, 90, 32, 0, 0, 0, 0, 0, 0, 246, 214, 0, 1, 0, 0, 0, 0, 211, 45, 115, 102, 51, 50, |
|||
0, 0, 0, 0, 0, 1, 12, 63, 0, 0, 5, 221, 255, 255, 243, 38, 0, 0, 7, 144, 0, 0, 253, 146, 255, 255, 251, 161, 255, |
|||
255, 253, 162, 0, 0, 3, 220, 0, 0, 192, 113, 88, 89, 90, 32, 0, 0, 0, 0, 0, 0, 111, 160, 0, 0, 56, 242, 0, 0, |
|||
3, 143, 88, 89, 90, 32, 0, 0, 0, 0, 0, 0, 98, 150, 0, 0, 183, 137, 0, 0, 24, 218, 88, 89, 90, 32, 0, 0, 0, |
|||
0, 0, 0, 36, 160, 0, 0, 15, 133, 0, 0, 182, 196, 112, 97, 114, 97, 0, 0, 0, 0, 0, 3, 0, 0, 0, 2, 102, 105, |
|||
0, 0, 242, 167, 0, 0, 13, 89, 0, 0, 19, 208, 0, 0, 10, 91, |
|||
]; |
|||
|
|||
public static IccProfile Profile => LazyIccProfile.Value; |
|||
|
|||
private static IccProfile GetIccProfile() |
|||
{ |
|||
byte[] buffer = new byte[Data.Length]; |
|||
Data.CopyTo(buffer); |
|||
return new IccProfile(buffer); |
|||
} |
|||
} |
|||
@ -0,0 +1,155 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
#nullable disable |
|||
|
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
|
|||
/// <summary>
|
|||
/// Color converter for ICC profiles
|
|||
/// </summary>
|
|||
internal abstract partial class IccConverterBase |
|||
{ |
|||
private static ConversionMethod GetConversionMethod(IccProfile profile, IccRenderingIntent renderingIntent) => profile.Header.Class switch |
|||
{ |
|||
IccProfileClass.InputDevice or |
|||
IccProfileClass.DisplayDevice or |
|||
IccProfileClass.OutputDevice or |
|||
IccProfileClass.ColorSpace => CheckMethod1(profile, renderingIntent), |
|||
IccProfileClass.DeviceLink or IccProfileClass.Abstract => CheckMethod2(profile), |
|||
_ => ConversionMethod.Invalid, |
|||
}; |
|||
|
|||
private static ConversionMethod CheckMethod1(IccProfile profile, IccRenderingIntent renderingIntent) |
|||
{ |
|||
ConversionMethod method = CheckMethodD(profile, renderingIntent); |
|||
if (method != ConversionMethod.Invalid) |
|||
{ |
|||
return method; |
|||
} |
|||
|
|||
method = CheckMethodA(profile, renderingIntent); |
|||
if (method != ConversionMethod.Invalid) |
|||
{ |
|||
return method; |
|||
} |
|||
|
|||
method = CheckMethodA0(profile); |
|||
if (method != ConversionMethod.Invalid) |
|||
{ |
|||
return method; |
|||
} |
|||
|
|||
method = CheckMethodTrc(profile); |
|||
if (method != ConversionMethod.Invalid) |
|||
{ |
|||
return method; |
|||
} |
|||
|
|||
return ConversionMethod.Invalid; |
|||
} |
|||
|
|||
private static ConversionMethod CheckMethodD(IccProfile profile, IccRenderingIntent renderingIntent) |
|||
{ |
|||
if ((HasTag(profile, IccProfileTag.DToB0) || HasTag(profile, IccProfileTag.BToD0)) |
|||
&& renderingIntent == IccRenderingIntent.Perceptual) |
|||
{ |
|||
return ConversionMethod.D0; |
|||
} |
|||
|
|||
if ((HasTag(profile, IccProfileTag.DToB1) || HasTag(profile, IccProfileTag.BToD1)) |
|||
&& renderingIntent == IccRenderingIntent.MediaRelativeColorimetric) |
|||
{ |
|||
return ConversionMethod.D1; |
|||
} |
|||
|
|||
if ((HasTag(profile, IccProfileTag.DToB2) || HasTag(profile, IccProfileTag.BToD2)) |
|||
&& renderingIntent == IccRenderingIntent.Saturation) |
|||
{ |
|||
return ConversionMethod.D2; |
|||
} |
|||
|
|||
if ((HasTag(profile, IccProfileTag.DToB3) || HasTag(profile, IccProfileTag.BToD3)) |
|||
&& renderingIntent == IccRenderingIntent.AbsoluteColorimetric) |
|||
{ |
|||
return ConversionMethod.D3; |
|||
} |
|||
|
|||
return ConversionMethod.Invalid; |
|||
} |
|||
|
|||
private static ConversionMethod CheckMethodA(IccProfile profile, IccRenderingIntent renderingIntent) |
|||
{ |
|||
if ((HasTag(profile, IccProfileTag.AToB0) || HasTag(profile, IccProfileTag.BToA0)) |
|||
&& renderingIntent == IccRenderingIntent.Perceptual) |
|||
{ |
|||
return ConversionMethod.A0; |
|||
} |
|||
|
|||
if ((HasTag(profile, IccProfileTag.AToB1) || HasTag(profile, IccProfileTag.BToA1)) |
|||
&& renderingIntent == IccRenderingIntent.MediaRelativeColorimetric) |
|||
{ |
|||
return ConversionMethod.A1; |
|||
} |
|||
|
|||
if ((HasTag(profile, IccProfileTag.AToB2) || HasTag(profile, IccProfileTag.BToA2)) |
|||
&& renderingIntent == IccRenderingIntent.Saturation) |
|||
{ |
|||
return ConversionMethod.A2; |
|||
} |
|||
|
|||
return ConversionMethod.Invalid; |
|||
} |
|||
|
|||
private static ConversionMethod CheckMethodA0(IccProfile profile) |
|||
{ |
|||
bool valid = HasTag(profile, IccProfileTag.AToB0) || HasTag(profile, IccProfileTag.BToA0); |
|||
return valid ? ConversionMethod.A0 : ConversionMethod.Invalid; |
|||
} |
|||
|
|||
private static ConversionMethod CheckMethodTrc(IccProfile profile) |
|||
{ |
|||
if (HasTag(profile, IccProfileTag.RedMatrixColumn) |
|||
&& HasTag(profile, IccProfileTag.GreenMatrixColumn) |
|||
&& HasTag(profile, IccProfileTag.BlueMatrixColumn) |
|||
&& HasTag(profile, IccProfileTag.RedTrc) |
|||
&& HasTag(profile, IccProfileTag.GreenTrc) |
|||
&& HasTag(profile, IccProfileTag.BlueTrc)) |
|||
{ |
|||
return ConversionMethod.ColorTrc; |
|||
} |
|||
|
|||
if (HasTag(profile, IccProfileTag.GrayTrc)) |
|||
{ |
|||
return ConversionMethod.GrayTrc; |
|||
} |
|||
|
|||
return ConversionMethod.Invalid; |
|||
} |
|||
|
|||
private static ConversionMethod CheckMethod2(IccProfile profile) |
|||
{ |
|||
if (HasTag(profile, IccProfileTag.DToB0) || HasTag(profile, IccProfileTag.BToD0)) |
|||
{ |
|||
return ConversionMethod.D0; |
|||
} |
|||
|
|||
if (HasTag(profile, IccProfileTag.AToB0) || HasTag(profile, IccProfileTag.AToB0)) |
|||
{ |
|||
return ConversionMethod.A0; |
|||
} |
|||
|
|||
return ConversionMethod.Invalid; |
|||
} |
|||
|
|||
private static bool HasTag(IccProfile profile, IccProfileTag tag) |
|||
=> profile.Entries.Any(t => t.TagSignature == tag); |
|||
|
|||
private static IccTagDataEntry GetTag(IccProfile profile, IccProfileTag tag) |
|||
=> Array.Find(profile.Entries, t => t.TagSignature == tag); |
|||
|
|||
private static T GetTag<T>(IccProfile profile, IccProfileTag tag) |
|||
where T : IccTagDataEntry |
|||
=> profile.Entries.OfType<T>().FirstOrDefault(t => t.TagSignature == tag); |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
|
|||
/// <summary>
|
|||
/// Color converter for ICC profiles
|
|||
/// </summary>
|
|||
internal abstract partial class IccConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Conversion methods with ICC profiles
|
|||
/// </summary>
|
|||
private enum ConversionMethod |
|||
{ |
|||
/// <summary>
|
|||
/// Conversion using anything but Multi Process Elements with perceptual rendering intent
|
|||
/// </summary>
|
|||
A0, |
|||
|
|||
/// <summary>
|
|||
/// Conversion using anything but Multi Process Elements with relative colorimetric rendering intent
|
|||
/// </summary>
|
|||
A1, |
|||
|
|||
/// <summary>
|
|||
/// Conversion using anything but Multi Process Elements with saturation rendering intent
|
|||
/// </summary>
|
|||
A2, |
|||
|
|||
/// <summary>
|
|||
/// Conversion using Multi Process Elements with perceptual rendering intent
|
|||
/// </summary>
|
|||
D0, |
|||
|
|||
/// <summary>
|
|||
/// Conversion using Multi Process Elements with relative colorimetric rendering intent
|
|||
/// </summary>
|
|||
D1, |
|||
|
|||
/// <summary>
|
|||
/// Conversion using Multi Process Elements with saturation rendering intent
|
|||
/// </summary>
|
|||
D2, |
|||
|
|||
/// <summary>
|
|||
/// Conversion using Multi Process Elements with absolute colorimetric rendering intent
|
|||
/// </summary>
|
|||
D3, |
|||
|
|||
/// <summary>
|
|||
/// Conversion of more than one channel using tone reproduction curves
|
|||
/// </summary>
|
|||
ColorTrc, |
|||
|
|||
/// <summary>
|
|||
/// Conversion of exactly one channel using a tone reproduction curve
|
|||
/// </summary>
|
|||
GrayTrc, |
|||
|
|||
/// <summary>
|
|||
/// No valid conversion method available or found
|
|||
/// </summary>
|
|||
Invalid, |
|||
} |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles.Icc.Calculators; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
|
|||
/// <summary>
|
|||
/// Color converter for ICC profiles
|
|||
/// </summary>
|
|||
internal abstract partial class IccConverterBase |
|||
{ |
|||
private IVector4Calculator calculator; |
|||
|
|||
internal bool Is16BitLutEntry => this.calculator is LutEntryCalculator { Is16Bit: true }; |
|||
|
|||
internal bool IsTrc => this.calculator is ColorTrcCalculator or GrayTrcCalculator; |
|||
|
|||
/// <summary>
|
|||
/// Checks the profile for available conversion methods and gathers all the information's necessary for it.
|
|||
/// </summary>
|
|||
/// <param name="profile">The profile to use for the conversion.</param>
|
|||
/// <param name="toPcs">True if the conversion is to the Profile Connection Space.</param>
|
|||
/// <param name="renderingIntent">The wanted rendering intent. Can be ignored if not available.</param>
|
|||
/// <exception cref="InvalidIccProfileException">Invalid conversion method.</exception>
|
|||
protected void Init(IccProfile profile, bool toPcs, IccRenderingIntent renderingIntent) |
|||
=> this.calculator = GetConversionMethod(profile, renderingIntent) switch |
|||
{ |
|||
ConversionMethod.D0 => toPcs ? |
|||
InitD(profile, IccProfileTag.DToB0) : |
|||
InitD(profile, IccProfileTag.BToD0), |
|||
ConversionMethod.D1 => toPcs ? |
|||
InitD(profile, IccProfileTag.DToB1) : |
|||
InitD(profile, IccProfileTag.BToD1), |
|||
ConversionMethod.D2 => toPcs ? |
|||
InitD(profile, IccProfileTag.DToB2) : |
|||
InitD(profile, IccProfileTag.BToD2), |
|||
ConversionMethod.D3 => toPcs ? |
|||
InitD(profile, IccProfileTag.DToB3) : |
|||
InitD(profile, IccProfileTag.BToD3), |
|||
ConversionMethod.A0 => toPcs ? |
|||
InitA(profile, IccProfileTag.AToB0) : |
|||
InitA(profile, IccProfileTag.BToA0), |
|||
ConversionMethod.A1 => toPcs ? |
|||
InitA(profile, IccProfileTag.AToB1) : |
|||
InitA(profile, IccProfileTag.BToA1), |
|||
ConversionMethod.A2 => toPcs ? |
|||
InitA(profile, IccProfileTag.AToB2) : |
|||
InitA(profile, IccProfileTag.BToA2), |
|||
ConversionMethod.ColorTrc => InitColorTrc(profile, toPcs), |
|||
ConversionMethod.GrayTrc => InitGrayTrc(profile, toPcs), |
|||
_ => throw new InvalidIccProfileException("Invalid conversion method."), |
|||
}; |
|||
|
|||
private static IVector4Calculator InitA(IccProfile profile, IccProfileTag tag) |
|||
=> GetTag(profile, tag) switch |
|||
{ |
|||
IccLut8TagDataEntry lut8 => new LutEntryCalculator(lut8), |
|||
IccLut16TagDataEntry lut16 => new LutEntryCalculator(lut16), |
|||
IccLutAToBTagDataEntry lutAtoB => new LutABCalculator(lutAtoB), |
|||
IccLutBToATagDataEntry lutBtoA => new LutABCalculator(lutBtoA), |
|||
_ => throw new InvalidIccProfileException($"Invalid entry {tag}."), |
|||
}; |
|||
|
|||
private static IVector4Calculator InitD(IccProfile profile, IccProfileTag tag) |
|||
{ |
|||
IccMultiProcessElementsTagDataEntry entry = GetTag<IccMultiProcessElementsTagDataEntry>(profile, tag) |
|||
?? throw new InvalidIccProfileException("Entry is null."); |
|||
|
|||
throw new NotImplementedException("Multi process elements are not supported"); |
|||
} |
|||
|
|||
private static ColorTrcCalculator InitColorTrc(IccProfile profile, bool toPcs) |
|||
{ |
|||
IccXyzTagDataEntry redMatrixColumn = GetTag<IccXyzTagDataEntry>(profile, IccProfileTag.RedMatrixColumn); |
|||
IccXyzTagDataEntry greenMatrixColumn = GetTag<IccXyzTagDataEntry>(profile, IccProfileTag.GreenMatrixColumn); |
|||
IccXyzTagDataEntry blueMatrixColumn = GetTag<IccXyzTagDataEntry>(profile, IccProfileTag.BlueMatrixColumn); |
|||
|
|||
IccTagDataEntry redTrc = GetTag(profile, IccProfileTag.RedTrc); |
|||
IccTagDataEntry greenTrc = GetTag(profile, IccProfileTag.GreenTrc); |
|||
IccTagDataEntry blueTrc = GetTag(profile, IccProfileTag.BlueTrc); |
|||
|
|||
if (redMatrixColumn == null || |
|||
greenMatrixColumn == null || |
|||
blueMatrixColumn == null || |
|||
redTrc == null || |
|||
greenTrc == null || |
|||
blueTrc == null) |
|||
{ |
|||
throw new InvalidIccProfileException("Missing matrix column or channel."); |
|||
} |
|||
|
|||
return new ColorTrcCalculator( |
|||
redMatrixColumn, |
|||
greenMatrixColumn, |
|||
blueMatrixColumn, |
|||
redTrc, |
|||
greenTrc, |
|||
blueTrc, |
|||
toPcs); |
|||
} |
|||
|
|||
private static GrayTrcCalculator InitGrayTrc(IccProfile profile, bool toPcs) |
|||
{ |
|||
IccTagDataEntry entry = GetTag(profile, IccProfileTag.GrayTrc); |
|||
return new GrayTrcCalculator(entry, toPcs); |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
#nullable disable |
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
|
|||
/// <summary>
|
|||
/// Color converter for ICC profiles
|
|||
/// </summary>
|
|||
internal abstract partial class IccConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="IccConverterBase"/> class.
|
|||
/// </summary>
|
|||
/// <param name="profile">The ICC profile to use for the conversions</param>
|
|||
/// <param name="toPcs">True if the conversion is to the profile connection space (PCS); False if the conversion is to the data space</param>
|
|||
protected IccConverterBase(IccProfile profile, bool toPcs) |
|||
{ |
|||
Guard.NotNull(profile, nameof(profile)); |
|||
this.Init(profile, toPcs, profile.Header.RenderingIntent); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts colors with the initially provided ICC profile
|
|||
/// </summary>
|
|||
/// <param name="value">The value to convert</param>
|
|||
/// <returns>The converted value</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector4 Calculate(Vector4 value) => this.calculator.Calculate(value); |
|||
|
|||
/// <summary>
|
|||
/// Converts colors with the initially provided ICC profile
|
|||
/// </summary>
|
|||
/// <param name="source">The source colors</param>
|
|||
/// <param name="destination">The destination colors</param>
|
|||
public void Calculate(ReadOnlySpan<Vector4> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = this.Calculate(source[i]); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc; |
|||
|
|||
/// <summary>
|
|||
/// Color converter for ICC profiles
|
|||
/// </summary>
|
|||
internal class IccDataToDataConverter : IccConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="IccDataToDataConverter"/> class.
|
|||
/// </summary>
|
|||
/// <param name="profile">The ICC profile to use for the conversions</param>
|
|||
public IccDataToDataConverter(IccProfile profile) |
|||
: base(profile, true) // toPCS is true because in this case the PCS space is also a data space
|
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc; |
|||
|
|||
/// <summary>
|
|||
/// Color converter for ICC profiles
|
|||
/// </summary>
|
|||
internal class IccDataToPcsConverter : IccConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="IccDataToPcsConverter"/> class.
|
|||
/// </summary>
|
|||
/// <param name="profile">The ICC profile to use for the conversions</param>
|
|||
public IccDataToPcsConverter(IccProfile profile) |
|||
: base(profile, true) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc; |
|||
|
|||
/// <summary>
|
|||
/// Color converter for ICC profiles
|
|||
/// </summary>
|
|||
internal class IccPcsToDataConverter : IccConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="IccPcsToDataConverter"/> class.
|
|||
/// </summary>
|
|||
/// <param name="profile">The ICC profile to use for the conversions</param>
|
|||
public IccPcsToDataConverter(IccProfile profile) |
|||
: base(profile, false) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles.Conversion.Icc; |
|||
using SixLabors.ImageSharp.Metadata.Profiles.Icc; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.Icc; |
|||
|
|||
/// <summary>
|
|||
/// Color converter for ICC profiles
|
|||
/// </summary>
|
|||
internal class IccPcsToPcsConverter : IccConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="IccPcsToPcsConverter"/> class.
|
|||
/// </summary>
|
|||
/// <param name="profile">The ICC profile to use for the conversions</param>
|
|||
public IccPcsToPcsConverter(IccProfile profile) |
|||
: base(profile, true) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,135 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Provides matrices for chromatic adaptation, facilitating the adjustment of color values
|
|||
/// under different light sources to maintain color constancy. This class supports common
|
|||
/// adaptation transforms based on the von Kries coefficient law, which assumes independent
|
|||
/// scaling of the cone responses in the human eye. These matrices can be applied to convert
|
|||
/// color coordinates between different illuminants, ensuring consistent color appearance
|
|||
/// across various lighting conditions.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Supported adaptation matrices include the Bradford, von Kries, and Sharp transforms.
|
|||
/// These matrices are typically used in conjunction with color space conversions, such as from XYZ
|
|||
/// to RGB, to achieve accurate color rendition in digital imaging applications.
|
|||
/// </remarks>
|
|||
public static class KnownChromaticAdaptationMatrices |
|||
{ |
|||
/// <summary>
|
|||
/// von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65)
|
|||
/// </summary>
|
|||
public static readonly Matrix4x4 VonKriesHPEAdjusted |
|||
= Matrix4x4.Transpose(new Matrix4x4 |
|||
{ |
|||
M11 = 0.40024F, |
|||
M12 = 0.7076F, |
|||
M13 = -0.08081F, |
|||
M21 = -0.2263F, |
|||
M22 = 1.16532F, |
|||
M23 = 0.0457F, |
|||
M31 = 0, |
|||
M32 = 0, |
|||
M33 = 0.91822F, |
|||
M44 = 1F // Important for inverse transforms.
|
|||
}); |
|||
|
|||
/// <summary>
|
|||
/// von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez for equal energy)
|
|||
/// </summary>
|
|||
public static readonly Matrix4x4 VonKriesHPE |
|||
= Matrix4x4.Transpose(new Matrix4x4 |
|||
{ |
|||
M11 = 0.3897F, |
|||
M12 = 0.6890F, |
|||
M13 = -0.0787F, |
|||
M21 = -0.2298F, |
|||
M22 = 1.1834F, |
|||
M23 = 0.0464F, |
|||
M31 = 0, |
|||
M32 = 0, |
|||
M33 = 1F, |
|||
M44 = 1F |
|||
}); |
|||
|
|||
/// <summary>
|
|||
/// XYZ scaling chromatic adaptation transform matrix
|
|||
/// </summary>
|
|||
public static readonly Matrix4x4 XyzScaling = Matrix4x4.Transpose(Matrix4x4.Identity); |
|||
|
|||
/// <summary>
|
|||
/// Bradford chromatic adaptation transform matrix (used in CMCCAT97)
|
|||
/// </summary>
|
|||
public static readonly Matrix4x4 Bradford |
|||
= Matrix4x4.Transpose(new Matrix4x4 |
|||
{ |
|||
M11 = 0.8951F, |
|||
M12 = 0.2664F, |
|||
M13 = -0.1614F, |
|||
M21 = -0.7502F, |
|||
M22 = 1.7135F, |
|||
M23 = 0.0367F, |
|||
M31 = 0.0389F, |
|||
M32 = -0.0685F, |
|||
M33 = 1.0296F, |
|||
M44 = 1F |
|||
}); |
|||
|
|||
/// <summary>
|
|||
/// Spectral sharpening and the Bradford transform
|
|||
/// </summary>
|
|||
public static readonly Matrix4x4 BradfordSharp |
|||
= Matrix4x4.Transpose(new Matrix4x4 |
|||
{ |
|||
M11 = 1.2694F, |
|||
M12 = -0.0988F, |
|||
M13 = -0.1706F, |
|||
M21 = -0.8364F, |
|||
M22 = 1.8006F, |
|||
M23 = 0.0357F, |
|||
M31 = 0.0297F, |
|||
M32 = -0.0315F, |
|||
M33 = 1.0018F, |
|||
M44 = 1F |
|||
}); |
|||
|
|||
/// <summary>
|
|||
/// CMCCAT2000 (fitted from all available color data sets)
|
|||
/// </summary>
|
|||
public static readonly Matrix4x4 CMCCAT2000 |
|||
= Matrix4x4.Transpose(new Matrix4x4 |
|||
{ |
|||
M11 = 0.7982F, |
|||
M12 = 0.3389F, |
|||
M13 = -0.1371F, |
|||
M21 = -0.5918F, |
|||
M22 = 1.5512F, |
|||
M23 = 0.0406F, |
|||
M31 = 0.0008F, |
|||
M32 = 0.239F, |
|||
M33 = 0.9753F, |
|||
M44 = 1F |
|||
}); |
|||
|
|||
/// <summary>
|
|||
/// CAT02 (optimized for minimizing CIELAB differences)
|
|||
/// </summary>
|
|||
public static readonly Matrix4x4 CAT02 |
|||
= Matrix4x4.Transpose(new Matrix4x4 |
|||
{ |
|||
M11 = 0.7328F, |
|||
M12 = 0.4296F, |
|||
M13 = -0.1624F, |
|||
M21 = -0.7036F, |
|||
M22 = 1.6975F, |
|||
M23 = 0.0061F, |
|||
M31 = 0.0030F, |
|||
M32 = 0.0136F, |
|||
M33 = 0.9834F, |
|||
M44 = 1F |
|||
}); |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// The well known standard illuminants.
|
|||
/// Standard illuminants provide a basis for comparing images or colors recorded under different lighting
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Coefficients taken from: http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
|
|||
/// and https://color.org/specification/ICC.1-2022-05.pdf
|
|||
/// <br />
|
|||
/// Descriptions taken from: http://en.wikipedia.org/wiki/Standard_illuminant
|
|||
/// </remarks>
|
|||
public static class KnownIlluminants |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the Incandescent / Tungsten illuminant.
|
|||
/// </summary>
|
|||
public static CieXyz A { get; } = new(1.09850F, 1F, 0.35585F); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Direct sunlight at noon (obsoleteF) illuminant.
|
|||
/// </summary>
|
|||
public static CieXyz B { get; } = new(0.99072F, 1F, 0.85223F); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Average / North sky Daylight (obsoleteF) illuminant.
|
|||
/// </summary>
|
|||
public static CieXyz C { get; } = new(0.98074F, 1F, 1.18232F); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Horizon Light.
|
|||
/// </summary>
|
|||
public static CieXyz D50 { get; } = new(0.96422F, 1F, 0.82521F); |
|||
|
|||
/// <summary>
|
|||
/// Gets the D50 illuminant used in the ICC profile specification.
|
|||
/// </summary>
|
|||
public static CieXyz D50Icc { get; } = new(0.9642F, 1F, 0.8249F); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Mid-morning / Mid-afternoon Daylight illuminant.
|
|||
/// </summary>
|
|||
public static CieXyz D55 { get; } = new(0.95682F, 1F, 0.92149F); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Noon Daylight: TelevisionF, sRGB color space illuminant.
|
|||
/// </summary>
|
|||
public static CieXyz D65 { get; } = new(0.95047F, 1F, 1.08883F); |
|||
|
|||
/// <summary>
|
|||
/// Gets the North sky Daylight illuminant.
|
|||
/// </summary>
|
|||
public static CieXyz D75 { get; } = new(0.94972F, 1F, 1.22638F); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Equal energy illuminant.
|
|||
/// </summary>
|
|||
public static CieXyz E { get; } = new(1F, 1F, 1F); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Cool White Fluorescent illuminant.
|
|||
/// </summary>
|
|||
public static CieXyz F2 { get; } = new(0.99186F, 1F, 0.67393F); |
|||
|
|||
/// <summary>
|
|||
/// Gets the D65 simulatorF, Daylight simulator illuminant.
|
|||
/// </summary>
|
|||
public static CieXyz F7 { get; } = new(0.95041F, 1F, 1.08747F); |
|||
|
|||
/// <summary>
|
|||
/// Gets the Philips TL84F, Ultralume 40 illuminant.
|
|||
/// </summary>
|
|||
public static CieXyz F11 { get; } = new(1.00962F, 1F, 0.64350F); |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles.Companding; |
|||
using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Chromaticity coordinates based on: <see href="http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html"/>
|
|||
/// </summary>
|
|||
public static class KnownRgbWorkingSpaces |
|||
{ |
|||
/// <summary>
|
|||
/// sRgb working space.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Uses proper companding function, according to:
|
|||
/// <see href="http://www.brucelindbloom.com/index.html?Eqn_Rgb_to_XYZ.html"/>
|
|||
/// </remarks>
|
|||
public static readonly RgbWorkingSpace SRgb = new SRgbWorkingSpace(KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); |
|||
|
|||
/// <summary>
|
|||
/// Simplified sRgb working space (uses <see cref="GammaCompanding">gamma companding</see> instead of <see cref="SRgbCompanding"/>).
|
|||
/// See also <see cref="SRgb"/>.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace SRgbSimplified = new GammaWorkingSpace(2.2F, KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); |
|||
|
|||
/// <summary>
|
|||
/// Rec. 709 (ITU-R Recommendation BT.709) working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace Rec709 = new Rec709WorkingSpace(KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.64F, 0.33F), new CieXyChromaticityCoordinates(0.30F, 0.60F), new CieXyChromaticityCoordinates(0.15F, 0.06F))); |
|||
|
|||
/// <summary>
|
|||
/// Rec. 2020 (ITU-R Recommendation BT.2020F) working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace Rec2020 = new Rec2020WorkingSpace(KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.708F, 0.292F), new CieXyChromaticityCoordinates(0.170F, 0.797F), new CieXyChromaticityCoordinates(0.131F, 0.046F))); |
|||
|
|||
/// <summary>
|
|||
/// ECI Rgb v2 working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace ECIRgbv2 = new LWorkingSpace(KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F))); |
|||
|
|||
/// <summary>
|
|||
/// Adobe Rgb (1998) working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace AdobeRgb1998 = new GammaWorkingSpace(2.2F, KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); |
|||
|
|||
/// <summary>
|
|||
/// Apple sRgb working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace ApplesRgb = new GammaWorkingSpace(1.8F, KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6250F, 0.3400F), new CieXyChromaticityCoordinates(0.2800F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F))); |
|||
|
|||
/// <summary>
|
|||
/// Best Rgb working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace BestRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.2150F, 0.7750F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F))); |
|||
|
|||
/// <summary>
|
|||
/// Beta Rgb working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace BetaRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6888F, 0.3112F), new CieXyChromaticityCoordinates(0.1986F, 0.7551F), new CieXyChromaticityCoordinates(0.1265F, 0.0352F))); |
|||
|
|||
/// <summary>
|
|||
/// Bruce Rgb working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace BruceRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2800F, 0.6500F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); |
|||
|
|||
/// <summary>
|
|||
/// CIE Rgb working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace CIERgb = new GammaWorkingSpace(2.2F, KnownIlluminants.E, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.2740F, 0.7170F), new CieXyChromaticityCoordinates(0.1670F, 0.0090F))); |
|||
|
|||
/// <summary>
|
|||
/// ColorMatch Rgb working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace ColorMatchRgb = new GammaWorkingSpace(1.8F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.2950F, 0.6050F), new CieXyChromaticityCoordinates(0.1500F, 0.0750F))); |
|||
|
|||
/// <summary>
|
|||
/// Don Rgb 4 working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace DonRgb4 = new GammaWorkingSpace(2.2F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6960F, 0.3000F), new CieXyChromaticityCoordinates(0.2150F, 0.7650F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F))); |
|||
|
|||
/// <summary>
|
|||
/// Ekta Space PS5 working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace EktaSpacePS5 = new GammaWorkingSpace(2.2F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6950F, 0.3050F), new CieXyChromaticityCoordinates(0.2600F, 0.7000F), new CieXyChromaticityCoordinates(0.1100F, 0.0050F))); |
|||
|
|||
/// <summary>
|
|||
/// NTSC Rgb working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace NTSCRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.C, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F))); |
|||
|
|||
/// <summary>
|
|||
/// PAL/SECAM Rgb working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace PALSECAMRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2900F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); |
|||
|
|||
/// <summary>
|
|||
/// ProPhoto Rgb working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace ProPhotoRgb = new GammaWorkingSpace(1.8F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.1596F, 0.8404F), new CieXyChromaticityCoordinates(0.0366F, 0.0001F))); |
|||
|
|||
/// <summary>
|
|||
/// SMPTE-C Rgb working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace SMPTECRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.3100F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F))); |
|||
|
|||
/// <summary>
|
|||
/// Wide Gamut Rgb working space.
|
|||
/// </summary>
|
|||
public static readonly RgbWorkingSpace WideGamutRgb = new GammaWorkingSpace(2.2F, KnownIlluminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.1150F, 0.8260F), new CieXyChromaticityCoordinates(0.1570F, 0.0180F))); |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Provides standard YCbCr matrices for RGB to YCbCr conversion.
|
|||
/// </summary>
|
|||
public static class KnownYCbCrMatrices |
|||
{ |
|||
#pragma warning disable SA1137 // Elements should have the same indentation
|
|||
#pragma warning disable SA1117 // Parameters should be on same line or separate lines
|
|||
/// <summary>
|
|||
/// ITU-R BT.601 (SD video standard).
|
|||
/// </summary>
|
|||
public static readonly YCbCrTransform BT601 = new( |
|||
new Matrix4x4( |
|||
0.299000F, 0.587000F, 0.114000F, 0F, |
|||
-0.168736F, -0.331264F, 0.500000F, 0F, |
|||
0.500000F, -0.418688F, -0.081312F, 0F, |
|||
0F, 0F, 0F, 1F), |
|||
new Matrix4x4( |
|||
1.000000F, 0.000000F, 1.402000F, 0F, |
|||
1.000000F, -0.344136F, -0.714136F, 0F, |
|||
1.000000F, 1.772000F, 0.000000F, 0F, |
|||
0F, 0F, 0F, 1F), |
|||
new Vector3(0F, 0.5F, 0.5F)); |
|||
|
|||
/// <summary>
|
|||
/// ITU-R BT.709 (HD video, sRGB standard).
|
|||
/// </summary>
|
|||
public static readonly YCbCrTransform BT709 = new( |
|||
new Matrix4x4( |
|||
0.212600F, 0.715200F, 0.072200F, 0F, |
|||
-0.114572F, -0.385428F, 0.500000F, 0F, |
|||
0.500000F, -0.454153F, -0.045847F, 0F, |
|||
0F, 0F, 0F, 1F), |
|||
new Matrix4x4( |
|||
1.000000F, 0.000000F, 1.574800F, 0F, |
|||
1.000000F, -0.187324F, -0.468124F, 0F, |
|||
1.000000F, 1.855600F, 0.000000F, 0F, |
|||
0F, 0F, 0F, 1F), |
|||
new Vector3(0F, 0.5F, 0.5F)); |
|||
|
|||
/// <summary>
|
|||
/// ITU-R BT.2020 (UHD/4K video standard).
|
|||
/// </summary>
|
|||
public static readonly YCbCrTransform BT2020 = new( |
|||
new Matrix4x4( |
|||
0.262700F, 0.678000F, 0.059300F, 0F, |
|||
-0.139630F, -0.360370F, 0.500000F, 0F, |
|||
0.500000F, -0.459786F, -0.040214F, 0F, |
|||
0F, 0F, 0F, 1F), |
|||
new Matrix4x4( |
|||
1.000000F, 0.000000F, 1.474600F, 0F, |
|||
1.000000F, -0.164553F, -0.571353F, 0F, |
|||
1.000000F, 1.881400F, 0.000000F, 0F, |
|||
0F, 0F, 0F, 1F), |
|||
new Vector3(0F, 0.5F, 0.5F)); |
|||
} |
|||
@ -0,0 +1,178 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// LMS is a color space represented by the response of the three types of cones of the human eye,
|
|||
/// named after their responsivity (sensitivity) at long, medium and short wavelengths.
|
|||
/// <see href="https://en.wikipedia.org/wiki/LMS_color_space"/>
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct Lms : IColorProfile<Lms, CieXyz> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Lms"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="l">L represents the responsivity at long wavelengths.</param>
|
|||
/// <param name="m">M represents the responsivity at medium wavelengths.</param>
|
|||
/// <param name="s">S represents the responsivity at short wavelengths.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Lms(float l, float m, float s) |
|||
{ |
|||
this.L = l; |
|||
this.M = m; |
|||
this.S = s; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Lms"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the l, m, s components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Lms(Vector3 vector) |
|||
{ |
|||
// Not clamping as documentation about this space only indicates "usual" ranges
|
|||
this.L = vector.X; |
|||
this.M = vector.Y; |
|||
this.S = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the L long component.
|
|||
/// <remarks>A value usually ranging between -1 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float L { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the M medium component.
|
|||
/// <remarks>A value usually ranging between -1 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float M { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the S short component.
|
|||
/// <remarks>A value usually ranging between -1 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float S { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Lms"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Lms"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Lms"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(Lms left, Lms right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Lms"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Lms"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Lms"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(Lms left, Lms right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() |
|||
{ |
|||
Vector3 v3 = default; |
|||
v3 += this.AsVector3Unsafe(); |
|||
v3 += new Vector3(1F); |
|||
v3 /= 2F; |
|||
return new Vector4(v3, 1F); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Lms FromScaledVector4(Vector4 source) |
|||
{ |
|||
Vector3 v3 = source.AsVector3(); |
|||
v3 *= 2F; |
|||
v3 -= new Vector3(1F); |
|||
return new Lms(v3); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<Lms> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToScaledVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<Lms> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = FromScaledVector4(source[i]); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Lms FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) |
|||
=> new(Vector3.Transform(source.AsVector3Unsafe(), options.AdaptationMatrix)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<Lms> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
CieXyz xyz = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in xyz); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) |
|||
=> new(Vector3.Transform(this.AsVector3Unsafe(), options.InverseAdaptationMatrix)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Lms> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Lms lms = source[i]; |
|||
destination[i] = lms.ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.WhitePoint; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.L, this.M, this.S); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"Lms({this.L:#0.##}, {this.M:#0.##}, {this.S:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is Lms other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(Lms other) |
|||
=> this.AsVector3Unsafe() == other.AsVector3Unsafe(); |
|||
|
|||
private Vector3 AsVector3Unsafe() => Unsafe.As<Lms, Vector3>(ref Unsafe.AsRef(in this)); |
|||
} |
|||
@ -0,0 +1,463 @@ |
|||
// 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; |
|||
using System.Runtime.Intrinsics.X86; |
|||
using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents an RGB (red, green, blue) color profile.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rgb"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="r">The red component usually ranging between 0 and 1.</param>
|
|||
/// <param name="g">The green component usually ranging between 0 and 1.</param>
|
|||
/// <param name="b">The blue component usually ranging between 0 and 1.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Rgb(float r, float g, float b) |
|||
{ |
|||
// Not clamping as this space can exceed "usual" ranges
|
|||
this.R = r; |
|||
this.G = g; |
|||
this.B = b; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rgb"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="source">The vector representing the r, g, b components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Rgb(Vector3 source) |
|||
{ |
|||
this.R = source.X; |
|||
this.G = source.Y; |
|||
this.B = source.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the red component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float R { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the green component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float G { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue component.
|
|||
/// <remarks>A value usually ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float B { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Rgb"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Rgb"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Rgb"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(Rgb left, Rgb right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Rgb"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Rgb"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Rgb"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(Rgb left, Rgb right) => !left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Initializes the color instance from a generic scaled <see cref="Vector4"/>.
|
|||
/// </summary>
|
|||
/// <param name="source">The vector to load the color from.</param>
|
|||
/// <returns>The <see cref="Rgb"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb FromScaledVector4(Vector4 source) |
|||
=> new(source.AsVector3()); |
|||
|
|||
/// <summary>
|
|||
/// Expands the color into a generic ("scaled") <see cref="Vector4"/> representation
|
|||
/// with values scaled and usually clamped between <value>0</value> and <value>1</value>.
|
|||
/// The vector components are typically expanded in least to greatest significance order.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector4 ToScaledVector4() |
|||
=> new(this.AsVector3Unsafe(), 1F); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<Rgb> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
int length = source.Length; |
|||
if (length == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
ref Rgb srcRgb = ref MemoryMarshal.GetReference(source); |
|||
ref Vector4 dstV4 = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
// Float streams:
|
|||
// src: r0 g0 b0 r1 g1 b1 ...
|
|||
// dst: r0 g0 b0 a0 r1 g1 b1 a1 ...
|
|||
ref float src = ref Unsafe.As<Rgb, float>(ref srcRgb); |
|||
ref float dst = ref Unsafe.As<Vector4, float>(ref dstV4); |
|||
|
|||
int i = 0; |
|||
|
|||
if (Avx512F.IsSupported) |
|||
{ |
|||
// 4 pixels per iteration. Using overlapped 16-float loads.
|
|||
Vector512<int> perm = Vector512.Create(0, 1, 2, 0, 3, 4, 5, 0, 6, 7, 8, 0, 9, 10, 11, 0); |
|||
Vector512<float> ones = Vector512.Create(1F); |
|||
|
|||
// BlendVariable selects from 'ones' where the sign-bit of mask lane is set.
|
|||
// Using -0f sets only the sign bit, producing an efficient "select lane" mask.
|
|||
Vector512<float> alphaSelect = Vector512.Create(0F, 0F, 0F, -0F, 0F, 0F, 0F, -0F, 0F, 0F, 0F, -0F, 0F, 0F, 0F, -0F); |
|||
|
|||
int quads = length >> 2; |
|||
|
|||
// Leave the last quad (4 pixels) for the scalar tail.
|
|||
int simdQuads = quads - 1; |
|||
|
|||
for (int q = 0; q < simdQuads; q++) |
|||
{ |
|||
Vector512<float> v = ReadVector512(ref src); |
|||
Vector512<float> rgbx = Avx512F.PermuteVar16x32(v, perm); |
|||
Vector512<float> rgba = Avx512F.BlendVariable(rgbx, ones, alphaSelect); |
|||
|
|||
WriteVector512(ref dst, rgba); |
|||
|
|||
src = ref Unsafe.Add(ref src, 12); |
|||
dst = ref Unsafe.Add(ref dst, 16); |
|||
|
|||
i += 4; |
|||
} |
|||
} |
|||
else if (Avx2.IsSupported) |
|||
{ |
|||
// 2 pixels per iteration. Using overlapped 8-float loads.
|
|||
Vector256<int> perm = Vector256.Create(0, 1, 2, 0, 3, 4, 5, 0); |
|||
|
|||
Vector256<float> ones = Vector256.Create(1F); |
|||
|
|||
// vblendps mask: bit i selects lane i from 'ones' when set.
|
|||
// We want lanes 3 and 7 -> 0b10001000 = 0x88.
|
|||
const byte alphaMask = 0x88; |
|||
|
|||
int pairs = length >> 1; |
|||
|
|||
// Leave the last pair (2 pixels) for the scalar tail.
|
|||
int simdPairs = pairs - 1; |
|||
|
|||
for (int p = 0; p < simdPairs; p++) |
|||
{ |
|||
Vector256<float> v = ReadVector256(ref src); |
|||
Vector256<float> rgbx = Avx2.PermuteVar8x32(v, perm); |
|||
Vector256<float> rgba = Avx.Blend(rgbx, ones, alphaMask); |
|||
|
|||
WriteVector256(ref dst, rgba); |
|||
|
|||
src = ref Unsafe.Add(ref src, 6); |
|||
dst = ref Unsafe.Add(ref dst, 8); |
|||
|
|||
i += 2; |
|||
} |
|||
} |
|||
|
|||
// Tail (and non-AVX paths)
|
|||
for (; i < length; i++) |
|||
{ |
|||
Unsafe.Add(ref dstV4, i) = Unsafe.Add(ref srcRgb, i).ToScaledVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<Rgb> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
int length = source.Length; |
|||
if (length == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
ref Vector4 srcV4 = ref MemoryMarshal.GetReference(source); |
|||
ref Rgb dstRgb = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
// Float streams:
|
|||
// src: r0 g0 b0 a0 r1 g1 b1 a1 ...
|
|||
// dst: r0 g0 b0 r1 g1 b1 ...
|
|||
ref float src = ref Unsafe.As<Vector4, float>(ref srcV4); |
|||
ref float dst = ref Unsafe.As<Rgb, float>(ref dstRgb); |
|||
|
|||
int i = 0; |
|||
|
|||
if (Avx512F.IsSupported) |
|||
{ |
|||
// 4 pixels per iteration. Using overlapped 16-float stores:
|
|||
Vector512<int> idx = Vector512.Create(0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 3, 7, 11, 15); |
|||
|
|||
// Number of 4-pixel groups in the input.
|
|||
int quads = length >> 2; |
|||
|
|||
// Leave the last quad (4 pixels) for the scalar tail.
|
|||
int simdQuads = quads - 1; |
|||
|
|||
for (int q = 0; q < simdQuads; q++) |
|||
{ |
|||
Vector512<float> v = ReadVector512(ref src); |
|||
Vector512<float> packed = Avx512F.PermuteVar16x32(v, idx); |
|||
|
|||
WriteVector512(ref dst, packed); |
|||
|
|||
src = ref Unsafe.Add(ref src, 16); |
|||
dst = ref Unsafe.Add(ref dst, 12); |
|||
i += 4; |
|||
} |
|||
} |
|||
else if (Avx2.IsSupported) |
|||
{ |
|||
// 2 pixels per iteration, using overlapped 8-float stores:
|
|||
Vector256<int> idx = Vector256.Create(0, 1, 2, 4, 5, 6, 0, 0); |
|||
|
|||
int pairs = length >> 1; |
|||
|
|||
// Leave the last pair (2 pixels) for the scalar tail.
|
|||
int simdPairs = pairs - 1; |
|||
|
|||
int pairIndex = 0; |
|||
for (; pairIndex < simdPairs; pairIndex++) |
|||
{ |
|||
Vector256<float> v = ReadVector256(ref src); |
|||
Vector256<float> packed = Avx2.PermuteVar8x32(v, idx); |
|||
|
|||
WriteVector256(ref dst, packed); |
|||
|
|||
src = ref Unsafe.Add(ref src, 8); |
|||
dst = ref Unsafe.Add(ref dst, 6); |
|||
i += 2; |
|||
} |
|||
} |
|||
|
|||
// Tail (and non-AVX paths)
|
|||
for (; i < length; i++) |
|||
{ |
|||
Vector4 v = Unsafe.Add(ref srcV4, i); |
|||
Unsafe.Add(ref dstRgb, i) = FromScaledVector4(v); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Rgb FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source) |
|||
{ |
|||
// Convert to linear rgb then compress.
|
|||
Rgb linear = new(Vector3.Transform(source.AsVector3Unsafe(), GetCieXyzToRgbMatrix(options.TargetRgbWorkingSpace))); |
|||
return FromScaledVector4(options.TargetRgbWorkingSpace.Compress(linear.ToScaledVector4())); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<CieXyz> source, Span<Rgb> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
Matrix4x4 matrix = GetCieXyzToRgbMatrix(options.TargetRgbWorkingSpace); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
// Convert to linear rgb then compress.
|
|||
Rgb linear = new(Vector3.Transform(source[i].AsVector3Unsafe(), matrix)); |
|||
Vector4 nonlinear = options.TargetRgbWorkingSpace.Compress(linear.ToScaledVector4()); |
|||
destination[i] = FromScaledVector4(nonlinear); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public CieXyz ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
// First expand to linear rgb
|
|||
Rgb linear = FromScaledVector4(options.SourceRgbWorkingSpace.Expand(this.ToScaledVector4())); |
|||
|
|||
// Then convert to xyz
|
|||
return new CieXyz(Vector3.Transform(linear.AsVector3Unsafe(), GetRgbToCieXyzMatrix(options.SourceRgbWorkingSpace))); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<CieXyz> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
Matrix4x4 matrix = GetRgbToCieXyzMatrix(options.SourceRgbWorkingSpace); |
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Rgb rgb = source[i]; |
|||
|
|||
// First expand to linear rgb
|
|||
Rgb linear = FromScaledVector4(options.SourceRgbWorkingSpace.Expand(rgb.ToScaledVector4())); |
|||
|
|||
// Then convert to xyz
|
|||
destination[i] = new CieXyz(Vector3.Transform(linear.AsVector3Unsafe(), matrix)); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.RgbWorkingSpace; |
|||
|
|||
/// <summary>
|
|||
/// Initializes the color instance from a generic scaled <see cref="Vector3"/>.
|
|||
/// </summary>
|
|||
/// <param name="source">The vector to load the color from.</param>
|
|||
/// <returns>The <see cref="Rgb"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb FromScaledVector3(Vector3 source) |
|||
=> new(source); |
|||
|
|||
/// <summary>
|
|||
/// Initializes the color instance for a source clamped between <value>0</value> and <value>1</value>
|
|||
/// </summary>
|
|||
/// <param name="source">The source to load the color from.</param>
|
|||
/// <returns>The <see cref="Rgb"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Rgb Clamp(Rgb source) |
|||
=> new(Vector3.Clamp(source.AsVector3Unsafe(), Vector3.Zero, Vector3.One)); |
|||
|
|||
/// <summary>
|
|||
/// Expands the color into a generic ("scaled") <see cref="Vector3"/> representation
|
|||
/// with values scaled and usually clamped between <value>0</value> and <value>1</value>.
|
|||
/// The vector components are typically expanded in least to greatest significance order.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="Vector3"/>.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector3 ToScaledVector3() |
|||
{ |
|||
Vector3 v3 = default; |
|||
v3 += this.AsVector3Unsafe(); |
|||
return v3; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"Rgb({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is Rgb other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(Rgb other) |
|||
=> this.AsVector3Unsafe() == other.AsVector3Unsafe(); |
|||
|
|||
internal Vector3 AsVector3Unsafe() => Unsafe.As<Rgb, Vector3>(ref Unsafe.AsRef(in this)); |
|||
|
|||
private static Matrix4x4 GetCieXyzToRgbMatrix(RgbWorkingSpace workingSpace) |
|||
{ |
|||
Matrix4x4 matrix = GetRgbToCieXyzMatrix(workingSpace); |
|||
Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); |
|||
return inverseMatrix; |
|||
} |
|||
|
|||
private static Matrix4x4 GetRgbToCieXyzMatrix(RgbWorkingSpace workingSpace) |
|||
{ |
|||
DebugGuard.NotNull(workingSpace, nameof(workingSpace)); |
|||
RgbPrimariesChromaticityCoordinates chromaticity = workingSpace.ChromaticityCoordinates; |
|||
|
|||
float xr = chromaticity.R.X; |
|||
float xg = chromaticity.G.X; |
|||
float xb = chromaticity.B.X; |
|||
float yr = chromaticity.R.Y; |
|||
float yg = chromaticity.G.Y; |
|||
float yb = chromaticity.B.Y; |
|||
|
|||
float mXr = xr / yr; |
|||
float mZr = (1 - xr - yr) / yr; |
|||
|
|||
float mXg = xg / yg; |
|||
float mZg = (1 - xg - yg) / yg; |
|||
|
|||
float mXb = xb / yb; |
|||
float mZb = (1 - xb - yb) / yb; |
|||
|
|||
Matrix4x4 xyzMatrix = new() |
|||
{ |
|||
M11 = mXr, |
|||
M21 = mXg, |
|||
M31 = mXb, |
|||
M12 = 1F, |
|||
M22 = 1F, |
|||
M32 = 1F, |
|||
M13 = mZr, |
|||
M23 = mZg, |
|||
M33 = mZb, |
|||
M44 = 1F |
|||
}; |
|||
|
|||
Matrix4x4.Invert(xyzMatrix, out Matrix4x4 inverseXyzMatrix); |
|||
|
|||
Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.AsVector3Unsafe(), inverseXyzMatrix); |
|||
|
|||
// Use transposed Rows/Columns
|
|||
return new Matrix4x4 |
|||
{ |
|||
M11 = vector.X * mXr, |
|||
M21 = vector.Y * mXg, |
|||
M31 = vector.Z * mXb, |
|||
M12 = vector.X, |
|||
M22 = vector.Y, |
|||
M32 = vector.Z, |
|||
M13 = vector.X * mZr, |
|||
M23 = vector.Y * mZg, |
|||
M33 = vector.Z * mZb, |
|||
M44 = 1F |
|||
}; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<float> ReadVector512(ref float src) |
|||
{ |
|||
ref byte b = ref Unsafe.As<float, byte>(ref src); |
|||
return Unsafe.ReadUnaligned<Vector512<float>>(ref b); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<float> ReadVector256(ref float src) |
|||
{ |
|||
ref byte b = ref Unsafe.As<float, byte>(ref src); |
|||
return Unsafe.ReadUnaligned<Vector256<float>>(ref b); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void WriteVector512(ref float dst, Vector512<float> value) |
|||
{ |
|||
ref byte b = ref Unsafe.As<float, byte>(ref dst); |
|||
Unsafe.WriteUnaligned(ref b, value); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void WriteVector256(ref float dst, Vector256<float> value) |
|||
{ |
|||
ref byte b = ref Unsafe.As<float, byte>(ref dst); |
|||
Unsafe.WriteUnaligned(ref b, value); |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents the chromaticity coordinates of RGB primaries.
|
|||
/// One of the specifiers of <see cref="RgbWorkingSpace"/>.
|
|||
/// </summary>
|
|||
public readonly struct RgbPrimariesChromaticityCoordinates : IEquatable<RgbPrimariesChromaticityCoordinates> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RgbPrimariesChromaticityCoordinates"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="r">The chromaticity coordinates of the red channel.</param>
|
|||
/// <param name="g">The chromaticity coordinates of the green channel.</param>
|
|||
/// <param name="b">The chromaticity coordinates of the blue channel.</param>
|
|||
public RgbPrimariesChromaticityCoordinates(CieXyChromaticityCoordinates r, CieXyChromaticityCoordinates g, CieXyChromaticityCoordinates b) |
|||
{ |
|||
this.R = r; |
|||
this.G = g; |
|||
this.B = b; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the chromaticity coordinates of the red channel.
|
|||
/// </summary>
|
|||
public CieXyChromaticityCoordinates R { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chromaticity coordinates of the green channel.
|
|||
/// </summary>
|
|||
public CieXyChromaticityCoordinates G { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chromaticity coordinates of the blue channel.
|
|||
/// </summary>
|
|||
public CieXyChromaticityCoordinates B { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="RgbPrimariesChromaticityCoordinates"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="RgbPrimariesChromaticityCoordinates"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="RgbPrimariesChromaticityCoordinates"/> on the right side of the operand.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
public static bool operator ==(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right) |
|||
=> left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="RgbPrimariesChromaticityCoordinates"/> objects for inequality
|
|||
/// </summary>
|
|||
/// <param name="left">
|
|||
/// The <see cref="RgbPrimariesChromaticityCoordinates"/> on the left side of the operand.
|
|||
/// </param>
|
|||
/// <param name="right">
|
|||
/// The <see cref="RgbPrimariesChromaticityCoordinates"/> on the right side of the operand.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
public static bool operator !=(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right) |
|||
=> !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) |
|||
=> obj is RgbPrimariesChromaticityCoordinates other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(RgbPrimariesChromaticityCoordinates other) |
|||
=> this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); |
|||
|
|||
/// <inheritdoc />
|
|||
public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Implementation of the Von Kries chromatic adaptation model.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Transformation described here:
|
|||
/// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
|
|||
/// </remarks>
|
|||
public static class VonKriesChromaticAdaptation |
|||
{ |
|||
/// <summary>
|
|||
/// Performs a linear transformation of a source color in to the destination color.
|
|||
/// </summary>
|
|||
/// <remarks>Doesn't crop the resulting color space coordinates (e.g. allows negative values for XYZ coordinates).</remarks>
|
|||
/// <param name="source">The source color.</param>
|
|||
/// <param name="whitePoints">The conversion white points.</param>
|
|||
/// <param name="matrix">The chromatic adaptation matrix.</param>
|
|||
/// <returns>The <see cref="CieXyz"/></returns>
|
|||
public static CieXyz Transform(in CieXyz source, (CieXyz From, CieXyz To) whitePoints, Matrix4x4 matrix) |
|||
{ |
|||
CieXyz from = whitePoints.From; |
|||
CieXyz to = whitePoints.To; |
|||
|
|||
if (from.Equals(to)) |
|||
{ |
|||
return new CieXyz(source.X, source.Y, source.Z); |
|||
} |
|||
|
|||
Vector3 sourceColorLms = Vector3.Transform(source.AsVector3Unsafe(), matrix); |
|||
Vector3 sourceWhitePointLms = Vector3.Transform(from.AsVector3Unsafe(), matrix); |
|||
Vector3 targetWhitePointLms = Vector3.Transform(to.AsVector3Unsafe(), matrix); |
|||
|
|||
Vector3 vector = targetWhitePointLms / sourceWhitePointLms; |
|||
Vector3 targetColorLms = Vector3.Multiply(vector, sourceColorLms); |
|||
|
|||
Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); |
|||
return new CieXyz(Vector3.Transform(targetColorLms, inverseMatrix)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Performs a bulk linear transformation of a source color in to the destination color.
|
|||
/// </summary>
|
|||
/// <remarks>Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates).</remarks>
|
|||
/// <param name="source">The span to the source colors.</param>
|
|||
/// <param name="destination">The span to the destination colors.</param>
|
|||
/// <param name="whitePoints">The conversion white points.</param>
|
|||
/// <param name="matrix">The chromatic adaptation matrix.</param>
|
|||
public static void Transform( |
|||
ReadOnlySpan<CieXyz> source, |
|||
Span<CieXyz> destination, |
|||
(CieXyz From, CieXyz To) whitePoints, |
|||
Matrix4x4 matrix) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
int count = source.Length; |
|||
|
|||
CieXyz from = whitePoints.From; |
|||
CieXyz to = whitePoints.To; |
|||
|
|||
if (from.Equals(to)) |
|||
{ |
|||
source.CopyTo(destination[..count]); |
|||
return; |
|||
} |
|||
|
|||
Matrix4x4.Invert(matrix, out Matrix4x4 inverseMatrix); |
|||
|
|||
ref CieXyz sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref CieXyz destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
|
|||
Vector3 sourceWhitePointLms = Vector3.Transform(from.AsVector3Unsafe(), matrix); |
|||
Vector3 targetWhitePointLms = Vector3.Transform(to.AsVector3Unsafe(), matrix); |
|||
|
|||
Vector3 vector = targetWhitePointLms / sourceWhitePointLms; |
|||
|
|||
for (nuint i = 0; i < (uint)count; i++) |
|||
{ |
|||
ref CieXyz sp = ref Unsafe.Add(ref sourceBase, i); |
|||
ref CieXyz dp = ref Unsafe.Add(ref destinationBase, i); |
|||
|
|||
Vector3 sourceColorLms = Vector3.Transform(sp.AsVector3Unsafe(), matrix); |
|||
|
|||
Vector3 targetColorLms = Vector3.Multiply(vector, sourceColorLms); |
|||
dp = new CieXyz(Vector3.Transform(targetColorLms, inverseMatrix)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.ColorProfiles.Companding; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; |
|||
|
|||
/// <summary>
|
|||
/// The gamma working space.
|
|||
/// </summary>
|
|||
public sealed class GammaWorkingSpace : RgbWorkingSpace |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="GammaWorkingSpace" /> class.
|
|||
/// </summary>
|
|||
/// <param name="gamma">The gamma value.</param>
|
|||
/// <param name="referenceWhite">The reference white point.</param>
|
|||
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
|
|||
public GammaWorkingSpace(float gamma, CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) |
|||
: base(referenceWhite, chromaticityCoordinates) => this.Gamma = gamma; |
|||
|
|||
/// <summary>
|
|||
/// Gets the gamma value.
|
|||
/// </summary>
|
|||
public float Gamma { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Compress(Span<Vector4> vectors) => GammaCompanding.Compress(vectors, this.Gamma); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Expand(Span<Vector4> vectors) => GammaCompanding.Expand(vectors, this.Gamma); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Vector4 Compress(Vector4 vector) => GammaCompanding.Compress(vector, this.Gamma); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Vector4 Expand(Vector4 vector) => GammaCompanding.Expand(vector, this.Gamma); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) |
|||
{ |
|||
if (obj is null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (ReferenceEquals(this, obj)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (obj is GammaWorkingSpace other) |
|||
{ |
|||
return this.Gamma.Equals(other.Gamma) |
|||
&& this.WhitePoint.Equals(other.WhitePoint) |
|||
&& this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine( |
|||
this.WhitePoint, |
|||
this.ChromaticityCoordinates, |
|||
this.Gamma); |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.ColorProfiles.Companding; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; |
|||
|
|||
/// <summary>
|
|||
/// L* working space.
|
|||
/// </summary>
|
|||
public sealed class LWorkingSpace : RgbWorkingSpace |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LWorkingSpace" /> class.
|
|||
/// </summary>
|
|||
/// <param name="referenceWhite">The reference white point.</param>
|
|||
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
|
|||
public LWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) |
|||
: base(referenceWhite, chromaticityCoordinates) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Compress(Span<Vector4> vectors) => LCompanding.Compress(vectors); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Expand(Span<Vector4> vectors) => LCompanding.Expand(vectors); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Vector4 Compress(Vector4 vector) => LCompanding.Compress(vector); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Vector4 Expand(Vector4 vector) => LCompanding.Expand(vector); |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.ColorProfiles.Companding; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; |
|||
|
|||
/// <summary>
|
|||
/// Rec. 2020 (ITU-R Recommendation BT.2020F) working space.
|
|||
/// </summary>
|
|||
public sealed class Rec2020WorkingSpace : RgbWorkingSpace |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rec2020WorkingSpace" /> class.
|
|||
/// </summary>
|
|||
/// <param name="referenceWhite">The reference white point.</param>
|
|||
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
|
|||
public Rec2020WorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) |
|||
: base(referenceWhite, chromaticityCoordinates) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Compress(Span<Vector4> vectors) => Rec2020Companding.Compress(vectors); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Expand(Span<Vector4> vectors) => Rec2020Companding.Expand(vectors); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Vector4 Compress(Vector4 vector) => Rec2020Companding.Compress(vector); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Vector4 Expand(Vector4 vector) => Rec2020Companding.Expand(vector); |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.ColorProfiles.Companding; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; |
|||
|
|||
/// <summary>
|
|||
/// Rec. 709 (ITU-R Recommendation BT.709) working space.
|
|||
/// </summary>
|
|||
public sealed class Rec709WorkingSpace : RgbWorkingSpace |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Rec709WorkingSpace" /> class.
|
|||
/// </summary>
|
|||
/// <param name="referenceWhite">The reference white point.</param>
|
|||
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
|
|||
public Rec709WorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) |
|||
: base(referenceWhite, chromaticityCoordinates) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Compress(Span<Vector4> vectors) => Rec709Companding.Compress(vectors); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Expand(Span<Vector4> vectors) => Rec709Companding.Expand(vectors); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Vector4 Compress(Vector4 vector) => Rec709Companding.Compress(vector); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Vector4 Expand(Vector4 vector) => Rec709Companding.Expand(vector); |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; |
|||
|
|||
/// <summary>
|
|||
/// Base class for all implementations of <see cref="RgbWorkingSpace"/>.
|
|||
/// </summary>
|
|||
public abstract class RgbWorkingSpace |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RgbWorkingSpace"/> class.
|
|||
/// </summary>
|
|||
/// <param name="referenceWhite">The reference white point.</param>
|
|||
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
|
|||
protected RgbWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) |
|||
{ |
|||
this.WhitePoint = referenceWhite; |
|||
this.ChromaticityCoordinates = chromaticityCoordinates; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the reference white point
|
|||
/// </summary>
|
|||
public CieXyz WhitePoint { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chromaticity of the rgb primaries.
|
|||
/// </summary>
|
|||
public RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vectors to their nonlinear equivalents with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
public abstract void Compress(Span<Vector4> vectors); |
|||
|
|||
/// <summary>
|
|||
/// Expands the nonlinear vectors to their linear equivalents with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vectors">The span of vectors.</param>
|
|||
public abstract void Expand(Span<Vector4> vectors); |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public abstract Vector4 Compress(Vector4 vector); |
|||
|
|||
/// <summary>
|
|||
/// Compresses the linear vector to its nonlinear equivalent with respect to the energy.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector.</param>
|
|||
/// <returns>The <see cref="Vector4"/>.</returns>
|
|||
public abstract Vector4 Expand(Vector4 vector); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) |
|||
{ |
|||
if (obj is null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (ReferenceEquals(this, obj)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (obj is RgbWorkingSpace other) |
|||
{ |
|||
return this.WhitePoint.Equals(other.WhitePoint) |
|||
&& this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
=> HashCode.Combine(this.WhitePoint, this.ChromaticityCoordinates); |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.ColorProfiles.Companding; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; |
|||
|
|||
/// <summary>
|
|||
/// The sRgb working space.
|
|||
/// </summary>
|
|||
public sealed class SRgbWorkingSpace : RgbWorkingSpace |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="SRgbWorkingSpace" /> class.
|
|||
/// </summary>
|
|||
/// <param name="referenceWhite">The reference white point.</param>
|
|||
/// <param name="chromaticityCoordinates">The chromaticity of the rgb primaries.</param>
|
|||
public SRgbWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) |
|||
: base(referenceWhite, chromaticityCoordinates) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Compress(Span<Vector4> vectors) => SRgbCompanding.Compress(vectors); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void Expand(Span<Vector4> vectors) => SRgbCompanding.Expand(vectors); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Vector4 Compress(Vector4 vector) => SRgbCompanding.Compress(vector); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Vector4 Expand(Vector4 vector) => SRgbCompanding.Expand(vector); |
|||
} |
|||
@ -0,0 +1,142 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents a Y (luminance) color.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct Y : IColorProfile<Y, Rgb> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Y"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="l">The luminance component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Y(float l) => this.L = Numerics.Clamp(l, 0, 1); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
|||
private Y(float l, bool _) => this.L = l; |
|||
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
|
|||
|
|||
/// <summary>
|
|||
/// Gets the luminance component.
|
|||
/// </summary>
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
public float L { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Y"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Y"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Y"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(Y left, Y right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="Y"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="Y"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="Y"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(Y left, Y right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() => new(this.L); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Y FromScaledVector4(Vector4 source) => new(source.X, true); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<Y> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToScaledVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<Y> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = FromScaledVector4(source[i]); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Rgb ToProfileConnectingSpace(ColorConversionOptions options) |
|||
=> new(this.L, this.L, this.L); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Y FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) |
|||
{ |
|||
Matrix4x4 m = options.YCbCrTransform.Forward; |
|||
float offset = options.YCbCrTransform.Offset.X; |
|||
return new Y(Vector3.Dot(source.AsVector3Unsafe(), new Vector3(m.M11, m.M12, m.M13)) + offset); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Y> source, Span<Rgb> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: We can optimize this by using SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<Y> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: We can optimize this by using SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Rgb rgb = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in rgb); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.RgbWorkingSpace; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public override int GetHashCode() |
|||
=> this.L.GetHashCode(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
=> FormattableString.Invariant($"Y({this.L:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) |
|||
=> obj is Y other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(Y other) => this.L == other.L; |
|||
} |
|||
@ -0,0 +1,194 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents an YCbCr (luminance, blue chroma, red chroma) color.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct YCbCr : IColorProfile<YCbCr, Rgb> |
|||
{ |
|||
private static readonly Vector3 Min = Vector3.Zero; |
|||
private static readonly Vector3 Max = Vector3.One; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="YCbCr"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="y">The y luminance component.</param>
|
|||
/// <param name="cb">The cb chroma component.</param>
|
|||
/// <param name="cr">The cr chroma component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public YCbCr(float y, float cb, float cr) |
|||
: this(new Vector3(y, cb, cr)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="YCbCr"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the y, cb, cr components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public YCbCr(Vector3 vector) |
|||
{ |
|||
vector = Vector3.Clamp(vector, Min, Max); |
|||
this.Y = vector.X; |
|||
this.Cb = vector.Y; |
|||
this.Cr = vector.Z; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
|||
private YCbCr(Vector3 vector, bool _) |
|||
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
|
|||
{ |
|||
this.Y = vector.X; |
|||
this.Cb = vector.Y; |
|||
this.Cr = vector.Z; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the Y luminance component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Y { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Cb chroma component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Cb { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Cr chroma component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Cr { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="YCbCr"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="YCbCr"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="YCbCr"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
public static bool operator ==(YCbCr left, YCbCr right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="YCbCr"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="YCbCr"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="YCbCr"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(YCbCr left, YCbCr right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() |
|||
{ |
|||
Vector3 v3 = default; |
|||
v3 += this.AsVector3Unsafe(); |
|||
return new Vector4(v3, 1F); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static YCbCr FromScaledVector4(Vector4 source) |
|||
=> new(source.AsVector3(), true); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<YCbCr> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToScaledVector4(); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<YCbCr> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: Optimize via SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = FromScaledVector4(source[i]); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static YCbCr FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) |
|||
{ |
|||
Vector3 rgb = source.AsVector3Unsafe(); |
|||
Matrix4x4 m = options.TransposedYCbCrTransform.Forward; |
|||
Vector3 offset = options.TransposedYCbCrTransform.Offset; |
|||
|
|||
return new YCbCr(Vector3.Transform(rgb, m) + offset, true); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<YCbCr> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: We can optimize this by using SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Rgb rgb = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in rgb); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Rgb ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
Matrix4x4 m = options.TransposedYCbCrTransform.Inverse; |
|||
Vector3 offset = options.TransposedYCbCrTransform.Offset; |
|||
Vector3 normalized = this.AsVector3Unsafe() - offset; |
|||
|
|||
return Rgb.FromScaledVector3(Vector3.Transform(normalized, m)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<YCbCr> source, Span<Rgb> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: We can optimize this by using SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.RgbWorkingSpace; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public override int GetHashCode() => HashCode.Combine(this.Y, this.Cb, this.Cr); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"YCbCr({this.Y}, {this.Cb}, {this.Cr})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is YCbCr other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public bool Equals(YCbCr other) |
|||
=> this.AsVector3Unsafe() == other.AsVector3Unsafe(); |
|||
|
|||
private Vector3 AsVector3Unsafe() => Unsafe.As<YCbCr, Vector3>(ref Unsafe.AsRef(in this)); |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// <para>
|
|||
/// Represents a YCbCr color transform containing forward and inverse transformation matrices,
|
|||
/// and the chrominance offsets to apply for full-range encoding
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// These matrices must be selected to match the characteristics of the associated <see cref="RgbWorkingSpace"/>,
|
|||
/// including its transfer function (gamma or companding) and chromaticity coordinates. Using mismatched matrices and
|
|||
/// working spaces will produce incorrect conversions.
|
|||
/// </para>
|
|||
/// </summary>
|
|||
public readonly struct YCbCrTransform |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="YCbCrTransform"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="forward">
|
|||
/// The forward transformation matrix from RGB to YCbCr. The matrix must include the
|
|||
/// standard chrominance offsets in the fourth column, such as <c>(0, 0.5, 0.5)</c>.
|
|||
/// </param>
|
|||
/// <param name="inverse">
|
|||
/// The inverse transformation matrix from YCbCr to RGB. This matrix expects that
|
|||
/// chrominance offsets have already been subtracted prior to application.
|
|||
/// </param>
|
|||
/// <param name="offset">
|
|||
/// The chrominance offsets to be added after the forward conversion,
|
|||
/// and subtracted before the inverse conversion. Usually <c>(0, 0.5, 0.5)</c>.
|
|||
/// </param>
|
|||
public YCbCrTransform(Matrix4x4 forward, Matrix4x4 inverse, Vector3 offset) |
|||
{ |
|||
this.Forward = forward; |
|||
this.Inverse = inverse; |
|||
this.Offset = offset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the matrix used to convert gamma-encoded RGB to YCbCr.
|
|||
/// </summary>
|
|||
public Matrix4x4 Forward { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the matrix used to convert YCbCr back to gamma-encoded RGB.
|
|||
/// </summary>
|
|||
public Matrix4x4 Inverse { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chrominance offset vector to apply during encoding (add) or decoding (subtract).
|
|||
/// </summary>
|
|||
public Vector3 Offset { get; } |
|||
|
|||
internal YCbCrTransform Transpose() |
|||
=> new(Matrix4x4.Transpose(this.Forward), Matrix4x4.Transpose(this.Inverse), this.Offset); |
|||
} |
|||
@ -0,0 +1,206 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorProfiles; |
|||
|
|||
/// <summary>
|
|||
/// Represents a YCCK (luminance, blue chroma, red chroma, black) color.
|
|||
/// YCCK is not a true color space but a reversible transform of CMYK, where the CMY components
|
|||
/// are converted to YCbCr using the ITU-R BT.601 standard, and the K (black) component is preserved separately.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public readonly struct YccK : IColorProfile<YccK, Rgb> |
|||
{ |
|||
private static readonly Vector4 Min = Vector4.Zero; |
|||
private static readonly Vector4 Max = Vector4.One; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="YccK"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="y">The y luminance component.</param>
|
|||
/// <param name="cb">The cb chroma component.</param>
|
|||
/// <param name="cr">The cr chroma component.</param>
|
|||
/// <param name="k">The keyline black component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public YccK(float y, float cb, float cr, float k) |
|||
: this(new Vector4(y, cb, cr, k)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="YccK"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the c, m, y, k components.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public YccK(Vector4 vector) |
|||
{ |
|||
vector = Vector4.Clamp(vector, Min, Max); |
|||
this.Y = vector.X; |
|||
this.Cb = vector.Y; |
|||
this.Cr = vector.Z; |
|||
this.K = vector.W; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
|||
private YccK(Vector4 vector, bool _) |
|||
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
|
|||
{ |
|||
this.Y = vector.X; |
|||
this.Cb = vector.Y; |
|||
this.Cr = vector.Z; |
|||
this.K = vector.W; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the Y luminance component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Y { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the C (blue) chroma component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Cb { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the C (red) chroma component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float Cr { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the keyline black color component.
|
|||
/// <remarks>A value ranging between 0 and 1.</remarks>
|
|||
/// </summary>
|
|||
public float K { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="YccK"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="YccK"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="YccK"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator ==(YccK left, YccK right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="YccK"/> objects for inequality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="YccK"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="YccK"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static bool operator !=(YccK left, YccK right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Vector4 ToScaledVector4() |
|||
{ |
|||
Vector4 v4 = default; |
|||
v4 += this.AsVector4Unsafe(); |
|||
return v4; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static YccK FromScaledVector4(Vector4 source) |
|||
=> new(source, true); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToScaledVector4(ReadOnlySpan<YccK> source, Span<Vector4> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
MemoryMarshal.Cast<YccK, Vector4>(source).CopyTo(destination); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromScaledVector4(ReadOnlySpan<Vector4> source, Span<YccK> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
MemoryMarshal.Cast<Vector4, YccK>(source).CopyTo(destination); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Rgb ToProfileConnectingSpace(ColorConversionOptions options) |
|||
{ |
|||
Matrix4x4 m = options.TransposedYCbCrTransform.Inverse; |
|||
Vector3 offset = options.TransposedYCbCrTransform.Offset; |
|||
Vector3 normalized = this.AsVector3Unsafe() - offset; |
|||
|
|||
return Rgb.FromScaledVector3(Vector3.Transform(normalized, m) * (1F - this.K)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static YccK FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source) |
|||
{ |
|||
Matrix4x4 m = options.TransposedYCbCrTransform.Forward; |
|||
Vector3 offset = options.TransposedYCbCrTransform.Offset; |
|||
|
|||
Vector3 rgb = source.AsVector3Unsafe(); |
|||
float k = 1F - MathF.Max(rgb.X, MathF.Max(rgb.Y, rgb.Z)); |
|||
|
|||
if (k >= 1F - Constants.Epsilon) |
|||
{ |
|||
return new YccK(new Vector4(0F, 0.5F, 0.5F, 1F), true); |
|||
} |
|||
|
|||
rgb /= 1F - k; |
|||
return new YccK(new Vector4(Vector3.Transform(rgb, m), k) + new Vector4(offset, 0F)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<YccK> source, Span<Rgb> destination) |
|||
{ |
|||
// TODO: We can possibly optimize this by using SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
destination[i] = source[i].ToProfileConnectingSpace(options); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan<Rgb> source, Span<YccK> destination) |
|||
{ |
|||
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); |
|||
|
|||
// TODO: We can optimize this by using SIMD
|
|||
for (int i = 0; i < source.Length; i++) |
|||
{ |
|||
Rgb rgb = source[i]; |
|||
destination[i] = FromProfileConnectingSpace(options, in rgb); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() |
|||
=> ChromaticAdaptionWhitePointSource.RgbWorkingSpace; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public override int GetHashCode() |
|||
=> HashCode.Combine(this.Y, this.Cb, this.Cr, this.K); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
=> FormattableString.Invariant($"YccK({this.Y:#0.##}, {this.Cb:#0.##}, {this.Cr:#0.##}, {this.K:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) |
|||
=> obj is YccK other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(YccK other) |
|||
=> this.AsVector4Unsafe() == other.AsVector4Unsafe(); |
|||
|
|||
private Vector3 AsVector3Unsafe() => Unsafe.As<YccK, Vector3>(ref Unsafe.AsRef(in this)); |
|||
|
|||
private Vector4 AsVector4Unsafe() => Unsafe.As<YccK, Vector4>(ref Unsafe.AsRef(in this)); |
|||
} |
|||
@ -1,135 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.ColorSpaces; |
|||
|
|||
/// <summary>
|
|||
/// Represents a CIE L*a*b* 1976 color.
|
|||
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>
|
|||
/// </summary>
|
|||
public readonly struct CieLab : IEquatable<CieLab> |
|||
{ |
|||
/// <summary>
|
|||
/// D50 standard illuminant.
|
|||
/// Used when reference white is not specified explicitly.
|
|||
/// </summary>
|
|||
public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLab"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="l">The lightness dimension.</param>
|
|||
/// <param name="a">The a (green - magenta) component.</param>
|
|||
/// <param name="b">The b (blue - yellow) component.</param>
|
|||
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public CieLab(float l, float a, float b) |
|||
: this(l, a, b, DefaultWhitePoint) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLab"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="l">The lightness dimension.</param>
|
|||
/// <param name="a">The a (green - magenta) component.</param>
|
|||
/// <param name="b">The b (blue - yellow) component.</param>
|
|||
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public CieLab(float l, float a, float b, CieXyz whitePoint) |
|||
: this(new Vector3(l, a, b), whitePoint) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLab"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the l, a, b components.</param>
|
|||
/// <remarks>Uses <see cref="DefaultWhitePoint"/> as white point.</remarks>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public CieLab(Vector3 vector) |
|||
: this(vector, DefaultWhitePoint) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CieLab"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="vector">The vector representing the l, a, b components.</param>
|
|||
/// <param name="whitePoint">The reference white point. <see cref="Illuminants"/></param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public CieLab(Vector3 vector, CieXyz whitePoint) |
|||
: this() |
|||
{ |
|||
// Not clamping as documentation about this space only indicates "usual" ranges
|
|||
this.L = vector.X; |
|||
this.A = vector.Y; |
|||
this.B = vector.Z; |
|||
this.WhitePoint = whitePoint; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the lightness dimension.
|
|||
/// <remarks>A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
|
|||
/// </summary>
|
|||
public readonly float L { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the a color component.
|
|||
/// <remarks>A value usually ranging from -100 to 100. Negative is green, positive magenta.</remarks>
|
|||
/// </summary>
|
|||
public readonly float A { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the b color component.
|
|||
/// <remarks>A value usually ranging from -100 to 100. Negative is blue, positive is yellow</remarks>
|
|||
/// </summary>
|
|||
public readonly float B { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the reference white point of this color
|
|||
/// </summary>
|
|||
public readonly CieXyz WhitePoint { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieLab"/> objects for equality.
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieLab"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieLab"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static bool operator ==(CieLab left, CieLab right) => left.Equals(right); |
|||
|
|||
/// <summary>
|
|||
/// Compares two <see cref="CieLab"/> objects for inequality
|
|||
/// </summary>
|
|||
/// <param name="left">The <see cref="CieLab"/> on the left side of the operand.</param>
|
|||
/// <param name="right">The <see cref="CieLab"/> on the right side of the operand.</param>
|
|||
/// <returns>
|
|||
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
|
|||
/// </returns>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static bool operator !=(CieLab left, CieLab right) => !left.Equals(right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B, this.WhitePoint); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() => FormattableString.Invariant($"CieLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object? obj) => obj is CieLab other && this.Equals(other); |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public bool Equals(CieLab other) => |
|||
this.L.Equals(other.L) |
|||
&& this.A.Equals(other.A) |
|||
&& this.B.Equals(other.B) |
|||
&& this.WhitePoint.Equals(other.WhitePoint); |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue