diff --git a/.editorconfig b/.editorconfig
index 2e3045fb17..f579ff5d3d 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -104,8 +104,8 @@ dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:war
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
dotnet_style_parentheses_in_other_operators = always_for_clarity:suggestion
# Expression-level preferences
-dotnet_style_object_initializer = true:warning
-dotnet_style_collection_initializer = true:warning
+dotnet_style_object_initializer = true:error
+dotnet_style_collection_initializer = true:error
dotnet_style_explicit_tuple_names = true:warning
dotnet_style_prefer_inferred_tuple_names = true:warning
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
@@ -135,9 +135,9 @@ csharp_style_prefer_null_check_over_type_check = true:warning
# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#c-style-rules
[*.{cs,csx,cake}]
# 'var' preferences
-csharp_style_var_for_built_in_types = false:warning
-csharp_style_var_when_type_is_apparent = false:warning
-csharp_style_var_elsewhere = false:warning
+csharp_style_var_for_built_in_types = false:error
+csharp_style_var_when_type_is_apparent = false:error
+csharp_style_var_elsewhere = false:error
# Expression-bodied members
csharp_style_expression_bodied_methods = true:warning
csharp_style_expression_bodied_constructors = true:warning
@@ -160,7 +160,10 @@ csharp_style_pattern_local_over_anonymous_function = true:warning
csharp_style_deconstructed_variable_declaration = true:warning
csharp_style_prefer_index_operator = true:warning
csharp_style_prefer_range_operator = true:warning
-csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
+csharp_style_implicit_object_creation_when_type_is_apparent = true:error
+# ReSharper inspection severities
+resharper_arrange_object_creation_when_type_evident_highlighting = error
+resharper_arrange_object_creation_when_type_not_evident_highlighting = error
# "Null" checking preferences
csharp_style_throw_expression = true:warning
csharp_style_conditional_delegate_call = true:warning
@@ -172,6 +175,11 @@ 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
+# Collection preferences
+dotnet_style_prefer_collection_expression = true:error
+resharper_use_collection_expression_highlighting =true:error
##########################################
# Unnecessary Code Rules
diff --git a/.gitattributes b/.gitattributes
index ff4ec94087..f7bd4d061e 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -118,6 +118,7 @@
*.bmp filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
+*.qoi filter=lfs diff=lfs merge=lfs -text
*.tif filter=lfs diff=lfs merge=lfs -text
*.tiff filter=lfs diff=lfs merge=lfs -text
*.tga filter=lfs diff=lfs merge=lfs -text
@@ -132,3 +133,13 @@
*.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
+*.heic filter=lfs diff=lfs merge=lfs -text
+*.hif filter=lfs diff=lfs merge=lfs -text
+*.avif filter=lfs diff=lfs merge=lfs -text
+###############################################################################
+# Handle ICC files by git lfs
+###############################################################################
+*.icc filter=lfs diff=lfs merge=lfs -text
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index ffacf51e4a..543506197b 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -29,7 +29,6 @@
#### **Running tests and Debugging**
* Expected test output is pulled in as a submodule from the [ImageSharp.Tests.Images repository](https://github.com/SixLabors/Imagesharp.Tests.Images/tree/main/ReferenceOutput). To succesfully run tests, make sure that you have updated the submodules!
-* Debugging (running tests in Debug mode) is only supported on .NET Core 2.1+, because of JIT Code Generation bugs like [dotnet/coreclr#16443](https://github.com/dotnet/coreclr/issues/16443) or [dotnet/coreclr#20657](https://github.com/dotnet/coreclr/issues/20657)
#### **Do you have questions about consuming the library or the source code?**
@@ -37,7 +36,6 @@
#### Code of Conduct
This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community.
-For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
And please remember. SixLabors.ImageSharp is the work of a very, very, small number of developers who struggle balancing time to contribute to the project with family time and work commitments. We encourage you to pitch in and help make our vision of simple accessible image processing available to all. Open Source can only exist with your help.
diff --git a/.github/ISSUE_TEMPLATE/commercial-bug-report.yml b/.github/ISSUE_TEMPLATE/commercial-bug-report.yml
deleted file mode 100644
index 6b4d914d7e..0000000000
--- a/.github/ISSUE_TEMPLATE/commercial-bug-report.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-name: "Commercial License : Bug Report"
-description: |
- Create a report to help us improve the project. For Commercial License holders only.
- Please contact help@sixlabors.com for issues requiring private support.
-labels: ["commercial", "needs triage"]
-body:
-- type: checkboxes
- attributes:
- label: Prerequisites
- options:
- - label: I have bought a Commercial License
- required: true
- - label: I have written a descriptive issue title
- required: true
- - label: I have verified that I am running the latest version of ImageSharp
- required: true
- - label: I have verified if the problem exist in both `DEBUG` and `RELEASE` mode
- required: true
- - label: I have searched [open](https://github.com/SixLabors/ImageSharp/issues) and [closed](https://github.com/SixLabors/ImageSharp/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported
- required: true
-- type: input
- attributes:
- label: ImageSharp version
- validations:
- required: true
-- type: input
- attributes:
- label: Other ImageSharp packages and versions
- validations:
- required: true
-- type: input
- attributes:
- label: Environment (Operating system, version and so on)
- validations:
- required: true
-- type: input
- attributes:
- label: .NET Framework version
- validations:
- required: true
-- type: textarea
- attributes:
- label: Description
- description: A description of the bug
- validations:
- required: true
-- type: textarea
- attributes:
- label: Steps to Reproduce
- description: List of steps, sample code, failing test or link to a project that reproduces the behavior. Make sure you place a stack trace inside a code (```) block to avoid linking unrelated issues.
- validations:
- required: true
-- type: textarea
- attributes:
- label: Images
- description: Please upload images that can be used to reproduce issues in the area below. If the file type is not supported the file can be zipped and then uploaded instead.
diff --git a/.github/ISSUE_TEMPLATE/oss-bug-report.yml b/.github/ISSUE_TEMPLATE/oss-bug-report.yml
index a4e5619d46..87cd1a7a17 100644
--- a/.github/ISSUE_TEMPLATE/oss-bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/oss-bug-report.yml
@@ -1,5 +1,5 @@
-name: "OSS : Bug Report"
-description: Create a report to help us improve the project. OSS Issues are not guaranteed to be triaged.
+name: "Bug Report"
+description: Create a report to help us improve the project. Issues are not guaranteed to be triaged.
labels: ["needs triage"]
body:
- type: checkboxes
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index b5cc5daca2..ace6a4306e 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -4,69 +4,131 @@ on:
push:
branches:
- main
+ - release/*
tags:
- "v*"
pull_request:
branches:
- main
- types: [ labeled, opened, synchronize, reopened ]
+ - release/*
+ types: [ opened, synchronize, reopened ]
+
jobs:
+ # Prime a single LFS cache and expose the exact key for the matrix
+ WarmLFS:
+ runs-on: ubuntu-latest
+ outputs:
+ lfs_key: ${{ steps.expose-key.outputs.lfs_key }}
+ steps:
+ - name: Git Config
+ shell: bash
+ run: |
+ git config --global core.autocrlf false
+ git config --global core.longpaths true
+
+ - name: Git Checkout
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: recursive
+
+ # Deterministic list of LFS object IDs, then compute a portable key:
+ # - `git lfs ls-files -l` lists all tracked LFS objects with their SHA-256
+ # - `awk '{print $1}'` extracts just the SHA field
+ # - `sort` sorts in byte order (hex hashes sort the same everywhere)
+ # This ensures the file content is identical regardless of OS or locale
+ - name: Git Create LFS id list
+ shell: bash
+ run: git lfs ls-files -l | awk '{print $1}' | sort > .lfs-assets-id
+
+ - name: Git Expose LFS cache key
+ id: expose-key
+ shell: bash
+ env:
+ LFS_KEY: lfs-${{ hashFiles('.lfs-assets-id') }}-v1
+ run: echo "lfs_key=$LFS_KEY" >> "$GITHUB_OUTPUT"
+
+ - name: Git Setup LFS Cache
+ uses: actions/cache@v5
+ with:
+ path: .git/lfs
+ key: ${{ steps.expose-key.outputs.lfs_key }}
+
+ - name: Git Pull LFS
+ shell: bash
+ run: git lfs pull
+
Build:
+ needs: WarmLFS
strategy:
matrix:
- isARM:
- - ${{ 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
+ framework: net10.0
+ sdk: 10.0.x
sdk-preview: true
runtime: -x64
codecov: false
- - os: macos-latest
- framework: net7.0
- sdk: 7.0.x
+ - os: macos-26
+ framework: net10.0
+ sdk: 10.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: windows-latest
- framework: net7.0
- sdk: 7.0.x
+ framework: net10.0
+ sdk: 10.0.x
sdk-preview: true
runtime: -x64
codecov: false
- - os: buildjet-4vcpu-ubuntu-2204-arm
- framework: net7.0
- sdk: 7.0.x
+ - os: ubuntu-22.04-arm
+ framework: net10.0
+ sdk: 10.0.x
sdk-preview: true
runtime: -x64
codecov: false
+
- os: ubuntu-latest
- framework: net6.0
- sdk: 6.0.x
+ framework: net8.0
+ sdk: 8.0.x
runtime: -x64
codecov: false
- - os: macos-latest
- framework: net6.0
- sdk: 6.0.x
+ - os: macos-26
+ framework: net8.0
+ sdk: 8.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
+ - os: ubuntu-22.04-arm
+ framework: net8.0
+ sdk: 8.0.x
runtime: -x64
codecov: false
- exclude:
- - isARM: false
- options:
- os: buildjet-4vcpu-ubuntu-2204-arm
- runs-on: ${{matrix.options.os}}
+ runs-on: ${{ matrix.options.os }}
steps:
- name: Install libgdi+, which is required for tests running on ubuntu
- if: ${{ matrix.options.os == 'buildjet-4vcpu-ubuntu-2204-arm' }}
- run: sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev
+ if: ${{ contains(matrix.options.os, 'ubuntu') }}
+ run: |
+ sudo apt-get update
+ sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev
+
+ - name: Install libgdi+, which is required for tests running on macos
+ if: ${{ contains(matrix.options.os, 'macos-26') }}
+ run: |
+ brew update
+ brew install mono-libgdiplus
+ # Create symlinks to make libgdiplus discoverable
+ sudo mkdir -p /usr/local/lib
+ sudo ln -sf $(brew --prefix)/lib/libgdiplus.dylib /usr/local/lib/libgdiplus.dylib
+ # Verify installation
+ ls -la $(brew --prefix)/lib/libgdiplus* || echo "libgdiplus not found in brew prefix"
+ ls -la /usr/local/lib/libgdiplus* || echo "libgdiplus not found in /usr/local/lib"
- name: Git Config
shell: bash
@@ -75,30 +137,27 @@ jobs:
git config --global core.longpaths true
- name: Git Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
- # See https://github.com/actions/checkout/issues/165#issuecomment-657673315
- - name: Git Create LFS FileList
- run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id
-
+ # Use the warmed key from WarmLFS. Do not recompute or recreate .lfs-assets-id here.
- name: Git Setup LFS Cache
- uses: actions/cache@v3
- id: lfs-cache
+ uses: actions/cache@v5
with:
path: .git/lfs
- key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1
+ key: ${{ needs.WarmLFS.outputs.lfs_key }}
- name: Git Pull LFS
+ shell: bash
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@v5
id: nuget-cache
with:
path: ~/.nuget
@@ -107,17 +166,17 @@ jobs:
- name: DotNet Setup
if: ${{ matrix.options.sdk-preview != true }}
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v5
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@v5
with:
dotnet-version: |
- 7.0.x
+ 10.0.x
- name: DotNet Build
if: ${{ matrix.options.sdk-preview != true }}
@@ -150,7 +209,7 @@ jobs:
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Export Failed Output
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v7
if: failure()
with:
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
@@ -158,11 +217,8 @@ jobs:
Publish:
needs: [Build]
-
runs-on: ubuntu-latest
-
if: (github.event_name == 'push')
-
steps:
- name: Git Config
shell: bash
@@ -171,16 +227,16 @@ jobs:
git config --global core.longpaths true
- name: Git Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v6
with:
fetch-depth: 0
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@v5
id: nuget-cache
with:
path: ~/.nuget
@@ -203,4 +259,3 @@ jobs:
run: |
dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json --skip-duplicate
dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json --skip-duplicate
-
diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml
index 049a4cba05..16ae0317dc 100644
--- a/.github/workflows/code-coverage.yml
+++ b/.github/workflows/code-coverage.yml
@@ -4,19 +4,26 @@ on:
schedule:
# 2AM every Tuesday/Thursday
- cron: "0 2 * * 2,4"
+
jobs:
Build:
strategy:
matrix:
options:
- os: ubuntu-latest
- framework: net6.0
+ framework: net8.0
runtime: -x64
codecov: true
- runs-on: ${{matrix.options.os}}
+ runs-on: ${{ matrix.options.os }}
steps:
+ - name: Install libgdi+, which is required for tests running on ubuntu
+ if: ${{ contains(matrix.options.os, 'ubuntu') }}
+ run: |
+ sudo apt-get update
+ sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev
+
- name: Git Config
shell: bash
run: |
@@ -24,30 +31,35 @@ jobs:
git config --global core.longpaths true
- name: Git Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
- # See https://github.com/actions/checkout/issues/165#issuecomment-657673315
- - name: Git Create LFS FileList
- run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id
+ # Deterministic list of LFS object IDs, then compute a portable key:
+ # - `git lfs ls-files -l` lists all tracked LFS objects with their SHA-256
+ # - `awk '{print $1}'` extracts just the SHA field
+ # - `sort` sorts in byte order (hex hashes sort the same everywhere)
+ # This ensures the file content is identical regardless of OS or locale
+ - name: Git Create LFS id list
+ shell: bash
+ run: git lfs ls-files -l | awk '{print $1}' | sort > .lfs-assets-id
- name: Git Setup LFS Cache
- uses: actions/cache@v3
+ uses: actions/cache@v5
id: lfs-cache
with:
path: .git/lfs
- key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1
+ key: lfs-${{ hashFiles('.lfs-assets-id') }}-v1
- name: Git Pull LFS
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@v5
id: nuget-cache
with:
path: ~/.nuget
@@ -55,33 +67,34 @@ jobs:
restore-keys: ${{ runner.os }}-nuget-
- name: DotNet Setup
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v5
with:
dotnet-version: |
- 6.0.x
+ 8.0.x
- name: DotNet Build
shell: pwsh
- run: ./ci-build.ps1 "${{matrix.options.framework}}"
+ run: ./ci-build.ps1 "${{ matrix.options.framework }}"
env:
SIXLABORS_TESTING: True
- name: DotNet Test
shell: pwsh
- run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}"
+ run: ./ci-test.ps1 "${{ matrix.options.os }}" "${{ matrix.options.framework }}" "${{ matrix.options.runtime }}" "${{ matrix.options.codecov }}"
env:
SIXLABORS_TESTING: True
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Export Failed Output
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v7
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@v5
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors')
with:
flags: unittests
+ token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/Directory.Build.props b/Directory.Build.props
index 26b3cc5afc..9bda76f882 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -21,9 +21,12 @@
-
-
- preview
+
+ 12.0
+
+
+
+ 14.0
-
+
diff --git a/shared-infrastructure b/shared-infrastructure
index 9a6cf00d9a..a1d3ac2049 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit 9a6cf00d9a3d482bb08211dd8309f4724a2735cb
+Subproject commit a1d3ac20494631e3cc13132897573796b0e4ee6d
diff --git a/src/ImageSharp.ruleset b/src/ImageSharp.ruleset
index e88c43f838..dee0393cd7 100644
--- a/src/ImageSharp.ruleset
+++ b/src/ImageSharp.ruleset
@@ -1,7 +1,7 @@
-
-
+
+
\ No newline at end of file
diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
index c3a9c212ee..a451e111d2 100644
--- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
+++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs
@@ -27,11 +27,11 @@ public static class AdvancedImageExtensions
Guard.NotNull(filePath, nameof(filePath));
string ext = Path.GetExtension(filePath);
- if (!source.GetConfiguration().ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat? format))
+ if (!source.Configuration.ImageFormatsManager.TryFindFormatByFileExtension(ext, out IImageFormat? format))
{
StringBuilder sb = new();
sb = sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}'. Registered encoders include:");
- foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats)
+ foreach (IImageFormat fmt in source.Configuration.ImageFormats)
{
sb = sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine);
}
@@ -39,13 +39,13 @@ public static class AdvancedImageExtensions
throw new UnknownImageFormatException(sb.ToString());
}
- IImageEncoder? encoder = source.GetConfiguration().ImageFormatsManager.GetEncoder(format);
+ IImageEncoder? encoder = source.Configuration.ImageFormatsManager.GetEncoder(format);
if (encoder is null)
{
StringBuilder sb = new();
sb = sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:");
- foreach (KeyValuePair enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders)
+ foreach (KeyValuePair enc in source.Configuration.ImageFormatsManager.ImageEncoders)
{
sb = sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine);
}
@@ -76,30 +76,6 @@ public static class AdvancedImageExtensions
public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default)
=> source.AcceptAsync(visitor, cancellationToken);
- ///
- /// Gets the configuration for the image.
- ///
- /// The source image.
- /// Returns the configuration.
- public static Configuration GetConfiguration(this Image source)
- => GetConfiguration((IConfigurationProvider)source);
-
- ///
- /// Gets the configuration for the image frame.
- ///
- /// The source image.
- /// Returns the configuration.
- public static Configuration GetConfiguration(this ImageFrame source)
- => GetConfiguration((IConfigurationProvider)source);
-
- ///
- /// Gets the configuration.
- ///
- /// The source image
- /// Returns the bounds of the image
- private static Configuration GetConfiguration(IConfigurationProvider source)
- => source?.Configuration ?? Configuration.Default;
-
///
/// Gets the representation of the pixels as a containing the backing pixel data of the image
/// stored in row major order, as a list of contiguous blocks in the source image's pixel format.
@@ -167,12 +143,4 @@ public static class AdvancedImageExtensions
return source.Frames.RootFrame.PixelBuffer.GetSafeRowMemory(rowIndex);
}
-
- ///
- /// Gets the assigned to 'source'.
- ///
- /// The source image.
- /// Returns the configuration.
- internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source)
- => GetConfiguration(source).MemoryAllocator;
}
diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs
index f36f3d09b4..fef49bffd4 100644
--- a/src/ImageSharp/Advanced/AotCompilerTools.cs
+++ b/src/ImageSharp/Advanced/AotCompilerTools.cs
@@ -10,10 +10,13 @@ using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
+using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Qoi;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
+using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -129,15 +132,17 @@ internal static class AotCompilerTools
AotCompileImageDecoderInternals();
AotCompileImageEncoders();
AotCompileImageDecoders();
+ AotCompileSpectralConverter();
AotCompileImageProcessors();
AotCompileGenericImageProcessors();
AotCompileResamplers();
AotCompileQuantizers();
AotCompilePixelSamplingStrategys();
+ AotCompilePixelMaps();
AotCompileDithers();
AotCompileMemoryManagers();
- Unsafe.SizeOf();
+ _ = Unsafe.SizeOf();
// TODO: Do the discovery work to figure out what works and what doesn't.
}
@@ -195,39 +200,41 @@ internal static class AotCompilerTools
=> default(DefaultImageOperationsProviderFactory).CreateImageProcessingContext(default, default, default);
///
- /// This method pre-seeds the all in the AoT compiler.
+ /// This method pre-seeds the all core encoders in the AoT compiler.
///
/// The pixel format.
[Preserve]
private static void AotCompileImageEncoderInternals()
where TPixel : unmanaged, IPixel
{
- default(WebpEncoderCore).Encode(default, default, default);
default(BmpEncoderCore).Encode(default, default, default);
default(GifEncoderCore).Encode(default, default, default);
default(JpegEncoderCore).Encode(default, default, default);
default(PbmEncoderCore).Encode(default, default, default);
default(PngEncoderCore).Encode(default, default, default);
+ default(QoiEncoderCore).Encode(default, default, default);
default(TgaEncoderCore).Encode(default, default, default);
default(TiffEncoderCore).Encode(default, default, default);
+ default(WebpEncoderCore).Encode(default, default, default);
}
///
- /// This method pre-seeds the all in the AoT compiler.
+ /// This method pre-seeds the all in the AoT compiler.
///
/// The pixel format.
[Preserve]
private static void AotCompileImageDecoderInternals()
where TPixel : unmanaged, IPixel
{
- default(WebpDecoderCore).Decode(default, default);
- default(BmpDecoderCore).Decode(default, default);
- default(GifDecoderCore).Decode(default, default);
- default(JpegDecoderCore).Decode(default, default);
- default(PbmDecoderCore).Decode(default, default);
- default(PngDecoderCore).Decode(default, default);
- default(TgaDecoderCore).Decode(default, default);
- default(TiffDecoderCore).Decode(default, default);
+ default(BmpDecoderCore).Decode(default, default, default);
+ default(GifDecoderCore).Decode(default, default, default);
+ default(JpegDecoderCore).Decode(default, default, default);
+ default(PbmDecoderCore).Decode(default, default, default);
+ default(PngDecoderCore).Decode(default, default, default);
+ default(QoiDecoderCore).Decode(default, default, default);
+ default(TgaDecoderCore).Decode(default, default, default);
+ default(TiffDecoderCore).Decode(default, default, default);
+ default(WebpDecoderCore).Decode(default, default, default);
}
///
@@ -266,6 +273,17 @@ internal static class AotCompilerTools
AotCompileImageDecoder();
}
+ [Preserve]
+ private static void AotCompileSpectralConverter()
+ where TPixel : unmanaged, IPixel
+ {
+ default(SpectralConverter).GetPixelBuffer(default, default);
+ default(GrayJpegSpectralConverter).GetPixelBuffer(default, default);
+ default(RgbJpegSpectralConverter).GetPixelBuffer(default, default);
+ default(TiffJpegSpectralConverter).GetPixelBuffer(default, default);
+ default(TiffOldJpegSpectralConverter).GetPixelBuffer(default, default);
+ }
+
///
/// This method pre-seeds the in the AoT compiler.
///
@@ -497,6 +515,20 @@ internal static class AotCompilerTools
default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame));
}
+ ///
+ /// This method pre-seeds the all in the AoT compiler.
+ ///
+ /// The pixel format.
+ [Preserve]
+ private static void AotCompilePixelMaps()
+ where TPixel : unmanaged, IPixel
+ {
+ default(EuclideanPixelMap).GetClosestColor(default, out _);
+ default(EuclideanPixelMap).GetClosestColor(default, out _);
+ default(EuclideanPixelMap).GetClosestColor(default, out _);
+ default(EuclideanPixelMap).GetClosestColor(default, out _);
+ }
+
///
/// This method pre-seeds the all in the AoT compiler.
///
diff --git a/src/ImageSharp/Advanced/IConfigurationProvider.cs b/src/ImageSharp/Advanced/IConfigurationProvider.cs
index 086461f448..bb6d124f68 100644
--- a/src/ImageSharp/Advanced/IConfigurationProvider.cs
+++ b/src/ImageSharp/Advanced/IConfigurationProvider.cs
@@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Advanced;
///
/// Defines the contract for objects that can provide access to configuration.
///
-internal interface IConfigurationProvider
+public interface IConfigurationProvider
{
///
/// Gets the configuration which allows altering default behaviour or extending the library.
diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
index 9629b0097e..cbcd12aec2 100644
--- a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
+++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
@@ -51,7 +51,7 @@ public static partial class ParallelRowIterator
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 static partial class ParallelRowIterator
for (int y = yMin; y < yMax; y++)
{
- Unsafe.AsRef(this.action).Invoke(y, span);
+ Unsafe.AsRef(in this.action).Invoke(y, span);
}
}
}
@@ -139,7 +139,7 @@ public static partial class ParallelRowIterator
}
int yMax = Math.Min(yMin + this.stepY, this.maxY);
- var rows = new RowInterval(yMin, yMax);
+ RowInterval rows = new(yMin, yMax);
// Skip the safety copy when invoking a potentially impure method on a readonly field
Unsafe.AsRef(in this.operation).Invoke(in rows);
@@ -185,7 +185,7 @@ public static partial class ParallelRowIterator
}
int yMax = Math.Min(yMin + this.stepY, this.maxY);
- var rows = new RowInterval(yMin, yMax);
+ RowInterval rows = new(yMin, yMax);
using IMemoryOwner buffer = this.allocator.Allocate(this.bufferLength);
diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs
index 0eb5952a63..b878f9ec0a 100644
--- a/src/ImageSharp/Advanced/ParallelRowIterator.cs
+++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs
@@ -26,7 +26,7 @@ public static partial class ParallelRowIterator
public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation)
where T : struct, IRowOperation
{
- var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+ ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows(rectangle, in parallelSettings, in operation);
}
@@ -50,7 +50,7 @@ public static partial class ParallelRowIterator
int width = rectangle.Width;
int height = rectangle.Height;
- int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
// Avoid TPL overhead in this trivial case:
@@ -58,15 +58,15 @@ public static partial class ParallelRowIterator
{
for (int y = top; y < bottom; y++)
{
- Unsafe.AsRef(operation).Invoke(y);
+ Unsafe.AsRef(in operation).Invoke(y);
}
return;
}
int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
- var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
- var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, in operation);
+ ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps };
+ RowOperationWrapper wrappingOperation = new(top, bottom, verticalStep, in operation);
Parallel.For(
0,
@@ -88,7 +88,7 @@ public static partial class ParallelRowIterator
where T : struct, IRowOperation
where TBuffer : unmanaged
{
- var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+ ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows(rectangle, in parallelSettings, in operation);
}
@@ -115,10 +115,10 @@ public static partial class ParallelRowIterator
int width = rectangle.Width;
int height = rectangle.Height;
- int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ 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,15 +128,15 @@ public static partial class ParallelRowIterator
for (int y = top; y < bottom; y++)
{
- Unsafe.AsRef(operation).Invoke(y, span);
+ Unsafe.AsRef(in operation).Invoke(y, span);
}
return;
}
int verticalStep = DivideCeil(height, numOfSteps);
- var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
- var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, bufferLength, allocator, in operation);
+ ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps };
+ RowOperationWrapper wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation);
Parallel.For(
0,
@@ -156,7 +156,7 @@ public static partial class ParallelRowIterator
public static void IterateRowIntervals(Configuration configuration, Rectangle rectangle, in T operation)
where T : struct, IRowIntervalOperation
{
- var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+ ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRowIntervals(rectangle, in parallelSettings, in operation);
}
@@ -180,20 +180,20 @@ public static partial class ParallelRowIterator
int width = rectangle.Width;
int height = rectangle.Height;
- int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ int maxSteps = DivideCeil(width * (long)height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
{
- var rows = new RowInterval(top, bottom);
+ RowInterval rows = new(top, bottom);
Unsafe.AsRef(in operation).Invoke(in rows);
return;
}
int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
- var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
- var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, in operation);
+ ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps };
+ RowIntervalOperationWrapper wrappingOperation = new(top, bottom, verticalStep, in operation);
Parallel.For(
0,
@@ -215,7 +215,7 @@ public static partial class ParallelRowIterator
where T : struct, IRowIntervalOperation
where TBuffer : unmanaged
{
- var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
+ ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRowIntervals(rectangle, in parallelSettings, in operation);
}
@@ -242,25 +242,25 @@ public static partial class ParallelRowIterator
int width = rectangle.Width;
int height = rectangle.Height;
- int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
+ 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)
{
- var rows = new RowInterval(top, bottom);
+ RowInterval rows = new(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;
}
int verticalStep = DivideCeil(height, numOfSteps);
- var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
- var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, bufferLength, allocator, in operation);
+ ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = numOfSteps };
+ RowIntervalOperationWrapper wrappingOperation = new(top, bottom, verticalStep, bufferLength, allocator, in operation);
Parallel.For(
0,
@@ -270,7 +270,7 @@ public static partial class ParallelRowIterator
}
[MethodImpl(InliningOptions.ShortMethod)]
- private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor);
+ private static int DivideCeil(long dividend, int divisor) => (int)Math.Min(1 + ((dividend - 1) / divisor), int.MaxValue);
private static void ValidateRectangle(Rectangle rectangle)
{
diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs
deleted file mode 100644
index bbb848867d..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.ToVector4();
-
- ///
- /// 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 ToVector4()
- {
- 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 @@ public readonly partial struct Color
{ nameof(Yellow), Yellow },
{ nameof(YellowGreen), YellowGreen }
};
- }
}
diff --git a/src/ImageSharp/Color/Color.WebSafePalette.cs b/src/ImageSharp/Color/Color.WebSafePalette.cs
index feb4a8659d..bd17d9a76c 100644
--- a/src/ImageSharp/Color/Color.WebSafePalette.cs
+++ b/src/ImageSharp/Color/Color.WebSafePalette.cs
@@ -8,15 +8,15 @@ namespace SixLabors.ImageSharp;
///
public partial struct Color
{
- private static readonly Lazy WebSafePaletteLazy = new Lazy(CreateWebSafePalette, true);
+ private static readonly Lazy WebSafePaletteLazy = new(CreateWebSafePalette, true);
///
/// Gets a collection of named, web safe colors as defined in the CSS Color Module Level 4.
///
public static ReadOnlyMemory WebSafePalette => WebSafePaletteLazy.Value;
- private static Color[] CreateWebSafePalette() => new[]
- {
+ private static Color[] CreateWebSafePalette() =>
+ [
AliceBlue,
AntiqueWhite,
Aqua,
@@ -159,5 +159,5 @@ public partial struct Color
WhiteSmoke,
Yellow,
YellowGreen
- };
+ ];
}
diff --git a/src/ImageSharp/Color/Color.WernerPalette.cs b/src/ImageSharp/Color/Color.WernerPalette.cs
index 1058da6547..583c71379f 100644
--- a/src/ImageSharp/Color/Color.WernerPalette.cs
+++ b/src/ImageSharp/Color/Color.WernerPalette.cs
@@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp;
///
public partial struct Color
{
- private static readonly Lazy WernerPaletteLazy = new Lazy(CreateWernerPalette, true);
+ private static readonly Lazy WernerPaletteLazy = new(CreateWernerPalette, true);
///
/// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821.
@@ -16,8 +16,8 @@ public partial struct Color
///
public static ReadOnlyMemory WernerPalette => WernerPaletteLazy.Value;
- private static Color[] CreateWernerPalette() => new[]
- {
+ private static Color[] CreateWernerPalette() =>
+ [
ParseHex("#f1e9cd"),
ParseHex("#f2e7cf"),
ParseHex("#ece6d0"),
@@ -128,5 +128,5 @@ public partial struct Color
ParseHex("#9b856b"),
ParseHex("#766051"),
ParseHex("#453b32")
- };
+ ];
}
diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs
index 13af25f6c7..dd248d488f 100644
--- a/src/ImageSharp/Color/Color.cs
+++ b/src/ImageSharp/Color/Color.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
@@ -18,34 +19,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 +53,7 @@ public readonly partial struct Color : IEquatable
/// 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,131 +65,168 @@ public readonly partial struct Color : IEquatable
/// 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 Color(source.ToScaledVector4());
+ }
+
+ return new Color(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 generic scaled to a span of .
///
- /// The pixel to convert from.
- /// The pixel format.
- /// The .
- [MethodImpl(InliningOptions.ShortMethod)]
- public static Color FromPixel(TPixel pixel)
- where TPixel : unmanaged, IPixel
+ /// The source vector span.
+ /// The destination color span.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void FromScaledVector(ReadOnlySpan source, Span destination)
{
- // 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))
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+ for (int i = 0; i < source.Length; i++)
{
- return new((L16)(object)pixel);
+ destination[i] = FromScaledVector(source[i]);
}
- else if (Unsafe.SizeOf() <= Unsafe.SizeOf())
+ }
+
+ ///
+ /// Bulk converts a span of a specified type to a span of .
+ ///
+ /// 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
+ {
+ 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 < source.Length; i++)
+ {
+ destination[i] = FromScaledVector(source[i].ToScaledVector4());
+ }
}
else
{
- return new(pixel);
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = new Color(source[i]);
+ }
}
}
///
- /// Creates a new instance of the struct
- /// from the given hexadecimal string.
+ /// Gets a from the given hexadecimal string.
///
///
- /// The hexadecimal representation of the combined color components arranged
- /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
+ /// The hexadecimal representation of the combined color components.
+ ///
+ ///
+ /// The format of the hexadecimal string to parse, if applicable. Defaults to .
///
///
- /// The .
+ /// The equivalent of the hexadecimal input.
///
- [MethodImpl(InliningOptions.ShortMethod)]
- public static Color ParseHex(string hex)
+ ///
+ /// Thrown when the is not in the correct format.
+ ///
+ public static Color ParseHex(string hex, ColorHexFormat format = ColorHexFormat.Rgba)
{
- Rgba32 rgba = Rgba32.ParseHex(hex);
+ Guard.NotNull(hex, nameof(hex));
+
+ if (!TryParseHex(hex, out Color color, format))
+ {
+ throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex));
+ }
- return new Color(rgba);
+ return color;
}
///
- /// Attempts to creates a new instance of the struct
- /// from the given hexadecimal string.
+ /// Gets a from the given hexadecimal string.
///
///
- /// The hexadecimal representation of the combined color components arranged
- /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
+ /// The hexadecimal representation of the combined color components.
+ ///
+ ///
+ /// When this method returns, contains the equivalent of the hexadecimal input.
+ ///
+ ///
+ /// The format of the hexadecimal string to parse, if applicable. Defaults to .
///
- /// When this method returns, contains the equivalent of the hexadecimal input.
///
- /// The .
+ /// if the parsing was successful; otherwise, .
///
- [MethodImpl(InliningOptions.ShortMethod)]
- public static bool TryParseHex(string hex, out Color result)
+ public static bool TryParseHex(string hex, out Color result, ColorHexFormat format = ColorHexFormat.Rgba)
{
result = default;
- if (Rgba32.TryParseHex(hex, out Rgba32 rgba))
+ if (format == ColorHexFormat.Argb)
{
- result = new Color(rgba);
- return true;
+ if (TryParseArgbHex(hex, out Argb32 argb))
+ {
+ result = FromPixel(argb);
+ return true;
+ }
+ }
+ else if (format == ColorHexFormat.Rgba)
+ {
+ if (TryParseRgbaHex(hex, out Rgba32 rgba))
+ {
+ result = FromPixel(rgba);
+ return true;
+ }
}
return false;
}
///
- /// Creates a new instance of the struct
- /// from the given input string.
+ /// Gets a from the given input string.
///
///
- /// The name of the color or the hexadecimal representation of the combined color components arranged
- /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
+ /// The name of the color or the hexadecimal representation of the combined color components.
+ ///
+ ///
+ /// The format of the hexadecimal string to parse, if applicable. Defaults to .
///
///
- /// The .
+ /// The equivalent of the input string.
///
- /// Input string is not in the correct format.
- public static Color Parse(string input)
+ ///
+ /// Thrown when the is not in the correct format.
+ ///
+ public static Color Parse(string input, ColorHexFormat format = ColorHexFormat.Rgba)
{
Guard.NotNull(input, nameof(input));
- if (!TryParse(input, out Color color))
+ if (!TryParse(input, out Color color, format))
{
throw new ArgumentException("Input string is not in the correct format.", nameof(input));
}
@@ -206,18 +235,21 @@ public readonly partial struct Color : IEquatable
}
///
- /// Attempts to creates a new instance of the struct
- /// from the given input string.
+ /// Tries to create a new instance of the struct from the given input string.
///
///
- /// The name of the color or the hexadecimal representation of the combined color components arranged
- /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax.
+ /// The name of the color or the hexadecimal representation of the combined color components.
+ ///
+ ///
+ /// When this method returns, contains the equivalent of the input string.
+ ///
+ ///
+ /// The format of the hexadecimal string to parse, if applicable. Defaults to .
///
- /// When this method returns, contains the equivalent of the hexadecimal input.
///
- /// The .
+ /// if the parsing was successful; otherwise, .
///
- public static bool TryParse(string input, out Color result)
+ public static bool TryParse(string input, out Color result, ColorHexFormat format = ColorHexFormat.Rgba)
{
result = default;
@@ -231,7 +263,13 @@ public readonly partial struct Color : IEquatable
return true;
}
- return TryParseHex(input, out result);
+ result = default;
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ return false;
+ }
+
+ return TryParseHex(input, out result, format);
}
///
@@ -239,29 +277,48 @@ public readonly partial struct Color : IEquatable
///
/// The new value of alpha [0..1].
/// The color having it's alpha channel altered.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
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.
+ /// Gets the hexadecimal string representation of the color instance.
///
+ ///
+ /// The format of the hexadecimal string to return. Defaults to .
+ ///
/// A hexadecimal string representation of the value.
- [MethodImpl(InliningOptions.ShortMethod)]
- public string ToHex() => this.data.ToRgba32().ToHex();
+ /// Thrown when the is not supported.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public string ToHex(ColorHexFormat format = ColorHexFormat.Rgba)
+ {
+ Rgba32 rgba = (this.boxedHighPrecisionPixel is not null)
+ ? this.boxedHighPrecisionPixel.ToRgba32()
+ : Rgba32.FromScaledVector4(this.data);
+
+ uint hexOrder = format switch
+ {
+ ColorHexFormat.Argb => (uint)((rgba.B << 0) | (rgba.G << 8) | (rgba.R << 16) | (rgba.A << 24)),
+ ColorHexFormat.Rgba => (uint)((rgba.A << 0) | (rgba.B << 8) | (rgba.G << 16) | (rgba.R << 24)),
+ _ => throw new ArgumentOutOfRangeException(nameof(format), format, "Unsupported color hex format.")
+ };
+
+ return hexOrder.ToString("X8", CultureInfo.InvariantCulture);
+ }
///
- public override string ToString() => this.ToHex();
+ public override string ToString() => this.ToHex(ColorHexFormat.Rgba);
///
/// 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
{
@@ -272,14 +329,27 @@ public readonly partial struct Color : IEquatable
if (this.boxedHighPrecisionPixel is null)
{
- pixel = default;
- pixel.FromRgba64(this.data);
- return pixel;
+ return TPixel.FromScaledVector4(this.data);
}
- pixel = default;
- pixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
- return pixel;
+ 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;
+ }
+
+ return this.boxedHighPrecisionPixel.ToScaledVector4();
}
///
@@ -288,11 +358,12 @@ public readonly partial struct Color : IEquatable
/// 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++)
{
@@ -301,12 +372,12 @@ public readonly partial struct Color : IEquatable
}
///
- [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;
@@ -316,14 +387,251 @@ public readonly partial struct Color : IEquatable
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();
}
+
+ ///
+ /// Gets the hexadecimal string representation of the color instance in the format RRGGBBAA.
+ ///
+ ///
+ /// The hexadecimal representation of the combined color components.
+ ///
+ ///
+ /// When this method returns, contains the equivalent of the hexadecimal input.
+ ///
+ ///
+ /// if the parsing was successful; otherwise, .
+ ///
+ private static bool TryParseRgbaHex(string? hex, out Rgba32 result)
+ {
+ result = default;
+
+ if (!TryConvertToRgbaUInt32(hex, out uint packedValue))
+ {
+ return false;
+ }
+
+ result = Unsafe.As(ref packedValue);
+ return true;
+ }
+
+ ///
+ /// Gets the hexadecimal string representation of the color instance in the format AARRGGBB.
+ ///
+ ///
+ /// The hexadecimal representation of the combined color components.
+ ///
+ ///
+ /// When this method returns, contains the equivalent of the hexadecimal input.
+ ///
+ ///
+ /// if the parsing was successful; otherwise, .
+ ///
+ private static bool TryParseArgbHex(string? hex, out Argb32 result)
+ {
+ result = default;
+
+ if (!TryConvertToArgbUInt32(hex, out uint packedValue))
+ {
+ return false;
+ }
+
+ result = Unsafe.As(ref packedValue);
+ return true;
+ }
+
+ private static bool TryConvertToRgbaUInt32(string? value, out uint result)
+ {
+ result = default;
+
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return false;
+ }
+
+ ReadOnlySpan hex = value.AsSpan();
+
+ if (hex[0] == '#')
+ {
+ hex = hex[1..];
+ }
+
+ byte a = 255, r, g, b;
+
+ switch (hex.Length)
+ {
+ case 8:
+ if (!TryParseByte(hex[0], hex[1], out r) ||
+ !TryParseByte(hex[2], hex[3], out g) ||
+ !TryParseByte(hex[4], hex[5], out b) ||
+ !TryParseByte(hex[6], hex[7], out a))
+ {
+ return false;
+ }
+
+ break;
+
+ case 6:
+ if (!TryParseByte(hex[0], hex[1], out r) ||
+ !TryParseByte(hex[2], hex[3], out g) ||
+ !TryParseByte(hex[4], hex[5], out b))
+ {
+ return false;
+ }
+
+ break;
+
+ case 4:
+ if (!TryExpand(hex[0], out r) ||
+ !TryExpand(hex[1], out g) ||
+ !TryExpand(hex[2], out b) ||
+ !TryExpand(hex[3], out a))
+ {
+ return false;
+ }
+
+ break;
+
+ case 3:
+ if (!TryExpand(hex[0], out r) ||
+ !TryExpand(hex[1], out g) ||
+ !TryExpand(hex[2], out b))
+ {
+ return false;
+ }
+
+ break;
+
+ default:
+ return false;
+ }
+
+ result = (uint)(r | (g << 8) | (b << 16) | (a << 24)); // RGBA layout
+ return true;
+ }
+
+ private static bool TryConvertToArgbUInt32(string? value, out uint result)
+ {
+ result = default;
+
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return false;
+ }
+
+ ReadOnlySpan hex = value.AsSpan();
+
+ if (hex[0] == '#')
+ {
+ hex = hex[1..];
+ }
+
+ byte a = 255, r, g, b;
+
+ switch (hex.Length)
+ {
+ case 8:
+ if (!TryParseByte(hex[0], hex[1], out a) ||
+ !TryParseByte(hex[2], hex[3], out r) ||
+ !TryParseByte(hex[4], hex[5], out g) ||
+ !TryParseByte(hex[6], hex[7], out b))
+ {
+ return false;
+ }
+
+ break;
+
+ case 6:
+ if (!TryParseByte(hex[0], hex[1], out r) ||
+ !TryParseByte(hex[2], hex[3], out g) ||
+ !TryParseByte(hex[4], hex[5], out b))
+ {
+ return false;
+ }
+
+ break;
+
+ case 4:
+ if (!TryExpand(hex[0], out a) ||
+ !TryExpand(hex[1], out r) ||
+ !TryExpand(hex[2], out g) ||
+ !TryExpand(hex[3], out b))
+ {
+ return false;
+ }
+
+ break;
+
+ case 3:
+ if (!TryExpand(hex[0], out r) ||
+ !TryExpand(hex[1], out g) ||
+ !TryExpand(hex[2], out b))
+ {
+ return false;
+ }
+
+ break;
+
+ default:
+ return false;
+ }
+
+ result = (uint)((b << 24) | (g << 16) | (r << 8) | a);
+ return true;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool TryParseByte(char hi, char lo, out byte value)
+ {
+ if (TryConvertHexCharToByte(hi, out byte high) && TryConvertHexCharToByte(lo, out byte low))
+ {
+ value = (byte)((high << 4) | low);
+ return true;
+ }
+
+ value = 0;
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool TryExpand(char c, out byte value)
+ {
+ if (TryConvertHexCharToByte(c, out byte nibble))
+ {
+ value = (byte)((nibble << 4) | nibble);
+ return true;
+ }
+
+ value = 0;
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool TryConvertHexCharToByte(char c, out byte value)
+ {
+ if ((uint)(c - '0') <= 9)
+ {
+ value = (byte)(c - '0');
+ return true;
+ }
+
+ char lower = (char)(c | 0x20); // Normalize to lowercase
+
+ if ((uint)(lower - 'a') <= 5)
+ {
+ value = (byte)(lower - 'a' + 10);
+ return true;
+ }
+
+ value = 0;
+ return false;
+ }
}
diff --git a/src/ImageSharp/Color/ColorHexFormat.cs b/src/ImageSharp/Color/ColorHexFormat.cs
new file mode 100644
index 0000000000..e1cd898c70
--- /dev/null
+++ b/src/ImageSharp/Color/ColorHexFormat.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp;
+
+///
+/// Specifies the channel order when formatting or parsing a color as a hexadecimal string.
+///
+public enum ColorHexFormat
+{
+ ///
+ /// Uses RRGGBBAA channel order where the red, green, and blue components come first,
+ /// followed by the alpha component. This matches the CSS Color Module Level 4 and common web standards.
+ ///
+ /// When parsing, supports the following formats:
+ ///
+ /// - #RGB expands to RRGGBBFF (fully opaque)
+ /// - #RGBA expands to RRGGBBAA
+ /// - #RRGGBB expands to RRGGBBFF (fully opaque)
+ /// - #RRGGBBAA used as-is
+ ///
+ ///
+ /// When formatting, outputs an 8-digit hex string in RRGGBBAA order.
+ ///
+ Rgba,
+
+ ///
+ /// Uses AARRGGBB channel order where the alpha component comes first,
+ /// followed by the red, green, and blue components. This matches the Microsoft/XAML convention.
+ ///
+ /// When parsing, supports the following formats:
+ ///
+ /// - #ARGB expands to AARRGGBB
+ /// - #AARRGGBB used as-is
+ ///
+ ///
+ /// When formatting, outputs an 8-digit hex string in AARRGGBB order.
+ ///
+ Argb
+}
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/ColorProfiles/CieConstants.cs b/src/ImageSharp/ColorProfiles/CieConstants.cs
new file mode 100644
index 0000000000..d13a84450f
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/CieConstants.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// Constants use for Cie conversion calculations
+///
+///
+internal static class CieConstants
+{
+ ///
+ /// 216F / 24389F
+ ///
+ public const float Epsilon = 216f / 24389f;
+
+ ///
+ /// 24389F / 27F
+ ///
+ 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..ebe1631021
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/CieLab.cs
@@ -0,0 +1,220 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// 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.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);
+
+ ///
+ public Vector4 ToScaledVector4()
+ {
+ Vector3 v3 = default;
+ v3 += this.AsVector3Unsafe();
+ v3 += new Vector3(0, 128F, 128F);
+ v3 /= new Vector3(100F, 255F, 255F);
+ return new Vector4(v3, 1F);
+ }
+
+ ///
+ public static CieLab FromScaledVector4(Vector4 source)
+ {
+ Vector3 v3 = source.AsVector3();
+ v3 *= new Vector3(100F, 255, 255);
+ v3 -= new Vector3(0, 128F, 128F);
+ return new CieLab(v3);
+ }
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
+ ///
+ [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.SourceWhitePoint;
+ Vector3 wxyz = new(whitePoint.X, whitePoint.Y, whitePoint.Z);
+ Vector3 xyzr = new(xr, yr, zr);
+
+ return new CieXyz(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/ColorProfiles/CieLch.cs b/src/ImageSharp/ColorProfiles/CieLch.cs
new file mode 100644
index 0000000000..e62aa2ba23
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/CieLch.cs
@@ -0,0 +1,220 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color.
+///
+///
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct CieLch : 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 CieLch(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 CieLch(Vector3 vector)
+ {
+ vector = Vector3.Clamp(vector, Min, Max);
+ this.L = vector.X;
+ this.C = vector.Y;
+ this.H = vector.Z;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
+ private CieLch(Vector3 vector, bool _)
+#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
+ {
+ vector = Vector3.Clamp(vector, Min, Max);
+ this.L = vector.X;
+ this.C = vector.Y;
+ this.H = vector.Z;
+ }
+
+ ///
+ /// 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 -200 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.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(CieLch left, CieLch 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 !=(CieLch left, CieLch right) => !left.Equals(right);
+
+ ///
+ public Vector4 ToScaledVector4()
+ {
+ Vector3 v3 = default;
+ v3 += this.AsVector3Unsafe();
+ v3 += new Vector3(0, 200, 0);
+ v3 /= new Vector3(100, 400, 360);
+ return new Vector4(v3, 1F);
+ }
+
+ ///
+ public static CieLch FromScaledVector4(Vector4 source)
+ {
+ Vector3 v3 = source.AsVector3();
+ v3 *= new Vector3(100, 400, 360);
+ v3 -= new Vector3(0, 200, 0);
+ return new CieLch(v3, true);
+ }
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
+ ///
+ public static CieLch FromProfileConnectingSpace(ColorConversionOptions options, in CieLab source)
+ {
+ // Conversion algorithm described here:
+ // https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC
+ float l = source.L, a = source.A, b = source.B;
+ float c = MathF.Sqrt((a * a) + (b * b));
+ float hRadians = MathF.Atan2(b, a);
+ float hDegrees = GeometryUtilities.RadianToDegree(hRadians);
+
+ // Wrap the angle round at 360.
+ hDegrees %= 360;
+
+ // Make sure it's not negative.
+ while (hDegrees < 0)
+ {
+ hDegrees += 360;
+ }
+
+ return new CieLch(l, c, hDegrees);
+ }
+
+ ///
+ 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);
+ }
+ }
+
+ ///
+ public CieLab ToProfileConnectingSpace(ColorConversionOptions options)
+ {
+ // Conversion algorithm described here:
+ // https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC
+ float l = this.L, c = this.C, hDegrees = this.H;
+ float hRadians = GeometryUtilities.DegreeToRadian(hDegrees);
+
+ float a = c * MathF.Cos(hRadians);
+ float b = c * MathF.Sin(hRadians);
+
+ return new CieLab(l, a, b);
+ }
+
+ ///
+ public static void ToProfileConnectionSpace(ColorConversionOptions options, ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ for (int i = 0; i < source.Length; i++)
+ {
+ CieLch 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($"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..5478752ddc
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/CieLchuv.cs
@@ -0,0 +1,219 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// 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)
+ {
+ vector = Vector3.Clamp(vector, Min, Max);
+ this.L = vector.X;
+ this.C = vector.Y;
+ this.H = vector.Z;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
+ private CieLchuv(Vector3 vector, bool _)
+#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
+ {
+ this.L = vector.X;
+ this.C = vector.Y;
+ this.H = vector.Z;
+ }
+
+ ///
+ /// 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 -200 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 Vector4 ToScaledVector4()
+ {
+ Vector3 v3 = default;
+ v3 += this.AsVector3Unsafe();
+ v3 += new Vector3(0, 200, 0);
+ v3 /= new Vector3(100, 400, 360);
+ return new Vector4(v3, 1F);
+ }
+
+ ///
+ public static CieLchuv FromScaledVector4(Vector4 source)
+ {
+ Vector3 v3 = source.AsVector3();
+ v3 *= new Vector3(100, 400, 360);
+ v3 -= new Vector3(0, 200, 0);
+ return new CieLchuv(v3, true);
+ }
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
+ ///
+ 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..b17c433313
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/CieLuv.cs
@@ -0,0 +1,232 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// 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.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 Vector4 ToScaledVector4() => throw new NotImplementedException();
+
+ ///
+ public static CieLuv FromScaledVector4(Vector4 source) => throw new NotImplementedException();
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination) => throw new NotImplementedException();
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination) => throw new NotImplementedException();
+
+ ///
+ 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.SourceWhitePoint;
+
+ double l = this.L, u = this.U, v = this.V;
+
+ double u0 = ComputeU(in whitePoint);
+ double v0 = ComputeV(in whitePoint);
+
+ double y = l > CieConstants.Kappa * CieConstants.Epsilon
+ ? Numerics.Pow3((l + 16) / 116d)
+ : l / CieConstants.Kappa;
+
+ double a = ((52 * l / (u + (13 * l * u0))) - 1) / 3;
+ double b = -5 * y;
+ const double c = -1 / 3d;
+ double d = y * ((39 * l / (v + (13 * l * v0))) - 5);
+
+ double x = (d - b) / (a - c);
+ double z = (x * a) + b;
+
+ if (double.IsNaN(x) || x == -0d)
+ {
+ x = 0;
+ }
+
+ if (double.IsNaN(y) || y == -0d)
+ {
+ y = 0;
+ }
+
+ if (double.IsNaN(z) || z == -0d)
+ {
+ z = 0;
+ }
+
+ return new CieXyz((float)x, (float)y, (float)z);
+ }
+
+ ///
+ 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/ColorProfiles/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorProfiles/CieXyChromaticityCoordinates.cs
new file mode 100644
index 0000000000..fa12b81d22
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/CieXyChromaticityCoordinates.cs
@@ -0,0 +1,87 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// Represents the coordinates of CIEXY chromaticity space.
+///
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct CieXyChromaticityCoordinates : IEquatable
+{
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Chromaticity coordinate x (usually from 0 to 1)
+ /// Chromaticity coordinate y (usually from 0 to 1)
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public CieXyChromaticityCoordinates(float x, float y)
+ {
+ this.X = x;
+ this.Y = y;
+ }
+
+ ///
+ /// Gets the chromaticity X-coordinate.
+ ///
+ ///
+ /// Ranges usually from 0 to 1.
+ ///
+ public float X { get; }
+
+ ///
+ /// Gets the chromaticity Y-coordinate
+ ///
+ ///
+ /// Ranges usually from 0 to 1.
+ ///
+ public float Y { 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(InliningOptions.ShortMethod)]
+ public static bool operator ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates 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(InliningOptions.ShortMethod)]
+ public static bool operator !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right)
+ => !left.Equals(right);
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public override int GetHashCode()
+ => HashCode.Combine(this.X, this.Y);
+
+ ///
+ public override string ToString()
+ => FormattableString.Invariant($"CieXyChromaticityCoordinates({this.X:#0.##}, {this.Y:#0.##})");
+
+ ///
+ public override bool Equals(object? obj)
+ => obj is CieXyChromaticityCoordinates other && this.Equals(other);
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public bool Equals(CieXyChromaticityCoordinates other)
+ => this.AsVector2Unsafe() == other.AsVector2Unsafe();
+
+ private Vector2 AsVector2Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this));
+}
diff --git a/src/ImageSharp/ColorProfiles/CieXyy.cs b/src/ImageSharp/ColorProfiles/CieXyy.cs
new file mode 100644
index 0000000000..744b6195e9
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/CieXyy.cs
@@ -0,0 +1,189 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// Represents an CIE xyY 1931 color
+///
+///
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct CieXyy : IColorProfile
+{
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The x chroma component.
+ /// The y chroma component.
+ /// The y luminance component.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyy(float x, float y, float yl)
+ {
+ // Not clamping as documentation about this space only indicates "usual" ranges
+ this.X = x;
+ this.Y = y;
+ this.Yl = yl;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the x, y, Y components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyy(Vector3 vector)
+ {
+ // Not clamping as documentation about this space only indicates "usual" ranges
+ this.X = vector.X;
+ this.Y = vector.Y;
+ this.Yl = vector.Z;
+ }
+
+ ///
+ /// Gets the X chrominance component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float X { get; }
+
+ ///
+ /// Gets the Y chrominance component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float Y { get; }
+
+ ///
+ /// Gets the Y luminance component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float Yl { 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 ==(CieXyy left, CieXyy 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 !=(CieXyy left, CieXyy right) => !left.Equals(right);
+
+ ///
+ public Vector4 ToScaledVector4()
+ => new(this.AsVector3Unsafe(), 1F);
+
+ ///
+ public static CieXyy FromScaledVector4(Vector4 source)
+ => new(source.AsVector3());
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
+ ///
+ 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 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(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(CieXyy other)
+ => this.AsVector3Unsafe() == other.AsVector3Unsafe();
+
+ private Vector3 AsVector3Unsafe() => Unsafe.As(ref Unsafe.AsRef(in this));
+}
diff --git a/src/ImageSharp/ColorProfiles/CieXyz.cs b/src/ImageSharp/ColorProfiles/CieXyz.cs
new file mode 100644
index 0000000000..94fcfb21bb
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/CieXyz.cs
@@ -0,0 +1,203 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// Represents an CIE XYZ 1931 color
+///
+///
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct CieXyz : IProfileConnectingSpace
+{
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// 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(MethodImplOptions.AggressiveInlining)]
+ public CieXyz(float x, float y, float z)
+ {
+ // Not clamping as documentation about this space only indicates "usual" ranges
+ this.X = x;
+ this.Y = y;
+ this.Z = z;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the x, y, z components.
+ public CieXyz(Vector3 vector)
+ {
+ this.X = vector.X;
+ this.Y = vector.Y;
+ this.Z = vector.Z;
+ }
+
+ ///
+ /// 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 float X { get; }
+
+ ///
+ /// Gets the Y luminance component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ 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 float Z { 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 ==(CieXyz left, CieXyz 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 !=(CieXyz left, CieXyz right) => !left.Equals(right);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Vector3 ToVector3() => new(this.X, this.Y, this.Z);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Vector4 ToVector4()
+ {
+ Vector3 v3 = default;
+ v3 += this.AsVector3Unsafe();
+ return new Vector4(v3, 1F);
+ }
+
+ ///
+ public Vector4 ToScaledVector4()
+ {
+ Vector3 v3 = default;
+ v3 += this.AsVector3Unsafe();
+ v3 *= 32768F / 65535;
+ return new Vector4(v3, 1F);
+ }
+
+ internal static CieXyz FromVector4(Vector4 source)
+ {
+ Vector3 v3 = source.AsVector3();
+ return new CieXyz(v3);
+ }
+
+ ///
+ public static CieXyz FromScaledVector4(Vector4 source)
+ {
+ Vector3 v3 = source.AsVector3();
+ v3 *= 65535 / 32768F;
+ return new CieXyz(v3);
+ }
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToScaledVector4();
+ }
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromScaledVector4(source[i]);
+ }
+ }
+
+ internal static void FromVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = FromVector4(source[i]);
+ }
+ }
+
+ internal static void ToVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+
+ // TODO: Optimize via SIMD
+ for (int i = 0; i < source.Length; i++)
+ {
+ destination[i] = source[i].ToVector4();
+ }
+ }
+
+ ///
+ 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);
+
+ ///
+ public override string ToString() => FormattableString.Invariant($"CieXyz({this.X:#0.##}, {this.Y:#0.##}, {this.Z:#0.##})");
+
+ ///
+ public override bool Equals(object? obj) => obj is CieXyz other && this.Equals(other);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(CieXyz other)
+ => this.AsVector3Unsafe() == other.AsVector3Unsafe();
+
+ internal 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..ee81ff9f7e
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/Cmyk.cs
@@ -0,0 +1,202 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// 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 = Vector4.Clamp(vector, Min, Max);
+ this.C = vector.X;
+ this.M = vector.Y;
+ this.Y = vector.Z;
+ this.K = vector.W;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
+ private Cmyk(Vector4 vector, bool _)
+#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
+ {
+ this.C = vector.X;
+ this.M = vector.Y;
+ this.Y = vector.Z;
+ this.K = vector.W;
+ }
+
+ ///
+ /// 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 Vector4 ToScaledVector4()
+ {
+ Vector4 v4 = default;
+ v4 += this.AsVector4Unsafe();
+ return v4;
+ }
+
+ ///
+ public static Cmyk FromScaledVector4(Vector4 source)
+ => new(source, true);
+
+ ///
+ public static void ToScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+ MemoryMarshal.Cast(source).CopyTo(destination);
+ }
+
+ ///
+ public static void FromScaledVector4(ReadOnlySpan source, Span destination)
+ {
+ Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
+ MemoryMarshal.Cast(source).CopyTo(destination);
+ }
+
+ ///
+ public static Cmyk FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
+ {
+ // To CMY
+ Vector3 cmy = Vector3.One - source.AsVector3Unsafe();
+
+ // To CMYK
+ Vector3 k = new(MathF.Min(cmy.X, MathF.Min(cmy.Y, cmy.Z)));
+
+ if (k.X >= 1F - Constants.Epsilon)
+ {
+ return new Cmyk(0, 0, 0, 1F);
+ }
+
+ cmy = (cmy - k) / (Vector3.One - k);
+
+ return new Cmyk(cmy.X, cmy.Y, cmy.Z, k.X);
+ }
+
+ ///
+ 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)) * (1F - 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++)
+ {
+ destination[i] = source[i].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..882d246a76
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorConversionOptions.cs
@@ -0,0 +1,94 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// Provides options for color profile conversion.
+///
+public class ColorConversionOptions
+{
+ private Matrix4x4 adaptationMatrix;
+ private YCbCrTransform yCbCrTransform;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ColorConversionOptions()
+ {
+ this.AdaptationMatrix = KnownChromaticAdaptationMatrices.Bradford;
+ this.YCbCrTransform = KnownYCbCrMatrices.BT601;
+ }
+
+ ///
+ /// 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 SourceWhitePoint { 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 SourceRgbWorkingSpace { 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 YCbCr matrix to used to perform conversions from/to RGB.
+ ///
+ public YCbCrTransform YCbCrTransform
+ {
+ get => this.yCbCrTransform;
+ init
+ {
+ this.yCbCrTransform = value;
+ this.TransposedYCbCrTransform = value.Transpose();
+ }
+ }
+
+ ///
+ /// Gets the source ICC profile.
+ ///
+ public IccProfile? SourceIccProfile { get; init; }
+
+ ///
+ /// Gets the target ICC profile.
+ ///
+ public IccProfile? TargetIccProfile { get; init; }
+
+ ///
+ /// 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 YCbCrTransform TransposedYCbCrTransform { get; private set; }
+
+ 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..7072537a4a
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverter.cs
@@ -0,0 +1,48 @@
+// 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 ColorConversionOptions())
+ {
+ }
+
+ ///
+ /// 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.SourceWhitePoint
+ : this.Options.SourceRgbWorkingSpace.WhitePoint;
+
+ CieXyz targetWhitePoint = TTo.GetChromaticAdaptionWhitePointSource() == ChromaticAdaptionWhitePointSource.WhitePoint
+ ? this.Options.TargetWhitePoint
+ : this.Options.TargetRgbWorkingSpace.WhitePoint;
+
+ return (sourceWhitePoint, targetWhitePoint);
+ }
+
+ internal bool ShouldUseIccProfiles()
+ => this.Options.SourceIccProfile != null && this.Options.TargetIccProfile != null;
+}
diff --git a/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs
new file mode 100644
index 0000000000..4d94f583ab
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieLab.cs
@@ -0,0 +1,98 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// Allows conversion between two color profiles based on the CIE Lab color space.
+///
+public static class ColorProfileConverterExtensionsCieLabCieLab
+{
+ ///
+ /// Converts a color value from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// The conversion process may use ICC profiles if available; otherwise, it performs a manual
+ /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
+ /// both source and target types to be value types implementing the appropriate color profile interface.
+ ///
+ /// The source color profile type. Must implement .
+ /// The target color profile type. Must implement .
+ /// The color profile converter to use for the conversion.
+ /// The source color value to convert.
+ /// A value of type representing the converted color in the target color profile.
+ public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ if (converter.ShouldUseIccProfiles())
+ {
+ return converter.ConvertUsingIccProfile(source);
+ }
+
+ ColorConversionOptions options = converter.Options;
+
+ // Convert to input PCS
+ CieLab pcsFromA = source.ToProfileConnectingSpace(options);
+ CieXyz pcsFromB = pcsFromA.ToProfileConnectingSpace(options);
+
+ // Adapt to target white point
+ (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints();
+ 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);
+ }
+
+ ///
+ /// Converts a span of color values from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// This method performs color conversion between two color profiles, handling necessary
+ /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
+ /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
+ /// for the destination; the caller is responsible for providing a suitably sized span.
+ ///
+ /// The type representing the source color profile. Must implement .
+ /// The type representing the destination color profile. Must implement .
+ /// The color profile converter to use for the conversion operation.
+ /// A read-only span containing the source color values to convert.
+ /// A span that receives the converted color values. Must be at least as long as the source span.
+ public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ if (converter.ShouldUseIccProfiles())
+ {
+ converter.ConvertUsingIccProfile(source, destination);
+ return;
+ }
+
+ 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..1de4510bc9
--- /dev/null
+++ b/src/ImageSharp/ColorProfiles/ColorProfileConverterExtensionsCieLabCieXyz.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.ColorProfiles;
+
+///
+/// Allows conversion between two color profiles based on the CIE Lab and CIE XYZ color spaces.
+///
+public static class ColorProfileConverterExtensionsCieLabCieXyz
+{
+ ///
+ /// Converts a color value from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// The conversion process may use ICC profiles if available; otherwise, it performs a manual
+ /// conversion through the profile connection space (PCS) with chromatic adaptation as needed. The method requires
+ /// both source and target types to be value types implementing the appropriate color profile interface.
+ ///
+ /// The source color profile type. Must implement .
+ /// The target color profile type. Must implement .
+ /// The color profile converter to use for the conversion.
+ /// The source color value to convert.
+ /// A value of type representing the converted color in the target color profile.
+ public static TTo Convert(this ColorProfileConverter converter, in TFrom source)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ if (converter.ShouldUseIccProfiles())
+ {
+ return converter.ConvertUsingIccProfile(source);
+ }
+
+ ColorConversionOptions options = converter.Options;
+
+ // Convert to input PCS
+ CieLab pcsFrom = source.ToProfileConnectingSpace(options);
+
+ // Convert between PCS
+ CieXyz pcsTo = pcsFrom.ToProfileConnectingSpace(options);
+
+ // Adapt to target white point
+ (CieXyz From, CieXyz To) whitePoints = converter.GetChromaticAdaptionWhitePoints();
+ pcsTo = VonKriesChromaticAdaptation.Transform(in pcsTo, whitePoints, options.AdaptationMatrix);
+
+ // Convert to output from PCS
+ return TTo.FromProfileConnectingSpace(options, in pcsTo);
+ }
+
+ ///
+ /// Converts a span of color values from one color profile to another using the specified color profile converter.
+ ///
+ ///
+ /// This method performs color conversion between two color profiles, handling necessary
+ /// transformations such as profile connection space conversion and chromatic adaptation. If ICC profiles are
+ /// available and applicable, the conversion uses them for improved accuracy. The method does not allocate memory
+ /// for the destination; the caller is responsible for providing a suitably sized span.
+ ///
+ /// The type representing the source color profile. Must implement .
+ /// The type representing the destination color profile. Must implement .
+ /// The color profile converter to use for the conversion operation.
+ /// A read-only span containing the source color values to convert.
+ /// A span that receives the converted color values. Must be at least as long as the source span.
+ public static void Convert(this ColorProfileConverter converter, ReadOnlySpan source, Span destination)
+ where TFrom : struct, IColorProfile
+ where TTo : struct, IColorProfile
+ {
+ if (converter.ShouldUseIccProfiles())
+ {
+ converter.ConvertUsingIccProfile(source, destination);
+ return;
+ }
+
+ 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