diff --git a/src/ImageSharp.Drawing/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs
index 975bce9ed..db40132c8 100644
--- a/src/ImageSharp.Drawing/DrawImage.cs
+++ b/src/ImageSharp.Drawing/DrawImage.cs
@@ -1,129 +1,129 @@
-//
-// Copyright (c) James Jackson-South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-
-namespace ImageSharp
-{
- using Drawing.Processors;
- using ImageSharp.PixelFormats;
-
- ///
- /// Extension methods for the type.
- ///
- public static partial class ImageExtensions
- {
- ///
- /// Draws the given image together with the current one by blending their pixels.
- ///
- /// The image this method extends.
- /// The image to blend with the currently processing image.
- /// The pixel format.
- /// The size to draw the blended image.
- /// The location to draw the blended image.
- /// The options.
- /// The .
- public static Image DrawImage(this Image source, Image image, Size size, Point location, GraphicsOptions options)
- where TPixel : struct, IPixel
- {
- if (size == default(Size))
- {
- size = new Size(image.Width, image.Height);
- }
-
- if (location == default(Point))
- {
- location = Point.Empty;
- }
-
- source.ApplyProcessor(new DrawImageProcessor(image, size, location, options), source.Bounds);
- return source;
- }
-
- ///
- /// Draws the given image together with the current one by blending their pixels.
- ///
- /// The pixel format.
- /// The image this method extends.
- /// The image to blend with the currently processing image.
- /// The opacity of the image image to blend. Must be between 0 and 1.
- /// The .
- public static Image Blend(this Image source, Image image, float percent)
- where TPixel : struct, IPixel
- {
- GraphicsOptions options = GraphicsOptions.Default;
- options.BlendPercentage = percent;
- return DrawImage(source, image, default(Size), default(Point), options);
- }
-
- ///
- /// Draws the given image together with the current one by blending their pixels.
- ///
- /// The pixel format.
- /// The image this method extends.
- /// The image to blend with the currently processing image.
- /// The blending mode.
- /// The opacity of the image image to blend. Must be between 0 and 1.
- /// The .
- public static Image Blend(this Image source, Image image, PixelBlenderMode blender, float percent)
- where TPixel : struct, IPixel
- {
- GraphicsOptions options = GraphicsOptions.Default;
- options.BlendPercentage = percent;
- options.BlenderMode = blender;
- return DrawImage(source, image, default(Size), default(Point), options);
- }
-
- ///
- /// Draws the given image together with the current one by blending their pixels.
- ///
- /// The pixel format.
- /// The image this method extends.
- /// The image to blend with the currently processing image.
- /// The options, including the blending type and belnding amount.
- /// The .
- public static Image Blend(this Image source, Image image, GraphicsOptions options)
- where TPixel : struct, IPixel
- {
- return DrawImage(source, image, default(Size), default(Point), options);
- }
-
- ///
- /// Draws the given image together with the current one by blending their pixels.
- ///
- /// The image this method extends.
- /// The image to blend with the currently processing image.
- /// The pixel format.
- /// The opacity of the image image to blend. Must be between 0 and 1.
- /// The size to draw the blended image.
- /// The location to draw the blended image.
- /// The .
- public static Image DrawImage(this Image source, Image image, float percent, Size size, Point location)
- where TPixel : struct, IPixel
- {
- GraphicsOptions options = GraphicsOptions.Default;
- options.BlendPercentage = percent;
- return source.DrawImage(image, size, location, options);
- }
-
- ///
- /// Draws the given image together with the current one by blending their pixels.
- ///
- /// The image this method extends.
- /// The image to blend with the currently processing image.
- /// The pixel format.
- /// The type of bending to apply.
- /// The opacity of the image image to blend. Must be between 0 and 1.
- /// The size to draw the blended image.
- /// The location to draw the blended image.
- /// The .
- public static Image DrawImage(this Image source, Image image, PixelBlenderMode blender, float percent, Size size, Point location)
- where TPixel : struct, IPixel
- {
- GraphicsOptions options = GraphicsOptions.Default;
- options.BlenderMode = blender;
- options.BlendPercentage = percent;
- return source.DrawImage(image, size, location, options);
- }
- }
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using Drawing.Processors;
+ using ImageSharp.PixelFormats;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// The image this method extends.
+ /// The image to blend with the currently processing image.
+ /// The pixel format.
+ /// The size to draw the blended image.
+ /// The location to draw the blended image.
+ /// The options.
+ /// The .
+ public static Image DrawImage(this Image source, Image image, Size size, Point location, GraphicsOptions options)
+ where TPixel : struct, IPixel
+ {
+ if (size == default(Size))
+ {
+ size = new Size(image.Width, image.Height);
+ }
+
+ if (location == default(Point))
+ {
+ location = Point.Empty;
+ }
+
+ source.ApplyProcessor(new DrawImageProcessor(image, size, location, options), source.Bounds);
+ return source;
+ }
+
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The image to blend with the currently processing image.
+ /// The opacity of the image image to blend. Must be between 0 and 1.
+ /// The .
+ public static Image Blend(this Image source, Image image, float percent)
+ where TPixel : struct, IPixel
+ {
+ GraphicsOptions options = GraphicsOptions.Default;
+ options.BlendPercentage = percent;
+ return DrawImage(source, image, default(Size), default(Point), options);
+ }
+
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The image to blend with the currently processing image.
+ /// The blending mode.
+ /// The opacity of the image image to blend. Must be between 0 and 1.
+ /// The .
+ public static Image Blend(this Image source, Image image, PixelBlenderMode blender, float percent)
+ where TPixel : struct, IPixel
+ {
+ GraphicsOptions options = GraphicsOptions.Default;
+ options.BlendPercentage = percent;
+ options.BlenderMode = blender;
+ return DrawImage(source, image, default(Size), default(Point), options);
+ }
+
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The image to blend with the currently processing image.
+ /// The options, including the blending type and belnding amount.
+ /// The .
+ public static Image Blend(this Image source, Image image, GraphicsOptions options)
+ where TPixel : struct, IPixel
+ {
+ return DrawImage(source, image, default(Size), default(Point), options);
+ }
+
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// The image this method extends.
+ /// The image to blend with the currently processing image.
+ /// The pixel format.
+ /// The opacity of the image image to blend. Must be between 0 and 1.
+ /// The size to draw the blended image.
+ /// The location to draw the blended image.
+ /// The .
+ public static Image DrawImage(this Image source, Image image, float percent, Size size, Point location)
+ where TPixel : struct, IPixel
+ {
+ GraphicsOptions options = GraphicsOptions.Default;
+ options.BlendPercentage = percent;
+ return source.DrawImage(image, size, location, options);
+ }
+
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// The image this method extends.
+ /// The image to blend with the currently processing image.
+ /// The pixel format.
+ /// The type of bending to apply.
+ /// The opacity of the image image to blend. Must be between 0 and 1.
+ /// The size to draw the blended image.
+ /// The location to draw the blended image.
+ /// The .
+ public static Image DrawImage(this Image source, Image image, PixelBlenderMode blender, float percent, Size size, Point location)
+ where TPixel : struct, IPixel
+ {
+ GraphicsOptions options = GraphicsOptions.Default;
+ options.BlenderMode = blender;
+ options.BlendPercentage = percent;
+ return source.DrawImage(image, size, location, options);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/CieLab.cs b/src/ImageSharp/ColorSpaces/CieLab.cs
similarity index 51%
rename from src/ImageSharp/Colors/Spaces/CieLab.cs
rename to src/ImageSharp/ColorSpaces/CieLab.cs
index c1e5cba5a..d382bbedb 100644
--- a/src/ImageSharp/Colors/Spaces/CieLab.cs
+++ b/src/ImageSharp/ColorSpaces/CieLab.cs
@@ -3,33 +3,29 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Colors.Spaces
+namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
- using ImageSharp.PixelFormats;
+ using System.Runtime.CompilerServices;
///
- /// Represents an CIE LAB 1976 color.
+ /// Represents a CIE L*a*b* 1976 color.
///
///
- public struct CieLab : IEquatable, IAlmostEquatable
+ internal struct CieLab : IColorVector, IEquatable, IAlmostEquatable
{
///
- /// Represents a that has L, A, B values set to zero.
+ /// D50 standard illuminant.
+ /// Used when reference white is not specified explicitly.
///
- public static readonly CieLab Empty = default(CieLab);
+ public static readonly CieXyz DefaultWhitePoint = Illuminants.D50;
///
- /// Min range used for clamping
- ///
- private static readonly Vector3 VectorMin = new Vector3(0, -100, -100);
-
- ///
- /// Max range used for clamping
+ /// Represents a that has L, A, B values set to zero.
///
- private static readonly Vector3 VectorMax = new Vector3(100);
+ public static readonly CieLab Empty = default(CieLab);
///
/// The backing vector for SIMD support.
@@ -42,29 +38,88 @@ namespace ImageSharp.Colors.Spaces
/// The lightness dimension.
/// The a (green - magenta) component.
/// The b (blue - yellow) component.
+ /// Uses as white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public CieLab(float l, float a, float b)
+ : this(new Vector3(l, a, b), DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The a (green - magenta) component.
+ /// The b (blue - yellow) component.
+ /// The reference white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLab(float l, float a, float b, CieXyz whitePoint)
+ : this(new Vector3(l, a, b), whitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, a, b components.
+ /// Uses as white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLab(Vector3 vector)
+ : this(vector, DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, a, b components.
+ /// The reference white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLab(Vector3 vector, CieXyz whitePoint)
: this()
{
- this.backingVector = Vector3.Clamp(new Vector3(l, a, b), VectorMin, VectorMax);
+ this.backingVector = vector;
+ this.WhitePoint = whitePoint;
+ }
+
+ ///
+ /// Gets the reference white point of this color
+ ///
+ public CieXyz WhitePoint
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
}
///
/// Gets the lightness dimension.
/// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).
///
- public float L => this.backingVector.X;
+ public float L
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
///
/// Gets the a color component.
- /// Negative is green, positive magenta.
+ /// A value ranging from -100 to 100. Negative is green, positive magenta.
///
- public float A => this.backingVector.Y;
+ public float A
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
///
/// Gets the b color component.
- /// Negative is blue, positive is yellow
+ /// A value ranging from -100 to 100. Negative is blue, positive is yellow
///
- public float B => this.backingVector.Z;
+ public float B
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
///
/// Gets a value indicating whether this is empty.
@@ -72,39 +127,11 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- ///
- /// The instance of to convert.
- ///
- ///
- /// An instance of .
- ///
- public static implicit operator CieLab(Rgba32 color)
+ ///
+ public Vector3 Vector
{
- // First convert to CIE XYZ
- Vector4 vector = color.ToVector4().Expand();
- float x = (vector.X * 0.4124F) + (vector.Y * 0.3576F) + (vector.Z * 0.1805F);
- float y = (vector.X * 0.2126F) + (vector.Y * 0.7152F) + (vector.Z * 0.0722F);
- float z = (vector.X * 0.0193F) + (vector.Y * 0.1192F) + (vector.Z * 0.9505F);
-
- // Now to LAB
- x /= 0.95047F;
-
- // y /= 1F;
- z /= 1.08883F;
-
- x = x > 0.008856F ? MathF.Pow(x, 0.3333333F) : ((903.3F * x) + 16F) / 116F;
- y = y > 0.008856F ? MathF.Pow(y, 0.3333333F) : ((903.3F * y) + 16F) / 116F;
- z = z > 0.008856F ? MathF.Pow(z, 0.3333333F) : ((903.3F * z) + 16F) / 116F;
-
- float l = MathF.Max(0, (116F * y) - 16F);
- float a = 500F * (x - y);
- float b = 200F * (y - z);
-
- return new CieLab(l, a, b);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
}
///
@@ -119,6 +146,7 @@ namespace ImageSharp.Colors.Spaces
///
/// True if the current left is equal to the parameter; otherwise, false.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(CieLab left, CieLab right)
{
return left.Equals(right);
@@ -136,6 +164,7 @@ namespace ImageSharp.Colors.Spaces
///
/// True if the current left is unequal to the parameter; otherwise, false.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieLab left, CieLab right)
{
return !left.Equals(right);
@@ -144,7 +173,12 @@ namespace ImageSharp.Colors.Spaces
///
public override int GetHashCode()
{
- return this.backingVector.GetHashCode();
+ unchecked
+ {
+ int hashCode = this.WhitePoint.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode();
+ return hashCode;
+ }
}
///
@@ -159,6 +193,7 @@ namespace ImageSharp.Colors.Spaces
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is CieLab)
@@ -170,19 +205,23 @@ namespace ImageSharp.Colors.Spaces
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieLab other)
{
- return this.backingVector.Equals(other.backingVector);
+ return this.backingVector.Equals(other.backingVector)
+ && this.WhitePoint.Equals(other.WhitePoint);
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(CieLab other, float precision)
{
- Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
- return result.X <= precision
+ return this.WhitePoint.Equals(other.WhitePoint)
+ && result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/CieLch.cs b/src/ImageSharp/ColorSpaces/CieLch.cs
new file mode 100644
index 000000000..03cf80bf1
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/CieLch.cs
@@ -0,0 +1,247 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color.
+ ///
+ ///
+ internal struct CieLch : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// D50 standard illuminant.
+ /// Used when reference white is not specified explicitly.
+ ///
+ public static readonly CieXyz DefaultWhitePoint = Illuminants.D50;
+
+ ///
+ /// Represents a that has L, C, H values set to zero.
+ ///
+ public static readonly CieLch Empty = default(CieLch);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The chroma, relative saturation.
+ /// The hue in degrees.
+ /// Uses as white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLch(float l, float c, float h)
+ : this(new Vector3(l, c, h), DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The chroma, relative saturation.
+ /// The hue in degrees.
+ /// The reference white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLch(float l, float c, float h, CieXyz whitePoint)
+ : this(new Vector3(l, c, h), whitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, c, h components.
+ /// Uses as white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLch(Vector3 vector)
+ : this(vector, DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, c, h components.
+ /// The reference white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLch(Vector3 vector, CieXyz whitePoint)
+ : this()
+ {
+ this.backingVector = vector;
+ this.WhitePoint = whitePoint;
+ }
+
+ ///
+ /// Gets the reference white point of this color
+ ///
+ public CieXyz WhitePoint
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ ///
+ /// Gets the lightness dimension.
+ /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).
+ ///
+ public float L
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
+
+ ///
+ /// Gets the a chroma component.
+ /// A value ranging from 0 to 100.
+ ///
+ public float C
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
+
+ ///
+ /// Gets the h° hue component in degrees.
+ /// A value ranging from 0 to 360.
+ ///
+ public float H
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ public Vector3 Vector
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
+ }
+
+ ///
+ /// 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)
+ {
+ return 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)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.WhitePoint.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "CieLch [Empty]";
+ }
+
+ return $"CieLch [ L={this.L:#0.##}, C={this.C:#0.##}, H={this.H:#0.##}]";
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj)
+ {
+ if (obj is CieLch)
+ {
+ return this.Equals((CieLch)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(CieLch other)
+ {
+ return this.backingVector.Equals(other.backingVector)
+ && this.WhitePoint.Equals(other.WhitePoint);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool AlmostEquals(CieLch other, float precision)
+ {
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return this.WhitePoint.Equals(other.WhitePoint)
+ && result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
+
+ ///
+ /// Computes the saturation of the color (chroma normalized by lightness)
+ ///
+ ///
+ /// A value ranging from 0 to 100.
+ ///
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Saturation()
+ {
+ float result = 100 * (this.C / this.L);
+
+ if (float.IsNaN(result))
+ {
+ return 0;
+ }
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/CieLchuv.cs b/src/ImageSharp/ColorSpaces/CieLchuv.cs
new file mode 100644
index 000000000..a4e8b424d
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/CieLchuv.cs
@@ -0,0 +1,247 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color.
+ ///
+ ///
+ internal struct CieLchuv : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// D50 standard illuminant.
+ /// Used when reference white is not specified explicitly.
+ ///
+ public static readonly CieXyz DefaultWhitePoint = Illuminants.D65;
+
+ ///
+ /// Represents a that has L, C, H values set to zero.
+ ///
+ public static readonly CieLchuv Empty = default(CieLchuv);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The chroma, relative saturation.
+ /// The hue in degrees.
+ /// Uses as white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLchuv(float l, float c, float h)
+ : this(new Vector3(l, c, h), DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The chroma, relative saturation.
+ /// The hue in degrees.
+ /// The reference white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLchuv(float l, float c, float h, CieXyz whitePoint)
+ : this(new Vector3(l, c, h), whitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, c, h components.
+ /// Uses as white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLchuv(Vector3 vector)
+ : this(vector, DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, c, h components.
+ /// The reference white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLchuv(Vector3 vector, CieXyz whitePoint)
+ : this()
+ {
+ this.backingVector = vector;
+ this.WhitePoint = whitePoint;
+ }
+
+ ///
+ /// Gets the reference white point of this color
+ ///
+ public CieXyz WhitePoint
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ ///
+ /// Gets the lightness dimension.
+ /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).
+ ///
+ public float L
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
+
+ ///
+ /// Gets the a chroma component.
+ /// A value ranging from 0 to 100.
+ ///
+ public float C
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
+
+ ///
+ /// Gets the h° hue component in degrees.
+ /// A value ranging from 0 to 360.
+ ///
+ public float H
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ public Vector3 Vector
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
+ }
+
+ ///
+ /// 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 ==(CieLchuv left, CieLchuv right)
+ {
+ return 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 !=(CieLchuv left, CieLchuv right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.WhitePoint.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "CieLchuv [Empty]";
+ }
+
+ return $"CieLchuv [ L={this.L:#0.##}, C={this.C:#0.##}, H={this.H:#0.##}]";
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj)
+ {
+ if (obj is CieLchuv)
+ {
+ return this.Equals((CieLchuv)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(CieLchuv other)
+ {
+ return this.backingVector.Equals(other.backingVector)
+ && this.WhitePoint.Equals(other.WhitePoint);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool AlmostEquals(CieLchuv other, float precision)
+ {
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return this.WhitePoint.Equals(other.WhitePoint)
+ && result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
+
+ ///
+ /// Computes the saturation of the color (chroma normalized by lightness)
+ ///
+ ///
+ /// A value ranging from 0 to 100.
+ ///
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Saturation()
+ {
+ float result = 100 * (this.C / this.L);
+
+ if (float.IsNaN(result))
+ {
+ return 0;
+ }
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/CieLuv.cs b/src/ImageSharp/ColorSpaces/CieLuv.cs
new file mode 100644
index 000000000..c3fd626e6
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/CieLuv.cs
@@ -0,0 +1,229 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// 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
+ ///
+ ///
+ internal struct CieLuv : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// D65 standard illuminant.
+ /// Used when reference white is not specified explicitly.
+ ///
+ public static readonly CieXyz DefaultWhitePoint = Illuminants.D65;
+
+ ///
+ /// Represents a that has L, U, and V values set to zero.
+ ///
+ public static readonly CieLuv Empty = default(CieLuv);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The blue-yellow chromaticity coordinate of the given whitepoint.
+ /// The red-green chromaticity coordinate of the given whitepoint.
+ /// Uses as white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLuv(float l, float u, float v)
+ : this(new Vector3(l, u, v), DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The blue-yellow chromaticity coordinate of the given whitepoint.
+ /// The red-green chromaticity coordinate of the given whitepoint.
+ /// The reference white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLuv(float l, float u, float v, CieXyz whitePoint)
+ : this(new Vector3(l, u, v), whitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, u, v components.
+ /// Uses as white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLuv(Vector3 vector)
+ : this(vector, DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, u, v components.
+ /// The reference white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLuv(Vector3 vector, CieXyz whitePoint)
+ : this()
+ {
+ this.backingVector = vector;
+ this.WhitePoint = whitePoint;
+ }
+
+ ///
+ /// Gets the reference white point of this color
+ ///
+ public CieXyz WhitePoint
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ ///
+ /// Gets the lightness dimension
+ /// A value usually ranging between 0 and 100.
+ ///
+ public float L
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
+
+ ///
+ /// Gets the blue-yellow chromaticity coordinate of the given whitepoint.
+ /// A value usually ranging between -100 and 100.
+ ///
+ public float U
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
+
+ ///
+ /// Gets the red-green chromaticity coordinate of the given whitepoint.
+ /// A value usually ranging between -100 and 100.
+ ///
+ public float V
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ public Vector3 Vector
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
+ }
+
+ ///
+ /// 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)
+ {
+ return 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)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.WhitePoint.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "CieLuv [ Empty ]";
+ }
+
+ return $"CieLuv [ L={this.L:#0.##}, U={this.U:#0.##}, V={this.V:#0.##} ]";
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj)
+ {
+ if (obj is CieLuv)
+ {
+ return this.Equals((CieLuv)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(CieLuv other)
+ {
+ return this.backingVector.Equals(other.backingVector)
+ && this.WhitePoint.Equals(other.WhitePoint);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool AlmostEquals(CieLuv other, float precision)
+ {
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return this.WhitePoint.Equals(other.WhitePoint)
+ && result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs
new file mode 100644
index 000000000..d9bfe88cd
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs
@@ -0,0 +1,161 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Represents the coordinates of CIEXY chromaticity space
+ ///
+ internal struct CieXyChromaticityCoordinates : IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has X, Y values set to zero.
+ ///
+ public static readonly CieXyChromaticityCoordinates Empty = default(CieXyChromaticityCoordinates);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector2 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Chromaticity coordinate x (usually from 0 to 1)
+ /// Chromaticity coordinate y (usually from 0 to 1)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyChromaticityCoordinates(float x, float y)
+ : this(new Vector2(x, y))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector containing the XY Chromaticity coordinates
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyChromaticityCoordinates(Vector2 vector)
+ {
+ this.backingVector = vector;
+ }
+
+ ///
+ /// Gets the chromaticity X-coordinate.
+ ///
+ ///
+ /// Ranges usually from 0 to 1.
+ ///
+ public float X
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
+
+ ///
+ /// Gets the chromaticity Y-coordinate
+ ///
+ ///
+ /// Ranges usually from 0 to 1.
+ ///
+ public float Y
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ /// 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 ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right)
+ {
+ return 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 !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.backingVector.GetHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "CieXyChromaticityCoordinates [Empty]";
+ }
+
+ return $"CieXyChromaticityCoordinates [ X={this.X:#0.##}, Y={this.Y:#0.##}]";
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj)
+ {
+ if (obj is CieXyChromaticityCoordinates)
+ {
+ return this.Equals((CieXyChromaticityCoordinates)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(CieXyChromaticityCoordinates other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool AlmostEquals(CieXyChromaticityCoordinates other, float precision)
+ {
+ var result = Vector2.Abs(this.backingVector - other.backingVector);
+
+ return result.X <= precision
+ && result.Y <= precision;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/CieXyy.cs b/src/ImageSharp/ColorSpaces/CieXyy.cs
new file mode 100644
index 000000000..1578f1ac3
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/CieXyy.cs
@@ -0,0 +1,179 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Represents an CIE xyY 1931 color
+ ///
+ ///
+ internal struct CieXyy : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has X, Y, and Y values set to zero.
+ ///
+ public static readonly CieXyy Empty = default(CieXyy);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// 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)
+ : this(new Vector3(x, y, yl))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the x, y, Y components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyy(Vector3 vector)
+ : this()
+ {
+ // Not clamping as documentation about this space seems to indicate "usual" ranges
+ this.backingVector = vector;
+ }
+
+ ///
+ /// Gets the X chrominance component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float X
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
+
+ ///
+ /// Gets the Y chrominance component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float Y
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
+
+ ///
+ /// Gets the Y luminance component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float Yl
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ public Vector3 Vector
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
+ }
+
+ ///
+ /// 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)
+ {
+ return 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)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.backingVector.GetHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "CieXyy [ Empty ]";
+ }
+
+ return $"CieXyy [ X={this.X:#0.##}, Y={this.Y:#0.##}, Yl={this.Yl:#0.##} ]";
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj)
+ {
+ if (obj is CieXyy)
+ {
+ return this.Equals((CieXyy)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(CieXyy other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool AlmostEquals(CieXyy other, float precision)
+ {
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/CieXyz.cs b/src/ImageSharp/ColorSpaces/CieXyz.cs
similarity index 65%
rename from src/ImageSharp/Colors/Spaces/CieXyz.cs
rename to src/ImageSharp/ColorSpaces/CieXyz.cs
index 9c6c9bf60..8d3255e65 100644
--- a/src/ImageSharp/Colors/Spaces/CieXyz.cs
+++ b/src/ImageSharp/ColorSpaces/CieXyz.cs
@@ -3,21 +3,21 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Colors.Spaces
+namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
- using ImageSharp.PixelFormats;
+ using System.Runtime.CompilerServices;
///
- /// Represents an CIE 1931 color
- ///
+ /// Represents an CIE XYZ 1931 color
+ ///
///
- public struct CieXyz : IEquatable, IAlmostEquatable
+ internal struct CieXyz : IColorVector, IEquatable, IAlmostEquatable
{
///
- /// Represents a that has Y, Cb, and Cr values set to zero.
+ /// Represents a that has X, Y, and Z values set to zero.
///
public static readonly CieXyz Empty = default(CieXyz);
@@ -32,30 +32,53 @@ namespace ImageSharp.Colors.Spaces
/// 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)
+ : this(new Vector3(x, y, z))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the x, y, z components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyz(Vector3 vector)
: this()
{
// Not clamping as documentation about this space seems to indicate "usual" ranges
- this.backingVector = new Vector3(x, y, z);
+ this.backingVector = vector;
}
///
- /// Gets the Y luminance component.
- /// A value ranging between 380 and 780.
+ /// 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 => this.backingVector.X;
+ public float X
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
///
- /// Gets the Cb chroma component.
- /// A value ranging between 380 and 780.
+ /// Gets the Y luminance component.
+ /// A value usually ranging between 0 and 1.
///
- public float Y => this.backingVector.Y;
+ public float Y
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
///
- /// Gets the Cr chroma component.
- /// A value ranging between 380 and 780.
+ /// 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 => this.backingVector.Z;
+ public float Z
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
///
/// Gets a value indicating whether this is empty.
@@ -63,29 +86,11 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- ///
- /// The instance of to convert.
- ///
- ///
- /// An instance of .
- ///
- public static implicit operator CieXyz(Rgba32 color)
+ ///
+ public Vector3 Vector
{
- Vector4 vector = color.ToVector4().Expand();
-
- float x = (vector.X * 0.4124F) + (vector.Y * 0.3576F) + (vector.Z * 0.1805F);
- float y = (vector.X * 0.2126F) + (vector.Y * 0.7152F) + (vector.Z * 0.0722F);
- float z = (vector.X * 0.0193F) + (vector.Y * 0.1192F) + (vector.Z * 0.9505F);
-
- x *= 100F;
- y *= 100F;
- z *= 100F;
-
- return new CieXyz(x, y, z);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
}
///
@@ -100,6 +105,7 @@ namespace ImageSharp.Colors.Spaces
///
/// True if the current left is equal to the parameter; otherwise, false.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(CieXyz left, CieXyz right)
{
return left.Equals(right);
@@ -117,6 +123,7 @@ namespace ImageSharp.Colors.Spaces
///
/// True if the current left is unequal to the parameter; otherwise, false.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(CieXyz left, CieXyz right)
{
return !left.Equals(right);
@@ -140,6 +147,7 @@ namespace ImageSharp.Colors.Spaces
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is CieXyz)
@@ -151,19 +159,21 @@ namespace ImageSharp.Colors.Spaces
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(CieXyz other)
{
return this.backingVector.Equals(other.backingVector);
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(CieXyz other, float precision)
{
- Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Cmyk.cs b/src/ImageSharp/ColorSpaces/Cmyk.cs
similarity index 72%
rename from src/ImageSharp/Colors/Spaces/Cmyk.cs
rename to src/ImageSharp/ColorSpaces/Cmyk.cs
index 4ca9f018c..eeaef21bd 100644
--- a/src/ImageSharp/Colors/Spaces/Cmyk.cs
+++ b/src/ImageSharp/ColorSpaces/Cmyk.cs
@@ -3,33 +3,23 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Colors.Spaces
+namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
- using ImageSharp.PixelFormats;
+ using System.Runtime.CompilerServices;
///
/// Represents an CMYK (cyan, magenta, yellow, keyline) color.
///
- public struct Cmyk : IEquatable, IAlmostEquatable
+ internal struct Cmyk : IEquatable, IAlmostEquatable
{
///
/// Represents a that has C, M, Y, and K values set to zero.
///
public static readonly Cmyk Empty = default(Cmyk);
- ///
- /// Min range used for clamping
- ///
- private static readonly Vector4 VectorMin = Vector4.Zero;
-
- ///
- /// Max range used for clamping
- ///
- private static readonly Vector4 VectorMax = Vector4.One;
-
///
/// The backing vector for SIMD support.
///
@@ -42,35 +32,62 @@ namespace ImageSharp.Colors.Spaces
/// 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)
: this()
{
- this.backingVector = Vector4.Clamp(new Vector4(c, m, y, k), VectorMin, VectorMax);
+ this.backingVector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One);
}
///
/// Gets the cyan color component.
/// A value ranging between 0 and 1.
///
- public float C => this.backingVector.X;
+ public float C
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
///
/// Gets the magenta color component.
/// A value ranging between 0 and 1.
///
- public float M => this.backingVector.Y;
+ public float M
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
///
/// Gets the yellow color component.
/// A value ranging between 0 and 1.
///
- public float Y => this.backingVector.Z;
+ public float Y
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
///
/// Gets the keyline black color component.
/// A value ranging between 0 and 1.
///
- public float K => this.backingVector.W;
+ public float K
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.W;
+ }
///
/// Gets a value indicating whether this is empty.
@@ -78,36 +95,6 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- ///
- /// The instance of to convert.
- ///
- ///
- /// An instance of .
- ///
- public static implicit operator Cmyk(Rgba32 color)
- {
- float c = 1f - (color.R / 255F);
- float m = 1f - (color.G / 255F);
- float y = 1f - (color.B / 255F);
-
- float k = MathF.Min(c, MathF.Min(m, y));
-
- if (MathF.Abs(k - 1.0f) <= Constants.Epsilon)
- {
- return new Cmyk(0, 0, 0, 1);
- }
-
- c = (c - k) / (1 - k);
- m = (m - k) / (1 - k);
- y = (y - k) / (1 - k);
-
- return new Cmyk(c, m, y, k);
- }
-
///
/// Compares two objects for equality.
///
@@ -120,6 +107,7 @@ namespace ImageSharp.Colors.Spaces
///
/// True if the current left is equal to the parameter; otherwise, false.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Cmyk left, Cmyk right)
{
return left.Equals(right);
@@ -137,6 +125,7 @@ namespace ImageSharp.Colors.Spaces
///
/// True if the current left is unequal to the parameter; otherwise, false.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Cmyk left, Cmyk right)
{
return !left.Equals(right);
@@ -160,6 +149,7 @@ namespace ImageSharp.Colors.Spaces
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is Cmyk)
@@ -171,15 +161,17 @@ namespace ImageSharp.Colors.Spaces
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Cmyk other)
{
return this.backingVector.Equals(other.backingVector);
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(Cmyk other, float precision)
{
- Vector4 result = Vector4.Abs(this.backingVector - other.backingVector);
+ var result = Vector4.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
@@ -187,4 +179,4 @@ namespace ImageSharp.Colors.Spaces
&& result.W <= precision;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs b/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs
new file mode 100644
index 000000000..29200823e
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs
@@ -0,0 +1,24 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ ///
+ /// Constants use for Cie conversion calculations
+ ///
+ ///
+ internal static class CieConstants
+ {
+ ///
+ /// 216F / 24389F
+ ///
+ public const float Epsilon = 0.008856452F;
+
+ ///
+ /// 24389F / 27F
+ ///
+ public const float Kappa = 903.2963F;
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs
new file mode 100644
index 000000000..acdb356a2
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs
@@ -0,0 +1,198 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using System;
+
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb;
+
+ ///
+ /// Performs chromatic adaptation on the various color spaces.
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ ///
+ /// Performs chromatic adaptation of given color.
+ /// Target white point is .
+ ///
+ /// The color to adapt
+ /// The white point to adapt for
+ /// The adapted color
+ public CieXyz Adapt(CieXyz color, CieXyz sourceWhitePoint)
+ {
+ Guard.NotNull(color, nameof(color));
+ Guard.NotNull(sourceWhitePoint, nameof(sourceWhitePoint));
+
+ if (!this.IsChromaticAdaptationPerformed)
+ {
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
+ }
+
+ return this.ChromaticAdaptation.Transform(color, sourceWhitePoint, this.WhitePoint);
+ }
+
+ ///
+ /// Adapts color from the source white point to white point set in .
+ ///
+ /// The color to adapt
+ /// The adapted color
+ public CieLab Adapt(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ if (!this.IsChromaticAdaptationPerformed)
+ {
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
+ }
+
+ if (color.WhitePoint.Equals(this.TargetLabWhitePoint))
+ {
+ return color;
+ }
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
+ ///
+ /// Adapts color from the source white point to white point set in .
+ ///
+ /// The color to adapt
+ /// The adapted color
+ public CieLch Adapt(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ if (!this.IsChromaticAdaptationPerformed)
+ {
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
+ }
+
+ if (color.WhitePoint.Equals(this.TargetLabWhitePoint))
+ {
+ return color;
+ }
+
+ CieLab labColor = this.ToCieLab(color);
+ return this.ToCieLch(labColor);
+ }
+
+ ///
+ /// Adapts color from the source white point to white point set in .
+ ///
+ /// The color to adapt
+ /// The adapted color
+ public CieLchuv Adapt(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ if (!this.IsChromaticAdaptationPerformed)
+ {
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
+ }
+
+ if (color.WhitePoint.Equals(this.TargetLabWhitePoint))
+ {
+ return color;
+ }
+
+ CieLuv luvColor = this.ToCieLuv(color);
+ return this.ToCieLchuv(luvColor);
+ }
+
+ ///
+ /// Adapts color from the source white point to white point set in .
+ ///
+ /// The color to adapt
+ /// The adapted color
+ public CieLuv Adapt(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ if (!this.IsChromaticAdaptationPerformed)
+ {
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
+ }
+
+ if (color.WhitePoint.Equals(this.TargetLuvWhitePoint))
+ {
+ return color;
+ }
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLuv(xyzColor);
+ }
+
+ ///
+ /// Adapts color from the source white point to white point set in .
+ ///
+ /// The color to adapt
+ /// The adapted color
+ public HunterLab Adapt(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ if (!this.IsChromaticAdaptationPerformed)
+ {
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
+ }
+
+ if (color.WhitePoint.Equals(this.TargetHunterLabWhitePoint))
+ {
+ return color;
+ }
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
+ ///
+ /// Adapts a color from the source working space to working space set in .
+ ///
+ /// The color to adapt
+ /// The adapted color
+ public LinearRgb Adapt(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ if (!this.IsChromaticAdaptationPerformed)
+ {
+ throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point.");
+ }
+
+ if (color.WorkingSpace.Equals(this.TargetRgbWorkingSpace))
+ {
+ return color;
+ }
+
+ // Conversion to XYZ
+ LinearRgbToCieXyzConverter converterToXYZ = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace);
+ CieXyz unadapted = converterToXYZ.Convert(color);
+
+ // Adaptation
+ CieXyz adapted = this.ChromaticAdaptation.Transform(unadapted, color.WorkingSpace.WhitePoint, this.TargetRgbWorkingSpace.WhitePoint);
+
+ // Conversion back to RGB
+ CieXyzToLinearRgbConverter converterToRGB = this.GetCieXyxToLinearRgbConverter(this.TargetRgbWorkingSpace);
+ return converterToRGB.Convert(adapted);
+ }
+
+ ///
+ /// Adapts an color from the source working space to working space set in .
+ ///
+ /// The color to adapt
+ /// The adapted color
+ public Rgb Adapt(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ LinearRgb linearInput = this.ToLinearRgb(color);
+ LinearRgb linearOutput = this.Adapt(linearInput);
+ return this.ToRgb(linearOutput);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs
new file mode 100644
index 000000000..2c274c17a
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs
@@ -0,0 +1,205 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.CieLab;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.CieLch;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ ///
+ /// The converter for converting between CieLch to CieLab.
+ ///
+ private static readonly CieLchToCieLabConverter CieLchToCieLabConverter = new CieLchToCieLabConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion (perserving white point)
+ CieLab unadapted = CieLchToCieLabConverter.Convert(color);
+
+ if (!this.IsChromaticAdaptationPerformed)
+ {
+ return unadapted;
+ }
+
+ // Adaptation
+ return this.Adapt(unadapted);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Adaptation
+ CieXyz adapted = !this.WhitePoint.Equals(this.TargetLabWhitePoint) && this.IsChromaticAdaptationPerformed
+ ? this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetLabWhitePoint)
+ : color;
+
+ // Conversion
+ CieXyzToCieLabConverter converter = new CieXyzToCieLabConverter(this.TargetLabWhitePoint);
+ return converter.Convert(adapted);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLab ToCieLab(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLab(xyzColor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs
new file mode 100644
index 000000000..e3b3975a4
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs
@@ -0,0 +1,192 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces.Conversion.Implementation.CieLch;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ ///
+ /// The converter for converting between CieLab to CieLch.
+ ///
+ private static readonly CieLabToCieLchConverter CieLabToCieLchConverter = new CieLabToCieLchConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Adaptation
+ CieLab adapted = this.IsChromaticAdaptationPerformed ? this.Adapt(color) : color;
+
+ // Conversion
+ return CieLabToCieLchConverter.Convert(adapted);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLch(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLch(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLch(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieLab labColor = this.ToCieLab(color);
+ return this.ToCieLch(labColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLch(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLch(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLch(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLch(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLch(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLch(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLch(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLch ToCieLch(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLch(xyzColor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs
new file mode 100644
index 000000000..7f2d18480
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs
@@ -0,0 +1,192 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuv;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ ///
+ /// The converter for converting between CieLab to CieLchuv.
+ ///
+ private static readonly CieLuvToCieLchuvConverter CieLuvToCieLchuvConverter = new CieLuvToCieLchuvConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLchuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLchuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Adaptation
+ CieLuv adapted = this.IsChromaticAdaptationPerformed ? this.Adapt(color) : color;
+
+ // Conversion
+ return CieLuvToCieLchuvConverter.Convert(adapted);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLchuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieLab labColor = this.ToCieLab(color);
+ return this.ToCieLchuv(labColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLchuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLchuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLchuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLchuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLchuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLchuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLchuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLchuv ToCieLchuv(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ CieXyz xyzColor = this.ToCieXyz(color);
+ return this.ToCieLchuv(xyzColor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs
new file mode 100644
index 000000000..dc63e7a67
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs
@@ -0,0 +1,202 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuv;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.CieLuv;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ private static readonly CieLchuvToCieLuvConverter CieLchuvToCieLuvConverter = new CieLchuvToCieLuvConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToCieLuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToCieLuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion (perserving white point)
+ CieLuv unadapted = CieLchuvToCieLuvConverter.Convert(color);
+
+ if (!this.IsChromaticAdaptationPerformed)
+ {
+ return unadapted;
+ }
+
+ // Adaptation
+ return this.Adapt(unadapted);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToCieLuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Adaptation
+ CieXyz adapted = !this.WhitePoint.Equals(this.TargetLabWhitePoint) && this.IsChromaticAdaptationPerformed
+ ? this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetLabWhitePoint)
+ : color;
+
+ // Conversion
+ var converter = new CieXyzToCieLuvConverter(this.TargetLuvWhitePoint);
+ return converter.Convert(adapted);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToCieLuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToCieLuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToCieLuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToCieLuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToCieLuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToCieLuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToCieLuv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieLuv ToCieLuv(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToCieLuv(xyzColor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs
new file mode 100644
index 000000000..6c4b6ca53
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs
@@ -0,0 +1,197 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces.Conversion.Implementation.CieXyy;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ private static readonly CieXyzAndCieXyyConverter CieXyzAndCieXyyConverter = new CieXyzAndCieXyyConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ return CieXyzAndCieXyyConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyy ToCieXyy(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCieXyy(xyzColor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs
new file mode 100644
index 000000000..ca8b31d03
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs
@@ -0,0 +1,253 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.CieLab;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.CieLuv;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ private static readonly CieLabToCieXyzConverter CieLabToCieXyzConverter = new CieLabToCieXyzConverter();
+
+ private static readonly CieLuvToCieXyzConverter CieLuvToCieXyzConverter = new CieLuvToCieXyzConverter();
+
+ private static readonly HunterLabToCieXyzConverter HunterLabToCieXyzConverter = new HunterLabToCieXyzConverter();
+
+ private LinearRgbToCieXyzConverter linearRgbToCieXyzConverter;
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ CieXyz unadapted = CieLabToCieXyzConverter.Convert(color);
+
+ // Adaptation
+ CieXyz adapted = color.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
+ ? unadapted
+ : this.Adapt(unadapted, color.WhitePoint);
+
+ return adapted;
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion to Lab
+ CieLab labColor = CieLchToCieLabConverter.Convert(color);
+
+ // Conversion to XYZ (incl. adaptation)
+ return this.ToCieXyz(labColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion to Luv
+ CieLuv luvColor = CieLchuvToCieLuvConverter.Convert(color);
+
+ // Conversion to XYZ (incl. adaptation)
+ return this.ToCieXyz(luvColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ CieXyz unadapted = CieLuvToCieXyzConverter.Convert(color);
+
+ // Adaptation
+ CieXyz adapted = color.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
+ ? unadapted
+ : this.Adapt(unadapted, color.WhitePoint);
+
+ return adapted;
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return CieXyzAndCieXyyConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ var rgb = this.ToRgb(color);
+
+ return this.ToCieXyz(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ var rgb = this.ToRgb(color);
+
+ return this.ToCieXyz(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ var rgb = this.ToRgb(color);
+
+ return this.ToCieXyz(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ CieXyz unadapted = HunterLabToCieXyzConverter.Convert(color);
+
+ // Adaptation
+ CieXyz adapted = color.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
+ ? unadapted
+ : this.Adapt(unadapted, color.WhitePoint);
+
+ return adapted;
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ LinearRgbToCieXyzConverter converter = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace);
+ CieXyz unadapted = converter.Convert(color);
+
+ // Adaptation
+ return color.WorkingSpace.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
+ ? unadapted
+ : this.Adapt(unadapted, color.WorkingSpace.WhitePoint);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return this.cachedCieXyzAndLmsConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ LinearRgb linear = RgbToLinearRgbConverter.Convert(color);
+ return this.ToCieXyz(linear);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public CieXyz ToCieXyz(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ var rgb = this.ToRgb(color);
+
+ return this.ToCieXyz(rgb);
+ }
+
+ ///
+ /// Gets the correct converter for the given rgb working space.
+ ///
+ /// The source working space
+ /// The
+ private LinearRgbToCieXyzConverter GetLinearRgbToCieXyzConverter(IRgbWorkingSpace workingSpace)
+ {
+ if (this.linearRgbToCieXyzConverter != null && this.linearRgbToCieXyzConverter.SourceWorkingSpace.Equals(workingSpace))
+ {
+ return this.linearRgbToCieXyzConverter;
+ }
+
+ return this.linearRgbToCieXyzConverter = new LinearRgbToCieXyzConverter(workingSpace);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs
new file mode 100644
index 000000000..9cfa8f0c3
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs
@@ -0,0 +1,198 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.Cmyk;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ private static readonly CmykAndRgbConverter CmykAndRgbConverter = new CmykAndRgbConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCmyk(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCmyk(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCmyk(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCmyk(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCmyk(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return CmykAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return CmykAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return CmykAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCmyk(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return CmykAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToCmyk(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ return CmykAndRgbConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Cmyk ToCmyk(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return CmykAndRgbConverter.Convert(rgb);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs
new file mode 100644
index 000000000..9e4a8d9c3
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs
@@ -0,0 +1,198 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.Hsl;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ private static readonly HslAndRgbConverter HslAndRgbConverter = new HslAndRgbConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsl(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsl(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsl(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsl(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsl(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return HslAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return HslAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return HslAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsl(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return HslAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsl(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ return HslAndRgbConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsl ToHsl(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return HslAndRgbConverter.Convert(rgb);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs
new file mode 100644
index 000000000..80b235894
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs
@@ -0,0 +1,198 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.Hsv;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ private static readonly HsvAndRgbConverter HsvAndRgbConverter = new HsvAndRgbConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return HsvAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return HsvAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return HsvAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return HsvAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToHsv(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ return HsvAndRgbConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Hsv ToHsv(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return HsvAndRgbConverter.Convert(rgb);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs
new file mode 100644
index 000000000..b6d9d4cb9
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs
@@ -0,0 +1,189 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Adaptation
+ CieXyz adapted = !this.WhitePoint.Equals(this.TargetHunterLabWhitePoint) && this.IsChromaticAdaptationPerformed
+ ? this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetHunterLabWhitePoint)
+ : color;
+
+ // Conversion
+ return new CieXyzToHunterLabConverter(this.TargetHunterLabWhitePoint).Convert(adapted);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public HunterLab ToHunterLab(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToHunterLab(xyzColor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs
new file mode 100644
index 000000000..5fcc2cd5e
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs
@@ -0,0 +1,209 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ private static readonly RgbToLinearRgbConverter RgbToLinearRgbConverter = new RgbToLinearRgbConverter();
+
+ private CieXyzToLinearRgbConverter cieXyzToLinearRgbConverter;
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLinearRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLinearRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLinearRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLinearRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLinearRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Adaptation
+ CieXyz adapted = this.TargetRgbWorkingSpace.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed
+ ? color
+ : this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetRgbWorkingSpace.WhitePoint);
+
+ // Conversion
+ CieXyzToLinearRgbConverter xyzConverter = this.GetCieXyxToLinearRgbConverter(this.TargetRgbWorkingSpace);
+ return xyzConverter.Convert(adapted);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+ return this.ToLinearRgb(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+ return this.ToLinearRgb(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+ return this.ToLinearRgb(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLinearRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLinearRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return RgbToLinearRgbConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public LinearRgb ToLinearRgb(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+ return this.ToLinearRgb(rgb);
+ }
+
+ ///
+ /// Gets the correct converter for the given rgb working space.
+ ///
+ /// The target working space
+ /// The
+ private CieXyzToLinearRgbConverter GetCieXyxToLinearRgbConverter(IRgbWorkingSpace workingSpace)
+ {
+ if (this.cieXyzToLinearRgbConverter != null && this.cieXyzToLinearRgbConverter.TargetWorkingSpace.Equals(workingSpace))
+ {
+ return this.cieXyzToLinearRgbConverter;
+ }
+
+ return this.cieXyzToLinearRgbConverter = new CieXyzToLinearRgbConverter(workingSpace);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs
new file mode 100644
index 000000000..8d888182f
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs
@@ -0,0 +1,184 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return this.cachedCieXyzAndLmsConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Lms ToLms(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToLms(xyzColor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs
new file mode 100644
index 000000000..1cfd565e8
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs
@@ -0,0 +1,193 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ private static readonly LinearRgbToRgbConverter LinearRgbToRgbConverter = new LinearRgbToRgbConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ var linear = this.ToLinearRgb(color);
+
+ // Compand
+ return this.ToRgb(linear);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return CmykAndRgbConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return HsvAndRgbConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return HslAndRgbConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ return LinearRgbToRgbConverter.Convert(color);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+ return this.ToRgb(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public Rgb ToRgb(YCbCr color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ // Conversion
+ Rgb rgb = YCbCrAndRgbConverter.Convert(color);
+
+ // Adaptation
+ // TODO: Check this!
+ return rgb.WorkingSpace.Equals(this.TargetRgbWorkingSpace) ? rgb : this.Adapt(rgb);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs
new file mode 100644
index 000000000..6660f579a
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs
@@ -0,0 +1,198 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.YCbCr;
+
+ ///
+ /// Allows conversion to .
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ private static readonly YCbCrAndRgbConverter YCbCrAndRgbConverter = new YCbCrAndRgbConverter();
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(CieLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToYCbCr(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(CieLch color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToYCbCr(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(CieLchuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToYCbCr(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(CieLuv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToYCbCr(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(CieXyy color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToYCbCr(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(CieXyz color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return YCbCrAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(Cmyk color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return YCbCrAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(Hsl color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return YCbCrAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(Hsv color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return YCbCrAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(HunterLab color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToYCbCr(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(LinearRgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var rgb = this.ToRgb(color);
+
+ return YCbCrAndRgbConverter.Convert(rgb);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(Lms color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ var xyzColor = this.ToCieXyz(color);
+
+ return this.ToYCbCr(xyzColor);
+ }
+
+ ///
+ /// Converts a into a
+ ///
+ /// The color to convert.
+ /// The
+ public YCbCr ToYCbCr(Rgb color)
+ {
+ Guard.NotNull(color, nameof(color));
+
+ return YCbCrAndRgbConverter.Convert(color);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs
new file mode 100644
index 000000000..a52207cb4
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs
@@ -0,0 +1,104 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using System.Numerics;
+
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.Lms;
+
+ ///
+ /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation.
+ ///
+ internal partial class ColorSpaceConverter
+ {
+ ///
+ /// The default whitepoint used for converting to CieLab
+ ///
+ public static readonly CieXyz DefaultWhitePoint = Illuminants.D65;
+
+ private Matrix4x4 transformationMatrix;
+
+ private CieXyzAndLmsConverter cachedCieXyzAndLmsConverter;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ColorSpaceConverter()
+ {
+ // Note the order here this is important.
+ this.WhitePoint = DefaultWhitePoint;
+ this.LmsAdaptationMatrix = CieXyzAndLmsConverter.DefaultTransformationMatrix;
+ this.ChromaticAdaptation = new VonKriesChromaticAdaptation(this.cachedCieXyzAndLmsConverter);
+ this.TargetLuvWhitePoint = CieLuv.DefaultWhitePoint;
+ this.TargetLabWhitePoint = CieLab.DefaultWhitePoint;
+ this.TargetHunterLabWhitePoint = HunterLab.DefaultWhitePoint;
+ this.TargetRgbWorkingSpace = Rgb.DefaultWorkingSpace;
+ }
+
+ ///
+ /// Gets or sets the white point used for chromatic adaptation in conversions from/to XYZ color space.
+ /// When null, no adaptation will be performed.
+ ///
+ public CieXyz WhitePoint { get; set; }
+
+ ///
+ /// Gets or sets the white point used *when creating* Luv/LChuv colors. (Luv/LChuv colors on the input already contain the white point information)
+ /// Defaults to: .
+ ///
+ public CieXyz TargetLuvWhitePoint { get; set; }
+
+ ///
+ /// Gets or sets the white point used *when creating* Lab/LChab colors. (Lab/LChab colors on the input already contain the white point information)
+ /// Defaults to: .
+ ///
+ public CieXyz TargetLabWhitePoint { get; set; }
+
+ ///
+ /// Gets or sets the white point used *when creating* HunterLab colors. (HunterLab colors on the input already contain the white point information)
+ /// Defaults to: .
+ ///
+ public CieXyz TargetHunterLabWhitePoint { get; set; }
+
+ ///
+ /// Gets or sets the target working space used *when creating* RGB colors. (RGB colors on the input already contain the working space information)
+ /// Defaults to: .
+ ///
+ public IRgbWorkingSpace TargetRgbWorkingSpace { get; set; }
+
+ ///
+ /// Gets or sets the chromatic adaptation method used. When null, no adaptation will be performed.
+ ///
+ public IChromaticAdaptation ChromaticAdaptation { get; set; }
+
+ ///
+ /// Gets or sets transformation matrix used in conversion to ,
+ /// also used in the default Von Kries Chromatic Adaptation method.
+ ///
+ public Matrix4x4 LmsAdaptationMatrix
+ {
+ get => this.transformationMatrix;
+
+ set
+ {
+ this.transformationMatrix = value;
+ if (this.cachedCieXyzAndLmsConverter == null)
+ {
+ this.cachedCieXyzAndLmsConverter = new CieXyzAndLmsConverter(value);
+ }
+ else
+ {
+ this.cachedCieXyzAndLmsConverter.TransformationMatrix = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether chromatic adaptation has been performed.
+ ///
+ private bool IsChromaticAdaptationPerformed => this.ChromaticAdaptation != null;
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs
new file mode 100644
index 000000000..aead65e59
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs
@@ -0,0 +1,27 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Chromatic adaptation.
+ /// A linear transformation of a source color (XS, YS, ZS) into a destination color (XD, YD, ZD) by a linear transformation [M]
+ /// which is dependent on the source reference white (XWS, YWS, ZWS) and the destination reference white (XWD, YWD, ZWD).
+ ///
+ internal interface IChromaticAdaptation
+ {
+ ///
+ /// Performs a linear transformation of a source color in to the destination color.
+ ///
+ /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates).
+ /// The source color.
+ /// The source white point.
+ /// The target white point.
+ /// The
+ CieXyz Transform(CieXyz sourceColor, CieXyz sourceWhitePoint, CieXyz targetWhitePoint);
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/IColorConversion.cs b/src/ImageSharp/ColorSpaces/Conversion/IColorConversion.cs
new file mode 100644
index 000000000..920fba18f
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/IColorConversion.cs
@@ -0,0 +1,22 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ ///
+ /// Converts color between two color spaces.
+ ///
+ /// The input color type.
+ /// The result color type.
+ internal interface IColorConversion
+ {
+ ///
+ /// Performs the conversion from the input to an instance of the output type.
+ ///
+ /// The input color instance.
+ /// The converted result
+ TResult Convert(T input);
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs
new file mode 100644
index 000000000..71b1596ca
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs
@@ -0,0 +1,50 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLab
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Converts from to .
+ ///
+ internal class CieLabToCieXyzConverter : IColorConversion
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyz Convert(CieLab input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
+ float l = input.L, a = input.A, b = input.B;
+ float fy = (l + 16) / 116F;
+ float fx = (a / 500F) + fy;
+ float fz = fy - (b / 200F);
+
+ float fx3 = MathF.Pow(fx, 3F);
+ float fz3 = MathF.Pow(fz, 3F);
+
+ float xr = fx3 > CieConstants.Epsilon ? fx3 : ((116F * fx) - 16F) / CieConstants.Kappa;
+ float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? MathF.Pow((l + 16F) / 116F, 3F) : l / CieConstants.Kappa;
+ float zr = fz3 > CieConstants.Epsilon ? fz3 : ((116F * fz) - 16F) / CieConstants.Kappa;
+
+ float wx = input.WhitePoint.X, wy = input.WhitePoint.Y, wz = input.WhitePoint.Z;
+
+ // Avoids XYZ coordinates out range (restricted by 0 and XYZ reference white)
+ xr = xr.Clamp(0, 1F);
+ yr = yr.Clamp(0, 1F);
+ zr = zr.Clamp(0, 1F);
+
+ float x = xr * wx;
+ float y = yr * wy;
+ float z = zr * wz;
+
+ return new CieXyz(x, y, z);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs
new file mode 100644
index 000000000..8d877503a
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs
@@ -0,0 +1,67 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLab
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Converts from to .
+ ///
+ internal class CieXyzToCieLabConverter : IColorConversion
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyzToCieLabConverter()
+ : this(CieLab.DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The target reference lab white point
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyzToCieLabConverter(CieXyz labWhitePoint)
+ {
+ this.LabWhitePoint = labWhitePoint;
+ }
+
+ ///
+ /// Gets the target reference whitepoint. When not set, is used.
+ ///
+ public CieXyz LabWhitePoint
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLab Convert(CieXyz input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
+ float wx = this.LabWhitePoint.X, wy = this.LabWhitePoint.Y, wz = this.LabWhitePoint.Z;
+
+ float xr = input.X / wx, yr = input.Y / wy, zr = input.Z / wz;
+
+ float fx = xr > CieConstants.Epsilon ? MathF.Pow(xr, 0.3333333F) : ((CieConstants.Kappa * xr) + 16F) / 116F;
+ float fy = yr > CieConstants.Epsilon ? MathF.Pow(yr, 0.3333333F) : ((CieConstants.Kappa * yr) + 16F) / 116F;
+ float fz = zr > CieConstants.Epsilon ? MathF.Pow(zr, 0.3333333F) : ((CieConstants.Kappa * zr) + 16F) / 116F;
+
+ float l = (116F * fy) - 16F;
+ float a = 500F * (fx - fy);
+ float b = 200F * (fy - fz);
+
+ return new CieLab(l, a, b, this.LabWhitePoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs
new file mode 100644
index 000000000..5b63b167e
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLch
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Converts from to .
+ ///
+ internal class CieLchToCieLabConverter : IColorConversion
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLab Convert(CieLch input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here:
+ // https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC
+ float l = input.L, c = input.C, hDegrees = input.H;
+ float hRadians = MathF.DegreeToRadian(hDegrees);
+
+ float a = c * MathF.Cos(hRadians);
+ float b = c * MathF.Sin(hRadians);
+
+ return new CieLab(l, a, b, input.WhitePoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs
new file mode 100644
index 000000000..b93dce75a
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs
@@ -0,0 +1,42 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLch
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Converts from to .
+ ///
+ internal class CieLabToCieLchConverter : IColorConversion
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLch Convert(CieLab input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here:
+ // https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC
+ float l = input.L, a = input.A, b = input.B;
+ float c = MathF.Sqrt((a * a) + (b * b));
+ float hRadians = MathF.Atan2(b, a);
+ float hDegrees = MathF.RadianToDegree(hRadians);
+
+ // Wrap the angle round at 360.
+ hDegrees = hDegrees % 360;
+
+ // Make sure it's not negative.
+ while (hDegrees < 0)
+ {
+ hDegrees += 360;
+ }
+
+ return new CieLch(l, c, hDegrees, input.WhitePoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLchuvToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLchuvToCieLuvConverter.cs
new file mode 100644
index 000000000..3e399016a
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLchuvToCieLuvConverter.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuv
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Converts from to .
+ ///
+ internal class CieLchuvToCieLuvConverter : IColorConversion
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLuv Convert(CieLchuv input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here:
+ // https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29
+ float l = input.L, c = input.C, hDegrees = input.H;
+ float hRadians = MathF.DegreeToRadian(hDegrees);
+
+ float u = c * MathF.Cos(hRadians);
+ float v = c * MathF.Sin(hRadians);
+
+ return new CieLuv(l, u, v, input.WhitePoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLuvToCieLchuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLuvToCieLchuvConverter.cs
new file mode 100644
index 000000000..5339f1bcd
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLuvToCieLchuvConverter.cs
@@ -0,0 +1,42 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuv
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Converts from to .
+ ///
+ internal class CieLuvToCieLchuvConverter : IColorConversion
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLchuv Convert(CieLuv input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here:
+ // https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29
+ float l = input.L, a = input.U, b = input.V;
+ float c = MathF.Sqrt((a * a) + (b * b));
+ float hRadians = MathF.Atan2(b, a);
+ float hDegrees = MathF.RadianToDegree(hRadians);
+
+ // Wrap the angle round at 360.
+ hDegrees = hDegrees % 360;
+
+ // Make sure it's not negative.
+ while (hDegrees < 0)
+ {
+ hDegrees += 360;
+ }
+
+ return new CieLchuv(l, c, hDegrees, input.WhitePoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieLuvToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieLuvToCieXyzConverter.cs
new file mode 100644
index 000000000..36c458828
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieLuvToCieXyzConverter.cs
@@ -0,0 +1,80 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLuv
+{
+ using System.Runtime.CompilerServices;
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Converts from to .
+ ///
+ internal class CieLuvToCieXyzConverter : IColorConversion
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyz Convert(CieLuv input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Luv_to_XYZ.html
+ float l = input.L, u = input.U, v = input.V;
+
+ float u0 = ComputeU0(input.WhitePoint);
+ float v0 = ComputeV0(input.WhitePoint);
+
+ float y = l > CieConstants.Kappa * CieConstants.Epsilon
+ ? MathF.Pow((l + 16) / 116, 3)
+ : l / CieConstants.Kappa;
+
+ float a = ((52 * l / (u + (13 * l * u0))) - 1) / 3;
+ float b = -5 * y;
+ float c = -0.3333333F;
+ float d = y * ((39 * l / (v + (13 * l * v0))) - 5);
+
+ float x = (d - b) / (a - c);
+ float z = (x * a) + b;
+
+ if (float.IsNaN(x) || x < 0)
+ {
+ x = 0;
+ }
+
+ if (float.IsNaN(y) || y < 0)
+ {
+ y = 0;
+ }
+
+ if (float.IsNaN(z) || z < 0)
+ {
+ z = 0;
+ }
+
+ return new CieXyz(x, y, z);
+ }
+
+ ///
+ /// Calculates the blue-yellow chromacity based on the given whitepoint.
+ ///
+ /// The whitepoint
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float ComputeU0(CieXyz input)
+ {
+ return (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z));
+ }
+
+ ///
+ /// Calculates the red-green chromacity based on the given whitepoint.
+ ///
+ /// The whitepoint
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float ComputeV0(CieXyz input)
+ {
+ return (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieXyzToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieXyzToCieLuvConverter.cs
new file mode 100644
index 000000000..3883f3a1e
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieXyzToCieLuvConverter.cs
@@ -0,0 +1,102 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieLuv
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Converts from to .
+ ///
+ internal class CieXyzToCieLuvConverter : IColorConversion
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyzToCieLuvConverter()
+ : this(CieLuv.DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The target reference luv white point
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyzToCieLuvConverter(CieXyz luvWhitePoint)
+ {
+ this.LuvWhitePoint = luvWhitePoint;
+ }
+
+ ///
+ /// Gets the target reference whitepoint. When not set, is used.
+ ///
+ public CieXyz LuvWhitePoint
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieLuv Convert(CieXyz input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Luv.html
+ float yr = input.Y / this.LuvWhitePoint.Y;
+ float up = ComputeUp(input);
+ float vp = ComputeVp(input);
+ float upr = ComputeUp(this.LuvWhitePoint);
+ float vpr = ComputeVp(this.LuvWhitePoint);
+
+ float l = yr > CieConstants.Epsilon ? ((116 * MathF.Pow(yr, 0.3333333F)) - 16F) : (CieConstants.Kappa * yr);
+
+ if (float.IsNaN(l) || l < 0)
+ {
+ l = 0;
+ }
+
+ float u = 13 * l * (up - upr);
+ float v = 13 * l * (vp - vpr);
+
+ if (float.IsNaN(u))
+ {
+ u = 0;
+ }
+
+ if (float.IsNaN(v))
+ {
+ v = 0;
+ }
+
+ return new CieLuv(l, u, v, this.LuvWhitePoint);
+ }
+
+ ///
+ /// Calculates the blue-yellow chromacity based on the given whitepoint.
+ ///
+ /// The whitepoint
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float ComputeUp(CieXyz input)
+ {
+ return (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z));
+ }
+
+ ///
+ /// Calculates the red-green chromacity based on the given whitepoint.
+ ///
+ /// The whitepoint
+ /// The
+ private static float ComputeVp(CieXyz input)
+ {
+ return (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs
new file mode 100644
index 000000000..dc4a5ccf4
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.CieXyy
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Color converter between CIE XYZ and CIE xyY
+ /// for formulas.
+ ///
+ internal class CieXyzAndCieXyyConverter : IColorConversion, IColorConversion
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyy Convert(CieXyz input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ float x = input.X / (input.X + input.Y + input.Z);
+ float y = input.Y / (input.X + input.Y + input.Z);
+
+ if (float.IsNaN(x) || float.IsNaN(y))
+ {
+ return new CieXyy(0, 0, input.Y);
+ }
+
+ return new CieXyy(x, y, input.Y);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyz Convert(CieXyy input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ if (MathF.Abs(input.Y) < Constants.Epsilon)
+ {
+ return new CieXyz(0, 0, input.Yl);
+ }
+
+ float x = (input.X * input.Yl) / input.Y;
+ float y = input.Yl;
+ float z = ((1 - input.X - input.Y) * y) / input.Y;
+
+ return new CieXyz(x, y, z);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Cmyk/CmykAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Cmyk/CmykAndRgbConverter.cs
new file mode 100644
index 000000000..b50e89b18
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Cmyk/CmykAndRgbConverter.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Cmyk
+{
+ using System;
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Color converter between CMYK and Rgb
+ ///
+ internal class CmykAndRgbConverter : IColorConversion, IColorConversion
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Rgb Convert(Cmyk input)
+ {
+ float r = (1F - input.C) * (1F - input.K);
+ float g = (1F - input.M) * (1F - input.K);
+ float b = (1F - input.Y) * (1F - input.K);
+
+ return new Rgb(r, g, b);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Cmyk Convert(Rgb input)
+ {
+ // To CMYK
+ float c = 1F - input.R;
+ float m = 1F - input.G;
+ float y = 1F - input.B;
+
+ // To CMYK
+ float k = MathF.Min(c, MathF.Min(m, y));
+
+ if (MathF.Abs(k - 1F) < Constants.Epsilon)
+ {
+ return new Cmyk(0, 0, 0, 1F);
+ }
+
+ c = (c - k) / (1F - k);
+ m = (m - k) / (1F - k);
+ y = (y - k) / (1F - k);
+
+ return new Cmyk(c, m, y, k);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs
new file mode 100644
index 000000000..6c72cf294
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs
@@ -0,0 +1,159 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Hsl
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Color converter between HSL and Rgb
+ /// See for formulas.
+ ///
+ internal class HslAndRgbConverter : IColorConversion, IColorConversion
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Rgb Convert(Hsl input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ float rangedH = input.H / 360F;
+ float r = 0;
+ float g = 0;
+ float b = 0;
+ float s = input.S;
+ float l = input.L;
+
+ if (MathF.Abs(l) > Constants.Epsilon)
+ {
+ if (MathF.Abs(s) < Constants.Epsilon)
+ {
+ r = g = b = l;
+ }
+ else
+ {
+ float temp2 = (l < .5F) ? l * (1F + s) : l + s - (l * s);
+ float temp1 = (2F * l) - temp2;
+
+ r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F);
+ g = GetColorComponent(temp1, temp2, rangedH);
+ b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F);
+ }
+ }
+
+ return new Rgb(r, g, b);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Hsl Convert(Rgb input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ float r = input.R;
+ float g = input.G;
+ float b = input.B;
+
+ float max = MathF.Max(r, MathF.Max(g, b));
+ float min = MathF.Min(r, MathF.Min(g, b));
+ float chroma = max - min;
+ float h = 0F;
+ float s = 0F;
+ float l = (max + min) / 2F;
+
+ if (MathF.Abs(chroma) < Constants.Epsilon)
+ {
+ return new Hsl(0F, s, l);
+ }
+
+ if (MathF.Abs(r - max) < Constants.Epsilon)
+ {
+ h = (g - b) / chroma;
+ }
+ else if (MathF.Abs(g - max) < Constants.Epsilon)
+ {
+ h = 2F + ((b - r) / chroma);
+ }
+ else if (MathF.Abs(b - max) < Constants.Epsilon)
+ {
+ h = 4F + ((r - g) / chroma);
+ }
+
+ h *= 60F;
+ if (h < 0F)
+ {
+ h += 360F;
+ }
+
+ if (l <= .5F)
+ {
+ s = chroma / (max + min);
+ }
+ else
+ {
+ s = chroma / (2F - chroma);
+ }
+
+ return new Hsl(h, s, l);
+ }
+
+ ///
+ /// Gets the color component from the given values.
+ ///
+ /// The first value.
+ /// The second value.
+ /// The third value.
+ ///
+ /// The .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float GetColorComponent(float first, float second, float third)
+ {
+ third = MoveIntoRange(third);
+ if (third < 0.1666667F)
+ {
+ return first + ((second - first) * 6F * third);
+ }
+
+ if (third < .5F)
+ {
+ return second;
+ }
+
+ if (third < 0.6666667F)
+ {
+ return first + ((second - first) * (0.6666667F - third) * 6F);
+ }
+
+ return first;
+ }
+
+ ///
+ /// Moves the specific value within the acceptable range for
+ /// conversion.
+ /// Used for converting colors to this type.
+ ///
+ /// The value to shift.
+ ///
+ /// The .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float MoveIntoRange(float value)
+ {
+ if (value < 0F)
+ {
+ value += 1F;
+ }
+ else if (value > 1F)
+ {
+ value -= 1F;
+ }
+
+ return value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsv/HsvAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsv/HsvAndRgbConverter.cs
new file mode 100644
index 000000000..54c8e405f
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsv/HsvAndRgbConverter.cs
@@ -0,0 +1,130 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Hsv
+{
+ using System;
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Color converter between HSV and Rgb
+ /// See for formulas.
+ ///
+ internal class HsvAndRgbConverter : IColorConversion, IColorConversion
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Rgb Convert(Hsv input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ float s = input.S;
+ float v = input.V;
+
+ if (MathF.Abs(s) < Constants.Epsilon)
+ {
+ return new Rgb(v, v, v);
+ }
+
+ float h = (MathF.Abs(input.H - 360) < Constants.Epsilon) ? 0 : input.H / 60;
+ int i = (int)Math.Truncate(h);
+ float f = h - i;
+
+ float p = v * (1F - s);
+ float q = v * (1F - (s * f));
+ float t = v * (1F - (s * (1F - f)));
+
+ float r, g, b;
+ switch (i)
+ {
+ case 0:
+ r = v;
+ g = t;
+ b = p;
+ break;
+
+ case 1:
+ r = q;
+ g = v;
+ b = p;
+ break;
+
+ case 2:
+ r = p;
+ g = v;
+ b = t;
+ break;
+
+ case 3:
+ r = p;
+ g = q;
+ b = v;
+ break;
+
+ case 4:
+ r = t;
+ g = p;
+ b = v;
+ break;
+
+ default:
+ r = v;
+ g = p;
+ b = q;
+ break;
+ }
+
+ return new Rgb(r, g, b);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Hsv Convert(Rgb input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ float r = input.R;
+ float g = input.G;
+ float b = input.B;
+
+ float max = MathF.Max(r, MathF.Max(g, b));
+ float min = MathF.Min(r, MathF.Min(g, b));
+ float chroma = max - min;
+ float h = 0;
+ float s = 0;
+ float v = max;
+
+ if (MathF.Abs(chroma) < Constants.Epsilon)
+ {
+ return new Hsv(0, s, v);
+ }
+
+ if (MathF.Abs(r - max) < Constants.Epsilon)
+ {
+ h = (g - b) / chroma;
+ }
+ else if (MathF.Abs(g - max) < Constants.Epsilon)
+ {
+ h = 2 + ((b - r) / chroma);
+ }
+ else if (MathF.Abs(b - max) < Constants.Epsilon)
+ {
+ h = 4 + ((r - g) / chroma);
+ }
+
+ h *= 60;
+ if (h < 0.0)
+ {
+ h += 360;
+ }
+
+ s = chroma / v;
+
+ return new Hsv(h, s, v);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzAndHunterLabConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzAndHunterLabConverterBase.cs
new file mode 100644
index 000000000..19fc78e9a
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzAndHunterLabConverterBase.cs
@@ -0,0 +1,51 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab
+{
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// The base class for converting between and color spaces.
+ ///
+ internal abstract class CieXyzAndHunterLabConverterBase
+ {
+ ///
+ /// Returns the Ka coefficient that depends upon the whitepoint illuminant.
+ ///
+ /// The whitepoint
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ComputeKa(CieXyz whitePoint)
+ {
+ DebugGuard.NotNull(whitePoint, nameof(whitePoint));
+
+ if (whitePoint.Equals(Illuminants.C))
+ {
+ return 175F;
+ }
+
+ return 100F * (175F / 198.04F) * (whitePoint.X + whitePoint.Y);
+ }
+
+ ///
+ /// Returns the Kb coefficient that depends upon the whitepoint illuminant.
+ ///
+ /// The whitepoint
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ComputeKb(CieXyz whitePoint)
+ {
+ DebugGuard.NotNull(whitePoint, nameof(whitePoint));
+
+ if (whitePoint == Illuminants.C)
+ {
+ return 70F;
+ }
+
+ return 100F * (70F / 218.11F) * (whitePoint.Y + whitePoint.Z);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzToHunterLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzToHunterLabConverter.cs
new file mode 100644
index 000000000..7f2df8336
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzToHunterLabConverter.cs
@@ -0,0 +1,75 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Color converter between CieXyz and HunterLab
+ ///
+ internal class CieXyzToHunterLabConverter : CieXyzAndHunterLabConverterBase, IColorConversion
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyzToHunterLabConverter()
+ : this(HunterLab.DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The hunter Lab white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyzToHunterLabConverter(CieXyz labWhitePoint)
+ {
+ this.HunterLabWhitePoint = labWhitePoint;
+ }
+
+ ///
+ /// Gets the target reference white. When not set, is used.
+ ///
+ public CieXyz HunterLabWhitePoint
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public HunterLab Convert(CieXyz input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here: http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab
+ float x = input.X, y = input.Y, z = input.Z;
+ float xn = this.HunterLabWhitePoint.X, yn = this.HunterLabWhitePoint.Y, zn = this.HunterLabWhitePoint.Z;
+
+ float ka = ComputeKa(this.HunterLabWhitePoint);
+ float kb = ComputeKb(this.HunterLabWhitePoint);
+
+ float l = 100 * MathF.Sqrt(y / yn);
+ float a = ka * (((x / xn) - (y / yn)) / MathF.Sqrt(y / yn));
+ float b = kb * (((y / yn) - (z / zn)) / MathF.Sqrt(y / yn));
+
+ if (float.IsNaN(a))
+ {
+ a = 0;
+ }
+
+ if (float.IsNaN(b))
+ {
+ b = 0;
+ }
+
+ return new HunterLab(l, a, b, this.HunterLabWhitePoint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/HunterLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/HunterLabToCieXyzConverter.cs
new file mode 100644
index 000000000..af7f4f370
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/HunterLabToCieXyzConverter.cs
@@ -0,0 +1,37 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.HunterLab
+{
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Color converter between HunterLab and CieXyz
+ ///
+ internal class HunterLabToCieXyzConverter : CieXyzAndHunterLabConverterBase, IColorConversion
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyz Convert(HunterLab input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ // Conversion algorithm described here: http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab
+ float l = input.L, a = input.A, b = input.B;
+ float xn = input.WhitePoint.X, yn = input.WhitePoint.Y, zn = input.WhitePoint.Z;
+
+ float ka = ComputeKa(input.WhitePoint);
+ float kb = ComputeKb(input.WhitePoint);
+
+ float y = MathF.Pow(l / 100F, 2) * yn;
+ float x = (((a / ka) * MathF.Sqrt(y / yn)) + (y / yn)) * xn;
+ float z = (((b / kb) * MathF.Sqrt(y / yn)) - (y / yn)) * (-zn);
+
+ return new CieXyz(x, y, z);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs
new file mode 100644
index 000000000..c856c7ff7
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs
@@ -0,0 +1,85 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Lms
+{
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Color converter between CIE XYZ and LMS
+ ///
+ internal class CieXyzAndLmsConverter : IColorConversion, IColorConversion
+ {
+ ///
+ /// Default transformation matrix used, when no other is set. (Bradford)
+ ///
+ ///
+ public static readonly Matrix4x4 DefaultTransformationMatrix = LmsAdaptationMatrix.Bradford;
+
+ private Matrix4x4 inverseTransformationMatrix;
+ private Matrix4x4 transformationMatrix;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyzAndLmsConverter()
+ : this(DefaultTransformationMatrix)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Definition of the cone response domain (see ),
+ /// if not set will be used.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyzAndLmsConverter(Matrix4x4 transformationMatrix)
+ {
+ this.TransformationMatrix = transformationMatrix;
+ }
+
+ ///
+ /// Gets or sets the transformation matrix used for the conversion (definition of the cone response domain).
+ ///
+ ///
+ public Matrix4x4 TransformationMatrix
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.transformationMatrix;
+
+ set
+ {
+ this.transformationMatrix = value;
+ Matrix4x4.Invert(this.transformationMatrix, out this.inverseTransformationMatrix);
+ }
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Lms Convert(CieXyz input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ Vector3 vector = Vector3.Transform(input.Vector, this.transformationMatrix);
+ return new Lms(vector);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public CieXyz Convert(Lms input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ Vector3 vector = Vector3.Transform(input.Vector, this.inverseTransformationMatrix);
+ return new CieXyz(vector);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs
new file mode 100644
index 000000000..279044baa
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs
@@ -0,0 +1,101 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+// ReSharper disable InconsistentNaming
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Lms
+{
+ using System.Numerics;
+
+ ///
+ /// AdaptionMatrix3X3 used for transformation from XYZ to LMS, defining the cone response domain.
+ /// Used in
+ ///
+ ///
+ /// Matrix data obtained from:
+ /// Two New von Kries Based Chromatic Adaptation Transforms Found by Numerical Optimization
+ /// S. Bianco, R. Schettini
+ /// DISCo, Department of Informatics, Systems and Communication, University of Milan-Bicocca, viale Sarca 336, 20126 Milan, Italy
+ /// https://web.stanford.edu/~sujason/ColorBalancing/Papers/Two%20New%20von%20Kries%20Based%20Chromatic%20Adaptation.pdf
+ ///
+ public static class LmsAdaptationMatrix
+ {
+ ///
+ /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65)
+ ///
+ public static readonly Matrix4x4 VonKriesHPEAdjusted
+ = Matrix4x4.Transpose(new Matrix4x4
+ {
+ M11 = 0.40024F, M12 = 0.7076F, M13 = -0.08081F,
+ M21 = -0.2263F, M22 = 1.16532F, M23 = 0.0457F,
+ M31 = 0, M32 = 0, M33 = 0.91822F,
+ M44 = 1F // Important for inverse transforms.
+ });
+
+ ///
+ /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez for equal energy)
+ ///
+ public static readonly Matrix4x4 VonKriesHPE
+ = Matrix4x4.Transpose(new Matrix4x4
+ {
+ M11 = 0.3897F, M12 = 0.6890F, M13 = -0.0787F,
+ M21 = -0.2298F, M22 = 1.1834F, M23 = 0.0464F,
+ M31 = 0, M32 = 0, M33 = 1F,
+ M44 = 1F
+ });
+
+ ///
+ /// XYZ scaling chromatic adaptation transform matrix
+ ///
+ public static readonly Matrix4x4 XyzScaling = Matrix4x4.Transpose(Matrix4x4.Identity);
+
+ ///
+ /// Bradford chromatic adaptation transform matrix (used in CMCCAT97)
+ ///
+ public static readonly Matrix4x4 Bradford
+ = Matrix4x4.Transpose(new Matrix4x4
+ {
+ M11 = 0.8951F, M12 = 0.2664F, M13 = -0.1614F,
+ M21 = -0.7502F, M22 = 1.7135F, M23 = 0.0367F,
+ M31 = 0.0389F, M32 = -0.0685F, M33 = 1.0296F,
+ M44 = 1F
+ });
+
+ ///
+ /// Spectral sharpening and the Bradford transform
+ ///
+ public static readonly Matrix4x4 BradfordSharp
+ = Matrix4x4.Transpose(new Matrix4x4
+ {
+ M11 = 1.2694F, M12 = -0.0988F, M13 = -0.1706F,
+ M21 = -0.8364F, M22 = 1.8006F, M23 = 0.0357F,
+ M31 = 0.0297F, M32 = -0.0315F, M33 = 1.0018F,
+ M44 = 1F
+ });
+
+ ///
+ /// CMCCAT2000 (fitted from all available color data sets)
+ ///
+ public static readonly Matrix4x4 CMCCAT2000
+ = Matrix4x4.Transpose(new Matrix4x4
+ {
+ M11 = 0.7982F, M12 = 0.3389F, M13 = -0.1371F,
+ M21 = -0.5918F, M22 = 1.5512F, M23 = 0.0406F,
+ M31 = 0.0008F, M32 = 0.239F, M33 = 0.9753F,
+ M44 = 1F
+ });
+
+ ///
+ /// CAT02 (optimized for minimizing CIELAB differences)
+ ///
+ public static readonly Matrix4x4 CAT02
+ = Matrix4x4.Transpose(new Matrix4x4
+ {
+ M11 = 0.7328F, M12 = 0.4296F, M13 = -0.1624F,
+ M21 = -0.7036F, M22 = 1.6975F, M23 = 0.0061F,
+ M31 = 0.0030F, M32 = 0.0136F, M33 = 0.9834F,
+ M44 = 1F
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs
new file mode 100644
index 000000000..c5c612409
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
+{
+ using System.Numerics;
+
+ using Rgb = ImageSharp.ColorSpaces.Rgb;
+
+ ///
+ /// Color converter between CieXyz and LinearRgb
+ ///
+ internal class CieXyzToLinearRgbConverter : LinearRgbAndCieXyzConverterBase, IColorConversion
+ {
+ private readonly Matrix4x4 conversionMatrix;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CieXyzToLinearRgbConverter()
+ : this(Rgb.DefaultWorkingSpace)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The target working space.
+ public CieXyzToLinearRgbConverter(IRgbWorkingSpace workingSpace)
+ {
+ this.TargetWorkingSpace = workingSpace;
+ this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace);
+ }
+
+ ///
+ /// Gets the target working space
+ ///
+ public IRgbWorkingSpace TargetWorkingSpace { get; }
+
+ ///
+ public LinearRgb Convert(CieXyz input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ Matrix4x4.Invert(this.conversionMatrix, out Matrix4x4 inverted);
+ Vector3 vector = Vector3.Transform(input.Vector, inverted);
+ return new LinearRgb(vector, this.TargetWorkingSpace);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/GammaCompanding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/GammaCompanding.cs
new file mode 100644
index 000000000..08200ce6b
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/GammaCompanding.cs
@@ -0,0 +1,48 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
+{
+ using System;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Implements gamma companding
+ ///
+ ///
+ ///
+ ///
+ ///
+ public class GammaCompanding : ICompanding
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The gamma value.
+ public GammaCompanding(float gamma)
+ {
+ this.Gamma = gamma;
+ }
+
+ ///
+ /// Gets the gamma value
+ ///
+ public float Gamma { get; }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Expand(float channel)
+ {
+ return MathF.Pow(channel, this.Gamma);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Compress(float channel)
+ {
+ return MathF.Pow(channel, 1 / this.Gamma);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LCompanding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LCompanding.cs
new file mode 100644
index 000000000..bbf12ca00
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LCompanding.cs
@@ -0,0 +1,36 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
+{
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Implements L* companding
+ ///
+ ///
+ /// For more info see:
+ ///
+ ///
+ ///
+ public class LCompanding : ICompanding
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Expand(float channel)
+ {
+ return channel <= 0.08 ? 100 * channel / CieConstants.Kappa : MathF.Pow((channel + 0.16F) / 1.16F, 3);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Compress(float channel)
+ {
+ return channel <= CieConstants.Epsilon
+ ? channel * CieConstants.Kappa / 100F
+ : MathF.Pow(1.16F * channel, 0.3333333F) - 0.16F;
+ }
+ }
+}
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs
new file mode 100644
index 000000000..4c46e4d8c
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs
@@ -0,0 +1,69 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
+{
+ using System.Numerics;
+
+ ///
+ /// Provides base methods for converting between Rgb and CieXyz color spaces.
+ ///
+ internal abstract class LinearRgbAndCieXyzConverterBase
+ {
+ ///
+ /// Geturns the correct matrix to convert between the Rgb and CieXyz color space.
+ ///
+ /// The Rgb working space.
+ /// The based on the chromaticity and working space.
+ public static Matrix4x4 GetRgbToCieXyzMatrix(IRgbWorkingSpace workingSpace)
+ {
+ DebugGuard.NotNull(workingSpace, nameof(workingSpace));
+
+ RgbPrimariesChromaticityCoordinates chromaticity = workingSpace.ChromaticityCoordinates;
+
+ float xr = chromaticity.R.X;
+ float xg = chromaticity.G.X;
+ float xb = chromaticity.B.X;
+ float yr = chromaticity.R.Y;
+ float yg = chromaticity.G.Y;
+ float yb = chromaticity.B.Y;
+
+ float mXr = xr / yr;
+ const float Yr = 1;
+ float mZr = (1 - xr - yr) / yr;
+
+ float mXg = xg / yg;
+ const float Yg = 1;
+ float mZg = (1 - xg - yg) / yg;
+
+ float mXb = xb / yb;
+ const float Yb = 1;
+ float mZb = (1 - xb - yb) / yb;
+
+ Matrix4x4 xyzMatrix = new Matrix4x4
+ {
+ M11 = mXr, M21 = mXg, M31 = mXb,
+ M12 = Yr, M22 = Yg, M32 = Yb,
+ M13 = mZr, M23 = mZg, M33 = mZb,
+ M44 = 1F
+ };
+
+ Matrix4x4 inverseXyzMatrix;
+ Matrix4x4.Invert(xyzMatrix, out inverseXyzMatrix);
+
+ Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.Vector, inverseXyzMatrix);
+
+ // Use transposed Rows/Coloumns
+ // TODO: Is there a built in method for this multiplication?
+ return new Matrix4x4
+ {
+ M11 = vector.X * mXr, M21 = vector.Y * mXg, M31 = vector.Z * mXb,
+ M12 = vector.X * Yr, M22 = vector.Y * Yg, M32 = vector.Z * Yb,
+ M13 = vector.X * mZr, M23 = vector.Y * mZg, M33 = vector.Z * mZb,
+ M44 = 1F
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs
new file mode 100644
index 000000000..4a04185e8
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs
@@ -0,0 +1,52 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
+{
+ using System.Numerics;
+
+ using Rgb = ImageSharp.ColorSpaces.Rgb;
+
+ ///
+ /// Color converter between LinearRgb and CieXyz
+ ///
+ internal class LinearRgbToCieXyzConverter : LinearRgbAndCieXyzConverterBase, IColorConversion
+ {
+ private readonly Matrix4x4 conversionMatrix;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public LinearRgbToCieXyzConverter()
+ : this(Rgb.DefaultWorkingSpace)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The target working space.
+ public LinearRgbToCieXyzConverter(IRgbWorkingSpace workingSpace)
+ {
+ this.SourceWorkingSpace = workingSpace;
+ this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace);
+ }
+
+ ///
+ /// Gets the source working space
+ ///
+ public IRgbWorkingSpace SourceWorkingSpace { get; }
+
+ ///
+ public CieXyz Convert(LinearRgb input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+ Guard.IsTrue(input.WorkingSpace.Equals(this.SourceWorkingSpace), nameof(input.WorkingSpace), "Input and source working spaces must be equal.");
+
+ Vector3 vector = Vector3.Transform(input.Vector, this.conversionMatrix);
+ return new CieXyz(vector);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs
new file mode 100644
index 000000000..cf4638ae7
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs
@@ -0,0 +1,30 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
+{
+ using System.Numerics;
+
+ using Rgb = ColorSpaces.Rgb;
+
+ ///
+ /// Color converter between LinearRgb and Rgb
+ ///
+ internal class LinearRgbToRgbConverter : IColorConversion
+ {
+ ///
+ public Rgb Convert(LinearRgb input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ Vector3 vector = input.Vector;
+ vector.X = input.WorkingSpace.Companding.Compress(vector.X);
+ vector.Y = input.WorkingSpace.Companding.Compress(vector.Y);
+ vector.Z = input.WorkingSpace.Companding.Compress(vector.Z);
+
+ return new Rgb(vector, input.WorkingSpace);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RGBPrimariesChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RGBPrimariesChromaticityCoordinates.cs
new file mode 100644
index 000000000..0148b91d5
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RGBPrimariesChromaticityCoordinates.cs
@@ -0,0 +1,107 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
+{
+ using System;
+
+ ///
+ /// Represents the chromaticity coordinates of RGB primaries.
+ /// One of the specifiers of .
+ ///
+ internal struct RgbPrimariesChromaticityCoordinates : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The chomaticity coordinates of the red channel.
+ /// The chomaticity coordinates of the green channel.
+ /// The chomaticity coordinates of the blue channel.
+ public RgbPrimariesChromaticityCoordinates(CieXyChromaticityCoordinates r, CieXyChromaticityCoordinates g, CieXyChromaticityCoordinates b)
+ {
+ this.R = r;
+ this.G = g;
+ this.B = b;
+ }
+
+ ///
+ /// Gets the chomaticity coordinates of the red channel.
+ ///
+ public CieXyChromaticityCoordinates R { get; }
+
+ ///
+ /// Gets the chomaticity coordinates of the green channel.
+ ///
+ public CieXyChromaticityCoordinates G { get; }
+
+ ///
+ /// Gets the chomaticity coordinates of the blue channel.
+ ///
+ public CieXyChromaticityCoordinates 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.
+ ///
+ public static bool operator ==(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right)
+ {
+ return 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 !=(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is RgbPrimariesChromaticityCoordinates)
+ {
+ return this.Equals((RgbPrimariesChromaticityCoordinates)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(RgbPrimariesChromaticityCoordinates other)
+ {
+ return this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.R.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.G.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.B.GetHashCode();
+ return hashCode;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec2020Companding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec2020Companding.cs
new file mode 100644
index 000000000..630faedca
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec2020Companding.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
+{
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Implements Rec. 2020 companding function (for 12-bits).
+ ///
+ ///
+ ///
+ /// For 10-bits, companding is identical to
+ ///
+ public class Rec2020Companding : ICompanding
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Expand(float channel)
+ {
+ return channel < 0.08145F ? channel / 4.5F : MathF.Pow((channel + 0.0993F) / 1.0993F, 2.222222F);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Compress(float channel)
+ {
+ return channel < 0.0181F ? 4500F * channel : (1.0993F * channel) - 0.0993F;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec709Companding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec709Companding.cs
new file mode 100644
index 000000000..24dd6ca17
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec709Companding.cs
@@ -0,0 +1,32 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
+{
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Implements the Rec. 709 companding function
+ ///
+ ///
+ /// http://en.wikipedia.org/wiki/Rec._709
+ ///
+ public class Rec709Companding : ICompanding
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Expand(float channel)
+ {
+ return channel < 0.081F ? channel / 4.5F : MathF.Pow((channel + 0.099F) / 1.099F, 2.222222F);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Compress(float channel)
+ {
+ return channel < 0.018F ? 4500F * channel : (1.099F * channel) - 0.099F;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs
new file mode 100644
index 000000000..0d0d58828
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs
@@ -0,0 +1,30 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
+{
+ using System.Numerics;
+
+ using Rgb = ColorSpaces.Rgb;
+
+ ///
+ /// Color converter between Rgb and LinearRgb
+ ///
+ internal class RgbToLinearRgbConverter : IColorConversion
+ {
+ ///
+ public LinearRgb Convert(Rgb input)
+ {
+ Guard.NotNull(input, nameof(input));
+
+ Vector3 vector = input.Vector;
+ vector.X = input.WorkingSpace.Companding.Expand(vector.X);
+ vector.Y = input.WorkingSpace.Companding.Expand(vector.Y);
+ vector.Z = input.WorkingSpace.Companding.Expand(vector.Z);
+
+ return new LinearRgb(vector, input.WorkingSpace);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs
new file mode 100644
index 000000000..89e403e76
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs
@@ -0,0 +1,107 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
+{
+ ///
+ /// Trivial implementation of
+ ///
+ internal struct RgbWorkingSpace : IRgbWorkingSpace
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The reference white point.
+ /// The function pair for converting to and back.
+ /// The chromaticity of the rgb primaries.
+ public RgbWorkingSpace(CieXyz referenceWhite, ICompanding companding, RgbPrimariesChromaticityCoordinates chromaticityCoordinates)
+ {
+ this.WhitePoint = referenceWhite;
+ this.Companding = companding;
+ this.ChromaticityCoordinates = chromaticityCoordinates;
+ }
+
+ ///
+ /// Gets the reference white point
+ ///
+ public CieXyz WhitePoint { get; }
+
+ ///
+ /// Gets the function pair for converting to and back.
+ ///
+ public ICompanding Companding { get; }
+
+ ///
+ /// Gets the chromaticity of the rgb primaries.
+ ///
+ public RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { 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 ==(RgbWorkingSpace left, RgbWorkingSpace right)
+ {
+ return Equals(left, 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 !=(RgbWorkingSpace left, RgbWorkingSpace right)
+ {
+ return !Equals(left, right);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is RgbWorkingSpace)
+ {
+ return this.Equals((RgbWorkingSpace)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IRgbWorkingSpace other)
+ {
+ // TODO: Object.Equals for ICompanding will be slow.
+ return this.WhitePoint.Equals(other.WhitePoint)
+ && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates)
+ && Equals(this.Companding, other.Companding);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.WhitePoint.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.ChromaticityCoordinates.GetHashCode();
+ hashCode = (hashCode * 397) ^ (this.Companding?.GetHashCode() ?? 0);
+ return hashCode;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/SRgbCompanding.cs
new file mode 100644
index 000000000..18ec94c51
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/SRgbCompanding.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.Rgb
+{
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Implements sRGB companding
+ ///
+ ///
+ /// For more info see:
+ ///
+ ///
+ ///
+ public class SRgbCompanding : ICompanding
+ {
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Expand(float channel)
+ {
+ return channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float Compress(float channel)
+ {
+ return channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/YCbCr/YCbCrAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/YCbCr/YCbCrAndRgbConverter.cs
new file mode 100644
index 000000000..e58da580d
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/YCbCr/YCbCrAndRgbConverter.cs
@@ -0,0 +1,57 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion.Implementation.YCbCr
+{
+ using System;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ using ImageSharp.ColorSpaces;
+
+ ///
+ /// Color converter between YCbCr and Rgb
+ /// See for formulas.
+ ///
+ internal class YCbCrAndRgbConverter : IColorConversion, IColorConversion
+ {
+ private static readonly Vector3 MaxBytes = new Vector3(255F);
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Rgb Convert(YCbCr input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ float y = input.Y;
+ float cb = input.Cb - 128F;
+ float cr = input.Cr - 128F;
+
+ float r = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero);
+ float g = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero);
+ float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero);
+
+ return new Rgb(new Vector3(r, g, b) / MaxBytes);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public YCbCr Convert(Rgb input)
+ {
+ DebugGuard.NotNull(input, nameof(input));
+
+ Vector3 rgb = input.Vector * MaxBytes;
+ float r = rgb.X;
+ float g = rgb.Y;
+ float b = rgb.Z;
+
+ float y = (0.299F * r) + (0.587F * g) + (0.114F * b);
+ float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
+ float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
+
+ return new YCbCr(y, cb, cr);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs
new file mode 100644
index 000000000..55d1d65a3
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs
@@ -0,0 +1,75 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces.Conversion
+{
+ using System.Numerics;
+
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion.Implementation.Lms;
+
+ ///
+ /// Basic implementation of the von Kries chromatic adaptation model
+ ///
+ ///
+ /// Transformation described here:
+ /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ ///
+ internal class VonKriesChromaticAdaptation : IChromaticAdaptation
+ {
+ private readonly CieXyzAndLmsConverter converter;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public VonKriesChromaticAdaptation()
+ : this(new CieXyzAndLmsConverter())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The transformation matrix used for the conversion (definition of the cone response domain).
+ ///
+ ///
+ public VonKriesChromaticAdaptation(Matrix4x4 transformationMatrix)
+ : this(new CieXyzAndLmsConverter(transformationMatrix))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The color converter
+ public VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter)
+ {
+ this.converter = converter;
+ }
+
+ ///
+ public CieXyz Transform(CieXyz sourceColor, CieXyz sourceWhitePoint, CieXyz targetWhitePoint)
+ {
+ Guard.NotNull(sourceColor, nameof(sourceColor));
+ Guard.NotNull(sourceWhitePoint, nameof(sourceWhitePoint));
+ Guard.NotNull(targetWhitePoint, nameof(targetWhitePoint));
+
+ if (sourceWhitePoint.Equals(targetWhitePoint))
+ {
+ return sourceColor;
+ }
+
+ Lms sourceColorLms = this.converter.Convert(sourceColor);
+ Lms sourceWhitePointLms = this.converter.Convert(sourceWhitePoint);
+ Lms targetWhitePointLms = this.converter.Convert(targetWhitePoint);
+
+ var vector = new Vector3(targetWhitePointLms.L / sourceWhitePointLms.L, targetWhitePointLms.M / sourceWhitePointLms.M, targetWhitePointLms.S / sourceWhitePointLms.S);
+ var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.Vector));
+
+ return this.converter.Convert(targetColorLms);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Hsl.cs b/src/ImageSharp/ColorSpaces/Hsl.cs
similarity index 66%
rename from src/ImageSharp/Colors/Spaces/Hsl.cs
rename to src/ImageSharp/ColorSpaces/Hsl.cs
index de706c350..5bbbeec30 100644
--- a/src/ImageSharp/Colors/Spaces/Hsl.cs
+++ b/src/ImageSharp/ColorSpaces/Hsl.cs
@@ -3,28 +3,23 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Colors.Spaces
+namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
- using ImageSharp.PixelFormats;
+ using System.Runtime.CompilerServices;
///
/// Represents a Hsl (hue, saturation, lightness) color.
///
- public struct Hsl : IEquatable, IAlmostEquatable
+ internal struct Hsl : IColorVector, IEquatable, IAlmostEquatable
{
///
/// Represents a that has H, S, and L values set to zero.
///
public static readonly Hsl Empty = default(Hsl);
- ///
- /// Min range used for clamping
- ///
- private static readonly Vector3 VectorMin = Vector3.Zero;
-
///
/// Max range used for clamping
///
@@ -41,28 +36,51 @@ namespace ImageSharp.Colors.Spaces
/// The h hue component.
/// The s saturation component.
/// The l value (lightness) component.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Hsl(float h, float s, float l)
+ : this(new Vector3(h, s, l))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the h, s, l components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Hsl(Vector3 vector)
{
- this.backingVector = Vector3.Clamp(new Vector3(h, s, l), VectorMin, VectorMax);
+ this.backingVector = Vector3.Clamp(vector, Vector3.Zero, VectorMax);
}
///
/// Gets the hue component.
/// A value ranging between 0 and 360.
///
- public float H => this.backingVector.X;
+ public float H
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
///
/// Gets the saturation component.
/// A value ranging between 0 and 1.
///
- public float S => this.backingVector.Y;
+ public float S
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
///
/// Gets the lightness component.
/// A value ranging between 0 and 1.
///
- public float L => this.backingVector.Z;
+ public float L
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
///
/// Gets a value indicating whether this is empty.
@@ -70,61 +88,11 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Hsl(Rgba32 color)
+ ///
+ public Vector3 Vector
{
- float r = color.R / 255F;
- float g = color.G / 255F;
- float b = color.B / 255F;
-
- float max = MathF.Max(r, MathF.Max(g, b));
- float min = MathF.Min(r, MathF.Min(g, b));
- float chroma = max - min;
- float h = 0;
- float s = 0;
- float l = (max + min) / 2;
-
- if (MathF.Abs(chroma) < Constants.Epsilon)
- {
- return new Hsl(0, s, l);
- }
-
- if (MathF.Abs(r - max) < Constants.Epsilon)
- {
- h = (g - b) / chroma;
- }
- else if (MathF.Abs(g - max) < Constants.Epsilon)
- {
- h = 2 + ((b - r) / chroma);
- }
- else if (MathF.Abs(b - max) < Constants.Epsilon)
- {
- h = 4 + ((r - g) / chroma);
- }
-
- h *= 60;
- if (h < 0.0)
- {
- h += 360;
- }
-
- if (l <= .5f)
- {
- s = chroma / (max + min);
- }
- else
- {
- s = chroma / (2 - chroma);
- }
-
- return new Hsl(h, s, l);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
}
///
@@ -139,6 +107,7 @@ namespace ImageSharp.Colors.Spaces
///
/// True if the current left is equal to the parameter; otherwise, false.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Hsl left, Hsl right)
{
return left.Equals(right);
@@ -156,6 +125,7 @@ namespace ImageSharp.Colors.Spaces
///
/// True if the current left is unequal to the parameter; otherwise, false.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Hsl left, Hsl right)
{
return !left.Equals(right);
@@ -179,6 +149,7 @@ namespace ImageSharp.Colors.Spaces
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is Hsl)
@@ -190,19 +161,21 @@ namespace ImageSharp.Colors.Spaces
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Hsl other)
{
return this.backingVector.Equals(other.backingVector);
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(Hsl other, float precision)
{
- Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Hsv.cs b/src/ImageSharp/ColorSpaces/Hsv.cs
similarity index 78%
rename from src/ImageSharp/Colors/Spaces/Hsv.cs
rename to src/ImageSharp/ColorSpaces/Hsv.cs
index 2b3d79afe..dcbb73692 100644
--- a/src/ImageSharp/Colors/Spaces/Hsv.cs
+++ b/src/ImageSharp/ColorSpaces/Hsv.cs
@@ -3,28 +3,23 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Colors.Spaces
+namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
- using ImageSharp.PixelFormats;
+ using System.Runtime.CompilerServices;
///
/// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness).
///
- public struct Hsv : IEquatable, IAlmostEquatable
+ internal struct Hsv : IColorVector, IEquatable, IAlmostEquatable
{
///
/// Represents a that has H, S, and V values set to zero.
///
public static readonly Hsv Empty = default(Hsv);
- ///
- /// Min range used for clamping
- ///
- private static readonly Vector3 VectorMin = Vector3.Zero;
-
///
/// Max range used for clamping
///
@@ -41,28 +36,51 @@ namespace ImageSharp.Colors.Spaces
/// The h hue component.
/// The s saturation component.
/// The v value (brightness) component.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Hsv(float h, float s, float v)
+ : this(new Vector3(h, s, v))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the h, s, v components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Hsv(Vector3 vector)
{
- this.backingVector = Vector3.Clamp(new Vector3(h, s, v), VectorMin, VectorMax);
+ this.backingVector = Vector3.Clamp(vector, Vector3.Zero, VectorMax);
}
///
/// Gets the hue component.
/// A value ranging between 0 and 360.
///
- public float H => this.backingVector.X;
+ public float H
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
///
/// Gets the saturation component.
/// A value ranging between 0 and 1.
///
- public float S => this.backingVector.Y;
+ public float S
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
///
/// Gets the value (brightness) component.
/// A value ranging between 0 and 1.
///
- public float V => this.backingVector.Z;
+ public float V
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
///
/// Gets a value indicating whether this is empty.
@@ -70,6 +88,13 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
+ ///
+ public Vector3 Vector
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
+ }
+
///
/// Allows the implicit conversion of an instance of to a
/// .
@@ -132,6 +157,7 @@ namespace ImageSharp.Colors.Spaces
///
/// True if the current left is equal to the parameter; otherwise, false.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Hsv left, Hsv right)
{
return left.Equals(right);
@@ -149,6 +175,7 @@ namespace ImageSharp.Colors.Spaces
///
/// True if the current left is unequal to the parameter; otherwise, false.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Hsv left, Hsv right)
{
return !left.Equals(right);
@@ -172,6 +199,7 @@ namespace ImageSharp.Colors.Spaces
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is Hsv)
@@ -183,19 +211,21 @@ namespace ImageSharp.Colors.Spaces
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Hsv other)
{
return this.backingVector.Equals(other.backingVector);
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AlmostEquals(Hsv other, float precision)
{
- Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
return result.X <= precision
&& result.Y <= precision
&& result.Z <= precision;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/HunterLab.cs b/src/ImageSharp/ColorSpaces/HunterLab.cs
new file mode 100644
index 000000000..2b49ee944
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/HunterLab.cs
@@ -0,0 +1,223 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Represents an Hunter LAB color.
+ ///
+ ///
+ internal struct HunterLab : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// D50 standard illuminant.
+ /// Used when reference white is not specified explicitly.
+ ///
+ public static readonly CieXyz DefaultWhitePoint = Illuminants.C;
+
+ ///
+ /// Represents a that has L, A, B values set to zero.
+ ///
+ public static readonly HunterLab Empty = default(HunterLab);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The a (green - magenta) component.
+ /// The b (blue - yellow) component.
+ /// Uses as white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public HunterLab(float l, float a, float b)
+ : this(new Vector3(l, a, b), DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The a (green - magenta) component.
+ /// The b (blue - yellow) component.
+ /// The reference white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public HunterLab(float l, float a, float b, CieXyz whitePoint)
+ : this(new Vector3(l, a, b), whitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, a, b components.
+ /// Uses as white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public HunterLab(Vector3 vector)
+ : this(vector, DefaultWhitePoint)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l a b components.
+ /// The reference white point.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public HunterLab(Vector3 vector, CieXyz whitePoint)
+ : this()
+ {
+ this.backingVector = vector;
+ this.WhitePoint = whitePoint;
+ }
+
+ ///
+ /// Gets the reference white point of this color
+ ///
+ public CieXyz WhitePoint { get; }
+
+ ///
+ /// Gets the lightness dimension.
+ /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).
+ ///
+ public float L
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
+
+ ///
+ /// Gets the a color component.
+ /// A value ranging from -100 to 100. Negative is green, positive magenta.
+ ///
+ public float A
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
+
+ ///
+ /// Gets the b color component.
+ /// A value ranging from -100 to 100. Negative is blue, positive is yellow
+ ///
+ public float B
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ public Vector3 Vector
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
+ }
+
+ ///
+ /// 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 ==(HunterLab left, HunterLab right)
+ {
+ return 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 !=(HunterLab left, HunterLab right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.WhitePoint.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.backingVector.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "HunterLab [Empty]";
+ }
+
+ return $"HunterLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]";
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj)
+ {
+ if (obj is HunterLab)
+ {
+ return this.Equals((HunterLab)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(HunterLab other)
+ {
+ return this.backingVector.Equals(other.backingVector)
+ && this.WhitePoint.Equals(other.WhitePoint);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool AlmostEquals(HunterLab other, float precision)
+ {
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return this.WhitePoint.Equals(other.WhitePoint)
+ && result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs b/src/ImageSharp/ColorSpaces/IAlmostEquatable.cs
similarity index 97%
rename from src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs
rename to src/ImageSharp/ColorSpaces/IAlmostEquatable.cs
index 04ea91cba..a67eaeb06 100644
--- a/src/ImageSharp/Colors/Spaces/IAlmostEquatable.cs
+++ b/src/ImageSharp/ColorSpaces/IAlmostEquatable.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Colors.Spaces
+namespace ImageSharp.ColorSpaces
{
using System;
@@ -27,4 +27,4 @@ namespace ImageSharp.Colors.Spaces
///
bool AlmostEquals(TPixel other, TPrecision precision);
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/IColorVector.cs b/src/ImageSharp/ColorSpaces/IColorVector.cs
new file mode 100644
index 000000000..08b70e482
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/IColorVector.cs
@@ -0,0 +1,20 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces
+{
+ using System.Numerics;
+
+ ///
+ /// Color represented as a vector in its color space
+ ///
+ public interface IColorVector
+ {
+ ///
+ /// Gets the vector representation of the color
+ ///
+ Vector3 Vector { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/ICompanding.cs b/src/ImageSharp/ColorSpaces/ICompanding.cs
new file mode 100644
index 000000000..e4f0e4a4c
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/ICompanding.cs
@@ -0,0 +1,37 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces
+{
+ ///
+ /// Pair of companding functions for .
+ /// Used for conversion to and backwards.
+ /// See also:
+ ///
+ internal interface ICompanding
+ {
+ ///
+ /// Expands a companded channel to its linear equivalent with respect to the energy.
+ ///
+ ///
+ /// For more info see:
+ ///
+ ///
+ /// The channel value
+ /// The linear channel value
+ float Expand(float channel);
+
+ ///
+ /// Compresses an uncompanded channel (linear) to its nonlinear equivalent (depends on the RGB color system).
+ ///
+ ///
+ /// For more info see:
+ ///
+ ///
+ /// The channel value
+ /// The nonlinear channel value
+ float Compress(float channel);
+ }
+}
diff --git a/src/ImageSharp/ColorSpaces/IRgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/IRgbWorkingSpace.cs
new file mode 100644
index 000000000..236085434
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/IRgbWorkingSpace.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces
+{
+ using System;
+
+ using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb;
+
+ ///
+ /// Encasulates the RGB working color space
+ ///
+ internal interface IRgbWorkingSpace : IEquatable
+ {
+ ///
+ /// Gets the reference white of the color space
+ ///
+ CieXyz WhitePoint { get; }
+
+ ///
+ /// Gets the chromaticity coordinates of the primaries
+ ///
+ RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; }
+
+ ///
+ /// Gets the companding function associated with the RGB color system. Used for conversion to XYZ and backwards.
+ ///
+ ///
+ ///
+ ICompanding Companding { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Illuminants.cs b/src/ImageSharp/ColorSpaces/Illuminants.cs
new file mode 100644
index 000000000..224cf9939
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Illuminants.cs
@@ -0,0 +1,71 @@
+namespace ImageSharp.ColorSpaces
+{
+ ///
+ /// The well known standard illuminants.
+ /// Standard illuminants provide a basis for comparing images or colors recorded under different lighting
+ ///
+ ///
+ /// Coefficients taken from:
+ /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ ///
+ /// Descriptions taken from:
+ /// http://en.wikipedia.org/wiki/Standard_illuminant
+ ///
+ internal static class Illuminants
+ {
+ ///
+ /// Incandescent / Tungsten
+ ///
+ public static readonly CieXyz A = new CieXyz(1.09850F, 1F, 0.35585F);
+
+ ///
+ /// Direct sunlight at noon (obsoleteF)
+ ///
+ public static readonly CieXyz B = new CieXyz(0.99072F, 1F, 0.85223F);
+
+ ///
+ /// Average / North sky Daylight (obsoleteF)
+ ///
+ public static readonly CieXyz C = new CieXyz(0.98074F, 1F, 1.18232F);
+
+ ///
+ /// Horizon Light. ICC profile PCS
+ ///
+ public static readonly CieXyz D50 = new CieXyz(0.96422F, 1F, 0.82521F);
+
+ ///
+ /// Mid-morning / Mid-afternoon Daylight
+ ///
+ public static readonly CieXyz D55 = new CieXyz(0.95682F, 1F, 0.92149F);
+
+ ///
+ /// Noon Daylight: TelevisionF, sRGB color space
+ ///
+ public static readonly CieXyz D65 = new CieXyz(0.95047F, 1F, 1.08883F);
+
+ ///
+ /// North sky Daylight
+ ///
+ public static readonly CieXyz D75 = new CieXyz(0.94972F, 1F, 1.22638F);
+
+ ///
+ /// Equal energy
+ ///
+ public static readonly CieXyz E = new CieXyz(1F, 1F, 1F);
+
+ ///
+ /// Cool White Fluorescent
+ ///
+ public static readonly CieXyz F2 = new CieXyz(0.99186F, 1F, 0.67393F);
+
+ ///
+ /// D65 simulatorF, Daylight simulator
+ ///
+ public static readonly CieXyz F7 = new CieXyz(0.95041F, 1F, 1.08747F);
+
+ ///
+ /// Philips TL84F, Ultralume 40
+ ///
+ public static readonly CieXyz F11 = new CieXyz(1.00962F, 1F, 0.64350F);
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/LinearRgb.cs b/src/ImageSharp/ColorSpaces/LinearRgb.cs
new file mode 100644
index 000000000..fdfc0d266
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/LinearRgb.cs
@@ -0,0 +1,213 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Represents an linear Rgb color with specified working space
+ ///
+ internal struct LinearRgb : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has R, G, and B values set to zero.
+ ///
+ public static readonly LinearRgb Empty = default(LinearRgb);
+
+ ///
+ /// The default LinearRgb working space
+ ///
+ public static readonly IRgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb;
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The red component ranging between 0 and 1.
+ /// The green component ranging between 0 and 1.
+ /// The blue component ranging between 0 and 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public LinearRgb(float r, float g, float b)
+ : this(new Vector3(r, g, b))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The red component ranging between 0 and 1.
+ /// The green component ranging between 0 and 1.
+ /// The blue component ranging between 0 and 1.
+ /// The rgb working space.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public LinearRgb(float r, float g, float b, IRgbWorkingSpace workingSpace)
+ : this(new Vector3(r, g, b), workingSpace)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the r, g, b components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public LinearRgb(Vector3 vector)
+ : this(vector, DefaultWorkingSpace)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the r, g, b components.
+ /// The LinearRgb working space.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public LinearRgb(Vector3 vector, IRgbWorkingSpace workingSpace)
+ : this()
+ {
+ // Clamp to 0-1 range.
+ this.backingVector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One);
+ this.WorkingSpace = workingSpace;
+ }
+
+ ///
+ /// Gets the red component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float R
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
+
+ ///
+ /// Gets the green component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float G
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
+
+ ///
+ /// Gets the blue component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float B
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
+
+ ///
+ /// Gets the LinearRgb color space
+ ///
+ public IRgbWorkingSpace WorkingSpace { get; }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ public Vector3 Vector
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
+ }
+
+ ///
+ /// 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 ==(LinearRgb left, LinearRgb right)
+ {
+ return 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 !=(LinearRgb left, LinearRgb right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.backingVector.GetHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "LinearRgb [ Empty ]";
+ }
+
+ return $"LinearRgb [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##} ]";
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj)
+ {
+ if (obj is LinearRgb)
+ {
+ return this.Equals((LinearRgb)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(LinearRgb other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool AlmostEquals(LinearRgb other, float precision)
+ {
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Lms.cs b/src/ImageSharp/ColorSpaces/Lms.cs
new file mode 100644
index 000000000..c76d87253
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Lms.cs
@@ -0,0 +1,180 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// LMS is a color space represented by the response of the three types of cones of the human eye,
+ /// named after their responsivity (sensitivity) at long, medium and short wavelengths.
+ ///
+ ///
+ internal struct Lms : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has L, M, and S values set to zero.
+ ///
+ public static readonly Lms Empty = default(Lms);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// L represents the responsivity at long wavelengths.
+ /// M represents the responsivity at medium wavelengths.
+ /// S represents the responsivity at short wavelengths.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Lms(float l, float m, float s)
+ : this(new Vector3(l, m, s))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the l, m, s components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Lms(Vector3 vector)
+ : this()
+ {
+ // Not clamping as documentation about this space seems to indicate "usual" ranges
+ this.backingVector = vector;
+ }
+
+ ///
+ /// Gets the L long component.
+ /// A value usually ranging between -1 and 1.
+ ///
+ public float L
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
+
+ ///
+ /// Gets the M medium component.
+ /// A value usually ranging between -1 and 1.
+ ///
+ public float M
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
+
+ ///
+ /// Gets the S short component.
+ /// A value usually ranging between -1 and 1.
+ ///
+ public float S
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ public Vector3 Vector
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
+ }
+
+ ///
+ /// 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 ==(Lms left, Lms right)
+ {
+ return 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 !=(Lms left, Lms right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.backingVector.GetHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "Lms [ Empty ]";
+ }
+
+ return $"Lms [ L={this.L:#0.##}, M={this.M:#0.##}, S={this.S:#0.##} ]";
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj)
+ {
+ if (obj is Lms)
+ {
+ return this.Equals((Lms)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(Lms other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool AlmostEquals(Lms other, float precision)
+ {
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Rgb.cs b/src/ImageSharp/ColorSpaces/Rgb.cs
new file mode 100644
index 000000000..898c81730
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/Rgb.cs
@@ -0,0 +1,233 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.ColorSpaces
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Represents an RGB color with specified working space
+ ///
+ internal struct Rgb : IColorVector, IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has R, G, and B values set to zero.
+ ///
+ public static readonly Rgb Empty = default(Rgb);
+
+ ///
+ /// The default rgb working space
+ ///
+ public static readonly IRgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb;
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private readonly Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The red component ranging between 0 and 1.
+ /// The green component ranging between 0 and 1.
+ /// The blue component ranging between 0 and 1.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Rgb(float r, float g, float b)
+ : this(new Vector3(r, g, b))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The red component ranging between 0 and 1.
+ /// The green component ranging between 0 and 1.
+ /// The blue component ranging between 0 and 1.
+ /// The rgb working space.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Rgb(float r, float g, float b, IRgbWorkingSpace workingSpace)
+ : this(new Vector3(r, g, b), workingSpace)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the r, g, b components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Rgb(Vector3 vector)
+ : this(vector, DefaultWorkingSpace)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the r, g, b components.
+ /// The rgb working space.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Rgb(Vector3 vector, IRgbWorkingSpace workingSpace)
+ : this()
+ {
+ // Clamp to 0-1 range.
+ this.backingVector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One);
+ this.WorkingSpace = workingSpace;
+ }
+
+ ///
+ /// Gets the red component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float R
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
+
+ ///
+ /// Gets the green component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float G
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
+
+ ///
+ /// Gets the blue component.
+ /// A value usually ranging between 0 and 1.
+ ///
+ public float B
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
+
+ ///
+ /// Gets the Rgb color space
+ ///
+ public IRgbWorkingSpace WorkingSpace
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get;
+ }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ public Vector3 Vector
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
+ }
+
+ ///
+ /// Allows the implicit conversion of an instance of to a
+ /// .
+ ///
+ ///
+ /// The instance of to convert.
+ ///
+ ///
+ /// An instance of .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Rgb(Rgba32 color)
+ {
+ return new Rgb(color.R / 255F, color.G / 255F, color.B / 255F);
+ }
+
+ ///
+ /// 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 ==(Rgb left, Rgb right)
+ {
+ return 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 !=(Rgb left, Rgb right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.backingVector.GetHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "Rgb [ Empty ]";
+ }
+
+ return $"Rgb [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##} ]";
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj)
+ {
+ if (obj is Rgb)
+ {
+ return this.Equals((Rgb)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(Rgb other)
+ {
+ return this.backingVector.Equals(other.backingVector);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool AlmostEquals(Rgb other, float precision)
+ {
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs
new file mode 100644
index 000000000..20b937394
--- /dev/null
+++ b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs
@@ -0,0 +1,117 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+// ReSharper disable InconsistentNaming
+namespace ImageSharp.ColorSpaces
+{
+ using ImageSharp.ColorSpaces.Conversion.Implementation.Rgb;
+
+ ///
+ /// Chromaticity coordinates taken from:
+ ///
+ ///
+ internal static class RgbWorkingSpaces
+ {
+ ///
+ /// sRgb working space.
+ ///
+ ///
+ /// Uses proper companding function, according to:
+ ///
+ ///
+ public static readonly IRgbWorkingSpace SRgb = new RgbWorkingSpace(Illuminants.D65, new SRgbCompanding(), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
+
+ ///
+ /// Simplified sRgb working space (uses gamma companding instead of ).
+ /// See also .
+ ///
+ public static readonly IRgbWorkingSpace SRgbSimplified = new RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
+
+ ///
+ /// Rec. 709 (ITU-R Recommendation BT.709) working space
+ ///
+ public static readonly IRgbWorkingSpace Rec709 = new RgbWorkingSpace(Illuminants.D65, new Rec709Companding(), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.64F, 0.33F), new CieXyChromaticityCoordinates(0.30F, 0.60F), new CieXyChromaticityCoordinates(0.15F, 0.06F)));
+
+ ///
+ /// Rec. 2020 (ITU-R Recommendation BT.2020F) working space
+ ///
+ public static readonly IRgbWorkingSpace Rec2020 = new RgbWorkingSpace(Illuminants.D65, new Rec2020Companding(), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.708F, 0.292F), new CieXyChromaticityCoordinates(0.170F, 0.797F), new CieXyChromaticityCoordinates(0.131F, 0.046F)));
+
+ ///
+ /// ECI Rgb v2 working space
+ ///
+ public static readonly IRgbWorkingSpace ECIRgbv2 = new RgbWorkingSpace(Illuminants.D50, new LCompanding(), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F)));
+
+ ///
+ /// Adobe Rgb (1998) working space
+ ///
+ public static readonly IRgbWorkingSpace AdobeRgb1998 = new RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
+
+ ///
+ /// Apple sRgb working space
+ ///
+ public static readonly IRgbWorkingSpace ApplesRgb = new RgbWorkingSpace(Illuminants.D65, new GammaCompanding(1.8F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6250F, 0.3400F), new CieXyChromaticityCoordinates(0.2800F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F)));
+
+ ///
+ /// Best Rgb working space
+ ///
+ public static readonly IRgbWorkingSpace BestRgb = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.2150F, 0.7750F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F)));
+
+ ///
+ /// Beta Rgb working space
+ ///
+ public static readonly IRgbWorkingSpace BetaRgb = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6888F, 0.3112F), new CieXyChromaticityCoordinates(0.1986F, 0.7551F), new CieXyChromaticityCoordinates(0.1265F, 0.0352F)));
+
+ ///
+ /// Bruce Rgb working space
+ ///
+ public static readonly IRgbWorkingSpace BruceRgb = new RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2800F, 0.6500F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
+
+ ///
+ /// CIE Rgb working space
+ ///
+ public static readonly IRgbWorkingSpace CIERgb = new RgbWorkingSpace(Illuminants.E, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.2740F, 0.7170F), new CieXyChromaticityCoordinates(0.1670F, 0.0090F)));
+
+ ///
+ /// ColorMatch Rgb working space
+ ///
+ public static readonly IRgbWorkingSpace ColorMatchRgb = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(1.8F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.2950F, 0.6050F), new CieXyChromaticityCoordinates(0.1500F, 0.0750F)));
+
+ ///
+ /// Don Rgb 4 working space
+ ///
+ public static readonly IRgbWorkingSpace DonRgb4 = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6960F, 0.3000F), new CieXyChromaticityCoordinates(0.2150F, 0.7650F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F)));
+
+ ///
+ /// Ekta Space PS5 working space
+ ///
+ public static readonly IRgbWorkingSpace EktaSpacePS5 = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6950F, 0.3050F), new CieXyChromaticityCoordinates(0.2600F, 0.7000F), new CieXyChromaticityCoordinates(0.1100F, 0.0050F)));
+
+ ///
+ /// NTSC Rgb working space
+ ///
+ public static readonly IRgbWorkingSpace NTSCRgb = new RgbWorkingSpace(Illuminants.C, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F)));
+
+ ///
+ /// PAL/SECAM Rgb working space
+ ///
+ public static readonly IRgbWorkingSpace PALSECAMRgb = new RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2900F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F)));
+
+ ///
+ /// ProPhoto Rgb working space
+ ///
+ public static readonly IRgbWorkingSpace ProPhotoRgb = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(1.8F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.1596F, 0.8404F), new CieXyChromaticityCoordinates(0.0366F, 0.0001F)));
+
+ ///
+ /// SMPTE-C Rgb working space
+ ///
+ public static readonly IRgbWorkingSpace SMPTECRgb = new RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.3100F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F)));
+
+ ///
+ /// Wide Gamut Rgb working space
+ ///
+ public static readonly IRgbWorkingSpace WideGamutRgb = new RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.1150F, 0.8260F), new CieXyChromaticityCoordinates(0.1570F, 0.0180F)));
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/YCbCr.cs b/src/ImageSharp/ColorSpaces/YCbCr.cs
similarity index 66%
rename from src/ImageSharp/Colors/Spaces/YCbCr.cs
rename to src/ImageSharp/ColorSpaces/YCbCr.cs
index 06696af9e..cbba02305 100644
--- a/src/ImageSharp/Colors/Spaces/YCbCr.cs
+++ b/src/ImageSharp/ColorSpaces/YCbCr.cs
@@ -3,33 +3,29 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageSharp.Colors.Spaces
+namespace ImageSharp.ColorSpaces
{
using System;
using System.ComponentModel;
using System.Numerics;
- using ImageSharp.PixelFormats;
+ using System.Runtime.CompilerServices;
///
- /// Represents an YCbCr (luminance, blue chroma, red chroma) color conforming to the full range standard used in digital imaging systems.
+ /// Represents an YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification for the JFIF use with Jpeg.
///
+ ///
///
- public struct YCbCr : IEquatable
+ internal struct YCbCr : IColorVector, IEquatable, IAlmostEquatable
{
///
/// Represents a that has Y, Cb, and Cr values set to zero.
///
public static readonly YCbCr Empty = default(YCbCr);
- ///
- /// Min range used for clamping
- ///
- private static readonly Vector3 VectorMin = Vector3.Zero;
-
///
/// Vector which is used in clamping to the max value
///
- private static readonly Vector3 VectorMax = new Vector3(255);
+ private static readonly Vector3 VectorMax = new Vector3(255F);
///
/// The backing vector for SIMD support.
@@ -42,29 +38,51 @@ namespace ImageSharp.Colors.Spaces
/// The y luminance component.
/// The cb chroma component.
/// The cr chroma component.
- public YCbCr(byte y, byte cb, byte cr)
- : this()
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public YCbCr(float y, float cb, float cr)
+ : this(new Vector3(y, cb, cr))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The vector representing the y, cb, cr components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public YCbCr(Vector3 vector)
{
- this.backingVector = Vector3.Clamp(new Vector3(y, cb, cr), VectorMin, VectorMax);
+ this.backingVector = Vector3.Clamp(vector, Vector3.Zero, VectorMax);
}
///
/// Gets the Y luminance component.
/// A value ranging between 0 and 255.
///
- public byte Y => (byte)this.backingVector.X;
+ public float Y
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.X;
+ }
///
/// Gets the Cb chroma component.
/// A value ranging between 0 and 255.
///
- public byte Cb => (byte)this.backingVector.Y;
+ public float Cb
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Y;
+ }
///
/// Gets the Cr chroma component.
/// A value ranging between 0 and 255.
///
- public byte Cr => (byte)this.backingVector.Z;
+ public float Cr
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector.Z;
+ }
///
/// Gets a value indicating whether this is empty.
@@ -72,27 +90,11 @@ namespace ImageSharp.Colors.Spaces
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- ///
- /// The instance of to convert.
- ///
- ///
- /// An instance of .
- ///
- public static implicit operator YCbCr(Rgba32 color)
+ ///
+ public Vector3 Vector
{
- byte r = color.R;
- byte g = color.G;
- byte b = color.B;
-
- byte y = (byte)((0.299F * r) + (0.587F * g) + (0.114F * b));
- byte cb = (byte)(128 + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)));
- byte cr = (byte)(128 + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)));
-
- return new YCbCr(y, cb, cr);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.backingVector;
}
///
@@ -107,6 +109,7 @@ namespace ImageSharp.Colors.Spaces
///
/// True if the current left is equal to the parameter; otherwise, false.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(YCbCr left, YCbCr right)
{
return left.Equals(right);
@@ -124,6 +127,7 @@ namespace ImageSharp.Colors.Spaces
///
/// True if the current left is unequal to the parameter; otherwise, false.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(YCbCr left, YCbCr right)
{
return !left.Equals(right);
@@ -147,6 +151,7 @@ namespace ImageSharp.Colors.Spaces
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is YCbCr)
@@ -158,9 +163,21 @@ namespace ImageSharp.Colors.Spaces
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(YCbCr other)
{
return this.backingVector.Equals(other.backingVector);
}
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool AlmostEquals(YCbCr other, float precision)
+ {
+ var result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X <= precision
+ && result.Y <= precision
+ && result.Z <= precision;
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Colors/Spaces/Bgra32.cs b/src/ImageSharp/Colors/Spaces/Bgra32.cs
deleted file mode 100644
index b1f72033d..000000000
--- a/src/ImageSharp/Colors/Spaces/Bgra32.cs
+++ /dev/null
@@ -1,167 +0,0 @@
-//
-// Copyright (c) James Jackson-South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-
-namespace ImageSharp.Colors.Spaces
-{
- using System;
- using System.ComponentModel;
- using System.Numerics;
- using ImageSharp.PixelFormats;
-
- ///
- /// Represents an BGRA (blue, green, red, alpha) color.
- ///
- public struct Bgra32 : IEquatable
- {
- ///
- /// Represents a 32 bit that has B, G, R, and A values set to zero.
- ///
- public static readonly Bgra32 Empty = default(Bgra32);
-
- ///
- /// Min range used for clamping
- ///
- private static readonly Vector4 VectorMin = Vector4.Zero;
-
- ///
- /// Max range used for clamping
- ///
- private static readonly Vector4 VectorMax = new Vector4(255);
-
- ///
- /// The backing vector for SIMD support.
- ///
- private readonly Vector4 backingVector;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The blue component of this .
- /// The green component of this .
- /// The red component of this .
- /// The alpha component of this .
- public Bgra32(byte b, byte g, byte r, byte a = 255)
- : this()
- {
- this.backingVector = Vector4.Clamp(new Vector4(b, g, r, a), VectorMin, VectorMax);
- }
-
- ///
- /// Gets the blue component of the color
- ///
- public byte B => (byte)this.backingVector.X;
-
- ///
- /// Gets the green component of the color
- ///
- public byte G => (byte)this.backingVector.Y;
-
- ///
- /// Gets the red component of the color
- ///
- public byte R => (byte)this.backingVector.Z;
-
- ///
- /// Gets the alpha component of the color
- ///
- public byte A => (byte)this.backingVector.W;
-
- ///
- /// Gets the integer representation of the color.
- ///
- public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24);
-
- ///
- /// Gets a value indicating whether this is empty.
- ///
- [EditorBrowsable(EditorBrowsableState.Never)]
- public bool IsEmpty => this.Equals(Empty);
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- ///
- /// The instance of to convert.
- ///
- ///
- /// An instance of .
- ///
- public static implicit operator Bgra32(Rgba32 color)
- {
- return new Bgra32(color.B, color.G, color.R, color.A);
- }
-
- ///
- /// 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 ==(Bgra32 left, Bgra32 right)
- {
- return 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 !=(Bgra32 left, Bgra32 right)
- {
- return !left.Equals(right);
- }
-
- ///
- public override bool Equals(object obj)
- {
- if (obj is Bgra32)
- {
- Bgra32 color = (Bgra32)obj;
-
- return this.backingVector == color.backingVector;
- }
-
- return false;
- }
-
- ///
- public override int GetHashCode()
- {
- return this.backingVector.GetHashCode();
- }
-
- ///
- public override string ToString()
- {
- if (this.IsEmpty)
- {
- return "Bgra32 [ Empty ]";
- }
-
- return $"Bgra32 [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]";
- }
-
- ///
- public bool Equals(Bgra32 other)
- {
- return this.backingVector.Equals(other.backingVector);
- }
- }
-}
diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs
index 0bfcec361..6c8c62039 100644
--- a/src/ImageSharp/Common/Helpers/ImageMaths.cs
+++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs
@@ -102,19 +102,6 @@ namespace ImageSharp
return 0F;
}
- ///
- /// Returns the given degrees converted to radians.
- ///
- /// The angle in degrees.
- ///
- /// The representing the degree as radians.
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float DegreesToRadians(float degrees)
- {
- return degrees * (MathF.PI / 180);
- }
-
///
/// Gets the bounding from the given points.
///
diff --git a/src/ImageSharp/Common/Helpers/MathF.cs b/src/ImageSharp/Common/Helpers/MathF.cs
index 1877fe8af..5ce5b5d5d 100644
--- a/src/ImageSharp/Common/Helpers/MathF.cs
+++ b/src/ImageSharp/Common/Helpers/MathF.cs
@@ -19,28 +19,89 @@ namespace ImageSharp
///
public const float PI = (float)Math.PI;
- /// Returns the absolute value of a single-precision floating-point number.
- /// A number that is greater than or equal to , but less than or equal to .
- /// A single-precision floating-point number, x, such that 0 ≤ x ≤.
+ ///
+ /// Returns the absolute value of a single-precision floating-point number.
+ ///
+ ///
+ /// A number that is greater than or equal to , but less than or equal to .
+ ///
+ ///
+ /// A single-precision floating-point number, x, such that 0 ≤ x ≤.
+ ///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Abs(float f)
{
return Math.Abs(f);
}
- /// Returns the smallest integral value that is greater than or equal to the specified single-precision floating-point number.
- /// A single-precision floating-point number.
- /// The smallest integral value that is greater than or equal to .
+ ///
+ /// Returns the angle whose tangent is the quotient of two specified numbers.
+ ///
+ /// The y coordinate of a point.
+ /// The x coordinate of a point.
+ ///
+ /// An angle, θ, measured in radians, such that -π≤θ≤π, and tan(θ) = y / x, where
+ /// (x, y) is a point in the Cartesian plane. Observe the following: For (x, y) in
+ /// quadrant 1, 0 < θ < π/2.For (x, y) in quadrant 2, π/2 < θ≤π.For (x, y) in quadrant
+ /// 3, -π < θ < -π/2.For (x, y) in quadrant 4, -π/2 < θ < 0.For points on the boundaries
+ /// of the quadrants, the return value is the following:If y is 0 and x is not negative,
+ /// θ = 0.If y is 0 and x is negative, θ = π.If y is positive and x is 0, θ = π/2.If
+ /// y is negative and x is 0, θ = -π/2.If y is 0 and x is 0, θ = 0. If x or y is
+ /// , or if x and y are either or
+ /// , the method returns .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Atan2(float y, float x)
+ {
+ return (float)Math.Atan2(y, x);
+ }
+
+ ///
+ /// Returns the smallest integral value that is greater than or equal to the specified single-precision floating-point number.
+ ///
+ /// A single-precision floating-point number.
+ ///
+ /// The smallest integral value that is greater than or equal to .
/// If is equal to , ,
/// or , that value is returned.
- /// Note that this method returns a instead of an integral type.
+ /// Note that this method returns a instead of an integral type.
+ ///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Ceiling(float f)
{
return (float)Math.Ceiling(f);
}
- /// Returns e raised to the specified power.
+ ///
+ /// Returns the cosine of the specified angle.
+ ///
+ /// An angle, measured in radians.
+ ///
+ /// The cosine of . If is equal to , ,
+ /// or , this method returns .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Cos(float f)
+ {
+ return (float)Math.Cos(f);
+ }
+
+ ///
+ /// Converts a degree (360-periodic) angle to a radian (2*Pi-periodic) angle.
+ ///
+ /// The angle in degrees.
+ ///
+ /// The representing the degree as radians.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float DegreeToRadian(float degree)
+ {
+ return degree * (PI / 180F);
+ }
+
+ ///
+ /// Returns e raised to the specified power.
+ ///
/// A number specifying a power.
///
/// The number e raised to the power .
@@ -53,9 +114,12 @@ namespace ImageSharp
return (float)Math.Exp(f);
}
- /// Returns the largest integer less than or equal to the specified single-precision floating-point number.
+ ///
+ /// Returns the largest integer less than or equal to the specified single-precision floating-point number.
+ ///
/// A single-precision floating-point number.
- /// The largest integer less than or equal to .
+ ///
+ /// The largest integer less than or equal to .
/// If is equal to , ,
/// or , that value is returned.
///
@@ -65,10 +129,13 @@ namespace ImageSharp
return (float)Math.Floor(f);
}
- /// Returns the larger of two single-precision floating-point numbers.
+ ///
+ /// Returns the larger of two single-precision floating-point numbers.
+ ///
/// The first of two single-precision floating-point numbers to compare.
/// The second of two single-precision floating-point numbers to compare.
- /// Parameter or , whichever is larger.
+ ///
+ /// Parameter or , whichever is larger.
/// If , or , or both and are
/// equal to , is returned.
///
@@ -78,19 +145,25 @@ namespace ImageSharp
return Math.Max(val1, val2);
}
- /// Returns the smaller of two single-precision floating-point numbers.
+ ///
+ /// Returns the smaller of two single-precision floating-point numbers.
+ ///
/// The first of two single-precision floating-point numbers to compare.
/// The second of two single-precision floating-point numbers to compare.
- /// Parameter or , whichever is smaller.
+ ///
+ /// Parameter or , whichever is smaller.
/// If , , or both and are equal
- /// to , is returned.
+ /// to , is returned.
+ ///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Min(float val1, float val2)
{
return Math.Min(val1, val2);
}
- /// Returns a specified number raised to the specified power.
+ ///
+ /// Returns a specified number raised to the specified power.
+ ///
/// A single-precision floating-point number to be raised to a power.
/// A single-precision floating-point number that specifies a power.
/// The number raised to the power .
@@ -100,8 +173,23 @@ namespace ImageSharp
return (float)Math.Pow(x, y);
}
- /// Rounds a single-precision floating-point value to the nearest integral value.
- /// A single-precision floating-point number to be rounded.
+ ///
+ /// Converts a radian (2*Pi-periodic) angle to a degree (360-periodic) angle.
+ ///
+ /// The angle in radians.
+ ///
+ /// The representing the degree as radians.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float RadianToDegree(float radian)
+ {
+ return radian / (PI / 180F);
+ }
+
+ ///
+ /// Rounds a single-precision floating-point value to the nearest integral value.
+ ///
+ /// A single-precision floating-point number to be rounded.
///
/// The integer nearest .
/// If the fractional component of is halfway between two integers, one of which is even and the other odd, then the even number is returned.
@@ -113,8 +201,29 @@ namespace ImageSharp
return (float)Math.Round(f);
}
- /// Returns the sine of the specified angle.
- /// An angle, measured in radians.
+ ///
+ /// Rounds a single-precision floating-point value to the nearest integer.
+ /// A parameter specifies how to round the value if it is midway between two numbers.
+ ///
+ /// A single-precision floating-point number to be rounded.
+ /// Specification for how to round if it is midway between two other numbers.
+ ///
+ /// The integer nearest . If is halfway between two integers, one of which is even
+ /// and the other odd, then determines which of the two is returned.
+ /// Note that this method returns a instead of an integral type.
+ ///
+ ///
+ /// is not a valid value of .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float Round(float f, MidpointRounding mode)
+ {
+ return (float)Math.Round(f, mode);
+ }
+
+ ///
+ /// Returns the sine of the specified angle.
+ ///
+ /// An angle, measured in radians.
///
/// The sine of .
/// If is equal to , ,
@@ -146,8 +255,10 @@ namespace ImageSharp
return 1F;
}
- /// Returns the square root of a specified number.
- /// The number whose square root is to be found.
+ ///
+ /// Returns the square root of a specified number.
+ ///
+ /// The number whose square root is to be found.
///
/// One of the values in the following table.
/// parameter Return value Zero or positive The positive square root of .
diff --git a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs
index 74f9a3c07..dcda39842 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs
@@ -200,6 +200,11 @@ namespace ImageSharp.Formats
///
public const byte APP1 = 0xe1;
+ ///
+ /// Application specific marker for marking where to store ICC profile information.
+ ///
+ public const byte APP2 = 0xe2;
+
///
/// Application specific marker used by Adobe for storing encoding information for DCT filters.
///
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 9d9ecacfe..13c51db24 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -6,6 +6,7 @@
namespace ImageSharp.Formats
{
using System;
+ using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
@@ -413,6 +414,9 @@ namespace ImageSharp.Formats
case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(remaining, metadata);
break;
+ case JpegConstants.Markers.APP2:
+ this.ProcessApp2Marker(remaining, metadata);
+ break;
case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(remaining);
break;
@@ -949,14 +953,64 @@ namespace ImageSharp.Formats
byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining);
- if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0'
- && profile[5] == '\0')
+ if (profile[0] == 'E' &&
+ profile[1] == 'x' &&
+ profile[2] == 'i' &&
+ profile[3] == 'f' &&
+ profile[4] == '\0' &&
+ profile[5] == '\0')
{
this.isExif = true;
metadata.ExifProfile = new ExifProfile(profile);
}
}
+ ///
+ /// Processes the App2 marker retrieving any stored ICC profile information
+ ///
+ /// The remaining bytes in the segment block.
+ /// The image.
+ private void ProcessApp2Marker(int remaining, ImageMetaData metadata)
+ {
+ // Length is 14 though we only need to check 12.
+ const int Icclength = 14;
+ if (remaining < Icclength || this.options.IgnoreMetadata)
+ {
+ this.InputProcessor.Skip(remaining);
+ return;
+ }
+
+ byte[] identifier = new byte[Icclength];
+ this.InputProcessor.ReadFull(identifier, 0, Icclength);
+
+ if (identifier[0] == 'I' &&
+ identifier[1] == 'C' &&
+ identifier[2] == 'C' &&
+ identifier[3] == '_' &&
+ identifier[4] == 'P' &&
+ identifier[5] == 'R' &&
+ identifier[6] == 'O' &&
+ identifier[7] == 'F' &&
+ identifier[8] == 'I' &&
+ identifier[9] == 'L' &&
+ identifier[10] == 'E' &&
+ identifier[11] == '\0')
+ {
+ remaining -= Icclength;
+ byte[] profile = new byte[remaining];
+ this.InputProcessor.ReadFull(profile, 0, remaining);
+
+ if (metadata.IccProfile == null)
+ {
+ metadata.IccProfile = new IccProfile(profile);
+ }
+ else
+ {
+ metadata.IccProfile.Extend(profile);
+ }
+ }
+ }
+
///
/// Processes the application header containing the JFIF identifier plus extra data.
///
@@ -973,8 +1027,11 @@ namespace ImageSharp.Formats
remaining -= 13;
// TODO: We should be using constants for this.
- this.isJfif = this.Temp[0] == 'J' && this.Temp[1] == 'F' && this.Temp[2] == 'I' && this.Temp[3] == 'F'
- && this.Temp[4] == '\x00';
+ this.isJfif = this.Temp[0] == 'J' &&
+ this.Temp[1] == 'F' &&
+ this.Temp[2] == 'I' &&
+ this.Temp[3] == 'F' &&
+ this.Temp[4] == '\x00';
if (this.isJfif)
{
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index fd7062729..b65a56e73 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -6,7 +6,9 @@
namespace ImageSharp.Formats
{
using System.Buffers;
+ using System.Collections.Generic;
using System.IO;
+ using System.Linq;
using System.Runtime.CompilerServices;
using ImageSharp.Formats.Jpg;
using ImageSharp.Formats.Jpg.Components;
@@ -672,7 +674,7 @@ namespace ImageSharp.Formats
///
/// Thrown if the EXIF profile size exceeds the limit
///
- private void WriteProfile(ExifProfile exifProfile)
+ private void WriteExifProfile(ExifProfile exifProfile)
{
const int Max = 65533;
byte[] data = exifProfile?.ToByteArray();
@@ -697,6 +699,87 @@ namespace ImageSharp.Formats
this.outputStream.Write(data, 0, data.Length);
}
+ ///
+ /// Writes the ICC profile.
+ ///
+ /// The ICC profile to write.
+ ///
+ /// Thrown if any of the ICC profiles size exceeds the limit
+ ///
+ private void WriteIccProfile(IccProfile iccProfile)
+ {
+ // Just incase someone set the value to null by accident.
+ if (iccProfile == null)
+ {
+ return;
+ }
+
+ const int IccOverheadLength = 14;
+ const int Max = 65533;
+ const int MaxData = Max - IccOverheadLength;
+
+ byte[] data = iccProfile.ToByteArray();
+
+ if (data == null || data.Length == 0)
+ {
+ return;
+ }
+
+ // Calculate the number of markers we'll need, rounding up of course
+ int dataLength = data.Length;
+ int count = dataLength / MaxData;
+
+ if (count * MaxData != dataLength)
+ {
+ count++;
+ }
+
+ // Per spec, counting starts at 1.
+ int current = 1;
+ int offset = 0;
+
+ while (dataLength > 0)
+ {
+ int length = dataLength; // Number of bytes to write.
+
+ if (length > MaxData)
+ {
+ length = MaxData;
+ }
+
+ dataLength -= length;
+
+ this.buffer[0] = JpegConstants.Markers.XFF;
+ this.buffer[1] = JpegConstants.Markers.APP2; // Application Marker
+ int markerLength = length + 16;
+ this.buffer[2] = (byte)((markerLength >> 8) & 0xFF);
+ this.buffer[3] = (byte)(markerLength & 0xFF);
+
+ this.outputStream.Write(this.buffer, 0, 4);
+
+ this.buffer[0] = (byte)'I';
+ this.buffer[1] = (byte)'C';
+ this.buffer[2] = (byte)'C';
+ this.buffer[3] = (byte)'_';
+ this.buffer[4] = (byte)'P';
+ this.buffer[5] = (byte)'R';
+ this.buffer[6] = (byte)'O';
+ this.buffer[7] = (byte)'F';
+ this.buffer[8] = (byte)'I';
+ this.buffer[9] = (byte)'L';
+ this.buffer[10] = (byte)'E';
+ this.buffer[11] = 0x00;
+ this.buffer[12] = (byte)current; // The position within the collection.
+ this.buffer[13] = (byte)count; // The total number of profiles.
+
+ this.outputStream.Write(this.buffer, 0, IccOverheadLength);
+ this.outputStream.Write(data, offset, length);
+
+ current++;
+ offset += length;
+ }
+ }
+
///
/// Writes the metadata profiles to the image.
///
@@ -711,7 +794,8 @@ namespace ImageSharp.Formats
}
image.MetaData.SyncProfiles();
- this.WriteProfile(image.MetaData.ExifProfile);
+ this.WriteExifProfile(image.MetaData.ExifProfile);
+ this.WriteIccProfile(image.MetaData.IccProfile);
}
///
diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs
index 127ae720f..91ea9ac4b 100644
--- a/src/ImageSharp/MetaData/ImageMetaData.cs
+++ b/src/ImageSharp/MetaData/ImageMetaData.cs
@@ -5,7 +5,6 @@
namespace ImageSharp
{
- using System;
using System.Collections.Generic;
using ImageSharp.Formats;
@@ -61,27 +60,23 @@ namespace ImageSharp
this.Properties.Add(new ImageProperty(property));
}
- if (other.ExifProfile != null)
- {
- this.ExifProfile = new ExifProfile(other.ExifProfile);
- }
- else
- {
- this.ExifProfile = null;
- }
+ this.ExifProfile = other.ExifProfile != null
+ ? new ExifProfile(other.ExifProfile)
+ : null;
+
+ this.IccProfile = other.IccProfile != null
+ ? new IccProfile(other.IccProfile)
+ : null;
}
///
- /// Gets or sets the resolution of the image in x- direction. It is defined as
- /// number of dots per inch and should be an positive value.
+ /// Gets or sets the resolution of the image in x- direction.
+ /// It is defined as the number of dots per inch and should be an positive value.
///
/// The density of the image in x- direction.
public double HorizontalResolution
{
- get
- {
- return this.horizontalResolution;
- }
+ get => this.horizontalResolution;
set
{
@@ -93,16 +88,13 @@ namespace ImageSharp
}
///
- /// Gets or sets the resolution of the image in y- direction. It is defined as
- /// number of dots per inch and should be an positive value.
+ /// Gets or sets the resolution of the image in y- direction.
+ /// It is defined as the number of dots per inch and should be an positive value.
///
/// The density of the image in y- direction.
public double VerticalResolution
{
- get
- {
- return this.verticalResolution;
- }
+ get => this.verticalResolution;
set
{
@@ -118,6 +110,11 @@ namespace ImageSharp
///
public ExifProfile ExifProfile { get; set; }
+ ///
+ /// Gets or sets the list of ICC profiles.
+ ///
+ public IccProfile IccProfile { get; set; }
+
///
public int FrameDelay { get; set; }
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs
new file mode 100644
index 000000000..f34f006e7
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccCurveSegment.cs
@@ -0,0 +1,45 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// A segment of a curve
+ ///
+ internal abstract class IccCurveSegment : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The signature of this segment
+ protected IccCurveSegment(IccCurveSegmentSignature signature)
+ {
+ this.Signature = signature;
+ }
+
+ ///
+ /// Gets the signature of this segment
+ ///
+ public IccCurveSegmentSignature Signature { get; }
+
+ ///
+ public virtual bool Equals(IccCurveSegment other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Signature == other.Signature;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs
new file mode 100644
index 000000000..ad03c8ff8
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccFormulaCurveElement.cs
@@ -0,0 +1,95 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// A formula based curve segment
+ ///
+ internal sealed class IccFormulaCurveElement : IccCurveSegment, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type of this segment
+ /// Gamma segment parameter
+ /// A segment parameter
+ /// B segment parameter
+ /// C segment parameter
+ /// D segment parameter
+ /// E segment parameter
+ public IccFormulaCurveElement(IccFormulaCurveType type, float gamma, float a, float b, float c, float d, float e)
+ : base(IccCurveSegmentSignature.FormulaCurve)
+ {
+ this.Type = type;
+ this.Gamma = gamma;
+ this.A = a;
+ this.B = b;
+ this.C = c;
+ this.D = d;
+ this.E = e;
+ }
+
+ ///
+ /// Gets the type of this curve
+ ///
+ public IccFormulaCurveType Type { get; }
+
+ ///
+ /// Gets the gamma curve parameter
+ ///
+ public float Gamma { get; }
+
+ ///
+ /// Gets the A curve parameter
+ ///
+ public float A { get; }
+
+ ///
+ /// Gets the B curve parameter
+ ///
+ public float B { get; }
+
+ ///
+ /// Gets the C curve parameter
+ ///
+ public float C { get; }
+
+ ///
+ /// Gets the D curve parameter
+ ///
+ public float D { get; }
+
+ ///
+ /// Gets the E curve parameter
+ ///
+ public float E { get; }
+
+ ///
+ public override bool Equals(IccCurveSegment other)
+ {
+ if (base.Equals(other) && other is IccFormulaCurveElement segment)
+ {
+ return this.Type == segment.Type
+ && this.Gamma == segment.Gamma
+ && this.A == segment.A
+ && this.B == segment.B
+ && this.C == segment.C
+ && this.D == segment.D
+ && this.E == segment.E;
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccFormulaCurveElement other)
+ {
+ return this.Equals((IccCurveSegment)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs
new file mode 100644
index 000000000..3f6497ccc
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccOneDimensionalCurve.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// A one dimensional curve
+ ///
+ internal sealed class IccOneDimensionalCurve : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The break points of this curve
+ /// The segments of this curve
+ public IccOneDimensionalCurve(float[] breakPoints, IccCurveSegment[] segments)
+ {
+ Guard.NotNull(breakPoints, nameof(breakPoints));
+ Guard.NotNull(segments, nameof(segments));
+
+ bool isSizeCorrect = breakPoints.Length == segments.Length - 1;
+ Guard.IsTrue(isSizeCorrect, $"{nameof(breakPoints)},{nameof(segments)}", "Number of BreakPoints must be one less than number of Segments");
+
+ this.BreakPoints = breakPoints;
+ this.Segments = segments;
+ }
+
+ ///
+ /// Gets the breakpoints that separate two curve segments
+ ///
+ public float[] BreakPoints { get; }
+
+ ///
+ /// Gets an array of curve segments
+ ///
+ public IccCurveSegment[] Segments { get; }
+
+ ///
+ public bool Equals(IccOneDimensionalCurve other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.BreakPoints.SequenceEqual(other.BreakPoints)
+ && this.Segments.SequenceEqual(other.Segments);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs
new file mode 100644
index 000000000..b36185e13
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccParametricCurve.cs
@@ -0,0 +1,150 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// A parametric curve
+ ///
+ internal sealed class IccParametricCurve : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// G curve parameter
+ public IccParametricCurve(float g)
+ : this(IccParametricCurveType.Type1, g, 0, 0, 0, 0, 0, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// G curve parameter
+ /// A curve parameter
+ /// B curve parameter
+ public IccParametricCurve(float g, float a, float b)
+ : this(IccParametricCurveType.Cie122_1996, g, a, b, 0, 0, 0, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// G curve parameter
+ /// A curve parameter
+ /// B curve parameter
+ /// C curve parameter
+ public IccParametricCurve(float g, float a, float b, float c)
+ : this(IccParametricCurveType.Iec61966_3, g, a, b, c, 0, 0, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// G curve parameter
+ /// A curve parameter
+ /// B curve parameter
+ /// C curve parameter
+ /// D curve parameter
+ public IccParametricCurve(float g, float a, float b, float c, float d)
+ : this(IccParametricCurveType.SRgb, g, a, b, c, d, 0, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// G curve parameter
+ /// A curve parameter
+ /// B curve parameter
+ /// C curve parameter
+ /// D curve parameter
+ /// E curve parameter
+ /// F curve parameter
+ public IccParametricCurve(float g, float a, float b, float c, float d, float e, float f)
+ : this(IccParametricCurveType.Type5, g, a, b, c, d, e, f)
+ {
+ }
+
+ private IccParametricCurve(IccParametricCurveType type, float g, float a, float b, float c, float d, float e, float f)
+ {
+ this.Type = type;
+ this.G = g;
+ this.A = a;
+ this.B = b;
+ this.C = c;
+ this.D = d;
+ this.E = e;
+ this.F = f;
+ }
+
+ ///
+ /// Gets the type of this curve
+ ///
+ public IccParametricCurveType Type { get; }
+
+ ///
+ /// Gets the G curve parameter
+ ///
+ public float G { get; }
+
+ ///
+ /// Gets the A curve parameter
+ ///
+ public float A { get; }
+
+ ///
+ /// Gets the B curve parameter
+ ///
+ public float B { get; }
+
+ ///
+ /// Gets the C curve parameter
+ ///
+ public float C { get; }
+
+ ///
+ /// Gets the D curve parameter
+ ///
+ public float D { get; }
+
+ ///
+ /// Gets the E curve parameter
+ ///
+ public float E { get; }
+
+ ///
+ /// Gets the F curve parameter
+ ///
+ public float F { get; }
+
+ ///
+ public bool Equals(IccParametricCurve other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Type == other.Type
+ && this.G == other.G
+ && this.A == other.A
+ && this.B == other.B
+ && this.C == other.C
+ && this.D == other.D
+ && this.E == other.E
+ && this.F == other.F;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs
new file mode 100644
index 000000000..346be9c42
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccResponseCurve.cs
@@ -0,0 +1,87 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+ using System.Numerics;
+
+ ///
+ /// A response curve
+ ///
+ internal sealed class IccResponseCurve : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type of this curve
+ /// The XYZ values
+ /// The response arrays
+ public IccResponseCurve(IccCurveMeasurementEncodings curveType, Vector3[] xyzValues, IccResponseNumber[][] responseArrays)
+ {
+ Guard.NotNull(xyzValues, nameof(xyzValues));
+ Guard.NotNull(responseArrays, nameof(responseArrays));
+
+ Guard.IsTrue(xyzValues.Length == responseArrays.Length, $"{nameof(xyzValues)},{nameof(responseArrays)}", "Arrays must have same length");
+ Guard.MustBeBetweenOrEqualTo(xyzValues.Length, 1, 15, nameof(xyzValues));
+
+ this.CurveType = curveType;
+ this.XyzValues = xyzValues;
+ this.ResponseArrays = responseArrays;
+ }
+
+ ///
+ /// Gets the type of this curve
+ ///
+ public IccCurveMeasurementEncodings CurveType { get; }
+
+ ///
+ /// Gets the XYZ values
+ ///
+ public Vector3[] XyzValues { get; }
+
+ ///
+ /// Gets the response arrays
+ ///
+ public IccResponseNumber[][] ResponseArrays { get; }
+
+ ///
+ public bool Equals(IccResponseCurve other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.CurveType == other.CurveType
+ && this.XyzValues.SequenceEqual(other.XyzValues)
+ && this.EqualsResponseArray(other);
+ }
+
+ private bool EqualsResponseArray(IccResponseCurve other)
+ {
+ if (this.ResponseArrays.Length != other.ResponseArrays.Length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < this.ResponseArrays.Length; i++)
+ {
+ if (!this.ResponseArrays[i].SequenceEqual(other.ResponseArrays[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs
new file mode 100644
index 000000000..859d43338
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Curves/IccSampledCurveElement.cs
@@ -0,0 +1,51 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// A sampled curve segment
+ ///
+ internal sealed class IccSampledCurveElement : IccCurveSegment, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The curve values of this segment
+ public IccSampledCurveElement(float[] curveEntries)
+ : base(IccCurveSegmentSignature.SampledCurve)
+ {
+ Guard.NotNull(curveEntries, nameof(curveEntries));
+ Guard.IsTrue(curveEntries.Length > 0, nameof(curveEntries), "There must be at least one value");
+
+ this.CurveEntries = curveEntries;
+ }
+
+ ///
+ /// Gets the curve values of this segment
+ ///
+ public float[] CurveEntries { get; }
+
+ ///
+ public override bool Equals(IccCurveSegment other)
+ {
+ if (base.Equals(other) && other is IccSampledCurveElement segment)
+ {
+ return this.CurveEntries.SequenceEqual(segment.CurveEntries);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccSampledCurveElement other)
+ {
+ return this.Equals((IccCurveSegment)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs
new file mode 100644
index 000000000..ba32586fe
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Curves.cs
@@ -0,0 +1,221 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Numerics;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// Reads a
+ ///
+ /// The read curve
+ public IccOneDimensionalCurve ReadOneDimensionalCurve()
+ {
+ ushort segmentCount = this.ReadUInt16();
+ this.AddIndex(2); // 2 bytes reserved
+ float[] breakPoints = new float[segmentCount - 1];
+ for (int i = 0; i < breakPoints.Length; i++)
+ {
+ breakPoints[i] = this.ReadSingle();
+ }
+
+ IccCurveSegment[] segments = new IccCurveSegment[segmentCount];
+ for (int i = 0; i < segmentCount; i++)
+ {
+ segments[i] = this.ReadCurveSegment();
+ }
+
+ return new IccOneDimensionalCurve(breakPoints, segments);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The number of channels
+ /// The read curve
+ public IccResponseCurve ReadResponseCurve(int channelCount)
+ {
+ var type = (IccCurveMeasurementEncodings)this.ReadUInt32();
+ uint[] measurment = new uint[channelCount];
+ for (int i = 0; i < channelCount; i++)
+ {
+ measurment[i] = this.ReadUInt32();
+ }
+
+ Vector3[] xyzValues = new Vector3[channelCount];
+ for (int i = 0; i < channelCount; i++)
+ {
+ xyzValues[i] = this.ReadXyzNumber();
+ }
+
+ IccResponseNumber[][] response = new IccResponseNumber[channelCount][];
+ for (int i = 0; i < channelCount; i++)
+ {
+ response[i] = new IccResponseNumber[measurment[i]];
+ for (uint j = 0; j < measurment[i]; j++)
+ {
+ response[i][j] = this.ReadResponseNumber();
+ }
+ }
+
+ return new IccResponseCurve(type, xyzValues, response);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read curve
+ public IccParametricCurve ReadParametricCurve()
+ {
+ ushort type = this.ReadUInt16();
+ this.AddIndex(2); // 2 bytes reserved
+ float gamma, a, b, c, d, e, f;
+ gamma = a = b = c = d = e = f = 0;
+
+ if (type <= 4)
+ {
+ gamma = this.ReadFix16();
+ }
+
+ if (type > 0 && type <= 4)
+ {
+ a = this.ReadFix16();
+ b = this.ReadFix16();
+ }
+
+ if (type > 1 && type <= 4)
+ {
+ c = this.ReadFix16();
+ }
+
+ if (type > 2 && type <= 4)
+ {
+ d = this.ReadFix16();
+ }
+
+ if (type == 4)
+ {
+ e = this.ReadFix16();
+ f = this.ReadFix16();
+ }
+
+ switch (type)
+ {
+ case 0: return new IccParametricCurve(gamma);
+ case 1: return new IccParametricCurve(gamma, a, b);
+ case 2: return new IccParametricCurve(gamma, a, b, c);
+ case 3: return new IccParametricCurve(gamma, a, b, c, d);
+ case 4: return new IccParametricCurve(gamma, a, b, c, d, e, f);
+ default: throw new InvalidIccProfileException($"Invalid parametric curve type of {type}");
+ }
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read segment
+ public IccCurveSegment ReadCurveSegment()
+ {
+ var signature = (IccCurveSegmentSignature)this.ReadUInt32();
+ this.AddIndex(4); // 4 bytes reserved
+
+ switch (signature)
+ {
+ case IccCurveSegmentSignature.FormulaCurve:
+ return this.ReadFormulaCurveElement();
+ case IccCurveSegmentSignature.SampledCurve:
+ return this.ReadSampledCurveElement();
+ default:
+ throw new InvalidIccProfileException($"Invalid curve segment type of {signature}");
+ }
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read segment
+ public IccFormulaCurveElement ReadFormulaCurveElement()
+ {
+ var type = (IccFormulaCurveType)this.ReadUInt16();
+ this.AddIndex(2); // 2 bytes reserved
+ float gamma, a, b, c, d, e;
+ gamma = d = e = 0;
+
+ if (type == IccFormulaCurveType.Type1 || type == IccFormulaCurveType.Type2)
+ {
+ gamma = this.ReadSingle();
+ }
+
+ a = this.ReadSingle();
+ b = this.ReadSingle();
+ c = this.ReadSingle();
+
+ if (type == IccFormulaCurveType.Type2 || type == IccFormulaCurveType.Type3)
+ {
+ d = this.ReadSingle();
+ }
+
+ if (type == IccFormulaCurveType.Type3)
+ {
+ e = this.ReadSingle();
+ }
+
+ return new IccFormulaCurveElement(type, gamma, a, b, c, d, e);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read segment
+ public IccSampledCurveElement ReadSampledCurveElement()
+ {
+ uint count = this.ReadUInt32();
+ float[] entries = new float[count];
+ for (int i = 0; i < count; i++)
+ {
+ entries[i] = this.ReadSingle();
+ }
+
+ return new IccSampledCurveElement(entries);
+ }
+
+ ///
+ /// Reads curve data
+ ///
+ /// Number of input channels
+ /// The curve data
+ private IccTagDataEntry[] ReadCurves(int count)
+ {
+ IccTagDataEntry[] tdata = new IccTagDataEntry[count];
+ for (int i = 0; i < count; i++)
+ {
+ IccTypeSignature type = this.ReadTagDataEntryHeader();
+ if (type != IccTypeSignature.Curve && type != IccTypeSignature.ParametricCurve)
+ {
+ throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" +
+ $" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries");
+ }
+
+ if (type == IccTypeSignature.Curve)
+ {
+ tdata[i] = this.ReadCurveTagDataEntry();
+ }
+ else if (type == IccTypeSignature.ParametricCurve)
+ {
+ tdata[i] = this.ReadParametricCurveTagDataEntry();
+ }
+
+ this.AddPadding();
+ }
+
+ return tdata;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs
new file mode 100644
index 000000000..a34ee42fc
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Lut.cs
@@ -0,0 +1,173 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// Reads an 8bit lookup table
+ ///
+ /// The read LUT
+ public IccLut ReadLut8()
+ {
+ return new IccLut(this.ReadBytes(256));
+ }
+
+ ///
+ /// Reads a 16bit lookup table
+ ///
+ /// The number of entries
+ /// The read LUT
+ public IccLut ReadLut16(int count)
+ {
+ ushort[] values = new ushort[count];
+ for (int i = 0; i < count; i++)
+ {
+ values[i] = this.ReadUInt16();
+ }
+
+ return new IccLut(values);
+ }
+
+ ///
+ /// Reads a CLUT depending on type
+ ///
+ /// Input channel count
+ /// Output channel count
+ /// If true, it's read as CLUTf32,
+ /// else read as either CLUT8 or CLUT16 depending on embedded information
+ /// The read CLUT
+ public IccClut ReadClut(int inChannelCount, int outChannelCount, bool isFloat)
+ {
+ // Grid-points are always 16 bytes long but only 0-inChCount are used
+ byte[] gridPointCount = new byte[inChannelCount];
+ Buffer.BlockCopy(this.data, this.AddIndex(16), gridPointCount, 0, inChannelCount);
+
+ if (!isFloat)
+ {
+ byte size = this.data[this.AddIndex(4)]; // First byte is info, last 3 bytes are reserved
+ if (size == 1)
+ {
+ return this.ReadClut8(inChannelCount, outChannelCount, gridPointCount);
+ }
+
+ if (size == 2)
+ {
+ return this.ReadClut16(inChannelCount, outChannelCount, gridPointCount);
+ }
+
+ throw new InvalidIccProfileException($"Invalid CLUT size of {size}");
+ }
+
+ return this.ReadClutF32(inChannelCount, outChannelCount, gridPointCount);
+ }
+
+ ///
+ /// Reads an 8 bit CLUT
+ ///
+ /// Input channel count
+ /// Output channel count
+ /// Grid point count for each CLUT channel
+ /// The read CLUT8
+ public IccClut ReadClut8(int inChannelCount, int outChannelCount, byte[] gridPointCount)
+ {
+ int start = this.currentIndex;
+ int length = 0;
+ for (int i = 0; i < inChannelCount; i++)
+ {
+ length += (int)Math.Pow(gridPointCount[i], inChannelCount);
+ }
+
+ length /= inChannelCount;
+
+ const float Max = byte.MaxValue;
+
+ float[][] values = new float[length][];
+ for (int i = 0; i < length; i++)
+ {
+ values[i] = new float[outChannelCount];
+ for (int j = 0; j < outChannelCount; j++)
+ {
+ values[i][j] = this.data[this.currentIndex++] / Max;
+ }
+ }
+
+ this.currentIndex = start + (length * outChannelCount);
+ return new IccClut(values, gridPointCount, IccClutDataType.UInt8);
+ }
+
+ ///
+ /// Reads a 16 bit CLUT
+ ///
+ /// Input channel count
+ /// Output channel count
+ /// Grid point count for each CLUT channel
+ /// The read CLUT16
+ public IccClut ReadClut16(int inChannelCount, int outChannelCount, byte[] gridPointCount)
+ {
+ int start = this.currentIndex;
+ int length = 0;
+ for (int i = 0; i < inChannelCount; i++)
+ {
+ length += (int)Math.Pow(gridPointCount[i], inChannelCount);
+ }
+
+ length /= inChannelCount;
+
+ const float Max = ushort.MaxValue;
+
+ float[][] values = new float[length][];
+ for (int i = 0; i < length; i++)
+ {
+ values[i] = new float[outChannelCount];
+ for (int j = 0; j < outChannelCount; j++)
+ {
+ values[i][j] = this.ReadUInt16() / Max;
+ }
+ }
+
+ this.currentIndex = start + (length * outChannelCount * 2);
+ return new IccClut(values, gridPointCount, IccClutDataType.UInt16);
+ }
+
+ ///
+ /// Reads a 32bit floating point CLUT
+ ///
+ /// Input channel count
+ /// Output channel count
+ /// Grid point count for each CLUT channel
+ /// The read CLUTf32
+ public IccClut ReadClutF32(int inChCount, int outChCount, byte[] gridPointCount)
+ {
+ int start = this.currentIndex;
+ int length = 0;
+ for (int i = 0; i < inChCount; i++)
+ {
+ length += (int)Math.Pow(gridPointCount[i], inChCount);
+ }
+
+ length /= inChCount;
+
+ float[][] values = new float[length][];
+ for (int i = 0; i < length; i++)
+ {
+ values[i] = new float[outChCount];
+ for (int j = 0; j < outChCount; j++)
+ {
+ values[i][j] = this.ReadSingle();
+ }
+ }
+
+ this.currentIndex = start + (length * outChCount * 4);
+ return new IccClut(values, gridPointCount, IccClutDataType.Float);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs
new file mode 100644
index 000000000..23ad9feb2
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Matrix.cs
@@ -0,0 +1,65 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// Reads a two dimensional matrix
+ ///
+ /// Number of values in X
+ /// Number of values in Y
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The read matrix
+ public float[,] ReadMatrix(int xCount, int yCount, bool isSingle)
+ {
+ float[,] matrix = new float[xCount, yCount];
+ for (int y = 0; y < yCount; y++)
+ {
+ for (int x = 0; x < xCount; x++)
+ {
+ if (isSingle)
+ {
+ matrix[x, y] = this.ReadSingle();
+ }
+ else
+ {
+ matrix[x, y] = this.ReadFix16();
+ }
+ }
+ }
+
+ return matrix;
+ }
+
+ ///
+ /// Reads a one dimensional matrix
+ ///
+ /// Number of values
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The read matrix
+ public float[] ReadMatrix(int yCount, bool isSingle)
+ {
+ float[] matrix = new float[yCount];
+ for (int i = 0; i < yCount; i++)
+ {
+ if (isSingle)
+ {
+ matrix[i] = this.ReadSingle();
+ }
+ else
+ {
+ matrix[i] = this.ReadFix16();
+ }
+ }
+
+ return matrix;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs
new file mode 100644
index 000000000..371c5c42a
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs
@@ -0,0 +1,87 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// Reads a
+ ///
+ /// The read
+ public IccMultiProcessElement ReadMultiProcessElement()
+ {
+ IccMultiProcessElementSignature signature = (IccMultiProcessElementSignature)this.ReadUInt32();
+ ushort inChannelCount = this.ReadUInt16();
+ ushort outChannelCount = this.ReadUInt16();
+
+ switch (signature)
+ {
+ case IccMultiProcessElementSignature.CurveSet:
+ return this.ReadCurveSetProcessElement(inChannelCount, outChannelCount);
+ case IccMultiProcessElementSignature.Matrix:
+ return this.ReadMatrixProcessElement(inChannelCount, outChannelCount);
+ case IccMultiProcessElementSignature.Clut:
+ return this.ReadClutProcessElement(inChannelCount, outChannelCount);
+
+ // Currently just placeholders for future ICC expansion
+ case IccMultiProcessElementSignature.BAcs:
+ this.AddIndex(8);
+ return new IccBAcsProcessElement(inChannelCount, outChannelCount);
+ case IccMultiProcessElementSignature.EAcs:
+ this.AddIndex(8);
+ return new IccEAcsProcessElement(inChannelCount, outChannelCount);
+
+ default:
+ throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {signature}");
+ }
+ }
+
+ ///
+ /// Reads a CurveSet
+ ///
+ /// Number of input channels
+ /// Number of output channels
+ /// The read
+ public IccCurveSetProcessElement ReadCurveSetProcessElement(int inChannelCount, int outChannelCount)
+ {
+ IccOneDimensionalCurve[] curves = new IccOneDimensionalCurve[inChannelCount];
+ for (int i = 0; i < inChannelCount; i++)
+ {
+ curves[i] = this.ReadOneDimensionalCurve();
+ this.AddPadding();
+ }
+
+ return new IccCurveSetProcessElement(curves);
+ }
+
+ ///
+ /// Reads a Matrix
+ ///
+ /// Number of input channels
+ /// Number of output channels
+ /// The read
+ public IccMatrixProcessElement ReadMatrixProcessElement(int inChannelCount, int outChannelCount)
+ {
+ return new IccMatrixProcessElement(
+ this.ReadMatrix(inChannelCount, outChannelCount, true),
+ this.ReadMatrix(outChannelCount, true));
+ }
+
+ ///
+ /// Reads a CLUT
+ ///
+ /// Number of input channels
+ /// Number of output channels
+ /// The read
+ public IccClutProcessElement ReadClutProcessElement(int inChannelCount, int outChannelCount)
+ {
+ return new IccClutProcessElement(this.ReadClut(inChannelCount, outChannelCount, true));
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs
new file mode 100644
index 000000000..44a892084
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs
@@ -0,0 +1,183 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Numerics;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// Reads a DateTime
+ ///
+ /// the value
+ public DateTime ReadDateTime()
+ {
+ try
+ {
+ return new DateTime(
+ year: this.ReadUInt16(),
+ month: this.ReadUInt16(),
+ day: this.ReadUInt16(),
+ hour: this.ReadUInt16(),
+ minute: this.ReadUInt16(),
+ second: this.ReadUInt16(),
+ kind: DateTimeKind.Utc);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ return DateTime.MinValue;
+ }
+ }
+
+ ///
+ /// Reads an ICC profile version number
+ ///
+ /// the version number
+ public Version ReadVersionNumber()
+ {
+ int version = this.ReadInt32();
+
+ int major = (version >> 24) & 0xFF;
+ int minor = (version >> 20) & 0x0F;
+ int bugfix = (version >> 16) & 0x0F;
+
+ return new Version(major, minor, bugfix);
+ }
+
+ ///
+ /// Reads an XYZ number
+ ///
+ /// the XYZ number
+ public Vector3 ReadXyzNumber()
+ {
+ return new Vector3(
+ x: this.ReadFix16(),
+ y: this.ReadFix16(),
+ z: this.ReadFix16());
+ }
+
+ ///
+ /// Reads a profile ID
+ ///
+ /// the profile ID
+ public IccProfileId ReadProfileId()
+ {
+ return new IccProfileId(
+ p1: this.ReadUInt32(),
+ p2: this.ReadUInt32(),
+ p3: this.ReadUInt32(),
+ p4: this.ReadUInt32());
+ }
+
+ ///
+ /// Reads a position number
+ ///
+ /// the position number
+ public IccPositionNumber ReadPositionNumber()
+ {
+ return new IccPositionNumber(
+ offset: this.ReadUInt32(),
+ size: this.ReadUInt32());
+ }
+
+ ///
+ /// Reads a response number
+ ///
+ /// the response number
+ public IccResponseNumber ReadResponseNumber()
+ {
+ return new IccResponseNumber(
+ deviceCode: this.ReadUInt16(),
+ measurementValue: this.ReadFix16());
+ }
+
+ ///
+ /// Reads a named color
+ ///
+ /// Number of device coordinates
+ /// the named color
+ public IccNamedColor ReadNamedColor(uint deviceCoordCount)
+ {
+ string name = this.ReadAsciiString(32);
+ ushort[] pcsCoord = { this.ReadUInt16(), this.ReadUInt16(), this.ReadUInt16() };
+ ushort[] deviceCoord = new ushort[deviceCoordCount];
+
+ for (int i = 0; i < deviceCoordCount; i++)
+ {
+ deviceCoord[i] = this.ReadUInt16();
+ }
+
+ return new IccNamedColor(name, pcsCoord, deviceCoord);
+ }
+
+ ///
+ /// Reads a profile description
+ ///
+ /// the profile description
+ public IccProfileDescription ReadProfileDescription()
+ {
+ uint manufacturer = this.ReadUInt32();
+ uint model = this.ReadUInt32();
+ var attributes = (IccDeviceAttribute)this.ReadInt64();
+ var technologyInfo = (IccProfileTag)this.ReadUInt32();
+
+ IccMultiLocalizedUnicodeTagDataEntry manufacturerInfo = ReadText();
+ IccMultiLocalizedUnicodeTagDataEntry modelInfo = ReadText();
+
+ return new IccProfileDescription(
+ manufacturer,
+ model,
+ attributes,
+ technologyInfo,
+ manufacturerInfo.Texts,
+ modelInfo.Texts);
+
+ IccMultiLocalizedUnicodeTagDataEntry ReadText()
+ {
+ IccTypeSignature type = this.ReadTagDataEntryHeader();
+ switch (type)
+ {
+ case IccTypeSignature.MultiLocalizedUnicode:
+ return this.ReadMultiLocalizedUnicodeTagDataEntry();
+ case IccTypeSignature.TextDescription:
+ return (IccMultiLocalizedUnicodeTagDataEntry)this.ReadTextDescriptionTagDataEntry();
+
+ default:
+ throw new InvalidIccProfileException("Profile description can only have multi-localized Unicode or text description entries");
+ }
+ }
+ }
+
+ ///
+ /// Reads a colorant table entry
+ ///
+ /// the profile description
+ public IccColorantTableEntry ReadColorantTableEntry()
+ {
+ return new IccColorantTableEntry(
+ name: this.ReadAsciiString(32),
+ pcs1: this.ReadUInt16(),
+ pcs2: this.ReadUInt16(),
+ pcs3: this.ReadUInt16());
+ }
+
+ ///
+ /// Reads a screening channel
+ ///
+ /// the screening channel
+ public IccScreeningChannel ReadScreeningChannel()
+ {
+ return new IccScreeningChannel(
+ frequency: this.ReadFix16(),
+ angle: this.ReadFix16(),
+ spotShape: (IccScreeningSpotType)this.ReadInt32());
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs
new file mode 100644
index 000000000..85d80c7a8
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs
@@ -0,0 +1,178 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Text;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// Reads an ushort
+ ///
+ /// the value
+ public ushort ReadUInt16()
+ {
+ return this.converter.ToUInt16(this.data, this.AddIndex(2));
+ }
+
+ ///
+ /// Reads a short
+ ///
+ /// the value
+ public short ReadInt16()
+ {
+ return this.converter.ToInt16(this.data, this.AddIndex(2));
+ }
+
+ ///
+ /// Reads an uint
+ ///
+ /// the value
+ public uint ReadUInt32()
+ {
+ return this.converter.ToUInt32(this.data, this.AddIndex(4));
+ }
+
+ ///
+ /// Reads an int
+ ///
+ /// the value
+ public int ReadInt32()
+ {
+ return this.converter.ToInt32(this.data, this.AddIndex(4));
+ }
+
+ ///
+ /// Reads an ulong
+ ///
+ /// the value
+ public ulong ReadUInt64()
+ {
+ return this.converter.ToUInt64(this.data, this.AddIndex(8));
+ }
+
+ ///
+ /// Reads a long
+ ///
+ /// the value
+ public long ReadInt64()
+ {
+ return this.converter.ToInt64(this.data, this.AddIndex(8));
+ }
+
+ ///
+ /// Reads a float
+ ///
+ /// the value
+ public float ReadSingle()
+ {
+ return this.converter.ToSingle(this.data, this.AddIndex(4));
+ }
+
+ ///
+ /// Reads a double
+ ///
+ /// the value
+ public double ReadDouble()
+ {
+ return this.converter.ToDouble(this.data, this.AddIndex(8));
+ }
+
+ ///
+ /// Reads an ASCII encoded string
+ ///
+ /// number of bytes to read
+ /// The value as a string
+ public string ReadAsciiString(int length)
+ {
+ if (length == 0)
+ {
+ return string.Empty;
+ }
+
+ Guard.MustBeGreaterThan(length, 0, nameof(length));
+ string value = AsciiEncoding.GetString(this.data, this.AddIndex(length), length);
+
+ // remove data after (potential) null terminator
+ int pos = value.IndexOf('\0');
+ if (pos >= 0)
+ {
+ value = value.Substring(0, pos);
+ }
+
+ return value;
+ }
+
+ ///
+ /// Reads an UTF-16 big-endian encoded string
+ ///
+ /// number of bytes to read
+ /// The value as a string
+ public string ReadUnicodeString(int length)
+ {
+ if (length == 0)
+ {
+ return string.Empty;
+ }
+
+ Guard.MustBeGreaterThan(length, 0, nameof(length));
+
+ return Encoding.BigEndianUnicode.GetString(this.data, this.AddIndex(length), length);
+ }
+
+ ///
+ /// Reads a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits
+ ///
+ /// The number as double
+ public float ReadFix16()
+ {
+ return this.ReadInt32() / 65536f;
+ }
+
+ ///
+ /// Reads an unsigned 32bit number with 16 value bits and 16 fractional bits
+ ///
+ /// The number as double
+ public float ReadUFix16()
+ {
+ return this.ReadUInt32() / 65536f;
+ }
+
+ ///
+ /// Reads an unsigned 16bit number with 1 value bit and 15 fractional bits
+ ///
+ /// The number as double
+ public float ReadU1Fix15()
+ {
+ return this.ReadUInt16() / 32768f;
+ }
+
+ ///
+ /// Reads an unsigned 16bit number with 8 value bits and 8 fractional bits
+ ///
+ /// The number as double
+ public float ReadUFix8()
+ {
+ return this.ReadUInt16() / 256f;
+ }
+
+ ///
+ /// Reads a number of bytes and advances the index
+ ///
+ /// The number of bytes to read
+ /// The read bytes
+ public byte[] ReadBytes(int count)
+ {
+ byte[] bytes = new byte[count];
+ Buffer.BlockCopy(this.data, this.AddIndex(count), bytes, 0, count);
+ return bytes;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs
new file mode 100644
index 000000000..1c130b54d
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs
@@ -0,0 +1,875 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Globalization;
+ using System.Numerics;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ ///
+ /// Reads a tag data entry
+ ///
+ /// The table entry with reading information
+ /// the tag data entry
+ public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info)
+ {
+ this.currentIndex = (int)info.Offset;
+ IccTypeSignature type = this.ReadTagDataEntryHeader();
+
+ switch (type)
+ {
+ case IccTypeSignature.Chromaticity:
+ return this.ReadChromaticityTagDataEntry();
+ case IccTypeSignature.ColorantOrder:
+ return this.ReadColorantOrderTagDataEntry();
+ case IccTypeSignature.ColorantTable:
+ return this.ReadColorantTableTagDataEntry();
+ case IccTypeSignature.Curve:
+ return this.ReadCurveTagDataEntry();
+ case IccTypeSignature.Data:
+ return this.ReadDataTagDataEntry(info.DataSize);
+ case IccTypeSignature.DateTime:
+ return this.ReadDateTimeTagDataEntry();
+ case IccTypeSignature.Lut16:
+ return this.ReadLut16TagDataEntry();
+ case IccTypeSignature.Lut8:
+ return this.ReadLut8TagDataEntry();
+ case IccTypeSignature.LutAToB:
+ return this.ReadLutAtoBTagDataEntry();
+ case IccTypeSignature.LutBToA:
+ return this.ReadLutBtoATagDataEntry();
+ case IccTypeSignature.Measurement:
+ return this.ReadMeasurementTagDataEntry();
+ case IccTypeSignature.MultiLocalizedUnicode:
+ return this.ReadMultiLocalizedUnicodeTagDataEntry();
+ case IccTypeSignature.MultiProcessElements:
+ return this.ReadMultiProcessElementsTagDataEntry();
+ case IccTypeSignature.NamedColor2:
+ return this.ReadNamedColor2TagDataEntry();
+ case IccTypeSignature.ParametricCurve:
+ return this.ReadParametricCurveTagDataEntry();
+ case IccTypeSignature.ProfileSequenceDesc:
+ return this.ReadProfileSequenceDescTagDataEntry();
+ case IccTypeSignature.ProfileSequenceIdentifier:
+ return this.ReadProfileSequenceIdentifierTagDataEntry();
+ case IccTypeSignature.ResponseCurveSet16:
+ return this.ReadResponseCurveSet16TagDataEntry();
+ case IccTypeSignature.S15Fixed16Array:
+ return this.ReadFix16ArrayTagDataEntry(info.DataSize);
+ case IccTypeSignature.Signature:
+ return this.ReadSignatureTagDataEntry();
+ case IccTypeSignature.Text:
+ return this.ReadTextTagDataEntry(info.DataSize);
+ case IccTypeSignature.U16Fixed16Array:
+ return this.ReadUFix16ArrayTagDataEntry(info.DataSize);
+ case IccTypeSignature.UInt16Array:
+ return this.ReadUInt16ArrayTagDataEntry(info.DataSize);
+ case IccTypeSignature.UInt32Array:
+ return this.ReadUInt32ArrayTagDataEntry(info.DataSize);
+ case IccTypeSignature.UInt64Array:
+ return this.ReadUInt64ArrayTagDataEntry(info.DataSize);
+ case IccTypeSignature.UInt8Array:
+ return this.ReadUInt8ArrayTagDataEntry(info.DataSize);
+ case IccTypeSignature.ViewingConditions:
+ return this.ReadViewingConditionsTagDataEntry();
+ case IccTypeSignature.Xyz:
+ return this.ReadXyzTagDataEntry(info.DataSize);
+
+ // V2 Types:
+ case IccTypeSignature.TextDescription:
+ return this.ReadTextDescriptionTagDataEntry();
+ case IccTypeSignature.CrdInfo:
+ return this.ReadCrdInfoTagDataEntry();
+ case IccTypeSignature.Screening:
+ return this.ReadScreeningTagDataEntry();
+ case IccTypeSignature.UcrBg:
+ return this.ReadUcrBgTagDataEntry(info.DataSize);
+
+ // Unsupported or unknown
+ case IccTypeSignature.DeviceSettings:
+ case IccTypeSignature.NamedColor:
+ case IccTypeSignature.Unknown:
+ default:
+ return this.ReadUnknownTagDataEntry(info.DataSize);
+ }
+ }
+
+ ///
+ /// Reads the header of a
+ ///
+ /// The read signature
+ public IccTypeSignature ReadTagDataEntryHeader()
+ {
+ var type = (IccTypeSignature)this.ReadUInt32();
+ this.AddIndex(4); // 4 bytes are not used
+ return type;
+ }
+
+ ///
+ /// Reads the header of a and checks if it's the expected value
+ ///
+ /// expected value to check against
+ public void ReadCheckTagDataEntryHeader(IccTypeSignature expected)
+ {
+ IccTypeSignature type = this.ReadTagDataEntryHeader();
+ if (expected != (IccTypeSignature)uint.MaxValue && type != expected)
+ {
+ throw new InvalidIccProfileException($"Read signature {type} is not the expected {expected}");
+ }
+ }
+
+ ///
+ /// Reads a with an unknown
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size)
+ {
+ int count = (int)size - 8; // 8 is the tag header size
+ return new IccUnknownTagDataEntry(this.ReadBytes(count));
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry()
+ {
+ ushort channelCount = this.ReadUInt16();
+ var colorant = (IccColorantEncoding)this.ReadUInt16();
+
+ if (Enum.IsDefined(typeof(IccColorantEncoding), colorant) && colorant != IccColorantEncoding.Unknown)
+ {
+ // The type is known and so are the values (they are constant)
+ // channelCount should always be 3 but it doesn't really matter if it's not
+ return new IccChromaticityTagDataEntry(colorant);
+ }
+ else
+ {
+ // The type is not know, so the values need be read
+ double[][] values = new double[channelCount][];
+ for (int i = 0; i < channelCount; i++)
+ {
+ values[i] = new double[2];
+ values[i][0] = this.ReadUFix16();
+ values[i][1] = this.ReadUFix16();
+ }
+
+ return new IccChromaticityTagDataEntry(values);
+ }
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry()
+ {
+ uint colorantCount = this.ReadUInt32();
+ byte[] number = this.ReadBytes((int)colorantCount);
+ return new IccColorantOrderTagDataEntry(number);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccColorantTableTagDataEntry ReadColorantTableTagDataEntry()
+ {
+ uint colorantCount = this.ReadUInt32();
+ IccColorantTableEntry[] cdata = new IccColorantTableEntry[colorantCount];
+ for (int i = 0; i < colorantCount; i++)
+ {
+ cdata[i] = this.ReadColorantTableEntry();
+ }
+
+ return new IccColorantTableTagDataEntry(cdata);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccCurveTagDataEntry ReadCurveTagDataEntry()
+ {
+ uint pointCount = this.ReadUInt32();
+
+ if (pointCount == 0)
+ {
+ return new IccCurveTagDataEntry();
+ }
+
+ if (pointCount == 1)
+ {
+ return new IccCurveTagDataEntry(this.ReadUFix8());
+ }
+
+ float[] cdata = new float[pointCount];
+ for (int i = 0; i < pointCount; i++)
+ {
+ cdata[i] = this.ReadUInt16() / 65535f;
+ }
+
+ return new IccCurveTagDataEntry(cdata);
+
+ // TODO: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768).
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccDataTagDataEntry ReadDataTagDataEntry(uint size)
+ {
+ this.AddIndex(3); // first 3 bytes are zero
+ byte b = this.data[this.AddIndex(1)];
+
+ // last bit of 4th byte is either 0 = ASCII or 1 = binary
+ bool ascii = this.GetBit(b, 7);
+ int length = (int)size - 12;
+ byte[] cdata = this.ReadBytes(length);
+
+ return new IccDataTagDataEntry(cdata, ascii);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry()
+ {
+ return new IccDateTimeTagDataEntry(this.ReadDateTime());
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccLut16TagDataEntry ReadLut16TagDataEntry()
+ {
+ byte inChCount = this.data[this.AddIndex(1)];
+ byte outChCount = this.data[this.AddIndex(1)];
+ byte clutPointCount = this.data[this.AddIndex(1)];
+ this.AddIndex(1); // 1 byte reserved
+
+ float[,] matrix = this.ReadMatrix(3, 3, false);
+
+ ushort inTableCount = this.ReadUInt16();
+ ushort outTableCount = this.ReadUInt16();
+
+ // Input LUT
+ IccLut[] inValues = new IccLut[inChCount];
+ byte[] gridPointCount = new byte[inChCount];
+ for (int i = 0; i < inChCount; i++)
+ {
+ inValues[i] = this.ReadLut16(inTableCount);
+ gridPointCount[i] = clutPointCount;
+ }
+
+ // CLUT
+ IccClut clut = this.ReadClut16(inChCount, outChCount, gridPointCount);
+
+ // Output LUT
+ IccLut[] outValues = new IccLut[outChCount];
+ for (int i = 0; i < outChCount; i++)
+ {
+ outValues[i] = this.ReadLut16(outTableCount);
+ }
+
+ return new IccLut16TagDataEntry(matrix, inValues, clut, outValues);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccLut8TagDataEntry ReadLut8TagDataEntry()
+ {
+ byte inChCount = this.data[this.AddIndex(1)];
+ byte outChCount = this.data[this.AddIndex(1)];
+ byte clutPointCount = this.data[this.AddIndex(1)];
+ this.AddIndex(1); // 1 byte reserved
+
+ float[,] matrix = this.ReadMatrix(3, 3, false);
+
+ // Input LUT
+ IccLut[] inValues = new IccLut[inChCount];
+ byte[] gridPointCount = new byte[inChCount];
+ for (int i = 0; i < inChCount; i++)
+ {
+ inValues[i] = this.ReadLut8();
+ gridPointCount[i] = clutPointCount;
+ }
+
+ // CLUT
+ IccClut clut = this.ReadClut8(inChCount, outChCount, gridPointCount);
+
+ // Output LUT
+ IccLut[] outValues = new IccLut[outChCount];
+ for (int i = 0; i < outChCount; i++)
+ {
+ outValues[i] = this.ReadLut8();
+ }
+
+ return new IccLut8TagDataEntry(matrix, inValues, clut, outValues);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccLutAToBTagDataEntry ReadLutAtoBTagDataEntry()
+ {
+ int start = this.currentIndex - 8; // 8 is the tag header size
+
+ byte inChCount = this.data[this.AddIndex(1)];
+ byte outChCount = this.data[this.AddIndex(1)];
+ this.AddIndex(2); // 2 bytes reserved
+
+ uint bCurveOffset = this.ReadUInt32();
+ uint matrixOffset = this.ReadUInt32();
+ uint mCurveOffset = this.ReadUInt32();
+ uint clutOffset = this.ReadUInt32();
+ uint aCurveOffset = this.ReadUInt32();
+
+ IccTagDataEntry[] bCurve = null;
+ IccTagDataEntry[] mCurve = null;
+ IccTagDataEntry[] aCurve = null;
+ IccClut clut = null;
+ float[,] matrix3x3 = null;
+ float[] matrix3x1 = null;
+
+ if (bCurveOffset != 0)
+ {
+ this.currentIndex = (int)bCurveOffset + start;
+ bCurve = this.ReadCurves(outChCount);
+ }
+
+ if (mCurveOffset != 0)
+ {
+ this.currentIndex = (int)mCurveOffset + start;
+ mCurve = this.ReadCurves(outChCount);
+ }
+
+ if (aCurveOffset != 0)
+ {
+ this.currentIndex = (int)aCurveOffset + start;
+ aCurve = this.ReadCurves(inChCount);
+ }
+
+ if (clutOffset != 0)
+ {
+ this.currentIndex = (int)clutOffset + start;
+ clut = this.ReadClut(inChCount, outChCount, false);
+ }
+
+ if (matrixOffset != 0)
+ {
+ this.currentIndex = (int)matrixOffset + start;
+ matrix3x3 = this.ReadMatrix(3, 3, false);
+ matrix3x1 = this.ReadMatrix(3, false);
+ }
+
+ return new IccLutAToBTagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccLutBToATagDataEntry ReadLutBtoATagDataEntry()
+ {
+ int start = this.currentIndex - 8; // 8 is the tag header size
+
+ byte inChCount = this.data[this.AddIndex(1)];
+ byte outChCount = this.data[this.AddIndex(1)];
+ this.AddIndex(2); // 2 bytes reserved
+
+ uint bCurveOffset = this.ReadUInt32();
+ uint matrixOffset = this.ReadUInt32();
+ uint mCurveOffset = this.ReadUInt32();
+ uint clutOffset = this.ReadUInt32();
+ uint aCurveOffset = this.ReadUInt32();
+
+ IccTagDataEntry[] bCurve = null;
+ IccTagDataEntry[] mCurve = null;
+ IccTagDataEntry[] aCurve = null;
+ IccClut clut = null;
+ float[,] matrix3x3 = null;
+ float[] matrix3x1 = null;
+
+ if (bCurveOffset != 0)
+ {
+ this.currentIndex = (int)bCurveOffset + start;
+ bCurve = this.ReadCurves(inChCount);
+ }
+
+ if (mCurveOffset != 0)
+ {
+ this.currentIndex = (int)mCurveOffset + start;
+ mCurve = this.ReadCurves(inChCount);
+ }
+
+ if (aCurveOffset != 0)
+ {
+ this.currentIndex = (int)aCurveOffset + start;
+ aCurve = this.ReadCurves(outChCount);
+ }
+
+ if (clutOffset != 0)
+ {
+ this.currentIndex = (int)clutOffset + start;
+ clut = this.ReadClut(inChCount, outChCount, false);
+ }
+
+ if (matrixOffset != 0)
+ {
+ this.currentIndex = (int)matrixOffset + start;
+ matrix3x3 = this.ReadMatrix(3, 3, false);
+ matrix3x1 = this.ReadMatrix(3, false);
+ }
+
+ return new IccLutBToATagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry()
+ {
+ return new IccMeasurementTagDataEntry(
+ observer: (IccStandardObserver)this.ReadUInt32(),
+ xyzBacking: this.ReadXyzNumber(),
+ geometry: (IccMeasurementGeometry)this.ReadUInt32(),
+ flare: this.ReadUFix16(),
+ illuminant: (IccStandardIlluminant)this.ReadUInt32());
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccMultiLocalizedUnicodeTagDataEntry ReadMultiLocalizedUnicodeTagDataEntry()
+ {
+ int start = this.currentIndex - 8; // 8 is the tag header size
+ uint recordCount = this.ReadUInt32();
+
+ // TODO: Why are we storing variable
+ uint recordSize = this.ReadUInt32();
+ IccLocalizedString[] text = new IccLocalizedString[recordCount];
+
+ string[] culture = new string[recordCount];
+ uint[] length = new uint[recordCount];
+ uint[] offset = new uint[recordCount];
+
+ for (int i = 0; i < recordCount; i++)
+ {
+ culture[i] = $"{this.ReadAsciiString(2)}-{this.ReadAsciiString(2)}";
+ length[i] = this.ReadUInt32();
+ offset[i] = this.ReadUInt32();
+ }
+
+ for (int i = 0; i < recordCount; i++)
+ {
+ this.currentIndex = (int)(start + offset[i]);
+ text[i] = new IccLocalizedString(new CultureInfo(culture[i]), this.ReadUnicodeString((int)length[i]));
+ }
+
+ return new IccMultiLocalizedUnicodeTagDataEntry(text);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccMultiProcessElementsTagDataEntry ReadMultiProcessElementsTagDataEntry()
+ {
+ int start = this.currentIndex - 8;
+
+ // TODO: Why are we storing variable
+ ushort inChannelCount = this.ReadUInt16();
+ ushort outChannelCount = this.ReadUInt16();
+ uint elementCount = this.ReadUInt32();
+
+ IccPositionNumber[] positionTable = new IccPositionNumber[elementCount];
+ for (int i = 0; i < elementCount; i++)
+ {
+ positionTable[i] = this.ReadPositionNumber();
+ }
+
+ IccMultiProcessElement[] elements = new IccMultiProcessElement[elementCount];
+ for (int i = 0; i < elementCount; i++)
+ {
+ this.currentIndex = (int)positionTable[i].Offset + start;
+ elements[i] = this.ReadMultiProcessElement();
+ }
+
+ return new IccMultiProcessElementsTagDataEntry(elements);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccNamedColor2TagDataEntry ReadNamedColor2TagDataEntry()
+ {
+ int vendorFlag = this.ReadInt32();
+ uint colorCount = this.ReadUInt32();
+ uint coordCount = this.ReadUInt32();
+ string prefix = this.ReadAsciiString(32);
+ string suffix = this.ReadAsciiString(32);
+
+ IccNamedColor[] colors = new IccNamedColor[colorCount];
+ for (int i = 0; i < colorCount; i++)
+ {
+ colors[i] = this.ReadNamedColor(coordCount);
+ }
+
+ return new IccNamedColor2TagDataEntry(vendorFlag, prefix, suffix, colors);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry()
+ {
+ return new IccParametricCurveTagDataEntry(this.ReadParametricCurve());
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccProfileSequenceDescTagDataEntry ReadProfileSequenceDescTagDataEntry()
+ {
+ uint count = this.ReadUInt32();
+ IccProfileDescription[] description = new IccProfileDescription[count];
+ for (int i = 0; i < count; i++)
+ {
+ description[i] = this.ReadProfileDescription();
+ }
+
+ return new IccProfileSequenceDescTagDataEntry(description);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccProfileSequenceIdentifierTagDataEntry ReadProfileSequenceIdentifierTagDataEntry()
+ {
+ int start = this.currentIndex - 8; // 8 is the tag header size
+ uint count = this.ReadUInt32();
+ IccPositionNumber[] table = new IccPositionNumber[count];
+ for (int i = 0; i < count; i++)
+ {
+ table[i] = this.ReadPositionNumber();
+ }
+
+ IccProfileSequenceIdentifier[] entries = new IccProfileSequenceIdentifier[count];
+ for (int i = 0; i < count; i++)
+ {
+ this.currentIndex = (int)(start + table[i].Offset);
+ IccProfileId id = this.ReadProfileId();
+ this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode);
+ IccMultiLocalizedUnicodeTagDataEntry description = this.ReadMultiLocalizedUnicodeTagDataEntry();
+ entries[i] = new IccProfileSequenceIdentifier(id, description.Texts);
+ }
+
+ return new IccProfileSequenceIdentifierTagDataEntry(entries);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccResponseCurveSet16TagDataEntry ReadResponseCurveSet16TagDataEntry()
+ {
+ int start = this.currentIndex - 8; // 8 is the tag header size
+ ushort channelCount = this.ReadUInt16();
+ ushort measurmentCount = this.ReadUInt16();
+
+ uint[] offset = new uint[measurmentCount];
+ for (int i = 0; i < measurmentCount; i++)
+ {
+ offset[i] = this.ReadUInt32();
+ }
+
+ IccResponseCurve[] curves = new IccResponseCurve[measurmentCount];
+ for (int i = 0; i < measurmentCount; i++)
+ {
+ this.currentIndex = (int)(start + offset[i]);
+ curves[i] = this.ReadResponseCurve(channelCount);
+ }
+
+ return new IccResponseCurveSet16TagDataEntry(curves);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size)
+ {
+ uint count = (size - 8) / 4;
+ float[] arrayData = new float[count];
+ for (int i = 0; i < count; i++)
+ {
+ arrayData[i] = this.ReadFix16() / 256f;
+ }
+
+ return new IccFix16ArrayTagDataEntry(arrayData);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccSignatureTagDataEntry ReadSignatureTagDataEntry()
+ {
+ return new IccSignatureTagDataEntry(this.ReadAsciiString(4));
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccTextTagDataEntry ReadTextTagDataEntry(uint size)
+ {
+ return new IccTextTagDataEntry(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size)
+ {
+ uint count = (size - 8) / 4;
+ float[] arrayData = new float[count];
+ for (int i = 0; i < count; i++)
+ {
+ arrayData[i] = this.ReadUFix16();
+ }
+
+ return new IccUFix16ArrayTagDataEntry(arrayData);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size)
+ {
+ uint count = (size - 8) / 2;
+ ushort[] arrayData = new ushort[count];
+ for (int i = 0; i < count; i++)
+ {
+ arrayData[i] = this.ReadUInt16();
+ }
+
+ return new IccUInt16ArrayTagDataEntry(arrayData);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size)
+ {
+ uint count = (size - 8) / 4;
+ uint[] arrayData = new uint[count];
+ for (int i = 0; i < count; i++)
+ {
+ arrayData[i] = this.ReadUInt32();
+ }
+
+ return new IccUInt32ArrayTagDataEntry(arrayData);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size)
+ {
+ uint count = (size - 8) / 8;
+ ulong[] arrayData = new ulong[count];
+ for (int i = 0; i < count; i++)
+ {
+ arrayData[i] = this.ReadUInt64();
+ }
+
+ return new IccUInt64ArrayTagDataEntry(arrayData);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUInt8ArrayTagDataEntry ReadUInt8ArrayTagDataEntry(uint size)
+ {
+ int count = (int)size - 8; // 8 is the tag header size
+ byte[] adata = this.ReadBytes(count);
+
+ return new IccUInt8ArrayTagDataEntry(adata);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry()
+ {
+ return new IccViewingConditionsTagDataEntry(
+ illuminantXyz: this.ReadXyzNumber(),
+ surroundXyz: this.ReadXyzNumber(),
+ illuminant: (IccStandardIlluminant)this.ReadUInt32());
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccXyzTagDataEntry ReadXyzTagDataEntry(uint size)
+ {
+ uint count = (size - 8) / 12;
+ Vector3[] arrayData = new Vector3[count];
+ for (int i = 0; i < count; i++)
+ {
+ arrayData[i] = this.ReadXyzNumber();
+ }
+
+ return new IccXyzTagDataEntry(arrayData);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccTextDescriptionTagDataEntry ReadTextDescriptionTagDataEntry()
+ {
+ string unicodeValue, scriptcodeValue;
+ string asciiValue = unicodeValue = scriptcodeValue = null;
+
+ int asciiCount = (int)this.ReadUInt32();
+ if (asciiCount > 0)
+ {
+ asciiValue = this.ReadAsciiString(asciiCount - 1);
+ this.AddIndex(1); // Null terminator
+ }
+
+ uint unicodeLangCode = this.ReadUInt32();
+ int unicodeCount = (int)this.ReadUInt32();
+ if (unicodeCount > 0)
+ {
+ unicodeValue = this.ReadUnicodeString((unicodeCount * 2) - 2);
+ this.AddIndex(2); // Null terminator
+ }
+
+ ushort scriptcodeCode = this.ReadUInt16();
+ int scriptcodeCount = Math.Min(this.data[this.AddIndex(1)], (byte)67);
+ if (scriptcodeCount > 0)
+ {
+ scriptcodeValue = this.ReadAsciiString(scriptcodeCount - 1);
+ this.AddIndex(1); // Null terminator
+ }
+
+ return new IccTextDescriptionTagDataEntry(
+ asciiValue,
+ unicodeValue,
+ scriptcodeValue,
+ unicodeLangCode,
+ scriptcodeCode);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccCrdInfoTagDataEntry ReadCrdInfoTagDataEntry()
+ {
+ uint productNameCount = this.ReadUInt32();
+ string productName = this.ReadAsciiString((int)productNameCount);
+
+ uint crd0Count = this.ReadUInt32();
+ string crd0Name = this.ReadAsciiString((int)crd0Count);
+
+ uint crd1Count = this.ReadUInt32();
+ string crd1Name = this.ReadAsciiString((int)crd1Count);
+
+ uint crd2Count = this.ReadUInt32();
+ string crd2Name = this.ReadAsciiString((int)crd2Count);
+
+ uint crd3Count = this.ReadUInt32();
+ string crd3Name = this.ReadAsciiString((int)crd3Count);
+
+ return new IccCrdInfoTagDataEntry(productName, crd0Name, crd1Name, crd2Name, crd3Name);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The read entry
+ public IccScreeningTagDataEntry ReadScreeningTagDataEntry()
+ {
+ var flags = (IccScreeningFlag)this.ReadInt32();
+ uint channelCount = this.ReadUInt32();
+ IccScreeningChannel[] channels = new IccScreeningChannel[channelCount];
+ for (int i = 0; i < channels.Length; i++)
+ {
+ channels[i] = this.ReadScreeningChannel();
+ }
+
+ return new IccScreeningTagDataEntry(flags, channels);
+ }
+
+ ///
+ /// Reads a
+ ///
+ /// The size of the entry in bytes
+ /// The read entry
+ public IccUcrBgTagDataEntry ReadUcrBgTagDataEntry(uint size)
+ {
+ uint ucrCount = this.ReadUInt32();
+ ushort[] ucrCurve = new ushort[ucrCount];
+ for (int i = 0; i < ucrCurve.Length; i++)
+ {
+ ucrCurve[i] = this.ReadUInt16();
+ }
+
+ uint bgCount = this.ReadUInt32();
+ ushort[] bgCurve = new ushort[bgCount];
+ for (int i = 0; i < bgCurve.Length; i++)
+ {
+ bgCurve[i] = this.ReadUInt16();
+ }
+
+ // ((ucr length + bg length) * UInt16 size) + (ucrCount + bgCount)
+ uint dataSize = ((ucrCount + bgCount) * 2) + 8;
+ int descriptionLength = (int)(size - 8 - dataSize); // 8 is the tag header size
+ string description = this.ReadAsciiString(descriptionLength);
+
+ return new IccUcrBgTagDataEntry(ucrCurve, bgCurve, description);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs
new file mode 100644
index 000000000..cb1fe5b55
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs
@@ -0,0 +1,106 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Text;
+ using ImageSharp.IO;
+
+ ///
+ /// Provides methods to read ICC data types
+ ///
+ internal sealed partial class IccDataReader
+ {
+ private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian;
+ private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII");
+
+ ///
+ /// The data that is read
+ ///
+ private readonly byte[] data;
+
+ ///
+ /// The bit converter
+ ///
+ private readonly EndianBitConverter converter = new BigEndianBitConverter();
+
+ ///
+ /// The current reading position
+ ///
+ private int currentIndex;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The data to read
+ public IccDataReader(byte[] data)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.data = data;
+ }
+
+ ///
+ /// Sets the reading position to the given value
+ ///
+ /// The new index position
+ public void SetIndex(int index)
+ {
+ this.currentIndex = index.Clamp(0, this.data.Length);
+ }
+
+ ///
+ /// Returns the current without increment and adds the given increment
+ ///
+ /// The value to increment
+ /// The current without the increment
+ private int AddIndex(int increment)
+ {
+ int tmp = this.currentIndex;
+ this.currentIndex += increment;
+ return tmp;
+ }
+
+ ///
+ /// Calculates the 4 byte padding and adds it to the variable
+ ///
+ private void AddPadding()
+ {
+ this.currentIndex += this.CalcPadding();
+ }
+
+ ///
+ /// Calculates the 4 byte padding
+ ///
+ /// the number of bytes to pad
+ private int CalcPadding()
+ {
+ int p = 4 - (this.currentIndex % 4);
+ return p >= 4 ? 0 : p;
+ }
+
+ ///
+ /// Gets the bit value at a specified position
+ ///
+ /// The value from where the bit will be extracted
+ /// Position of the bit. Zero based index from left to right.
+ /// The bit value at specified position
+ private bool GetBit(byte value, int position)
+ {
+ return ((value >> (7 - position)) & 1) == 1;
+ }
+
+ ///
+ /// Gets the bit value at a specified position
+ ///
+ /// The value from where the bit will be extracted
+ /// Position of the bit. Zero based index from left to right.
+ /// The bit value at specified position
+ private bool GetBit(ushort value, int position)
+ {
+ return ((value >> (15 - position)) & 1) == 1;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs
new file mode 100644
index 000000000..4b6e454a1
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs
@@ -0,0 +1,179 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Numerics;
+
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// Writes a
+ ///
+ /// The curve to write
+ /// The number of bytes written
+ public int WriteOneDimensionalCurve(IccOneDimensionalCurve value)
+ {
+ int count = this.WriteUInt16((ushort)value.Segments.Length);
+ count += this.WriteEmpty(2);
+
+ foreach (float point in value.BreakPoints)
+ {
+ count += this.WriteSingle(point);
+ }
+
+ foreach (IccCurveSegment segment in value.Segments)
+ {
+ count += this.WriteCurveSegment(segment);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The curve to write
+ /// The number of bytes written
+ public int WriteResponseCurve(IccResponseCurve value)
+ {
+ int count = this.WriteUInt32((uint)value.CurveType);
+ int channels = value.XyzValues.Length;
+
+ foreach (IccResponseNumber[] responseArray in value.ResponseArrays)
+ {
+ count += this.WriteUInt32((uint)responseArray.Length);
+ }
+
+ foreach (Vector3 xyz in value.XyzValues)
+ {
+ count += this.WriteXyzNumber(xyz);
+ }
+
+ foreach (IccResponseNumber[] responseArray in value.ResponseArrays)
+ {
+ foreach (IccResponseNumber response in responseArray)
+ {
+ count += this.WriteResponseNumber(response);
+ }
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The curve to write
+ /// The number of bytes written
+ public int WriteParametricCurve(IccParametricCurve value)
+ {
+ ushort typeValue = (ushort)value.Type;
+ int count = this.WriteUInt16(typeValue);
+ count += this.WriteEmpty(2);
+
+ if (typeValue <= 4)
+ {
+ count += this.WriteFix16(value.G);
+ }
+
+ if (typeValue > 0 && typeValue <= 4)
+ {
+ count += this.WriteFix16(value.A);
+ count += this.WriteFix16(value.B);
+ }
+
+ if (typeValue > 1 && typeValue <= 4)
+ {
+ count += this.WriteFix16(value.C);
+ }
+
+ if (typeValue > 2 && typeValue <= 4)
+ {
+ count += this.WriteFix16(value.D);
+ }
+
+ if (typeValue == 4)
+ {
+ count += this.WriteFix16(value.E);
+ count += this.WriteFix16(value.F);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The curve to write
+ /// The number of bytes written
+ public int WriteCurveSegment(IccCurveSegment value)
+ {
+ int count = this.WriteUInt32((uint)value.Signature);
+ count += this.WriteEmpty(4);
+
+ switch (value.Signature)
+ {
+ case IccCurveSegmentSignature.FormulaCurve:
+ return count + this.WriteFormulaCurveElement(value as IccFormulaCurveElement);
+ case IccCurveSegmentSignature.SampledCurve:
+ return count + this.WriteSampledCurveElement(value as IccSampledCurveElement);
+ default:
+ throw new InvalidIccProfileException($"Invalid CurveSegment type of {value.Signature}");
+ }
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The curve to write
+ /// The number of bytes written
+ public int WriteFormulaCurveElement(IccFormulaCurveElement value)
+ {
+ int count = this.WriteUInt16((ushort)value.Type);
+ count += this.WriteEmpty(2);
+
+ if (value.Type == IccFormulaCurveType.Type1 || value.Type == IccFormulaCurveType.Type2)
+ {
+ count += this.WriteSingle(value.Gamma);
+ }
+
+ count += this.WriteSingle(value.A);
+ count += this.WriteSingle(value.B);
+ count += this.WriteSingle(value.C);
+
+ if (value.Type == IccFormulaCurveType.Type2 || value.Type == IccFormulaCurveType.Type3)
+ {
+ count += this.WriteSingle(value.D);
+ }
+
+ if (value.Type == IccFormulaCurveType.Type3)
+ {
+ count += this.WriteSingle(value.E);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The curve to write
+ /// The number of bytes written
+ public int WriteSampledCurveElement(IccSampledCurveElement value)
+ {
+ int count = this.WriteUInt32((uint)value.CurveEntries.Length);
+ foreach (float entry in value.CurveEntries)
+ {
+ count += this.WriteSingle(entry);
+ }
+
+ return count;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs
new file mode 100644
index 000000000..f85d59714
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs
@@ -0,0 +1,128 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// Writes an 8bit lookup table
+ ///
+ /// The LUT to write
+ /// The number of bytes written
+ public int WriteLut8(IccLut value)
+ {
+ foreach (float item in value.Values)
+ {
+ this.WriteByte((byte)((item * byte.MaxValue) + 0.5f).Clamp(0, byte.MaxValue));
+ }
+
+ return value.Values.Length;
+ }
+
+ ///
+ /// Writes an 16bit lookup table
+ ///
+ /// The LUT to write
+ /// The number of bytes written
+ public int WriteLut16(IccLut value)
+ {
+ foreach (float item in value.Values)
+ {
+ this.WriteUInt16((ushort)((item * ushort.MaxValue) + 0.5f).Clamp(0, ushort.MaxValue));
+ }
+
+ return value.Values.Length * 2;
+ }
+
+ ///
+ /// Writes an color lookup table
+ ///
+ /// The CLUT to write
+ /// The number of bytes written
+ public int WriteClut(IccClut value)
+ {
+ int count = this.WriteArray(value.GridPointCount);
+ count += this.WriteEmpty(16 - value.GridPointCount.Length);
+
+ switch (value.DataType)
+ {
+ case IccClutDataType.Float:
+ return count + this.WriteClutF32(value);
+ case IccClutDataType.UInt8:
+ count += this.WriteByte(1);
+ count += this.WriteEmpty(3);
+ return count + this.WriteClut8(value);
+ case IccClutDataType.UInt16:
+ count += this.WriteByte(2);
+ count += this.WriteEmpty(3);
+ return count + this.WriteClut16(value);
+
+ default:
+ throw new InvalidIccProfileException($"Invalid CLUT data type of {value.DataType}");
+ }
+ }
+
+ ///
+ /// Writes a 8bit color lookup table
+ ///
+ /// The CLUT to write
+ /// The number of bytes written
+ public int WriteClut8(IccClut value)
+ {
+ int count = 0;
+ foreach (float[] inArray in value.Values)
+ {
+ foreach (float item in inArray)
+ {
+ count += this.WriteByte((byte)((item * byte.MaxValue) + 0.5f).Clamp(0, byte.MaxValue));
+ }
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a 16bit color lookup table
+ ///
+ /// The CLUT to write
+ /// The number of bytes written
+ public int WriteClut16(IccClut value)
+ {
+ int count = 0;
+ foreach (float[] inArray in value.Values)
+ {
+ foreach (float item in inArray)
+ {
+ count += this.WriteUInt16((ushort)((item * ushort.MaxValue) + 0.5f).Clamp(0, ushort.MaxValue));
+ }
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a 32bit float color lookup table
+ ///
+ /// The CLUT to write
+ /// The number of bytes written
+ public int WriteClutF32(IccClut value)
+ {
+ int count = 0;
+ foreach (float[] inArray in value.Values)
+ {
+ foreach (float item in inArray)
+ {
+ count += this.WriteSingle(item);
+ }
+ }
+
+ return count;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
new file mode 100644
index 000000000..13a15b483
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
@@ -0,0 +1,162 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Numerics;
+
+ using ImageSharp.Memory;
+
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// Writes a two dimensional matrix
+ ///
+ /// The matrix to write
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The number of bytes written
+ public int WriteMatrix(Matrix4x4 value, bool isSingle)
+ {
+ int count = 0;
+
+ if (isSingle)
+ {
+ count += this.WriteSingle(value.M11);
+ count += this.WriteSingle(value.M21);
+ count += this.WriteSingle(value.M31);
+
+ count += this.WriteSingle(value.M12);
+ count += this.WriteSingle(value.M22);
+ count += this.WriteSingle(value.M32);
+
+ count += this.WriteSingle(value.M13);
+ count += this.WriteSingle(value.M23);
+ count += this.WriteSingle(value.M33);
+ }
+ else
+ {
+ count += this.WriteFix16(value.M11);
+ count += this.WriteFix16(value.M21);
+ count += this.WriteFix16(value.M31);
+
+ count += this.WriteFix16(value.M12);
+ count += this.WriteFix16(value.M22);
+ count += this.WriteFix16(value.M32);
+
+ count += this.WriteFix16(value.M13);
+ count += this.WriteFix16(value.M23);
+ count += this.WriteFix16(value.M33);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a two dimensional matrix
+ ///
+ /// The matrix to write
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The number of bytes written
+ public int WriteMatrix(Fast2DArray value, bool isSingle)
+ {
+ int count = 0;
+ for (int y = 0; y < value.Height; y++)
+ {
+ for (int x = 0; x < value.Width; x++)
+ {
+ if (isSingle)
+ {
+ count += this.WriteSingle(value[x, y]);
+ }
+ else
+ {
+ count += this.WriteFix16(value[x, y]);
+ }
+ }
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a two dimensional matrix
+ ///
+ /// The matrix to write
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The number of bytes written
+ public int WriteMatrix(float[,] value, bool isSingle)
+ {
+ int count = 0;
+ for (int y = 0; y < value.GetLength(1); y++)
+ {
+ for (int x = 0; x < value.GetLength(0); x++)
+ {
+ if (isSingle)
+ {
+ count += this.WriteSingle(value[x, y]);
+ }
+ else
+ {
+ count += this.WriteFix16(value[x, y]);
+ }
+ }
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a one dimensional matrix
+ ///
+ /// The matrix to write
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The number of bytes written
+ public int WriteMatrix(Vector3 value, bool isSingle)
+ {
+ int count = 0;
+ if (isSingle)
+ {
+ count += this.WriteSingle(value.X);
+ count += this.WriteSingle(value.Y);
+ count += this.WriteSingle(value.Z);
+ }
+ else
+ {
+ count += this.WriteFix16(value.X);
+ count += this.WriteFix16(value.Y);
+ count += this.WriteFix16(value.Z);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a one dimensional matrix
+ ///
+ /// The matrix to write
+ /// True if the values are encoded as Single; false if encoded as Fix16
+ /// The number of bytes written
+ public int WriteMatrix(float[] value, bool isSingle)
+ {
+ int count = 0;
+ for (int i = 0; i < value.Length; i++)
+ {
+ if (isSingle)
+ {
+ count += this.WriteSingle(value[i]);
+ }
+ else
+ {
+ count += this.WriteFix16(value[i]);
+ }
+ }
+
+ return count;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs
new file mode 100644
index 000000000..b3ddb538c
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs
@@ -0,0 +1,80 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// Writes a
+ ///
+ /// The element to write
+ /// The number of bytes written
+ public int WriteMultiProcessElement(IccMultiProcessElement value)
+ {
+ int count = this.WriteUInt32((uint)value.Signature);
+ count += this.WriteUInt16((ushort)value.InputChannelCount);
+ count += this.WriteUInt16((ushort)value.OutputChannelCount);
+
+ switch (value.Signature)
+ {
+ case IccMultiProcessElementSignature.CurveSet:
+ return count + this.WriteCurveSetProcessElement(value as IccCurveSetProcessElement);
+ case IccMultiProcessElementSignature.Matrix:
+ return count + this.WriteMatrixProcessElement(value as IccMatrixProcessElement);
+ case IccMultiProcessElementSignature.Clut:
+ return count + this.WriteClutProcessElement(value as IccClutProcessElement);
+
+ case IccMultiProcessElementSignature.BAcs:
+ case IccMultiProcessElementSignature.EAcs:
+ return count + this.WriteEmpty(8);
+
+ default:
+ throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {value.Signature}");
+ }
+ }
+
+ ///
+ /// Writes a CurveSet
+ ///
+ /// The element to write
+ /// The number of bytes written
+ public int WriteCurveSetProcessElement(IccCurveSetProcessElement value)
+ {
+ int count = 0;
+ foreach (IccOneDimensionalCurve curve in value.Curves)
+ {
+ count += this.WriteOneDimensionalCurve(curve);
+ count += this.WritePadding();
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a Matrix
+ ///
+ /// The element to write
+ /// The number of bytes written
+ public int WriteMatrixProcessElement(IccMatrixProcessElement value)
+ {
+ return this.WriteMatrix(value.MatrixIxO, true)
+ + this.WriteMatrix(value.MatrixOx1, true);
+ }
+
+ ///
+ /// Writes a CLUT
+ ///
+ /// The element to write
+ /// The number of bytes written
+ public int WriteClutProcessElement(IccClutProcessElement value)
+ {
+ return this.WriteClut(value.ClutValue);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs
new file mode 100644
index 000000000..d90c1ff54
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs
@@ -0,0 +1,137 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Numerics;
+
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// Writes a DateTime
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteDateTime(DateTime value)
+ {
+ return this.WriteUInt16((ushort)value.Year)
+ + this.WriteUInt16((ushort)value.Month)
+ + this.WriteUInt16((ushort)value.Day)
+ + this.WriteUInt16((ushort)value.Hour)
+ + this.WriteUInt16((ushort)value.Minute)
+ + this.WriteUInt16((ushort)value.Second);
+ }
+
+ ///
+ /// Writes an ICC profile version number
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteVersionNumber(Version value)
+ {
+ int major = value.Major.Clamp(0, byte.MaxValue);
+ int minor = value.Minor.Clamp(0, 15);
+ int bugfix = value.Build.Clamp(0, 15);
+
+ // TODO: This is not used?
+ byte mb = (byte)((minor << 4) | bugfix);
+
+ int version = (major << 24) | (minor << 20) | (bugfix << 16);
+ return this.WriteInt32(version);
+ }
+
+ ///
+ /// Writes an XYZ number
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteXyzNumber(Vector3 value)
+ {
+ return this.WriteFix16(value.X)
+ + this.WriteFix16(value.Y)
+ + this.WriteFix16(value.Z);
+ }
+
+ ///
+ /// Writes a profile ID
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteProfileId(IccProfileId value)
+ {
+ return this.WriteUInt32(value.Part1)
+ + this.WriteUInt32(value.Part2)
+ + this.WriteUInt32(value.Part3)
+ + this.WriteUInt32(value.Part4);
+ }
+
+ ///
+ /// Writes a position number
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WritePositionNumber(IccPositionNumber value)
+ {
+ return this.WriteUInt32(value.Offset)
+ + this.WriteUInt32(value.Size);
+ }
+
+ ///
+ /// Writes a response number
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteResponseNumber(IccResponseNumber value)
+ {
+ return this.WriteUInt16(value.DeviceCode)
+ + this.WriteFix16(value.MeasurementValue);
+ }
+
+ ///
+ /// Writes a named color
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteNamedColor(IccNamedColor value)
+ {
+ return this.WriteAsciiString(value.Name, 32, true)
+ + this.WriteArray(value.PcsCoordinates)
+ + this.WriteArray(value.DeviceCoordinates);
+ }
+
+ ///
+ /// Writes a profile description
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteProfileDescription(IccProfileDescription value)
+ {
+ return this.WriteUInt32(value.DeviceManufacturer)
+ + this.WriteUInt32(value.DeviceModel)
+ + this.WriteInt64((long)value.DeviceAttributes)
+ + this.WriteUInt32((uint)value.TechnologyInformation)
+ + this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode)
+ + this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceManufacturerInfo))
+ + this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode)
+ + this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceModelInfo));
+ }
+
+ ///
+ /// Writes a screening channel
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteScreeningChannel(IccScreeningChannel value)
+ {
+ return this.WriteFix16(value.Frequency)
+ + this.WriteFix16(value.Angle)
+ + this.WriteInt32((int)value.SpotShape);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs
new file mode 100644
index 000000000..fbb7065e6
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs
@@ -0,0 +1,248 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Text;
+
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// Writes a byte
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteByte(byte value)
+ {
+ this.dataStream.WriteByte(value);
+ return 1;
+ }
+
+ ///
+ /// Writes an ushort
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteUInt16(ushort value)
+ {
+ return this.WriteBytes((byte*)&value, 2);
+ }
+
+ ///
+ /// Writes a short
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteInt16(short value)
+ {
+ return this.WriteBytes((byte*)&value, 2);
+ }
+
+ ///
+ /// Writes an uint
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteUInt32(uint value)
+ {
+ return this.WriteBytes((byte*)&value, 4);
+ }
+
+ ///
+ /// Writes an int
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteInt32(int value)
+ {
+ return this.WriteBytes((byte*)&value, 4);
+ }
+
+ ///
+ /// Writes an ulong
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteUInt64(ulong value)
+ {
+ return this.WriteBytes((byte*)&value, 8);
+ }
+
+ ///
+ /// Writes a long
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteInt64(long value)
+ {
+ return this.WriteBytes((byte*)&value, 8);
+ }
+
+ ///
+ /// Writes a float
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteSingle(float value)
+ {
+ return this.WriteBytes((byte*)&value, 4);
+ }
+
+ ///
+ /// Writes a double
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public unsafe int WriteDouble(double value)
+ {
+ return this.WriteBytes((byte*)&value, 8);
+ }
+
+ ///
+ /// Writes a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteFix16(double value)
+ {
+ const double Max = short.MaxValue + (65535d / 65536d);
+ const double Min = short.MinValue;
+
+ value = value.Clamp(Min, Max);
+ value *= 65536d;
+
+ return this.WriteInt32((int)Math.Round(value, MidpointRounding.AwayFromZero));
+ }
+
+ ///
+ /// Writes an unsigned 32bit number with 16 value bits and 16 fractional bits
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteUFix16(double value)
+ {
+ const double Max = ushort.MaxValue + (65535d / 65536d);
+ const double Min = ushort.MinValue;
+
+ value = value.Clamp(Min, Max);
+ value *= 65536d;
+
+ return this.WriteUInt32((uint)Math.Round(value, MidpointRounding.AwayFromZero));
+ }
+
+ ///
+ /// Writes an unsigned 16bit number with 1 value bit and 15 fractional bits
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteU1Fix15(double value)
+ {
+ const double Max = 1 + (32767d / 32768d);
+ const double Min = 0;
+
+ value = value.Clamp(Min, Max);
+ value *= 32768d;
+
+ return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero));
+ }
+
+ ///
+ /// Writes an unsigned 16bit number with 8 value bits and 8 fractional bits
+ ///
+ /// The value to write
+ /// the number of bytes written
+ public int WriteUFix8(double value)
+ {
+ const double Max = byte.MaxValue + (255d / 256d);
+ const double Min = byte.MinValue;
+
+ value = value.Clamp(Min, Max);
+ value *= 256d;
+
+ return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero));
+ }
+
+ ///
+ /// Writes an ASCII encoded string
+ ///
+ /// the string to write
+ /// the number of bytes written
+ public int WriteAsciiString(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ return 0;
+ }
+
+ byte[] data = AsciiEncoding.GetBytes(value);
+ this.dataStream.Write(data, 0, data.Length);
+ return data.Length;
+ }
+
+ ///
+ /// Writes an ASCII encoded string resizes it to the given length
+ ///
+ /// The string to write
+ /// The desired length of the string (including potential null terminator)
+ /// If True, there will be a \0 added at the end
+ /// the number of bytes written
+ public int WriteAsciiString(string value, int length, bool ensureNullTerminator)
+ {
+ if (length == 0)
+ {
+ return 0;
+ }
+
+ Guard.MustBeGreaterThan(length, 0, nameof(length));
+
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+
+ byte paddingChar = (byte)' ';
+ int lengthAdjust = 0;
+
+ if (ensureNullTerminator)
+ {
+ paddingChar = 0;
+ lengthAdjust = 1;
+ }
+
+ value = value.Substring(0, Math.Min(length - lengthAdjust, value.Length));
+
+ byte[] textData = AsciiEncoding.GetBytes(value);
+ int actualLength = Math.Min(length - lengthAdjust, textData.Length);
+ this.dataStream.Write(textData, 0, actualLength);
+ for (int i = 0; i < length - actualLength; i++)
+ {
+ this.dataStream.WriteByte(paddingChar);
+ }
+
+ return length;
+ }
+
+ ///
+ /// Writes an UTF-16 big-endian encoded string
+ ///
+ /// the string to write
+ /// the number of bytes written
+ public int WriteUnicodeString(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ return 0;
+ }
+
+ byte[] data = Encoding.BigEndianUnicode.GetBytes(value);
+ this.dataStream.Write(data, 0, data.Length);
+ return data.Length;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs
new file mode 100644
index 000000000..8e8065cee
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs
@@ -0,0 +1,1003 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter
+ {
+ ///
+ /// Writes a tag data entry
+ ///
+ /// The entry to write
+ /// The table entry for the written data entry
+ /// The number of bytes written (excluding padding)
+ public int WriteTagDataEntry(IccTagDataEntry data, out IccTagTableEntry table)
+ {
+ uint offset = (uint)this.dataStream.Position;
+ int count = this.WriteTagDataEntry(data);
+ this.WritePadding();
+ table = new IccTagTableEntry(data.TagSignature, offset, (uint)count);
+ return count;
+ }
+
+ ///
+ /// Writes a tag data entry (without padding)
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteTagDataEntry(IccTagDataEntry entry)
+ {
+ int count = this.WriteTagDataEntryHeader(entry.Signature);
+
+ switch (entry.Signature)
+ {
+ case IccTypeSignature.Chromaticity:
+ count += this.WriteChromaticityTagDataEntry(entry as IccChromaticityTagDataEntry);
+ break;
+ case IccTypeSignature.ColorantOrder:
+ count += this.WriteColorantOrderTagDataEntry(entry as IccColorantOrderTagDataEntry);
+ break;
+ case IccTypeSignature.ColorantTable:
+ count += this.WriteColorantTableTagDataEntry(entry as IccColorantTableTagDataEntry);
+ break;
+ case IccTypeSignature.Curve:
+ count += this.WriteCurveTagDataEntry(entry as IccCurveTagDataEntry);
+ break;
+ case IccTypeSignature.Data:
+ count += this.WriteDataTagDataEntry(entry as IccDataTagDataEntry);
+ break;
+ case IccTypeSignature.DateTime:
+ count += this.WriteDateTimeTagDataEntry(entry as IccDateTimeTagDataEntry);
+ break;
+ case IccTypeSignature.Lut16:
+ count += this.WriteLut16TagDataEntry(entry as IccLut16TagDataEntry);
+ break;
+ case IccTypeSignature.Lut8:
+ count += this.WriteLut8TagDataEntry(entry as IccLut8TagDataEntry);
+ break;
+ case IccTypeSignature.LutAToB:
+ count += this.WriteLutAtoBTagDataEntry(entry as IccLutAToBTagDataEntry);
+ break;
+ case IccTypeSignature.LutBToA:
+ count += this.WriteLutBtoATagDataEntry(entry as IccLutBToATagDataEntry);
+ break;
+ case IccTypeSignature.Measurement:
+ count += this.WriteMeasurementTagDataEntry(entry as IccMeasurementTagDataEntry);
+ break;
+ case IccTypeSignature.MultiLocalizedUnicode:
+ count += this.WriteMultiLocalizedUnicodeTagDataEntry(entry as IccMultiLocalizedUnicodeTagDataEntry);
+ break;
+ case IccTypeSignature.MultiProcessElements:
+ count += this.WriteMultiProcessElementsTagDataEntry(entry as IccMultiProcessElementsTagDataEntry);
+ break;
+ case IccTypeSignature.NamedColor2:
+ count += this.WriteNamedColor2TagDataEntry(entry as IccNamedColor2TagDataEntry);
+ break;
+ case IccTypeSignature.ParametricCurve:
+ count += this.WriteParametricCurveTagDataEntry(entry as IccParametricCurveTagDataEntry);
+ break;
+ case IccTypeSignature.ProfileSequenceDesc:
+ count += this.WriteProfileSequenceDescTagDataEntry(entry as IccProfileSequenceDescTagDataEntry);
+ break;
+ case IccTypeSignature.ProfileSequenceIdentifier:
+ count += this.WriteProfileSequenceIdentifierTagDataEntry(entry as IccProfileSequenceIdentifierTagDataEntry);
+ break;
+ case IccTypeSignature.ResponseCurveSet16:
+ count += this.WriteResponseCurveSet16TagDataEntry(entry as IccResponseCurveSet16TagDataEntry);
+ break;
+ case IccTypeSignature.S15Fixed16Array:
+ count += this.WriteFix16ArrayTagDataEntry(entry as IccFix16ArrayTagDataEntry);
+ break;
+ case IccTypeSignature.Signature:
+ count += this.WriteSignatureTagDataEntry(entry as IccSignatureTagDataEntry);
+ break;
+ case IccTypeSignature.Text:
+ count += this.WriteTextTagDataEntry(entry as IccTextTagDataEntry);
+ break;
+ case IccTypeSignature.U16Fixed16Array:
+ count += this.WriteUFix16ArrayTagDataEntry(entry as IccUFix16ArrayTagDataEntry);
+ break;
+ case IccTypeSignature.UInt16Array:
+ count += this.WriteUInt16ArrayTagDataEntry(entry as IccUInt16ArrayTagDataEntry);
+ break;
+ case IccTypeSignature.UInt32Array:
+ count += this.WriteUInt32ArrayTagDataEntry(entry as IccUInt32ArrayTagDataEntry);
+ break;
+ case IccTypeSignature.UInt64Array:
+ count += this.WriteUInt64ArrayTagDataEntry(entry as IccUInt64ArrayTagDataEntry);
+ break;
+ case IccTypeSignature.UInt8Array:
+ count += this.WriteUInt8ArrayTagDataEntry(entry as IccUInt8ArrayTagDataEntry);
+ break;
+ case IccTypeSignature.ViewingConditions:
+ count += this.WriteViewingConditionsTagDataEntry(entry as IccViewingConditionsTagDataEntry);
+ break;
+ case IccTypeSignature.Xyz:
+ count += this.WriteXyzTagDataEntry(entry as IccXyzTagDataEntry);
+ break;
+
+ // V2 Types:
+ case IccTypeSignature.TextDescription:
+ count += this.WriteTextDescriptionTagDataEntry(entry as IccTextDescriptionTagDataEntry);
+ break;
+ case IccTypeSignature.CrdInfo:
+ count += this.WriteCrdInfoTagDataEntry(entry as IccCrdInfoTagDataEntry);
+ break;
+ case IccTypeSignature.Screening:
+ count += this.WriteScreeningTagDataEntry(entry as IccScreeningTagDataEntry);
+ break;
+ case IccTypeSignature.UcrBg:
+ count += this.WriteUcrBgTagDataEntry(entry as IccUcrBgTagDataEntry);
+ break;
+
+ // Unsupported or unknown
+ case IccTypeSignature.DeviceSettings:
+ case IccTypeSignature.NamedColor:
+ case IccTypeSignature.Unknown:
+ default:
+ count += this.WriteUnknownTagDataEntry(entry as IccUnknownTagDataEntry);
+ break;
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes the header of a
+ ///
+ /// The signature of the entry
+ /// The number of bytes written
+ public int WriteTagDataEntryHeader(IccTypeSignature signature)
+ {
+ return this.WriteUInt32((uint)signature)
+ + this.WriteEmpty(4);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUnknownTagDataEntry(IccUnknownTagDataEntry value)
+ {
+ return this.WriteArray(value.Data);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteChromaticityTagDataEntry(IccChromaticityTagDataEntry value)
+ {
+ int count = this.WriteUInt16((ushort)value.ChannelCount);
+ count += this.WriteUInt16((ushort)value.ColorantType);
+
+ for (int i = 0; i < value.ChannelCount; i++)
+ {
+ count += this.WriteUFix16(value.ChannelValues[i][0]);
+ count += this.WriteUFix16(value.ChannelValues[i][1]);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteColorantOrderTagDataEntry(IccColorantOrderTagDataEntry value)
+ {
+ return this.WriteUInt32((uint)value.ColorantNumber.Length)
+ + this.WriteArray(value.ColorantNumber);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteColorantTableTagDataEntry(IccColorantTableTagDataEntry value)
+ {
+ int count = this.WriteUInt32((uint)value.ColorantData.Length);
+ foreach (IccColorantTableEntry colorant in value.ColorantData)
+ {
+ count += this.WriteAsciiString(colorant.Name, 32, true);
+ count += this.WriteUInt16(colorant.Pcs1);
+ count += this.WriteUInt16(colorant.Pcs2);
+ count += this.WriteUInt16(colorant.Pcs3);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteCurveTagDataEntry(IccCurveTagDataEntry value)
+ {
+ int count = 0;
+
+ if (value.IsIdentityResponse)
+ {
+ count += this.WriteUInt32(0);
+ }
+ else if (value.IsGamma)
+ {
+ count += this.WriteUInt32(1);
+ count += this.WriteUFix8(value.Gamma);
+ }
+ else
+ {
+ count += this.WriteUInt32((uint)value.CurveData.Length);
+ for (int i = 0; i < value.CurveData.Length; i++)
+ {
+ count += this.WriteUInt16((ushort)((value.CurveData[i] * ushort.MaxValue) + 0.5f).Clamp(0, ushort.MaxValue));
+ }
+ }
+
+ return count;
+
+ // TODO: Page 48: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768).
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteDataTagDataEntry(IccDataTagDataEntry value)
+ {
+ return this.WriteEmpty(3)
+ + this.WriteByte((byte)(value.IsAscii ? 0x01 : 0x00))
+ + this.WriteArray(value.Data);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteDateTimeTagDataEntry(IccDateTimeTagDataEntry value)
+ {
+ return this.WriteDateTime(value.Value);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteLut16TagDataEntry(IccLut16TagDataEntry value)
+ {
+ int count = this.WriteByte((byte)value.InputValues.Length);
+ count += this.WriteByte((byte)value.OutputValues.Length);
+ count += this.WriteByte(value.ClutValues.GridPointCount[0]);
+ count += this.WriteEmpty(1);
+
+ count += this.WriteMatrix(value.Matrix, false);
+
+ count += this.WriteUInt16((ushort)value.InputValues[0].Values.Length);
+ count += this.WriteUInt16((ushort)value.OutputValues[0].Values.Length);
+
+ foreach (IccLut lut in value.InputValues)
+ {
+ count += this.WriteLut16(lut);
+ }
+
+ count += this.WriteClut16(value.ClutValues);
+
+ foreach (IccLut lut in value.OutputValues)
+ {
+ count += this.WriteLut16(lut);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteLut8TagDataEntry(IccLut8TagDataEntry value)
+ {
+ int count = this.WriteByte((byte)value.InputChannelCount);
+ count += this.WriteByte((byte)value.OutputChannelCount);
+ count += this.WriteByte((byte)value.ClutValues.Values[0].Length);
+ count += this.WriteEmpty(1);
+
+ count += this.WriteMatrix(value.Matrix, false);
+
+ foreach (IccLut lut in value.InputValues)
+ {
+ count += this.WriteLut8(lut);
+ }
+
+ count += this.WriteClut8(value.ClutValues);
+
+ foreach (IccLut lut in value.OutputValues)
+ {
+ count += this.WriteLut8(lut);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteLutAtoBTagDataEntry(IccLutAToBTagDataEntry value)
+ {
+ long start = this.dataStream.Position - 8; // 8 is the tag header size
+
+ int count = this.WriteByte((byte)value.InputChannelCount);
+ count += this.WriteByte((byte)value.OutputChannelCount);
+ count += this.WriteEmpty(2);
+
+ long bCurveOffset = 0;
+ long matrixOffset = 0;
+ long mCurveOffset = 0;
+ long clutOffset = 0;
+ long aCurveOffset = 0;
+
+ // Jump over offset values
+ long offsetpos = this.dataStream.Position;
+ this.dataStream.Position += 5 * 4;
+
+ if (value.CurveB != null)
+ {
+ bCurveOffset = this.dataStream.Position;
+ count += this.WriteCurves(value.CurveB);
+ count += this.WritePadding();
+ }
+
+ if (value.Matrix3x1 != null && value.Matrix3x3 != null)
+ {
+ matrixOffset = this.dataStream.Position;
+ count += this.WriteMatrix(value.Matrix3x3.Value, false);
+ count += this.WriteMatrix(value.Matrix3x1.Value, false);
+ count += this.WritePadding();
+ }
+
+ if (value.CurveM != null)
+ {
+ mCurveOffset = this.dataStream.Position;
+ count += this.WriteCurves(value.CurveM);
+ count += this.WritePadding();
+ }
+
+ if (value.ClutValues != null)
+ {
+ clutOffset = this.dataStream.Position;
+ count += this.WriteClut(value.ClutValues);
+ count += this.WritePadding();
+ }
+
+ if (value.CurveA != null)
+ {
+ aCurveOffset = this.dataStream.Position;
+ count += this.WriteCurves(value.CurveA);
+ count += this.WritePadding();
+ }
+
+ // Set offset values
+ long lpos = this.dataStream.Position;
+ this.dataStream.Position = offsetpos;
+
+ if (bCurveOffset != 0)
+ {
+ bCurveOffset -= start;
+ }
+
+ if (matrixOffset != 0)
+ {
+ matrixOffset -= start;
+ }
+
+ if (mCurveOffset != 0)
+ {
+ mCurveOffset -= start;
+ }
+
+ if (clutOffset != 0)
+ {
+ clutOffset -= start;
+ }
+
+ if (aCurveOffset != 0)
+ {
+ aCurveOffset -= start;
+ }
+
+ count += this.WriteUInt32((uint)bCurveOffset);
+ count += this.WriteUInt32((uint)matrixOffset);
+ count += this.WriteUInt32((uint)mCurveOffset);
+ count += this.WriteUInt32((uint)clutOffset);
+ count += this.WriteUInt32((uint)aCurveOffset);
+
+ this.dataStream.Position = lpos;
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteLutBtoATagDataEntry(IccLutBToATagDataEntry value)
+ {
+ long start = this.dataStream.Position - 8; // 8 is the tag header size
+
+ int count = this.WriteByte((byte)value.InputChannelCount);
+ count += this.WriteByte((byte)value.OutputChannelCount);
+ count += this.WriteEmpty(2);
+
+ long bCurveOffset = 0;
+ long matrixOffset = 0;
+ long mCurveOffset = 0;
+ long clutOffset = 0;
+ long aCurveOffset = 0;
+
+ // Jump over offset values
+ long offsetpos = this.dataStream.Position;
+ this.dataStream.Position += 5 * 4;
+
+ if (value.CurveB != null)
+ {
+ bCurveOffset = this.dataStream.Position;
+ count += this.WriteCurves(value.CurveB);
+ count += this.WritePadding();
+ }
+
+ if (value.Matrix3x1 != null && value.Matrix3x3 != null)
+ {
+ matrixOffset = this.dataStream.Position;
+ count += this.WriteMatrix(value.Matrix3x3.Value, false);
+ count += this.WriteMatrix(value.Matrix3x1.Value, false);
+ count += this.WritePadding();
+ }
+
+ if (value.CurveM != null)
+ {
+ mCurveOffset = this.dataStream.Position;
+ count += this.WriteCurves(value.CurveM);
+ count += this.WritePadding();
+ }
+
+ if (value.ClutValues != null)
+ {
+ clutOffset = this.dataStream.Position;
+ count += this.WriteClut(value.ClutValues);
+ count += this.WritePadding();
+ }
+
+ if (value.CurveA != null)
+ {
+ aCurveOffset = this.dataStream.Position;
+ count += this.WriteCurves(value.CurveA);
+ count += this.WritePadding();
+ }
+
+ // Set offset values
+ long lpos = this.dataStream.Position;
+ this.dataStream.Position = offsetpos;
+
+ if (bCurveOffset != 0)
+ {
+ bCurveOffset -= start;
+ }
+
+ if (matrixOffset != 0)
+ {
+ matrixOffset -= start;
+ }
+
+ if (mCurveOffset != 0)
+ {
+ mCurveOffset -= start;
+ }
+
+ if (clutOffset != 0)
+ {
+ clutOffset -= start;
+ }
+
+ if (aCurveOffset != 0)
+ {
+ aCurveOffset -= start;
+ }
+
+ count += this.WriteUInt32((uint)bCurveOffset);
+ count += this.WriteUInt32((uint)matrixOffset);
+ count += this.WriteUInt32((uint)mCurveOffset);
+ count += this.WriteUInt32((uint)clutOffset);
+ count += this.WriteUInt32((uint)aCurveOffset);
+
+ this.dataStream.Position = lpos;
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteMeasurementTagDataEntry(IccMeasurementTagDataEntry value)
+ {
+ return this.WriteUInt32((uint)value.Observer)
+ + this.WriteXyzNumber(value.XyzBacking)
+ + this.WriteUInt32((uint)value.Geometry)
+ + this.WriteUFix16(value.Flare)
+ + this.WriteUInt32((uint)value.Illuminant);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteMultiLocalizedUnicodeTagDataEntry(IccMultiLocalizedUnicodeTagDataEntry value)
+ {
+ long start = this.dataStream.Position - 8; // 8 is the tag header size
+
+ int cultureCount = value.Texts.Length;
+
+ int count = this.WriteUInt32((uint)cultureCount);
+ count += this.WriteUInt32(12); // One record has always 12 bytes size
+
+ // Jump over position table
+ long tpos = this.dataStream.Position;
+ this.dataStream.Position += cultureCount * 12;
+
+ uint[] offset = new uint[cultureCount];
+ int[] lengths = new int[cultureCount];
+
+ for (int i = 0; i < cultureCount; i++)
+ {
+ offset[i] = (uint)(this.dataStream.Position - start);
+ count += lengths[i] = this.WriteUnicodeString(value.Texts[i].Text);
+ }
+
+ // Write position table
+ long lpos = this.dataStream.Position;
+ this.dataStream.Position = tpos;
+ for (int i = 0; i < cultureCount; i++)
+ {
+ string[] code = value.Texts[i].Culture.Name.Split('-');
+
+ count += this.WriteAsciiString(code[0].ToLower(), 2, false);
+ count += this.WriteAsciiString(code[1].ToUpper(), 2, false);
+
+ count += this.WriteUInt32((uint)lengths[i]);
+ count += this.WriteUInt32(offset[i]);
+ }
+
+ this.dataStream.Position = lpos;
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteMultiProcessElementsTagDataEntry(IccMultiProcessElementsTagDataEntry value)
+ {
+ long start = this.dataStream.Position - 8; // 8 is the tag header size
+
+ int count = this.WriteUInt16((ushort)value.InputChannelCount);
+ count += this.WriteUInt16((ushort)value.OutputChannelCount);
+ count += this.WriteUInt32((uint)value.Data.Length);
+
+ // Jump over position table
+ long tpos = this.dataStream.Position;
+ this.dataStream.Position += value.Data.Length * 8;
+
+ IccPositionNumber[] posTable = new IccPositionNumber[value.Data.Length];
+ for (int i = 0; i < value.Data.Length; i++)
+ {
+ uint offset = (uint)(this.dataStream.Position - start);
+ int size = this.WriteMultiProcessElement(value.Data[i]);
+ count += this.WritePadding();
+ posTable[i] = new IccPositionNumber(offset, (uint)size);
+ count += size;
+ }
+
+ // Write position table
+ long lpos = this.dataStream.Position;
+ this.dataStream.Position = tpos;
+ foreach (IccPositionNumber pos in posTable)
+ {
+ count += this.WritePositionNumber(pos);
+ }
+
+ this.dataStream.Position = lpos;
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteNamedColor2TagDataEntry(IccNamedColor2TagDataEntry value)
+ {
+ int count = this.WriteInt32(value.VendorFlags)
+ + this.WriteUInt32((uint)value.Colors.Length)
+ + this.WriteUInt32((uint)value.CoordinateCount)
+ + this.WriteAsciiString(value.Prefix, 32, true)
+ + this.WriteAsciiString(value.Suffix, 32, true);
+
+ foreach (IccNamedColor color in value.Colors)
+ {
+ count += this.WriteNamedColor(color);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteParametricCurveTagDataEntry(IccParametricCurveTagDataEntry value)
+ {
+ return this.WriteParametricCurve(value.Curve);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteProfileSequenceDescTagDataEntry(IccProfileSequenceDescTagDataEntry value)
+ {
+ int count = this.WriteUInt32((uint)value.Descriptions.Length);
+ foreach (IccProfileDescription desc in value.Descriptions)
+ {
+ count += this.WriteProfileDescription(desc);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifierTagDataEntry value)
+ {
+ long start = this.dataStream.Position - 8; // 8 is the tag header size
+ int length = value.Data.Length;
+
+ int count = this.WriteUInt32((uint)length);
+
+ // Jump over position table
+ long tablePosition = this.dataStream.Position;
+ this.dataStream.Position += length * 8;
+ IccPositionNumber[] table = new IccPositionNumber[length];
+
+ for (int i = 0; i < length; i++)
+ {
+ uint offset = (uint)(this.dataStream.Position - start);
+ int size = this.WriteProfileId(value.Data[i].Id);
+ size += this.WriteTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.Data[i].Description));
+ size += this.WritePadding();
+ table[i] = new IccPositionNumber(offset, (uint)size);
+ count += size;
+ }
+
+ // Write position table
+ long lpos = this.dataStream.Position;
+ this.dataStream.Position = tablePosition;
+ foreach (IccPositionNumber pos in table)
+ {
+ count += this.WritePositionNumber(pos);
+ }
+
+ this.dataStream.Position = lpos;
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteResponseCurveSet16TagDataEntry(IccResponseCurveSet16TagDataEntry value)
+ {
+ long start = this.dataStream.Position - 8;
+
+ int count = this.WriteUInt16(value.ChannelCount);
+ count += this.WriteUInt16((ushort)value.Curves.Length);
+
+ // Jump over position table
+ long tablePosition = this.dataStream.Position;
+ this.dataStream.Position += value.Curves.Length * 4;
+
+ uint[] offset = new uint[value.Curves.Length];
+
+ for (int i = 0; i < value.Curves.Length; i++)
+ {
+ offset[i] = (uint)(this.dataStream.Position - start);
+ count += this.WriteResponseCurve(value.Curves[i]);
+ count += this.WritePadding();
+ }
+
+ // Write position table
+ long lpos = this.dataStream.Position;
+ this.dataStream.Position = tablePosition;
+ count += this.WriteArray(offset);
+
+ this.dataStream.Position = lpos;
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteFix16ArrayTagDataEntry(IccFix16ArrayTagDataEntry value)
+ {
+ int count = 0;
+ for (int i = 0; i < value.Data.Length; i++)
+ {
+ count += this.WriteFix16(value.Data[i] * 256d);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteSignatureTagDataEntry(IccSignatureTagDataEntry value)
+ {
+ return this.WriteAsciiString(value.SignatureData, 4, false);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteTextTagDataEntry(IccTextTagDataEntry value)
+ {
+ return this.WriteAsciiString(value.Text);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUFix16ArrayTagDataEntry(IccUFix16ArrayTagDataEntry value)
+ {
+ int count = 0;
+ for (int i = 0; i < value.Data.Length; i++)
+ {
+ count += this.WriteUFix16(value.Data[i]);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUInt16ArrayTagDataEntry(IccUInt16ArrayTagDataEntry value)
+ {
+ return this.WriteArray(value.Data);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUInt32ArrayTagDataEntry(IccUInt32ArrayTagDataEntry value)
+ {
+ return this.WriteArray(value.Data);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUInt64ArrayTagDataEntry(IccUInt64ArrayTagDataEntry value)
+ {
+ return this.WriteArray(value.Data);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUInt8ArrayTagDataEntry(IccUInt8ArrayTagDataEntry value)
+ {
+ return this.WriteArray(value.Data);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteViewingConditionsTagDataEntry(IccViewingConditionsTagDataEntry value)
+ {
+ return this.WriteXyzNumber(value.IlluminantXyz)
+ + this.WriteXyzNumber(value.SurroundXyz)
+ + this.WriteUInt32((uint)value.Illuminant);
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteXyzTagDataEntry(IccXyzTagDataEntry value)
+ {
+ int count = 0;
+ for (int i = 0; i < value.Data.Length; i++)
+ {
+ count += this.WriteXyzNumber(value.Data[i]);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteTextDescriptionTagDataEntry(IccTextDescriptionTagDataEntry value)
+ {
+ int size, count = 0;
+
+ if (value.Ascii == null)
+ {
+ count += this.WriteUInt32(0);
+ }
+ else
+ {
+ this.dataStream.Position += 4;
+ count += size = this.WriteAsciiString(value.Ascii + '\0');
+ this.dataStream.Position -= size + 4;
+ count += this.WriteUInt32((uint)size);
+ this.dataStream.Position += size;
+ }
+
+ if (value.Unicode == null)
+ {
+ count += this.WriteUInt32(0);
+ count += this.WriteUInt32(0);
+ }
+ else
+ {
+ this.dataStream.Position += 8;
+ count += size = this.WriteUnicodeString(value.Unicode + '\0');
+ this.dataStream.Position -= size + 8;
+ count += this.WriteUInt32(value.UnicodeLanguageCode);
+ count += this.WriteUInt32((uint)value.Unicode.Length + 1);
+ this.dataStream.Position += size;
+ }
+
+ if (value.ScriptCode == null)
+ {
+ count += this.WriteUInt16(0);
+ count += this.WriteByte(0);
+ count += this.WriteEmpty(67);
+ }
+ else
+ {
+ this.dataStream.Position += 3;
+ count += size = this.WriteAsciiString(value.ScriptCode, 67, true);
+ this.dataStream.Position -= size + 3;
+ count += this.WriteUInt16(value.ScriptCodeCode);
+ count += this.WriteByte((byte)(value.ScriptCode.Length > 66 ? 67 : value.ScriptCode.Length + 1));
+ this.dataStream.Position += size;
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteCrdInfoTagDataEntry(IccCrdInfoTagDataEntry value)
+ {
+ int count = 0;
+ WriteString(value.PostScriptProductName);
+ WriteString(value.RenderingIntent0Crd);
+ WriteString(value.RenderingIntent1Crd);
+ WriteString(value.RenderingIntent2Crd);
+ WriteString(value.RenderingIntent3Crd);
+
+ return count;
+
+ void WriteString(string text)
+ {
+ int textLength;
+ if (string.IsNullOrEmpty(text))
+ {
+ textLength = 0;
+ }
+ else
+ {
+ textLength = text.Length + 1; // + 1 for null terminator
+ }
+
+ count += this.WriteUInt32((uint)textLength);
+ count += this.WriteAsciiString(text, textLength, true);
+ }
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteScreeningTagDataEntry(IccScreeningTagDataEntry value)
+ {
+ int count = 0;
+
+ count += this.WriteInt32((int)value.Flags);
+ count += this.WriteUInt32((uint)value.Channels.Length);
+ for (int i = 0; i < value.Channels.Length; i++)
+ {
+ count += this.WriteScreeningChannel(value.Channels[i]);
+ }
+
+ return count;
+ }
+
+ ///
+ /// Writes a
+ ///
+ /// The entry to write
+ /// The number of bytes written
+ public int WriteUcrBgTagDataEntry(IccUcrBgTagDataEntry value)
+ {
+ int count = 0;
+
+ count += this.WriteUInt32((uint)value.UcrCurve.Length);
+ for (int i = 0; i < value.UcrCurve.Length; i++)
+ {
+ count += this.WriteUInt16(value.UcrCurve[i]);
+ }
+
+ count += this.WriteUInt32((uint)value.BgCurve.Length);
+ for (int i = 0; i < value.BgCurve.Length; i++)
+ {
+ count += this.WriteUInt16(value.BgCurve[i]);
+ }
+
+ count += this.WriteAsciiString(value.Description + '\0');
+
+ return count;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs
new file mode 100644
index 000000000..a9a65b80b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs
@@ -0,0 +1,253 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.IO;
+ using System.Text;
+
+ ///
+ /// Provides methods to write ICC data types
+ ///
+ internal sealed partial class IccDataWriter : IDisposable
+ {
+ private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian;
+ private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII");
+
+ ///
+ /// The underlying stream where the data is written to
+ ///
+ private readonly MemoryStream dataStream;
+
+ ///
+ /// To detect redundant calls
+ ///
+ private bool isDisposed;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public IccDataWriter()
+ {
+ this.dataStream = new MemoryStream();
+ }
+
+ ///
+ /// Gets the currently written length in bytes
+ ///
+ public uint Length => (uint)this.dataStream.Length;
+
+ ///
+ /// Gets the written data bytes
+ ///
+ /// The written data
+ public byte[] GetData()
+ {
+ return this.dataStream.ToArray();
+ }
+
+ ///
+ /// Sets the writing position to the given value
+ ///
+ /// The new index position
+ public void SetIndex(int index)
+ {
+ this.dataStream.Position = index;
+ }
+
+ ///
+ /// Writes a byte array
+ ///
+ /// The array to write
+ /// The number of bytes written
+ public int WriteArray(byte[] data)
+ {
+ this.dataStream.Write(data, 0, data.Length);
+ return data.Length;
+ }
+
+ ///
+ /// Writes a ushort array
+ ///
+ /// The array to write
+ /// The number of bytes written
+ public int WriteArray(ushort[] data)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ this.WriteUInt16(data[i]);
+ }
+
+ return data.Length * 2;
+ }
+
+ ///
+ /// Writes a short array
+ ///
+ /// The array to write
+ /// The number of bytes written
+ public int WriteArray(short[] data)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ this.WriteInt16(data[i]);
+ }
+
+ return data.Length * 2;
+ }
+
+ ///
+ /// Writes a uint array
+ ///
+ /// The array to write
+ /// The number of bytes written
+ public int WriteArray(uint[] data)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ this.WriteUInt32(data[i]);
+ }
+
+ return data.Length * 4;
+ }
+
+ ///
+ /// Writes an int array
+ ///
+ /// The array to write
+ /// The number of bytes written
+ public int WriteArray(int[] data)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ this.WriteInt32(data[i]);
+ }
+
+ return data.Length * 4;
+ }
+
+ ///
+ /// Writes a ulong array
+ ///
+ /// The array to write
+ /// The number of bytes written
+ public int WriteArray(ulong[] data)
+ {
+ for (int i = 0; i < data.Length; i++)
+ {
+ this.WriteUInt64(data[i]);
+ }
+
+ return data.Length * 8;
+ }
+
+ ///
+ /// Write a number of empty bytes
+ ///
+ /// The number of bytes to write
+ /// The number of bytes written
+ public int WriteEmpty(int length)
+ {
+ for (int i = 0; i < length; i++)
+ {
+ this.dataStream.WriteByte(0);
+ }
+
+ return length;
+ }
+
+ ///
+ /// Writes empty bytes to a 4-byte margin
+ ///
+ /// The number of bytes written
+ public int WritePadding()
+ {
+ int p = 4 - ((int)this.dataStream.Position % 4);
+ return this.WriteEmpty(p >= 4 ? 0 : p);
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.Dispose(true);
+ }
+
+ ///
+ /// Writes given bytes from pointer
+ ///
+ /// Pointer to the bytes to write
+ /// The number of bytes to write
+ /// The number of bytes written
+ private unsafe int WriteBytes(byte* data, int length)
+ {
+ if (IsLittleEndian)
+ {
+ for (int i = length - 1; i >= 0; i--)
+ {
+ this.dataStream.WriteByte(data[i]);
+ }
+ }
+ else
+ {
+ this.WriteBytesDirect(data, length);
+ }
+
+ return length;
+ }
+
+ ///
+ /// Writes given bytes from pointer ignoring endianness
+ ///
+ /// Pointer to the bytes to write
+ /// The number of bytes to write
+ /// The number of bytes written
+ private unsafe int WriteBytesDirect(byte* data, int length)
+ {
+ for (int i = 0; i < length; i++)
+ {
+ this.dataStream.WriteByte(data[i]);
+ }
+
+ return length;
+ }
+
+ ///
+ /// Writes curve data
+ ///
+ /// The curves to write
+ /// The number of bytes written
+ private int WriteCurves(IccTagDataEntry[] curves)
+ {
+ int count = 0;
+ foreach (IccTagDataEntry curve in curves)
+ {
+ if (curve.Signature != IccTypeSignature.Curve && curve.Signature != IccTypeSignature.ParametricCurve)
+ {
+ throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" +
+ $" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries");
+ }
+
+ count += this.WriteTagDataEntry(curve);
+ count += this.WritePadding();
+ }
+
+ return count;
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (!this.isDisposed)
+ {
+ if (disposing)
+ {
+ this.dataStream?.Dispose();
+ }
+
+ this.isDisposed = true;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs
new file mode 100644
index 000000000..066cbe848
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccClutDataType.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Color lookup table data type
+ ///
+ internal enum IccClutDataType
+ {
+ ///
+ /// 32bit floating point
+ ///
+ Float,
+
+ ///
+ /// 8bit unsigned integer (byte)
+ ///
+ UInt8,
+
+ ///
+ /// 16bit unsigned integer (ushort)
+ ///
+ UInt16,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs
new file mode 100644
index 000000000..3f57ded74
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorSpaceType.cs
@@ -0,0 +1,138 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Color Space Type
+ ///
+ public enum IccColorSpaceType : uint
+ {
+ ///
+ /// CIE XYZ
+ ///
+ CieXyz = 0x58595A20, // XYZ
+
+ ///
+ /// CIE Lab
+ ///
+ CieLab = 0x4C616220, // Lab
+
+ ///
+ /// CIE Luv
+ ///
+ CieLuv = 0x4C757620, // Luv
+
+ ///
+ /// YCbCr
+ ///
+ YCbCr = 0x59436272, // YCbr
+
+ ///
+ /// CIE Yxy
+ ///
+ CieYxy = 0x59787920, // Yxy
+
+ ///
+ /// RGB
+ ///
+ Rgb = 0x52474220, // RGB
+
+ ///
+ /// Gray
+ ///
+ Gray = 0x47524159, // GRAY
+
+ ///
+ /// HSV
+ ///
+ Hsv = 0x48535620, // HSV
+
+ ///
+ /// HLS
+ ///
+ Hls = 0x484C5320, // HLS
+
+ ///
+ /// CMYK
+ ///
+ Cmyk = 0x434D594B, // CMYK
+
+ ///
+ /// CMY
+ ///
+ Cmy = 0x434D5920, // CMY
+
+ ///
+ /// Generic 2 channel color
+ ///
+ Color2 = 0x32434C52, // 2CLR
+
+ ///
+ /// Generic 3 channel color
+ ///
+ Color3 = 0x33434C52, // 3CLR
+
+ ///
+ /// Generic 4 channel color
+ ///
+ Color4 = 0x34434C52, // 4CLR
+
+ ///
+ /// Generic 5 channel color
+ ///
+ Color5 = 0x35434C52, // 5CLR
+
+ ///
+ /// Generic 6 channel color
+ ///
+ Color6 = 0x36434C52, // 6CLR
+
+ ///
+ /// Generic 7 channel color
+ ///
+ Color7 = 0x37434C52, // 7CLR
+
+ ///
+ /// Generic 8 channel color
+ ///
+ Color8 = 0x38434C52, // 8CLR
+
+ ///
+ /// Generic 9 channel color
+ ///
+ Color9 = 0x39434C52, // 9CLR
+
+ ///
+ /// Generic 10 channel color
+ ///
+ Color10 = 0x41434C52, // ACLR
+
+ ///
+ /// Generic 11 channel color
+ ///
+ Color11 = 0x42434C52, // BCLR
+
+ ///
+ /// Generic 12 channel color
+ ///
+ Color12 = 0x43434C52, // CCLR
+
+ ///
+ /// Generic 13 channel color
+ ///
+ Color13 = 0x44434C52, // DCLR
+
+ ///
+ /// Generic 14 channel color
+ ///
+ Color14 = 0x45434C52, // ECLR
+
+ ///
+ /// Generic 15 channel color
+ ///
+ Color15 = 0x46434C52, // FCLR
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs
new file mode 100644
index 000000000..251a848b7
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccColorantEncoding.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Colorant Encoding
+ ///
+ internal enum IccColorantEncoding : ushort
+ {
+ ///
+ /// Unknown colorant encoding
+ ///
+ Unknown = 0x0000,
+
+ ///
+ /// ITU-R BT.709-2 colorant encoding
+ ///
+ ItuRBt709_2 = 0x0001,
+
+ ///
+ /// SMPTE RP145 colorant encoding
+ ///
+ SmpteRp145 = 0x0002,
+
+ ///
+ /// EBU Tech.3213-E colorant encoding
+ ///
+ EbuTech3213E = 0x0003,
+
+ ///
+ /// P22 colorant encoding
+ ///
+ P22 = 0x0004,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs
new file mode 100644
index 000000000..14515c113
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs
@@ -0,0 +1,62 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Curve Measurement Encodings
+ ///
+ internal enum IccCurveMeasurementEncodings : uint
+ {
+ ///
+ /// ISO 5-3 densitometer response. This is the accepted standard for
+ /// reflection densitometers for measuring photographic color prints
+ ///
+ StatusA = 0x53746141, // StaA
+
+ ///
+ /// ISO 5-3 densitometer response which is the accepted standard in
+ /// Europe for color reflection densitometers
+ ///
+ StatusE = 0x53746145, // StaE
+
+ ///
+ /// ISO 5-3 densitometer response commonly referred to as narrow band
+ /// or interference-type response.
+ ///
+ StatusI = 0x53746149, // StaI
+
+ ///
+ /// ISO 5-3 wide band color reflection densitometer response which is
+ /// the accepted standard in the United States for color reflection densitometers
+ ///
+ StatusT = 0x53746154, // StaT
+
+ ///
+ /// ISO 5-3 densitometer response for measuring color negatives
+ ///
+ StatusM = 0x5374614D, // StaM
+
+ ///
+ /// DIN 16536-2 densitometer response, with no polarizing filter
+ ///
+ DinE = 0x434E2020, // DN
+
+ ///
+ /// DIN 16536-2 densitometer response, with polarizing filter
+ ///
+ DinEPol = 0x434E2050, // DNP
+
+ ///
+ /// DIN 16536-2 narrow band densitometer response, with no polarizing filter
+ ///
+ DinI = 0x434E4E20, // DNN
+
+ ///
+ /// DIN 16536-2 narrow band densitometer response, with polarizing filter
+ ///
+ DinIPol = 0x434E4E50, // DNNP
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs
new file mode 100644
index 000000000..77ded0d1b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccCurveSegmentSignature.cs
@@ -0,0 +1,23 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Curve Segment Signature
+ ///
+ internal enum IccCurveSegmentSignature : uint
+ {
+ ///
+ /// Curve defined by a formula
+ ///
+ FormulaCurve = 0x70617266, // parf
+
+ ///
+ /// Curve defined by multiple segments
+ ///
+ SampledCurve = 0x73616D66, // samf
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs
new file mode 100644
index 000000000..a4ca4befa
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDataType.cs
@@ -0,0 +1,86 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Enumerates the basic data types as defined in ICC.1:2010 version 4.3.0.0
+ /// Section 4.2 to 4.15
+ ///
+ internal enum IccDataType
+ {
+ ///
+ /// A 12-byte value representation of the time and date
+ ///
+ DateTime,
+
+ ///
+ /// A single-precision 32-bit floating-point as specified in IEEE 754,
+ /// excluding un-normalized s, infinities, and not a "" (NaN) values
+ ///
+ Float32,
+
+ ///
+ /// Positions of some data elements are indicated using a position offset with the data element's size.
+ ///
+ Position,
+
+ ///
+ /// An 8-byte value, used to associate a normalized device code with a measurement value
+ ///
+ Response16,
+
+ ///
+ /// A fixed signed 4-byte (32-bit) quantity which has 16 fractional bits
+ ///
+ S15Fixed16,
+
+ ///
+ /// A fixed unsigned 4-byte (32-bit) quantity having 16 fractional bits
+ ///
+ U16Fixed16,
+
+ ///
+ /// A fixed unsigned 2-byte (16-bit) quantity having15 fractional bits
+ ///
+ U1Fixed15,
+
+ ///
+ /// A fixed unsigned 2-byte (16-bit) quantity having 8 fractional bits
+ ///
+ U8Fixed8,
+
+ ///
+ /// An unsigned 2-byte (16-bit) integer
+ ///
+ UInt16,
+
+ ///
+ /// An unsigned 4-byte (32-bit) integer
+ ///
+ UInt32,
+
+ ///
+ /// An unsigned 8-byte (64-bit) integer
+ ///
+ UInt64,
+
+ ///
+ /// An unsigned 1-byte (8-bit) integer
+ ///
+ UInt8,
+
+ ///
+ /// A set of three fixed signed 4-byte (32-bit) quantities used to encode CIEXYZ, nCIEXYZ, and PCSXYZ tristimulus values
+ ///
+ Xyz,
+
+ ///
+ /// Alpha-numeric values, and other input and output codes, shall conform to the American Standard Code for
+ /// Information Interchange (ASCII) specified in ISO/IEC 646.
+ ///
+ Ascii
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs
new file mode 100644
index 000000000..c57cf4f97
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccDeviceAttribute.cs
@@ -0,0 +1,58 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Device attributes. Can be combined with a logical OR
+ /// The least-significant 32 bits are defined by the ICC,
+ /// the rest can be used for vendor specific values
+ ///
+ [Flags]
+ public enum IccDeviceAttribute : long
+ {
+ ///
+ /// Opacity transparent
+ ///
+ OpacityTransparent = 1 << 0,
+
+ ///
+ /// Opacity reflective
+ ///
+ OpacityReflective = 0,
+
+ ///
+ /// Reflectivity matte
+ ///
+ ReflectivityMatte = 1 << 1,
+
+ ///
+ /// Reflectivity glossy
+ ///
+ ReflectivityGlossy = 0,
+
+ ///
+ /// Polarity negative
+ ///
+ PolarityNegative = 1 << 2,
+
+ ///
+ /// Polarity positive
+ ///
+ PolarityPositive = 0,
+
+ ///
+ /// Chroma black and white
+ ///
+ ChromaBlackWhite = 1 << 3,
+
+ ///
+ /// Chroma color
+ ///
+ ChromaColor = 0,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs
new file mode 100644
index 000000000..eacc1eb28
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccFormulaCurveType.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Formula curve segment type
+ ///
+ internal enum IccFormulaCurveType : ushort
+ {
+ ///
+ /// Type 1: Y = (a * X + b)^γ + c
+ ///
+ Type1 = 0,
+
+ ///
+ /// Type 1: Y = a * log10 (b * X^γ + c) + d
+ ///
+ Type2 = 1,
+
+ ///
+ /// Type 3: Y = a * b^(c * X + d) + e
+ ///
+ Type3 = 2
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs
new file mode 100644
index 000000000..fdfb78049
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMeasurementGeometry.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Measurement Geometry
+ ///
+ internal enum IccMeasurementGeometry : uint
+ {
+ ///
+ /// Unknown geometry
+ ///
+ Unknown = 0,
+
+ ///
+ /// Geometry of 0°:45° or 45°:0°
+ ///
+ Degree0To45Or45To0 = 1,
+
+ ///
+ /// Geometry of 0°:d or d:0°
+ ///
+ Degree0ToDOrDTo0 = 2,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs
new file mode 100644
index 000000000..8ab690b64
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Multi process element signature
+ ///
+ internal enum IccMultiProcessElementSignature : uint
+ {
+ ///
+ /// Set of curves
+ ///
+ CurveSet = 0x6D666C74, // cvst
+
+ ///
+ /// Matrix transformation
+ ///
+ Matrix = 0x6D617466, // matf
+
+ ///
+ /// Color lookup table
+ ///
+ Clut = 0x636C7574, // clut
+
+ ///
+ /// Reserved for future expansion. Do not use!
+ ///
+ BAcs = 0x62414353, // bACS
+
+ ///
+ /// Reserved for future expansion. Do not use!
+ ///
+ EAcs = 0x65414353, // eACS
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs
new file mode 100644
index 000000000..823b41340
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccParametricCurveType.cs
@@ -0,0 +1,46 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Formula curve segment type
+ ///
+ internal enum IccParametricCurveType : ushort
+ {
+ ///
+ /// Type 1: Y = X^g
+ ///
+ Type1 = 0,
+
+ ///
+ /// CIE 122-1996:
+ /// For X >= -b/a: Y =(a * X + b)^g
+ /// For X $lt; -b/a: Y = 0
+ ///
+ Cie122_1996 = 1,
+
+ ///
+ /// IEC 61966-3:
+ /// For X >= -b/a: Y =(a * X + b)^g + c
+ /// For X $lt; -b/a: Y = c
+ ///
+ Iec61966_3 = 2,
+
+ ///
+ /// IEC 61966-2-1 (sRGB):
+ /// For X >= d: Y =(a * X + b)^g
+ /// For X $lt; d: Y = c * X
+ ///
+ SRgb = 3,
+
+ ///
+ /// Type 5:
+ /// For X >= d: Y =(a * X + b)^g + c
+ /// For X $lt; d: Y = c * X + f
+ ///
+ Type5 = 4,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs
new file mode 100644
index 000000000..8fdeb3f41
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccPrimaryPlatformType.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Enumerates the primary platform/operating system framework for which the profile was created
+ ///
+ public enum IccPrimaryPlatformType : uint
+ {
+ ///
+ /// No platform identified
+ ///
+ NotIdentified = 0x00000000,
+
+ ///
+ /// Apple Computer, Inc.
+ ///
+ AppleComputerInc = 0x4150504C, // APPL
+
+ ///
+ /// Microsoft Corporation
+ ///
+ MicrosoftCorporation = 0x4D534654, // MSFT
+
+ ///
+ /// Silicon Graphics, Inc.
+ ///
+ SiliconGraphicsInc = 0x53474920, // SGI
+
+ ///
+ /// Sun Microsystems, Inc.
+ ///
+ SunMicrosystemsInc = 0x53554E57, // SUNW
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs
new file mode 100644
index 000000000..9fb0b51f3
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileClass.cs
@@ -0,0 +1,65 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Profile Class Name
+ ///
+ public enum IccProfileClass : uint
+ {
+ ///
+ /// Input profiles are generally used with devices such as scanners and
+ /// digital cameras. The types of profiles available for use as Input
+ /// profiles are N-component LUT-based, Three-component matrix-based,
+ /// and monochrome.
+ ///
+ InputDevice = 0x73636E72, // scnr
+
+ ///
+ /// This class of profiles represents display devices such as monitors.
+ /// The types of profiles available for use as Display profiles are
+ /// N-component LUT-based, Three-component matrix-based, and monochrome.
+ ///
+ DisplayDevice = 0x6D6E7472, // mntr
+
+ ///
+ /// Output profiles are used to support devices such as printers and
+ /// film recorders. The types of profiles available for use as Output
+ /// profiles are N-component LUT-based and Monochrome.
+ ///
+ OutputDevice = 0x70727472, // prtr
+
+ ///
+ /// This profile contains a pre-evaluated transform that cannot be undone,
+ /// which represents a one-way link or connection between devices. It does
+ /// not represent any device model nor can it be embedded into images.
+ ///
+ DeviceLink = 0x6C696E6B, // link
+
+ ///
+ /// This profile provides the relevant information to perform a transformation
+ /// between colour encodings and the PCS. This type of profile is based on
+ /// modelling rather than device measurement or characterization data.
+ /// ColorSpace profiles may be embedded in images.
+ ///
+ ColorSpace = 0x73706163, // spac
+
+ ///
+ /// This profile represents abstract transforms and does not represent any
+ /// device model. Colour transformations using Abstract profiles are performed
+ /// from PCS to PCS. Abstract profiles cannot be embedded in images.
+ ///
+ Abstract = 0x61627374, // abst
+
+ ///
+ /// NamedColor profiles can be thought of as sibling profiles to device profiles.
+ /// For a given device there would be one or more device profiles to handle
+ /// process colour conversions and one or more named colour profiles to handle
+ /// named colours.
+ ///
+ NamedColor = 0x6E6D636C, // nmcl
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs
new file mode 100644
index 000000000..aa8df88bd
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileFlag.cs
@@ -0,0 +1,43 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Profile flags. Can be combined with a logical OR.
+ /// The least-significant 16 bits are reserved for the ICC,
+ /// the rest can be used for vendor specific values
+ ///
+ [Flags]
+ public enum IccProfileFlag : int
+ {
+ ///
+ /// No flags (equivalent to NotEmbedded and Independent)
+ ///
+ None = 0,
+
+ ///
+ /// Profile is embedded within another file
+ ///
+ Embedded = 1 << 0,
+
+ ///
+ /// Profile is embedded within another file
+ ///
+ NotEmbedded = 0,
+
+ ///
+ /// Profile cannot be used independently of the embedded colour data
+ ///
+ NotIndependent = 1 << 1,
+
+ ///
+ /// Profile can be used independently of the embedded colour data
+ ///
+ Independent = 0,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs
new file mode 100644
index 000000000..6eab5b3fe
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs
@@ -0,0 +1,364 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+// ReSharper disable InconsistentNaming
+namespace ImageSharp
+{
+ ///
+ /// Enumerates the ICC Profile Tags as defined in ICC.1:2010 version 4.3.0.0
+ /// Section 9
+ ///
+ /// Each tag value represent the size of the tag in the profile.
+ ///
+ ///
+ public enum IccProfileTag : uint
+ {
+ ///
+ /// Unknown tag
+ ///
+ Unknown,
+
+ ///
+ /// A2B0 - This tag defines a colour transform from Device, Colour Encoding or PCS, to PCS, or a colour transform
+ /// from Device 1 to Device 2, using lookup table tag element structures
+ ///
+ AToB0 = 0x41324230,
+
+ ///
+ /// A2B2 - This tag describes the colour transform from Device or Colour Encoding to PCS using lookup table tag element structures
+ ///
+ AToB1 = 0x41324231,
+
+ ///
+ /// A2B2 - This tag describes the colour transform from Device or Colour Encoding to PCS using lookup table tag element structures
+ ///
+ AToB2 = 0x41324232,
+
+ ///
+ /// bXYZ - This tag contains the third column in the matrix used in matrix/TRC transforms.
+ ///
+ BlueMatrixColumn = 0x6258595A,
+
+ ///
+ /// bTRC - This tag contains the blue channel tone reproduction curve. The first element represents no colorant (white) or
+ /// phosphor (black) and the last element represents 100 % colorant (blue) or 100 % phosphor (blue).
+ ///
+ BlueTrc = 0x62545243,
+
+ ///
+ /// B2A0 - This tag defines a colour transform from PCS to Device or Colour Encoding using the lookup table tag element structures
+ ///
+ BToA0 = 0x42324130,
+
+ ///
+ /// B2A1 - This tag defines a colour transform from PCS to Device or Colour Encoding using the lookup table tag element structures.
+ ///
+ BToA1 = 0x42324131,
+
+ ///
+ /// B2A2 - This tag defines a colour transform from PCS to Device or Colour Encoding using the lookup table tag element structures.
+ ///
+ BToA2 = 0x42324132,
+
+ ///
+ /// B2D0 - This tag defines a colour transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and
+ /// provides a means to override the BToA0 tag.
+ ///
+ BToD0 = 0x42324430,
+
+ ///
+ /// B2D1 - This tag defines a colour transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and
+ /// provides a means to override the BToA1 tag.
+ ///
+ BToD1 = 0x42324431,
+
+ ///
+ /// B2D2 - This tag defines a colour transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and
+ /// provides a means to override the BToA2 tag.
+ ///
+ BToD2 = 0x42324432,
+
+ ///
+ /// B2D3 - This tag defines a colour transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and
+ /// provides a means to override the BToA1 tag.
+ ///
+ BToD3 = 0x42324433,
+
+ ///
+ /// calt - This tag contains the profile calibration date and time. This allows applications and utilities to verify if this profile matches a
+ /// vendor's profile and how recently calibration has been performed.
+ ///
+ CalibrationDateTime = 0x63616C74,
+
+ ///
+ /// targ - This tag contains the name of the registered characterization data set, or it contains the measurement
+ /// data for a characterization target.
+ ///
+ CharTarget = 0x74617267,
+
+ ///
+ /// chad - This tag contains a matrix, which shall be invertible, and which converts an nCIEXYZ colour, measured using the actual illumination
+ /// conditions and relative to the actual adopted white, to an nCIEXYZ colour relative to the PCS adopted white
+ ///
+ ChromaticAdaptation = 0x63686164,
+
+ ///
+ /// chrm - This tag contains the type and the data of the phosphor/colorant chromaticity set used.
+ ///
+ Chromaticity = 0x6368726D,
+
+ ///
+ /// clro - This tag specifies the laydown order of colorants.
+ ///
+ ColorantOrder = 0x636C726F,
+
+ ///
+ /// clrt
+ ///
+ ColorantTable = 0x636C7274,
+
+ ///
+ /// clot - This tag identifies the colorants used in the profile by a unique name and set of PCSXYZ or PCSLAB values.
+ /// When used in DeviceLink profiles only the PCSLAB values shall be permitted.
+ ///
+ ColorantTableOut = 0x636C6F74,
+
+ ///
+ /// ciis - This tag indicates the image state of PCS colorimetry produced using the colorimetric intent transforms.
+ ///
+ ColorimetricIntentImageStat = 0x63696973,
+
+ ///
+ /// cprt - This tag contains the text copyright information for the profile.
+ ///
+ Copyright = 0x63707274,
+
+ ///
+ /// crdi - Removed in V4
+ ///
+ CrdInfo = 0x63726469,
+
+ ///
+ /// data - Removed in V4
+ ///
+ Data = 0x64617461,
+
+ ///
+ /// dtim - Removed in V4
+ ///
+ DateTime = 0x6474696D,
+
+ ///
+ /// dmnd - This tag describes the structure containing invariant and localizable
+ /// versions of the device manufacturer for display
+ ///
+ DeviceManufacturerDescription = 0x646D6E64,
+
+ ///
+ /// dmdd - This tag describes the structure containing invariant and localizable
+ /// versions of the device model for display.
+ ///
+ DeviceModelDescription = 0x646D6464,
+
+ ///
+ /// devs - Removed in V4
+ ///
+ DeviceSettings = 0x64657673,
+
+ ///
+ /// D2B0 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded
+ /// input range, output range and transform, and provides a means to override the AToB0 tag
+ ///
+ DToB0 = 0x44324230,
+
+ ///
+ /// D2B1 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded
+ /// input range, output range and transform, and provides a means to override the AToB1 tag
+ ///
+ DToB1 = 0x44324230,
+
+ ///
+ /// D2B2 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded
+ /// input range, output range and transform, and provides a means to override the AToB1 tag
+ ///
+ DToB2 = 0x44324230,
+
+ ///
+ /// D2B3 - This tag defines a colour transform from Device to PCS. It supports float32Number-encoded
+ /// input range, output range and transform, and provides a means to override the AToB1 tag
+ ///
+ DToB3 = 0x44324230,
+
+ ///
+ /// gamt - This tag provides a table in which PCS values are the input and a single
+ /// output value for each input value is the output. If the output value is 0, the PCS colour is in-gamut.
+ /// If the output is non-zero, the PCS colour is out-of-gamut
+ ///
+ Gamut = 0x67616D74,
+
+ ///
+ /// kTRC - This tag contains the grey tone reproduction curve. The tone reproduction curve provides the necessary
+ /// information to convert between a single device channel and the PCSXYZ or PCSLAB encoding.
+ ///
+ GrayTrc = 0x6b545243,
+
+ ///
+ /// gXYZ - This tag contains the second column in the matrix, which is used in matrix/TRC transforms.
+ ///
+ GreenMatrixColumn = 0x6758595A,
+
+ ///
+ /// gTRC - This tag contains the green channel tone reproduction curve. The first element represents no
+ /// colorant (white) or phosphor (black) and the last element represents 100 % colorant (green) or 100 % phosphor (green).
+ ///
+ GreenTrc = 0x67545243,
+
+ ///
+ /// lumi - This tag contains the absolute luminance of emissive devices in candelas per square metre as described by the Y channel.
+ ///
+ Luminance = 0x6C756d69,
+
+ ///
+ /// meas - This tag describes the alternative measurement specification, such as a D65 illuminant instead of the default D50.
+ ///
+ Measurement = 0x6D656173,
+
+ ///
+ /// bkpt - Removed in V4
+ ///
+ MediaBlackPoint = 0x626B7074,
+
+ ///
+ /// wtpt - This tag, which is used for generating the ICC-absolute colorimetric intent, specifies the chromatically
+ /// adapted nCIEXYZ tristimulus values of the media white point.
+ ///
+ MediaWhitePoint = 0x77747074,
+
+ ///
+ /// ncol - OBSOLETE, use
+ ///
+ NamedColor = 0x6E636f6C,
+
+ ///
+ /// ncl2 - This tag contains the named colour information providing a PCS and optional device representation
+ /// for a list of named colours.
+ ///
+ NamedColor2 = 0x6E636C32,
+
+ ///
+ /// resp - This tag describes the structure containing a description of the device response for which the profile is intended.
+ ///
+ OutputResponse = 0x72657370,
+
+ ///
+ /// rig0 - There is only one standard reference medium gamut, as defined in ISO 12640-3
+ ///
+ PerceptualRenderingIntentGamut = 0x72696730,
+
+ ///
+ /// pre0 - This tag contains the preview transformation from PCS to device space and back to the PCS.
+ ///
+ Preview0 = 0x70726530,
+
+ ///
+ /// pre1 - This tag defines the preview transformation from PCS to device space and back to the PCS.
+ ///
+ Preview1 = 0x70726531,
+
+ ///
+ /// pre2 - This tag contains the preview transformation from PCS to device space and back to the PCS.
+ ///
+ Preview2 = 0x70726532,
+
+ ///
+ /// desc - This tag describes the structure containing invariant and localizable versions of the profile
+ /// description for display.
+ ///
+ ProfileDescription = 0x64657363,
+
+ ///
+ /// pseq - This tag describes the structure containing a description of the profile sequence from source to
+ /// destination, typically used with the DeviceLink profile.
+ ///
+ ProfileSequenceDescription = 0x70736571,
+
+ ///
+ /// psd0 - Removed in V4
+ ///
+ PostScript2Crd0 = 0x70736430,
+
+ ///
+ /// psd1 - Removed in V4
+ ///
+ PostScript2Crd1 = 0x70736431,
+
+ ///
+ /// psd2 - Removed in V4
+ ///
+ PostScript2Crd2 = 0x70736432,
+
+ ///
+ /// psd3 - Removed in V4
+ ///
+ PostScript2Crd3 = 0x70736433,
+
+ ///
+ /// ps2s - Removed in V4
+ ///
+ PostScript2Csa = 0x70733273,
+
+ ///
+ /// psd2i- Removed in V4
+ ///
+ PostScript2RenderingIntent = 0x70733269,
+
+ ///
+ /// rXYZ - This tag contains the first column in the matrix, which is used in matrix/TRC transforms.
+ ///
+ RedMatrixColumn = 0x7258595A,
+
+ ///
+ /// This tag contains the red channel tone reproduction curve. The first element represents no colorant
+ /// (white) or phosphor (black) and the last element represents 100 % colorant (red) or 100 % phosphor (red).
+ ///
+ RedTrc = 0x72545243,
+
+ ///
+ /// rig2 - There is only one standard reference medium gamut, as defined in ISO 12640-3.
+ ///
+ SaturationRenderingIntentGamut = 0x72696732,
+
+ ///
+ /// scrd - Removed in V4
+ ///
+ ScreeningDescription = 0x73637264,
+
+ ///
+ /// scrn - Removed in V4
+ ///
+ Screening = 0x7363726E,
+
+ ///
+ /// tech - The device technology signature
+ ///
+ Technology = 0x74656368,
+
+ ///
+ /// bfd - Removed in V4
+ ///
+ UcrBgSpecification = 0x62666420,
+
+ ///
+ /// vued - This tag describes the structure containing invariant and localizable
+ /// versions of the viewing conditions.
+ ///
+ ViewingCondDescription = 0x76756564,
+
+ ///
+ /// view - This tag defines the viewing conditions parameters
+ ///
+ ViewingConditions = 0x76696577,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs
new file mode 100644
index 000000000..10e0fbac9
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccRenderingIntent.cs
@@ -0,0 +1,44 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Rendering intent
+ ///
+ public enum IccRenderingIntent : uint
+ {
+ ///
+ /// In perceptual transforms the PCS values represent hypothetical
+ /// measurements of a colour reproduction on the reference reflective
+ /// medium. By extension, for the perceptual intent, the PCS represents
+ /// the appearance of that reproduction as viewed in the reference viewing
+ /// environment by a human observer adapted to that environment. The exact
+ /// colour rendering of the perceptual intent is vendor specific.
+ ///
+ Perceptual = 0,
+
+ ///
+ /// Transformations for this intent shall re-scale the in-gamut,
+ /// chromatically adapted tristimulus values such that the white
+ /// point of the actual medium is mapped to the PCS white point
+ /// (for either input or output)
+ ///
+ MediaRelativeColorimetric = 1,
+
+ ///
+ /// The exact colour rendering of the saturation intent is vendor
+ /// specific and involves compromises such as trading off
+ /// preservation of hue in order to preserve the vividness of pure colours.
+ ///
+ Saturation = 2,
+
+ ///
+ /// Transformations for this intent shall leave the chromatically
+ /// adapted nCIEXYZ tristimulus values of the in-gamut colours unchanged.
+ ///
+ AbsoluteColorimetric = 3,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs
new file mode 100644
index 000000000..2938d4469
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningFlag.cs
@@ -0,0 +1,41 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Screening flags. Can be combined with a logical OR.
+ ///
+ [Flags]
+ internal enum IccScreeningFlag : int
+ {
+ ///
+ /// No flags (equivalent to NotDefaultScreens and UnitLinesPerCm)
+ ///
+ None = 0,
+
+ ///
+ /// Use printer default screens
+ ///
+ DefaultScreens = 1 << 0,
+
+ ///
+ /// Don't use printer default screens
+ ///
+ NotDefaultScreens = 0,
+
+ ///
+ /// Frequency units in Lines/Inch
+ ///
+ UnitLinesPerInch = 1 << 1,
+
+ ///
+ /// Frequency units in Lines/cm
+ ///
+ UnitLinesPerCm = 0,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs
new file mode 100644
index 000000000..0d24c3ae5
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccScreeningSpotType.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Enumerates the screening spot types
+ ///
+ internal enum IccScreeningSpotType : int
+ {
+ ///
+ /// Unknown spot type
+ ///
+ Unknown = 0,
+
+ ///
+ /// Default printer spot type
+ ///
+ PrinterDefault = 1,
+
+ ///
+ /// Round stop type
+ ///
+ Round = 2,
+
+ ///
+ /// Diamond spot type
+ ///
+ Diamond = 3,
+
+ ///
+ /// Ellipse spot type
+ ///
+ Ellipse = 4,
+
+ ///
+ /// Line spot type
+ ///
+ Line = 5,
+
+ ///
+ /// Square spot type
+ ///
+ Square = 6,
+
+ ///
+ /// Cross spot type
+ ///
+ Cross = 7,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs
new file mode 100644
index 000000000..5fc2fa228
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccSignatureName.cs
@@ -0,0 +1,178 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Signature Name
+ ///
+ internal enum IccSignatureName : uint
+ {
+ ///
+ /// Unknown signature
+ ///
+ Unknown = 0,
+
+ ///
+ /// Scene Colorimetry Estimates
+ ///
+ SceneColorimetryEstimates = 0x73636F65, // scoe
+
+ ///
+ /// Scene Appearance Estimates
+ ///
+ SceneAppearanceEstimates = 0x73617065, // sape
+
+ ///
+ /// Focal Plane Colorimetry Estimates
+ ///
+ FocalPlaneColorimetryEstimates = 0x66706365, // fpce
+
+ ///
+ /// Reflection Hardcopy Original Colorimetry
+ ///
+ ReflectionHardcopyOriginalColorimetry = 0x72686F63, // rhoc
+
+ ///
+ /// Reflection Print Output Colorimetry
+ ///
+ ReflectionPrintOutputColorimetry = 0x72706F63, // rpoc
+
+ ///
+ /// Perceptual Reference Medium Gamut
+ ///
+ PerceptualReferenceMediumGamut = 0x70726D67, // prmg
+
+ ///
+ /// Film Scanner
+ ///
+ FilmScanner = 0x6673636E, // fscn
+
+ ///
+ /// Digital Camera
+ ///
+ DigitalCamera = 0x6463616D, // dcam
+
+ ///
+ /// Reflective Scanner
+ ///
+ ReflectiveScanner = 0x7273636E, // rscn
+
+ ///
+ /// InkJet Printer
+ ///
+ InkJetPrinter = 0x696A6574, // ijet
+
+ ///
+ /// Thermal Wax Printer
+ ///
+ ThermalWaxPrinter = 0x74776178, // twax
+
+ ///
+ /// Electrophotographic Printer
+ ///
+ ElectrophotographicPrinter = 0x6570686F, // epho
+
+ ///
+ /// Electrostatic Printer
+ ///
+ ElectrostaticPrinter = 0x65737461, // esta
+
+ ///
+ /// Dye Sublimation Printer
+ ///
+ DyeSublimationPrinter = 0x64737562, // dsub
+
+ ///
+ /// Photographic Paper Printer
+ ///
+ PhotographicPaperPrinter = 0x7270686F, // rpho
+
+ ///
+ /// Film Writer
+ ///
+ FilmWriter = 0x6670726E, // fprn
+
+ ///
+ /// Video Monitor
+ ///
+ VideoMonitor = 0x7669646D, // vidm
+
+ ///
+ /// Video Camera
+ ///
+ VideoCamera = 0x76696463, // vidc
+
+ ///
+ /// Projection Television
+ ///
+ ProjectionTelevision = 0x706A7476, // pjtv
+
+ ///
+ /// Cathode Ray Tube Display
+ ///
+ CathodeRayTubeDisplay = 0x43525420, // CRT
+
+ ///
+ /// Passive Matrix Display
+ ///
+ PassiveMatrixDisplay = 0x504D4420, // PMD
+
+ ///
+ /// Active Matrix Display
+ ///
+ ActiveMatrixDisplay = 0x414D4420, // AMD
+
+ ///
+ /// Photo CD
+ ///
+ PhotoCD = 0x4B504344, // KPCD
+
+ ///
+ /// Photographic Image Setter
+ ///
+ PhotographicImageSetter = 0x696D6773, // imgs
+
+ ///
+ /// Gravure
+ ///
+ Gravure = 0x67726176, // grav
+
+ ///
+ /// Offset Lithography
+ ///
+ OffsetLithography = 0x6F666673, // offs
+
+ ///
+ /// Silkscreen
+ ///
+ Silkscreen = 0x73696C6B, // silk
+
+ ///
+ /// Flexography
+ ///
+ Flexography = 0x666C6578, // flex
+
+ ///
+ /// Motion Picture Film Scanner
+ ///
+ MotionPictureFilmScanner = 0x6D706673, // mpfs
+
+ ///
+ /// Motion Picture Film Recorder
+ ///
+ MotionPictureFilmRecorder = 0x6D706672, // mpfr
+
+ ///
+ /// Digital Motion Picture Camera
+ ///
+ DigitalMotionPictureCamera = 0x646D7063, // dmpc
+
+ ///
+ /// Digital Cinema Projector
+ ///
+ DigitalCinemaProjector = 0x64636A70, // dcpj
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs
new file mode 100644
index 000000000..3526887ed
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardIlluminant.cs
@@ -0,0 +1,58 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Standard Illuminant
+ ///
+ internal enum IccStandardIlluminant : uint
+ {
+ ///
+ /// Unknown illuminant
+ ///
+ Unknown = 0,
+
+ ///
+ /// D50 illuminant
+ ///
+ D50 = 1,
+
+ ///
+ /// D65 illuminant
+ ///
+ D65 = 2,
+
+ ///
+ /// D93 illuminant
+ ///
+ D93 = 3,
+
+ ///
+ /// F2 illuminant
+ ///
+ F2 = 4,
+
+ ///
+ /// D55 illuminant
+ ///
+ D55 = 5,
+
+ ///
+ /// A illuminant
+ ///
+ A = 6,
+
+ ///
+ /// D50 illuminant
+ ///
+ EquiPowerE = 7,
+
+ ///
+ /// F8 illuminant
+ ///
+ F8 = 8,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs
new file mode 100644
index 000000000..0efc5fd40
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccStandardObserver.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Standard Observer
+ ///
+ internal enum IccStandardObserver : uint
+ {
+ ///
+ /// Unknown observer
+ ///
+ Unkown = 0,
+
+ ///
+ /// CIE 1931 observer
+ ///
+ Cie1931Observer = 1,
+
+ ///
+ /// CIE 1964 observer
+ ///
+ Cie1964Observer = 2,
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs
new file mode 100644
index 000000000..a42cc8cd1
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Enums/IccTypeSignature.cs
@@ -0,0 +1,273 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Type Signature
+ ///
+ public enum IccTypeSignature : uint
+ {
+ ///
+ /// Unknown type signature
+ ///
+ Unknown,
+
+ ///
+ /// The chromaticity tag type provides basic chromaticity data and type of
+ /// phosphors or colorants of a monitor to applications and utilities
+ ///
+ Chromaticity = 0x6368726D,
+
+ ///
+ /// This is an optional tag which specifies the laydown order in which colorants
+ /// will be printed on an n-colorant device. The laydown order may be the same
+ /// as the channel generation order listed in the colorantTableTag or the channel
+ /// order of a colour encoding type such as CMYK, in which case this tag is not
+ /// needed. When this is not the case (for example, ink-towers sometimes use
+ /// the order KCMY), this tag may be used to specify the laydown order of the
+ /// colorants
+ ///
+ ColorantOrder = 0x636c726f,
+
+ ///
+ /// The purpose of this tag is to identify the colorants used in the profile
+ /// by a unique name and set of PCSXYZ or PCSLAB values to give the colorant
+ /// an unambiguous value. The first colorant listed is the colorant of the
+ /// first device channel of a LUT tag. The second colorant listed is the
+ /// colorant of the second device channel of a LUT tag, and so on
+ ///
+ ColorantTable = 0x636c7274,
+
+ ///
+ /// The curveType embodies a one-dimensional function which maps an input
+ /// value in the domain of the function to an output value in the range
+ /// of the function
+ ///
+ Curve = 0x63757276,
+
+ ///
+ /// The dataType is a simple data structure that contains either 7-bit ASCII
+ /// or binary data
+ ///
+ Data = 0x64617461,
+
+ ///
+ /// Date and time defined by 6 unsigned 16bit integers
+ /// (year, month, day, hour, minute, second)
+ ///
+ DateTime = 0x6474696D,
+
+ ///
+ /// This structure represents a colour transform using tables with 16-bit
+ /// precision. This type contains four processing elements: a 3 × 3 matrix
+ /// (which shall be the identity matrix unless the input colour space is
+ /// PCSXYZ), a set of one-dimensional input tables, a multi-dimensional
+ /// lookup table, and a set of one-dimensional output tables
+ ///
+ Lut16 = 0x6D667432,
+
+ ///
+ /// This structure represents a colour transform using tables of 8-bit
+ /// precision. This type contains four processing elements: a 3 × 3 matrix
+ /// (which shall be the identity matrix unless the input colour space is
+ /// PCSXYZ), a set of one-dimensional input tables, a multi-dimensional
+ /// lookup table, and a set of one-dimensional output tables.
+ ///
+ Lut8 = 0x6D667431,
+
+ ///
+ /// This structure represents a colour transform. The type contains up
+ /// to five processing elements which are stored in the AToBTag tag
+ /// in the following order: a set of one-dimensional curves, a 3 × 3
+ /// matrix with offset terms, a set of one-dimensional curves, a
+ /// multi-dimensional lookup table, and a set of one-dimensional
+ /// output curves
+ ///
+ LutAToB = 0x6D414220,
+
+ ///
+ /// This structure represents a colour transform. The type contains
+ /// up to five processing elements which are stored in the BToATag
+ /// in the following order: a set of one-dimensional curves, a 3 × 3
+ /// matrix with offset terms, a set of one-dimensional curves, a
+ /// multi-dimensional lookup table, and a set of one-dimensional curves.
+ ///
+ LutBToA = 0x6D424120,
+
+ ///
+ /// This information refers only to the internal
+ /// profile data and is meant to provide profile makers an alternative
+ /// to the default measurement specifications
+ ///
+ Measurement = 0x6D656173,
+
+ ///
+ /// This tag structure contains a set of records each referencing a
+ /// multilingual Unicode string associated with a profile. Each string
+ /// is referenced in a separate record with the information about what
+ /// language and region the string is for.
+ ///
+ MultiLocalizedUnicode = 0x6D6C7563,
+
+ ///
+ /// This structure represents a colour transform, containing a sequence
+ /// of processing elements. The processing elements contained in the
+ /// structure are defined in the structure itself, allowing for a flexible
+ /// structure. Currently supported processing elements are: a set of one
+ /// dimensional curves, a matrix with offset terms, and a multidimensional
+ /// lookup table (CLUT). Other processing element types may be added in
+ /// the future. Each type of processing element may be contained any
+ /// number of times in the structure.
+ ///
+ MultiProcessElements = 0x6D706574,
+
+ ///
+ /// This type is a count value and array of structures that provide colour
+ /// coordinates for colour names. For each named colour, a PCS and optional
+ /// device representation of the colour are given. Both representations are
+ /// 16-bit values and PCS values shall be relative colorimetric. The device
+ /// representation corresponds to the header’s "data colour space" field.
+ /// This representation should be consistent with the "number of device
+ /// coordinates" field in the namedColor2Type. If this field is 0, device
+ /// coordinates are not provided. The PCS representation corresponds to the
+ /// header's PCS field. The PCS representation is always provided. Colour
+ /// names are fixed-length, 32-byte fields including null termination. In
+ /// order to maintain maximum portability, it is strongly recommended that
+ /// special characters of the 7-bit ASCII set not be used.
+ ///
+ NamedColor2 = 0x6E636C32,
+
+ ///
+ /// This type describes a one-dimensional curve by specifying one of a
+ /// predefined set of functions using the parameters.
+ ///
+ ParametricCurve = 0x70617261,
+
+ ///
+ /// This type is an array of structures, each of which contains information
+ /// from the header fields and tags from the original profiles which were
+ /// combined to create the final profile. The order of the structures is
+ /// the order in which the profiles were combined and includes a structure
+ /// for the final profile. This provides a description of the profile
+ /// sequence from source to destination, typically used with the DeviceLink
+ /// profile.
+ ///
+ ProfileSequenceDesc = 0x70736571,
+
+ ///
+ /// This type is an array of structures, each of which contains information
+ /// for identification of a profile used in a sequence.
+ ///
+ ProfileSequenceIdentifier = 0x70736964,
+
+ ///
+ /// The purpose of this tag type is to provide a mechanism to relate physical
+ /// colorant amounts with the normalized device codes produced by lut8Type,
+ /// lut16Type, lutAToBType, lutBToAType or multiProcessElementsType tags
+ /// so that corrections can be made for variation in the device without
+ /// having to produce a new profile. The mechanism can be used by applications
+ /// to allow users with relatively inexpensive and readily available
+ /// instrumentation to apply corrections to individual output colour
+ /// channels in order to achieve consistent results.
+ ///
+ ResponseCurveSet16 = 0x72637332,
+
+ ///
+ /// Array of signed floating point numbers with 1 sign bit, 15 value bits and 16 fractional bits
+ ///
+ S15Fixed16Array = 0x73663332,
+
+ ///
+ /// The signatureType contains a 4-byte sequence. Sequences of less than four
+ /// characters are padded at the end with spaces. Typically this type is used
+ /// for registered tags that can be displayed on many development systems as
+ /// a sequence of four characters.
+ ///
+ Signature = 0x73696720,
+
+ ///
+ /// Simple ASCII text
+ ///
+ Text = 0x74657874,
+
+ ///
+ /// Array of unsigned floating point numbers with 16 value bits and 16 fractional bits
+ ///
+ U16Fixed16Array = 0x75663332,
+
+ ///
+ /// Array of unsigned 16bit integers (ushort)
+ ///
+ UInt16Array = 0x75693136,
+
+ ///
+ /// Array of unsigned 32bit integers (uint)
+ ///
+ UInt32Array = 0x75693332,
+
+ ///
+ /// Array of unsigned 64bit integers (ulong)
+ ///
+ UInt64Array = 0x75693634,
+
+ ///
+ /// Array of unsigned 8bit integers (byte)
+ ///
+ UInt8Array = 0x75693038,
+
+ ///
+ /// This type represents a set of viewing condition parameters.
+ ///
+ ViewingConditions = 0x76696577,
+
+ ///
+ /// 3 floating point values describing a XYZ color value
+ ///
+ Xyz = 0x58595A20,
+
+ ///
+ /// REMOVED IN V4 - The textDescriptionType is a complex structure that contains three
+ /// types of text description structures: 7-bit ASCII, Unicode and ScriptCode. Since no
+ /// single standard method for specifying localizable character sets exists across
+ /// the major platform vendors, including all three provides access for the major
+ /// operating systems. The 7-bit ASCII description is to be an invariant,
+ /// nonlocalizable name for consistent reference. It is preferred that both the
+ /// Unicode and ScriptCode structures be properly localized.
+ ///
+ TextDescription = 0x64657363,
+
+ ///
+ /// REMOVED IN V4 - This type contains the PostScript product name to which this
+ /// profile corresponds and the names of the companion CRDs
+ ///
+ CrdInfo = 0x63726469,
+
+ ///
+ /// REMOVED IN V4 - The screeningType describes various screening parameters including
+ /// screen frequency, screening angle, and spot shape
+ ///
+ Screening = 0x7363726E,
+
+ ///
+ /// REMOVED IN V4 - This type contains curves representing the under color removal and
+ /// black generation and a text string which is a general description of the method
+ /// used for the UCR and BG
+ ///
+ UcrBg = 0x62666420,
+
+ ///
+ /// REMOVED IN V4 - This type is an array of structures each of which contains
+ /// platform-specific information about the settings of the device for which
+ /// this profile is valid. This type is not supported.
+ ///
+ DeviceSettings = 0x64657673, // not supported
+
+ ///
+ /// REMOVED IN V2 - use instead. This type is not supported.
+ ///
+ NamedColor = 0x6E636F6C, // not supported
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs b/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs
new file mode 100644
index 000000000..54fe7c764
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Exceptions/InvalidIccProfileException.cs
@@ -0,0 +1,42 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Represents an error that happened while reading or writing a corrupt/invalid ICC profile
+ ///
+ public class InvalidIccProfileException : Exception
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public InvalidIccProfileException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the error
+ public InvalidIccProfileException(string message)
+ : base(message)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the error
+ /// The exception that is the cause of the current exception, or a null reference
+ /// (Nothing in Visual Basic) if no inner exception is specified
+ public InvalidIccProfileException(string message, Exception inner)
+ : base(message, inner)
+ {
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
new file mode 100644
index 000000000..978d5bc24
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs
@@ -0,0 +1,193 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Collections.Generic;
+#if !NETSTANDARD1_1
+ using System.Security.Cryptography;
+#endif
+
+ ///
+ /// Represents an ICC profile
+ ///
+ public sealed class IccProfile
+ {
+ ///
+ /// The byte array to read the ICC profile from
+ ///
+ private byte[] data;
+
+ ///
+ /// The backing file for the property
+ ///
+ private List entries;
+
+ ///
+ /// ICC profile header
+ ///
+ private IccProfileHeader header;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public IccProfile()
+ : this((byte[])null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw ICC profile data
+ public IccProfile(byte[] data)
+ {
+ this.data = data;
+ }
+
+ ///
+ /// Initializes a new instance of the class
+ /// by making a copy from another ICC profile.
+ ///
+ /// The other ICC profile, where the clone should be made from.
+ /// is null.>
+ public IccProfile(IccProfile other)
+ {
+ Guard.NotNull(other, nameof(other));
+
+ // TODO: Do we need to copy anything else?
+ this.data = other.data;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The profile header
+ /// The actual profile data
+ internal IccProfile(IccProfileHeader header, IEnumerable entries)
+ {
+ Guard.NotNull(header, nameof(header));
+ Guard.NotNull(entries, nameof(entries));
+
+ this.header = header;
+ this.entries = new List(entries);
+ }
+
+ ///
+ /// Gets or sets the profile header
+ ///
+ public IccProfileHeader Header
+ {
+ get
+ {
+ this.InitializeHeader();
+ return this.header;
+ }
+
+ set => this.header = value;
+ }
+
+ ///
+ /// Gets the actual profile data
+ ///
+ public List Entries
+ {
+ get
+ {
+ this.InitializeEntries();
+ return this.entries;
+ }
+ }
+
+#if !NETSTANDARD1_1
+
+ ///
+ /// Calculates the MD5 hash value of an ICC profile header
+ ///
+ /// The data of which to calculate the hash value
+ /// The calculated hash
+ public static IccProfileId CalculateHash(byte[] data)
+ {
+ Guard.NotNull(data, nameof(data));
+ Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header");
+
+ byte[] header = new byte[128];
+ Buffer.BlockCopy(data, 0, header, 0, 128);
+
+ using (var md5 = MD5.Create())
+ {
+ // Zero out some values
+ Array.Clear(header, 44, 4); // Profile flags
+ Array.Clear(header, 64, 4); // Rendering Intent
+ Array.Clear(header, 84, 16); // Profile ID
+
+ // Calculate hash
+ byte[] hash = md5.ComputeHash(data);
+
+ // Read values from hash
+ var reader = new IccDataReader(hash);
+ return reader.ReadProfileId();
+ }
+ }
+
+#endif
+
+ ///
+ /// Extends the profile with additional data.
+ ///
+ /// The array containing addition profile data.
+ public void Extend(byte[] bytes)
+ {
+ int currentLength = this.data.Length;
+ Array.Resize(ref this.data, currentLength + bytes.Length);
+ Buffer.BlockCopy(bytes, 0, this.data, currentLength, bytes.Length);
+ }
+
+ ///
+ /// Converts this instance to a byte array.
+ ///
+ /// The
+ public byte[] ToByteArray()
+ {
+ var writer = new IccWriter();
+ return writer.Write(this);
+ }
+
+ private void InitializeHeader()
+ {
+ if (this.header != null)
+ {
+ return;
+ }
+
+ if (this.data == null)
+ {
+ this.header = new IccProfileHeader();
+ return;
+ }
+
+ var reader = new IccReader();
+ this.header = reader.ReadHeader(this.data);
+ }
+
+ private void InitializeEntries()
+ {
+ if (this.entries != null)
+ {
+ return;
+ }
+
+ if (this.data == null)
+ {
+ this.entries = new List();
+ return;
+ }
+
+ var reader = new IccReader();
+ this.entries = new List(reader.ReadTagData(this.data));
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs
new file mode 100644
index 000000000..8237dc7a8
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfileHeader.cs
@@ -0,0 +1,103 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Numerics;
+
+ ///
+ /// Contains all values of an ICC profile header
+ ///
+ public sealed class IccProfileHeader
+ {
+ ///
+ /// Gets or sets the profile size in bytes (will be ignored when writing a profile)
+ ///
+ public uint Size { get; set; }
+
+ ///
+ /// Gets or sets the preferred CMM (Color Management Module) type
+ ///
+ public string CmmType { get; set; }
+
+ ///
+ /// Gets or sets the profiles version number
+ ///
+ public Version Version { get; set; }
+
+ ///
+ /// Gets or sets the type of the profile
+ ///
+ public IccProfileClass Class { get; set; }
+
+ ///
+ /// Gets or sets the data colorspace
+ ///
+ public IccColorSpaceType DataColorSpace { get; set; }
+
+ ///
+ /// Gets or sets the profile connection space
+ ///
+ public IccColorSpaceType ProfileConnectionSpace { get; set; }
+
+ ///
+ /// Gets or sets the date and time this profile was created
+ ///
+ public DateTime CreationDate { get; set; }
+
+ ///
+ /// Gets or sets the file signature. Should always be "acsp".
+ /// Value will be ignored when writing a profile.
+ ///
+ public string FileSignature { get; set; }
+
+ ///
+ /// Gets or sets the primary platform this profile as created for
+ ///
+ public IccPrimaryPlatformType PrimaryPlatformSignature { get; set; }
+
+ ///
+ /// Gets or sets the profile flags to indicate various options for the CMM
+ /// such as distributed processing and caching options
+ ///
+ public IccProfileFlag Flags { get; set; }
+
+ ///
+ /// Gets or sets the device manufacturer of the device for which this profile is created
+ ///
+ public uint DeviceManufacturer { get; set; }
+
+ ///
+ /// Gets or sets the model of the device for which this profile is created
+ ///
+ public uint DeviceModel { get; set; }
+
+ ///
+ /// Gets or sets the device attributes unique to the particular device setup such as media type
+ ///
+ public IccDeviceAttribute DeviceAttributes { get; set; }
+
+ ///
+ /// Gets or sets the rendering Intent
+ ///
+ public IccRenderingIntent RenderingIntent { get; set; }
+
+ ///
+ /// Gets or sets The normalized XYZ values of the illuminant of the PCS
+ ///
+ public Vector3 PcsIlluminant { get; set; }
+
+ ///
+ /// Gets or sets Profile creator signature
+ ///
+ public string CreatorSignature { get; set; }
+
+ ///
+ /// Gets or sets the profile ID (hash)
+ ///
+ public IccProfileId Id { get; set; }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs
new file mode 100644
index 000000000..d7f556a81
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccReader.cs
@@ -0,0 +1,116 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// Reads and parses ICC data from a byte array
+ ///
+ internal sealed class IccReader
+ {
+ ///
+ /// Reads an ICC profile
+ ///
+ /// The raw ICC data
+ /// The read ICC profile
+ public IccProfile Read(byte[] data)
+ {
+ Guard.NotNull(data, nameof(data));
+ Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile");
+
+ var reader = new IccDataReader(data);
+ IccProfileHeader header = this.ReadHeader(reader);
+ IccTagDataEntry[] tagData = this.ReadTagData(reader);
+
+ return new IccProfile(header, tagData);
+ }
+
+ ///
+ /// Reads an ICC profile header
+ ///
+ /// The raw ICC data
+ /// The read ICC profile header
+ public IccProfileHeader ReadHeader(byte[] data)
+ {
+ Guard.NotNull(data, nameof(data));
+ Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header");
+
+ var reader = new IccDataReader(data);
+ return this.ReadHeader(reader);
+ }
+
+ ///
+ /// Reads the ICC profile tag data
+ ///
+ /// The raw ICC data
+ /// The read ICC profile tag data
+ public IccTagDataEntry[] ReadTagData(byte[] data)
+ {
+ Guard.NotNull(data, nameof(data));
+ Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile");
+
+ var reader = new IccDataReader(data);
+ return this.ReadTagData(reader);
+ }
+
+ private IccProfileHeader ReadHeader(IccDataReader reader)
+ {
+ reader.SetIndex(0);
+
+ return new IccProfileHeader
+ {
+ Size = reader.ReadUInt32(),
+ CmmType = reader.ReadAsciiString(4),
+ Version = reader.ReadVersionNumber(),
+ Class = (IccProfileClass)reader.ReadUInt32(),
+ DataColorSpace = (IccColorSpaceType)reader.ReadUInt32(),
+ ProfileConnectionSpace = (IccColorSpaceType)reader.ReadUInt32(),
+ CreationDate = reader.ReadDateTime(),
+ FileSignature = reader.ReadAsciiString(4),
+ PrimaryPlatformSignature = (IccPrimaryPlatformType)reader.ReadUInt32(),
+ Flags = (IccProfileFlag)reader.ReadInt32(),
+ DeviceManufacturer = reader.ReadUInt32(),
+ DeviceModel = reader.ReadUInt32(),
+ DeviceAttributes = (IccDeviceAttribute)reader.ReadInt64(),
+ RenderingIntent = (IccRenderingIntent)reader.ReadUInt32(),
+ PcsIlluminant = reader.ReadXyzNumber(),
+ CreatorSignature = reader.ReadAsciiString(4),
+ Id = reader.ReadProfileId(),
+ };
+ }
+
+ private IccTagDataEntry[] ReadTagData(IccDataReader reader)
+ {
+ IccTagTableEntry[] tagTable = this.ReadTagTable(reader);
+ IccTagDataEntry[] entries = new IccTagDataEntry[tagTable.Length];
+ for (int i = 0; i < tagTable.Length; i++)
+ {
+ IccTagDataEntry entry = reader.ReadTagDataEntry(tagTable[i]);
+ entry.TagSignature = tagTable[i].Signature;
+ entries[i] = entry;
+ }
+
+ return entries;
+ }
+
+ private IccTagTableEntry[] ReadTagTable(IccDataReader reader)
+ {
+ reader.SetIndex(128); // An ICC header is 128 bytes long
+
+ uint tagCount = reader.ReadUInt32();
+ IccTagTableEntry[] table = new IccTagTableEntry[tagCount];
+
+ for (int i = 0; i < tagCount; i++)
+ {
+ uint tagSignature = reader.ReadUInt32();
+ uint tagOffset = reader.ReadUInt32();
+ uint tagSize = reader.ReadUInt32();
+ table[i] = new IccTagTableEntry((IccProfileTag)tagSignature, tagOffset, tagSize);
+ }
+
+ return table;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs
new file mode 100644
index 000000000..753eb894b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccTagDataEntry.cs
@@ -0,0 +1,62 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// The data of an ICC tag entry
+ ///
+ public abstract class IccTagDataEntry : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ /// TagSignature will be
+ ///
+ /// Type Signature
+ protected IccTagDataEntry(IccTypeSignature signature)
+ : this(signature, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Type Signature
+ /// Tag Signature
+ protected IccTagDataEntry(IccTypeSignature signature, IccProfileTag tagSignature)
+ {
+ this.Signature = signature;
+ this.TagSignature = tagSignature;
+ }
+
+ ///
+ /// Gets the type Signature
+ ///
+ public IccTypeSignature Signature { get; }
+
+ ///
+ /// Gets or sets the tag Signature
+ ///
+ public IccProfileTag TagSignature { get; set; }
+
+ ///
+ public virtual bool Equals(IccTagDataEntry other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Signature == other.Signature;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs
new file mode 100644
index 000000000..de2823387
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs
@@ -0,0 +1,110 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Collections.Generic;
+ using System.Linq;
+
+ ///
+ /// Contains methods for writing ICC profiles.
+ ///
+ internal sealed class IccWriter
+ {
+ ///
+ /// Writes the ICC profile into a byte array
+ ///
+ /// The ICC profile to write
+ /// The ICC profile as a byte array
+ public byte[] Write(IccProfile profile)
+ {
+ Guard.NotNull(profile, nameof(profile));
+
+ using (var writer = new IccDataWriter())
+ {
+ IccTagTableEntry[] tagTable = this.WriteTagData(writer, profile.Entries);
+ this.WriteTagTable(writer, tagTable);
+ this.WriteHeader(writer, profile.Header);
+ return writer.GetData();
+ }
+ }
+
+ private void WriteHeader(IccDataWriter writer, IccProfileHeader header)
+ {
+ writer.SetIndex(0);
+
+ writer.WriteUInt32(writer.Length);
+ writer.WriteAsciiString(header.CmmType, 4, false);
+ writer.WriteVersionNumber(header.Version);
+ writer.WriteUInt32((uint)header.Class);
+ writer.WriteUInt32((uint)header.DataColorSpace);
+ writer.WriteUInt32((uint)header.ProfileConnectionSpace);
+ writer.WriteDateTime(header.CreationDate);
+ writer.WriteAsciiString("acsp");
+ writer.WriteUInt32((uint)header.PrimaryPlatformSignature);
+ writer.WriteInt32((int)header.Flags);
+ writer.WriteUInt32(header.DeviceManufacturer);
+ writer.WriteUInt32(header.DeviceModel);
+ writer.WriteInt64((long)header.DeviceAttributes);
+ writer.WriteUInt32((uint)header.RenderingIntent);
+ writer.WriteXyzNumber(header.PcsIlluminant);
+ writer.WriteAsciiString(header.CreatorSignature, 4, false);
+
+#if !NETSTANDARD1_1
+ IccProfileId id = IccProfile.CalculateHash(writer.GetData());
+ writer.WriteProfileId(id);
+#else
+ writer.WriteProfileId(IccProfileId.Zero);
+#endif
+ }
+
+ private void WriteTagTable(IccDataWriter writer, IccTagTableEntry[] table)
+ {
+ // 128 = size of ICC header
+ writer.SetIndex(128);
+
+ writer.WriteUInt32((uint)table.Length);
+ foreach (IccTagTableEntry entry in table)
+ {
+ writer.WriteUInt32((uint)entry.Signature);
+ writer.WriteUInt32(entry.Offset);
+ writer.WriteUInt32(entry.DataSize);
+ }
+ }
+
+ private IccTagTableEntry[] WriteTagData(IccDataWriter writer, List entries)
+ {
+ var inData = new List(entries);
+ var dupData = new List();
+
+ // Filter out duplicate entries. They only need to be defined once but can be used multiple times
+ while (inData.Count > 0)
+ {
+ IccTagDataEntry[] items = inData.Where(t => inData[0].Equals(t)).ToArray();
+ dupData.Add(items);
+ foreach (IccTagDataEntry item in items)
+ {
+ inData.Remove(item);
+ }
+ }
+
+ var table = new List();
+
+ // (Header size) + (entry count) + (nr of entries) * (size of table entry)
+ writer.SetIndex(128 + 4 + (entries.Count * 12));
+
+ foreach (IccTagDataEntry[] entry in dupData)
+ {
+ writer.WriteTagDataEntry(entry[0], out IccTagTableEntry tentry);
+ foreach (IccTagDataEntry item in entry)
+ {
+ table.Add(new IccTagTableEntry(item.TagSignature, tentry.Offset, tentry.DataSize));
+ }
+ }
+
+ return table.ToArray();
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs
new file mode 100644
index 000000000..a20a52d8e
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs
@@ -0,0 +1,31 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// A placeholder (might be used for future ICC versions)
+ ///
+ internal sealed class IccBAcsProcessElement : IccMultiProcessElement, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Number of input channels
+ /// Number of output channels
+ public IccBAcsProcessElement(int inChannelCount, int outChannelCount)
+ : base(IccMultiProcessElementSignature.BAcs, inChannelCount, outChannelCount)
+ {
+ }
+
+ ///
+ public bool Equals(IccBAcsProcessElement other)
+ {
+ return base.Equals(other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs
new file mode 100644
index 000000000..7d5855168
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs
@@ -0,0 +1,48 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// A CLUT (color lookup table) element to process data
+ ///
+ internal sealed class IccClutProcessElement : IccMultiProcessElement, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The color lookup table of this element
+ public IccClutProcessElement(IccClut clutValue)
+ : base(IccMultiProcessElementSignature.Clut, clutValue?.InputChannelCount ?? 1, clutValue?.OutputChannelCount ?? 1)
+ {
+ Guard.NotNull(clutValue, nameof(clutValue));
+ this.ClutValue = clutValue;
+ }
+
+ ///
+ /// Gets the color lookup table of this element
+ ///
+ public IccClut ClutValue { get; }
+
+ ///
+ public override bool Equals(IccMultiProcessElement other)
+ {
+ if (base.Equals(other) && other is IccClutProcessElement element)
+ {
+ return this.ClutValue.Equals(element.ClutValue);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccClutProcessElement other)
+ {
+ return this.Equals((IccMultiProcessElement)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs
new file mode 100644
index 000000000..c16ca93d2
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs
@@ -0,0 +1,49 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// A set of curves to process data
+ ///
+ internal sealed class IccCurveSetProcessElement : IccMultiProcessElement, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// An array with one dimensional curves
+ public IccCurveSetProcessElement(IccOneDimensionalCurve[] curves)
+ : base(IccMultiProcessElementSignature.CurveSet, curves?.Length ?? 1, curves?.Length ?? 1)
+ {
+ Guard.NotNull(curves, nameof(curves));
+ this.Curves = curves;
+ }
+
+ ///
+ /// Gets an array of one dimensional curves
+ ///
+ public IccOneDimensionalCurve[] Curves { get; }
+
+ ///
+ public override bool Equals(IccMultiProcessElement other)
+ {
+ if (base.Equals(other) && other is IccCurveSetProcessElement element)
+ {
+ return this.Curves.SequenceEqual(element.Curves);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccCurveSetProcessElement other)
+ {
+ return this.Equals((IccMultiProcessElement)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs
new file mode 100644
index 000000000..9219f5200
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs
@@ -0,0 +1,31 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// A placeholder (might be used for future ICC versions)
+ ///
+ internal sealed class IccEAcsProcessElement : IccMultiProcessElement, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Number of input channels
+ /// Number of output channels
+ public IccEAcsProcessElement(int inChannelCount, int outChannelCount)
+ : base(IccMultiProcessElementSignature.EAcs, inChannelCount, outChannelCount)
+ {
+ }
+
+ ///
+ public bool Equals(IccEAcsProcessElement other)
+ {
+ return base.Equals(other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs
new file mode 100644
index 000000000..259f71489
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs
@@ -0,0 +1,80 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ using ImageSharp.Memory;
+
+ ///
+ /// A matrix element to process data
+ ///
+ internal sealed class IccMatrixProcessElement : IccMultiProcessElement, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Two dimensional matrix with size of Input-Channels x Output-Channels
+ /// One dimensional matrix with size of Output-Channels x 1
+ public IccMatrixProcessElement(float[,] matrixIxO, float[] matrixOx1)
+ : base(IccMultiProcessElementSignature.Matrix, matrixIxO?.GetLength(0) ?? 1, matrixIxO?.GetLength(1) ?? 1)
+ {
+ Guard.NotNull(matrixIxO, nameof(matrixIxO));
+ Guard.NotNull(matrixOx1, nameof(matrixOx1));
+
+ bool matrixSizeCorrect = matrixIxO.GetLength(1) == matrixOx1.Length;
+ Guard.IsTrue(matrixSizeCorrect, $"{nameof(matrixIxO)},{nameof(matrixIxO)}", "Output channel length must match");
+
+ this.MatrixIxO = matrixIxO;
+ this.MatrixOx1 = matrixOx1;
+ }
+
+ ///
+ /// Gets the two dimensional matrix with size of Input-Channels x Output-Channels
+ ///
+ public Fast2DArray MatrixIxO { get; }
+
+ ///
+ /// Gets the one dimensional matrix with size of Output-Channels x 1
+ ///
+ public float[] MatrixOx1 { get; }
+
+ ///
+ public override bool Equals(IccMultiProcessElement other)
+ {
+ if (base.Equals(other) && other is IccMatrixProcessElement element)
+ {
+ return this.EqualsMatrix(element)
+ && this.MatrixOx1.SequenceEqual(element.MatrixOx1);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccMatrixProcessElement other)
+ {
+ return this.Equals((IccMultiProcessElement)other);
+ }
+
+ private bool EqualsMatrix(IccMatrixProcessElement element)
+ {
+ for (int x = 0; x < this.MatrixIxO.Width; x++)
+ {
+ for (int y = 0; y < this.MatrixIxO.Height; y++)
+ {
+ if (this.MatrixIxO[x, y] != element.MatrixIxO[x, y])
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs
new file mode 100644
index 000000000..205e61f01
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs
@@ -0,0 +1,64 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// An element to process data
+ ///
+ internal abstract class IccMultiProcessElement : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The signature of this element
+ /// Number of input channels
+ /// Number of output channels
+ protected IccMultiProcessElement(IccMultiProcessElementSignature signature, int inChannelCount, int outChannelCount)
+ {
+ Guard.MustBeBetweenOrEqualTo(inChannelCount, 1, 15, nameof(inChannelCount));
+ Guard.MustBeBetweenOrEqualTo(outChannelCount, 1, 15, nameof(outChannelCount));
+
+ this.Signature = signature;
+ this.InputChannelCount = inChannelCount;
+ this.OutputChannelCount = outChannelCount;
+ }
+
+ ///
+ /// Gets the signature of this element
+ ///
+ public IccMultiProcessElementSignature Signature { get; }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount { get; }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount { get; }
+
+ ///
+ public virtual bool Equals(IccMultiProcessElement other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Signature == other.Signature
+ && this.InputChannelCount == other.InputChannelCount
+ && this.OutputChannelCount == other.OutputChannelCount;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs
new file mode 100644
index 000000000..235988498
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs
@@ -0,0 +1,160 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// The chromaticity tag type provides basic chromaticity data
+ /// and type of phosphors or colorants of a monitor to applications and utilities.
+ ///
+ internal sealed class IccChromaticityTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Colorant Type
+ public IccChromaticityTagDataEntry(IccColorantEncoding colorantType)
+ : this(colorantType, GetColorantArray(colorantType), IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Values per channel
+ public IccChromaticityTagDataEntry(double[][] channelValues)
+ : this(IccColorantEncoding.Unknown, channelValues, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Colorant Type
+ /// Tag Signature
+ public IccChromaticityTagDataEntry(IccColorantEncoding colorantType, IccProfileTag tagSignature)
+ : this(colorantType, GetColorantArray(colorantType), tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Values per channel
+ /// Tag Signature
+ public IccChromaticityTagDataEntry(double[][] channelValues, IccProfileTag tagSignature)
+ : this(IccColorantEncoding.Unknown, channelValues, tagSignature)
+ {
+ }
+
+ private IccChromaticityTagDataEntry(IccColorantEncoding colorantType, double[][] channelValues, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Chromaticity, tagSignature)
+ {
+ Guard.NotNull(channelValues, nameof(channelValues));
+ Guard.MustBeBetweenOrEqualTo(channelValues.Length, 1, 15, nameof(channelValues));
+
+ this.ColorantType = colorantType;
+ this.ChannelValues = channelValues;
+
+ int channelLength = channelValues[0].Length;
+ bool channelsNotSame = channelValues.Any(t => t == null || t.Length != channelLength);
+ Guard.IsFalse(channelsNotSame, nameof(channelValues), "The number of values per channel is not the same for all channels");
+ }
+
+ ///
+ /// Gets the number of channels
+ ///
+ public int ChannelCount
+ {
+ get { return this.ChannelValues.Length; }
+ }
+
+ ///
+ /// Gets the colorant type
+ ///
+ public IccColorantEncoding ColorantType { get; }
+
+ ///
+ /// Gets the values per channel
+ ///
+ public double[][] ChannelValues { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccChromaticityTagDataEntry entry)
+ {
+ return this.ColorantType == entry.ColorantType
+ && this.EqualsChannelValues(entry);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccChromaticityTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+
+ private static double[][] GetColorantArray(IccColorantEncoding colorantType)
+ {
+ switch (colorantType)
+ {
+ case IccColorantEncoding.EbuTech3213E:
+ return new double[][]
+ {
+ new double[] { 0.640, 0.330 },
+ new double[] { 0.290, 0.600 },
+ new double[] { 0.150, 0.060 },
+ };
+ case IccColorantEncoding.ItuRBt709_2:
+ return new double[][]
+ {
+ new double[] { 0.640, 0.330 },
+ new double[] { 0.300, 0.600 },
+ new double[] { 0.150, 0.060 },
+ };
+ case IccColorantEncoding.P22:
+ return new double[][]
+ {
+ new double[] { 0.625, 0.340 },
+ new double[] { 0.280, 0.605 },
+ new double[] { 0.155, 0.070 },
+ };
+ case IccColorantEncoding.SmpteRp145:
+ return new double[][]
+ {
+ new double[] { 0.630, 0.340 },
+ new double[] { 0.310, 0.595 },
+ new double[] { 0.155, 0.070 },
+ };
+ default:
+ throw new ArgumentException("Unrecognized colorant encoding");
+ }
+ }
+
+ private bool EqualsChannelValues(IccChromaticityTagDataEntry entry)
+ {
+ if (this.ChannelValues.Length != entry.ChannelValues.Length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < this.ChannelValues.Length; i++)
+ {
+ if (!this.ChannelValues[i].SequenceEqual(entry.ChannelValues[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs
new file mode 100644
index 000000000..08875f085
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs
@@ -0,0 +1,62 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This tag specifies the laydown order in which colorants
+ /// will be printed on an n-colorant device.
+ ///
+ internal sealed class IccColorantOrderTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Colorant order numbers
+ public IccColorantOrderTagDataEntry(byte[] colorantNumber)
+ : this(colorantNumber, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Colorant order numbers
+ /// Tag Signature
+ public IccColorantOrderTagDataEntry(byte[] colorantNumber, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ColorantOrder, tagSignature)
+ {
+ Guard.NotNull(colorantNumber, nameof(colorantNumber));
+ Guard.MustBeBetweenOrEqualTo(colorantNumber.Length, 1, 15, nameof(colorantNumber));
+
+ this.ColorantNumber = colorantNumber;
+ }
+
+ ///
+ /// Gets the colorant order numbers
+ ///
+ public byte[] ColorantNumber { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccColorantOrderTagDataEntry entry)
+ {
+ return this.ColorantNumber.SequenceEqual(entry.ColorantNumber);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccColorantOrderTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs
new file mode 100644
index 000000000..e9411da4d
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs
@@ -0,0 +1,63 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// The purpose of this tag is to identify the colorants used in
+ /// the profile by a unique name and set of PCSXYZ or PCSLAB values
+ /// to give the colorant an unambiguous value.
+ ///
+ internal sealed class IccColorantTableTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Colorant Data
+ public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData)
+ : this(colorantData, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Colorant Data
+ /// Tag Signature
+ public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ColorantTable, tagSignature)
+ {
+ Guard.NotNull(colorantData, nameof(colorantData));
+ Guard.MustBeBetweenOrEqualTo(colorantData.Length, 1, 15, nameof(colorantData));
+
+ this.ColorantData = colorantData;
+ }
+
+ ///
+ /// Gets the colorant data
+ ///
+ public IccColorantTableEntry[] ColorantData { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccColorantTableTagDataEntry entry)
+ {
+ return this.ColorantData.SequenceEqual(entry.ColorantData);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccColorantTableTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs
new file mode 100644
index 000000000..29edc78d0
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs
@@ -0,0 +1,111 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// This type contains the PostScript product name to which this profile
+ /// corresponds and the names of the companion CRDs
+ ///
+ internal sealed class IccCrdInfoTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// the PostScript product name
+ /// the rendering intent 0 CRD name
+ /// the rendering intent 1 CRD name
+ /// the rendering intent 2 CRD name
+ /// the rendering intent 3 CRD name
+ public IccCrdInfoTagDataEntry(
+ string postScriptProductName,
+ string renderingIntent0Crd,
+ string renderingIntent1Crd,
+ string renderingIntent2Crd,
+ string renderingIntent3Crd)
+ : this(
+ postScriptProductName,
+ renderingIntent0Crd,
+ renderingIntent1Crd,
+ renderingIntent2Crd,
+ renderingIntent3Crd,
+ IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// the PostScript product name
+ /// the rendering intent 0 CRD name
+ /// the rendering intent 1 CRD name
+ /// the rendering intent 2 CRD name
+ /// the rendering intent 3 CRD name
+ /// Tag Signature
+ public IccCrdInfoTagDataEntry(
+ string postScriptProductName,
+ string renderingIntent0Crd,
+ string renderingIntent1Crd,
+ string renderingIntent2Crd,
+ string renderingIntent3Crd,
+ IccProfileTag tagSignature)
+ : base(IccTypeSignature.CrdInfo, tagSignature)
+ {
+ this.PostScriptProductName = postScriptProductName;
+ this.RenderingIntent0Crd = renderingIntent0Crd;
+ this.RenderingIntent1Crd = renderingIntent1Crd;
+ this.RenderingIntent2Crd = renderingIntent2Crd;
+ this.RenderingIntent3Crd = renderingIntent3Crd;
+ }
+
+ ///
+ /// Gets the PostScript product name
+ ///
+ public string PostScriptProductName { get; }
+
+ ///
+ /// Gets the rendering intent 0 CRD name
+ ///
+ public string RenderingIntent0Crd { get; }
+
+ ///
+ /// Gets the rendering intent 1 CRD name
+ ///
+ public string RenderingIntent1Crd { get; }
+
+ ///
+ /// Gets the rendering intent 2 CRD name
+ ///
+ public string RenderingIntent2Crd { get; }
+
+ ///
+ /// Gets the rendering intent 3 CRD name
+ ///
+ public string RenderingIntent3Crd { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccCrdInfoTagDataEntry entry)
+ {
+ return this.PostScriptProductName == entry.PostScriptProductName
+ && this.RenderingIntent0Crd == entry.RenderingIntent0Crd
+ && this.RenderingIntent1Crd == entry.RenderingIntent1Crd
+ && this.RenderingIntent2Crd == entry.RenderingIntent2Crd
+ && this.RenderingIntent3Crd == entry.RenderingIntent3Crd;
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccCrdInfoTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs
new file mode 100644
index 000000000..de2553dc5
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs
@@ -0,0 +1,129 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// The type contains a one-dimensional table of double values.
+ ///
+ internal sealed class IccCurveTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public IccCurveTagDataEntry()
+ : this(new float[0], IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Gamma value
+ public IccCurveTagDataEntry(float gamma)
+ : this(new float[] { gamma }, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Curve Data
+ public IccCurveTagDataEntry(float[] curveData)
+ : this(curveData, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Tag Signature
+ public IccCurveTagDataEntry(IccProfileTag tagSignature)
+ : this(new float[0], tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Gamma value
+ /// Tag Signature
+ public IccCurveTagDataEntry(float gamma, IccProfileTag tagSignature)
+ : this(new float[] { gamma }, tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Curve Data
+ /// Tag Signature
+ public IccCurveTagDataEntry(float[] curveData, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Curve, tagSignature)
+ {
+ this.CurveData = curveData ?? new float[0];
+ }
+
+ ///
+ /// Gets the curve data
+ ///
+ public float[] CurveData { get; }
+
+ ///
+ /// Gets the gamma value.
+ /// Only valid if is true
+ ///
+ public float Gamma
+ {
+ get
+ {
+ if (this.IsGamma)
+ {
+ return this.CurveData[0];
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the curve maps input directly to output
+ ///
+ public bool IsIdentityResponse
+ {
+ get { return this.CurveData.Length == 0; }
+ }
+
+ ///
+ /// Gets a value indicating whether the curve is a gamma curve
+ ///
+ public bool IsGamma
+ {
+ get { return this.CurveData.Length == 1; }
+ }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccCurveTagDataEntry entry)
+ {
+ return this.CurveData.SequenceEqual(entry.CurveData);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccCurveTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs
new file mode 100644
index 000000000..c2bfacf53
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs
@@ -0,0 +1,100 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// The dataType is a simple data structure that contains
+ /// either 7-bit ASCII or binary data, i.e. textType data or transparent bytes.
+ ///
+ internal sealed class IccDataTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII");
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw data
+ public IccDataTagDataEntry(byte[] data)
+ : this(data, false, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw data
+ /// True if the given data is 7bit ASCII encoded text
+ public IccDataTagDataEntry(byte[] data, bool isAscii)
+ : this(data, isAscii, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw data
+ /// True if the given data is 7bit ASCII encoded text
+ /// Tag Signature
+ public IccDataTagDataEntry(byte[] data, bool isAscii, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Data, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ this.IsAscii = isAscii;
+ }
+
+ ///
+ /// Gets the raw Data
+ ///
+ public byte[] Data { get; }
+
+ ///
+ /// Gets a value indicating whether the represents 7bit ASCII encoded text
+ ///
+ public bool IsAscii { get; }
+
+ ///
+ /// Gets the decoded as 7bit ASCII.
+ /// If is false, returns null
+ ///
+ public string AsciiString
+ {
+ get
+ {
+ if (this.IsAscii)
+ {
+ return AsciiEncoding.GetString(this.Data, 0, this.Data.Length);
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccDataTagDataEntry entry)
+ {
+ return this.IsAscii == entry.IsAscii
+ && this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccDataTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs
new file mode 100644
index 000000000..531327032
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs
@@ -0,0 +1,57 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// This type is a representation of the time and date.
+ ///
+ internal sealed class IccDateTimeTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The DateTime value
+ public IccDateTimeTagDataEntry(DateTime value)
+ : this(value, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The DateTime value
+ /// Tag Signature
+ public IccDateTimeTagDataEntry(DateTime value, IccProfileTag tagSignature)
+ : base(IccTypeSignature.DateTime, tagSignature)
+ {
+ this.Value = value;
+ }
+
+ ///
+ /// Gets the date and time value
+ ///
+ public DateTime Value { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccDateTimeTagDataEntry entry)
+ {
+ return this.Value == entry.Value;
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccDateTimeTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs
new file mode 100644
index 000000000..af1a7aa42
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs
@@ -0,0 +1,59 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This type represents an array of doubles (from 32bit fixed point values).
+ ///
+ internal sealed class IccFix16ArrayTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ public IccFix16ArrayTagDataEntry(float[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ /// Tag Signature
+ public IccFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.S15Fixed16Array, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the array data
+ ///
+ public float[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccFix16ArrayTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccFix16ArrayTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs
new file mode 100644
index 000000000..5080b2313
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs
@@ -0,0 +1,160 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+ using System.Numerics;
+
+ ///
+ /// This structure represents a color transform using tables
+ /// with 16-bit precision.
+ ///
+ internal sealed class IccLut16TagDataEntry : IccTagDataEntry, IEquatable
+ {
+ private static readonly float[,] IdentityMatrix = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues)
+ : this(IdentityMatrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ /// Tag Signature
+ public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature)
+ : this(IdentityMatrix, inputValues, clutValues, outputValues, tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Conversion matrix (must be 3x3)
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues)
+ : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Conversion matrix (must be 3x3)
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ /// Tag Signature
+ public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Lut16, tagSignature)
+ {
+ Guard.NotNull(matrix, nameof(matrix));
+ Guard.NotNull(inputValues, nameof(inputValues));
+ Guard.NotNull(clutValues, nameof(clutValues));
+ Guard.NotNull(outputValues, nameof(outputValues));
+
+ bool is3By3 = matrix.GetLength(0) == 3 && matrix.GetLength(1) == 3;
+ Guard.IsTrue(is3By3, nameof(matrix), "Matrix must have a size of three by three");
+
+ this.Matrix = this.CreateMatrix(matrix);
+ this.InputValues = inputValues;
+ this.ClutValues = clutValues;
+ this.OutputValues = outputValues;
+
+ Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size");
+ Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size");
+ }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount
+ {
+ get { return this.InputValues.Length; }
+ }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount
+ {
+ get { return this.OutputValues.Length; }
+ }
+
+ ///
+ /// Gets the conversion matrix
+ ///
+ public Matrix4x4 Matrix { get; }
+
+ ///
+ /// Gets the input lookup table
+ ///
+ public IccLut[] InputValues { get; }
+
+ ///
+ /// Gets the color lookup table
+ ///
+ public IccClut ClutValues { get; }
+
+ ///
+ /// Gets the output lookup table
+ ///
+ public IccLut[] OutputValues { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccLut16TagDataEntry entry)
+ {
+ return this.ClutValues.Equals(entry.ClutValues)
+ && this.Matrix == entry.Matrix
+ && this.InputValues.SequenceEqual(entry.InputValues)
+ && this.OutputValues.SequenceEqual(entry.OutputValues);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccLut16TagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+
+ private Matrix4x4 CreateMatrix(float[,] matrix)
+ {
+ return new Matrix4x4(
+ matrix[0, 0],
+ matrix[0, 1],
+ matrix[0, 2],
+ 0,
+ matrix[1, 0],
+ matrix[1, 1],
+ matrix[1, 2],
+ 0,
+ matrix[2, 0],
+ matrix[2, 1],
+ matrix[2, 2],
+ 0,
+ 0,
+ 0,
+ 0,
+ 1);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs
new file mode 100644
index 000000000..d3c41dde3
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs
@@ -0,0 +1,163 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+ using System.Numerics;
+
+ ///
+ /// This structure represents a color transform using tables
+ /// with 8-bit precision.
+ ///
+ internal sealed class IccLut8TagDataEntry : IccTagDataEntry, IEquatable
+ {
+ private static readonly float[,] IdentityMatrix = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues)
+ : this(IdentityMatrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ /// Tag Signature
+ public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature)
+ : this(IdentityMatrix, inputValues, clutValues, outputValues, tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Conversion matrix (must be 3x3)
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues)
+ : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Conversion matrix (must be 3x3)
+ /// Input LUT
+ /// CLUT
+ /// Output LUT
+ /// Tag Signature
+ public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Lut8, tagSignature)
+ {
+ Guard.NotNull(matrix, nameof(matrix));
+ Guard.NotNull(inputValues, nameof(inputValues));
+ Guard.NotNull(clutValues, nameof(clutValues));
+ Guard.NotNull(outputValues, nameof(outputValues));
+
+ bool is3By3 = matrix.GetLength(0) == 3 && matrix.GetLength(1) == 3;
+ Guard.IsTrue(is3By3, nameof(matrix), "Matrix must have a size of three by three");
+
+ this.Matrix = this.CreateMatrix(matrix);
+ this.InputValues = inputValues;
+ this.ClutValues = clutValues;
+ this.OutputValues = outputValues;
+
+ Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size");
+ Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size");
+
+ Guard.IsFalse(inputValues.Any(t => t.Values.Length != 256), nameof(inputValues), "Input lookup table has to have a length of 256");
+ Guard.IsFalse(outputValues.Any(t => t.Values.Length != 256), nameof(outputValues), "Output lookup table has to have a length of 256");
+ }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount
+ {
+ get { return this.InputValues.Length; }
+ }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount
+ {
+ get { return this.OutputValues.Length; }
+ }
+
+ ///
+ /// Gets the conversion matrix
+ ///
+ public Matrix4x4 Matrix { get; }
+
+ ///
+ /// Gets the input lookup table
+ ///
+ public IccLut[] InputValues { get; }
+
+ ///
+ /// Gets the color lookup table
+ ///
+ public IccClut ClutValues { get; }
+
+ ///
+ /// Gets the output lookup table
+ ///
+ public IccLut[] OutputValues { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccLut8TagDataEntry entry)
+ {
+ return this.ClutValues.Equals(entry.ClutValues)
+ && this.Matrix == entry.Matrix
+ && this.InputValues.SequenceEqual(entry.InputValues)
+ && this.OutputValues.SequenceEqual(entry.OutputValues);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccLut8TagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+
+ private Matrix4x4 CreateMatrix(float[,] matrix)
+ {
+ return new Matrix4x4(
+ matrix[0, 0],
+ matrix[0, 1],
+ matrix[0, 2],
+ 0,
+ matrix[1, 0],
+ matrix[1, 1],
+ matrix[1, 2],
+ 0,
+ matrix[2, 0],
+ matrix[2, 1],
+ matrix[2, 2],
+ 0,
+ 0,
+ 0,
+ 0,
+ 1);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs
new file mode 100644
index 000000000..cfbb64714
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs
@@ -0,0 +1,280 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+ using System.Numerics;
+
+ ///
+ /// This structure represents a color transform.
+ ///
+ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A Curve
+ /// CLUT
+ /// M Curve
+ /// Two dimensional conversion matrix (3x3)
+ /// One dimensional conversion matrix (3x1)
+ /// B Curve
+ public IccLutAToBTagDataEntry(
+ IccTagDataEntry[] curveB,
+ float[,] matrix3x3,
+ float[] matrix3x1,
+ IccTagDataEntry[] curveM,
+ IccClut clutValues,
+ IccTagDataEntry[] curveA)
+ : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A Curve
+ /// CLUT
+ /// M Curve
+ /// Two dimensional conversion matrix (3x3)
+ /// One dimensional conversion matrix (3x1)
+ /// B Curve
+ /// Tag Signature
+ public IccLutAToBTagDataEntry(
+ IccTagDataEntry[] curveB,
+ float[,] matrix3x3,
+ float[] matrix3x1,
+ IccTagDataEntry[] curveM,
+ IccClut clutValues,
+ IccTagDataEntry[] curveA,
+ IccProfileTag tagSignature)
+ : base(IccTypeSignature.LutAToB, tagSignature)
+ {
+ this.VerifyMatrix(matrix3x3, matrix3x1);
+ this.VerifyCurve(curveA, nameof(curveA));
+ this.VerifyCurve(curveB, nameof(curveB));
+ this.VerifyCurve(curveM, nameof(curveM));
+
+ this.Matrix3x3 = this.CreateMatrix3x3(matrix3x3);
+ this.Matrix3x1 = this.CreateMatrix3x1(matrix3x1);
+ this.CurveA = curveA;
+ this.CurveB = curveB;
+ this.CurveM = curveM;
+ this.ClutValues = clutValues;
+
+ if (this.IsAClutMMatrixB())
+ {
+ Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three");
+ Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three");
+ Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA));
+
+ this.InputChannelCount = curveA.Length;
+ this.OutputChannelCount = 3;
+
+ Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size");
+ Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size");
+ }
+ else if (this.IsMMatrixB())
+ {
+ Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three");
+ Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three");
+
+ this.InputChannelCount = this.OutputChannelCount = 3;
+ }
+ else if (this.IsAClutB())
+ {
+ Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA));
+ Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB));
+
+ this.InputChannelCount = curveA.Length;
+ this.OutputChannelCount = curveB.Length;
+
+ Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size");
+ Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size");
+ }
+ else if (this.IsB())
+ {
+ this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length;
+ }
+ else
+ {
+ throw new ArgumentException("Invalid combination of values given");
+ }
+ }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount { get; }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount { get; }
+
+ ///
+ /// Gets the two dimensional conversion matrix (3x3)
+ ///
+ public Matrix4x4? Matrix3x3 { get; }
+
+ ///
+ /// Gets the one dimensional conversion matrix (3x1)
+ ///
+ public Vector3? Matrix3x1 { get; }
+
+ ///
+ /// Gets the color lookup table
+ ///
+ public IccClut ClutValues { get; }
+
+ ///
+ /// Gets the B Curve
+ ///
+ public IccTagDataEntry[] CurveB { get; }
+
+ ///
+ /// Gets the M Curve
+ ///
+ public IccTagDataEntry[] CurveM { get; }
+
+ ///
+ /// Gets the A Curve
+ ///
+ public IccTagDataEntry[] CurveA { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccLutAToBTagDataEntry entry)
+ {
+ return this.InputChannelCount == entry.InputChannelCount
+ && this.OutputChannelCount == entry.OutputChannelCount
+ && this.Matrix3x1 == entry.Matrix3x1
+ && this.Matrix3x3 == entry.Matrix3x3
+ && this.ClutValues.Equals(entry.ClutValues)
+ && this.EqualsCurve(this.CurveA, entry.CurveA)
+ && this.EqualsCurve(this.CurveB, entry.CurveB)
+ && this.EqualsCurve(this.CurveM, entry.CurveM);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccLutAToBTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+
+ private bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves)
+ {
+ bool thisNull = thisCurves == null;
+ bool entryNull = entryCurves == null;
+
+ if (thisNull && entryNull)
+ {
+ return true;
+ }
+
+ if (entryNull)
+ {
+ return false;
+ }
+
+ return thisCurves.SequenceEqual(entryCurves);
+ }
+
+ private bool IsAClutMMatrixB()
+ {
+ return this.CurveB != null
+ && this.Matrix3x3 != null
+ && this.Matrix3x1 != null
+ && this.CurveM != null
+ && this.ClutValues != null
+ && this.CurveA != null;
+ }
+
+ private bool IsMMatrixB()
+ {
+ return this.CurveB != null
+ && this.Matrix3x3 != null
+ && this.Matrix3x1 != null
+ && this.CurveM != null;
+ }
+
+ private bool IsAClutB()
+ {
+ return this.CurveB != null
+ && this.ClutValues != null
+ && this.CurveA != null;
+ }
+
+ private bool IsB()
+ {
+ return this.CurveB != null;
+ }
+
+ private void VerifyCurve(IccTagDataEntry[] curves, string name)
+ {
+ if (curves != null)
+ {
+ bool isNotCurve = curves.Any(t => !(t is IccParametricCurveTagDataEntry) && !(t is IccCurveTagDataEntry));
+ Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}");
+ }
+ }
+
+ private void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1)
+ {
+ if (matrix3x1 != null)
+ {
+ Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three");
+ }
+
+ if (matrix3x3 != null)
+ {
+ bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3;
+ Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three");
+ }
+ }
+
+ private Vector3? CreateMatrix3x1(float[] matrix)
+ {
+ if (matrix == null)
+ {
+ return null;
+ }
+
+ return new Vector3(matrix[0], matrix[1], matrix[2]);
+ }
+
+ private Matrix4x4? CreateMatrix3x3(float[,] matrix)
+ {
+ if (matrix == null)
+ {
+ return null;
+ }
+
+ return new Matrix4x4(
+ matrix[0, 0],
+ matrix[0, 1],
+ matrix[0, 2],
+ 0,
+ matrix[1, 0],
+ matrix[1, 1],
+ matrix[1, 2],
+ 0,
+ matrix[2, 0],
+ matrix[2, 1],
+ matrix[2, 2],
+ 0,
+ 0,
+ 0,
+ 0,
+ 1);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs
new file mode 100644
index 000000000..e0bcab7be
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs
@@ -0,0 +1,280 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+ using System.Numerics;
+
+ ///
+ /// This structure represents a color transform.
+ ///
+ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A Curve
+ /// CLUT
+ /// M Curve
+ /// Two dimensional conversion matrix (3x3)
+ /// One dimensional conversion matrix (3x1)
+ /// B Curve
+ public IccLutBToATagDataEntry(
+ IccTagDataEntry[] curveB,
+ float[,] matrix3x3,
+ float[] matrix3x1,
+ IccTagDataEntry[] curveM,
+ IccClut clutValues,
+ IccTagDataEntry[] curveA)
+ : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A Curve
+ /// CLUT
+ /// M Curve
+ /// Two dimensional conversion matrix (3x3)
+ /// One dimensional conversion matrix (3x1)
+ /// B Curve
+ /// Tag Signature
+ public IccLutBToATagDataEntry(
+ IccTagDataEntry[] curveB,
+ float[,] matrix3x3,
+ float[] matrix3x1,
+ IccTagDataEntry[] curveM,
+ IccClut clutValues,
+ IccTagDataEntry[] curveA,
+ IccProfileTag tagSignature)
+ : base(IccTypeSignature.LutBToA, tagSignature)
+ {
+ this.VerifyMatrix(matrix3x3, matrix3x1);
+ this.VerifyCurve(curveA, nameof(curveA));
+ this.VerifyCurve(curveB, nameof(curveB));
+ this.VerifyCurve(curveM, nameof(curveM));
+
+ this.Matrix3x3 = this.CreateMatrix3x3(matrix3x3);
+ this.Matrix3x1 = this.CreateMatrix3x1(matrix3x1);
+ this.CurveA = curveA;
+ this.CurveB = curveB;
+ this.CurveM = curveM;
+ this.ClutValues = clutValues;
+
+ if (this.IsBMatrixMClutA())
+ {
+ Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three");
+ Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three");
+ Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA));
+
+ this.InputChannelCount = 3;
+ this.OutputChannelCount = curveA.Length;
+
+ Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size");
+ Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size");
+ }
+ else if (this.IsBMatrixM())
+ {
+ Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three");
+ Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three");
+
+ this.InputChannelCount = this.OutputChannelCount = 3;
+ }
+ else if (this.IsBClutA())
+ {
+ Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA));
+ Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB));
+
+ this.InputChannelCount = curveB.Length;
+ this.OutputChannelCount = curveA.Length;
+
+ Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size");
+ Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size");
+ }
+ else if (this.IsB())
+ {
+ this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length;
+ }
+ else
+ {
+ throw new ArgumentException("Invalid combination of values given");
+ }
+ }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount { get; }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount { get; }
+
+ ///
+ /// Gets the two dimensional conversion matrix (3x3)
+ ///
+ public Matrix4x4? Matrix3x3 { get; }
+
+ ///
+ /// Gets the one dimensional conversion matrix (3x1)
+ ///
+ public Vector3? Matrix3x1 { get; }
+
+ ///
+ /// Gets the color lookup table
+ ///
+ public IccClut ClutValues { get; }
+
+ ///
+ /// Gets the B Curve
+ ///
+ public IccTagDataEntry[] CurveB { get; }
+
+ ///
+ /// Gets the M Curve
+ ///
+ public IccTagDataEntry[] CurveM { get; }
+
+ ///
+ /// Gets the A Curve
+ ///
+ public IccTagDataEntry[] CurveA { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccLutBToATagDataEntry entry)
+ {
+ return this.InputChannelCount == entry.InputChannelCount
+ && this.OutputChannelCount == entry.OutputChannelCount
+ && this.Matrix3x1 == entry.Matrix3x1
+ && this.Matrix3x3 == entry.Matrix3x3
+ && this.ClutValues.Equals(entry.ClutValues)
+ && this.EqualsCurve(this.CurveA, entry.CurveA)
+ && this.EqualsCurve(this.CurveB, entry.CurveB)
+ && this.EqualsCurve(this.CurveM, entry.CurveM);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccLutBToATagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+
+ private bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves)
+ {
+ bool thisNull = thisCurves == null;
+ bool entryNull = entryCurves == null;
+
+ if (thisNull && entryNull)
+ {
+ return true;
+ }
+
+ if (entryNull)
+ {
+ return false;
+ }
+
+ return thisCurves.SequenceEqual(entryCurves);
+ }
+
+ private bool IsBMatrixMClutA()
+ {
+ return this.CurveB != null
+ && this.Matrix3x3 != null
+ && this.Matrix3x1 != null
+ && this.CurveM != null
+ && this.ClutValues != null
+ && this.CurveA != null;
+ }
+
+ private bool IsBMatrixM()
+ {
+ return this.CurveB != null
+ && this.Matrix3x3 != null
+ && this.Matrix3x1 != null
+ && this.CurveM != null;
+ }
+
+ private bool IsBClutA()
+ {
+ return this.CurveB != null
+ && this.ClutValues != null
+ && this.CurveA != null;
+ }
+
+ private bool IsB()
+ {
+ return this.CurveB != null;
+ }
+
+ private void VerifyCurve(IccTagDataEntry[] curves, string name)
+ {
+ if (curves != null)
+ {
+ bool isNotCurve = curves.Any(t => !(t is IccParametricCurveTagDataEntry) && !(t is IccCurveTagDataEntry));
+ Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}");
+ }
+ }
+
+ private void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1)
+ {
+ if (matrix3x1 != null)
+ {
+ Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three");
+ }
+
+ if (matrix3x3 != null)
+ {
+ bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3;
+ Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three");
+ }
+ }
+
+ private Vector3? CreateMatrix3x1(float[] matrix)
+ {
+ if (matrix == null)
+ {
+ return null;
+ }
+
+ return new Vector3(matrix[0], matrix[1], matrix[2]);
+ }
+
+ private Matrix4x4? CreateMatrix3x3(float[,] matrix)
+ {
+ if (matrix == null)
+ {
+ return null;
+ }
+
+ return new Matrix4x4(
+ matrix[0, 0],
+ matrix[0, 1],
+ matrix[0, 2],
+ 0,
+ matrix[1, 0],
+ matrix[1, 1],
+ matrix[1, 2],
+ 0,
+ matrix[2, 0],
+ matrix[2, 1],
+ matrix[2, 2],
+ 0,
+ 0,
+ 0,
+ 0,
+ 1);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs
new file mode 100644
index 000000000..b63e4a20b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs
@@ -0,0 +1,96 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Numerics;
+
+ ///
+ /// The measurementType information refers only to the internal
+ /// profile data and is meant to provide profile makers an alternative
+ /// to the default measurement specifications.
+ ///
+ internal sealed class IccMeasurementTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Observer
+ /// XYZ Backing values
+ /// Geometry
+ /// Flare
+ /// Illuminant
+ public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant)
+ : this(observer, xyzBacking, geometry, flare, illuminant, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Observer
+ /// XYZ Backing values
+ /// Geometry
+ /// Flare
+ /// Illuminant
+ /// Tag Signature
+ public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Measurement, tagSignature)
+ {
+ this.Observer = observer;
+ this.XyzBacking = xyzBacking;
+ this.Geometry = geometry;
+ this.Flare = flare;
+ this.Illuminant = illuminant;
+ }
+
+ ///
+ /// Gets the observer
+ ///
+ public IccStandardObserver Observer { get; }
+
+ ///
+ /// Gets the XYZ Backing values
+ ///
+ public Vector3 XyzBacking { get; }
+
+ ///
+ /// Gets the geometry
+ ///
+ public IccMeasurementGeometry Geometry { get; }
+
+ ///
+ /// Gets the flare
+ ///
+ public float Flare { get; }
+
+ ///
+ /// Gets the illuminant
+ ///
+ public IccStandardIlluminant Illuminant { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccMeasurementTagDataEntry entry)
+ {
+ return this.Observer == entry.Observer
+ && this.XyzBacking == entry.XyzBacking
+ && this.Geometry == entry.Geometry
+ && this.Flare == entry.Flare
+ && this.Illuminant == entry.Illuminant;
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccMeasurementTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs
new file mode 100644
index 000000000..c5af2399b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This tag structure contains a set of records each referencing
+ /// a multilingual string associated with a profile.
+ ///
+ internal sealed class IccMultiLocalizedUnicodeTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Localized Text
+ public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts)
+ : this(texts, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Localized Text
+ /// Tag Signature
+ public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts, IccProfileTag tagSignature)
+ : base(IccTypeSignature.MultiLocalizedUnicode, tagSignature)
+ {
+ Guard.NotNull(texts, nameof(texts));
+ this.Texts = texts;
+ }
+
+ ///
+ /// Gets the localized texts
+ ///
+ public IccLocalizedString[] Texts { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccMultiLocalizedUnicodeTagDataEntry entry)
+ {
+ return this.Texts.SequenceEqual(entry.Texts);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccMultiLocalizedUnicodeTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs
new file mode 100644
index 000000000..5ebfbbb6e
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs
@@ -0,0 +1,79 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This structure represents a color transform, containing
+ /// a sequence of processing elements.
+ ///
+ internal sealed class IccMultiProcessElementsTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Processing elements
+ public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Processing elements
+ /// Tag Signature
+ public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.MultiProcessElements, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ Guard.IsTrue(data.Length > 0, nameof(data), $"{nameof(data)} must have at least one element");
+
+ this.InputChannelCount = data[0].InputChannelCount;
+ this.OutputChannelCount = data[0].OutputChannelCount;
+ this.Data = data;
+
+ bool channelsNotSame = data.Any(t => t.InputChannelCount != this.InputChannelCount || t.OutputChannelCount != this.OutputChannelCount);
+ Guard.IsFalse(channelsNotSame, nameof(data), "The number of input and output channels are not the same for all elements");
+ }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount { get; }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount { get; }
+
+ ///
+ /// Gets the processing elements
+ ///
+ public IccMultiProcessElement[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccMultiProcessElementsTagDataEntry entry)
+ {
+ return this.InputChannelCount == entry.InputChannelCount
+ && this.OutputChannelCount == entry.OutputChannelCount
+ && this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccMultiProcessElementsTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs
new file mode 100644
index 000000000..6b354b311
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs
@@ -0,0 +1,144 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// The namedColor2Type is a count value and array of structures
+ /// that provide color coordinates for color names.
+ ///
+ internal sealed class IccNamedColor2TagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The named colors
+ public IccNamedColor2TagDataEntry(IccNamedColor[] colors)
+ : this(0, null, null, colors, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Prefix
+ /// Suffix
+ /// /// The named colors
+ public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors)
+ : this(0, prefix, suffix, colors, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Vendor specific flags
+ /// Prefix
+ /// Suffix
+ /// The named colors
+ public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors)
+ : this(vendorFlags, prefix, suffix, colors, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The named colors
+ /// Tag Signature
+ public IccNamedColor2TagDataEntry(IccNamedColor[] colors, IccProfileTag tagSignature)
+ : this(0, null, null, colors, tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Prefix
+ /// Suffix
+ /// The named colors
+ /// Tag Signature
+ public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature)
+ : this(0, prefix, suffix, colors, tagSignature)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Vendor specific flags
+ /// Prefix
+ /// Suffix
+ /// The named colors
+ /// Tag Signature
+ public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature)
+ : base(IccTypeSignature.NamedColor2, tagSignature)
+ {
+ Guard.NotNull(colors, nameof(colors));
+
+ int coordinateCount = 0;
+ if (colors.Length > 0)
+ {
+ coordinateCount = colors[0].DeviceCoordinates?.Length ?? 0;
+ Guard.IsFalse(colors.Any(t => (t.DeviceCoordinates?.Length ?? 0) != coordinateCount), nameof(colors), "Device coordinate count must be the same for all colors");
+ }
+
+ this.VendorFlags = vendorFlags;
+ this.CoordinateCount = coordinateCount;
+ this.Prefix = prefix;
+ this.Suffix = suffix;
+ this.Colors = colors;
+ }
+
+ ///
+ /// Gets the number of coordinates
+ ///
+ public int CoordinateCount { get; }
+
+ ///
+ /// Gets the prefix
+ ///
+ public string Prefix { get; }
+
+ ///
+ /// Gets the suffix
+ ///
+ public string Suffix { get; }
+
+ ///
+ /// Gets the vendor specific flags
+ ///
+ public int VendorFlags { get; }
+
+ ///
+ /// Gets the named colors
+ ///
+ public IccNamedColor[] Colors { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccNamedColor2TagDataEntry entry)
+ {
+ return this.CoordinateCount == entry.CoordinateCount
+ && this.Prefix == entry.Prefix
+ && this.Suffix == entry.Suffix
+ && this.VendorFlags == entry.VendorFlags
+ && this.Colors.SequenceEqual(entry.Colors);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccNamedColor2TagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs
new file mode 100644
index 000000000..779656911
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs
@@ -0,0 +1,58 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// The parametricCurveType describes a one-dimensional curve by
+ /// specifying one of a predefined set of functions using the parameters.
+ ///
+ internal sealed class IccParametricCurveTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Curve
+ public IccParametricCurveTagDataEntry(IccParametricCurve curve)
+ : this(curve, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Curve
+ /// Tag Signature
+ public IccParametricCurveTagDataEntry(IccParametricCurve curve, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ParametricCurve, tagSignature)
+ {
+ this.Curve = curve;
+ }
+
+ ///
+ /// Gets the Curve
+ ///
+ public IccParametricCurve Curve { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccParametricCurveTagDataEntry entry)
+ {
+ return this.Curve.Equals(entry.Curve);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccParametricCurveTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs
new file mode 100644
index 000000000..ef9da652b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs
@@ -0,0 +1,61 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This type is an array of structures, each of which contains information
+ /// from the header fields and tags from the original profiles which were
+ /// combined to create the final profile.
+ ///
+ internal sealed class IccProfileSequenceDescTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Profile Descriptions
+ public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions)
+ : this(descriptions, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Profile Descriptions
+ /// Tag Signature
+ public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ProfileSequenceDesc, tagSignature)
+ {
+ Guard.NotNull(descriptions, nameof(descriptions));
+ this.Descriptions = descriptions;
+ }
+
+ ///
+ /// Gets the profile descriptions
+ ///
+ public IccProfileDescription[] Descriptions { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccProfileSequenceDescTagDataEntry entry)
+ {
+ return this.Descriptions.SequenceEqual(entry.Descriptions);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccProfileSequenceDescTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs
new file mode 100644
index 000000000..480f3974b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This type is an array of structures, each of which contains information
+ /// for identification of a profile used in a sequence.
+ ///
+ internal sealed class IccProfileSequenceIdentifierTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Profile Identifiers
+ public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Profile Identifiers
+ /// Tag Signature
+ public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ProfileSequenceIdentifier, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the profile identifiers
+ ///
+ public IccProfileSequenceIdentifier[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccProfileSequenceIdentifierTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccProfileSequenceIdentifierTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs
new file mode 100644
index 000000000..1a4788aad
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs
@@ -0,0 +1,73 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// The purpose of this tag type is to provide a mechanism to relate physical
+ /// colorant amounts with the normalized device codes produced by lut8Type, lut16Type,
+ /// lutAToBType, lutBToAType or multiProcessElementsType tags so that corrections can
+ /// be made for variation in the device without having to produce a new profile.
+ ///
+ internal sealed class IccResponseCurveSet16TagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Curves
+ public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves)
+ : this(curves, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Curves
+ /// Tag Signature
+ public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ResponseCurveSet16, tagSignature)
+ {
+ Guard.NotNull(curves, nameof(curves));
+ Guard.IsTrue(curves.Length > 0, nameof(curves), $"{nameof(curves)} needs at least one element");
+
+ this.Curves = curves;
+ this.ChannelCount = (ushort)curves[0].ResponseArrays.Length;
+
+ Guard.IsFalse(curves.Any(t => t.ResponseArrays.Length != this.ChannelCount), nameof(curves), "All curves need to have the same number of channels");
+ }
+
+ ///
+ /// Gets the number of channels
+ ///
+ public ushort ChannelCount { get; }
+
+ ///
+ /// Gets the curves
+ ///
+ public IccResponseCurve[] Curves { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccResponseCurveSet16TagDataEntry entry)
+ {
+ return this.ChannelCount == entry.ChannelCount
+ && this.Curves.SequenceEqual(entry.Curves);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccResponseCurveSet16TagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs
new file mode 100644
index 000000000..166f74b37
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs
@@ -0,0 +1,70 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This type describes various screening parameters including
+ /// screen frequency, screening angle, and spot shape.
+ ///
+ internal sealed class IccScreeningTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Screening flags
+ /// Channel information
+ public IccScreeningTagDataEntry(IccScreeningFlag flags, IccScreeningChannel[] channels)
+ : this(flags, channels, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Screening flags
+ /// Channel information
+ /// Tag Signature
+ public IccScreeningTagDataEntry(IccScreeningFlag flags, IccScreeningChannel[] channels, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Screening, tagSignature)
+ {
+ Guard.NotNull(channels, nameof(channels));
+
+ this.Flags = flags;
+ this.Channels = channels;
+ }
+
+ ///
+ /// Gets the screening flags
+ ///
+ public IccScreeningFlag Flags { get; }
+
+ ///
+ /// Gets the channel information
+ ///
+ public IccScreeningChannel[] Channels { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccScreeningTagDataEntry entry)
+ {
+ return this.Flags == entry.Flags
+ && this.Channels.SequenceEqual(entry.Channels);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccScreeningTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs
new file mode 100644
index 000000000..9ee9c8fa3
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs
@@ -0,0 +1,59 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Typically this type is used for registered tags that can
+ /// be displayed on many development systems as a sequence of four characters.
+ ///
+ internal sealed class IccSignatureTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Signature
+ public IccSignatureTagDataEntry(string signatureData)
+ : this(signatureData, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Signature
+ /// Tag Signature
+ public IccSignatureTagDataEntry(string signatureData, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Signature, tagSignature)
+ {
+ Guard.NotNull(signatureData, nameof(signatureData));
+ this.SignatureData = signatureData;
+ }
+
+ ///
+ /// Gets the Signature
+ ///
+ public string SignatureData { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccSignatureTagDataEntry entry)
+ {
+ return this.SignatureData == entry.SignatureData;
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccSignatureTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs
new file mode 100644
index 000000000..b87538196
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs
@@ -0,0 +1,163 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Globalization;
+
+ ///
+ /// The TextDescriptionType contains three types of text description.
+ ///
+ internal sealed class IccTextDescriptionTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// ASCII text
+ /// Unicode text
+ /// ScriptCode text
+ /// Unicode Language-Code
+ /// ScriptCode Code
+ public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode)
+ : this(ascii, unicode, scriptCode, unicodeLanguageCode, scriptCodeCode, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// ASCII text
+ /// Unicode text
+ /// ScriptCode text
+ /// Unicode Language-Code
+ /// ScriptCode Code
+ /// Tag Signature
+ public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode, IccProfileTag tagSignature)
+ : base(IccTypeSignature.TextDescription, tagSignature)
+ {
+ this.Ascii = ascii;
+ this.Unicode = unicode;
+ this.ScriptCode = scriptCode;
+ this.UnicodeLanguageCode = unicodeLanguageCode;
+ this.ScriptCodeCode = scriptCodeCode;
+ }
+
+ ///
+ /// Gets the ASCII text
+ ///
+ public string Ascii { get; }
+
+ ///
+ /// Gets the Unicode text
+ ///
+ public string Unicode { get; }
+
+ ///
+ /// Gets the ScriptCode text
+ ///
+ public string ScriptCode { get; }
+
+ ///
+ /// Gets the Unicode Language-Code
+ ///
+ public uint UnicodeLanguageCode { get; }
+
+ ///
+ /// Gets the ScriptCode Code
+ ///
+ public ushort ScriptCodeCode { get; }
+
+ ///
+ /// Performs an explicit conversion from
+ /// to .
+ ///
+ /// The entry to convert
+ /// The converted entry
+ public static explicit operator IccMultiLocalizedUnicodeTagDataEntry(IccTextDescriptionTagDataEntry textEntry)
+ {
+ if (textEntry == null)
+ {
+ return null;
+ }
+
+ IccLocalizedString localString;
+ if (!string.IsNullOrEmpty(textEntry.Unicode))
+ {
+ CultureInfo culture = GetCulture(textEntry.UnicodeLanguageCode);
+ if (culture != null)
+ {
+ localString = new IccLocalizedString(culture, textEntry.Unicode);
+ }
+ else
+ {
+ localString = new IccLocalizedString(textEntry.Unicode);
+ }
+ }
+ else if (!string.IsNullOrEmpty(textEntry.Ascii))
+ {
+ localString = new IccLocalizedString(textEntry.Ascii);
+ }
+ else if (!string.IsNullOrEmpty(textEntry.ScriptCode))
+ {
+ localString = new IccLocalizedString(textEntry.ScriptCode);
+ }
+ else
+ {
+ localString = new IccLocalizedString(string.Empty);
+ }
+
+ return new IccMultiLocalizedUnicodeTagDataEntry(new IccLocalizedString[] { localString }, textEntry.TagSignature);
+
+ CultureInfo GetCulture(uint value)
+ {
+ if (value == 0)
+ {
+ return null;
+ }
+
+ byte p1 = (byte)(value >> 24);
+ byte p2 = (byte)(value >> 16);
+ byte p3 = (byte)(value >> 8);
+ byte p4 = (byte)value;
+
+ // Check if the values are [a-z]{2}[A-Z]{2}
+ if (p1 >= 0x61 && p1 <= 0x7A
+ && p2 >= 0x61 && p2 <= 0x7A
+ && p3 >= 0x41 && p3 <= 0x5A
+ && p4 >= 0x41 && p4 <= 0x5A)
+ {
+ string culture = new string(new char[] { (char)p1, (char)p2, '-', (char)p3, (char)p4 });
+ return new CultureInfo(culture);
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccTextDescriptionTagDataEntry entry)
+ {
+ return this.Ascii == entry.Ascii
+ && this.Unicode == entry.Unicode
+ && this.ScriptCode == entry.ScriptCode
+ && this.UnicodeLanguageCode == entry.UnicodeLanguageCode
+ && this.ScriptCodeCode == entry.ScriptCodeCode;
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccTextDescriptionTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs
new file mode 100644
index 000000000..cb102eaaf
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs
@@ -0,0 +1,58 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// This is a simple text structure that contains a text string.
+ ///
+ internal sealed class IccTextTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Text
+ public IccTextTagDataEntry(string text)
+ : this(text, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Text
+ /// Tag Signature
+ public IccTextTagDataEntry(string text, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Text, tagSignature)
+ {
+ Guard.NotNull(text, nameof(text));
+ this.Text = text;
+ }
+
+ ///
+ /// Gets the Text
+ ///
+ public string Text { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccTextTagDataEntry entry)
+ {
+ return this.Text == entry.Text;
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccTextTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs
new file mode 100644
index 000000000..3c326c994
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs
@@ -0,0 +1,59 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This type represents an array of doubles (from 32bit values).
+ ///
+ internal sealed class IccUFix16ArrayTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ public IccUFix16ArrayTagDataEntry(float[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ /// Tag Signature
+ public IccUFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.U16Fixed16Array, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the array data
+ ///
+ public float[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUFix16ArrayTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccUFix16ArrayTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs
new file mode 100644
index 000000000..b290fc5fe
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs
@@ -0,0 +1,59 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This type represents an array of unsigned shorts.
+ ///
+ internal sealed class IccUInt16ArrayTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ public IccUInt16ArrayTagDataEntry(ushort[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ /// Tag Signature
+ public IccUInt16ArrayTagDataEntry(ushort[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.UInt16Array, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the array data
+ ///
+ public ushort[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUInt16ArrayTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccUInt16ArrayTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs
new file mode 100644
index 000000000..2cf0b4fcc
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs
@@ -0,0 +1,59 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This type represents an array of unsigned 32bit integers.
+ ///
+ internal sealed class IccUInt32ArrayTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ public IccUInt32ArrayTagDataEntry(uint[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ /// Tag Signature
+ public IccUInt32ArrayTagDataEntry(uint[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.UInt32Array, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the array data
+ ///
+ public uint[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUInt32ArrayTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccUInt32ArrayTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs
new file mode 100644
index 000000000..6f6ce88bd
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs
@@ -0,0 +1,59 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This type represents an array of unsigned 64bit integers.
+ ///
+ internal sealed class IccUInt64ArrayTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ public IccUInt64ArrayTagDataEntry(ulong[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ /// Tag Signature
+ public IccUInt64ArrayTagDataEntry(ulong[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.UInt64Array, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the array data
+ ///
+ public ulong[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUInt64ArrayTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccUInt64ArrayTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs
new file mode 100644
index 000000000..13356dda6
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs
@@ -0,0 +1,59 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This type represents an array of bytes.
+ ///
+ internal sealed class IccUInt8ArrayTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ public IccUInt8ArrayTagDataEntry(byte[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array data
+ /// Tag Signature
+ public IccUInt8ArrayTagDataEntry(byte[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.UInt8Array, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the array data
+ ///
+ public byte[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUInt8ArrayTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccUInt8ArrayTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs
new file mode 100644
index 000000000..b5de0f10a
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs
@@ -0,0 +1,81 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This type contains curves representing the under color removal and black generation
+ /// and a text string which is a general description of the method used for the UCR and BG.
+ ///
+ internal sealed class IccUcrBgTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// UCR (under color removal) curve values
+ /// BG (black generation) curve values
+ /// Description of the used UCR and BG method
+ public IccUcrBgTagDataEntry(ushort[] ucrCurve, ushort[] bgCurve, string description)
+ : this(ucrCurve, bgCurve, description, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// UCR (under color removal) curve values
+ /// BG (black generation) curve values
+ /// Description of the used UCR and BG method
+ /// Tag Signature
+ public IccUcrBgTagDataEntry(ushort[] ucrCurve, ushort[] bgCurve, string description, IccProfileTag tagSignature)
+ : base(IccTypeSignature.UcrBg, tagSignature)
+ {
+ Guard.NotNull(ucrCurve, nameof(ucrCurve));
+ Guard.NotNull(bgCurve, nameof(bgCurve));
+ Guard.NotNull(description, nameof(description));
+
+ this.UcrCurve = ucrCurve;
+ this.BgCurve = bgCurve;
+ this.Description = description;
+ }
+
+ ///
+ /// Gets the UCR (under color removal) curve values
+ ///
+ public ushort[] UcrCurve { get; }
+
+ ///
+ /// Gets the BG (black generation) curve values
+ ///
+ public ushort[] BgCurve { get; }
+
+ ///
+ /// Gets a description of the used UCR and BG method
+ ///
+ public string Description { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUcrBgTagDataEntry entry)
+ {
+ return this.Description == entry.Description
+ && this.UcrCurve.SequenceEqual(entry.UcrCurve)
+ && this.BgCurve.SequenceEqual(entry.BgCurve);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccUcrBgTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs
new file mode 100644
index 000000000..02726b600
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs
@@ -0,0 +1,59 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// This tag stores data of an unknown tag data entry
+ ///
+ internal sealed class IccUnknownTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw data of the entry
+ public IccUnknownTagDataEntry(byte[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw data of the entry
+ /// Tag Signature
+ public IccUnknownTagDataEntry(byte[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Unknown, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the raw data of the entry
+ ///
+ public byte[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccUnknownTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccUnknownTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs
new file mode 100644
index 000000000..a1a65ed48
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs
@@ -0,0 +1,76 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Numerics;
+
+ ///
+ /// This type represents a set of viewing condition parameters.
+ ///
+ internal sealed class IccViewingConditionsTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// XYZ values of Illuminant
+ /// XYZ values of Surrounding
+ /// Illuminant
+ public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant)
+ : this(illuminantXyz, surroundXyz, illuminant, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// XYZ values of Illuminant
+ /// XYZ values of Surrounding
+ /// Illuminant
+ /// Tag Signature
+ public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant, IccProfileTag tagSignature)
+ : base(IccTypeSignature.ViewingConditions, tagSignature)
+ {
+ this.IlluminantXyz = illuminantXyz;
+ this.SurroundXyz = surroundXyz;
+ this.Illuminant = illuminant;
+ }
+
+ ///
+ /// Gets the XYZ values of Illuminant
+ ///
+ public Vector3 IlluminantXyz { get; }
+
+ ///
+ /// Gets the XYZ values of Surrounding
+ ///
+ public Vector3 SurroundXyz { get; }
+
+ ///
+ /// Gets the illuminant
+ ///
+ public IccStandardIlluminant Illuminant { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccViewingConditionsTagDataEntry entry)
+ {
+ return this.IlluminantXyz == entry.IlluminantXyz
+ && this.SurroundXyz == entry.SurroundXyz
+ && this.Illuminant == entry.Illuminant;
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccViewingConditionsTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs
new file mode 100644
index 000000000..77f9ff706
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs
@@ -0,0 +1,60 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+ using System.Numerics;
+
+ ///
+ /// The XYZType contains an array of XYZ values.
+ ///
+ internal sealed class IccXyzTagDataEntry : IccTagDataEntry, IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The XYZ numbers
+ public IccXyzTagDataEntry(Vector3[] data)
+ : this(data, IccProfileTag.Unknown)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The XYZ numbers
+ /// Tag Signature
+ public IccXyzTagDataEntry(Vector3[] data, IccProfileTag tagSignature)
+ : base(IccTypeSignature.Xyz, tagSignature)
+ {
+ Guard.NotNull(data, nameof(data));
+ this.Data = data;
+ }
+
+ ///
+ /// Gets the XYZ numbers
+ ///
+ public Vector3[] Data { get; }
+
+ ///
+ public override bool Equals(IccTagDataEntry other)
+ {
+ if (base.Equals(other) && other is IccXyzTagDataEntry entry)
+ {
+ return this.Data.SequenceEqual(entry.Data);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(IccXyzTagDataEntry other)
+ {
+ return this.Equals((IccTagDataEntry)other);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs
new file mode 100644
index 000000000..a6a3afa4e
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccClut.cs
@@ -0,0 +1,175 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// Color Lookup Table
+ ///
+ internal sealed class IccClut : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The CLUT values
+ /// The gridpoint count
+ /// The data type of this CLUT
+ public IccClut(float[][] values, byte[] gridPointCount, IccClutDataType type)
+ {
+ Guard.NotNull(values, nameof(values));
+ Guard.NotNull(gridPointCount, nameof(gridPointCount));
+
+ this.Values = values;
+ this.DataType = type;
+ this.InputChannelCount = gridPointCount.Length;
+ this.OutputChannelCount = values[0].Length;
+ this.GridPointCount = gridPointCount;
+ this.CheckValues();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The CLUT values
+ /// The gridpoint count
+ public IccClut(ushort[][] values, byte[] gridPointCount)
+ {
+ Guard.NotNull(values, nameof(values));
+ Guard.NotNull(gridPointCount, nameof(gridPointCount));
+
+ const float max = ushort.MaxValue;
+
+ this.Values = new float[values.Length][];
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.Values[i] = new float[values[i].Length];
+ for (int j = 0; j < values[i].Length; j++)
+ {
+ this.Values[i][j] = values[i][j] / max;
+ }
+ }
+
+ this.DataType = IccClutDataType.UInt16;
+ this.InputChannelCount = gridPointCount.Length;
+ this.OutputChannelCount = values[0].Length;
+ this.GridPointCount = gridPointCount;
+ this.CheckValues();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The CLUT values
+ /// The gridpoint count
+ public IccClut(byte[][] values, byte[] gridPointCount)
+ {
+ Guard.NotNull(values, nameof(values));
+ Guard.NotNull(gridPointCount, nameof(gridPointCount));
+
+ const float max = byte.MaxValue;
+
+ this.Values = new float[values.Length][];
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.Values[i] = new float[values[i].Length];
+ for (int j = 0; j < values[i].Length; j++)
+ {
+ this.Values[i][j] = values[i][j] / max;
+ }
+ }
+
+ this.DataType = IccClutDataType.UInt8;
+ this.InputChannelCount = gridPointCount.Length;
+ this.OutputChannelCount = values[0].Length;
+ this.GridPointCount = gridPointCount;
+ this.CheckValues();
+ }
+
+ ///
+ /// Gets the values that make up this table
+ ///
+ public float[][] Values { get; }
+
+ ///
+ /// Gets or sets the CLUT data type (important when writing a profile)
+ ///
+ public IccClutDataType DataType { get; set; }
+
+ ///
+ /// Gets the number of input channels
+ ///
+ public int InputChannelCount { get; }
+
+ ///
+ /// Gets the number of output channels
+ ///
+ public int OutputChannelCount { get; }
+
+ ///
+ /// Gets the number of grid points per input channel
+ ///
+ public byte[] GridPointCount { get; }
+
+ ///
+ public bool Equals(IccClut other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.DataType == other.DataType
+ && this.InputChannelCount == other.InputChannelCount
+ && this.OutputChannelCount == other.OutputChannelCount
+ && this.GridPointCount.SequenceEqual(other.GridPointCount)
+ && this.EqualsValuesArray(other);
+ }
+
+ private bool EqualsValuesArray(IccClut other)
+ {
+ if (this.Values.Length != other.Values.Length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < this.Values.Length; i++)
+ {
+ if (!this.Values[i].SequenceEqual(other.Values[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void CheckValues()
+ {
+ Guard.MustBeBetweenOrEqualTo(this.InputChannelCount, 1, 15, nameof(this.InputChannelCount));
+ Guard.MustBeBetweenOrEqualTo(this.OutputChannelCount, 1, 15, nameof(this.OutputChannelCount));
+
+ bool isLengthDifferent = this.Values.Any(t => t.Length != this.OutputChannelCount);
+ Guard.IsFalse(isLengthDifferent, nameof(this.Values), "The number of output values varies");
+
+ int length = 0;
+ for (int i = 0; i < this.InputChannelCount; i++)
+ {
+ length += (int)Math.Pow(this.GridPointCount[i], this.InputChannelCount);
+ }
+
+ length /= this.InputChannelCount;
+
+ Guard.IsTrue(this.Values.Length == length, nameof(this.Values), "Length of values array does not match the grid points");
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs
new file mode 100644
index 000000000..bd8955075
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs
@@ -0,0 +1,125 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Entry of ICC colorant table
+ ///
+ internal struct IccColorantTableEntry : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Name of the colorant
+ public IccColorantTableEntry(string name)
+ : this(name, 0, 0, 0)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Name of the colorant
+ /// First PCS value
+ /// Second PCS value
+ /// Third PCS value
+ public IccColorantTableEntry(string name, ushort pcs1, ushort pcs2, ushort pcs3)
+ {
+ Guard.NotNull(name, nameof(name));
+
+ this.Name = name;
+ this.Pcs1 = pcs1;
+ this.Pcs2 = pcs2;
+ this.Pcs3 = pcs3;
+ }
+
+ ///
+ /// Gets the colorant name
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the first PCS value
+ ///
+ public ushort Pcs1 { get; }
+
+ ///
+ /// Gets the second PCS value
+ ///
+ public ushort Pcs2 { get; }
+
+ ///
+ /// Gets the third PCS value
+ ///
+ public ushort Pcs3 { 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 parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccColorantTableEntry left, IccColorantTableEntry right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccColorantTableEntry left, IccColorantTableEntry right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccColorantTableEntry) && this.Equals((IccColorantTableEntry)other);
+ }
+
+ ///
+ public bool Equals(IccColorantTableEntry other)
+ {
+ return this.Name == other.Name
+ && this.Pcs1 == other.Pcs1
+ && this.Pcs2 == other.Pcs2
+ && this.Pcs3 == other.Pcs3;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.Name.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Pcs1.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Pcs2.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Pcs3.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.Name}: {this.Pcs1}; {this.Pcs2}; {this.Pcs3}";
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs
new file mode 100644
index 000000000..0e2772963
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLocalizedString.cs
@@ -0,0 +1,69 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Globalization;
+
+ ///
+ /// A string with a specific locale
+ ///
+ internal sealed class IccLocalizedString : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ /// The culture will be
+ ///
+ /// The text value of this string
+ public IccLocalizedString(string text)
+ : this(CultureInfo.CurrentCulture, text)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ /// The culture will be
+ ///
+ /// The culture of this string
+ /// The text value of this string
+ public IccLocalizedString(CultureInfo culture, string text)
+ {
+ Guard.NotNull(culture, nameof(culture));
+ Guard.NotNull(text, nameof(text));
+
+ this.Culture = culture;
+ this.Text = text;
+ }
+
+ ///
+ /// Gets the actual text value
+ ///
+ public string Text { get; }
+
+ ///
+ /// Gets the culture of the text
+ ///
+ public CultureInfo Culture { get; }
+
+ ///
+ public bool Equals(IccLocalizedString other)
+ {
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Culture.Equals(other.Culture)
+ && this.Text == other.Text;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.Culture.Name}: {this.Text}";
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs
new file mode 100644
index 000000000..9b86e1113
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccLut.cs
@@ -0,0 +1,81 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// Lookup Table
+ ///
+ internal sealed class IccLut : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The LUT values
+ public IccLut(float[] values)
+ {
+ Guard.NotNull(values, nameof(values));
+ this.Values = values;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The LUT values
+ public IccLut(ushort[] values)
+ {
+ Guard.NotNull(values, nameof(values));
+
+ const float max = ushort.MaxValue;
+
+ this.Values = new float[values.Length];
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.Values[i] = values[i] / max;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The LUT values
+ public IccLut(byte[] values)
+ {
+ Guard.NotNull(values, nameof(values));
+
+ const float max = byte.MaxValue;
+
+ this.Values = new float[values.Length];
+ for (int i = 0; i < values.Length; i++)
+ {
+ this.Values[i] = values[i] / max;
+ }
+ }
+
+ ///
+ /// Gets the values that make up this table
+ ///
+ public float[] Values { get; }
+
+ ///
+ public bool Equals(IccLut other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Values.SequenceEqual(other.Values);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs
new file mode 100644
index 000000000..5d5161568
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs
@@ -0,0 +1,110 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// A specific color with a name
+ ///
+ internal struct IccNamedColor : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Name of the color
+ /// Coordinates of the color in the profiles PCS
+ /// Coordinates of the color in the profiles Device-Space
+ public IccNamedColor(string name, ushort[] pcsCoordinates, ushort[] deviceCoordinates)
+ {
+ Guard.NotNull(name, nameof(name));
+ Guard.NotNull(pcsCoordinates, nameof(pcsCoordinates));
+ Guard.IsTrue(pcsCoordinates.Length == 3, nameof(pcsCoordinates), "Must have a length of 3");
+
+ this.Name = name;
+ this.PcsCoordinates = pcsCoordinates;
+ this.DeviceCoordinates = deviceCoordinates;
+ }
+
+ ///
+ /// Gets the name of the color
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the coordinates of the color in the profiles PCS
+ ///
+ public ushort[] PcsCoordinates { get; }
+
+ ///
+ /// Gets the coordinates of the color in the profiles Device-Space
+ ///
+ public ushort[] DeviceCoordinates { 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 parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccNamedColor left, IccNamedColor right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccNamedColor left, IccNamedColor right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccNamedColor) && this.Equals((IccNamedColor)other);
+ }
+
+ ///
+ public bool Equals(IccNamedColor other)
+ {
+ return this.Name == other.Name
+ && this.PcsCoordinates.SequenceEqual(other.PcsCoordinates)
+ && this.DeviceCoordinates.SequenceEqual(other.DeviceCoordinates);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.Name.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.PcsCoordinates.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.DeviceCoordinates.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return this.Name;
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs
new file mode 100644
index 000000000..768aead0e
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs
@@ -0,0 +1,91 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Position of an object within an ICC profile
+ ///
+ internal struct IccPositionNumber : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Offset in bytes
+ /// Size in bytes
+ public IccPositionNumber(uint offset, uint size)
+ {
+ this.Offset = offset;
+ this.Size = size;
+ }
+
+ ///
+ /// Gets the offset in bytes
+ ///
+ public uint Offset { get; }
+
+ ///
+ /// Gets the size in bytes
+ ///
+ public uint Size { 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 parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccPositionNumber left, IccPositionNumber right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccPositionNumber left, IccPositionNumber right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccPositionNumber) && this.Equals((IccPositionNumber)other);
+ }
+
+ ///
+ public bool Equals(IccPositionNumber other)
+ {
+ return this.Offset == other.Offset
+ && this.Size == other.Size;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return unchecked((int)(this.Offset ^ this.Size));
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.Offset}; {this.Size}";
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs
new file mode 100644
index 000000000..dc5c7c1aa
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileDescription.cs
@@ -0,0 +1,90 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// ICC Profile description
+ ///
+ internal sealed class IccProfileDescription : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Device Manufacturer
+ /// Device Model
+ /// Device Attributes
+ /// Technology Information
+ /// Device Manufacturer Info
+ /// Device Model Info
+ public IccProfileDescription(
+ uint deviceManufacturer,
+ uint deviceModel,
+ IccDeviceAttribute deviceAttributes,
+ IccProfileTag technologyInformation,
+ IccLocalizedString[] deviceManufacturerInfo,
+ IccLocalizedString[] deviceModelInfo)
+ {
+ Guard.NotNull(deviceManufacturerInfo, nameof(deviceManufacturerInfo));
+ Guard.NotNull(deviceModelInfo, nameof(deviceModelInfo));
+
+ this.DeviceManufacturer = deviceManufacturer;
+ this.DeviceModel = deviceModel;
+ this.DeviceAttributes = deviceAttributes;
+ this.TechnologyInformation = technologyInformation;
+ this.DeviceManufacturerInfo = deviceManufacturerInfo;
+ this.DeviceModelInfo = deviceModelInfo;
+ }
+
+ ///
+ /// Gets the device manufacturer
+ ///
+ public uint DeviceManufacturer { get; }
+
+ ///
+ /// Gets the device model
+ ///
+ public uint DeviceModel { get; }
+
+ ///
+ /// Gets the device attributes
+ ///
+ public IccDeviceAttribute DeviceAttributes { get; }
+
+ ///
+ /// Gets the technology information
+ ///
+ public IccProfileTag TechnologyInformation { get; }
+
+ ///
+ /// Gets the device manufacturer info
+ ///
+ public IccLocalizedString[] DeviceManufacturerInfo { get; }
+
+ ///
+ /// Gets the device model info
+ ///
+ public IccLocalizedString[] DeviceModelInfo { get; }
+
+ ///
+ public bool Equals(IccProfileDescription other)
+ {
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.DeviceManufacturer == other.DeviceManufacturer
+ && this.DeviceModel == other.DeviceModel
+ && this.DeviceAttributes == other.DeviceAttributes
+ && this.TechnologyInformation == other.TechnologyInformation
+ && this.DeviceManufacturerInfo.SequenceEqual(other.DeviceManufacturerInfo)
+ && this.DeviceModelInfo.SequenceEqual(other.DeviceModelInfo);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs
new file mode 100644
index 000000000..fc4760d3f
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs
@@ -0,0 +1,138 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// ICC Profile ID
+ ///
+ public struct IccProfileId : IEquatable
+ {
+ ///
+ /// A profile ID with all values set to zero
+ ///
+ public static readonly IccProfileId Zero = new IccProfileId(0, 0, 0, 0);
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Part 1 of the ID
+ /// Part 2 of the ID
+ /// Part 3 of the ID
+ /// Part 4 of the ID
+ public IccProfileId(uint p1, uint p2, uint p3, uint p4)
+ {
+ this.Part1 = p1;
+ this.Part2 = p2;
+ this.Part3 = p3;
+ this.Part4 = p4;
+ }
+
+ ///
+ /// Gets the first part of the ID
+ ///
+ public uint Part1 { get; }
+
+ ///
+ /// Gets the second part of the ID
+ ///
+ public uint Part2 { get; }
+
+ ///
+ /// Gets the third part of the ID
+ ///
+ public uint Part3 { get; }
+
+ ///
+ /// Gets the fourth part of the ID
+ ///
+ public uint Part4 { get; }
+
+ ///
+ /// Gets a value indicating whether the ID is set or just consists of zeros
+ ///
+ public bool IsSet
+ {
+ get
+ {
+ return this.Part1 != 0
+ && this.Part2 != 0
+ && this.Part3 != 0
+ && this.Part4 != 0;
+ }
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccProfileId left, IccProfileId right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccProfileId left, IccProfileId right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccProfileId) && this.Equals((IccProfileId)other);
+ }
+
+ ///
+ public bool Equals(IccProfileId other)
+ {
+ return this.Part1 == other.Part1
+ && this.Part2 == other.Part2
+ && this.Part3 == other.Part3
+ && this.Part4 == other.Part4;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.Part1.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Part2.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Part3.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Part4.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{ToHex(this.Part1)}-{ToHex(this.Part2)}-{ToHex(this.Part3)}-{ToHex(this.Part4)}";
+ }
+
+ private static string ToHex(uint value)
+ {
+ return value.ToString("X").PadLeft(8, '0');
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs
new file mode 100644
index 000000000..c6d8519fc
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs
@@ -0,0 +1,51 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// Description of a profile within a sequence
+ ///
+ internal sealed class IccProfileSequenceIdentifier : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// ID of the profile
+ /// Description of the profile
+ public IccProfileSequenceIdentifier(IccProfileId id, IccLocalizedString[] description)
+ {
+ Guard.NotNull(description, nameof(description));
+
+ this.Id = id;
+ this.Description = description;
+ }
+
+ ///
+ /// Gets the ID of the profile
+ ///
+ public IccProfileId Id { get; }
+
+ ///
+ /// Gets the description of the profile
+ ///
+ public IccLocalizedString[] Description { get; }
+
+ ///
+ public bool Equals(IccProfileSequenceIdentifier other)
+ {
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return this.Id.Equals(other.Id)
+ && this.Description.SequenceEqual(other.Description);
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs
new file mode 100644
index 000000000..5c58aa1b1
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs
@@ -0,0 +1,96 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Associates a normalized device code with a measurement value
+ ///
+ internal struct IccResponseNumber : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Device Code
+ /// Measurement Value
+ public IccResponseNumber(ushort deviceCode, float measurementValue)
+ {
+ this.DeviceCode = deviceCode;
+ this.MeasurementValue = measurementValue;
+ }
+
+ ///
+ /// Gets the device code
+ ///
+ public ushort DeviceCode { get; }
+
+ ///
+ /// Gets the measurement value
+ ///
+ public float MeasurementValue { 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 parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccResponseNumber left, IccResponseNumber right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccResponseNumber left, IccResponseNumber right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccResponseNumber) && this.Equals((IccResponseNumber)other);
+ }
+
+ ///
+ public bool Equals(IccResponseNumber other)
+ {
+ return this.DeviceCode == other.DeviceCode
+ && this.MeasurementValue == other.MeasurementValue;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.DeviceCode.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.MeasurementValue.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"Code: {this.DeviceCode}; Value: {this.MeasurementValue}";
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs
new file mode 100644
index 000000000..40eab6ef4
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs
@@ -0,0 +1,105 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// A single channel of a
+ ///
+ internal struct IccScreeningChannel : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Screen frequency
+ /// Angle in degrees
+ /// Spot shape
+ public IccScreeningChannel(float frequency, float angle, IccScreeningSpotType spotShape)
+ {
+ this.Frequency = frequency;
+ this.Angle = angle;
+ this.SpotShape = spotShape;
+ }
+
+ ///
+ /// Gets the screen frequency
+ ///
+ public float Frequency { get; }
+
+ ///
+ /// Gets the angle in degrees
+ ///
+ public float Angle { get; }
+
+ ///
+ /// Gets the spot shape
+ ///
+ public IccScreeningSpotType SpotShape { 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 parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccScreeningChannel left, IccScreeningChannel right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccScreeningChannel left, IccScreeningChannel right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccScreeningChannel) && this.Equals((IccScreeningChannel)other);
+ }
+
+ ///
+ public bool Equals(IccScreeningChannel other)
+ {
+ return this.Frequency == other.Frequency
+ && this.Angle == other.Angle
+ && this.SpotShape == other.SpotShape;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.Frequency.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Angle.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.SpotShape.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.Frequency}Hz; {this.Angle}°; {this.SpotShape}";
+ }
+ }
+}
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs
new file mode 100644
index 000000000..eb7f0c63b
--- /dev/null
+++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs
@@ -0,0 +1,105 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Entry of ICC tag table
+ ///
+ internal struct IccTagTableEntry : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Signature of the tag
+ /// Offset of entry in bytes
+ /// Size of entry in bytes
+ public IccTagTableEntry(IccProfileTag signature, uint offset, uint dataSize)
+ {
+ this.Signature = signature;
+ this.Offset = offset;
+ this.DataSize = dataSize;
+ }
+
+ ///
+ /// Gets the signature of the tag
+ ///
+ public IccProfileTag Signature { get; }
+
+ ///
+ /// Gets the offset of entry in bytes
+ ///
+ public uint Offset { get; }
+
+ ///
+ /// Gets the size of entry in bytes
+ ///
+ public uint DataSize { 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 parameter is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(IccTagTableEntry left, IccTagTableEntry right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
+ ///
+ /// True if the parameter is not equal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(IccTagTableEntry left, IccTagTableEntry right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override bool Equals(object other)
+ {
+ return (other is IccProfileId) && this.Equals((IccProfileId)other);
+ }
+
+ ///
+ public bool Equals(IccTagTableEntry other)
+ {
+ return this.Signature == other.Signature
+ && this.Offset == other.Offset
+ && this.DataSize == other.DataSize;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = this.Signature.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.Offset.GetHashCode();
+ hashCode = (hashCode * 397) ^ this.DataSize.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"{this.Signature} (Offset: {this.Offset}; Size: {this.DataSize})";
+ }
+ }
+}
diff --git a/src/ImageSharp/Numerics/Point.cs b/src/ImageSharp/Numerics/Point.cs
index 3cd47659c..8d523895f 100644
--- a/src/ImageSharp/Numerics/Point.cs
+++ b/src/ImageSharp/Numerics/Point.cs
@@ -137,7 +137,7 @@ namespace ImageSharp
/// The rotation
public static Matrix3x2 CreateRotation(Point origin, float degrees)
{
- float radians = ImageMaths.DegreesToRadians(degrees);
+ float radians = MathF.DegreeToRadian(degrees);
return Matrix3x2.CreateRotation(radians, new Vector2(origin.X, origin.Y));
}
@@ -173,8 +173,8 @@ namespace ImageSharp
/// The rotation
public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY)
{
- float radiansX = ImageMaths.DegreesToRadians(degreesX);
- float radiansY = ImageMaths.DegreesToRadians(degreesY);
+ float radiansX = MathF.DegreeToRadian(degreesX);
+ float radiansY = MathF.DegreeToRadian(degreesY);
return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(origin.X, origin.Y));
}
diff --git a/src/ImageSharp/PixelFormats/Rgba32.ColorspaceTransforms.cs b/src/ImageSharp/PixelFormats/Rgba32.ColorspaceTransforms.cs
deleted file mode 100644
index 1dc2292b1..000000000
--- a/src/ImageSharp/PixelFormats/Rgba32.ColorspaceTransforms.cs
+++ /dev/null
@@ -1,283 +0,0 @@
-//
-// Copyright (c) James Jackson-South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-
-namespace ImageSharp
-{
- using System;
- using System.Numerics;
- using Colors.Spaces;
-
- ///
- /// Provides implicit colorspace transformation.
- ///
- public partial struct Rgba32
- {
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Rgba32(Bgra32 color)
- {
- return new Rgba32(color.R, color.G, color.B, color.A);
- }
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Rgba32(Cmyk cmykColor)
- {
- float r = (1 - cmykColor.C) * (1 - cmykColor.K);
- float g = (1 - cmykColor.M) * (1 - cmykColor.K);
- float b = (1 - cmykColor.Y) * (1 - cmykColor.K);
- return new Rgba32(r, g, b, 1);
- }
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Rgba32(YCbCr color)
- {
- float y = color.Y;
- float cb = color.Cb - 128;
- float cr = color.Cr - 128;
-
- byte r = (byte)(y + (1.402F * cr)).Clamp(0, 255);
- byte g = (byte)(y - (0.34414F * cb) - (0.71414F * cr)).Clamp(0, 255);
- byte b = (byte)(y + (1.772F * cb)).Clamp(0, 255);
-
- return new Rgba32(r, g, b);
- }
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Rgba32(CieXyz color)
- {
- float x = color.X / 100F;
- float y = color.Y / 100F;
- float z = color.Z / 100F;
-
- // Then XYZ to RGB (multiplication by 100 was done above already)
- float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F);
- float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F);
- float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F);
-
- Vector4 vector = new Vector4(r, g, b, 1).Compress();
- return new Rgba32(vector);
- }
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Rgba32(Hsv color)
- {
- float s = color.S;
- float v = color.V;
-
- if (MathF.Abs(s) < Constants.Epsilon)
- {
- return new Rgba32(v, v, v, 1);
- }
-
- float h = (MathF.Abs(color.H - 360) < Constants.Epsilon) ? 0 : color.H / 60;
- int i = (int)Math.Truncate(h);
- float f = h - i;
-
- float p = v * (1.0F - s);
- float q = v * (1.0F - (s * f));
- float t = v * (1.0F - (s * (1.0F - f)));
-
- float r, g, b;
- switch (i)
- {
- case 0:
- r = v;
- g = t;
- b = p;
- break;
-
- case 1:
- r = q;
- g = v;
- b = p;
- break;
-
- case 2:
- r = p;
- g = v;
- b = t;
- break;
-
- case 3:
- r = p;
- g = q;
- b = v;
- break;
-
- case 4:
- r = t;
- g = p;
- b = v;
- break;
-
- default:
- r = v;
- g = p;
- b = q;
- break;
- }
-
- return new Rgba32(r, g, b, 1);
- }
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Rgba32(Hsl color)
- {
- float rangedH = color.H / 360F;
- float r = 0;
- float g = 0;
- float b = 0;
- float s = color.S;
- float l = color.L;
-
- if (MathF.Abs(l) > Constants.Epsilon)
- {
- if (MathF.Abs(s) < Constants.Epsilon)
- {
- r = g = b = l;
- }
- else
- {
- float temp2 = (l < 0.5f) ? l * (1f + s) : l + s - (l * s);
- float temp1 = (2f * l) - temp2;
-
- r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F);
- g = GetColorComponent(temp1, temp2, rangedH);
- b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F);
- }
- }
-
- return new Rgba32(r, g, b, 1);
- }
-
- ///
- /// Allows the implicit conversion of an instance of to a
- /// .
- ///
- /// The instance of to convert.
- ///
- /// An instance of .
- ///
- public static implicit operator Rgba32(CieLab cieLabColor)
- {
- // First convert back to XYZ...
- float y = (cieLabColor.L + 16F) / 116F;
- float x = (cieLabColor.A / 500F) + y;
- float z = y - (cieLabColor.B / 200F);
-
- float x3 = x * x * x;
- float y3 = y * y * y;
- float z3 = z * z * z;
-
- x = x3 > 0.008856F ? x3 : (x - 0.137931F) / 7.787F;
- y = (cieLabColor.L > 7.999625F) ? y3 : (cieLabColor.L / 903.3F);
- z = (z3 > 0.008856F) ? z3 : (z - 0.137931F) / 7.787F;
-
- x *= 0.95047F;
- z *= 1.08883F;
-
- // Then XYZ to RGB (multiplication by 100 was done above already)
- float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F);
- float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F);
- float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F);
-
- return new Rgba32(new Vector4(r, g, b, 1F).Compress());
- }
-
- ///
- /// Gets the color component from the given values.
- ///
- /// The first value.
- /// The second value.
- /// The third value.
- ///
- /// The .
- ///
- private static float GetColorComponent(float first, float second, float third)
- {
- third = MoveIntoRange(third);
- if (third < 0.1666667F)
- {
- return first + ((second - first) * 6.0f * third);
- }
-
- if (third < 0.5)
- {
- return second;
- }
-
- if (third < 0.6666667F)
- {
- return first + ((second - first) * (0.6666667F - third) * 6.0f);
- }
-
- return first;
- }
-
- ///
- /// Moves the specific value within the acceptable range for
- /// conversion.
- /// Used for converting colors to this type.
- ///
- /// The value to shift.
- ///
- /// The .
- ///
- private static float MoveIntoRange(float value)
- {
- if (value < 0.0)
- {
- value += 1.0f;
- }
- else if (value > 1.0)
- {
- value -= 1.0f;
- }
-
- return value;
- }
- }
-}
diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/HueProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/HueProcessor.cs
index 8995663a3..f28683977 100644
--- a/src/ImageSharp/Processing/Processors/ColorMatrix/HueProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/ColorMatrix/HueProcessor.cs
@@ -34,13 +34,13 @@ namespace ImageSharp.Processing.Processors
this.Angle = angle;
- float radians = ImageMaths.DegreesToRadians(angle);
- double cosradians = Math.Cos(radians);
- double sinradians = Math.Sin(radians);
+ float radians = MathF.DegreeToRadian(angle);
+ float cosradians = MathF.Cos(radians);
+ float sinradians = MathF.Sin(radians);
- float lumR = .213f;
- float lumG = .715f;
- float lumB = .072f;
+ float lumR = .213F;
+ float lumG = .715F;
+ float lumB = .072F;
float oneMinusLumR = 1 - lumR;
float oneMinusLumG = 1 - lumG;
@@ -51,15 +51,15 @@ namespace ImageSharp.Processing.Processors
// Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx
Matrix4x4 matrix4X4 = new Matrix4x4()
{
- M11 = (float)(lumR + (cosradians * oneMinusLumR) - (sinradians * lumR)),
- M12 = (float)(lumR - (cosradians * lumR) - (sinradians * 0.143)),
- M13 = (float)(lumR - (cosradians * lumR) - (sinradians * oneMinusLumR)),
- M21 = (float)(lumG - (cosradians * lumG) - (sinradians * lumG)),
- M22 = (float)(lumG + (cosradians * oneMinusLumG) + (sinradians * 0.140)),
- M23 = (float)(lumG - (cosradians * lumG) + (sinradians * lumG)),
- M31 = (float)(lumB - (cosradians * lumB) + (sinradians * oneMinusLumB)),
- M32 = (float)(lumB - (cosradians * lumB) - (sinradians * 0.283)),
- M33 = (float)(lumB + (cosradians * oneMinusLumB) + (sinradians * lumB)),
+ M11 = lumR + (cosradians * oneMinusLumR) - (sinradians * lumR),
+ M12 = lumR - (cosradians * lumR) - (sinradians * 0.143F),
+ M13 = lumR - (cosradians * lumR) - (sinradians * oneMinusLumR),
+ M21 = lumG - (cosradians * lumG) - (sinradians * lumG),
+ M22 = lumG + (cosradians * oneMinusLumG) + (sinradians * 0.140F),
+ M23 = lumG - (cosradians * lumG) + (sinradians * lumG),
+ M31 = lumB - (cosradians * lumB) + (sinradians * oneMinusLumB),
+ M32 = lumB - (cosradians * lumB) - (sinradians * 0.283F),
+ M33 = lumB + (cosradians * oneMinusLumB) + (sinradians * lumB),
M44 = 1
};
diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs
new file mode 100644
index 000000000..b29fdc25b
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs
@@ -0,0 +1,34 @@
+namespace ImageSharp.Benchmarks.Color
+{
+ using BenchmarkDotNet.Attributes;
+
+ using Colourful;
+ using Colourful.Conversion;
+
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion;
+
+ public class ColorspaceCieXyzToCieLabConvert
+ {
+ private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F);
+
+ private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883);
+
+ private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter();
+
+ private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter();
+
+
+ [Benchmark(Baseline = true, Description = "Colourful Convert")]
+ public double ColourfulConvert()
+ {
+ return ColourfulConverter.ToLab(XYZColor).L;
+ }
+
+ [Benchmark(Description = "ImageSharp Convert")]
+ public float ColorSpaceConvert()
+ {
+ return ColorSpaceConverter.ToCieLab(CieXyz).L;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs
new file mode 100644
index 000000000..3e7d60972
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs
@@ -0,0 +1,34 @@
+namespace ImageSharp.Benchmarks.Color
+{
+ using BenchmarkDotNet.Attributes;
+
+ using Colourful;
+ using Colourful.Conversion;
+
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion;
+
+ public class ColorspaceCieXyzToHunterLabConvert
+ {
+ private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F);
+
+ private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883);
+
+ private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter();
+
+ private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter();
+
+
+ [Benchmark(Baseline = true, Description = "Colourful Convert")]
+ public double ColourfulConvert()
+ {
+ return ColourfulConverter.ToHunterLab(XYZColor).L;
+ }
+
+ [Benchmark(Description = "ImageSharp Convert")]
+ public float ColorSpaceConvert()
+ {
+ return ColorSpaceConverter.ToHunterLab(CieXyz).L;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs
new file mode 100644
index 000000000..f472dd292
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs
@@ -0,0 +1,34 @@
+namespace ImageSharp.Benchmarks.Color
+{
+ using BenchmarkDotNet.Attributes;
+
+ using Colourful;
+ using Colourful.Conversion;
+
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion;
+
+ public class ColorspaceCieXyzToLmsConvert
+ {
+ private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F);
+
+ private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883);
+
+ private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter();
+
+ private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter();
+
+
+ [Benchmark(Baseline = true, Description = "Colourful Convert")]
+ public double ColourfulConvert()
+ {
+ return ColourfulConverter.ToLMS(XYZColor).L;
+ }
+
+ [Benchmark(Description = "ImageSharp Convert")]
+ public float ColorSpaceConvert()
+ {
+ return ColorSpaceConverter.ToLms(CieXyz).L;
+ }
+ }
+}
diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs
new file mode 100644
index 000000000..eeea86c6e
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs
@@ -0,0 +1,34 @@
+namespace ImageSharp.Benchmarks.Color
+{
+ using BenchmarkDotNet.Attributes;
+
+ using Colourful;
+ using Colourful.Conversion;
+
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion;
+
+ public class ColorspaceCieXyzToRgbConvert
+ {
+ private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F);
+
+ private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883);
+
+ private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter();
+
+ private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter();
+
+
+ [Benchmark(Baseline = true, Description = "Colourful Convert")]
+ public double ColourfulConvert()
+ {
+ return ColourfulConverter.ToRGB(XYZColor).R;
+ }
+
+ [Benchmark(Description = "ImageSharp Convert")]
+ public float ColorSpaceConvert()
+ {
+ return ColorSpaceConverter.ToRgb(CieXyz).R;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs
new file mode 100644
index 000000000..21d040e5c
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs
@@ -0,0 +1,34 @@
+namespace ImageSharp.Benchmarks.Color
+{
+ using BenchmarkDotNet.Attributes;
+
+ using Colourful;
+ using Colourful.Conversion;
+
+ using ImageSharp.ColorSpaces;
+ using ImageSharp.ColorSpaces.Conversion;
+
+ public class RgbWorkingSpaceAdapt
+ {
+ private static readonly Rgb Rgb = new Rgb(0.206162F, 0.260277F, 0.746717F, RgbWorkingSpaces.WideGamutRgb);
+
+ private static readonly RGBColor RGBColor = new RGBColor(0.206162, 0.260277, 0.746717, RGBWorkingSpaces.WideGamutRGB);
+
+ private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb };
+
+ private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter { TargetRGBWorkingSpace = RGBWorkingSpaces.sRGB };
+
+
+ [Benchmark(Baseline = true, Description = "Colourful Adapt")]
+ public RGBColor ColourfulConvert()
+ {
+ return ColourfulConverter.Adapt(RGBColor);
+ }
+
+ [Benchmark(Description = "ImageSharp Adapt")]
+ internal Rgb ColorSpaceConvert()
+ {
+ return ColorSpaceConverter.Adapt(Rgb);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
index b2070c0de..763ede521 100644
--- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
+++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
@@ -8,6 +8,7 @@
+
diff --git a/tests/ImageSharp.Tests/Colors/ColorConversionTests.cs b/tests/ImageSharp.Tests/Colors/ColorConversionTests.cs
deleted file mode 100644
index 4b45d0ab4..000000000
--- a/tests/ImageSharp.Tests/Colors/ColorConversionTests.cs
+++ /dev/null
@@ -1,494 +0,0 @@
-//
-// Copyright (c) James Jackson-South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-
-namespace ImageSharp.Tests
-{
- using System;
- using System.Diagnostics.CodeAnalysis;
- using ImageSharp.Colors.Spaces;
- using ImageSharp.PixelFormats;
- using Xunit;
-
- ///
- /// Test conversion between the various color structs.
- ///
- ///
- /// Output values have been compared with
- /// and for accuracy.
- ///
- public class ColorConversionTests
- {
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
- Justification = "Reviewed. Suppression is OK here.")]
- public void ColorToYCbCr()
- {
- // White
- Rgba32 color = Rgba32.White;
- YCbCr yCbCr = color;
-
- Assert.Equal(255, yCbCr.Y);
- Assert.Equal(128, yCbCr.Cb);
- Assert.Equal(128, yCbCr.Cr);
-
- // Black
- Rgba32 color2 = Rgba32.Black;
- YCbCr yCbCr2 = color2;
- Assert.Equal(0, yCbCr2.Y);
- Assert.Equal(128, yCbCr2.Cb);
- Assert.Equal(128, yCbCr2.Cr);
-
- // Gray
- Rgba32 color3 = Rgba32.Gray;
- YCbCr yCbCr3 = color3;
- Assert.Equal(128, yCbCr3.Y);
- Assert.Equal(128, yCbCr3.Cb);
- Assert.Equal(128, yCbCr3.Cr);
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
- Justification = "Reviewed. Suppression is OK here.")]
- public void YCbCrToColor()
- {
- // White
- YCbCr yCbCr = new YCbCr(255, 128, 128);
- Rgba32 color = yCbCr;
-
- Assert.Equal(255, color.R);
- Assert.Equal(255, color.G);
- Assert.Equal(255, color.B);
- Assert.Equal(255, color.A);
-
- // Black
- YCbCr yCbCr2 = new YCbCr(0, 128, 128);
- Rgba32 color2 = yCbCr2;
-
- Assert.Equal(0, color2.R);
- Assert.Equal(0, color2.G);
- Assert.Equal(0, color2.B);
- Assert.Equal(255, color2.A);
-
- // Gray
- YCbCr yCbCr3 = new YCbCr(128, 128, 128);
- Rgba32 color3 = yCbCr3;
-
- Assert.Equal(128, color3.R);
- Assert.Equal(128, color3.G);
- Assert.Equal(128, color3.B);
- Assert.Equal(255, color3.A);
- }
-
- ///
- /// Tests the implicit conversion from to .
- /// Comparison values obtained from
- /// http://colormine.org/convert/rgb-to-xyz
- ///
- [Fact]
- public void ColorToCieXyz()
- {
- // White
- Rgba32 color = Rgba32.White;
- CieXyz ciexyz = color;
-
- Assert.Equal(95.05f, ciexyz.X, 3);
- Assert.Equal(100.0f, ciexyz.Y, 3);
- Assert.Equal(108.900f, ciexyz.Z, 3);
-
- // Black
- Rgba32 color2 = Rgba32.Black;
- CieXyz ciexyz2 = color2;
- Assert.Equal(0, ciexyz2.X, 3);
- Assert.Equal(0, ciexyz2.Y, 3);
- Assert.Equal(0, ciexyz2.Z, 3);
-
- // Gray
- Rgba32 color3 = Rgba32.Gray;
- CieXyz ciexyz3 = color3;
- Assert.Equal(20.518, ciexyz3.X, 3);
- Assert.Equal(21.586, ciexyz3.Y, 3);
- Assert.Equal(23.507, ciexyz3.Z, 3);
-
- // Cyan
- Rgba32 color4 = Rgba32.Cyan;
- CieXyz ciexyz4 = color4;
- Assert.Equal(53.810f, ciexyz4.X, 3);
- Assert.Equal(78.740f, ciexyz4.Y, 3);
- Assert.Equal(106.970f, ciexyz4.Z, 3);
- }
-
- ///
- /// Tests the implicit conversion from to .
- /// Comparison values obtained from
- /// http://colormine.org/convert/rgb-to-xyz
- ///
- [Fact]
- public void CieXyzToColor()
- {
- // Dark moderate pink.
- CieXyz ciexyz = new CieXyz(13.337f, 9.297f, 14.727f);
- Rgba32 color = ciexyz;
-
- Assert.Equal(128, color.R);
- Assert.Equal(64, color.G);
- Assert.Equal(106, color.B);
-
- // Ochre
- CieXyz ciexyz2 = new CieXyz(31.787f, 26.147f, 4.885f);
- Rgba32 color2 = ciexyz2;
-
- Assert.Equal(204, color2.R);
- Assert.Equal(119, color2.G);
- Assert.Equal(34, color2.B);
-
- // Black
- CieXyz ciexyz3 = new CieXyz(0, 0, 0);
- Rgba32 color3 = ciexyz3;
-
- Assert.Equal(0, color3.R);
- Assert.Equal(0, color3.G);
- Assert.Equal(0, color3.B);
-
- //// Check others.
- //Random random = new Random(0);
- //for (int i = 0; i < 1000; i++)
- //{
- // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
- // CieXyz ciexyz4 = color4;
- // Assert.Equal(color4, (Color)ciexyz4);
- //}
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
- Justification = "Reviewed. Suppression is OK here.")]
- public void ColorToHsv()
- {
- // Black
- Rgba32 b = Rgba32.Black;
- Hsv h = b;
-
- Assert.Equal(0, h.H, 1);
- Assert.Equal(0, h.S, 1);
- Assert.Equal(0, h.V, 1);
-
- // White
- Rgba32 color = Rgba32.White;
- Hsv hsv = color;
-
- Assert.Equal(0f, hsv.H, 1);
- Assert.Equal(0f, hsv.S, 1);
- Assert.Equal(1f, hsv.V, 1);
-
- // Dark moderate pink.
- Rgba32 color2 = new Rgba32(128, 64, 106);
- Hsv hsv2 = color2;
-
- Assert.Equal(320.6f, hsv2.H, 1);
- Assert.Equal(0.5f, hsv2.S, 1);
- Assert.Equal(0.502f, hsv2.V, 2);
-
- // Ochre.
- Rgba32 color3 = new Rgba32(204, 119, 34);
- Hsv hsv3 = color3;
-
- Assert.Equal(30f, hsv3.H, 1);
- Assert.Equal(0.833f, hsv3.S, 3);
- Assert.Equal(0.8f, hsv3.V, 1);
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- public void HsvToColor()
- {
- // Dark moderate pink.
- Hsv hsv = new Hsv(320.6f, 0.5f, 0.502f);
- Rgba32 color = hsv;
-
- Assert.Equal(color.R, 128);
- Assert.Equal(color.G, 64);
- Assert.Equal(color.B, 106);
-
- // Ochre
- Hsv hsv2 = new Hsv(30, 0.833f, 0.8f);
- Rgba32 color2 = hsv2;
-
- Assert.Equal(color2.R, 204);
- Assert.Equal(color2.G, 119);
- Assert.Equal(color2.B, 34);
-
- // White
- Hsv hsv3 = new Hsv(0, 0, 1);
- Rgba32 color3 = hsv3;
-
- Assert.Equal(color3.B, 255);
- Assert.Equal(color3.G, 255);
- Assert.Equal(color3.R, 255);
-
- // Check others.
- //Random random = new Random(0);
- //for (int i = 0; i < 1000; i++)
- //{
- // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
- // Hsv hsv4 = color4;
- // Assert.Equal(color4, (Color)hsv4);
- //}
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
- Justification = "Reviewed. Suppression is OK here.")]
- public void ColorToHsl()
- {
- // Black
- Rgba32 b = Rgba32.Black;
- Hsl h = b;
-
- Assert.Equal(0, h.H, 1);
- Assert.Equal(0, h.S, 1);
- Assert.Equal(0, h.L, 1);
-
- // White
- Rgba32 color = Rgba32.White;
- Hsl hsl = color;
-
- Assert.Equal(0f, hsl.H, 1);
- Assert.Equal(0f, hsl.S, 1);
- Assert.Equal(1f, hsl.L, 1);
-
- // Dark moderate pink.
- Rgba32 color2 = new Rgba32(128, 64, 106);
- Hsl hsl2 = color2;
-
- Assert.Equal(320.6f, hsl2.H, 1);
- Assert.Equal(0.33f, hsl2.S, 1);
- Assert.Equal(0.376f, hsl2.L, 2);
-
- // Ochre.
- Rgba32 color3 = new Rgba32(204, 119, 34);
- Hsl hsl3 = color3;
-
- Assert.Equal(30f, hsl3.H, 1);
- Assert.Equal(0.714f, hsl3.S, 3);
- Assert.Equal(0.467f, hsl3.L, 3);
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- public void HslToColor()
- {
- // Dark moderate pink.
- Hsl hsl = new Hsl(320.6f, 0.33f, 0.376f);
- Rgba32 color = hsl;
-
- Assert.Equal(color.R, 128);
- Assert.Equal(color.G, 64);
- Assert.Equal(color.B, 106);
-
- // Ochre
- Hsl hsl2 = new Hsl(30, 0.714f, 0.467f);
- Rgba32 color2 = hsl2;
-
- Assert.Equal(color2.R, 204);
- Assert.Equal(color2.G, 119);
- Assert.Equal(color2.B, 34);
-
- // White
- Hsl hsl3 = new Hsl(0, 0, 1);
- Rgba32 color3 = hsl3;
-
- Assert.Equal(color3.R, 255);
- Assert.Equal(color3.G, 255);
- Assert.Equal(color3.B, 255);
-
- // Check others.
- //Random random = new Random(0);
- //for (int i = 0; i < 1000; i++)
- //{
- // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
- // Hsl hsl4 = color4;
- // Assert.Equal(color4, (Color)hsl4);
- //}
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
- Justification = "Reviewed. Suppression is OK here.")]
- public void ColorToCmyk()
- {
- // White
- Rgba32 color = Rgba32.White;
- Cmyk cmyk = color;
-
- Assert.Equal(0, cmyk.C, 1);
- Assert.Equal(0, cmyk.M, 1);
- Assert.Equal(0, cmyk.Y, 1);
- Assert.Equal(0, cmyk.K, 1);
-
- // Black
- Rgba32 color2 = Rgba32.Black;
- Cmyk cmyk2 = color2;
- Assert.Equal(0, cmyk2.C, 1);
- Assert.Equal(0, cmyk2.M, 1);
- Assert.Equal(0, cmyk2.Y, 1);
- Assert.Equal(1, cmyk2.K, 1);
-
- // Gray
- Rgba32 color3 = Rgba32.Gray;
- Cmyk cmyk3 = color3;
- Assert.Equal(0f, cmyk3.C, 1);
- Assert.Equal(0f, cmyk3.M, 1);
- Assert.Equal(0f, cmyk3.Y, 1);
- Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters.
-
- // Cyan
- Rgba32 color4 = Rgba32.Cyan;
- Cmyk cmyk4 = color4;
- Assert.Equal(1, cmyk4.C, 1);
- Assert.Equal(0f, cmyk4.M, 1);
- Assert.Equal(0f, cmyk4.Y, 1);
- Assert.Equal(0f, cmyk4.K, 1);
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- [Fact]
- public void CmykToColor()
- {
- // Dark moderate pink.
- Cmyk cmyk = new Cmyk(0f, .5f, .171f, .498f);
- Rgba32 color = cmyk;
-
- Assert.Equal(color.R, 128);
- Assert.Equal(color.G, 64);
- Assert.Equal(color.B, 106);
-
- // Ochre
- Cmyk cmyk2 = new Cmyk(0, .416f, .833f, .199f);
- Rgba32 color2 = cmyk2;
-
- Assert.Equal(color2.R, 204);
- Assert.Equal(color2.G, 119);
- Assert.Equal(color2.B, 34);
-
- // White
- Cmyk cmyk3 = new Cmyk(0, 0, 0, 0);
- Rgba32 color3 = cmyk3;
-
- Assert.Equal(color3.R, 255);
- Assert.Equal(color3.G, 255);
- Assert.Equal(color3.B, 255);
-
- // Check others.
- //Random random = new Random(0);
- //for (int i = 0; i < 1000; i++)
- //{
- // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
- // Cmyk cmyk4 = color4;
- // Assert.Equal(color4, (Color)cmyk4);
- //}
- }
-
- ///
- /// Tests the implicit conversion from to .
- /// Comparison values obtained from
- /// http://colormine.org/convert/rgb-to-lab
- ///
- [Fact]
- public void ColorToCieLab()
- {
- // White
- Rgba32 color = Rgba32.White;
- CieLab cielab = color;
-
- Assert.Equal(100, cielab.L, 3);
- Assert.Equal(0.005, cielab.A, 3);
- Assert.Equal(-0.010, cielab.B, 3);
-
- // Black
- Rgba32 color2 = Rgba32.Black;
- CieLab cielab2 = color2;
- Assert.Equal(0, cielab2.L, 3);
- Assert.Equal(0, cielab2.A, 3);
- Assert.Equal(0, cielab2.B, 3);
-
- // Gray
- Rgba32 color3 = Rgba32.Gray;
- CieLab cielab3 = color3;
- Assert.Equal(53.585, cielab3.L, 3);
- Assert.Equal(0.003, cielab3.A, 3);
- Assert.Equal(-0.006, cielab3.B, 3);
-
- // Cyan
- Rgba32 color4 = Rgba32.Cyan;
- CieLab cielab4 = color4;
- Assert.Equal(91.117, cielab4.L, 3);
- Assert.Equal(-48.080, cielab4.A, 3);
- Assert.Equal(-14.138, cielab4.B, 3);
- }
-
- ///
- /// Tests the implicit conversion from to .
- ///
- /// Comparison values obtained from
- /// http://colormine.org/convert/rgb-to-lab
- [Fact]
- public void CieLabToColor()
- {
- // Dark moderate pink.
- CieLab cielab = new CieLab(36.5492f, 33.3173f, -12.0615f);
- Rgba32 color = cielab;
-
- Assert.Equal(color.R, 128);
- Assert.Equal(color.G, 64);
- Assert.Equal(color.B, 106);
-
- // Ochre
- CieLab cielab2 = new CieLab(58.1758f, 27.3399f, 56.8240f);
- Rgba32 color2 = cielab2;
-
- Assert.Equal(color2.R, 204);
- Assert.Equal(color2.G, 119);
- Assert.Equal(color2.B, 34);
-
- // Black
- CieLab cielab3 = new CieLab(0, 0, 0);
- Rgba32 color3 = cielab3;
-
- Assert.Equal(color3.R, 0);
- Assert.Equal(color3.G, 0);
- Assert.Equal(color3.B, 0);
-
- // Check others.
- //Random random = new Random(0);
- //for (int i = 0; i < 1000; i++)
- //{
- // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());
- // CieLab cielab4 = color4;
- // Assert.Equal(color4, (Color)cielab4);
- //}
- }
- }
-}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs b/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs
index f2113852f..7e5af9297 100644
--- a/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs
+++ b/tests/ImageSharp.Tests/Colors/ColorDefinitionTests.cs
@@ -5,16 +5,14 @@
namespace ImageSharp.Tests
{
- using System;
using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
- using ImageSharp.Colors.Spaces;
using ImageSharp.PixelFormats;
using Xunit;
+
public class ColorDefinitionTests
{
public static IEnumerable ColorNames => typeof(NamedColors).GetTypeInfo().GetFields().Select(x => new[] { x.Name });
@@ -37,4 +35,4 @@ namespace ImageSharp.Tests
Assert.Equal(expected, actual);
}
}
-}
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs b/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs
index efec4ea38..2967e7c86 100644
--- a/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs
+++ b/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs
@@ -7,7 +7,7 @@ namespace ImageSharp.Tests.Colors
{
using System;
using System.Numerics;
- using ImageSharp.Colors.Spaces;
+
using ImageSharp.PixelFormats;
using Xunit;
@@ -41,38 +41,6 @@ namespace ImageSharp.Tests.Colors
{ new Short4(Vector4.One * 0x7FFF), new Short4(Vector4.One * 0x7FFF), typeof(Short4) },
};
- public static readonly TheoryData