diff --git a/.editorconfig b/.editorconfig
index 2e3045fb17..c28089d720 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -172,6 +172,8 @@ dotnet_diagnostic.IDE0063.severity = suggestion
csharp_using_directive_placement = outside_namespace:warning
# Modifier preferences
csharp_prefer_static_local_function = true:warning
+# Primary constructor preferences
+csharp_style_prefer_primary_constructors = false:none
##########################################
# Unnecessary Code Rules
diff --git a/.gitattributes b/.gitattributes
index 3647a7063d..b5f742ab47 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -133,3 +133,6 @@
*.pnm filter=lfs diff=lfs merge=lfs -text
*.wbmp filter=lfs diff=lfs merge=lfs -text
*.exr filter=lfs diff=lfs merge=lfs -text
+*.ico filter=lfs diff=lfs merge=lfs -text
+*.cur filter=lfs diff=lfs merge=lfs -text
+*.ani filter=lfs diff=lfs merge=lfs -text
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index e17b8d724e..a450aebf43 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -20,42 +20,23 @@ jobs:
- ${{ contains(github.event.pull_request.labels.*.name, 'arch:arm32') || contains(github.event.pull_request.labels.*.name, 'arch:arm64') }}
options:
- os: ubuntu-latest
- framework: net7.0
- sdk: 7.0.x
- sdk-preview: true
+ framework: net8.0
+ sdk: 8.0.x
runtime: -x64
codecov: false
- os: macos-13 # macos-latest runs on arm64 runners where libgdiplus is unavailable
- framework: net7.0
- sdk: 7.0.x
- sdk-preview: true
+ framework: net8.0
+ sdk: 8.0.x
runtime: -x64
codecov: false
- os: windows-latest
- framework: net7.0
- sdk: 7.0.x
- sdk-preview: true
+ framework: net8.0
+ sdk: 8.0.x
runtime: -x64
codecov: false
- os: buildjet-4vcpu-ubuntu-2204-arm
- framework: net7.0
- sdk: 7.0.x
- sdk-preview: true
- runtime: -x64
- codecov: false
- - os: ubuntu-latest
- framework: net6.0
- sdk: 6.0.x
- runtime: -x64
- codecov: false
- - os: macos-13 # macos-latest runs on arm64 runners where libgdiplus is unavailable
- framework: net6.0
- sdk: 6.0.x
- runtime: -x64
- codecov: false
- - os: windows-latest
- framework: net6.0
- sdk: 6.0.x
+ framework: net8.0
+ sdk: 8.0.x
runtime: -x64
codecov: false
exclude:
@@ -87,7 +68,7 @@ jobs:
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id
- name: Git Setup LFS Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
id: lfs-cache
with:
path: .git/lfs
@@ -97,10 +78,10 @@ jobs:
run: git lfs pull
- name: NuGet Install
- uses: NuGet/setup-nuget@v1
+ uses: NuGet/setup-nuget@v2
- name: NuGet Setup Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
id: nuget-cache
with:
path: ~/.nuget
@@ -109,17 +90,17 @@ jobs:
- name: DotNet Setup
if: ${{ matrix.options.sdk-preview != true }}
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: |
- 6.0.x
+ 8.0.x
- name: DotNet Setup Preview
if: ${{ matrix.options.sdk-preview == true }}
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: |
- 7.0.x
+ 8.0.x
- name: DotNet Build
if: ${{ matrix.options.sdk-preview != true }}
@@ -152,7 +133,7 @@ jobs:
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Export Failed Output
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: failure()
with:
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
@@ -179,10 +160,10 @@ jobs:
submodules: recursive
- name: NuGet Install
- uses: NuGet/setup-nuget@v1
+ uses: NuGet/setup-nuget@v2
- name: NuGet Setup Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
id: nuget-cache
with:
path: ~/.nuget
diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml
index e551afbd6d..cd22fe5e58 100644
--- a/.github/workflows/code-coverage.yml
+++ b/.github/workflows/code-coverage.yml
@@ -10,7 +10,7 @@ jobs:
matrix:
options:
- os: ubuntu-latest
- framework: net6.0
+ framework: net8.0
runtime: -x64
codecov: true
@@ -34,7 +34,7 @@ jobs:
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id
- name: Git Setup LFS Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
id: lfs-cache
with:
path: .git/lfs
@@ -44,10 +44,10 @@ jobs:
run: git lfs pull
- name: NuGet Install
- uses: NuGet/setup-nuget@v1
+ uses: NuGet/setup-nuget@v2
- name: NuGet Setup Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
id: nuget-cache
with:
path: ~/.nuget
@@ -55,10 +55,10 @@ jobs:
restore-keys: ${{ runner.os }}-nuget-
- name: DotNet Setup
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: |
- 6.0.x
+ 8.0.x
- name: DotNet Build
shell: pwsh
@@ -74,14 +74,14 @@ jobs:
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Export Failed Output
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: failure()
with:
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
path: tests/Images/ActualOutput/
- name: Codecov Update
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v4
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors')
with:
flags: unittests
diff --git a/ImageSharp.sln b/ImageSharp.sln
index 2967acb8ff..7ccd92c07d 100644
--- a/ImageSharp.sln
+++ b/ImageSharp.sln
@@ -38,6 +38,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{815C0625-CD3
src\Directory.Build.props = src\Directory.Build.props
src\Directory.Build.targets = src\Directory.Build.targets
src\README.md = src\README.md
+ src\ImageSharp.ruleset = src\ImageSharp.ruleset
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageSharp\ImageSharp.csproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}"
@@ -237,6 +238,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{5C9B68
tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg = tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg
tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg = tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg
tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg = tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg
+ tests\Images\Input\Jpg\issues\issue-2067-comment.jpg = tests\Images\Input\Jpg\issues\issue-2067-comment.jpg
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fuzz", "fuzz", "{516A3532-6AC2-417B-AD79-9BD5D0D378A0}"
@@ -659,6 +661,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Qoi", "Qoi", "{E801B508-493
tests\Images\Input\Qoi\wikipedia_008.qoi = tests\Images\Input\Qoi\wikipedia_008.qoi
EndProjectSection
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icon", "Icon", "{95E45DDE-A67D-48AD-BBA8-5FAA151B860D}"
+ ProjectSection(SolutionItems) = preProject
+ tests\Images\Input\Icon\aero_arrow.cur = tests\Images\Input\Icon\aero_arrow.cur
+ tests\Images\Input\Icon\flutter.ico = tests\Images\Input\Icon\flutter.ico
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -712,6 +720,7 @@ Global
{670DD46C-82E9-499A-B2D2-00A802ED0141} = {E1C42A6F-913B-4A7B-B1A8-2BB62843B254}
{5DFC394F-136F-4B76-9BCA-3BA786515EFC} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{E801B508-4935-41CD-BA85-CF11BFF55A45} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
+ {95E45DDE-A67D-48AD-BBA8-5FAA151B860D} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}
diff --git a/README.md b/README.md
index fa51d57cdf..cf58b6b14b 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Designed to simplify image processing, ImageSharp brings you an incredibly power
ImageSharp is designed from the ground up to be flexible and extensible. The library provides API endpoints for common image processing operations and the building blocks to allow for the development of additional operations.
-Built against [.NET 6](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios.
+Built against [.NET 8](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios.
## License
@@ -64,7 +64,7 @@ If you prefer, you can compile ImageSharp yourself (please do and help!)
- Using [Visual Studio 2022](https://visualstudio.microsoft.com/vs/)
- Make sure you have the latest version installed
- - Make sure you have [the .NET 7 SDK](https://www.microsoft.com/net/core#windows) installed
+ - Make sure you have [the .NET 8 SDK](https://www.microsoft.com/net/core#windows) installed
Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**:
diff --git a/shared-infrastructure b/shared-infrastructure
index 353b9afe32..1dbfb576c8 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit 353b9afe32a8000410312d17263407cd7bb82d19
+Subproject commit 1dbfb576c83507645265c79e03369b66cdc0379f
diff --git a/src/ImageSharp.ruleset b/src/ImageSharp.ruleset
index e88c43f838..b609890200 100644
--- a/src/ImageSharp.ruleset
+++ b/src/ImageSharp.ruleset
@@ -1,7 +1,4 @@
-
-
-
\ No newline at end of file
diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
index 9629b0097e..a959faa3b6 100644
--- a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
+++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
@@ -51,7 +51,7 @@ public void Invoke(int i)
for (int y = yMin; y < yMax; y++)
{
// Skip the safety copy when invoking a potentially impure method on a readonly field
- Unsafe.AsRef(this.action).Invoke(y);
+ Unsafe.AsRef(in this.action).Invoke(y);
}
}
}
@@ -102,7 +102,7 @@ public void Invoke(int i)
for (int y = yMin; y < yMax; y++)
{
- Unsafe.AsRef(this.action).Invoke(y, span);
+ Unsafe.AsRef(in this.action).Invoke(y, span);
}
}
}
diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs
index 657654a84b..1284a3a898 100644
--- a/src/ImageSharp/Advanced/ParallelRowIterator.cs
+++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs
@@ -58,7 +58,7 @@ public static void IterateRows(
{
for (int y = top; y < bottom; y++)
{
- Unsafe.AsRef(operation).Invoke(y);
+ Unsafe.AsRef(in operation).Invoke(y);
}
return;
@@ -118,7 +118,7 @@ public static void IterateRows(
int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
MemoryAllocator allocator = parallelSettings.MemoryAllocator;
- int bufferLength = Unsafe.AsRef(operation).GetRequiredBufferLength(rectangle);
+ int bufferLength = Unsafe.AsRef(in operation).GetRequiredBufferLength(rectangle);
// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
@@ -128,7 +128,7 @@ public static void IterateRows(
for (int y = top; y < bottom; y++)
{
- Unsafe.AsRef(operation).Invoke(y, span);
+ Unsafe.AsRef(in operation).Invoke(y, span);
}
return;
@@ -245,7 +245,7 @@ public static void IterateRowIntervals(
int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
MemoryAllocator allocator = parallelSettings.MemoryAllocator;
- int bufferLength = Unsafe.AsRef(operation).GetRequiredBufferLength(rectangle);
+ int bufferLength = Unsafe.AsRef(in operation).GetRequiredBufferLength(rectangle);
// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
@@ -253,7 +253,7 @@ public static void IterateRowIntervals(
var rows = new RowInterval(top, bottom);
using IMemoryOwner buffer = allocator.Allocate(bufferLength);
- Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span);
+ Unsafe.AsRef(in operation).Invoke(in rows, buffer.Memory.Span);
return;
}
diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs
deleted file mode 100644
index 309ab83ec4..0000000000
--- a/src/ImageSharp/Color/Color.Conversions.cs
+++ /dev/null
@@ -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;
-
-///
-/// Contains constructors and implicit conversion methods.
-///
-public readonly partial struct Color
-{
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The containing the color information.
- [MethodImpl(InliningOptions.ShortMethod)]
- public Color(Rgba64 pixel)
- {
- this.data = pixel;
- this.boxedHighPrecisionPixel = null;
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The containing the color information.
- [MethodImpl(InliningOptions.ShortMethod)]
- public Color(Rgb48 pixel)
- {
- this.data = new Rgba64(pixel.R, pixel.G, pixel.B, ushort.MaxValue);
- this.boxedHighPrecisionPixel = null;
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The containing the color information.
- [MethodImpl(InliningOptions.ShortMethod)]
- public Color(La32 pixel)
- {
- this.data = new Rgba64(pixel.L, pixel.L, pixel.L, pixel.A);
- this.boxedHighPrecisionPixel = null;
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The containing the color information.
- [MethodImpl(InliningOptions.ShortMethod)]
- public Color(L16 pixel)
- {
- this.data = new Rgba64(pixel.PackedValue, pixel.PackedValue, pixel.PackedValue, ushort.MaxValue);
- this.boxedHighPrecisionPixel = null;
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The containing the color information.
- [MethodImpl(InliningOptions.ShortMethod)]
- public Color(Rgba32 pixel)
- {
- this.data = new Rgba64(pixel);
- this.boxedHighPrecisionPixel = null;
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The containing the color information.
- [MethodImpl(InliningOptions.ShortMethod)]
- public Color(Argb32 pixel)
- {
- this.data = new Rgba64(pixel);
- this.boxedHighPrecisionPixel = null;
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The containing the color information.
- [MethodImpl(InliningOptions.ShortMethod)]
- public Color(Bgra32 pixel)
- {
- this.data = new Rgba64(pixel);
- this.boxedHighPrecisionPixel = null;
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The containing the color information.
- [MethodImpl(InliningOptions.ShortMethod)]
- public Color(Abgr32 pixel)
- {
- this.data = new Rgba64(pixel);
- this.boxedHighPrecisionPixel = null;
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The containing the color information.
- [MethodImpl(InliningOptions.ShortMethod)]
- public Color(Rgb24 pixel)
- {
- this.data = new Rgba64(pixel);
- this.boxedHighPrecisionPixel = null;
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The containing the color information.
- [MethodImpl(InliningOptions.ShortMethod)]
- public Color(Bgr24 pixel)
- {
- this.data = new Rgba64(pixel);
- this.boxedHighPrecisionPixel = null;
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The containing the color information.
- [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;
- }
-
- ///
- /// Converts a to .
- ///
- /// The .
- /// The .
- public static explicit operator Vector4(Color color) => color.ToScaledVector4();
-
- ///
- /// Converts an to .
- ///
- /// The .
- /// The .
- [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 ToScaledVector4()
- {
- if (this.boxedHighPrecisionPixel is null)
- {
- return this.data.ToScaledVector4();
- }
-
- return this.boxedHighPrecisionPixel.ToScaledVector4();
- }
-}
diff --git a/src/ImageSharp/Color/Color.NamedColors.cs b/src/ImageSharp/Color/Color.NamedColors.cs
index f8b4c90fd6..00130dd904 100644
--- a/src/ImageSharp/Color/Color.NamedColors.cs
+++ b/src/ImageSharp/Color/Color.NamedColors.cs
@@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using SixLabors.ImageSharp.PixelFormats;
+
namespace SixLabors.ImageSharp;
///
@@ -9,107 +11,107 @@ namespace SixLabors.ImageSharp;
///
public readonly partial struct Color
{
- private static readonly Lazy> NamedColorsLookupLazy = new Lazy>(CreateNamedColorsLookup, true);
+ private static readonly Lazy> NamedColorsLookupLazy = new(CreateNamedColorsLookup, true);
///
/// Represents a matching the W3C definition that has an hex value of #F0F8FF.
///
- public static readonly Color AliceBlue = FromRgba(240, 248, 255, 255);
+ public static readonly Color AliceBlue = FromPixel(new Rgba32(240, 248, 255, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FAEBD7.
///
- public static readonly Color AntiqueWhite = FromRgba(250, 235, 215, 255);
+ public static readonly Color AntiqueWhite = FromPixel(new Rgba32(250, 235, 215, 255));
///
/// Represents a matching the W3C definition that has an hex value of #00FFFF.
///
- public static readonly Color Aqua = FromRgba(0, 255, 255, 255);
+ public static readonly Color Aqua = FromPixel(new Rgba32(0, 255, 255, 255));
///
/// Represents a matching the W3C definition that has an hex value of #7FFFD4.
///
- public static readonly Color Aquamarine = FromRgba(127, 255, 212, 255);
+ public static readonly Color Aquamarine = FromPixel(new Rgba32(127, 255, 212, 255));
///
/// Represents a matching the W3C definition that has an hex value of #F0FFFF.
///
- public static readonly Color Azure = FromRgba(240, 255, 255, 255);
+ public static readonly Color Azure = FromPixel(new Rgba32(240, 255, 255, 255));
///
/// Represents a matching the W3C definition that has an hex value of #F5F5DC.
///
- public static readonly Color Beige = FromRgba(245, 245, 220, 255);
+ public static readonly Color Beige = FromPixel(new Rgba32(245, 245, 220, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFE4C4.
///
- public static readonly Color Bisque = FromRgba(255, 228, 196, 255);
+ public static readonly Color Bisque = FromPixel(new Rgba32(255, 228, 196, 255));
///
/// Represents a matching the W3C definition that has an hex value of #000000.
///
- public static readonly Color Black = FromRgba(0, 0, 0, 255);
+ public static readonly Color Black = FromPixel(new Rgba32(0, 0, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFEBCD.
///
- public static readonly Color BlanchedAlmond = FromRgba(255, 235, 205, 255);
+ public static readonly Color BlanchedAlmond = FromPixel(new Rgba32(255, 235, 205, 255));
///
/// Represents a matching the W3C definition that has an hex value of #0000FF.
///
- public static readonly Color Blue = FromRgba(0, 0, 255, 255);
+ public static readonly Color Blue = FromPixel(new Rgba32(0, 0, 255, 255));
///
/// Represents a matching the W3C definition that has an hex value of #8A2BE2.
///
- public static readonly Color BlueViolet = FromRgba(138, 43, 226, 255);
+ public static readonly Color BlueViolet = FromPixel(new Rgba32(138, 43, 226, 255));
///
/// Represents a matching the W3C definition that has an hex value of #A52A2A.
///
- public static readonly Color Brown = FromRgba(165, 42, 42, 255);
+ public static readonly Color Brown = FromPixel(new Rgba32(165, 42, 42, 255));
///
/// Represents a matching the W3C definition that has an hex value of #DEB887.
///
- public static readonly Color BurlyWood = FromRgba(222, 184, 135, 255);
+ public static readonly Color BurlyWood = FromPixel(new Rgba32(222, 184, 135, 255));
///
/// Represents a matching the W3C definition that has an hex value of #5F9EA0.
///
- public static readonly Color CadetBlue = FromRgba(95, 158, 160, 255);
+ public static readonly Color CadetBlue = FromPixel(new Rgba32(95, 158, 160, 255));
///
/// Represents a matching the W3C definition that has an hex value of #7FFF00.
///
- public static readonly Color Chartreuse = FromRgba(127, 255, 0, 255);
+ public static readonly Color Chartreuse = FromPixel(new Rgba32(127, 255, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #D2691E.
///
- public static readonly Color Chocolate = FromRgba(210, 105, 30, 255);
+ public static readonly Color Chocolate = FromPixel(new Rgba32(210, 105, 30, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FF7F50.
///
- public static readonly Color Coral = FromRgba(255, 127, 80, 255);
+ public static readonly Color Coral = FromPixel(new Rgba32(255, 127, 80, 255));
///
/// Represents a matching the W3C definition that has an hex value of #6495ED.
///
- public static readonly Color CornflowerBlue = FromRgba(100, 149, 237, 255);
+ public static readonly Color CornflowerBlue = FromPixel(new Rgba32(100, 149, 237, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFF8DC.
///
- public static readonly Color Cornsilk = FromRgba(255, 248, 220, 255);
+ public static readonly Color Cornsilk = FromPixel(new Rgba32(255, 248, 220, 255));
///
/// Represents a matching the W3C definition that has an hex value of #DC143C.
///
- public static readonly Color Crimson = FromRgba(220, 20, 60, 255);
+ public static readonly Color Crimson = FromPixel(new Rgba32(220, 20, 60, 255));
///
/// Represents a matching the W3C definition that has an hex value of #00FFFF.
@@ -119,27 +121,27 @@ public readonly partial struct Color
///
/// Represents a matching the W3C definition that has an hex value of #00008B.
///
- public static readonly Color DarkBlue = FromRgba(0, 0, 139, 255);
+ public static readonly Color DarkBlue = FromPixel(new Rgba32(0, 0, 139, 255));
///
/// Represents a matching the W3C definition that has an hex value of #008B8B.
///
- public static readonly Color DarkCyan = FromRgba(0, 139, 139, 255);
+ public static readonly Color DarkCyan = FromPixel(new Rgba32(0, 139, 139, 255));
///
/// Represents a matching the W3C definition that has an hex value of #B8860B.
///
- public static readonly Color DarkGoldenrod = FromRgba(184, 134, 11, 255);
+ public static readonly Color DarkGoldenrod = FromPixel(new Rgba32(184, 134, 11, 255));
///
/// Represents a matching the W3C definition that has an hex value of #A9A9A9.
///
- public static readonly Color DarkGray = FromRgba(169, 169, 169, 255);
+ public static readonly Color DarkGray = FromPixel(new Rgba32(169, 169, 169, 255));
///
/// Represents a matching the W3C definition that has an hex value of #006400.
///
- public static readonly Color DarkGreen = FromRgba(0, 100, 0, 255);
+ public static readonly Color DarkGreen = FromPixel(new Rgba32(0, 100, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #A9A9A9.
@@ -149,52 +151,52 @@ public readonly partial struct Color
///
/// Represents a matching the W3C definition that has an hex value of #BDB76B.
///
- public static readonly Color DarkKhaki = FromRgba(189, 183, 107, 255);
+ public static readonly Color DarkKhaki = FromPixel(new Rgba32(189, 183, 107, 255));
///
/// Represents a matching the W3C definition that has an hex value of #8B008B.
///
- public static readonly Color DarkMagenta = FromRgba(139, 0, 139, 255);
+ public static readonly Color DarkMagenta = FromPixel(new Rgba32(139, 0, 139, 255));
///
/// Represents a matching the W3C definition that has an hex value of #556B2F.
///
- public static readonly Color DarkOliveGreen = FromRgba(85, 107, 47, 255);
+ public static readonly Color DarkOliveGreen = FromPixel(new Rgba32(85, 107, 47, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FF8C00.
///
- public static readonly Color DarkOrange = FromRgba(255, 140, 0, 255);
+ public static readonly Color DarkOrange = FromPixel(new Rgba32(255, 140, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #9932CC.
///
- public static readonly Color DarkOrchid = FromRgba(153, 50, 204, 255);
+ public static readonly Color DarkOrchid = FromPixel(new Rgba32(153, 50, 204, 255));
///
/// Represents a matching the W3C definition that has an hex value of #8B0000.
///
- public static readonly Color DarkRed = FromRgba(139, 0, 0, 255);
+ public static readonly Color DarkRed = FromPixel(new Rgba32(139, 0, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #E9967A.
///
- public static readonly Color DarkSalmon = FromRgba(233, 150, 122, 255);
+ public static readonly Color DarkSalmon = FromPixel(new Rgba32(233, 150, 122, 255));
///
/// Represents a matching the W3C definition that has an hex value of #8FBC8F.
///
- public static readonly Color DarkSeaGreen = FromRgba(143, 188, 143, 255);
+ public static readonly Color DarkSeaGreen = FromPixel(new Rgba32(143, 188, 143, 255));
///
/// Represents a matching the W3C definition that has an hex value of #483D8B.
///
- public static readonly Color DarkSlateBlue = FromRgba(72, 61, 139, 255);
+ public static readonly Color DarkSlateBlue = FromPixel(new Rgba32(72, 61, 139, 255));
///
/// Represents a matching the W3C definition that has an hex value of #2F4F4F.
///
- public static readonly Color DarkSlateGray = FromRgba(47, 79, 79, 255);
+ public static readonly Color DarkSlateGray = FromPixel(new Rgba32(47, 79, 79, 255));
///
/// Represents a matching the W3C definition that has an hex value of #2F4F4F.
@@ -204,27 +206,27 @@ public readonly partial struct Color
///
/// Represents a matching the W3C definition that has an hex value of #00CED1.
///
- public static readonly Color DarkTurquoise = FromRgba(0, 206, 209, 255);
+ public static readonly Color DarkTurquoise = FromPixel(new Rgba32(0, 206, 209, 255));
///
/// Represents a matching the W3C definition that has an hex value of #9400D3.
///
- public static readonly Color DarkViolet = FromRgba(148, 0, 211, 255);
+ public static readonly Color DarkViolet = FromPixel(new Rgba32(148, 0, 211, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FF1493.
///
- public static readonly Color DeepPink = FromRgba(255, 20, 147, 255);
+ public static readonly Color DeepPink = FromPixel(new Rgba32(255, 20, 147, 255));
///
/// Represents a matching the W3C definition that has an hex value of #00BFFF.
///
- public static readonly Color DeepSkyBlue = FromRgba(0, 191, 255, 255);
+ public static readonly Color DeepSkyBlue = FromPixel(new Rgba32(0, 191, 255, 255));
///
/// Represents a matching the W3C definition that has an hex value of #696969.
///
- public static readonly Color DimGray = FromRgba(105, 105, 105, 255);
+ public static readonly Color DimGray = FromPixel(new Rgba32(105, 105, 105, 255));
///
/// Represents a matching the W3C definition that has an hex value of #696969.
@@ -234,62 +236,62 @@ public readonly partial struct Color
///
/// Represents a matching the W3C definition that has an hex value of #1E90FF.
///
- public static readonly Color DodgerBlue = FromRgba(30, 144, 255, 255);
+ public static readonly Color DodgerBlue = FromPixel(new Rgba32(30, 144, 255, 255));
///
/// Represents a matching the W3C definition that has an hex value of #B22222.
///
- public static readonly Color Firebrick = FromRgba(178, 34, 34, 255);
+ public static readonly Color Firebrick = FromPixel(new Rgba32(178, 34, 34, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFFAF0.
///
- public static readonly Color FloralWhite = FromRgba(255, 250, 240, 255);
+ public static readonly Color FloralWhite = FromPixel(new Rgba32(255, 250, 240, 255));
///
/// Represents a matching the W3C definition that has an hex value of #228B22.
///
- public static readonly Color ForestGreen = FromRgba(34, 139, 34, 255);
+ public static readonly Color ForestGreen = FromPixel(new Rgba32(34, 139, 34, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FF00FF.
///
- public static readonly Color Fuchsia = FromRgba(255, 0, 255, 255);
+ public static readonly Color Fuchsia = FromPixel(new Rgba32(255, 0, 255, 255));
///
/// Represents a matching the W3C definition that has an hex value of #DCDCDC.
///
- public static readonly Color Gainsboro = FromRgba(220, 220, 220, 255);
+ public static readonly Color Gainsboro = FromPixel(new Rgba32(220, 220, 220, 255));
///
/// Represents a matching the W3C definition that has an hex value of #F8F8FF.
///
- public static readonly Color GhostWhite = FromRgba(248, 248, 255, 255);
+ public static readonly Color GhostWhite = FromPixel(new Rgba32(248, 248, 255, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFD700.
///
- public static readonly Color Gold = FromRgba(255, 215, 0, 255);
+ public static readonly Color Gold = FromPixel(new Rgba32(255, 215, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #DAA520.
///
- public static readonly Color Goldenrod = FromRgba(218, 165, 32, 255);
+ public static readonly Color Goldenrod = FromPixel(new Rgba32(218, 165, 32, 255));
///
/// Represents a matching the W3C definition that has an hex value of #808080.
///
- public static readonly Color Gray = FromRgba(128, 128, 128, 255);
+ public static readonly Color Gray = FromPixel(new Rgba32(128, 128, 128, 255));
///
/// Represents a matching the W3C definition that has an hex value of #008000.
///
- public static readonly Color Green = FromRgba(0, 128, 0, 255);
+ public static readonly Color Green = FromPixel(new Rgba32(0, 128, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #ADFF2F.
///
- public static readonly Color GreenYellow = FromRgba(173, 255, 47, 255);
+ public static readonly Color GreenYellow = FromPixel(new Rgba32(173, 255, 47, 255));
///
/// Represents a matching the W3C definition that has an hex value of #808080.
@@ -299,82 +301,82 @@ public readonly partial struct Color
///
/// Represents a matching the W3C definition that has an hex value of #F0FFF0.
///
- public static readonly Color Honeydew = FromRgba(240, 255, 240, 255);
+ public static readonly Color Honeydew = FromPixel(new Rgba32(240, 255, 240, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FF69B4.
///
- public static readonly Color HotPink = FromRgba(255, 105, 180, 255);
+ public static readonly Color HotPink = FromPixel(new Rgba32(255, 105, 180, 255));
///
/// Represents a matching the W3C definition that has an hex value of #CD5C5C.
///
- public static readonly Color IndianRed = FromRgba(205, 92, 92, 255);
+ public static readonly Color IndianRed = FromPixel(new Rgba32(205, 92, 92, 255));
///
/// Represents a matching the W3C definition that has an hex value of #4B0082.
///
- public static readonly Color Indigo = FromRgba(75, 0, 130, 255);
+ public static readonly Color Indigo = FromPixel(new Rgba32(75, 0, 130, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFFFF0.
///
- public static readonly Color Ivory = FromRgba(255, 255, 240, 255);
+ public static readonly Color Ivory = FromPixel(new Rgba32(255, 255, 240, 255));
///
/// Represents a matching the W3C definition that has an hex value of #F0E68C.
///
- public static readonly Color Khaki = FromRgba(240, 230, 140, 255);
+ public static readonly Color Khaki = FromPixel(new Rgba32(240, 230, 140, 255));
///
/// Represents a matching the W3C definition that has an hex value of #E6E6FA.
///
- public static readonly Color Lavender = FromRgba(230, 230, 250, 255);
+ public static readonly Color Lavender = FromPixel(new Rgba32(230, 230, 250, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFF0F5.
///
- public static readonly Color LavenderBlush = FromRgba(255, 240, 245, 255);
+ public static readonly Color LavenderBlush = FromPixel(new Rgba32(255, 240, 245, 255));
///
/// Represents a matching the W3C definition that has an hex value of #7CFC00.
///
- public static readonly Color LawnGreen = FromRgba(124, 252, 0, 255);
+ public static readonly Color LawnGreen = FromPixel(new Rgba32(124, 252, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFFACD.
///
- public static readonly Color LemonChiffon = FromRgba(255, 250, 205, 255);
+ public static readonly Color LemonChiffon = FromPixel(new Rgba32(255, 250, 205, 255));
///
/// Represents a matching the W3C definition that has an hex value of #ADD8E6.
///
- public static readonly Color LightBlue = FromRgba(173, 216, 230, 255);
+ public static readonly Color LightBlue = FromPixel(new Rgba32(173, 216, 230, 255));
///
/// Represents a matching the W3C definition that has an hex value of #F08080.
///
- public static readonly Color LightCoral = FromRgba(240, 128, 128, 255);
+ public static readonly Color LightCoral = FromPixel(new Rgba32(240, 128, 128, 255));
///
/// Represents a matching the W3C definition that has an hex value of #E0FFFF.
///
- public static readonly Color LightCyan = FromRgba(224, 255, 255, 255);
+ public static readonly Color LightCyan = FromPixel(new Rgba32(224, 255, 255, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FAFAD2.
///
- public static readonly Color LightGoldenrodYellow = FromRgba(250, 250, 210, 255);
+ public static readonly Color LightGoldenrodYellow = FromPixel(new Rgba32(250, 250, 210, 255));
///
/// Represents a matching the W3C definition that has an hex value of #D3D3D3.
///
- public static readonly Color LightGray = FromRgba(211, 211, 211, 255);
+ public static readonly Color LightGray = FromPixel(new Rgba32(211, 211, 211, 255));
///
/// Represents a matching the W3C definition that has an hex value of #90EE90.
///
- public static readonly Color LightGreen = FromRgba(144, 238, 144, 255);
+ public static readonly Color LightGreen = FromPixel(new Rgba32(144, 238, 144, 255));
///
/// Represents a matching the W3C definition that has an hex value of #D3D3D3.
@@ -384,27 +386,27 @@ public readonly partial struct Color
///
/// Represents a matching the W3C definition that has an hex value of #FFB6C1.
///
- public static readonly Color LightPink = FromRgba(255, 182, 193, 255);
+ public static readonly Color LightPink = FromPixel(new Rgba32(255, 182, 193, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFA07A.
///
- public static readonly Color LightSalmon = FromRgba(255, 160, 122, 255);
+ public static readonly Color LightSalmon = FromPixel(new Rgba32(255, 160, 122, 255));
///
/// Represents a matching the W3C definition that has an hex value of #20B2AA.
///
- public static readonly Color LightSeaGreen = FromRgba(32, 178, 170, 255);
+ public static readonly Color LightSeaGreen = FromPixel(new Rgba32(32, 178, 170, 255));
///
/// Represents a matching the W3C definition that has an hex value of #87CEFA.
///
- public static readonly Color LightSkyBlue = FromRgba(135, 206, 250, 255);
+ public static readonly Color LightSkyBlue = FromPixel(new Rgba32(135, 206, 250, 255));
///
/// Represents a matching the W3C definition that has an hex value of #778899.
///
- public static readonly Color LightSlateGray = FromRgba(119, 136, 153, 255);
+ public static readonly Color LightSlateGray = FromPixel(new Rgba32(119, 136, 153, 255));
///
/// Represents a matching the W3C definition that has an hex value of #778899.
@@ -414,27 +416,27 @@ public readonly partial struct Color
///
/// Represents a matching the W3C definition that has an hex value of #B0C4DE.
///
- public static readonly Color LightSteelBlue = FromRgba(176, 196, 222, 255);
+ public static readonly Color LightSteelBlue = FromPixel(new Rgba32(176, 196, 222, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFFFE0.
///
- public static readonly Color LightYellow = FromRgba(255, 255, 224, 255);
+ public static readonly Color LightYellow = FromPixel(new Rgba32(255, 255, 224, 255));
///
/// Represents a matching the W3C definition that has an hex value of #00FF00.
///
- public static readonly Color Lime = FromRgba(0, 255, 0, 255);
+ public static readonly Color Lime = FromPixel(new Rgba32(0, 255, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #32CD32.
///
- public static readonly Color LimeGreen = FromRgba(50, 205, 50, 255);
+ public static readonly Color LimeGreen = FromPixel(new Rgba32(50, 205, 50, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FAF0E6.
///
- public static readonly Color Linen = FromRgba(250, 240, 230, 255);
+ public static readonly Color Linen = FromPixel(new Rgba32(250, 240, 230, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FF00FF.
@@ -444,237 +446,237 @@ public readonly partial struct Color
///
/// Represents a matching the W3C definition that has an hex value of #800000.
///
- public static readonly Color Maroon = FromRgba(128, 0, 0, 255);
+ public static readonly Color Maroon = FromPixel(new Rgba32(128, 0, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #66CDAA.
///
- public static readonly Color MediumAquamarine = FromRgba(102, 205, 170, 255);
+ public static readonly Color MediumAquamarine = FromPixel(new Rgba32(102, 205, 170, 255));
///
/// Represents a matching the W3C definition that has an hex value of #0000CD.
///
- public static readonly Color MediumBlue = FromRgba(0, 0, 205, 255);
+ public static readonly Color MediumBlue = FromPixel(new Rgba32(0, 0, 205, 255));
///
/// Represents a matching the W3C definition that has an hex value of #BA55D3.
///
- public static readonly Color MediumOrchid = FromRgba(186, 85, 211, 255);
+ public static readonly Color MediumOrchid = FromPixel(new Rgba32(186, 85, 211, 255));
///
/// Represents a matching the W3C definition that has an hex value of #9370DB.
///
- public static readonly Color MediumPurple = FromRgba(147, 112, 219, 255);
+ public static readonly Color MediumPurple = FromPixel(new Rgba32(147, 112, 219, 255));
///
/// Represents a matching the W3C definition that has an hex value of #3CB371.
///
- public static readonly Color MediumSeaGreen = FromRgba(60, 179, 113, 255);
+ public static readonly Color MediumSeaGreen = FromPixel(new Rgba32(60, 179, 113, 255));
///
/// Represents a matching the W3C definition that has an hex value of #7B68EE.
///
- public static readonly Color MediumSlateBlue = FromRgba(123, 104, 238, 255);
+ public static readonly Color MediumSlateBlue = FromPixel(new Rgba32(123, 104, 238, 255));
///
/// Represents a matching the W3C definition that has an hex value of #00FA9A.
///
- public static readonly Color MediumSpringGreen = FromRgba(0, 250, 154, 255);
+ public static readonly Color MediumSpringGreen = FromPixel(new Rgba32(0, 250, 154, 255));
///
/// Represents a matching the W3C definition that has an hex value of #48D1CC.
///
- public static readonly Color MediumTurquoise = FromRgba(72, 209, 204, 255);
+ public static readonly Color MediumTurquoise = FromPixel(new Rgba32(72, 209, 204, 255));
///
/// Represents a matching the W3C definition that has an hex value of #C71585.
///
- public static readonly Color MediumVioletRed = FromRgba(199, 21, 133, 255);
+ public static readonly Color MediumVioletRed = FromPixel(new Rgba32(199, 21, 133, 255));
///
/// Represents a matching the W3C definition that has an hex value of #191970.
///
- public static readonly Color MidnightBlue = FromRgba(25, 25, 112, 255);
+ public static readonly Color MidnightBlue = FromPixel(new Rgba32(25, 25, 112, 255));
///
/// Represents a matching the W3C definition that has an hex value of #F5FFFA.
///
- public static readonly Color MintCream = FromRgba(245, 255, 250, 255);
+ public static readonly Color MintCream = FromPixel(new Rgba32(245, 255, 250, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFE4E1.
///
- public static readonly Color MistyRose = FromRgba(255, 228, 225, 255);
+ public static readonly Color MistyRose = FromPixel(new Rgba32(255, 228, 225, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFE4B5.
///
- public static readonly Color Moccasin = FromRgba(255, 228, 181, 255);
+ public static readonly Color Moccasin = FromPixel(new Rgba32(255, 228, 181, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFDEAD.
///
- public static readonly Color NavajoWhite = FromRgba(255, 222, 173, 255);
+ public static readonly Color NavajoWhite = FromPixel(new Rgba32(255, 222, 173, 255));
///
/// Represents a matching the W3C definition that has an hex value of #000080.
///
- public static readonly Color Navy = FromRgba(0, 0, 128, 255);
+ public static readonly Color Navy = FromPixel(new Rgba32(0, 0, 128, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FDF5E6.
///
- public static readonly Color OldLace = FromRgba(253, 245, 230, 255);
+ public static readonly Color OldLace = FromPixel(new Rgba32(253, 245, 230, 255));
///
/// Represents a matching the W3C definition that has an hex value of #808000.
///
- public static readonly Color Olive = FromRgba(128, 128, 0, 255);
+ public static readonly Color Olive = FromPixel(new Rgba32(128, 128, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #6B8E23.
///
- public static readonly Color OliveDrab = FromRgba(107, 142, 35, 255);
+ public static readonly Color OliveDrab = FromPixel(new Rgba32(107, 142, 35, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFA500.
///
- public static readonly Color Orange = FromRgba(255, 165, 0, 255);
+ public static readonly Color Orange = FromPixel(new Rgba32(255, 165, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FF4500.
///
- public static readonly Color OrangeRed = FromRgba(255, 69, 0, 255);
+ public static readonly Color OrangeRed = FromPixel(new Rgba32(255, 69, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #DA70D6.
///
- public static readonly Color Orchid = FromRgba(218, 112, 214, 255);
+ public static readonly Color Orchid = FromPixel(new Rgba32(218, 112, 214, 255));
///
/// Represents a matching the W3C definition that has an hex value of #EEE8AA.
///
- public static readonly Color PaleGoldenrod = FromRgba(238, 232, 170, 255);
+ public static readonly Color PaleGoldenrod = FromPixel(new Rgba32(238, 232, 170, 255));
///
/// Represents a matching the W3C definition that has an hex value of #98FB98.
///
- public static readonly Color PaleGreen = FromRgba(152, 251, 152, 255);
+ public static readonly Color PaleGreen = FromPixel(new Rgba32(152, 251, 152, 255));
///
/// Represents a matching the W3C definition that has an hex value of #AFEEEE.
///
- public static readonly Color PaleTurquoise = FromRgba(175, 238, 238, 255);
+ public static readonly Color PaleTurquoise = FromPixel(new Rgba32(175, 238, 238, 255));
///
/// Represents a matching the W3C definition that has an hex value of #DB7093.
///
- public static readonly Color PaleVioletRed = FromRgba(219, 112, 147, 255);
+ public static readonly Color PaleVioletRed = FromPixel(new Rgba32(219, 112, 147, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFEFD5.
///
- public static readonly Color PapayaWhip = FromRgba(255, 239, 213, 255);
+ public static readonly Color PapayaWhip = FromPixel(new Rgba32(255, 239, 213, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFDAB9.
///
- public static readonly Color PeachPuff = FromRgba(255, 218, 185, 255);
+ public static readonly Color PeachPuff = FromPixel(new Rgba32(255, 218, 185, 255));
///
/// Represents a matching the W3C definition that has an hex value of #CD853F.
///
- public static readonly Color Peru = FromRgba(205, 133, 63, 255);
+ public static readonly Color Peru = FromPixel(new Rgba32(205, 133, 63, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFC0CB.
///
- public static readonly Color Pink = FromRgba(255, 192, 203, 255);
+ public static readonly Color Pink = FromPixel(new Rgba32(255, 192, 203, 255));
///
/// Represents a matching the W3C definition that has an hex value of #DDA0DD.
///
- public static readonly Color Plum = FromRgba(221, 160, 221, 255);
+ public static readonly Color Plum = FromPixel(new Rgba32(221, 160, 221, 255));
///
/// Represents a matching the W3C definition that has an hex value of #B0E0E6.
///
- public static readonly Color PowderBlue = FromRgba(176, 224, 230, 255);
+ public static readonly Color PowderBlue = FromPixel(new Rgba32(176, 224, 230, 255));
///
/// Represents a matching the W3C definition that has an hex value of #800080.
///
- public static readonly Color Purple = FromRgba(128, 0, 128, 255);
+ public static readonly Color Purple = FromPixel(new Rgba32(128, 0, 128, 255));
///
/// Represents a matching the W3C definition that has an hex value of #663399.
///
- public static readonly Color RebeccaPurple = FromRgba(102, 51, 153, 255);
+ public static readonly Color RebeccaPurple = FromPixel(new Rgba32(102, 51, 153, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FF0000.
///
- public static readonly Color Red = FromRgba(255, 0, 0, 255);
+ public static readonly Color Red = FromPixel(new Rgba32(255, 0, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #BC8F8F.
///
- public static readonly Color RosyBrown = FromRgba(188, 143, 143, 255);
+ public static readonly Color RosyBrown = FromPixel(new Rgba32(188, 143, 143, 255));
///
/// Represents a matching the W3C definition that has an hex value of #4169E1.
///
- public static readonly Color RoyalBlue = FromRgba(65, 105, 225, 255);
+ public static readonly Color RoyalBlue = FromPixel(new Rgba32(65, 105, 225, 255));
///
/// Represents a matching the W3C definition that has an hex value of #8B4513.
///
- public static readonly Color SaddleBrown = FromRgba(139, 69, 19, 255);
+ public static readonly Color SaddleBrown = FromPixel(new Rgba32(139, 69, 19, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FA8072.
///
- public static readonly Color Salmon = FromRgba(250, 128, 114, 255);
+ public static readonly Color Salmon = FromPixel(new Rgba32(250, 128, 114, 255));
///
/// Represents a matching the W3C definition that has an hex value of #F4A460.
///
- public static readonly Color SandyBrown = FromRgba(244, 164, 96, 255);
+ public static readonly Color SandyBrown = FromPixel(new Rgba32(244, 164, 96, 255));
///
/// Represents a matching the W3C definition that has an hex value of #2E8B57.
///
- public static readonly Color SeaGreen = FromRgba(46, 139, 87, 255);
+ public static readonly Color SeaGreen = FromPixel(new Rgba32(46, 139, 87, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFF5EE.
///
- public static readonly Color SeaShell = FromRgba(255, 245, 238, 255);
+ public static readonly Color SeaShell = FromPixel(new Rgba32(255, 245, 238, 255));
///
/// Represents a matching the W3C definition that has an hex value of #A0522D.
///
- public static readonly Color Sienna = FromRgba(160, 82, 45, 255);
+ public static readonly Color Sienna = FromPixel(new Rgba32(160, 82, 45, 255));
///
/// Represents a matching the W3C definition that has an hex value of #C0C0C0.
///
- public static readonly Color Silver = FromRgba(192, 192, 192, 255);
+ public static readonly Color Silver = FromPixel(new Rgba32(192, 192, 192, 255));
///
/// Represents a matching the W3C definition that has an hex value of #87CEEB.
///
- public static readonly Color SkyBlue = FromRgba(135, 206, 235, 255);
+ public static readonly Color SkyBlue = FromPixel(new Rgba32(135, 206, 235, 255));
///
/// Represents a matching the W3C definition that has an hex value of #6A5ACD.
///
- public static readonly Color SlateBlue = FromRgba(106, 90, 205, 255);
+ public static readonly Color SlateBlue = FromPixel(new Rgba32(106, 90, 205, 255));
///
/// Represents a matching the W3C definition that has an hex value of #708090.
///
- public static readonly Color SlateGray = FromRgba(112, 128, 144, 255);
+ public static readonly Color SlateGray = FromPixel(new Rgba32(112, 128, 144, 255));
///
/// Represents a matching the W3C definition that has an hex value of #708090.
@@ -684,81 +686,80 @@ public readonly partial struct Color
///
/// Represents a matching the W3C definition that has an hex value of #FFFAFA.
///
- public static readonly Color Snow = FromRgba(255, 250, 250, 255);
+ public static readonly Color Snow = FromPixel(new Rgba32(255, 250, 250, 255));
///
/// Represents a matching the W3C definition that has an hex value of #00FF7F.
///
- public static readonly Color SpringGreen = FromRgba(0, 255, 127, 255);
+ public static readonly Color SpringGreen = FromPixel(new Rgba32(0, 255, 127, 255));
///
/// Represents a matching the W3C definition that has an hex value of #4682B4.
///
- public static readonly Color SteelBlue = FromRgba(70, 130, 180, 255);
+ public static readonly Color SteelBlue = FromPixel(new Rgba32(70, 130, 180, 255));
///
/// Represents a matching the W3C definition that has an hex value of #D2B48C.
///
- public static readonly Color Tan = FromRgba(210, 180, 140, 255);
+ public static readonly Color Tan = FromPixel(new Rgba32(210, 180, 140, 255));
///
/// Represents a matching the W3C definition that has an hex value of #008080.
///
- public static readonly Color Teal = FromRgba(0, 128, 128, 255);
+ public static readonly Color Teal = FromPixel(new Rgba32(0, 128, 128, 255));
///
/// Represents a matching the W3C definition that has an hex value of #D8BFD8.
///
- public static readonly Color Thistle = FromRgba(216, 191, 216, 255);
+ public static readonly Color Thistle = FromPixel(new Rgba32(216, 191, 216, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FF6347.
///
- public static readonly Color Tomato = FromRgba(255, 99, 71, 255);
+ public static readonly Color Tomato = FromPixel(new Rgba32(255, 99, 71, 255));
///
/// Represents a matching the W3C definition that has an hex value of #00000000.
///
- public static readonly Color Transparent = FromRgba(0, 0, 0, 0);
+ public static readonly Color Transparent = FromPixel(new Rgba32(0, 0, 0, 0));
///
/// Represents a matching the W3C definition that has an hex value of #40E0D0.
///
- public static readonly Color Turquoise = FromRgba(64, 224, 208, 255);
+ public static readonly Color Turquoise = FromPixel(new Rgba32(64, 224, 208, 255));
///
/// Represents a matching the W3C definition that has an hex value of #EE82EE.
///
- public static readonly Color Violet = FromRgba(238, 130, 238, 255);
+ public static readonly Color Violet = FromPixel(new Rgba32(238, 130, 238, 255));
///
/// Represents a matching the W3C definition that has an hex value of #F5DEB3.
///
- public static readonly Color Wheat = FromRgba(245, 222, 179, 255);
+ public static readonly Color Wheat = FromPixel(new Rgba32(245, 222, 179, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFFFFF.
///
- public static readonly Color White = FromRgba(255, 255, 255, 255);
+ public static readonly Color White = FromPixel(new Rgba32(255, 255, 255, 255));
///
/// Represents a matching the W3C definition that has an hex value of #F5F5F5.
///
- public static readonly Color WhiteSmoke = FromRgba(245, 245, 245, 255);
+ public static readonly Color WhiteSmoke = FromPixel(new Rgba32(245, 245, 245, 255));
///
/// Represents a matching the W3C definition that has an hex value of #FFFF00.
///
- public static readonly Color Yellow = FromRgba(255, 255, 0, 255);
+ public static readonly Color Yellow = FromPixel(new Rgba32(255, 255, 0, 255));
///
/// Represents a matching the W3C definition that has an hex value of #9ACD32.
///
- public static readonly Color YellowGreen = FromRgba(154, 205, 50, 255);
+ public static readonly Color YellowGreen = FromPixel(new Rgba32(154, 205, 50, 255));
private static Dictionary CreateNamedColorsLookup()
- {
- return new Dictionary(StringComparer.OrdinalIgnoreCase)
+ => new(StringComparer.OrdinalIgnoreCase)
{
{ nameof(AliceBlue), AliceBlue },
{ nameof(AntiqueWhite), AntiqueWhite },
@@ -910,5 +911,4 @@ private static Dictionary CreateNamedColorsLookup()
{ nameof(Yellow), Yellow },
{ nameof(YellowGreen), YellowGreen }
};
- }
}
diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs
index cebceabe09..8f54680ec4 100644
--- a/src/ImageSharp/Color/Color.cs
+++ b/src/ImageSharp/Color/Color.cs
@@ -18,34 +18,25 @@ namespace SixLabors.ImageSharp;
///
public readonly partial struct Color : IEquatable
{
- private readonly Rgba64 data;
+ private readonly Vector4 data;
private readonly IPixel? boxedHighPrecisionPixel;
- [MethodImpl(InliningOptions.ShortMethod)]
- private Color(byte r, byte g, byte b, byte a)
- {
- this.data = new Rgba64(
- ColorNumerics.UpscaleFrom8BitTo16Bit(r),
- ColorNumerics.UpscaleFrom8BitTo16Bit(g),
- ColorNumerics.UpscaleFrom8BitTo16Bit(b),
- ColorNumerics.UpscaleFrom8BitTo16Bit(a));
-
- this.boxedHighPrecisionPixel = null;
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private Color(byte r, byte g, byte b)
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The containing the color information.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private Color(Vector4 vector)
{
- this.data = new Rgba64(
- ColorNumerics.UpscaleFrom8BitTo16Bit(r),
- ColorNumerics.UpscaleFrom8BitTo16Bit(g),
- ColorNumerics.UpscaleFrom8BitTo16Bit(b),
- ushort.MaxValue);
-
+ this.data = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
this.boxedHighPrecisionPixel = null;
}
- [MethodImpl(InliningOptions.ShortMethod)]
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The pixel containing color information.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
private Color(IPixel pixel)
{
this.boxedHighPrecisionPixel = pixel;
@@ -61,7 +52,7 @@ private Color(IPixel pixel)
/// True if the parameter is equal to the parameter;
/// otherwise, false.
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Color left, Color right) => left.Equals(right);
///
@@ -73,66 +64,64 @@ private Color(IPixel pixel)
/// True if the parameter is not equal to the parameter;
/// otherwise, false.
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Color left, Color right) => !left.Equals(right);
///
- /// Creates a from RGBA bytes.
+ /// Creates a from the given .
///
- /// The red component (0-255).
- /// The green component (0-255).
- /// The blue component (0-255).
- /// The alpha component (0-255).
+ /// The pixel to convert from.
+ /// The pixel format.
/// The .
- [MethodImpl(InliningOptions.ShortMethod)]
- public static Color FromRgba(byte r, byte g, byte b, byte a) => new(r, g, b, a);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Color FromPixel(TPixel source)
+ where TPixel : unmanaged, IPixel
+ {
+ // Avoid boxing in case we can convert to Vector4 safely and efficiently
+ PixelTypeInfo info = TPixel.GetPixelTypeInfo();
+ if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32)
+ {
+ return new(source.ToScaledVector4());
+ }
+
+ return new(source);
+ }
///
- /// Creates a from RGB bytes.
+ /// Creates a from a generic scaled .
///
- /// The red component (0-255).
- /// The green component (0-255).
- /// The blue component (0-255).
+ /// The vector to load the pixel from.
/// The .
- [MethodImpl(InliningOptions.ShortMethod)]
- public static Color FromRgb(byte r, byte g, byte b) => new(r, g, b);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Color FromScaledVector(Vector4 source) => new(source);
///
- /// Creates a from the given .
+ /// Bulk converts a span of a specified type to a span of .
///
- /// The pixel to convert from.
- /// The pixel format.
- /// The .
- [MethodImpl(InliningOptions.ShortMethod)]
- public static Color FromPixel(TPixel pixel)
+ /// The pixel type to convert to.
+ /// The source pixel span.
+ /// The destination color span.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void FromPixel(ReadOnlySpan source, Span destination)
where TPixel : unmanaged, IPixel
{
- // Avoid boxing in case we can convert to Rgba64 safely and efficently
- if (typeof(TPixel) == typeof(Rgba64))
- {
- return new((Rgba64)(object)pixel);
- }
- else if (typeof(TPixel) == typeof(Rgb48))
- {
- return new((Rgb48)(object)pixel);
- }
- else if (typeof(TPixel) == typeof(La32))
- {
- return new((La32)(object)pixel);
- }
- else if (typeof(TPixel) == typeof(L16))
- {
- return new((L16)(object)pixel);
- }
- else if (Unsafe.SizeOf() <= Unsafe.SizeOf())
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // Avoid boxing in case we can convert to Vector4 safely and efficiently
+ PixelTypeInfo info = TPixel.GetPixelTypeInfo();
+ if (info.ComponentInfo.HasValue && info.ComponentInfo.Value.GetMaximumComponentPrecision() <= (int)PixelComponentBitDepth.Bit32)
{
- Rgba32 p = default;
- pixel.ToRgba32(ref p);
- return new(p);
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destination[i] = FromScaledVector(source[i].ToScaledVector4());
+ }
}
else
{
- return new(pixel);
+ for (int i = 0; i < destination.Length; i++)
+ {
+ destination[i] = new(source[i]);
+ }
}
}
@@ -147,12 +136,11 @@ public static Color FromPixel(TPixel pixel)
///
/// The .
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color ParseHex(string hex)
{
Rgba32 rgba = Rgba32.ParseHex(hex);
-
- return new Color(rgba);
+ return FromPixel(rgba);
}
///
@@ -167,14 +155,14 @@ public static Color ParseHex(string hex)
///
/// The .
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseHex(string hex, out Color result)
{
result = default;
if (Rgba32.TryParseHex(hex, out Rgba32 rgba))
{
- result = new Color(rgba);
+ result = FromPixel(rgba);
return true;
}
@@ -241,26 +229,24 @@ public static bool TryParse(string input, out Color result)
/// The color having it's alpha channel altered.
public Color WithAlpha(float alpha)
{
- Vector4 v = (Vector4)this;
+ Vector4 v = this.ToScaledVector4();
v.W = alpha;
- return new Color(v);
+ return FromScaledVector(v);
}
///
/// Gets the hexadecimal representation of the color instance in rrggbbaa form.
///
/// A hexadecimal string representation of the value.
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ToHex()
{
if (this.boxedHighPrecisionPixel is not null)
{
- Rgba32 rgba = default;
- this.boxedHighPrecisionPixel.ToRgba32(ref rgba);
- return rgba.ToHex();
+ return this.boxedHighPrecisionPixel.ToRgba32().ToHex();
}
- return this.data.ToRgba32().ToHex();
+ return Rgba32.FromScaledVector4(this.data).ToHex();
}
///
@@ -270,8 +256,8 @@ public string ToHex()
/// Converts the color instance to a specified type.
///
/// The pixel type to convert to.
- /// The pixel value.
- [MethodImpl(InliningOptions.ShortMethod)]
+ /// The .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public TPixel ToPixel()
where TPixel : unmanaged, IPixel
{
@@ -282,14 +268,27 @@ public TPixel ToPixel()
if (this.boxedHighPrecisionPixel is null)
{
- pixel = default;
- pixel.FromRgba64(this.data);
- return pixel;
+ return TPixel.FromScaledVector4(this.data);
+ }
+
+ return TPixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
+ }
+
+ ///
+ /// Expands the color into a generic ("scaled") representation
+ /// with values scaled and clamped between 0 and 1.
+ /// The vector components are typically expanded in least to greatest significance order.
+ ///
+ /// The .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Vector4 ToScaledVector4()
+ {
+ if (this.boxedHighPrecisionPixel is null)
+ {
+ return this.data;
}
- pixel = default;
- pixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
- return pixel;
+ return this.boxedHighPrecisionPixel.ToScaledVector4();
}
///
@@ -298,11 +297,12 @@ public TPixel ToPixel()
/// The pixel type to convert to.
/// The source color span.
/// The destination pixel span.
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ToPixel(ReadOnlySpan source, Span destination)
where TPixel : unmanaged, IPixel
{
- // TODO: Investigate bulk operations utilizing configuration parameter here.
+ // We cannot use bulk pixel operations here as there is no guarantee that the source colors are
+ // created from pixel formats which fit into the unboxed vector data.
Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
for (int i = 0; i < source.Length; i++)
{
@@ -311,12 +311,12 @@ public static void ToPixel(ReadOnlySpan source, Span dest
}
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Color other)
{
if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null)
{
- return this.data.PackedValue == other.data.PackedValue;
+ return this.data == other.data;
}
return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true;
@@ -326,12 +326,12 @@ public bool Equals(Color other)
public override bool Equals(object? obj) => obj is Color other && this.Equals(other);
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
if (this.boxedHighPrecisionPixel is null)
{
- return this.data.PackedValue.GetHashCode();
+ return this.data.GetHashCode();
}
return this.boxedHighPrecisionPixel.GetHashCode();
diff --git a/src/ImageSharp/ColorProfiles/ChromaticAdaptionWhitePointSource.cs b/src/ImageSharp/ColorProfiles/ChromaticAdaptionWhitePointSource.cs
new file mode 100644
index 0000000000..7e4a9c413b
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ChromaticAdaptionWhitePointSource.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// Enumerate the possible sources of the white point used in chromatic adaptation.
+///
+public enum ChromaticAdaptionWhitePointSource
+{
+ ///
+ /// The white point of the source color space.
+ ///
+ WhitePoint,
+
+ ///
+ /// The white point of the source working space.
+ ///
+ RgbWorkingSpace
+}
diff --git a/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs b/src/ImageSharp/ColorProfiles/CieConstants.cs
similarity index 67%
rename from src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs
rename to src/ImageSharp/ColorProfiles/CieConstants.cs
index 7c87944043..d13a84450f 100644
--- a/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs
+++ b/src/ImageSharp/ColorProfiles/CieConstants.cs
@@ -1,7 +1,7 @@
-// Copyright (c) Six Labors.
+// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
-namespace SixLabors.ImageSharp.ColorSpaces.Conversion;
+namespace SixLabors.ImageSharp.ColorProfiles;
///
/// Constants use for Cie conversion calculations
@@ -12,10 +12,10 @@ internal static class CieConstants
///
/// 216F / 24389F
///
- public const float Epsilon = 0.008856452F;
+ public const float Epsilon = 216f / 24389f;
///
/// 24389F / 27F
///
- public const float Kappa = 903.2963F;
+ public const float Kappa = 24389f / 27f;
}
diff --git a/src/ImageSharp/ColorProfiles/CieLab.cs b/src/ImageSharp/ColorProfiles/CieLab.cs
new file mode 100644
index 0000000000..377cc20a99
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/CieLab.cs
@@ -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;
+
+///
+/// Represents a CIE L*a*b* 1976 color.
+///
+///
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct CieLab : IProfileConnectingSpace
+{
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The a (green - magenta) component.
+ /// The b (blue - yellow) component.
+ [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;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, a, b components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLab(Vector3 vector)
+ : this()
+ {
+ this.L = vector.X;
+ this.A = vector.Y;
+ this.B = vector.Z;
+ }
+
+ ///
+ /// Gets the lightness dimension.
+ /// A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white).
+ ///
+ public float L { get; }
+
+ ///
+ /// Gets the a color component.
+ /// A value usually ranging from -100 to 100. Negative is green, positive magenta.
+ ///
+ public float A { get; }
+
+ ///
+ /// Gets the b color component.
+ /// A value usually ranging from -100 to 100. Negative is blue, positive is yellow
+ ///
+ public float B { get; }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(CieLab left, CieLab right) => left.Equals(right);
+
+ ///
+ /// Compares two objects for inequality
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(CieLab left, CieLab right) => !left.Equals(right);
+
+ ///
+ [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);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span 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);
+ }
+ }
+
+ ///
+ [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.WhitePoint;
+ Vector3 wxyz = new(whitePoint.X, whitePoint.Y, whitePoint.Z);
+ Vector3 xyzr = new(xr, yr, zr);
+
+ return new(xyzr * wxyz);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ for (int i = 0; i < source.Length; i++)
+ {
+ CieLab lab = source[i];
+ destination[i] = lab.ToProfileConnectingSpace(options);
+ }
+ }
+
+ ///
+ public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource()
+ => ChromaticAdaptionWhitePointSource.WhitePoint;
+
+ ///
+ public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B);
+
+ ///
+ public override string ToString() => FormattableString.Invariant($"CieLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})");
+
+ ///
+ public override bool Equals(object? obj) => obj is CieLab other && this.Equals(other);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(CieLab other)
+ => this.AsVector3Unsafe() == other.AsVector3Unsafe();
+
+ private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this));
+}
diff --git a/src/ImageSharp/ColorSpaces/CieLch.cs b/src/ImageSharp/ColorProfiles/CieLch.cs
similarity index 50%
rename from src/ImageSharp/ColorSpaces/CieLch.cs
rename to src/ImageSharp/ColorProfiles/CieLch.cs
index 48e8e2c6d9..1319783406 100644
--- a/src/ImageSharp/ColorSpaces/CieLch.cs
+++ b/src/ImageSharp/ColorProfiles/CieLch.cs
@@ -3,21 +3,17 @@
using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
-namespace SixLabors.ImageSharp.ColorSpaces;
+namespace SixLabors.ImageSharp.ColorProfiles;
///
/// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color.
///
///
-public readonly struct CieLch : IEquatable
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct CieLch : IColorProfile
{
- ///
- /// D50 standard illuminant.
- /// Used when reference white is not specified explicitly.
- ///
- public static readonly CieXyz DefaultWhitePoint = Illuminants.D50;
-
private static readonly Vector3 Min = new(0, -200, 0);
private static readonly Vector3 Max = new(100, 200, 360);
@@ -27,23 +23,9 @@ namespace SixLabors.ImageSharp.ColorSpaces;
/// The lightness dimension.
/// The chroma, relative saturation.
/// The hue in degrees.
- /// Uses as white point.
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLch(float l, float c, float h)
- : this(l, c, h, DefaultWhitePoint)
- {
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The lightness dimension.
- /// The chroma, relative saturation.
- /// The hue in degrees.
- /// The reference white point.
- [MethodImpl(InliningOptions.ShortMethod)]
- public CieLch(float l, float c, float h, CieXyz whitePoint)
- : this(new Vector3(l, c, h), whitePoint)
+ : this(new Vector3(l, c, h))
{
}
@@ -51,50 +33,32 @@ public CieLch(float l, float c, float h, CieXyz whitePoint)
/// Initializes a new instance of the struct.
///
/// The vector representing the l, c, h components.
- /// Uses as white point.
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLch(Vector3 vector)
- : this(vector, DefaultWhitePoint)
- {
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The vector representing the l, c, h components.
- /// The reference white point.
- [MethodImpl(InliningOptions.ShortMethod)]
- public CieLch(Vector3 vector, CieXyz whitePoint)
{
vector = Vector3.Clamp(vector, Min, Max);
this.L = vector.X;
this.C = vector.Y;
this.H = vector.Z;
- this.WhitePoint = whitePoint;
}
///
/// Gets the lightness dimension.
/// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).
///
- public readonly float L { get; }
+ public float L { get; }
///
/// Gets the a chroma component.
/// A value ranging from 0 to 200.
///
- public readonly float C { get; }
+ public float C { get; }
///
/// Gets the h° hue component in degrees.
/// A value ranging from 0 to 360.
///
- public readonly float H { get; }
-
- ///
- /// Gets the reference white point of this color
- ///
- public readonly CieXyz WhitePoint { get; }
+ public float H { get; }
///
/// Compares two objects for equality.
@@ -104,7 +68,7 @@ public CieLch(Vector3 vector, CieXyz whitePoint)
///
/// True if the current left is equal to the parameter; otherwise, false.
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(CieLch left, CieLch right) => left.Equals(right);
///
@@ -115,45 +79,88 @@ public CieLch(Vector3 vector, CieXyz whitePoint)
///
/// True if the current left is unequal to the parameter; otherwise, false.
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieLch left, CieLch right) => !left.Equals(right);
///
- public override int GetHashCode()
- => HashCode.Combine(this.L, this.C, this.H, this.WhitePoint);
+ 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;
+ }
- ///
- public override string ToString() => FormattableString.Invariant($"CieLch({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})");
+ return new CieLch(l, c, hDegrees);
+ }
///
- [MethodImpl(InliningOptions.ShortMethod)]
- public override bool Equals(object? obj) => obj is CieLch other && this.Equals(other);
+ public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span 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);
+ }
+ }
///
- [MethodImpl(InliningOptions.ShortMethod)]
- public bool Equals(CieLch other)
- => this.L.Equals(other.L)
- && this.C.Equals(other.C)
- && this.H.Equals(other.H)
- && this.WhitePoint.Equals(other.WhitePoint);
+ 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);
- ///
- /// Computes the saturation of the color (chroma normalized by lightness)
- ///
- ///
- /// A value ranging from 0 to 100.
- ///
- /// The
- [MethodImpl(InliningOptions.ShortMethod)]
- public float Saturation()
+ float a = c * MathF.Cos(hRadians);
+ float b = c * MathF.Sin(hRadians);
+
+ return new CieLab(l, a, b);
+ }
+
+ ///
+ public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination)
{
- float result = 100 * (this.C / this.L);
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
- if (float.IsNaN(result))
+ for (int i = 0; i < source.Length; i++)
{
- return 0;
+ CieLch lch = source[i];
+ destination[i] = lch.ToProfileConnectingSpace(options);
}
-
- return result;
}
+
+ ///
+ public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource()
+ => ChromaticAdaptionWhitePointSource.WhitePoint;
+
+ ///
+ public override int GetHashCode()
+ => HashCode.Combine(this.L, this.C, this.H);
+
+ ///
+ public override string ToString() => FormattableString.Invariant($"CieLch({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})");
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object? obj) => obj is CieLch other && this.Equals(other);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(CieLch other)
+ => this.AsVector3Unsafe() == other.AsVector3Unsafe();
+
+ private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this));
}
diff --git a/src/ImageSharp/ColorProfiles/CieLchuv.cs b/src/ImageSharp/ColorProfiles/CieLchuv.cs
new file mode 100644
index 0000000000..7fd95feb19
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/CieLchuv.cs
@@ -0,0 +1,167 @@
+// 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;
+
+///
+/// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color.
+///
+///
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct CieLchuv : IColorProfile
+{
+ private static readonly Vector3 Min = new(0, -200, 0);
+ private static readonly Vector3 Max = new(100, 200, 360);
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The chroma, relative saturation.
+ /// The hue in degrees.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLchuv(float l, float c, float h)
+ : this(new Vector3(l, c, h))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, c, h components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLchuv(Vector3 vector)
+ : this()
+ {
+ vector = Vector3.Clamp(vector, Min, Max);
+ this.L = vector.X;
+ this.C = vector.Y;
+ this.H = vector.Z;
+ }
+
+ ///
+ /// Gets the lightness dimension.
+ /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).
+ ///
+ public float L { get; }
+
+ ///
+ /// Gets the a chroma component.
+ /// A value ranging from 0 to 200.
+ ///
+ public float C { get; }
+
+ ///
+ /// Gets the h° hue component in degrees.
+ /// A value ranging from 0 to 360.
+ ///
+ public float H { get; }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(CieLchuv left, CieLchuv right) => left.Equals(right);
+
+ ///
+ /// Compares two objects for inequality
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(CieLchuv left, CieLchuv right) => !left.Equals(right);
+
+ ///
+ 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);
+ }
+
+ ///
+ public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span 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);
+ }
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+ for (int i = 0; i < source.Length; i++)
+ {
+ CieLchuv lch = source[i];
+ destination[i] = lch.ToProfileConnectingSpace(options);
+ }
+ }
+
+ ///
+ public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource()
+ => ChromaticAdaptionWhitePointSource.WhitePoint;
+
+ ///
+ public override int GetHashCode()
+ => HashCode.Combine(this.L, this.C, this.H);
+
+ ///
+ public override string ToString()
+ => FormattableString.Invariant($"CieLchuv({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})");
+
+ ///
+ public override bool Equals(object? obj)
+ => obj is CieLchuv other && this.Equals(other);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(CieLchuv other)
+ => this.AsVector3Unsafe() == other.AsVector3Unsafe();
+
+ private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this));
+}
diff --git a/src/ImageSharp/ColorProfiles/CieLuv.cs b/src/ImageSharp/ColorProfiles/CieLuv.cs
new file mode 100644
index 0000000000..97e2826f74
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/CieLuv.cs
@@ -0,0 +1,221 @@
+// 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;
+
+///
+/// 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
+///
+///
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct CieLuv : IColorProfile
+{
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The blue-yellow chromaticity coordinate of the given white point.
+ /// The red-green chromaticity coordinate of the given white point.
+ [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;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, u, v components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLuv(Vector3 vector)
+ : this()
+ {
+ this.L = vector.X;
+ this.U = vector.Y;
+ this.V = vector.Z;
+ }
+
+ ///
+ /// Gets the lightness dimension
+ /// A value usually ranging between 0 and 100.
+ ///
+ public float L { get; }
+
+ ///
+ /// Gets the blue-yellow chromaticity coordinate of the given white point.
+ /// A value usually ranging between -100 and 100.
+ ///
+ public float U { get; }
+
+ ///
+ /// Gets the red-green chromaticity coordinate of the given white point.
+ /// A value usually ranging between -100 and 100.
+ ///
+ public float V { get; }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(CieLuv left, CieLuv right) => left.Equals(right);
+
+ ///
+ /// Compares two objects for inequality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(CieLuv left, CieLuv right) => !left.Equals(right);
+
+ ///
+ 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);
+ }
+
+ ///
+ public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span 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);
+ }
+ }
+
+ ///
+ 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.WhitePoint;
+
+ 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);
+ }
+
+ ///
+ public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+ for (int i = 0; i < source.Length; i++)
+ {
+ CieLuv luv = source[i];
+ destination[i] = luv.ToProfileConnectingSpace(options);
+ }
+ }
+
+ ///
+ public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource()
+ => ChromaticAdaptionWhitePointSource.WhitePoint;
+
+ ///
+ public override int GetHashCode() => HashCode.Combine(this.L, this.U, this.V);
+
+ ///
+ public override string ToString() => FormattableString.Invariant($"CieLuv({this.L:#0.##}, {this.U:#0.##}, {this.V:#0.##})");
+
+ ///
+ public override bool Equals(object? obj) => obj is CieLuv other && this.Equals(other);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(CieLuv other)
+ => this.AsVector3Unsafe() == other.AsVector3Unsafe();
+
+ private Vector3 AsVector3Unsafe() => Unsafe.As(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));
+}
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorProfiles/CieXyChromaticityCoordinates.cs
similarity index 88%
rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyChromaticityCoordinates.cs
rename to src/ImageSharp/ColorProfiles/CieXyChromaticityCoordinates.cs
index 2cc785d53b..fa12b81d22 100644
--- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyChromaticityCoordinates.cs
+++ b/src/ImageSharp/ColorProfiles/CieXyChromaticityCoordinates.cs
@@ -1,14 +1,16 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
-// ReSharper disable CompareOfFloatsByEqualityOperator
-namespace SixLabors.ImageSharp.ColorSpaces.Conversion;
+namespace SixLabors.ImageSharp.ColorProfiles;
///
/// Represents the coordinates of CIEXY chromaticity space.
///
+[StructLayout(LayoutKind.Sequential)]
public readonly struct CieXyChromaticityCoordinates : IEquatable
{
///
@@ -29,7 +31,7 @@ public CieXyChromaticityCoordinates(float x, float y)
///
/// Ranges usually from 0 to 1.
///
- public readonly float X { get; }
+ public float X { get; }
///
/// Gets the chromaticity Y-coordinate
@@ -37,7 +39,7 @@ public CieXyChromaticityCoordinates(float x, float y)
///
/// Ranges usually from 0 to 1.
///
- public readonly float Y { get; }
+ public float Y { get; }
///
/// Compares two objects for equality.
@@ -79,5 +81,7 @@ public override bool Equals(object? obj)
///
[MethodImpl(InliningOptions.ShortMethod)]
public bool Equals(CieXyChromaticityCoordinates other)
- => this.X.Equals(other.X) && this.Y.Equals(other.Y);
+ => this.AsVector2Unsafe() == other.AsVector2Unsafe();
+
+ private Vector2 AsVector2Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this));
}
diff --git a/src/ImageSharp/ColorSpaces/CieXyy.cs b/src/ImageSharp/ColorProfiles/CieXyy.cs
similarity index 51%
rename from src/ImageSharp/ColorSpaces/CieXyy.cs
rename to src/ImageSharp/ColorProfiles/CieXyy.cs
index 6b7d2e6cbd..62873df147 100644
--- a/src/ImageSharp/ColorSpaces/CieXyy.cs
+++ b/src/ImageSharp/ColorProfiles/CieXyy.cs
@@ -3,14 +3,16 @@
using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
-namespace SixLabors.ImageSharp.ColorSpaces;
+namespace SixLabors.ImageSharp.ColorProfiles;
///
/// Represents an CIE xyY 1931 color
///
///
-public readonly struct CieXyy : IEquatable
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct CieXyy : IColorProfile
{
///
/// Initializes a new instance of the struct.
@@ -18,7 +20,7 @@ namespace SixLabors.ImageSharp.ColorSpaces;
/// The x chroma component.
/// The y chroma component.
/// The y luminance component.
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyy(float x, float y, float yl)
{
// Not clamping as documentation about this space only indicates "usual" ranges
@@ -31,7 +33,7 @@ public CieXyy(float x, float y, float yl)
/// Initializes a new instance of the struct.
///
/// The vector representing the x, y, Y components.
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyy(Vector3 vector)
: this()
{
@@ -45,19 +47,19 @@ public CieXyy(Vector3 vector)
/// Gets the X chrominance component.
/// A value usually ranging between 0 and 1.
///
- public readonly float X { get; }
+ public float X { get; }
///
/// Gets the Y chrominance component.
/// A value usually ranging between 0 and 1.
///
- public readonly float Y { get; }
+ public float Y { get; }
///
/// Gets the Y luminance component.
/// A value usually ranging between 0 and 1.
///
- public readonly float Yl { get; }
+ public float Yl { get; }
///
/// Compares two objects for equality.
@@ -67,7 +69,7 @@ public CieXyy(Vector3 vector)
///
/// True if the current left is equal to the parameter; otherwise, false.
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(CieXyy left, CieXyy right) => left.Equals(right);
///
@@ -78,22 +80,79 @@ public CieXyy(Vector3 vector)
///
/// True if the current left is unequal to the parameter; otherwise, false.
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieXyy left, CieXyy right) => !left.Equals(right);
///
- public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Yl);
+ 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);
+ }
+
+ ///
+ public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span 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);
+ }
+ }
///
- public override string ToString() => FormattableString.Invariant($"CieXyy({this.X:#0.##}, {this.Y:#0.##}, {this.Yl:#0.##})");
+ 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);
+ }
+
+ ///
+ public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+ for (int i = 0; i < source.Length; i++)
+ {
+ CieXyy xyz = source[i];
+ destination[i] = xyz.ToProfileConnectingSpace(options);
+ }
+ }
+
+ ///
+ public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource()
+ => ChromaticAdaptionWhitePointSource.WhitePoint;
+
+ ///
+ public override int GetHashCode()
+ => HashCode.Combine(this.X, this.Y, this.Yl);
+
+ ///
+ public override string ToString()
+ => FormattableString.Invariant($"CieXyy({this.X:#0.##}, {this.Y:#0.##}, {this.Yl:#0.##})");
///
public override bool Equals(object? obj) => obj is CieXyy other && this.Equals(other);
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieXyy other)
- => this.X.Equals(other.X)
- && this.Y.Equals(other.Y)
- && this.Yl.Equals(other.Yl);
+ => this.AsVector3Unsafe() == other.AsVector3Unsafe();
+
+ private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this));
}
diff --git a/src/ImageSharp/ColorSpaces/CieXyz.cs b/src/ImageSharp/ColorProfiles/CieXyz.cs
similarity index 64%
rename from src/ImageSharp/ColorSpaces/CieXyz.cs
rename to src/ImageSharp/ColorProfiles/CieXyz.cs
index 2ac9c9f286..07f9b47f9b 100644
--- a/src/ImageSharp/ColorSpaces/CieXyz.cs
+++ b/src/ImageSharp/ColorProfiles/CieXyz.cs
@@ -3,14 +3,16 @@
using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
-namespace SixLabors.ImageSharp.ColorSpaces;
+namespace SixLabors.ImageSharp.ColorProfiles;
///
/// Represents an CIE XYZ 1931 color
///
///
-public readonly struct CieXyz : IEquatable
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct CieXyz : IProfileConnectingSpace
{
///
/// Initializes a new instance of the struct.
@@ -18,10 +20,13 @@ namespace SixLabors.ImageSharp.ColorSpaces;
/// X is a mix (a linear combination) of cone response curves chosen to be nonnegative
/// The y luminance component.
/// Z is quasi-equal to blue stimulation, or the S cone of the human eye.
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieXyz(float x, float y, float z)
- : this(new Vector3(x, y, z))
{
+ // Not clamping as documentation about this space only indicates "usual" ranges
+ this.X = x;
+ this.Y = y;
+ this.Z = z;
}
///
@@ -31,7 +36,6 @@ public CieXyz(float x, float y, float z)
public CieXyz(Vector3 vector)
: this()
{
- // Not clamping as documentation about this space only indicates "usual" ranges
this.X = vector.X;
this.Y = vector.Y;
this.Z = vector.Z;
@@ -41,19 +45,19 @@ public CieXyz(Vector3 vector)
/// Gets the X component. A mix (a linear combination) of cone response curves chosen to be nonnegative.
/// A value usually ranging between 0 and 1.
///
- public readonly float X { get; }
+ public float X { get; }
///
/// Gets the Y luminance component.
/// A value usually ranging between 0 and 1.
///
- public readonly float Y { get; }
+ public float Y { get; }
///
/// Gets the Z component. Quasi-equal to blue stimulation, or the S cone response.
/// A value usually ranging between 0 and 1.
///
- public readonly float Z { get; }
+ public float Z { get; }
///
/// Compares two objects for equality.
@@ -63,7 +67,7 @@ public CieXyz(Vector3 vector)
///
/// True if the current left is equal to the parameter; otherwise, false.
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(CieXyz left, CieXyz right) => left.Equals(right);
///
@@ -74,16 +78,41 @@ public CieXyz(Vector3 vector)
///
/// True if the current left is unequal to the parameter; otherwise, false.
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieXyz left, CieXyz right) => !left.Equals(right);
///
/// Returns a new representing this instance.
///
/// The .
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 ToVector3() => new(this.X, this.Y, this.Z);
+ ///
+ public static CieXyz FromProfileConnectingSpace(ColorConversionOptions options, in CieXyz source)
+ => new(source.X, source.Y, source.Z);
+
+ ///
+ public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+ source.CopyTo(destination[..source.Length]);
+ }
+
+ ///
+ public CieXyz ToProfileConnectingSpace(ColorConversionOptions options)
+ => new(this.X, this.Y, this.Z);
+
+ ///
+ public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+ source.CopyTo(destination[..source.Length]);
+ }
+
+ ///
+ public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource() => ChromaticAdaptionWhitePointSource.WhitePoint;
+
///
public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Z);
@@ -94,9 +123,9 @@ public CieXyz(Vector3 vector)
public override bool Equals(object? obj) => obj is CieXyz other && this.Equals(other);
///
- [MethodImpl(InliningOptions.ShortMethod)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieXyz other)
- => this.X.Equals(other.X)
- && this.Y.Equals(other.Y)
- && this.Z.Equals(other.Z);
+ => this.AsVector3Unsafe() == other.AsVector3Unsafe();
+
+ private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this));
}
diff --git a/src/ImageSharp/ColorProfiles/Cmyk.cs b/src/ImageSharp/ColorProfiles/Cmyk.cs
new file mode 100644
index 0000000000..e924904497
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Cmyk.cs
@@ -0,0 +1,165 @@
+// 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;
+
+///
+/// Represents an CMYK (cyan, magenta, yellow, keyline) color.
+///
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct Cmyk : IColorProfile
+{
+ private static readonly Vector4 Min = Vector4.Zero;
+ private static readonly Vector4 Max = Vector4.One;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The cyan component.
+ /// The magenta component.
+ /// The yellow component.
+ /// The keyline black component.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Cmyk(float c, float m, float y, float k)
+ : this(new Vector4(c, m, y, k))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the c, m, y, k components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Cmyk(Vector4 vector)
+ {
+ vector = Numerics.Clamp(vector, Min, Max);
+ this.C = vector.X;
+ this.M = vector.Y;
+ this.Y = vector.Z;
+ this.K = vector.W;
+ }
+
+ ///
+ /// Gets the cyan color component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float C { get; }
+
+ ///
+ /// Gets the magenta color component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float M { get; }
+
+ ///
+ /// Gets the yellow color component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float Y { get; }
+
+ ///
+ /// Gets the keyline black color component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float K { get; }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(Cmyk left, Cmyk right) => left.Equals(right);
+
+ ///
+ /// Compares two objects for inequality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(Cmyk left, Cmyk right) => !left.Equals(right);
+
+ ///
+ public static Cmyk FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
+ {
+ // To CMY
+ Vector3 cmy = Vector3.One - source.ToScaledVector3();
+
+ // To CMYK
+ Vector3 k = new(MathF.Min(cmy.X, MathF.Min(cmy.Y, cmy.Z)));
+
+ if (MathF.Abs(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);
+ }
+
+ ///
+ public static void FromProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span 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);
+ }
+ }
+
+ ///
+ public Rgb ToProfileConnectingSpace(ColorConversionOptions options)
+ {
+ Vector3 rgb = (Vector3.One - new Vector3(this.C, this.M, this.Y)) * (Vector3.One - new Vector3(this.K));
+ return Rgb.FromScaledVector3(rgb);
+ }
+
+ ///
+ public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination)
+ {
+ // TODO: We can possibly optimize this by using SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ Cmyk cmyk = source[i];
+ destination[i] = cmyk.ToProfileConnectingSpace(options);
+ }
+ }
+
+ ///
+ public static ChromaticAdaptionWhitePointSource GetChromaticAdaptionWhitePointSource()
+ => ChromaticAdaptionWhitePointSource.RgbWorkingSpace;
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override int GetHashCode()
+ => HashCode.Combine(this.C, this.M, this.Y, this.K);
+
+ ///
+ public override string ToString()
+ => FormattableString.Invariant($"Cmyk({this.C:#0.##}, {this.M:#0.##}, {this.Y:#0.##}, {this.K:#0.##})");
+
+ ///
+ public override bool Equals(object? obj)
+ => obj is Cmyk other && this.Equals(other);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(Cmyk other)
+ => this.AsVector4Unsafe() == other.AsVector4Unsafe();
+
+ private Vector4 AsVector4Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this));
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs b/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs
new file mode 100644
index 0000000000..1eb118834a
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// Provides options for color profile conversion.
+///
+public class ColorConversionOptions
+{
+ private Matrix4x4 adaptationMatrix;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ColorConversionOptions() => this.AdaptationMatrix = KnownChromaticAdaptationMatrices.Bradford;
+
+ ///
+ /// Gets the memory allocator.
+ ///
+ public MemoryAllocator MemoryAllocator { get; init; } = MemoryAllocator.Default;
+
+ ///
+ /// Gets the source white point used for chromatic adaptation in conversions from/to XYZ color space.
+ ///
+ public CieXyz WhitePoint { get; init; } = KnownIlluminants.D50;
+
+ ///
+ /// Gets the destination white point used for chromatic adaptation in conversions from/to XYZ color space.
+ ///
+ public CieXyz TargetWhitePoint { get; init; } = KnownIlluminants.D50;
+
+ ///
+ /// Gets the source working space used for companding in conversions from/to XYZ color space.
+ ///
+ public RgbWorkingSpace RgbWorkingSpace { get; init; } = KnownRgbWorkingSpaces.SRgb;
+
+ ///
+ /// Gets the destination working space used for companding in conversions from/to XYZ color space.
+ ///
+ public RgbWorkingSpace TargetRgbWorkingSpace { get; init; } = KnownRgbWorkingSpaces.SRgb;
+
+ ///
+ /// Gets the transformation matrix used in conversion to perform chromatic adaptation.
+ /// for further information. Default is Bradford.
+ ///
+ public Matrix4x4 AdaptationMatrix
+ {
+ get => this.adaptationMatrix;
+ init
+ {
+ this.adaptationMatrix = value;
+ Matrix4x4.Invert(value, out Matrix4x4 inverted);
+ this.InverseAdaptationMatrix = inverted;
+ }
+ }
+
+ internal Matrix4x4 InverseAdaptationMatrix { get; private set; }
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverter.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverter.cs
new file mode 100644
index 0000000000..18b90a622a
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverter.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// Allows the conversion of color profiles.
+///
+public class ColorProfileConverter
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ColorProfileConverter()
+ : this(new())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The color profile conversion options.
+ public ColorProfileConverter(ColorConversionOptions options)
+ => this.Options = options;
+
+ ///
+ /// Gets the color profile conversion options.
+ ///
+ public ColorConversionOptions Options { get; }
+
+ internal (CieXyz From, CieXyz To) GetChromaticAdaptionWhitePoints()
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ CieXyz sourceWhitePoint = TFrom.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint
+ ? this.Options.WhitePoint
+ : this.Options.RgbWorkingSpace.WhitePoint;
+
+ CieXyz targetWhitePoint = TTo.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint
+ ? this.Options.TargetWhitePoint
+ : this.Options.TargetRgbWorkingSpace.WhitePoint;
+
+ return (sourceWhitePoint, targetWhitePoint);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs
new file mode 100644
index 0000000000..41ae4b08fa
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+internal static class ColorProfileConverterExtensionsCieLabCieLab
+{
+ public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ 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();
+ 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);
+ }
+
+ public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ ColorConversionOptions options = converter.Options;
+
+ // Convert to input PCS.
+ using IMemoryOwner pcsFromToOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFromTo = pcsFromToOwner.GetSpan();
+ TFrom.ToProfileConnectionSpace(options, source, pcsFromTo);
+
+ using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFrom = pcsFromOwner.GetSpan();
+ CieLab.ToProfileConnectionSpace(options, pcsFromTo, pcsFrom);
+
+ // Adapt to target white point
+ (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints();
+ 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);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs
new file mode 100644
index 0000000000..04937e927e
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+internal static class ColorProfileConverterExtensionsCieLabCieXyz
+{
+ public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ 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();
+ pcsTo = VonKriesChromaticAdaptation.Transform(in pcsTo, whitePoints, options.AdaptationMatrix);
+
+ // Convert to output from PCS
+ return TTo.FromProfileConnectingSpace(options, in pcsTo);
+ }
+
+ public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ ColorConversionOptions options = converter.Options;
+
+ // Convert to input PCS.
+ using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFrom = pcsFromOwner.GetSpan();
+ TFrom.ToProfileConnectionSpace(options, source, pcsFrom);
+
+ // Convert between PCS.
+ using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsTo = pcsToOwner.GetSpan();
+ CieLab.ToProfileConnectionSpace(options, pcsFrom, pcsTo);
+
+ // Adapt to target white point
+ (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints();
+ VonKriesChromaticAdaptation.Transform(pcsTo, pcsTo, whitePoints, options.AdaptationMatrix);
+
+ // Convert to output from PCS
+ TTo.FromProfileConnectionSpace(options, pcsTo, destination);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs
new file mode 100644
index 0000000000..47e4d2a80a
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabRgb.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+internal static class ColorProfileConverterExtensionsCieLabRgb
+{
+ public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ 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();
+ 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);
+ }
+
+ public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ ColorConversionOptions options = converter.Options;
+
+ // Convert to input PCS.
+ using IMemoryOwner pcsFromAOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFromA = pcsFromAOwner.GetSpan();
+ TFrom.ToProfileConnectionSpace(options, source, pcsFromA);
+
+ using IMemoryOwner pcsFromBOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFromB = pcsFromBOwner.GetSpan();
+ CieLab.ToProfileConnectionSpace(options, pcsFromA, pcsFromB);
+
+ // Adapt to target white point
+ (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints();
+ VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix);
+
+ // Convert between PCS.
+ using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsTo = pcsToOwner.GetSpan();
+ Rgb.FromProfileConnectionSpace(options, pcsFromB, pcsTo);
+
+ // Convert to output from PCS
+ TTo.FromProfileConnectionSpace(options, pcsTo, destination);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs
new file mode 100644
index 0000000000..6b1575d04c
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieLab.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+internal static class ColorProfileConverterExtensionsCieXyzCieLab
+{
+ public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ 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();
+ 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);
+ }
+
+ public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ ColorConversionOptions options = converter.Options;
+
+ // Convert to input PCS.
+ using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFrom = pcsFromOwner.GetSpan();
+ TFrom.ToProfileConnectionSpace(options, source, pcsFrom);
+
+ // Adapt to target white point
+ (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints();
+ VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix);
+
+ // Convert between PCS.
+ using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsTo = pcsToOwner.GetSpan();
+ CieLab.FromProfileConnectionSpace(options, pcsFrom, pcsTo);
+
+ // Convert to output from PCS
+ TTo.FromProfileConnectionSpace(options, pcsTo, destination);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs
new file mode 100644
index 0000000000..8f56a5a663
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzCieXyz.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+internal static class ColorProfileConverterExtensionsCieXyzCieXyz
+{
+ public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ 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();
+ pcsFrom = VonKriesChromaticAdaptation.Transform(in pcsFrom, whitePoints, options.AdaptationMatrix);
+
+ // Convert to output from PCS
+ return TTo.FromProfileConnectingSpace(options, in pcsFrom);
+ }
+
+ public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ ColorConversionOptions options = converter.Options;
+
+ // Convert to input PCS.
+ using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFrom = pcsFromOwner.GetSpan();
+ TFrom.ToProfileConnectionSpace(options, source, pcsFrom);
+
+ // Adapt to target white point
+ (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints();
+ VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix);
+
+ // Convert to output from PCS
+ TTo.FromProfileConnectionSpace(options, pcsFrom, destination);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs
new file mode 100644
index 0000000000..9cc0bd9436
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieXyzRgb.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+internal static class ColorProfileConverterExtensionsCieXyzRgb
+{
+ public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ 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();
+ 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);
+ }
+
+ public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ ColorConversionOptions options = converter.Options;
+
+ // Convert to input PCS.
+ using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFrom = pcsFromOwner.GetSpan();
+ TFrom.ToProfileConnectionSpace(options, source, pcsFrom);
+
+ // Adapt to target white point
+ (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints();
+ VonKriesChromaticAdaptation.Transform(pcsFrom, pcsFrom, whitePoints, options.AdaptationMatrix);
+
+ // Convert between PCS.
+ using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsTo = pcsToOwner.GetSpan();
+ Rgb.FromProfileConnectionSpace(options, pcsFrom, pcsTo);
+
+ // Convert to output from PCS
+ TTo.FromProfileConnectionSpace(options, pcsTo, destination);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs
new file mode 100644
index 0000000000..415dd94c3f
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieLab.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+internal static class ColorProfileConverterExtensionsRgbCieLab
+{
+ public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ 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();
+ 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);
+ }
+
+ public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ ColorConversionOptions options = converter.Options;
+
+ // Convert to input PCS.
+ using IMemoryOwner pcsFromAOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFromA = pcsFromAOwner.GetSpan();
+ TFrom.ToProfileConnectionSpace(options, source, pcsFromA);
+
+ using IMemoryOwner pcsFromBOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFromB = pcsFromBOwner.GetSpan();
+ Rgb.ToProfileConnectionSpace(options, pcsFromA, pcsFromB);
+
+ // Adapt to target white point
+ (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints();
+ VonKriesChromaticAdaptation.Transform(pcsFromB, pcsFromB, whitePoints, options.AdaptationMatrix);
+
+ // Convert between PCS.
+ using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsTo = pcsToOwner.GetSpan();
+ CieLab.FromProfileConnectionSpace(options, pcsFromB, pcsTo);
+
+ // Convert to output from PCS
+ TTo.FromProfileConnectionSpace(options, pcsTo, destination);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs
new file mode 100644
index 0000000000..a13f645778
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbCieXyz.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+internal static class ColorProfileConverterExtensionsRgbCieXyz
+{
+ public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ 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();
+ pcsTo = VonKriesChromaticAdaptation.Transform(in pcsTo, whitePoints, options.AdaptationMatrix);
+
+ // Convert to output from PCS
+ return TTo.FromProfileConnectingSpace(options, in pcsTo);
+ }
+
+ public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ ColorConversionOptions options = converter.Options;
+
+ // Convert to input PCS.
+ using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFrom = pcsFromOwner.GetSpan();
+ TFrom.ToProfileConnectionSpace(options, source, pcsFrom);
+
+ // Convert between PCS.
+ using IMemoryOwner pcsToOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsTo = pcsToOwner.GetSpan();
+ Rgb.ToProfileConnectionSpace(options, pcsFrom, pcsTo);
+
+ // Adapt to target white point
+ (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints();
+ VonKriesChromaticAdaptation.Transform(pcsTo, pcsTo, whitePoints, options.AdaptationMatrix);
+
+ // Convert to output from PCS
+ TTo.FromProfileConnectionSpace(options, pcsTo, destination);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs
new file mode 100644
index 0000000000..c1c75dea1b
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsRgbRgb.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+internal static class ColorProfileConverterExtensionsRgbRgb
+{
+ public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ 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();
+ 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);
+ }
+
+ public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ ColorConversionOptions options = converter.Options;
+
+ // Convert to input PCS.
+ using IMemoryOwner pcsFromToOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFromTo = pcsFromToOwner.GetSpan();
+ TFrom.ToProfileConnectionSpace(options, source, pcsFromTo);
+
+ using IMemoryOwner pcsFromOwner = options.MemoryAllocator.Allocate(source.Length);
+ Span pcsFrom = pcsFromOwner.GetSpan();
+ Rgb.ToProfileConnectionSpace(options, pcsFromTo, pcsFrom);
+
+ // Adapt to target white point
+ (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints();
+ 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);
+ }
+}
diff --git a/src/ImageSharp/ColorProfiles/Companding/CompandingUtilities.cs b/src/ImageSharp/ColorProfiles/Companding/CompandingUtilities.cs
new file mode 100644
index 0000000000..1970e2d949
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Companding/CompandingUtilities.cs
@@ -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;
+
+///
+/// Companding utilities that allow the accelerated compression-expansion of color channels.
+///
+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();
+
+ ///
+ /// Lazily creates and stores a companding compression lookup table using the given function and modifier.
+ ///
+ /// The type of companding function.
+ /// The companding function.
+ /// A modifier to pass to the function.
+ /// The array.
+ public static float[] GetCompressLookupTable(Func compandingFunction, double modifier = 0)
+ => CompressLookupTables.GetOrAdd((typeof(T), modifier), args => CreateLookupTableImpl(compandingFunction, args.Item2));
+
+ ///
+ /// Lazily creates and stores a companding expanding lookup table using the given function and modifier.
+ ///
+ /// The type of companding function.
+ /// The companding function.
+ /// A modifier to pass to the function.
+ /// The array.
+ public static float[] GetExpandLookupTable(Func compandingFunction, double modifier = 0)
+ => ExpandLookupTables.GetOrAdd((typeof(T), modifier), args => CreateLookupTableImpl(compandingFunction, args.Item2));
+
+ ///
+ /// Creates a companding lookup table using the given function.
+ ///
+ /// The companding function.
+ /// A modifier to pass to the function.
+ /// The array.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float[] CreateLookupTableImpl(Func 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;
+ }
+
+ ///
+ /// Performs the companding operation on the given vectors using the given table.
+ ///
+ /// The span of vectors.
+ /// The lookup table.
+ public static void Compand(Span 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);
+ }
+ }
+
+ ///
+ /// Performs the companding operation on the given vector using the given table.
+ ///
+ /// The vector.
+ /// The lookup table.
+ /// The
+ 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 vectors, float[] table)
+ {
+ fixed (float* tablePointer = &MemoryMarshal.GetArrayDataReference(table))
+ {
+ Vector256 scale = Vector256.Create((float)Scale);
+ Vector256 zero = Vector256.Zero;
+ Vector256 offset = Vector256.Create(1);
+
+ // Divide by 2 as 4 elements per Vector4 and 8 per Vector256
+ ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors));
+ ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (uint)vectors.Length / 2u);
+
+ while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast))
+ {
+ Vector256