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 EqualityDataColorSpaces = - new TheoryData() - { - { new Bgra32(0, 0, 0), new Bgra32(0, 0, 0), typeof(Bgra32) }, - { new Bgra32(0, 0, 0, 0), new Bgra32(0, 0, 0, 0), typeof(Bgra32) }, - { new Bgra32(100, 100, 0, 0), new Bgra32(100, 100, 0, 0), typeof(Bgra32) }, - { new Bgra32(255, 255, 255), new Bgra32(255, 255, 255), typeof(Bgra32) }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab) }, - { new CieLab(1f, 1f, 1f), new CieLab(1f, 1f, 1f), typeof(CieLab) }, - { new CieLab(0f, -100f, -100f), new CieLab(0f, -100f, -100f), typeof(CieLab) }, - { new CieLab(0f, 100f, -100f), new CieLab(0f, 100f, -100f), typeof(CieLab) }, - { new CieLab(0f, -100f, 100f), new CieLab(0f, -100f, 100f), typeof(CieLab) }, - { new CieLab(0f, -100f, 50f), new CieLab(0f, -100f, 50f), typeof(CieLab) }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380f), typeof(CieXyz) }, - { new CieXyz(780f, 780f, 780f), new CieXyz(780f, 780f, 780f), typeof(CieXyz) }, - { new CieXyz(380f, 780f, 780f), new CieXyz(380f, 780f, 780f), typeof(CieXyz) }, - { new CieXyz(50f, 20f, 60f), new CieXyz(50f, 20f, 60f), typeof(CieXyz) }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0f), typeof(Cmyk) }, - { new Cmyk(1f, 1f, 1f, 1f), new Cmyk(1f, 1f, 1f, 1f), typeof(Cmyk) }, - { new Cmyk(10f, 10f, 10f, 10f), new Cmyk(10f, 10f, 10f, 10f), typeof(Cmyk) }, - { new Cmyk(.4f, .5f, .1f, .2f), new Cmyk(.4f, .5f, .1f, .2f), typeof(Cmyk) }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0f), typeof(Hsl) }, - { new Hsl(360f, 1f, 1f), new Hsl(360f, 1f, 1f), typeof(Hsl) }, - { new Hsl(100f, .5f, .1f), new Hsl(100f, .5f, .1f), typeof(Hsl) }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0f), typeof(Hsv) }, - { new Hsv(360f, 1f, 1f), new Hsv(360f, 1f, 1f), typeof(Hsv) }, - { new Hsv(100f, .5f, .1f), new Hsv(100f, .5f, .1f), typeof(Hsv) }, - { new YCbCr(0, 0, 0), new YCbCr(0, 0, 0), typeof(YCbCr) }, - { new YCbCr(255, 255, 255), new YCbCr(255, 255, 255), typeof(YCbCr) }, - { new YCbCr(100, 100, 0), new YCbCr(100, 100, 0), typeof(YCbCr) }, - }; - public static readonly TheoryData NotEqualityDataNulls = new TheoryData() { @@ -97,18 +65,6 @@ namespace ImageSharp.Tests.Colors { new Short4(Vector4.One * 0x7FFF), null, typeof(Short4) }, }; - public static readonly TheoryData NotEqualityDataNullsColorSpaces = - new TheoryData() - { - { new Bgra32(0, 0, 0), null, typeof(Bgra32) }, - { new CieLab(0f, 0f, 0f), null, typeof(CieLab) }, - { new CieXyz(380f, 380f, 380f), null, typeof(CieXyz) }, - { new Cmyk(0f, 0f, 0f, 0f), null, typeof(Cmyk) }, - { new Hsl(0f, 0f, 0f), null, typeof(Hsl) }, - { new Hsv(360f, 1f, 1f), null, typeof(Hsv) }, - { new YCbCr(0, 0, 0), null, typeof(YCbCr) }, - }; - public static readonly TheoryData NotEqualityDataDifferentObjects = new TheoryData() { @@ -118,16 +74,6 @@ namespace ImageSharp.Tests.Colors { new Rgba1010102(Vector4.One), new Bgra5551(Vector4.Zero), null }, }; - public static readonly TheoryData NotEqualityDataDifferentObjectsColorSpaces = - new TheoryData() - { - // Valid objects of different types but not equal - { new Bgra32(0, 0, 0), new CieLab(0f, 0f, 0f), null }, - { new CieXyz(380f, 380f, 380f), new Cmyk(0f, 0f, 0f, 0f), null }, - { new Hsl(0f, 0f, 0f), new Hsv(360f, 1f, 1f), null }, - { new YCbCr(0, 0, 0), new Hsv(360f, 1f, 1f), null }, - }; - public static readonly TheoryData NotEqualityData = new TheoryData() { @@ -153,92 +99,8 @@ namespace ImageSharp.Tests.Colors { new Short4(Vector4.One * 0x7FFF), new Short4(Vector4.Zero), typeof(Short4) }, }; - public static readonly TheoryData NotEqualityDataColorSpaces = - new TheoryData() - { - { new Bgra32(0, 0, 0), new Bgra32(0, 1, 0), typeof(Bgra32) }, - { new Bgra32(0, 0, 0, 0), new Bgra32(0, 1, 0, 0), typeof(Bgra32) }, - { new Bgra32(100, 100, 0, 0), new Bgra32(100, 0, 0, 0), typeof(Bgra32) }, - { new Bgra32(255, 255, 255), new Bgra32(255, 0, 255), typeof(Bgra32) }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 1f, 0f), typeof(CieLab) }, - { new CieLab(1f, 1f, 1f), new CieLab(1f, 0f, 1f), typeof(CieLab) }, - { new CieLab(0f, -100f, -100f), new CieLab(0f, 100f, -100f), typeof(CieLab) }, - { new CieLab(0f, 100f, -100f), new CieLab(0f, -100f, -100f), typeof(CieLab) }, - { new CieLab(0f, -100f, 100f), new CieLab(0f, 100f, 100f), typeof(CieLab) }, - { new CieLab(0f, -100f, 50f), new CieLab(0f, 100f, 20f), typeof(CieLab) }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 0f, 380f), typeof(CieXyz) }, - { new CieXyz(780f, 780f, 780f), new CieXyz(780f, 0f, 780f), typeof(CieXyz) }, - { new CieXyz(380f, 780f, 780f), new CieXyz(380f, 0f, 780f), typeof(CieXyz) }, - { new CieXyz(50f, 20f, 60f), new CieXyz(50f, 0f, 60f), typeof(CieXyz) }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 1f, 0f, 0f), typeof(Cmyk) }, - { new Cmyk(1f, 1f, 1f, 1f), new Cmyk(1f, 1f, 0f, 1f), typeof(Cmyk) }, - { new Cmyk(10f, 10f, 10f, 10f), new Cmyk(10f, 10f, 0f, 10f), typeof(Cmyk) }, - { new Cmyk(.4f, .5f, .1f, .2f), new Cmyk(.4f, .5f, 5f, .2f), typeof(Cmyk) }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 5f, 0f), typeof(Hsl) }, - { new Hsl(360f, 1f, 1f), new Hsl(360f, .5f, 1f), typeof(Hsl) }, - { new Hsl(100f, .5f, .1f), new Hsl(100f, 9f, .1f), typeof(Hsl) }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 1f, 0f), typeof(Hsv) }, - { new Hsv(360f, 1f, 1f), new Hsv(0f, 1f, 1f), typeof(Hsv) }, - { new Hsv(100f, .5f, .1f), new Hsv(2f, .5f, .1f), typeof(Hsv) }, - { new YCbCr(0, 0, 0), new YCbCr(0, 1, 0), typeof(YCbCr) }, - { new YCbCr(255, 255, 255), new YCbCr(255, 0, 255), typeof(YCbCr) }, - { new YCbCr(100, 100, 0), new YCbCr(100, 20, 0), typeof(YCbCr) }, - }; - - public static readonly TheoryData AlmostEqualsData = - new TheoryData() - { - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab), 0f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab), .001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab), .0001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0f), typeof(CieLab), .0005f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, .001f, 0f), typeof(CieLab), .001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, .0001f), typeof(CieLab), .0001f }, - { new CieLab(0f, 0f, 0f), new CieLab(.0005f, 0f, 0f), typeof(CieLab), .0005f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380f), typeof(CieXyz), 0f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380.001f, 380f, 380f), typeof(CieXyz), .01f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380.001f, 380f), typeof(CieXyz), .01f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380.001f), typeof(CieXyz), .01f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0f), typeof(Cmyk), 0f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0.001f, 0f, 0f, 0f), typeof(Cmyk), .01f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0.001f, 0f, 0f), typeof(Cmyk), .01f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0.001f, 0f), typeof(Cmyk), .01f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0.001f), typeof(Cmyk), .01f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0f), typeof(Hsl), 0f }, - { new Hsl(0f, 0f, 0f), new Hsl(0.001f, 0f, 0f), typeof(Hsl), .01f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0.001f, 0f), typeof(Hsl), .01f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0.001f), typeof(Hsl), .01f }, - { new Hsv(360f, 1f, 1f), new Hsv(360f, 1f, 1f), typeof(Hsv), 0f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0f), typeof(Hsv), 0f }, - { new Hsv(0f, 0f, 0f), new Hsv(0.001f, 0f, 0f), typeof(Hsv), .01f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0.001f, 0f), typeof(Hsv), .01f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0.001f), typeof(Hsv), .01f }, - }; - - public static readonly TheoryData AlmostNotEqualsData = - new TheoryData() - { - { new CieLab(0f, 0f, 0f), new CieLab(0.1f, 0f, 0f), typeof(CieLab), .001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0.1f, 0f), typeof(CieLab), .001f }, - { new CieLab(0f, 0f, 0f), new CieLab(0f, 0f, 0.1f), typeof(CieLab), .001f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380.1f, 380f, 380f), typeof(CieXyz), .001f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380.1f, 380f), typeof(CieXyz), .001f }, - { new CieXyz(380f, 380f, 380f), new CieXyz(380f, 380f, 380.1f), typeof(CieXyz), .001f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0.1f, 0f, 0f, 0f), typeof(Cmyk), .001f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0.1f, 0f, 0f), typeof(Cmyk), .001f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0.1f, 0f), typeof(Cmyk), .001f }, - { new Cmyk(0f, 0f, 0f, 0f), new Cmyk(0f, 0f, 0f, 0.1f), typeof(Cmyk), .001f }, - { new Hsl(0f, 0f, 0f), new Hsl(0.1f, 0f, 0f), typeof(Hsl), .001f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0.1f, 0f), typeof(Hsl), .001f }, - { new Hsl(0f, 0f, 0f), new Hsl(0f, 0f, 0.1f), typeof(Hsl), .001f }, - { new Hsv(0f, 0f, 0f), new Hsv(0.1f, 0f, 0f), typeof(Hsv), .001f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0.1f, 0f), typeof(Hsv), .001f }, - { new Hsv(0f, 0f, 0f), new Hsv(0f, 0f, 0.1f), typeof(Hsv), .001f }, - }; - [Theory] [MemberData(nameof(EqualityData))] - [MemberData(nameof(EqualityDataColorSpaces))] public void Equality(object first, object second, Type type) { // Act @@ -250,11 +112,8 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityDataNulls))] - [MemberData(nameof(NotEqualityDataNullsColorSpaces))] [MemberData(nameof(NotEqualityDataDifferentObjects))] - [MemberData(nameof(NotEqualityDataDifferentObjectsColorSpaces))] [MemberData(nameof(NotEqualityData))] - [MemberData(nameof(NotEqualityDataColorSpaces))] public void NotEquality(object first, object second, Type type) { // Act @@ -266,7 +125,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(EqualityData))] - [MemberData(nameof(EqualityDataColorSpaces))] public void HashCodeEqual(object first, object second, Type type) { // Act @@ -278,7 +136,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityDataDifferentObjects))] - [MemberData(nameof(NotEqualityDataDifferentObjectsColorSpaces))] public void HashCodeNotEqual(object first, object second, Type type) { // Act @@ -290,7 +147,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(EqualityData))] - [MemberData(nameof(EqualityDataColorSpaces))] public void EqualityObject(object first, object second, Type type) { // Arrange @@ -309,7 +165,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityData))] - [MemberData(nameof(NotEqualityDataColorSpaces))] public void NotEqualityObject(object first, object second, Type type) { // Arrange @@ -328,7 +183,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(EqualityData))] - [MemberData(nameof(EqualityDataColorSpaces))] public void EqualityOperator(object first, object second, Type type) { // Arrange @@ -347,7 +201,6 @@ namespace ImageSharp.Tests.Colors [Theory] [MemberData(nameof(NotEqualityData))] - [MemberData(nameof(NotEqualityDataColorSpaces))] public void NotEqualityOperator(object first, object second, Type type) { // Arrange @@ -363,41 +216,5 @@ namespace ImageSharp.Tests.Colors // Assert Assert.True(notEqual); } - - [Theory] - [MemberData(nameof(AlmostEqualsData))] - public void AlmostEquals(object first, object second, Type type, float precision) - { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) - dynamic firstObject = Convert.ChangeType(first, type); - dynamic secondObject = Convert.ChangeType(second, type); - - // Act - dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); - - // Assert - Assert.True(almostEqual); - } - - [Theory] - [MemberData(nameof(AlmostNotEqualsData))] - public void AlmostNotEquals(object first, object second, Type type, float precision) - { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) - dynamic firstObject = Convert.ChangeType(first, type); - dynamic secondObject = Convert.ChangeType(second, type); - - // Act - dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); - - // Assert - Assert.False(almostEqual); - } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLabAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLabAndCieLchConversionTests.cs new file mode 100644 index 000000000..f2d8c92d2 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieLabAndCieLchConversionTests.cs @@ -0,0 +1,76 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieLabAndCieLchConversionTests + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, 50, 180, 100, -50, 0)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] + [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] + [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] + public void Convert_Lch_to_Lab(float l, float c, float h, float l2, float a, float b) + { + // Arrange + CieLch input = new CieLch(l, c, h); + + // Act + CieLab output = Converter.ToCieLab(input); + + // Assert + Assert.Equal(l2, output.L, FloatRoundingComparer); + Assert.Equal(a, output.A, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, -50, 0, 100, 50, 180)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] + [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] + [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] + public void Convert_Lab_to_LCHab(float l, float a, float b, float l2, float c, float h) + { + // Arrange + CieLab input = new CieLab(l, a, b); + + // Act + CieLch output = Converter.ToCieLch(input); + + // Assert + Assert.Equal(l2, output.L, FloatRoundingComparer); + Assert.Equal(c, output.C, FloatRoundingComparer); + Assert.Equal(h, output.H, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLuvAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLuvAndCieLchuvConversionTests.cs new file mode 100644 index 000000000..d263bbe18 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieLuvAndCieLchuvConversionTests.cs @@ -0,0 +1,77 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieLuvAndCieLchuvuvConversionTests + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, 50, 180, 100, -50, 0)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] + [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] + [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] + public void Convert_Lchuv_to_Luv(float l, float c, float h, float l2, float u, float v) + { + // Arrange + CieLchuv input = new CieLchuv(l, c, h); + + // Act + CieLuv output = Converter.ToCieLuv(input); + + // Assert + Assert.Equal(l2, output.L, FloatRoundingComparer); + Assert.Equal(u, output.U, FloatRoundingComparer); + Assert.Equal(v, output.V, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, -50, 0, 100, 50, 180)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] + [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] + [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] + [InlineData(37.3511, 24.1720, 16.0684, 37.3511, 29.0255, 33.6141)] + public void Convert_Luv_to_LCHuv(float l, float u, float v, float l2, float c, float h) + { + // Arrange + CieLuv input = new CieLuv(l, u, v); + + // Act + CieLchuv output = Converter.ToCieLchuv(input); + + // Assert + Assert.Equal(l2, output.L, FloatRoundingComparer); + Assert.Equal(c, output.C, FloatRoundingComparer); + Assert.Equal(h, output.H, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLabConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLabConversionTest.cs new file mode 100644 index 000000000..9bd8b17c7 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLabConversionTest.cs @@ -0,0 +1,72 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndCieLabConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 431.0345, 0, 0.95047, 0, 0)] + [InlineData(100, -431.0345, 172.4138, 0, 1, 0)] + [InlineData(0, 0, -172.4138, 0, 0, 1.08883)] + [InlineData(45.6398, 39.8753, 35.2091, 0.216938, 0.150041, 0.048850)] + [InlineData(77.1234, -40.1235, 78.1120, 0.358530, 0.517372, 0.076273)] + [InlineData(10, -400, 20, 0, 0.011260, 0)] + public void Convert_Lab_to_Xyz(float l, float a, float b, float x, float y, float z) + { + // Arrange + CieLab input = new CieLab(l, a, b, Illuminants.D65); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 0, 0, 0, 431.0345, 0)] + [InlineData(0, 1, 0, 100, -431.0345, 172.4138)] + [InlineData(0, 0, 1.08883, 0, 0, -172.4138)] + [InlineData(0.216938, 0.150041, 0.048850, 45.6398, 39.8753, 35.2091)] + public void Convert_Xyz_to_Lab(float x, float y, float z, float l, float a, float b) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + + // Act + CieLab output = converter.ToCieLab(input); + + // Assert + Assert.Equal(l, output.L, FloatRoundingComparer); + Assert.Equal(a, output.A, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLuvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLuvConversionTest.cs new file mode 100644 index 000000000..5589e9753 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLuvConversionTest.cs @@ -0,0 +1,71 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndCieLuvConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 100, 50, 0, 0, 0)] + [InlineData(0.1, 100, 50, 0.000493, 0.000111, 0)] + [InlineData(70.0000, 86.3525, 2.8240, 0.569310, 0.407494, 0.365843)] + [InlineData(10.0000, -1.2345, -10.0000, 0.012191, 0.011260, 0.025939)] + [InlineData(100, 0, 0, 0.950470, 1.000000, 1.088830)] + [InlineData(1, 1, 1, 0.001255, 0.001107, 0.000137)] + public void Convert_Luv_to_Xyz(float l, float u, float v, float x, float y, float z) + { + // Arrange + CieLuv input = new CieLuv(l, u, v, Illuminants.D65); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.000493, 0.000111, 0, 0.1003, 0.9332, -0.0070)] + [InlineData(0.569310, 0.407494, 0.365843, 70.0000, 86.3524, 2.8240)] + [InlineData(0.012191, 0.011260, 0.025939, 9.9998, -1.2343, -9.9999)] + [InlineData(0.950470, 1.000000, 1.088830, 100, 0, 0)] + [InlineData(0.001255, 0.001107, 0.000137, 0.9999, 0.9998, 1.0004)] + public void Convert_Xyz_to_Luv(float x, float y, float z, float l, float u, float v) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + + // Act + CieLuv output = converter.ToCieLuv(input); + + // Assert + Assert.Equal(l, output.L, FloatRoundingComparer); + Assert.Equal(u, output.U, FloatRoundingComparer); + Assert.Equal(v, output.V, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieXyyConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieXyyConversionTest.cs new file mode 100644 index 000000000..9b441f452 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieXyyConversionTest.cs @@ -0,0 +1,61 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndCieXyyConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + [Theory] + [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] + [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] + [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] + [InlineData(0, 0, 0, 0.538842, 0.000000, 0.000000)] + public void Convert_xyY_to_XYZ(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) + { + // Arrange + CieXyy input = new CieXyy(x, y, yl); + + // Act + CieXyz output = Converter.ToCieXyz(input); + + // Assert + Assert.Equal(xyzX, output.X, FloatRoundingComparer); + Assert.Equal(xyzY, output.Y, FloatRoundingComparer); + Assert.Equal(xyzZ, output.Z, FloatRoundingComparer); + } + + [Theory] + [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] + [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] + [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] + [InlineData(0.231809, 0, 0.077528, 0.749374, 0.000000, 0.000000)] + public void Convert_XYZ_to_xyY(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) + { + // Arrange + CieXyz input = new CieXyz(xyzX, xyzY, xyzZ); + + // Act + CieXyy output = Converter.ToCieXyy(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(yl, output.Yl, FloatRoundingComparer); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndHunterLabConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndHunterLabConversionTest.cs new file mode 100644 index 000000000..5ad224eaf --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndHunterLabConversionTest.cs @@ -0,0 +1,83 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndHunterLabConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(100, 0, 0, 0.98074, 1, 1.18232)] // C white point is HunterLab 100, 0, 0 + public void Convert_HunterLab_to_Xyz(float l, float a, float b, float x, float y, float z) + { + // Arrange + HunterLab input = new HunterLab(l, a, b); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.C }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] // D65 white point is HunerLab 100, 0, 0 (adaptation to C performed) + public void Convert_HunterLab_to_Xyz_D65(float l, float a, float b, float x, float y, float z) + { + // Arrange + HunterLab input = new HunterLab(l, a, b); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65 }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] // D65 white point is HunterLab 100, 0, 0 (adaptation to C performed) + public void Convert_Xyz_D65_to_HunterLab(float x, float y, float z, float l, float a, float b) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65 }; + + // Act + HunterLab output = converter.ToHunterLab(input); + + // Assert + Assert.Equal(l, output.L, FloatRoundingComparer); + Assert.Equal(a, output.A, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndLmsConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndLmsConversionTest.cs new file mode 100644 index 000000000..868d42975 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndLmsConversionTest.cs @@ -0,0 +1,69 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using original colorful library. + /// + public class CieXyzAndLmsConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(5); + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.941428535, 1.040417467, 1.089532651, 0.95047, 1, 1.08883)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.850765697, -0.713042594, 0.036973283, 0.95047, 0, 0)] + [InlineData(0.2664, 1.7135, -0.0685, 0, 1, 0)] + [InlineData(-0.175737162, 0.039960061, 1.121059368, 0, 0, 1.08883)] + [InlineData(0.2262677362, 0.0961411609, 0.0484570397, 0.216938, 0.150041, 0.048850)] + public void Convert_Lms_to_CieXyz(float l, float m, float s, float x, float y, float z) + { + // Arrange + Lms input = new Lms(l, m, s); + ColorSpaceConverter converter = new ColorSpaceConverter(); + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.95047, 1, 1.08883, 0.941428535, 1.040417467, 1.089532651)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 0, 0, 0.850765697, -0.713042594, 0.036973283)] + [InlineData(0, 1, 0, 0.2664, 1.7135, -0.0685)] + [InlineData(0, 0, 1.08883, -0.175737162, 0.039960061, 1.121059368)] + [InlineData(0.216938, 0.150041, 0.048850, 0.2262677362, 0.0961411609, 0.0484570397)] + public void Convert_CieXyz_to_Lms(float x, float y, float z, float l, float m, float s) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorSpaceConverter converter = new ColorSpaceConverter(); + + // Act + Lms output = converter.ToLms(input); + + // Assert + Assert.Equal(l, output.L, FloatRoundingComparer); + Assert.Equal(m, output.M, FloatRoundingComparer); + Assert.Equal(s, output.S, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs new file mode 100644 index 000000000..02095b31f --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs @@ -0,0 +1,150 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + using ImageSharp.ColorSpaces.Conversion.Implementation.Lms; + + using Xunit; + + /// + /// Tests methods. + /// Test data generated using: + /// + /// + /// + public class ColorConverterAdaptTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(3); + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 1, 1, 1)] + [InlineData(0.206162, 0.260277, 0.746717, 0.220000, 0.130000, 0.780000)] + public void Adapt_RGB_WideGamutRGB_To_sRGB(float r1, float g1, float b1, float r2, float g2, float b2) + { + // Arrange + Rgb input = new Rgb(r1, g1, b1, RgbWorkingSpaces.WideGamutRgb); + Rgb expectedOutput = new Rgb(r2, g2, b2, RgbWorkingSpaces.SRgb); + ColorSpaceConverter converter = new ColorSpaceConverter { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + + // Action + Rgb output = converter.Adapt(input); + + // Assert + Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace); + Assert.Equal(expectedOutput.R, output.R, FloatRoundingComparer); + Assert.Equal(expectedOutput.G, output.G, FloatRoundingComparer); + Assert.Equal(expectedOutput.B, output.B, FloatRoundingComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 1, 1, 1)] + [InlineData(0.220000, 0.130000, 0.780000, 0.206162, 0.260277, 0.746717)] + public void Adapt_RGB_SRGB_To_WideGamutRGB(float r1, float g1, float b1, float r2, float g2, float b2) + { + // Arrange + Rgb input = new Rgb(r1, g1, b1, RgbWorkingSpaces.SRgb); + Rgb expectedOutput = new Rgb(r2, g2, b2, RgbWorkingSpaces.WideGamutRgb); + ColorSpaceConverter converter = new ColorSpaceConverter { TargetRgbWorkingSpace = RgbWorkingSpaces.WideGamutRgb }; + + // Action + Rgb output = converter.Adapt(input); + + // Assert + Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace); + Assert.Equal(expectedOutput.R, output.R, FloatRoundingComparer); + Assert.Equal(expectedOutput.G, output.G, FloatRoundingComparer); + Assert.Equal(expectedOutput.B, output.B, FloatRoundingComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(22, 33, 1, 22.269869, 32.841164, 1.633926)] + public void Adapt_Lab_D50_To_D65(float l1, float a1, float b1, float l2, float a2, float b2) + { + // Arrange + CieLab input = new CieLab(l1, a1, b1, Illuminants.D65); + CieLab expectedOutput = new CieLab(l2, a2, b2); + ColorSpaceConverter converter = new ColorSpaceConverter { TargetLabWhitePoint = Illuminants.D50 }; + + // Action + CieLab output = converter.Adapt(input); + + // Assert + Assert.Equal(expectedOutput.L, output.L, FloatRoundingComparer); + Assert.Equal(expectedOutput.A, output.A, FloatRoundingComparer); + Assert.Equal(expectedOutput.B, output.B, FloatRoundingComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.510286, 0.501489, 0.378970)] + public void Adapt_Xyz_D65_To_D50_Bradford(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + CieXyz input = new CieXyz(x1, y1, z1); + CieXyz expectedOutput = new CieXyz(x2, y2, z2); + ColorSpaceConverter converter = new ColorSpaceConverter + { + WhitePoint = Illuminants.D50 + }; + + // Action + CieXyz output = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(expectedOutput.X, output.X, FloatRoundingComparer); + Assert.Equal(expectedOutput.Y, output.Y, FloatRoundingComparer); + Assert.Equal(expectedOutput.Z, output.Z, FloatRoundingComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] + public void Adapt_CieXyz_D65_To_D50_XyzScaling(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + CieXyz input = new CieXyz(x1, y1, z1); + CieXyz expectedOutput = new CieXyz(x2, y2, z2); + ColorSpaceConverter converter = new ColorSpaceConverter + { + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), + WhitePoint = Illuminants.D50 + }; + + // Action + CieXyz output = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(expectedOutput.X, output.X, FloatRoundingComparer); + Assert.Equal(expectedOutput.Y, output.Y, FloatRoundingComparer); + Assert.Equal(expectedOutput.Z, output.Z, FloatRoundingComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] + public void Adapt_Xyz_D65_To_D50_XyzScaling(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + CieXyz input = new CieXyz(x1, y1, z1); + CieXyz expectedOutput = new CieXyz(x2, y2, z2); + ColorSpaceConverter converter = new ColorSpaceConverter + { + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), + WhitePoint = Illuminants.D50 + }; + + // Action + CieXyz output = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(expectedOutput.X, output.X, FloatRoundingComparer); + Assert.Equal(expectedOutput.Y, output.Y, FloatRoundingComparer); + Assert.Equal(expectedOutput.Z, output.Z, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/ColorSpaceEqualityTests.cs b/tests/ImageSharp.Tests/Colorspaces/ColorSpaceEqualityTests.cs new file mode 100644 index 000000000..2a5462b40 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/ColorSpaceEqualityTests.cs @@ -0,0 +1,310 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Colorspaces +{ + using System; + using System.Numerics; + using Xunit; + + using ImageSharp.ColorSpaces; + + /// + /// Test implementations of IEquatable and IAlmostEquatable in our colorspaces + /// + public class ColorSpaceEqualityTests + { + public static readonly TheoryData EmptyData = + new TheoryData + { + CieLab.Empty, + CieLch.Empty, + CieLchuv.Empty, + CieLuv.Empty, + CieXyz.Empty, + CieXyy.Empty, + Hsl.Empty, + Hsl.Empty, + HunterLab.Empty, + Lms.Empty, + LinearRgb.Empty, + Rgb.Empty, + YCbCr.Empty + }; + + public static readonly TheoryData EqualityData = + new TheoryData + { + { new CieLab(Vector3.One), new CieLab(Vector3.One), typeof(CieLab) }, + { new CieLch(Vector3.One), new CieLch(Vector3.One), typeof(CieLch) }, + { new CieLchuv(Vector3.One), new CieLchuv(Vector3.One), typeof(CieLchuv) }, + { new CieLuv(Vector3.One), new CieLuv(Vector3.One), typeof(CieLuv) }, + { new CieXyz(Vector3.One), new CieXyz(Vector3.One), typeof(CieXyz) }, + { new CieXyy(Vector3.One), new CieXyy(Vector3.One), typeof(CieXyy) }, + { new HunterLab(Vector3.One), new HunterLab(Vector3.One), typeof(HunterLab) }, + { new Lms(Vector3.One), new Lms(Vector3.One), typeof(Lms) }, + { new LinearRgb(Vector3.One), new LinearRgb(Vector3.One), typeof(LinearRgb) }, + { new Rgb(Vector3.One), new Rgb(Vector3.One), typeof(Rgb) }, + { new Hsl(Vector3.One), new Hsl(Vector3.One), typeof(Hsl) }, + { new Hsv(Vector3.One), new Hsv(Vector3.One), typeof(Hsv) }, + { new YCbCr(Vector3.One), new YCbCr(Vector3.One), typeof(YCbCr) }, + }; + + public static readonly TheoryData NotEqualityDataNulls = + new TheoryData + { + // Valid object against null + { new CieLab(Vector3.One), null, typeof(CieLab) }, + { new CieLch(Vector3.One), null, typeof(CieLch) }, + { new CieLchuv(Vector3.One), null, typeof(CieLchuv) }, + { new CieLuv(Vector3.One), null, typeof(CieLuv) }, + { new CieXyz(Vector3.One), null, typeof(CieXyz) }, + { new CieXyy(Vector3.One), null, typeof(CieXyy) }, + { new HunterLab(Vector3.One), null, typeof(HunterLab) }, + { new Lms(Vector3.One), null, typeof(Lms) }, + { new LinearRgb(Vector3.One), null, typeof(LinearRgb) }, + { new Rgb(Vector3.One), null, typeof(Rgb) }, + { new Hsl(Vector3.One), null, typeof(Hsl) }, + { new Hsv(Vector3.One), null, typeof(Hsv) }, + { new YCbCr(Vector3.One), null, typeof(YCbCr) }, + }; + + public static readonly TheoryData NotEqualityDataDifferentObjects = + new TheoryData + { + // Valid objects of different types but not equal + { new CieLab(Vector3.One), new CieLch(Vector3.Zero), null }, + { new CieLuv(Vector3.One), new CieLchuv(Vector3.Zero), null }, + { new CieXyz(Vector3.One), new HunterLab(Vector3.Zero), null }, + { new Rgb(Vector3.One), new LinearRgb(Vector3.Zero), null }, + { new Rgb(Vector3.One), new Lms(Vector3.Zero), null }, + { new Cmyk(Vector4.One), new Hsl(Vector3.Zero), null }, + { new YCbCr(Vector3.One), new CieXyy(Vector3.Zero), null }, + { new Hsv(Vector3.One), new Hsl(Vector3.Zero), null }, + }; + + public static readonly TheoryData NotEqualityData = + new TheoryData + { + // Valid objects of the same type but not equal + { new CieLab(Vector3.One), new CieLab(Vector3.Zero), typeof(CieLab) }, + { new CieLch(Vector3.One), new CieLch(Vector3.Zero), typeof(CieLch) }, + { new CieLchuv(Vector3.One), new CieLchuv(Vector3.Zero), typeof(CieLchuv) }, + { new CieLuv(Vector3.One), new CieLuv(Vector3.Zero), typeof(CieLuv) }, + { new CieXyz(Vector3.One), new CieXyz(Vector3.Zero), typeof(CieXyz) }, + { new CieXyy(Vector3.One), new CieXyy(Vector3.Zero), typeof(CieXyy) }, + { new HunterLab(Vector3.One), new HunterLab(Vector3.Zero), typeof(HunterLab) }, + { new Lms(Vector3.One), new Lms(Vector3.Zero), typeof(Lms) }, + { new LinearRgb(Vector3.One), new LinearRgb(Vector3.Zero), typeof(LinearRgb) }, + { new Rgb(Vector3.One), new Rgb(Vector3.Zero), typeof(Rgb) }, + { new Cmyk(Vector4.One), new Cmyk(Vector4.Zero), typeof(Cmyk) }, + { new Hsl(Vector3.One), new Hsl(Vector3.Zero), typeof(Hsl) }, + { new Hsv(Vector3.One), new Hsv(Vector3.Zero), typeof(Hsv) }, + { new YCbCr(Vector3.One), new YCbCr(Vector3.Zero), typeof(YCbCr) }, + }; + + public static readonly TheoryData AlmostEqualsData = + new TheoryData + { + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), 0F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), .001F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), .0001F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), .0005F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, .001F, 0F), typeof(CieLab), .001F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, .0001F), typeof(CieLab), .0001F }, + { new CieLab(0F, 0F, 0F), new CieLab(.0005F, 0F, 0F), typeof(CieLab), .0005F }, + { new CieLch(0F, 0F, 0F), new CieLch(0F, .001F, 0F), typeof(CieLch), .001F }, + { new CieLchuv(0F, 0F, 0F), new CieLchuv(0F, .001F, 0F), typeof(CieLchuv), .001F }, + { new CieLuv(0F, 0F, 0F), new CieLuv(0F, .001F, 0F), typeof(CieLuv), .001F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380F, 380F), typeof(CieXyz), 0F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380.001F, 380F, 380F), typeof(CieXyz), .01F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380.001F, 380F), typeof(CieXyz), .01F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380F, 380.001F), typeof(CieXyz), .01F }, + { new Cmyk(1, 1, 1, 1), new Cmyk(1, 1, 1, .99F), typeof(Cmyk), .01F }, + { new YCbCr(255F, 128F, 128F), new YCbCr(255F, 128F, 128.001F), typeof(YCbCr), .01F }, + { new Hsv(0F, 0F, 0F), new Hsv(0F, 0F, 0F), typeof(Hsv), 0F }, + { new Hsl(0F, 0F, 0F), new Hsl(0F, 0F, 0F), typeof(Hsl), 0F }, + }; + + public static readonly TheoryData AlmostNotEqualsData = + new TheoryData + { + { new CieLab(0F, 0F, 0F), new CieLab(0.1F, 0F, 0F), typeof(CieLab), .001F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0.1F, 0F), typeof(CieLab), .001F }, + { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0.1F), typeof(CieLab), .001F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380.1F, 380F, 380F), typeof(CieXyz), .001F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380.1F, 380F), typeof(CieXyz), .001F }, + { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380F, 380.1F), typeof(CieXyz), .001F }, + }; + + [Theory] + [MemberData(nameof(EmptyData))] + public void Equality(IColorVector color) + { + // Act + bool equal = color.Vector.Equals(Vector3.Zero); + + // Assert + Assert.True(equal); + } + + [Theory] + [MemberData(nameof(EqualityData))] + public void Equality(object first, object second, Type type) + { + // Act + bool equal = first.Equals(second); + + // Assert + Assert.True(equal); + } + + [Theory] + [MemberData(nameof(NotEqualityDataNulls))] + [MemberData(nameof(NotEqualityDataDifferentObjects))] + [MemberData(nameof(NotEqualityData))] + public void NotEquality(object first, object second, Type type) + { + // Act + bool equal = first.Equals(second); + + // Assert + Assert.False(equal); + } + + [Theory] + [MemberData(nameof(EqualityData))] + public void HashCodeEqual(object first, object second, Type type) + { + // Act + bool equal = first.GetHashCode() == second.GetHashCode(); + + // Assert + Assert.True(equal); + } + + [Theory] + [MemberData(nameof(NotEqualityDataDifferentObjects))] + public void HashCodeNotEqual(object first, object second, Type type) + { + // Act + bool equal = first.GetHashCode() == second.GetHashCode(); + + // Assert + Assert.False(equal); + } + + [Theory] + [MemberData(nameof(EqualityData))] + public void EqualityObject(object first, object second, Type type) + { + // Arrange + // Cast to the known object types, this is so that we can hit the + // equality operator on the concrete type, otherwise it goes to the + // default "object" one :) + dynamic firstObject = Convert.ChangeType(first, type); + dynamic secondObject = Convert.ChangeType(second, type); + + // Act + dynamic equal = firstObject.Equals(secondObject); + + // Assert + Assert.True(equal); + } + + [Theory] + [MemberData(nameof(NotEqualityData))] + public void NotEqualityObject(object first, object second, Type type) + { + // Arrange + // Cast to the known object types, this is so that we can hit the + // equality operator on the concrete type, otherwise it goes to the + // default "object" one :) + dynamic firstObject = Convert.ChangeType(first, type); + dynamic secondObject = Convert.ChangeType(second, type); + + // Act + dynamic equal = firstObject.Equals(secondObject); + + // Assert + Assert.False(equal); + } + + // TODO:Disabled due to RuntypeBinder errors while structs are internal + //[Theory] + //[MemberData(nameof(EqualityData))] + //public void EqualityOperator(object first, object second, Type type) + //{ + // // Arrange + // // Cast to the known object types, this is so that we can hit the + // // equality operator on the concrete type, otherwise it goes to the + // // default "object" one :) + // dynamic firstObject = Convert.ChangeType(first, type); + // dynamic secondObject = Convert.ChangeType(second, type); + + // // Act + // dynamic equal = firstObject == secondObject; + + // // Assert + // Assert.True(equal); + //} + + [Theory] + [MemberData(nameof(NotEqualityData))] + public void NotEqualityOperator(object first, object second, Type type) + { + // Arrange + // Cast to the known object types, this is so that we can hit the + // equality operator on the concrete type, otherwise it goes to the + // default "object" one :) + dynamic firstObject = Convert.ChangeType(first, type); + dynamic secondObject = Convert.ChangeType(second, type); + + // Act + dynamic notEqual = firstObject != secondObject; + + // Assert + Assert.True(notEqual); + } + + // TODO:Disabled due to RuntypeBinder errors while structs are internal + //[Theory] + //[MemberData(nameof(AlmostEqualsData))] + //public void AlmostEquals(object first, object second, Type type, float precision) + //{ + // // Arrange + // // Cast to the known object types, this is so that we can hit the + // // equality operator on the concrete type, otherwise it goes to the + // // default "object" one :) + // dynamic firstObject = Convert.ChangeType(first, type); + // dynamic secondObject = Convert.ChangeType(second, type); + + // // Act + // dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); + + // // Assert + // Assert.True(almostEqual); + //} + + // TODO:Disabled due to RuntypeBinder errors while structs are internal + //[Theory] + //[MemberData(nameof(AlmostNotEqualsData))] + //public void AlmostNotEquals(object first, object second, Type type, float precision) + //{ + // // Arrange + // // Cast to the known object types, this is so that we can hit the + // // equality operator on the concrete type, otherwise it goes to the + // // default "object" one :) + // dynamic firstObject = Convert.ChangeType(first, type); + // dynamic secondObject = Convert.ChangeType(second, type); + + // // Act + // dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); + + // // Assert + // Assert.False(almostEqual); + //} + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs new file mode 100644 index 000000000..7a4520a57 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs @@ -0,0 +1,127 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class RgbAndCieXyzConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(6); + + /// + /// Tests conversion from () + /// to (default sRGB working space). + /// + [Theory] + [InlineData(0.96422, 1.00000, 0.82521, 1, 1, 1)] + [InlineData(0.00000, 1.00000, 0.00000, 0, 1, 0)] + [InlineData(0.96422, 0.00000, 0.00000, 1, 0, 0.292064)] + [InlineData(0.00000, 0.00000, 0.82521, 0, 0.181415, 1)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.297676, 0.267854, 0.045504, 0.720315, 0.509999, 0.168112)] + public void Convert_XYZ_D50_to_SRGB(float x, float y, float z, float r, float g, float b) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D50, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + + // Act + Rgb output = converter.ToRgb(input); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(r, output.R, FloatRoundingComparer); + Assert.Equal(g, output.G, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion + /// from () + /// to (default sRGB working space). + /// + [Theory] + [InlineData(0.950470, 1.000000, 1.088830, 1, 1, 1)] + [InlineData(0, 1.000000, 0, 0, 1, 0)] + [InlineData(0.950470, 0, 0, 1, 0, 0.254967)] + [InlineData(0, 0, 1.088830, 0, 0.235458, 1)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.297676, 0.267854, 0.045504, 0.754903, 0.501961, 0.099998)] + public void Convert_XYZ_D65_to_SRGB(float x, float y, float z, float r, float g, float b) + { + // Arrange + CieXyz input = new CieXyz(x, y, z); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + + // Act + Rgb output = converter.ToRgb(input); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(r, output.R, FloatRoundingComparer); + Assert.Equal(g, output.G, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from (default sRGB working space) + /// to (). + /// + [Theory] + [InlineData(1, 1, 1, 0.964220, 1.000000, 0.825210)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 0, 0, 0.436075, 0.222504, 0.013932)] + [InlineData(0, 1, 0, 0.385065, 0.716879, 0.0971045)] + [InlineData(0, 0, 1, 0.143080, 0.060617, 0.714173)] + [InlineData(0.754902, 0.501961, 0.100000, 0.315757, 0.273323, 0.035506)] + public void Convert_SRGB_to_XYZ_D50(float r, float g, float b, float x, float y, float z) + { + // Arrange + Rgb input = new Rgb(r, g, b); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D50 }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + + /// + /// Tests conversion from (default sRGB working space) + /// to (). + /// + [Theory] + [InlineData(1, 1, 1, 0.950470, 1.000000, 1.088830)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 0, 0, 0.412456, 0.212673, 0.019334)] + [InlineData(0, 1, 0, 0.357576, 0.715152, 0.119192)] + [InlineData(0, 0, 1, 0.1804375, 0.072175, 0.950304)] + [InlineData(0.754902, 0.501961, 0.100000, 0.297676, 0.267854, 0.045504)] + public void Convert_SRGB_to_XYZ_D65(float r, float g, float b, float x, float y, float z) + { + // Arrange + Rgb input = new Rgb(r, g, b); + ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65 }; + + // Act + CieXyz output = converter.ToCieXyz(input); + + // Assert + Assert.Equal(x, output.X, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(z, output.Z, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs new file mode 100644 index 000000000..8fe64e915 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs @@ -0,0 +1,68 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + /// + public class RgbAndCmykConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(1, 1, 1, 1, 0, 0, 0)] + [InlineData(0, 0, 0, 0, 1, 1, 1)] + [InlineData(0, 0.84, 0.037, 0.365, 0.635, 0.1016, 0.6115)] + public void Convert_Cmyk_To_Rgb(float c, float m, float y, float k, float r, float g, float b) + { + // Arrange + Cmyk input = new Cmyk(c, m, y, k); + + // Act + Rgb output = Converter.ToRgb(input); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(r, output.R, FloatRoundingComparer); + Assert.Equal(g, output.G, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(1, 1, 1, 0, 0, 0, 0)] + [InlineData(0, 0, 0, 0, 0, 0, 1)] + [InlineData(0.635, 0.1016, 0.6115, 0, 0.84, 0.037, 0.365)] + public void Convert_Rgb_To_Cmyk(float r, float g, float b, float c, float m, float y, float k) + { + // Arrange + Rgb input = new Rgb(r, g, b); + + // Act + Cmyk output = Converter.ToCmyk(input); + + // Assert + Assert.Equal(c, output.C, FloatRoundingComparer); + Assert.Equal(m, output.M, FloatRoundingComparer); + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(k, output.K, FloatRoundingComparer); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs new file mode 100644 index 000000000..fa02f887f --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs @@ -0,0 +1,72 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + /// + public class RgbAndHslConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 1, 1, 1, 1, 1)] + [InlineData(360, 1, 1, 1, 1, 1)] + [InlineData(0, 1, .5F, 1, 0, 0)] + [InlineData(120, 1, .5F, 0, 1, 0)] + [InlineData(240, 1, .5F, 0, 0, 1)] + public void Convert_Hsl_To_Rgb(float h, float s, float l, float r, float g, float b) + { + // Arrange + Hsl input = new Hsl(h, s, l); + + // Act + Rgb output = Converter.ToRgb(input); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(r, output.R, FloatRoundingComparer); + Assert.Equal(g, output.G, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 0, 0, 1)] + [InlineData(1, 0, 0, 0, 1, .5F)] + [InlineData(0, 1, 0, 120, 1, .5F)] + [InlineData(0, 0, 1, 240, 1, .5F)] + public void Convert_Rgb_To_Hsl(float r, float g, float b, float h, float s, float l) + { + // Arrange + Rgb input = new Rgb(r, g, b); + + // Act + Hsl output = Converter.ToHsl(input); + + // Assert + Assert.Equal(h, output.H, FloatRoundingComparer); + Assert.Equal(s, output.S, FloatRoundingComparer); + Assert.Equal(l, output.L, FloatRoundingComparer); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs new file mode 100644 index 000000000..f8d8c773a --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs @@ -0,0 +1,71 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class RgbAndHsvConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 0, 1, 1, 1, 1)] + [InlineData(360, 1, 1, 1, 0, 0)] + [InlineData(0, 1, 1, 1, 0, 0)] + [InlineData(120, 1, 1, 0, 1, 0)] + [InlineData(240, 1, 1, 0, 0, 1)] + public void Convert_Hsv_To_Rgb(float h, float s, float v, float r, float g, float b) + { + // Arrange + Hsv input = new Hsv(h, s, v); + + // Act + Rgb output = Converter.ToRgb(input); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(r, output.R, FloatRoundingComparer); + Assert.Equal(g, output.G, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 0, 0, 1)] + [InlineData(1, 0, 0, 0, 1, 1)] + [InlineData(0, 1, 0, 120, 1, 1)] + [InlineData(0, 0, 1, 240, 1, 1)] + public void Convert_Rgb_To_Hsv(float r, float g, float b, float h, float s, float v) + { + // Arrange + Rgb input = new Rgb(r, g, b); + + // Act + Hsv output = Converter.ToHsv(input); + + // Assert + Assert.Equal(h, output.H, FloatRoundingComparer); + Assert.Equal(s, output.S, FloatRoundingComparer); + Assert.Equal(v, output.V, FloatRoundingComparer); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs new file mode 100644 index 000000000..3f741ea3d --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs @@ -0,0 +1,66 @@ +namespace ImageSharp.Tests.Colorspaces +{ + using System.Collections.Generic; + + using ImageSharp.ColorSpaces; + using ImageSharp.ColorSpaces.Conversion; + + using Xunit; + + /// + /// Tests - conversions. + /// + /// + /// Test data generated mathematically + /// + public class RgbAndYCbCrConversionTest + { + private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(3); + + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(255, 128, 128, 1, 1, 1)] + [InlineData(0, 128, 128, 0, 0, 0)] + [InlineData(128, 128, 128, 0.502, 0.502, 0.502)] + public void Convert_YCbCr_To_Rgb(float y, float cb, float cr, float r, float g, float b) + { + // Arrange + YCbCr input = new YCbCr(y, cb, cr); + + // Act + Rgb output = Converter.ToRgb(input); + + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + Assert.Equal(r, output.R, FloatRoundingComparer); + Assert.Equal(g, output.G, FloatRoundingComparer); + Assert.Equal(b, output.B, FloatRoundingComparer); + } + + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(1, 1, 1, 255, 128, 128)] + [InlineData(0.5, 0.5, 0.5, 127.5, 128, 128)] + [InlineData(1, 0, 0, 76.245, 84.972, 255)] + public void Convert_Rgb_To_YCbCr(float r, float g, float b, float y, float cb, float cr) + { + // Arrange + Rgb input = new Rgb(r, g, b); + + // Act + YCbCr output = Converter.ToYCbCr(input); + + // Assert + Assert.Equal(y, output.Y, FloatRoundingComparer); + Assert.Equal(cb, output.Cb, FloatRoundingComparer); + Assert.Equal(cr, output.Cr, FloatRoundingComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/MathFTests.cs b/tests/ImageSharp.Tests/Helpers/MathFTests.cs index 7f3fb77d0..62a971f9f 100644 --- a/tests/ImageSharp.Tests/Helpers/MathFTests.cs +++ b/tests/ImageSharp.Tests/Helpers/MathFTests.cs @@ -18,12 +18,24 @@ Assert.Equal(MathF.Ceiling(0.3333F), (float)Math.Ceiling(0.3333F)); } + [Fact] + public void MathF_Cos_Is_Equal() + { + Assert.Equal(MathF.Cos(0.3333F), (float)Math.Cos(0.3333F)); + } + [Fact] public void MathF_Abs_Is_Equal() { Assert.Equal(MathF.Abs(-0.3333F), (float)Math.Abs(-0.3333F)); } + [Fact] + public void MathF_Atan2_Is_Equal() + { + Assert.Equal(MathF.Atan2(1.2345F, 1.2345F), (float)Math.Atan2(1.2345F, 1.2345F)); + } + [Fact] public void MathF_Exp_Is_Equal() { @@ -54,16 +66,56 @@ Assert.Equal(MathF.Pow(1.2345F, 5.4321F), (float)Math.Pow(1.2345F, 5.4321F)); } + [Fact] + public void MathF_Round_Is_Equal() + { + Assert.Equal(MathF.Round(1.2345F), (float)Math.Round(1.2345F)); + } + + [Fact] + public void MathF_Round_With_Midpoint_Is_Equal() + { + Assert.Equal(MathF.Round(1.2345F, MidpointRounding.AwayFromZero), (float)Math.Round(1.2345F, MidpointRounding.AwayFromZero)); + } + [Fact] public void MathF_Sin_Is_Equal() { Assert.Equal(MathF.Sin(1.2345F), (float)Math.Sin(1.2345F)); } + [Fact] + public void MathF_SinC_Is_Equal() + { + float f = 1.2345F; + float expected = 1F; + if (Math.Abs(f) > Constants.Epsilon) + { + f *= (float)Math.PI; + float sinC = (float)Math.Sin(f) / f; + + expected = Math.Abs(sinC) < Constants.Epsilon ? 0F : sinC; + } + + Assert.Equal(MathF.SinC(1.2345F), expected); + } + [Fact] public void MathF_Sqrt_Is_Equal() { Assert.Equal(MathF.Sqrt(2F), (float)Math.Sqrt(2F)); } + + [Fact] + public void Convert_Degree_To_Radian() + { + Assert.Equal((float)(Math.PI / 2D), MathF.DegreeToRadian(90F), new FloatRoundingComparer(6)); + } + + [Fact] + public void Convert_Radian_To_Degree() + { + Assert.Equal(60F, MathF.RadianToDegree((float)(Math.PI / 3D)), new FloatRoundingComparer(5)); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs new file mode 100644 index 000000000..2bde12543 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataReaderCurvesTests + { + [Theory] + [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadOneDimensionalCurve(byte[] data, IccOneDimensionalCurve expected) + { + IccDataReader reader = CreateReader(data); + + IccOneDimensionalCurve output = reader.ReadOneDimensionalCurve(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadResponseCurve(byte[] data, IccResponseCurve expected, int channelCount) + { + IccDataReader reader = CreateReader(data); + + IccResponseCurve output = reader.ReadResponseCurve(channelCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadParametricCurve(byte[] data, IccParametricCurve expected) + { + IccDataReader reader = CreateReader(data); + + IccParametricCurve output = reader.ReadParametricCurve(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadCurveSegment(byte[] data, IccCurveSegment expected) + { + IccDataReader reader = CreateReader(data); + + IccCurveSegment output = reader.ReadCurveSegment(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadFormulaCurveElement(byte[] data, IccFormulaCurveElement expected) + { + IccDataReader reader = CreateReader(data); + + IccFormulaCurveElement output = reader.ReadFormulaCurveElement(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadSampledCurveElement(byte[] data, IccSampledCurveElement expected) + { + IccDataReader reader = CreateReader(data); + + IccSampledCurveElement output = reader.ReadSampledCurveElement(); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs new file mode 100644 index 000000000..52c67ba53 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.LutTests.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataReaderLutTests + { + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, bool isFloat) + { + IccDataReader reader = CreateReader(data); + + IccClut output = reader.ReadClut(inChannelCount, outChannelCount, isFloat); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut8(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = CreateReader(data); + + IccClut output = reader.ReadClut8(inChannelCount, outChannelCount, gridPointCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut16(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = CreateReader(data); + + IccClut output = reader.ReadClut16(inChannelCount, outChannelCount, gridPointCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClutF32(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = CreateReader(data); + + IccClut output = reader.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadLut8(byte[] data, IccLut expected) + { + IccDataReader reader = CreateReader(data); + + IccLut output = reader.ReadLut8(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadLut16(byte[] data, IccLut expected, int count) + { + IccDataReader reader = CreateReader(data); + + IccLut output = reader.ReadLut16(count); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs new file mode 100644 index 000000000..9d148ec94 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataReaderMatrixTests + { + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void ReadMatrix2D(byte[] data, int xCount, int yCount, bool isSingle, float[,] expected) + { + IccDataReader reader = CreateReader(data); + + float[,] output = reader.ReadMatrix(xCount, yCount, isSingle); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void ReadMatrix1D(byte[] data, int yCount, bool isSingle, float[] expected) + { + IccDataReader reader = CreateReader(data); + + float[] output = reader.ReadMatrix(yCount, isSingle); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs new file mode 100644 index 000000000..c02ef40e3 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataReaderMultiProcessElementTests + { + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void ReadMultiProcessElement(byte[] data, IccMultiProcessElement expected) + { + IccDataReader reader = CreateReader(data); + + IccMultiProcessElement output = reader.ReadMultiProcessElement(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void ReadCurveSetProcessElement(byte[] data, IccCurveSetProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = CreateReader(data); + + IccCurveSetProcessElement output = reader.ReadCurveSetProcessElement(inChannelCount, outChannelCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void ReadMatrixProcessElement(byte[] data, IccMatrixProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = CreateReader(data); + + IccMatrixProcessElement output = reader.ReadMatrixProcessElement(inChannelCount, outChannelCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void ReadClutProcessElement(byte[] data, IccClutProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = CreateReader(data); + + IccClutProcessElement output = reader.ReadClutProcessElement(inChannelCount, outChannelCount); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs new file mode 100644 index 000000000..e3593bfa9 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs @@ -0,0 +1,129 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using System; + using System.Numerics; + using Xunit; + + public class IccDataReaderNonPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadDateTime(byte[] data, DateTime expected) + { + IccDataReader reader = CreateReader(data); + + DateTime output = reader.ReadDateTime(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadVersionNumber(byte[] data, Version expected) + { + IccDataReader reader = CreateReader(data); + + Version output = reader.ReadVersionNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadXyzNumber(byte[] data, Vector3 expected) + { + IccDataReader reader = CreateReader(data); + + Vector3 output = reader.ReadXyzNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadProfileId(byte[] data, IccProfileId expected) + { + IccDataReader reader = CreateReader(data); + + IccProfileId output = reader.ReadProfileId(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadPositionNumber(byte[] data, IccPositionNumber expected) + { + IccDataReader reader = CreateReader(data); + + IccPositionNumber output = reader.ReadPositionNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadResponseNumber(byte[] data, IccResponseNumber expected) + { + IccDataReader reader = CreateReader(data); + + IccResponseNumber output = reader.ReadResponseNumber(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadNamedColor(byte[] data, IccNamedColor expected, uint coordinateCount) + { + IccDataReader reader = CreateReader(data); + + IccNamedColor output = reader.ReadNamedColor(coordinateCount); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionReadTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadProfileDescription(byte[] data, IccProfileDescription expected) + { + IccDataReader reader = CreateReader(data); + + IccProfileDescription output = reader.ReadProfileDescription(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ColorantTableEntryTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadColorantTableEntry(byte[] data, IccColorantTableEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccColorantTableEntry output = reader.ReadColorantTableEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadScreeningChannel(byte[] data, IccScreeningChannel expected) + { + IccDataReader reader = CreateReader(data); + + IccScreeningChannel output = reader.ReadScreeningChannel(); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs new file mode 100644 index 000000000..b9b0c6655 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using System; + using Xunit; + + public class IccDataReaderPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiTestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadAsciiString(byte[] textBytes, int length, string expected) + { + IccDataReader reader = CreateReader(textBytes); + + string output = reader.ReadAsciiString(length); + + Assert.Equal(expected, output); + } + + [Fact] + public void ReadAsciiStringWithNegativeLenghtThrowsArgumentException() + { + IccDataReader reader = CreateReader(new byte[4]); + + Assert.Throws(() => reader.ReadAsciiString(-1)); + } + + [Fact] + public void ReadUnicodeStringWithNegativeLenghtThrowsArgumentException() + { + IccDataReader reader = CreateReader(new byte[4]); + + Assert.Throws(() => reader.ReadUnicodeString(-1)); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadFix16(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); + + float output = reader.ReadFix16(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadUFix16(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); + + float output = reader.ReadUFix16(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadU1Fix15(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); + + float output = reader.ReadU1Fix15(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadUFix8(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); + + float output = reader.ReadUFix8(); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs new file mode 100644 index 000000000..9a2455f0e --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs @@ -0,0 +1,380 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataReaderTagDataEntryTests + { + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUnknownTagDataEntry(byte[] data, IccUnknownTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUnknownTagDataEntry output = reader.ReadUnknownTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadChromaticityTagDataEntry(byte[] data, IccChromaticityTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccChromaticityTagDataEntry output = reader.ReadChromaticityTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadColorantOrderTagDataEntry(byte[] data, IccColorantOrderTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccColorantOrderTagDataEntry output = reader.ReadColorantOrderTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadColorantTableTagDataEntry(byte[] data, IccColorantTableTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccColorantTableTagDataEntry output = reader.ReadColorantTableTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadCurveTagDataEntry(byte[] data, IccCurveTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccCurveTagDataEntry output = reader.ReadCurveTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadDataTagDataEntry(byte[] data, IccDataTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccDataTagDataEntry output = reader.ReadDataTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadDateTimeTagDataEntry(byte[] data, IccDateTimeTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccDateTimeTagDataEntry output = reader.ReadDateTimeTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLut16TagDataEntry(byte[] data, IccLut16TagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccLut16TagDataEntry output = reader.ReadLut16TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLut8TagDataEntry(byte[] data, IccLut8TagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccLut8TagDataEntry output = reader.ReadLut8TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLutAToBTagDataEntry(byte[] data, IccLutAToBTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccLutAToBTagDataEntry output = reader.ReadLutAtoBTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLutBToATagDataEntry(byte[] data, IccLutBToATagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccLutBToATagDataEntry output = reader.ReadLutBtoATagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMeasurementTagDataEntry(byte[] data, IccMeasurementTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccMeasurementTagDataEntry output = reader.ReadMeasurementTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMultiLocalizedUnicodeTagDataEntry(byte[] data, IccMultiLocalizedUnicodeTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccMultiLocalizedUnicodeTagDataEntry output = reader.ReadMultiLocalizedUnicodeTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMultiProcessElementsTagDataEntry(byte[] data, IccMultiProcessElementsTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccMultiProcessElementsTagDataEntry output = reader.ReadMultiProcessElementsTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadNamedColor2TagDataEntry(byte[] data, IccNamedColor2TagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccNamedColor2TagDataEntry output = reader.ReadNamedColor2TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadParametricCurveTagDataEntry(byte[] data, IccParametricCurveTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccParametricCurveTagDataEntry output = reader.ReadParametricCurveTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadProfileSequenceDescTagDataEntry(byte[] data, IccProfileSequenceDescTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccProfileSequenceDescTagDataEntry output = reader.ReadProfileSequenceDescTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadProfileSequenceIdentifierTagDataEntry(byte[] data, IccProfileSequenceIdentifierTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccProfileSequenceIdentifierTagDataEntry output = reader.ReadProfileSequenceIdentifierTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadResponseCurveSet16TagDataEntry(byte[] data, IccResponseCurveSet16TagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccResponseCurveSet16TagDataEntry output = reader.ReadResponseCurveSet16TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadFix16ArrayTagDataEntry(byte[] data, IccFix16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccFix16ArrayTagDataEntry output = reader.ReadFix16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadSignatureTagDataEntry(byte[] data, IccSignatureTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccSignatureTagDataEntry output = reader.ReadSignatureTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadTextTagDataEntry(byte[] data, IccTextTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccTextTagDataEntry output = reader.ReadTextTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUFix16ArrayTagDataEntry(byte[] data, IccUFix16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUFix16ArrayTagDataEntry output = reader.ReadUFix16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt16ArrayTagDataEntry(byte[] data, IccUInt16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUInt16ArrayTagDataEntry output = reader.ReadUInt16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt32ArrayTagDataEntry(byte[] data, IccUInt32ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUInt32ArrayTagDataEntry output = reader.ReadUInt32ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt64ArrayTagDataEntry(byte[] data, IccUInt64ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUInt64ArrayTagDataEntry output = reader.ReadUInt64ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt8ArrayTagDataEntry(byte[] data, IccUInt8ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUInt8ArrayTagDataEntry output = reader.ReadUInt8ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadViewingConditionsTagDataEntry(byte[] data, IccViewingConditionsTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccViewingConditionsTagDataEntry output = reader.ReadViewingConditionsTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadXyzTagDataEntry(byte[] data, IccXyzTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccXyzTagDataEntry output = reader.ReadXyzTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadTextDescriptionTagDataEntry(byte[] data, IccTextDescriptionTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccTextDescriptionTagDataEntry output = reader.ReadTextDescriptionTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadCrdInfoTagDataEntry(byte[] data, IccCrdInfoTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccCrdInfoTagDataEntry output = reader.ReadCrdInfoTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadScreeningTagDataEntry(byte[] data, IccScreeningTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccScreeningTagDataEntry output = reader.ReadScreeningTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUcrBgTagDataEntry(byte[] data, IccUcrBgTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUcrBgTagDataEntry output = reader.ReadUcrBgTagDataEntry(size); + + Assert.Equal(expected, output); + } + + private IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs new file mode 100644 index 000000000..635ce6168 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataReader/IccDataReaderTests.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using System; + using Xunit; + + public class IccDataReaderTests + { + [Fact] + public void ConstructorThrowsNullException() + { + Assert.Throws(() => new IccDataReader(null)); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs new file mode 100644 index 000000000..c04401f6d --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataWriterCurvesTests + { + [Theory] + [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteOneDimensionalCurve(byte[] expected, IccOneDimensionalCurve data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteOneDimensionalCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteResponseCurve(byte[] expected, IccResponseCurve data, int channelCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteResponseCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteParametricCurve(byte[] expected, IccParametricCurve data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteParametricCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteCurveSegment(byte[] expected, IccCurveSegment data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteCurveSegment(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteFormulaCurveElement(byte[] expected, IccFormulaCurveElement data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteFormulaCurveElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteSampledCurveElement(byte[] expected, IccSampledCurveElement data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteSampledCurveElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs new file mode 100644 index 000000000..4fcc55d01 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataWriterLutTests + { + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClut(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClutF32(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut8(byte[] expected, IccLut data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut16(byte[] expected, IccLut data, int count) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs new file mode 100644 index 000000000..61b5d57ff --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs @@ -0,0 +1,81 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using System.Numerics; + + using ImageSharp.Memory; + + using Xunit; + + public class IccDataWriterMatrixTests + { + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix2D_Array(byte[] expected, int xCount, int yCount, bool isSingle, float[,] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_Matrix4x4TestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix2D_Matrix4x4(byte[] expected, int xCount, int yCount, bool isSingle, Matrix4x4 data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_Fast2DArrayTestData), MemberType = typeof(IccTestDataMatrix))] + internal void WriteMatrix2D_Fast2DArray(byte[] expected, int xCount, int yCount, bool isSingle, Fast2DArray data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix1D_Array(byte[] expected, int yCount, bool isSingle, float[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_Vector3TestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix1D_Vector3(byte[] expected, int yCount, bool isSingle, Vector3 data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs new file mode 100644 index 000000000..e3bc37574 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataWriterMultiProcessElementTests + { + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void WriteMultiProcessElement(byte[] expected, IccMultiProcessElement data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMultiProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void WriteCurveSetProcessElement(byte[] expected, IccCurveSetProcessElement data, int inChannelCount, int outChannelCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteCurveSetProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void WriteMatrixProcessElement(byte[] expected, IccMatrixProcessElement data, int inChannelCount, int outChannelCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMatrixProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElement.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + internal void WriteClutProcessElement(byte[] expected, IccClutProcessElement data, int inChannelCount, int outChannelCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteClutProcessElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs new file mode 100644 index 000000000..ae8345805 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs @@ -0,0 +1,127 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using System; + using System.Numerics; + using Xunit; + + public class IccDataWriterNonPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteDateTime(byte[] expected, DateTime data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteDateTime(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteVersionNumber(byte[] expected, Version data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteVersionNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteXyzNumber(byte[] expected, Vector3 data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteXyzNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteProfileId(byte[] expected, IccProfileId data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteProfileId(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WritePositionNumber(byte[] expected, IccPositionNumber data) + { + IccDataWriter writer = CreateWriter(); + + writer.WritePositionNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteResponseNumber(byte[] expected, IccResponseNumber data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteResponseNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteNamedColor(byte[] expected, IccNamedColor data, uint coordinateCount) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteNamedColor(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionWriteTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteProfileDescription(byte[] expected, IccProfileDescription data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteProfileDescription(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteScreeningChannel(byte[] expected, IccScreeningChannel data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteScreeningChannel(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs new file mode 100644 index 000000000..8d0cd32ab --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs @@ -0,0 +1,122 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using System; + using Xunit; + + public class IccDataWriterPrimitivesTests + { + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiWriteTestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteAsciiString(byte[] expected, string data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteAsciiString(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiPaddingTestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteAsciiStringPadded(byte[] expected, int length, string data, bool ensureNullTerminator) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteAsciiString(data, length, ensureNullTerminator); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Fact] + public void WriteAsciiStringWithNullWritesEmpty() + { + IccDataWriter writer = CreateWriter(); + + int count = writer.WriteAsciiString(null); + byte[] output = writer.GetData(); + + Assert.Equal(0, count); + Assert.Equal(new byte[0], output); + } + + [Fact] + public void WriteAsciiStringWithNegativeLenghtThrowsArgumentException() + { + IccDataWriter writer = CreateWriter(); + + Assert.Throws(() => writer.WriteAsciiString("abcd", -1, false)); + } + + [Fact] + public void WriteUnicodeStringWithNullWritesEmpty() + { + IccDataWriter writer = CreateWriter(); + + int count = writer.WriteUnicodeString(null); + byte[] output = writer.GetData(); + + Assert.Equal(0, count); + Assert.Equal(new byte[0], output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteFix16(byte[] expected, float data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteFix16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteUFix16(byte[] expected, float data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUFix16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteU1Fix15(byte[] expected, float data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteU1Fix15(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteUFix8(byte[] expected, float data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUFix8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs new file mode 100644 index 000000000..affe9835e --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs @@ -0,0 +1,413 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataWriterTagDataEntryTests + { + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUnknownTagDataEntry(byte[] expected, IccUnknownTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUnknownTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteChromaticityTagDataEntry(byte[] expected, IccChromaticityTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteChromaticityTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteColorantOrderTagDataEntry(byte[] expected, IccColorantOrderTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteColorantOrderTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteColorantTableTagDataEntry(byte[] expected, IccColorantTableTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteColorantTableTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteCurveTagDataEntry(byte[] expected, IccCurveTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteCurveTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteDataTagDataEntry(byte[] expected, IccDataTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteDataTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteDateTimeTagDataEntry(byte[] expected, IccDateTimeTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteDateTimeTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLut16TagDataEntry(byte[] expected, IccLut16TagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLut16TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLut8TagDataEntry(byte[] expected, IccLut8TagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLut8TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLutAToBTagDataEntry(byte[] expected, IccLutAToBTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLutAtoBTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLutBToATagDataEntry(byte[] expected, IccLutBToATagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteLutBtoATagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMeasurementTagDataEntry(byte[] expected, IccMeasurementTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMeasurementTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMultiLocalizedUnicodeTagDataEntry(byte[] expected, IccMultiLocalizedUnicodeTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMultiLocalizedUnicodeTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMultiProcessElementsTagDataEntry(byte[] expected, IccMultiProcessElementsTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteMultiProcessElementsTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteNamedColor2TagDataEntry(byte[] expected, IccNamedColor2TagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteNamedColor2TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteParametricCurveTagDataEntry(byte[] expected, IccParametricCurveTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteParametricCurveTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteProfileSequenceDescTagDataEntry(byte[] expected, IccProfileSequenceDescTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteProfileSequenceDescTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteProfileSequenceIdentifierTagDataEntry(byte[] expected, IccProfileSequenceIdentifierTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteProfileSequenceIdentifierTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteResponseCurveSet16TagDataEntry(byte[] expected, IccResponseCurveSet16TagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteResponseCurveSet16TagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteFix16ArrayTagDataEntry(byte[] expected, IccFix16ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteFix16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteSignatureTagDataEntry(byte[] expected, IccSignatureTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteSignatureTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteTextTagDataEntry(byte[] expected, IccTextTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteTextTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUFix16ArrayTagDataEntry(byte[] expected, IccUFix16ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUFix16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt16ArrayTagDataEntry(byte[] expected, IccUInt16ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUInt16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt32ArrayTagDataEntry(byte[] expected, IccUInt32ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUInt32ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt64ArrayTagDataEntry(byte[] expected, IccUInt64ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUInt64ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt8ArrayTagDataEntry(byte[] expected, IccUInt8ArrayTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUInt8ArrayTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteViewingConditionsTagDataEntry(byte[] expected, IccViewingConditionsTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteViewingConditionsTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteXyzTagDataEntry(byte[] expected, IccXyzTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteXyzTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteTextDescriptionTagDataEntry(byte[] expected, IccTextDescriptionTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteTextDescriptionTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteCrdInfoTagDataEntry(byte[] expected, IccCrdInfoTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteCrdInfoTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteScreeningTagDataEntry(byte[] expected, IccScreeningTagDataEntry data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteScreeningTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUcrBgTagDataEntry(byte[] expected, IccUcrBgTagDataEntry data, uint size) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteUcrBgTagDataEntry(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs new file mode 100644 index 000000000..5239d7cd5 --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/DataWriter/IccDataWriterTests.cs @@ -0,0 +1,114 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccDataWriterTests + { + [Fact] + public void WriteEmpty() + { + IccDataWriter writer = CreateWriter(); + + writer.WriteEmpty(4); + byte[] output = writer.GetData(); + + Assert.Equal(new byte[4], output); + } + + [Theory] + [InlineData(1, 4)] + [InlineData(4, 4)] + public void WritePadding(int writePosition, int expectedLength) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteEmpty(writePosition); + writer.WritePadding(); + byte[] output = writer.GetData(); + + Assert.Equal(new byte[expectedLength], output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt8TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt8(byte[] data, byte[] expected) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt16TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt16(byte[] expected, ushort[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.Int16TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayInt16(byte[] expected, short[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt32TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt32(byte[] expected, uint[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.Int32TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayInt32(byte[] expected, int[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt64TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt64(byte[] expected, ulong[] data) + { + IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs new file mode 100644 index 000000000..0db64c47f --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccReaderTests.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccReaderTests + { + [Fact] + public void ReadProfile() + { + IccReader reader = CreateReader(); + + IccProfile output = reader.Read(IccTestDataProfiles.Header_Random_Array); + + Assert.Equal(0, output.Entries.Count); + Assert.NotNull(output.Header); + + IccProfileHeader header = output.Header; + IccProfileHeader expected = IccTestDataProfiles.Header_Random_Read; + Assert.Equal(header.Class, expected.Class); + Assert.Equal(header.CmmType, expected.CmmType); + Assert.Equal(header.CreationDate, expected.CreationDate); + Assert.Equal(header.CreatorSignature, expected.CreatorSignature); + Assert.Equal(header.DataColorSpace, expected.DataColorSpace); + Assert.Equal(header.DeviceAttributes, expected.DeviceAttributes); + Assert.Equal(header.DeviceManufacturer, expected.DeviceManufacturer); + Assert.Equal(header.DeviceModel, expected.DeviceModel); + Assert.Equal(header.FileSignature, expected.FileSignature); + Assert.Equal(header.Flags, expected.Flags); + Assert.Equal(header.Id, expected.Id); + Assert.Equal(header.PcsIlluminant, expected.PcsIlluminant); + Assert.Equal(header.PrimaryPlatformSignature, expected.PrimaryPlatformSignature); + Assert.Equal(header.ProfileConnectionSpace, expected.ProfileConnectionSpace); + Assert.Equal(header.RenderingIntent, expected.RenderingIntent); + Assert.Equal(header.Size, expected.Size); + Assert.Equal(header.Version, expected.Version); + } + + private IccReader CreateReader() + { + return new IccReader(); + } + } +} diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs new file mode 100644 index 000000000..6192e6eae --- /dev/null +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccWriterTests.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Icc +{ + using Xunit; + + public class IccWriterTests + { + [Fact] + public void WriteProfile() + { + IccWriter writer = CreateWriter(); + + IccProfile profile = new IccProfile() + { + Header = IccTestDataProfiles.Header_Random_Write + }; + byte[] output = writer.Write(profile); + + Assert.Equal(IccTestDataProfiles.Header_Random_Array, output); + } + + private IccWriter CreateWriter() + { + return new IccWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs new file mode 100644 index 000000000..a14433f1e --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs @@ -0,0 +1,146 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + internal static class IccTestDataArray + { + #region Byte + + public static readonly byte[] UInt8 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly object[][] UInt8TestData = + { + new object[] { UInt8, UInt8 } + }; + + #endregion + + #region UInt16 + + public static readonly ushort[] UInt16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] UInt16_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_0, + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_6, + IccTestDataPrimitives.UInt16_7, + IccTestDataPrimitives.UInt16_8, + IccTestDataPrimitives.UInt16_9 + ); + + public static readonly object[][] UInt16TestData = + { + new object[] { UInt16_Arr, UInt16_Val } + }; + + #endregion + + #region Int16 + + public static readonly short[] Int16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] Int16_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.Int16_0, + IccTestDataPrimitives.Int16_1, + IccTestDataPrimitives.Int16_2, + IccTestDataPrimitives.Int16_3, + IccTestDataPrimitives.Int16_4, + IccTestDataPrimitives.Int16_5, + IccTestDataPrimitives.Int16_6, + IccTestDataPrimitives.Int16_7, + IccTestDataPrimitives.Int16_8, + IccTestDataPrimitives.Int16_9 + ); + + public static readonly object[][] Int16TestData = + { + new object[] { Int16_Arr, Int16_Val } + }; + + #endregion + + #region UInt32 + + public static readonly uint[] UInt32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] UInt32_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_0, + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_3, + IccTestDataPrimitives.UInt32_4, + IccTestDataPrimitives.UInt32_5, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.UInt32_7, + IccTestDataPrimitives.UInt32_8, + IccTestDataPrimitives.UInt32_9 + ); + + public static readonly object[][] UInt32TestData = + { + new object[] { UInt32_Arr, UInt32_Val } + }; + + #endregion + + #region Int32 + + public static readonly int[] Int32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] Int32_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.Int32_0, + IccTestDataPrimitives.Int32_1, + IccTestDataPrimitives.Int32_2, + IccTestDataPrimitives.Int32_3, + IccTestDataPrimitives.Int32_4, + IccTestDataPrimitives.Int32_5, + IccTestDataPrimitives.Int32_6, + IccTestDataPrimitives.Int32_7, + IccTestDataPrimitives.Int32_8, + IccTestDataPrimitives.Int32_9 + ); + + public static readonly object[][] Int32TestData = + { + new object[] { Int32_Arr, Int32_Val } + }; + + #endregion + + #region UInt64 + + public static readonly ulong[] UInt64_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] UInt64_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt64_0, + IccTestDataPrimitives.UInt64_1, + IccTestDataPrimitives.UInt64_2, + IccTestDataPrimitives.UInt64_3, + IccTestDataPrimitives.UInt64_4, + IccTestDataPrimitives.UInt64_5, + IccTestDataPrimitives.UInt64_6, + IccTestDataPrimitives.UInt64_7, + IccTestDataPrimitives.UInt64_8, + IccTestDataPrimitives.UInt64_9 + ); + + public static readonly object[][] UInt64TestData = + { + new object[] { UInt64_Arr, UInt64_Val } + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs new file mode 100644 index 000000000..c8d517aac --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs @@ -0,0 +1,386 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Numerics; + + internal static class IccTestDataCurves + { + #region Response + + /// + /// Channels: 3 + /// + public static readonly IccResponseCurve Response_ValGrad = new IccResponseCurve + ( + IccCurveMeasurementEncodings.StatusA, + new Vector3[] + { + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccTestDataNonPrimitives.XyzNumber_ValVar2, + IccTestDataNonPrimitives.XyzNumber_ValVar3, + }, + new IccResponseNumber[][] + { + new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val1, IccTestDataNonPrimitives.ResponseNumber_Val2 }, + new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val3, IccTestDataNonPrimitives.ResponseNumber_Val4 }, + new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val5, IccTestDataNonPrimitives.ResponseNumber_Val6 }, + } + ); + + /// + /// Channels: 3 + /// + public static readonly byte[] Response_Grad = ArrayHelper.Concat + ( + new byte[] { 0x53, 0x74, 0x61, 0x41 }, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_2, + + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataNonPrimitives.XyzNumber_Var2, + IccTestDataNonPrimitives.XyzNumber_Var3, + + IccTestDataNonPrimitives.ResponseNumber_1, + IccTestDataNonPrimitives.ResponseNumber_2, + + IccTestDataNonPrimitives.ResponseNumber_3, + IccTestDataNonPrimitives.ResponseNumber_4, + + IccTestDataNonPrimitives.ResponseNumber_5, + IccTestDataNonPrimitives.ResponseNumber_6 + ); + + public static readonly object[][] ResponseCurveTestData = + { + new object[] { Response_Grad, Response_ValGrad, 3 }, + }; + + #endregion + + #region Parametric + + public static readonly IccParametricCurve Parametric_ValVar1 = new IccParametricCurve(1); + public static readonly IccParametricCurve Parametric_ValVar2 = new IccParametricCurve(1, 2, 3); + public static readonly IccParametricCurve Parametric_ValVar3 = new IccParametricCurve(1, 2, 3, 4); + public static readonly IccParametricCurve Parametric_ValVar4 = new IccParametricCurve(1, 2, 3, 4, 5); + public static readonly IccParametricCurve Parametric_ValVar5 = new IccParametricCurve(1, 2, 3, 4, 5, 6, 7); + + public static readonly byte[] Parametric_Var1 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x00, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1 + ); + + public static readonly byte[] Parametric_Var2 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x01, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3 + ); + + public static readonly byte[] Parametric_Var3 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x02, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_4 + ); + + public static readonly byte[] Parametric_Var4 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_5 + ); + + public static readonly byte[] Parametric_Var5 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x04, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_5, + IccTestDataPrimitives.Fix16_6, + IccTestDataPrimitives.Fix16_7 + ); + + public static readonly object[][] ParametricCurveTestData = + { + new object[] { Parametric_Var1, Parametric_ValVar1 }, + new object[] { Parametric_Var2, Parametric_ValVar2 }, + new object[] { Parametric_Var3, Parametric_ValVar3 }, + new object[] { Parametric_Var4, Parametric_ValVar4 }, + new object[] { Parametric_Var5, Parametric_ValVar5 }, + }; + + #endregion + + #region Formula Segment + + public static readonly IccFormulaCurveElement Formula_ValVar1 = new IccFormulaCurveElement(IccFormulaCurveType.Type1, 1, 2, 3, 4, 0, 0); + public static readonly IccFormulaCurveElement Formula_ValVar2 = new IccFormulaCurveElement(IccFormulaCurveType.Type2, 1, 2, 3, 4, 5, 0); + public static readonly IccFormulaCurveElement Formula_ValVar3 = new IccFormulaCurveElement(IccFormulaCurveType.Type3, 0, 2, 3, 4, 5, 6); + + public static readonly byte[] Formula_Var1 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x00, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4 + ); + + public static readonly byte[] Formula_Var2 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x01, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5 + ); + + public static readonly byte[] Formula_Var3 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x02, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6 + ); + + public static readonly object[][] FormulaCurveSegmentTestData = + { + new object[] { Formula_Var1, Formula_ValVar1 }, + new object[] { Formula_Var2, Formula_ValVar2 }, + new object[] { Formula_Var3, Formula_ValVar3 }, + }; + + #endregion + + #region Sampled Segment + + public static readonly IccSampledCurveElement Sampled_ValGrad1 = new IccSampledCurveElement(new float[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + public static readonly IccSampledCurveElement Sampled_ValGrad2 = new IccSampledCurveElement(new float[] { 9, 8, 7, 6, 5, 4, 3, 2, 1 }); + + public static readonly byte[] Sampled_Grad1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_9, + + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_9 + ); + + public static readonly byte[] Sampled_Grad2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_9, + + IccTestDataPrimitives.Single_9, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_1 + ); + + public static readonly object[][] SampledCurveSegmentTestData = + { + new object[] { Sampled_Grad1, Sampled_ValGrad1 }, + new object[] { Sampled_Grad2, Sampled_ValGrad2 }, + }; + + #endregion + + #region Segment + + public static readonly IccCurveSegment Segment_ValFormula1 = Formula_ValVar1; + public static readonly IccCurveSegment Segment_ValFormula2 = Formula_ValVar2; + public static readonly IccCurveSegment Segment_ValFormula3 = Formula_ValVar3; + public static readonly IccCurveSegment Segment_ValSampled1 = Sampled_ValGrad1; + public static readonly IccCurveSegment Segment_ValSampled2 = Sampled_ValGrad2; + + public static readonly byte[] Segment_Formula1 = ArrayHelper.Concat + ( + new byte[] + { + 0x70, 0x61, 0x72, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Formula_Var1 + ); + + public static readonly byte[] Segment_Formula2 = ArrayHelper.Concat + ( + new byte[] + { + 0x70, 0x61, 0x72, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Formula_Var2 + ); + + public static readonly byte[] Segment_Formula3 = ArrayHelper.Concat + ( + new byte[] + { + 0x70, 0x61, 0x72, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Formula_Var3 + ); + + public static readonly byte[] Segment_Sampled1 = ArrayHelper.Concat + ( + new byte[] + { + 0x73, 0x61, 0x6D, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Sampled_Grad1 + ); + + public static readonly byte[] Segment_Sampled2 = ArrayHelper.Concat + ( + new byte[] + { + 0x73, 0x61, 0x6D, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Sampled_Grad2 + ); + + public static readonly object[][] CurveSegmentTestData = + { + new object[] { Segment_Formula1, Segment_ValFormula1 }, + new object[] { Segment_Formula2, Segment_ValFormula2 }, + new object[] { Segment_Formula3, Segment_ValFormula3 }, + new object[] { Segment_Sampled1, Segment_ValSampled1 }, + new object[] { Segment_Sampled2, Segment_ValSampled2 }, + }; + + #endregion + + #region One Dimensional + + public static readonly IccOneDimensionalCurve OneDimensional_ValFormula1 = new IccOneDimensionalCurve + ( + new float[] { 0, 1 }, + new IccCurveSegment[] { Segment_ValFormula1, Segment_ValFormula2, Segment_ValFormula3 } + ); + public static readonly IccOneDimensionalCurve OneDimensional_ValFormula2 = new IccOneDimensionalCurve + ( + new float[] { 0, 1 }, + new IccCurveSegment[] { Segment_ValFormula3, Segment_ValFormula2, Segment_ValFormula1 } + ); + public static readonly IccOneDimensionalCurve OneDimensional_ValSampled = new IccOneDimensionalCurve + ( + new float[] { 0, 1 }, + new IccCurveSegment[] { Segment_ValSampled1, Segment_ValSampled2, Segment_ValSampled1 } + ); + + public static readonly byte[] OneDimensional_Formula1 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_0, + IccTestDataPrimitives.Single_1, + Segment_Formula1, + Segment_Formula2, + Segment_Formula3 + ); + + public static readonly byte[] OneDimensional_Formula2 = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_0, + IccTestDataPrimitives.Single_1, + Segment_Formula3, + Segment_Formula2, + Segment_Formula1 + ); + + public static readonly byte[] OneDimensional_Sampled = ArrayHelper.Concat + ( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_0, + IccTestDataPrimitives.Single_1, + Segment_Sampled1, + Segment_Sampled2, + Segment_Sampled1 + ); + + public static readonly object[][] OneDimensionalCurveTestData = + { + new object[] { OneDimensional_Formula1, OneDimensional_ValFormula1 }, + new object[] { OneDimensional_Formula2, OneDimensional_ValFormula2 }, + new object[] { OneDimensional_Sampled, OneDimensional_ValSampled }, + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs new file mode 100644 index 000000000..cc2dfc6e9 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs @@ -0,0 +1,254 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + internal static class IccTestDataLut + { + #region LUT8 + + public static readonly IccLut LUT8_ValGrad = CreateLUT8Val(); + public static readonly byte[] LUT8_Grad = CreateLUT8(); + + private static IccLut CreateLUT8Val() + { + float[] result = new float[256]; + for (int i = 0; i < 256; i++) { result[i] = i / 255f; } + return new IccLut(result); + } + + private static byte[] CreateLUT8() + { + byte[] result = new byte[256]; + for (int i = 0; i < 256; i++) { result[i] = (byte)i; } + return result; + } + + public static readonly object[][] Lut8TestData = + { + new object[] { LUT8_Grad, LUT8_ValGrad }, + }; + + #endregion + + #region LUT16 + + public static readonly IccLut LUT16_ValGrad = new IccLut(new float[] + { + 1f / ushort.MaxValue, + 2f / ushort.MaxValue, + 3f / ushort.MaxValue, + 4f / ushort.MaxValue, + 5f / ushort.MaxValue, + 6f / ushort.MaxValue, + 7f / ushort.MaxValue, + 8f / ushort.MaxValue, + 9f / ushort.MaxValue, + 32768f / ushort.MaxValue, + 1f + }); + + public static readonly byte[] LUT16_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_6, + IccTestDataPrimitives.UInt16_7, + IccTestDataPrimitives.UInt16_8, + IccTestDataPrimitives.UInt16_9, + IccTestDataPrimitives.UInt16_32768, + IccTestDataPrimitives.UInt16_Max + ); + + public static readonly object[][] Lut16TestData = + { + new object[] { LUT16_Grad, LUT16_ValGrad, 11 }, + }; + + #endregion + + #region CLUT8 + + public static readonly IccClut CLUT8_ValGrad = new IccClut + ( + new float[][] + { + new float[] { 1f / byte.MaxValue, 2f / byte.MaxValue, 3f / byte.MaxValue }, + new float[] { 4f / byte.MaxValue, 5f / byte.MaxValue, 6f / byte.MaxValue }, + new float[] { 7f / byte.MaxValue, 8f / byte.MaxValue, 9f / byte.MaxValue }, + + new float[] { 10f / byte.MaxValue, 11f / byte.MaxValue, 12f / byte.MaxValue }, + new float[] { 13f / byte.MaxValue, 14f / byte.MaxValue, 15f / byte.MaxValue }, + new float[] { 16f / byte.MaxValue, 17f / byte.MaxValue, 18f / byte.MaxValue }, + + new float[] { 19f / byte.MaxValue, 20f / byte.MaxValue, 21f / byte.MaxValue }, + new float[] { 22f / byte.MaxValue, 23f / byte.MaxValue, 24f / byte.MaxValue }, + new float[] { 25f / byte.MaxValue, 26f / byte.MaxValue, 27f / byte.MaxValue }, + }, + new byte[] { 3, 3 }, IccClutDataType.UInt8 + ); + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// Grid-point Count: { 3, 3 } + /// + public static readonly byte[] CLUT8_Grad = + { + 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, + + 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, + + 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, + 0x19, 0x1A, 0x1B, + }; + + public static readonly object[][] Clut8TestData = + { + new object[] { CLUT8_Grad, CLUT8_ValGrad, 2, 3, new byte[] { 3, 3 } }, + }; + + #endregion + + #region CLUT16 + + public static readonly IccClut CLUT16_ValGrad = new IccClut + ( + new float[][] + { + new float[] { 1f / ushort.MaxValue, 2f / ushort.MaxValue, 3f / ushort.MaxValue }, + new float[] { 4f / ushort.MaxValue, 5f / ushort.MaxValue, 6f / ushort.MaxValue }, + new float[] { 7f / ushort.MaxValue, 8f / ushort.MaxValue, 9f / ushort.MaxValue }, + + new float[] { 10f / ushort.MaxValue, 11f / ushort.MaxValue, 12f / ushort.MaxValue }, + new float[] { 13f / ushort.MaxValue, 14f / ushort.MaxValue, 15f / ushort.MaxValue }, + new float[] { 16f / ushort.MaxValue, 17f / ushort.MaxValue, 18f / ushort.MaxValue }, + + new float[] { 19f / ushort.MaxValue, 20f / ushort.MaxValue, 21f / ushort.MaxValue }, + new float[] { 22f / ushort.MaxValue, 23f / ushort.MaxValue, 24f / ushort.MaxValue }, + new float[] { 25f / ushort.MaxValue, 26f / ushort.MaxValue, 27f / ushort.MaxValue }, + }, + new byte[] { 3, 3 }, IccClutDataType.UInt16 + ); + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// Grid-point Count: { 3, 3 } + /// + public static readonly byte[] CLUT16_Grad = + { + 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, + + 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0C, + 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, + 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, + + 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, + 0x00, 0x16, 0x00, 0x17, 0x00, 0x18, + 0x00, 0x19, 0x00, 0x1A, 0x00, 0x1B, + }; + + public static readonly object[][] Clut16TestData = + { + new object[] { CLUT16_Grad, CLUT16_ValGrad, 2, 3, new byte[] { 3, 3 } }, + }; + + #endregion + + #region CLUTf32 + + public static readonly IccClut CLUTf32_ValGrad = new IccClut + ( + new float[][] + { + new float[] { 1f, 2f, 3f }, + new float[] { 4f, 5f, 6f }, + new float[] { 7f, 8f, 9f }, + + new float[] { 1f, 2f, 3f }, + new float[] { 4f, 5f, 6f }, + new float[] { 7f, 8f, 9f }, + + new float[] { 1f, 2f, 3f }, + new float[] { 4f, 5f, 6f }, + new float[] { 7f, 8f, 9f }, + }, + new byte[] { 3, 3 }, IccClutDataType.Float + ); + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// Grid-point Count: { 3, 3 } + /// + public static readonly byte[] CLUTf32_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9, + + IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9, + + IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9 + ); + + public static readonly object[][] ClutF32TestData = + { + new object[] { CLUTf32_Grad, CLUTf32_ValGrad, 2, 3, new byte[] { 3, 3 } }, + }; + + #endregion + + #region CLUT + + public static readonly IccClut CLUT_Val8 = CLUT8_ValGrad; + public static readonly IccClut CLUT_Val16 = CLUT16_ValGrad; + public static readonly IccClut CLUT_Valf32 = CLUTf32_ValGrad; + + public static readonly byte[] CLUT_8 = ArrayHelper.Concat + ( + new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[4] { 0x01, 0x00, 0x00, 0x00 }, + CLUT8_Grad + ); + + public static readonly byte[] CLUT_16 = ArrayHelper.Concat + ( + new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[4] { 0x02, 0x00, 0x00, 0x00 }, + CLUT16_Grad + ); + + public static readonly byte[] CLUT_f32 = ArrayHelper.Concat + ( + new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + CLUTf32_Grad + ); + + public static readonly object[][] ClutTestData = + { + new object[] { CLUT_8, CLUT_Val8, 2, 3, false }, + new object[] { CLUT_16, CLUT_Val16, 2, 3, false }, + new object[] { CLUT_f32, CLUT_Valf32, 2, 3, true }, + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs new file mode 100644 index 000000000..78e493829 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs @@ -0,0 +1,177 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using System.Numerics; + +namespace ImageSharp.Tests +{ + using ImageSharp.Memory; + + internal static class IccTestDataMatrix + { + #region 2D + + /// + /// 3x3 Matrix + /// + public static readonly float[,] Single_2DArray_ValGrad = + { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + }; + /// + /// 3x3 Matrix + /// + public static readonly float[,] Single_2DArray_ValIdentity = + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 }, + }; + + /// + /// 3x3 Matrix + /// + public static readonly Matrix4x4 Single_Matrix4x4_ValGrad = new Matrix4x4(1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 0, 0, 0, 1); + + /// + /// 3x3 Matrix + /// + public static readonly Matrix4x4 Single_Matrix4x4_ValIdentity = Matrix4x4.Identity; + + /// + /// 3x3 Matrix + /// + public static readonly Fast2DArray Single_Fast2DArray_ValGrad = new Fast2DArray(Single_2DArray_ValGrad); + + /// + /// 3x3 Matrix + /// + public static readonly Fast2DArray Single_Fast2DArray_ValIdentity = new Fast2DArray(Single_2DArray_ValIdentity); + + /// + /// 3x3 Matrix + /// + public static readonly byte[] Fix16_2D_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_7, + + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_5, + IccTestDataPrimitives.Fix16_8, + + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_6, + IccTestDataPrimitives.Fix16_9 + ); + + /// + /// 3x3 Matrix + /// + public static readonly byte[] Fix16_2D_Identity = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_0, + IccTestDataPrimitives.Fix16_0, + + IccTestDataPrimitives.Fix16_0, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_0, + + IccTestDataPrimitives.Fix16_0, + IccTestDataPrimitives.Fix16_0, + IccTestDataPrimitives.Fix16_1 + ); + + /// + /// 3x3 Matrix + /// + public static readonly byte[] Single_2D_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_7, + + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_8, + + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_9 + ); + + public static readonly object[][] Matrix2D_FloatArrayTestData = + { + new object[] { Fix16_2D_Grad, 3, 3, false, Single_2DArray_ValGrad }, + new object[] { Fix16_2D_Identity, 3, 3, false, Single_2DArray_ValIdentity }, + new object[] { Single_2D_Grad, 3, 3, true, Single_2DArray_ValGrad }, + }; + + public static readonly object[][] Matrix2D_Fast2DArrayTestData = + { + new object[] { Fix16_2D_Grad, 3, 3, false, Single_Fast2DArray_ValGrad }, + new object[] { Fix16_2D_Identity, 3, 3, false, Single_Fast2DArray_ValIdentity }, + new object[] { Single_2D_Grad, 3, 3, true, Single_Fast2DArray_ValGrad }, + }; + + public static readonly object[][] Matrix2D_Matrix4x4TestData = + { + new object[] { Fix16_2D_Grad, 3, 3, false, Single_Matrix4x4_ValGrad }, + new object[] { Fix16_2D_Identity, 3, 3, false, Single_Matrix4x4_ValIdentity }, + new object[] { Single_2D_Grad, 3, 3, true, Single_Matrix4x4_ValGrad }, + }; + + #endregion + + #region 1D + + /// + /// 3x1 Matrix + /// + public static readonly float[] Single_1DArray_ValGrad = { 1, 4, 7 }; + /// + /// 3x1 Matrix + /// + public static readonly Vector3 Single_Vector3_ValGrad = new Vector3(1, 4, 7); + + /// + /// 3x1 Matrix + /// + public static readonly byte[] Fix16_1D_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_7 + ); + + /// + /// 3x1 Matrix + /// + public static readonly byte[] Single_1D_Grad = ArrayHelper.Concat + ( + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_7 + ); + + public static readonly object[][] Matrix1D_ArrayTestData = + { + new object[] { Fix16_1D_Grad, 3, false, Single_1DArray_ValGrad }, + new object[] { Single_1D_Grad, 3, true, Single_1DArray_ValGrad }, + }; + + public static readonly object[][] Matrix1D_Vector3TestData = + { + new object[] { Fix16_1D_Grad, 3, false, Single_Vector3_ValGrad }, + new object[] { Single_1D_Grad, 3, true, Single_Vector3_ValGrad }, + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs new file mode 100644 index 000000000..49e9550df --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs @@ -0,0 +1,157 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + internal static class IccTestDataMultiProcessElement + { + #region CurveSet + + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly IccCurveSetProcessElement CurvePE_ValGrad = new IccCurveSetProcessElement(new IccOneDimensionalCurve[] + { + IccTestDataCurves.OneDimensional_ValFormula1, + IccTestDataCurves.OneDimensional_ValFormula2, + IccTestDataCurves.OneDimensional_ValFormula1 + }); + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly byte[] CurvePE_Grad = ArrayHelper.Concat + ( + IccTestDataCurves.OneDimensional_Formula1, + IccTestDataCurves.OneDimensional_Formula2, + IccTestDataCurves.OneDimensional_Formula1 + ); + + public static readonly object[][] CurveSetTestData = + { + new object[] { CurvePE_Grad, CurvePE_ValGrad, 3, 3 }, + }; + + #endregion + + #region Matrix + + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly IccMatrixProcessElement MatrixPE_ValGrad = new IccMatrixProcessElement + ( + IccTestDataMatrix.Single_2DArray_ValGrad, + IccTestDataMatrix.Single_1DArray_ValGrad + ); + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly byte[] MatrixPE_Grad = ArrayHelper.Concat + ( + IccTestDataMatrix.Single_2D_Grad, + IccTestDataMatrix.Single_1D_Grad + ); + + public static readonly object[][] MatrixTestData = + { + new object[] { MatrixPE_Grad, MatrixPE_ValGrad, 3, 3 }, + }; + + + #endregion + + #region CLUT + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// + public static readonly IccClutProcessElement CLUTPE_ValGrad = new IccClutProcessElement(IccTestDataLut.CLUT_Valf32); + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// + public static readonly byte[] CLUTPE_Grad = IccTestDataLut.CLUT_f32; + + public static readonly object[][] ClutTestData = + { + new object[] { CLUTPE_Grad, CLUTPE_ValGrad, 2, 3 }, + }; + + #endregion + + #region MultiProcessElement + + public static readonly IccMultiProcessElement MPE_ValMatrix = MatrixPE_ValGrad; + public static readonly IccMultiProcessElement MPE_ValCLUT = CLUTPE_ValGrad; + public static readonly IccMultiProcessElement MPE_ValCurve = CurvePE_ValGrad; + public static readonly IccMultiProcessElement MPE_ValbACS = new IccBAcsProcessElement(3, 3); + public static readonly IccMultiProcessElement MPE_ValeACS = new IccEAcsProcessElement(3, 3); + + public static readonly byte[] MPE_Matrix = ArrayHelper.Concat + ( + new byte[] + { + 0x6D, 0x61, 0x74, 0x66, + 0x00, 0x03, + 0x00, 0x03, + }, + MatrixPE_Grad + ); + + public static readonly byte[] MPE_CLUT = ArrayHelper.Concat + ( + new byte[] + { + 0x63, 0x6C, 0x75, 0x74, + 0x00, 0x02, + 0x00, 0x03, + }, + CLUTPE_Grad + ); + + public static readonly byte[] MPE_Curve = ArrayHelper.Concat + ( + new byte[] + { + 0x6D, 0x66, 0x6C, 0x74, + 0x00, 0x03, + 0x00, 0x03, + }, + CurvePE_Grad + ); + + public static readonly byte[] MPE_bACS = + { + 0x62, 0x41, 0x43, 0x53, + 0x00, 0x03, + 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly byte[] MPE_eACS = + { + 0x65, 0x41, 0x43, 0x53, + 0x00, 0x03, + 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly object[][] MultiProcessElementTestData = + { + new object[] { MPE_Matrix, MPE_ValMatrix }, + new object[] { MPE_CLUT, MPE_ValCLUT }, + new object[] { MPE_Curve, MPE_ValCurve }, + new object[] { MPE_bACS, MPE_ValbACS }, + new object[] { MPE_eACS, MPE_ValeACS }, + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs new file mode 100644 index 000000000..fdbcb3127 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs @@ -0,0 +1,412 @@ +namespace ImageSharp.Tests +{ + using System; + using System.Globalization; + using System.Numerics; + + internal static class IccTestDataNonPrimitives + { + #region DateTime + + public static readonly DateTime DateTime_ValMin = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Utc); + public static readonly DateTime DateTime_ValMax = new DateTime(9999, 12, 31, 23, 59, 59, DateTimeKind.Utc); + public static readonly DateTime DateTime_ValRand1 = new DateTime(1990, 11, 26, 3, 19, 47, DateTimeKind.Utc); + + public static readonly byte[] DateTime_Min = + { + 0x00, 0x01, // Year 1 + 0x00, 0x01, // Month 1 + 0x00, 0x01, // Day 1 + 0x00, 0x00, // Hour 0 + 0x00, 0x00, // Minute 0 + 0x00, 0x00, // Second 0 + }; + + public static readonly byte[] DateTime_Max = + { + 0x27, 0x0F, // Year 9999 + 0x00, 0x0C, // Month 12 + 0x00, 0x1F, // Day 31 + 0x00, 0x17, // Hour 23 + 0x00, 0x3B, // Minute 59 + 0x00, 0x3B, // Second 59 + }; + + public static readonly byte[] DateTime_Invalid = + { + 0xFF, 0xFF, // Year 65535 + 0x00, 0x0E, // Month 14 + 0x00, 0x21, // Day 33 + 0x00, 0x19, // Hour 25 + 0x00, 0x3D, // Minute 61 + 0x00, 0x3D, // Second 61 + }; + + public static readonly byte[] DateTime_Rand1 = + { + 0x07, 0xC6, // Year 1990 + 0x00, 0x0B, // Month 11 + 0x00, 0x1A, // Day 26 + 0x00, 0x03, // Hour 3 + 0x00, 0x13, // Minute 19 + 0x00, 0x2F, // Second 47 + }; + + public static readonly object[][] DateTimeTestData = + { + new object[] { DateTime_Min, DateTime_ValMin }, + new object[] { DateTime_Max, DateTime_ValMax }, + new object[] { DateTime_Rand1, DateTime_ValRand1 }, + }; + + #endregion + + #region VersionNumber + + public static readonly Version VersionNumber_ValMin = new Version(0, 0, 0); + public static readonly Version VersionNumber_Val211 = new Version(2, 1, 1); + public static readonly Version VersionNumber_Val430 = new Version(4, 3, 0); + public static readonly Version VersionNumber_ValMax = new Version(255, 15, 15); + + public static readonly byte[] VersionNumber_Min = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] VersionNumber_211 = { 0x02, 0x11, 0x00, 0x00 }; + public static readonly byte[] VersionNumber_430 = { 0x04, 0x30, 0x00, 0x00 }; + public static readonly byte[] VersionNumber_Max = { 0xFF, 0xFF, 0x00, 0x00 }; + + public static readonly object[][] VersionNumberTestData = + { + new object[] { VersionNumber_Min, VersionNumber_ValMin }, + new object[] { VersionNumber_211, VersionNumber_Val211 }, + new object[] { VersionNumber_430, VersionNumber_Val430 }, + new object[] { VersionNumber_Max, VersionNumber_ValMax }, + }; + + #endregion + + #region XyzNumber + + public static readonly Vector3 XyzNumber_ValMin = new Vector3(IccTestDataPrimitives.Fix16_ValMin, IccTestDataPrimitives.Fix16_ValMin, IccTestDataPrimitives.Fix16_ValMin); + public static readonly Vector3 XyzNumber_Val0 = new Vector3(0, 0, 0); + public static readonly Vector3 XyzNumber_Val1 = new Vector3(1, 1, 1); + public static readonly Vector3 XyzNumber_ValVar1 = new Vector3(1, 2, 3); + public static readonly Vector3 XyzNumber_ValVar2 = new Vector3(4, 5, 6); + public static readonly Vector3 XyzNumber_ValVar3 = new Vector3(7, 8, 9); + public static readonly Vector3 XyzNumber_ValMax = new Vector3(IccTestDataPrimitives.Fix16_ValMax, IccTestDataPrimitives.Fix16_ValMax, IccTestDataPrimitives.Fix16_ValMax); + + public static readonly byte[] XyzNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_Min, IccTestDataPrimitives.Fix16_Min, IccTestDataPrimitives.Fix16_Min); + public static readonly byte[] XyzNumber_0 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0); + public static readonly byte[] XyzNumber_1 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_1); + public static readonly byte[] XyzNumber_Var1 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_3); + public static readonly byte[] XyzNumber_Var2 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_5, IccTestDataPrimitives.Fix16_6); + public static readonly byte[] XyzNumber_Var3 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_7, IccTestDataPrimitives.Fix16_8, IccTestDataPrimitives.Fix16_9); + public static readonly byte[] XyzNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_Max, IccTestDataPrimitives.Fix16_Max, IccTestDataPrimitives.Fix16_Max); + + public static readonly object[][] XyzNumberTestData = + { + new object[] { XyzNumber_Min, XyzNumber_ValMin }, + new object[] { XyzNumber_0, XyzNumber_Val0 }, + new object[] { XyzNumber_Var1, XyzNumber_ValVar1 }, + new object[] { XyzNumber_Max, XyzNumber_ValMax }, + }; + + #endregion + + #region ProfileId + + public static readonly IccProfileId ProfileId_ValMin = new IccProfileId(0, 0, 0, 0); + public static readonly IccProfileId ProfileId_ValRand = new IccProfileId(IccTestDataPrimitives.UInt32_ValRand1, IccTestDataPrimitives.UInt32_ValRand2, IccTestDataPrimitives.UInt32_ValRand3, IccTestDataPrimitives.UInt32_ValRand4); + public static readonly IccProfileId ProfileId_ValMax = new IccProfileId(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue); + + public static readonly byte[] ProfileId_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0); + public static readonly byte[] ProfileId_Rand = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Rand1, IccTestDataPrimitives.UInt32_Rand2, IccTestDataPrimitives.UInt32_Rand3, IccTestDataPrimitives.UInt32_Rand4); + public static readonly byte[] ProfileId_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max); + + public static readonly object[][] ProfileIdTestData = + { + new object[] { ProfileId_Min, ProfileId_ValMin }, + new object[] { ProfileId_Rand, ProfileId_ValRand }, + new object[] { ProfileId_Max, ProfileId_ValMax }, + }; + + #endregion + + #region PositionNumber + + public static readonly IccPositionNumber PositionNumber_ValMin = new IccPositionNumber(0, 0); + public static readonly IccPositionNumber PositionNumber_ValRand = new IccPositionNumber(IccTestDataPrimitives.UInt32_ValRand1, IccTestDataPrimitives.UInt32_ValRand2); + public static readonly IccPositionNumber PositionNumber_ValMax = new IccPositionNumber(uint.MaxValue, uint.MaxValue); + + public static readonly byte[] PositionNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0); + public static readonly byte[] PositionNumber_Rand = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Rand1, IccTestDataPrimitives.UInt32_Rand2); + public static readonly byte[] PositionNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max); + + public static readonly object[][] PositionNumberTestData = + { + new object[] { PositionNumber_Min, PositionNumber_ValMin }, + new object[] { PositionNumber_Rand, PositionNumber_ValRand }, + new object[] { PositionNumber_Max, PositionNumber_ValMax }, + }; + + #endregion + + #region ResponseNumber + + public static readonly IccResponseNumber ResponseNumber_ValMin = new IccResponseNumber(0, IccTestDataPrimitives.Fix16_ValMin); + public static readonly IccResponseNumber ResponseNumber_Val1 = new IccResponseNumber(1, 1); + public static readonly IccResponseNumber ResponseNumber_Val2 = new IccResponseNumber(2, 2); + public static readonly IccResponseNumber ResponseNumber_Val3 = new IccResponseNumber(3, 3); + public static readonly IccResponseNumber ResponseNumber_Val4 = new IccResponseNumber(4, 4); + public static readonly IccResponseNumber ResponseNumber_Val5 = new IccResponseNumber(5, 5); + public static readonly IccResponseNumber ResponseNumber_Val6 = new IccResponseNumber(6, 6); + public static readonly IccResponseNumber ResponseNumber_Val7 = new IccResponseNumber(7, 7); + public static readonly IccResponseNumber ResponseNumber_Val8 = new IccResponseNumber(8, 8); + public static readonly IccResponseNumber ResponseNumber_Val9 = new IccResponseNumber(9, 9); + public static readonly IccResponseNumber ResponseNumber_ValMax = new IccResponseNumber(ushort.MaxValue, IccTestDataPrimitives.Fix16_ValMax); + + public static readonly byte[] ResponseNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_0, IccTestDataPrimitives.Fix16_Min); + public static readonly byte[] ResponseNumber_1 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.Fix16_1); + public static readonly byte[] ResponseNumber_2 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.Fix16_2); + public static readonly byte[] ResponseNumber_3 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.Fix16_3); + public static readonly byte[] ResponseNumber_4 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_4, IccTestDataPrimitives.Fix16_4); + public static readonly byte[] ResponseNumber_5 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_5, IccTestDataPrimitives.Fix16_5); + public static readonly byte[] ResponseNumber_6 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_6, IccTestDataPrimitives.Fix16_6); + public static readonly byte[] ResponseNumber_7 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_7, IccTestDataPrimitives.Fix16_7); + public static readonly byte[] ResponseNumber_8 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_8, IccTestDataPrimitives.Fix16_8); + public static readonly byte[] ResponseNumber_9 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_9, IccTestDataPrimitives.Fix16_9); + public static readonly byte[] ResponseNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_Max, IccTestDataPrimitives.Fix16_Max); + + public static readonly object[][] ResponseNumberTestData = + { + new object[] { ResponseNumber_Min, ResponseNumber_ValMin }, + new object[] { ResponseNumber_1, ResponseNumber_Val1 }, + new object[] { ResponseNumber_4, ResponseNumber_Val4 }, + new object[] { ResponseNumber_Max, ResponseNumber_ValMax }, + }; + + #endregion + + #region NamedColor + + public static readonly IccNamedColor NamedColor_ValMin = new IccNamedColor + ( + ArrayHelper.Fill('A', 31), + new ushort[] { 0, 0, 0 }, + new ushort[] { 0, 0, 0 } + ); + public static readonly IccNamedColor NamedColor_ValRand = new IccNamedColor + ( + ArrayHelper.Fill('5', 31), + new ushort[] { 10794, 10794, 10794 }, + new ushort[] { 17219, 17219, 17219, 17219, 17219 } + ); + public static readonly IccNamedColor NamedColor_ValMax = new IccNamedColor + ( + ArrayHelper.Fill('4', 31), + new ushort[] { ushort.MaxValue, ushort.MaxValue, ushort.MaxValue }, + new ushort[] { ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue } + ); + + public static readonly byte[] NamedColor_Min = CreateNamedColor(3, 0x41, 0x00, 0x00); + public static readonly byte[] NamedColor_Rand = CreateNamedColor(5, 0x35, 42, 67); + public static readonly byte[] NamedColor_Max = CreateNamedColor(4, 0x34, 0xFF, 0xFF); + + private static byte[] CreateNamedColor(int devCoordCount, byte name, byte PCS, byte device) + { + byte[] data = new byte[32 + 6 + devCoordCount * 2]; + for (int i = 0; i < data.Length; i++) + { + if (i < 31) { data[i] = name; } // Name + else if (i == 31) { data[i] = 0x00; } // Name null terminator + else if (i < 32 + 6) { data[i] = PCS; } // PCS Coordinates + else { data[i] = device; } // Device Coordinates + } + return data; + } + + public static readonly object[][] NamedColorTestData = + { + new object[] { NamedColor_Min, NamedColor_ValMin, 3u }, + new object[] { NamedColor_Rand, NamedColor_ValRand, 5u }, + new object[] { NamedColor_Max, NamedColor_ValMax, 4u }, + }; + + #endregion + + #region ProfileDescription + + private static readonly CultureInfo CultureEnUs = new CultureInfo("en-US"); + private static readonly CultureInfo CultureDeAT = new CultureInfo("de-AT"); + + private static readonly IccLocalizedString LocalizedString_Rand1 = new IccLocalizedString(CultureEnUs, IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand2 = new IccLocalizedString(CultureDeAT, IccTestDataPrimitives.Unicode_ValRand3); + + private static readonly IccLocalizedString[] LocalizedString_RandArr1 = new IccLocalizedString[] + { + LocalizedString_Rand1, + LocalizedString_Rand2, + }; + private static readonly IccLocalizedString[] LocalizedString_RandArr2 = new IccLocalizedString[] + { + LocalizedString_Rand2, + LocalizedString_Rand1, + }; + + private static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr1); + private static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + + new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 + + new byte[] { (byte)'d', (byte)'e', (byte)'A', (byte)'T' }, + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 + + IccTestDataPrimitives.Unicode_Rand2, + IccTestDataPrimitives.Unicode_Rand3 + ); + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry + ( + IccTestDataPrimitives.Ascii_ValRand, IccTestDataPrimitives.Unicode_ValRand1, ArrayHelper.Fill('A', 66), + 1701729619, 2 + ); + public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat + ( + new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 + IccTestDataPrimitives.Ascii_Rand, + new byte[] { 0x00 }, // Null terminator + + new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, + new byte[] { 0x00, 0x00, 0x00, 0x07 }, // 7 + IccTestDataPrimitives.Unicode_Rand2, + new byte[] { 0x00, 0x00 }, // Null terminator + + new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 + ArrayHelper.Fill((byte)0x41, 66), + new byte[] { 0x00 } // Null terminator + ); + + public static readonly IccProfileDescription ProfileDescription_ValRand1 = new IccProfileDescription + ( + 1, 2, + IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, + IccProfileTag.ProfileDescription, + MultiLocalizedUnicode_Val.Texts, + MultiLocalizedUnicode_Val.Texts + ); + + public static readonly IccProfileDescription ProfileDescription_ValRand2 = new IccProfileDescription + ( + 1, 2, + IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, + IccProfileTag.ProfileDescription, + new IccLocalizedString[] { LocalizedString_Rand1 }, + new IccLocalizedString[] { LocalizedString_Rand1 } + ); + + public static readonly byte[] ProfileDescription_Rand1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + new byte[] { 0, 0, 0, 0, 0, 0, 0, 10 }, + new byte[] { 0x64, 0x65, 0x73, 0x63 }, + + new byte[] { 0x6D, 0x6C, 0x75, 0x63 }, + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + MultiLocalizedUnicode_Arr, + new byte[] { 0x6D, 0x6C, 0x75, 0x63 }, + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + MultiLocalizedUnicode_Arr + ); + + public static readonly byte[] ProfileDescription_Rand2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + new byte[] { 0, 0, 0, 0, 0, 0, 0, 10 }, + new byte[] { 0x64, 0x65, 0x73, 0x63 }, + + new byte[] { 0x64, 0x65, 0x73, 0x63 }, + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + TextDescription_Arr1, + new byte[] { 0x64, 0x65, 0x73, 0x63 }, + new byte[] { 0x00, 0x00, 0x00, 0x00 }, + TextDescription_Arr1 + ); + + public static readonly object[][] ProfileDescriptionReadTestData = + { + new object[] { ProfileDescription_Rand1, ProfileDescription_ValRand1 }, + new object[] { ProfileDescription_Rand2, ProfileDescription_ValRand2 }, + }; + + public static readonly object[][] ProfileDescriptionWriteTestData = + { + new object[] { ProfileDescription_Rand1, ProfileDescription_ValRand1 }, + }; + + #endregion + + #region ColorantTableEntry + + public static readonly IccColorantTableEntry ColorantTableEntry_ValRand1 = new IccColorantTableEntry(ArrayHelper.Fill('A', 31), 1, 2, 3); + public static readonly IccColorantTableEntry ColorantTableEntry_ValRand2 = new IccColorantTableEntry(ArrayHelper.Fill('4', 31), 4, 5, 6); + + public static readonly byte[] ColorantTableEntry_Rand1 = ArrayHelper.Concat + ( + ArrayHelper.Fill((byte)0x41, 31), + new byte[1], // null terminator + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3 + ); + + public static readonly byte[] ColorantTableEntry_Rand2 = ArrayHelper.Concat + ( + ArrayHelper.Fill((byte)0x34, 31), + new byte[1], // null terminator + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_6 + ); + + public static readonly object[][] ColorantTableEntryTestData = + { + new object[] { ColorantTableEntry_Rand1, ColorantTableEntry_ValRand1 }, + new object[] { ColorantTableEntry_Rand2, ColorantTableEntry_ValRand2 }, + }; + + #endregion + + #region ScreeningChannel + + public static readonly IccScreeningChannel ScreeningChannel_ValRand1 = new IccScreeningChannel(4, 6, IccScreeningSpotType.Cross); + public static readonly IccScreeningChannel ScreeningChannel_ValRand2 = new IccScreeningChannel(8, 5, IccScreeningSpotType.Diamond); + + public static readonly byte[] ScreeningChannel_Rand1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_6, + IccTestDataPrimitives.Int32_7 + ); + + public static readonly byte[] ScreeningChannel_Rand2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_8, + IccTestDataPrimitives.Fix16_5, + IccTestDataPrimitives.Int32_3 + ); + + public static readonly object[][] ScreeningChannelTestData = + { + new object[] { ScreeningChannel_Rand1, ScreeningChannel_ValRand1 }, + new object[] { ScreeningChannel_Rand2, ScreeningChannel_ValRand2 }, + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs new file mode 100644 index 000000000..fcfa2d0d7 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs @@ -0,0 +1,332 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + internal static class IccTestDataPrimitives + { + #region UInt16 + + public static readonly byte[] UInt16_0 = { 0x00, 0x00 }; + public static readonly byte[] UInt16_1 = { 0x00, 0x01 }; + public static readonly byte[] UInt16_2 = { 0x00, 0x02 }; + public static readonly byte[] UInt16_3 = { 0x00, 0x03 }; + public static readonly byte[] UInt16_4 = { 0x00, 0x04 }; + public static readonly byte[] UInt16_5 = { 0x00, 0x05 }; + public static readonly byte[] UInt16_6 = { 0x00, 0x06 }; + public static readonly byte[] UInt16_7 = { 0x00, 0x07 }; + public static readonly byte[] UInt16_8 = { 0x00, 0x08 }; + public static readonly byte[] UInt16_9 = { 0x00, 0x09 }; + public static readonly byte[] UInt16_32768 = { 0x80, 0x00 }; + public static readonly byte[] UInt16_Max = { 0xFF, 0xFF }; + + #endregion + + #region Int16 + + public static readonly byte[] Int16_Min = { 0x80, 0x00 }; + public static readonly byte[] Int16_0 = { 0x00, 0x00 }; + public static readonly byte[] Int16_1 = { 0x00, 0x01 }; + public static readonly byte[] Int16_2 = { 0x00, 0x02 }; + public static readonly byte[] Int16_3 = { 0x00, 0x03 }; + public static readonly byte[] Int16_4 = { 0x00, 0x04 }; + public static readonly byte[] Int16_5 = { 0x00, 0x05 }; + public static readonly byte[] Int16_6 = { 0x00, 0x06 }; + public static readonly byte[] Int16_7 = { 0x00, 0x07 }; + public static readonly byte[] Int16_8 = { 0x00, 0x08 }; + public static readonly byte[] Int16_9 = { 0x00, 0x09 }; + public static readonly byte[] Int16_Max = { 0x7F, 0xFF }; + + #endregion + + #region UInt32 + + public static readonly byte[] UInt32_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] UInt32_1 = { 0x00, 0x00, 0x00, 0x01 }; + public static readonly byte[] UInt32_2 = { 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] UInt32_3 = { 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] UInt32_4 = { 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] UInt32_5 = { 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] UInt32_6 = { 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] UInt32_7 = { 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] UInt32_8 = { 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] UInt32_9 = { 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] UInt32_Max = { 0xFF, 0xFF, 0xFF, 0xFF }; + + + public static readonly uint UInt32_ValRand1 = 1749014123; + public static readonly uint UInt32_ValRand2 = 3870560989; + public static readonly uint UInt32_ValRand3 = 1050090334; + public static readonly uint UInt32_ValRand4 = 3550252874; + + public static readonly byte[] UInt32_Rand1 = { 0x68, 0x3F, 0xD6, 0x6B }; + public static readonly byte[] UInt32_Rand2 = { 0xE6, 0xB4, 0x12, 0xDD }; + public static readonly byte[] UInt32_Rand3 = { 0x3E, 0x97, 0x1B, 0x5E }; + public static readonly byte[] UInt32_Rand4 = { 0xD3, 0x9C, 0x8F, 0x4A }; + + #endregion + + #region Int32 + + public static readonly byte[] Int32_Min = { 0x80, 0x00, 0x00, 0x00 }; + public static readonly byte[] Int32_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Int32_1 = { 0x00, 0x00, 0x00, 0x01 }; + public static readonly byte[] Int32_2 = { 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] Int32_3 = { 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] Int32_4 = { 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] Int32_5 = { 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] Int32_6 = { 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] Int32_7 = { 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] Int32_8 = { 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] Int32_9 = { 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] Int32_Max = { 0x7F, 0xFF, 0xFF, 0xFF }; + + #endregion + + #region UInt64 + + public static readonly byte[] UInt64_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] UInt64_1 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; + public static readonly byte[] UInt64_2 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] UInt64_3 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] UInt64_4 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] UInt64_5 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] UInt64_6 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] UInt64_7 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] UInt64_8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] UInt64_9 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] UInt64_Max = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + #endregion + + #region Int64 + + public static readonly byte[] Int64_Min = { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Int64_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Int64_1 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; + public static readonly byte[] Int64_2 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] Int64_3 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] Int64_4 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] Int64_5 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] Int64_6 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] Int64_7 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] Int64_8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] Int64_9 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] Int64_Max = { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + #endregion + + #region Single + + public static readonly byte[] Single_Min = { 0xFF, 0x7F, 0xFF, 0xFF }; + public static readonly byte[] Single_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Single_1 = { 0x3F, 0x80, 0x00, 0x00 }; + public static readonly byte[] Single_2 = { 0x40, 0x00, 0x00, 0x00 }; + public static readonly byte[] Single_3 = { 0x40, 0x40, 0x00, 0x00 }; + public static readonly byte[] Single_4 = { 0x40, 0x80, 0x00, 0x00 }; + public static readonly byte[] Single_5 = { 0x40, 0xA0, 0x00, 0x00 }; + public static readonly byte[] Single_6 = { 0x40, 0xC0, 0x00, 0x00 }; + public static readonly byte[] Single_7 = { 0x40, 0xE0, 0x00, 0x00 }; + public static readonly byte[] Single_8 = { 0x41, 0x00, 0x00, 0x00 }; + public static readonly byte[] Single_9 = { 0x41, 0x10, 0x00, 0x00 }; + public static readonly byte[] Single_Max = { 0x7F, 0x7F, 0xFF, 0xFF }; + + #endregion + + #region Double + + public static readonly byte[] Double_Min = { 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + public static readonly byte[] Double_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Double_1 = { 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Double_Max = { 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + #endregion + + #region Fix16 + + public const float Fix16_ValMin = short.MinValue; + public const float Fix16_ValMax = short.MaxValue + 65535f / 65536f; + + public static readonly byte[] Fix16_Min = { 0x80, 0x00, 0x00, 0x00 }; + public static readonly byte[] Fix16_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] Fix16_1 = { 0x00, 0x01, 0x00, 0x00 }; + public static readonly byte[] Fix16_2 = { 0x00, 0x02, 0x00, 0x00 }; + public static readonly byte[] Fix16_3 = { 0x00, 0x03, 0x00, 0x00 }; + public static readonly byte[] Fix16_4 = { 0x00, 0x04, 0x00, 0x00 }; + public static readonly byte[] Fix16_5 = { 0x00, 0x05, 0x00, 0x00 }; + public static readonly byte[] Fix16_6 = { 0x00, 0x06, 0x00, 0x00 }; + public static readonly byte[] Fix16_7 = { 0x00, 0x07, 0x00, 0x00 }; + public static readonly byte[] Fix16_8 = { 0x00, 0x08, 0x00, 0x00 }; + public static readonly byte[] Fix16_9 = { 0x00, 0x09, 0x00, 0x00 }; + public static readonly byte[] Fix16_Max = { 0x7F, 0xFF, 0xFF, 0xFF }; + + public static readonly object[][] Fix16TestData = + { + new object[] { Fix16_Min, Fix16_ValMin }, + new object[] { Fix16_0, 0 }, + new object[] { Fix16_4, 4 }, + new object[] { Fix16_Max, Fix16_ValMax }, + }; + + #endregion + + #region UFix16 + + public const float UFix16_ValMin = 0; + public const float UFix16_ValMax = ushort.MaxValue + 65535f / 65536f; + + public static readonly byte[] UFix16_0 = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] UFix16_1 = { 0x00, 0x01, 0x00, 0x00 }; + public static readonly byte[] UFix16_2 = { 0x00, 0x02, 0x00, 0x00 }; + public static readonly byte[] UFix16_3 = { 0x00, 0x03, 0x00, 0x00 }; + public static readonly byte[] UFix16_4 = { 0x00, 0x04, 0x00, 0x00 }; + public static readonly byte[] UFix16_5 = { 0x00, 0x05, 0x00, 0x00 }; + public static readonly byte[] UFix16_6 = { 0x00, 0x06, 0x00, 0x00 }; + public static readonly byte[] UFix16_7 = { 0x00, 0x07, 0x00, 0x00 }; + public static readonly byte[] UFix16_8 = { 0x00, 0x08, 0x00, 0x00 }; + public static readonly byte[] UFix16_9 = { 0x00, 0x09, 0x00, 0x00 }; + public static readonly byte[] UFix16_Max = { 0xFF, 0xFF, 0xFF, 0xFF }; + + public static readonly object[][] UFix16TestData = + { + new object[] { UFix16_0, 0 }, + new object[] { UFix16_4, 4 }, + new object[] { UFix16_Max, UFix16_ValMax }, + }; + + #endregion + + #region U1Fix15 + + public const float U1Fix15_ValMin = 0; + public const float U1Fix15_ValMax = 1f + 32767f / 32768f; + + public static readonly byte[] U1Fix15_0 = { 0x00, 0x00 }; + public static readonly byte[] U1Fix15_1 = { 0x80, 0x00 }; + public static readonly byte[] U1Fix15_Max = { 0xFF, 0xFF }; + + public static readonly object[][] U1Fix15TestData = + { + new object[] { U1Fix15_0, 0 }, + new object[] { U1Fix15_1, 1 }, + new object[] { U1Fix15_Max, U1Fix15_ValMax }, + }; + + #endregion + + #region UFix8 + + public const float UFix8_ValMin = 0; + public const float UFix8_ValMax = byte.MaxValue + 255f / 256f; + + public static readonly byte[] UFix8_0 = { 0x00, 0x00 }; + public static readonly byte[] UFix8_1 = { 0x01, 0x00 }; + public static readonly byte[] UFix8_2 = { 0x02, 0x00 }; + public static readonly byte[] UFix8_3 = { 0x03, 0x00 }; + public static readonly byte[] UFix8_4 = { 0x04, 0x00 }; + public static readonly byte[] UFix8_5 = { 0x05, 0x00 }; + public static readonly byte[] UFix8_6 = { 0x06, 0x00 }; + public static readonly byte[] UFix8_7 = { 0x07, 0x00 }; + public static readonly byte[] UFix8_8 = { 0x08, 0x00 }; + public static readonly byte[] UFix8_9 = { 0x09, 0x00 }; + public static readonly byte[] UFix8_Max = { 0xFF, 0xFF }; + + public static readonly object[][] UFix8TestData = + { + new object[] { UFix8_0, 0 }, + new object[] { UFix8_4, 4 }, + new object[] { UFix8_Max, UFix8_ValMax }, + }; + + #endregion + + #region ASCII String + + public const string Ascii_ValRand = "aBcdEf1234"; + public const string Ascii_ValRand1 = "Ecf3a"; + public const string Ascii_ValRand2 = "2Bd4c"; + public const string Ascii_ValRand3 = "cad14"; + public const string Ascii_ValRand4 = "fd4E1"; + public const string Ascii_ValRandLength4 = "aBcd"; + public const string Ascii_ValNullRand = "aBcd\0Ef\0123"; + + public static readonly byte[] Ascii_Rand = { 97, 66, 99, 100, 69, 102, 49, 50, 51, 52 }; + public static readonly byte[] Ascii_Rand1 = { 69, 99, 102, 51, 97 }; + public static readonly byte[] Ascii_Rand2 = { 50, 66, 100, 52, 99 }; + public static readonly byte[] Ascii_Rand3 = { 99, 97, 100, 49, 52 }; + public static readonly byte[] Ascii_Rand4 = { 102, 100, 52, 69, 49 }; + public static readonly byte[] Ascii_RandLength4 = { 97, 66, 99, 100 }; + public static readonly byte[] Ascii_PaddedRand = { 97, 66, 99, 100, 69, 102, 49, 50, 51, 52, 0, 0, 0, 0 }; + public static readonly byte[] Ascii_NullRand = { 97, 66, 99, 100, 0, 69, 102, 0, 49, 50, 51 }; + + public const int Ascii_Rand_Length = 10; + public const int Ascii_PaddedRand_Length = 14; + public const int Ascii_NullRand_Length = 11; + public const int Ascii_NullRand_LengthNoNull = 4; + + public static readonly object[][] AsciiTestData = + { + new object[] { Ascii_Rand, Ascii_Rand_Length, Ascii_ValRand }, + new object[] { Ascii_Rand, 4, Ascii_ValRandLength4 }, + new object[] { Ascii_NullRand, Ascii_NullRand_LengthNoNull, Ascii_ValRandLength4 }, + }; + + public static readonly object[][] AsciiWriteTestData = + { + new object[] { Ascii_Rand, Ascii_ValRand }, + new object[] { Ascii_NullRand, Ascii_ValNullRand }, + }; + + public static readonly object[][] AsciiPaddingTestData = + { + new object[] { Ascii_PaddedRand, Ascii_PaddedRand_Length, Ascii_ValRand, true }, + new object[] { Ascii_RandLength4, 4, Ascii_ValRand, false }, + }; + + #endregion + + #region Unicode String + + public const string Unicode_ValRand1 = ".6Abäñ$€β𐐷𤭢"; + public const string Unicode_ValRand2 = ".6Abäñ"; + public const string Unicode_ValRand3 = "$€β𐐷𤭢"; + + public static readonly byte[] Unicode_Rand1 = + { + 0x00, 0x2e, // . + 0x00, 0x36, // 6 + 0x00, 0x41, // A + 0x00, 0x62, // b + 0x00, 0xe4, // ä + 0x00, 0xf1, // ñ + 0x00, 0x24, // $ + 0x20, 0xAC, // € + 0x03, 0xb2, // β + 0xD8, 0x01, 0xDC, 0x37, // 𐐷 + 0xD8, 0x52, 0xDF, 0x62, // 𤭢 + }; + + public static readonly byte[] Unicode_Rand2 = + { + 0x00, 0x2e, // . + 0x00, 0x36, // 6 + 0x00, 0x41, // A + 0x00, 0x62, // b + 0x00, 0xe4, // ä + 0x00, 0xf1, // ñ + }; + + public static readonly byte[] Unicode_Rand3 = + { + 0x00, 0x24, // $ + 0x20, 0xAC, // € + 0x03, 0xb2, // β + 0xD8, 0x01, 0xDC, 0x37, // 𐐷 + 0xD8, 0x52, 0xDF, 0x62, // 𤭢 + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs new file mode 100644 index 000000000..20fff50a8 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs @@ -0,0 +1,92 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using System; +using System.Numerics; + +namespace ImageSharp.Tests +{ + internal static class IccTestDataProfiles + { + public static readonly IccProfileHeader Header_Random_Write = new IccProfileHeader + { + Class = IccProfileClass.DisplayDevice, + CmmType = "abcd", + CreationDate = new DateTime(1990, 11, 26, 7, 21, 42), + CreatorSignature = "dcba", + DataColorSpace = IccColorSpaceType.Rgb, + DeviceAttributes = IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.OpacityTransparent, + DeviceManufacturer = 123456789u, + DeviceModel = 987654321u, + FileSignature = "ijkl", // should be overwritten to "acsp" + Flags = IccProfileFlag.Embedded | IccProfileFlag.Independent, + Id = new IccProfileId(1, 2, 3, 4), // should be overwritten + PcsIlluminant = new Vector3(4, 5, 6), + PrimaryPlatformSignature = IccPrimaryPlatformType.MicrosoftCorporation, + ProfileConnectionSpace = IccColorSpaceType.CieXyz, + RenderingIntent = IccRenderingIntent.AbsoluteColorimetric, + Size = 562, // should be overwritten + Version = new Version(4, 3, 0), + }; + + public static readonly IccProfileHeader Header_Random_Read = new IccProfileHeader + { + Class = IccProfileClass.DisplayDevice, + CmmType = "abcd", + CreationDate = new DateTime(1990, 11, 26, 7, 21, 42), + CreatorSignature = "dcba", + DataColorSpace = IccColorSpaceType.Rgb, + DeviceAttributes = IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.OpacityTransparent, + DeviceManufacturer = 123456789u, + DeviceModel = 987654321u, + FileSignature = "acsp", + Flags = IccProfileFlag.Embedded | IccProfileFlag.Independent, +#if !NETSTANDARD1_1 + Id = new IccProfileId(2931428592, 418415738, 3086756963, 2237536530), +#else + Id = IccProfileId.Zero, +#endif + PcsIlluminant = new Vector3(4, 5, 6), + PrimaryPlatformSignature = IccPrimaryPlatformType.MicrosoftCorporation, + ProfileConnectionSpace = IccColorSpaceType.CieXyz, + RenderingIntent = IccRenderingIntent.AbsoluteColorimetric, + Size = 132, + Version = new Version(4, 3, 0), + }; + + public static readonly byte[] Header_Random_Array = + { + 0x00, 0x00, 0x00, 0x84, // Size (132) + 0x61, 0x62, 0x63, 0x64, // CmmType + 0x04, 0x30, 0x00, 0x00, // Version + 0x6D, 0x6E, 0x74, 0x72, // Class + 0x52, 0x47, 0x42, 0x20, // DataColorSpace + 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace + 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate + 0x61, 0x63, 0x73, 0x70, // FileSignature + 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature + 0x00, 0x00, 0x00, 0x01, // Flags + 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer + 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes + 0x00, 0x00, 0x00, 0x03, // RenderingIntent + 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant + 0x64, 0x63, 0x62, 0x61, // CreatorSignature + +#if !NETSTANDARD1_1 + 0xAE, 0xBA, 0x0C, 0xF0, 0x18, 0xF0, 0x84, 0x7A, 0xB7, 0xFC, 0x2C, 0x63, 0x85, 0x5E, 0x19, 0x12, // Id +#else + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Id +#endif + // Padding + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + // Nr of tag table entries (0) + 0x00, 0x00, 0x00, 0x00, + }; + } +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs new file mode 100644 index 000000000..769ec3a01 --- /dev/null +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs @@ -0,0 +1,1004 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Globalization; + using System.Numerics; + + internal static class IccTestDataTagDataEntry + { + #region TagDataEntry Header + + public static readonly IccTypeSignature TagDataEntryHeader_UnknownVal = IccTypeSignature.Unknown; + public static readonly byte[] TagDataEntryHeader_UnknownArr = + { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly IccTypeSignature TagDataEntryHeader_MultiLocalizedUnicodeVal = IccTypeSignature.MultiLocalizedUnicode; + public static readonly byte[] TagDataEntryHeader_MultiLocalizedUnicodeArr = + { + 0x6D, 0x6C, 0x75, 0x63, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly IccTypeSignature TagDataEntryHeader_CurveVal = IccTypeSignature.Curve; + public static readonly byte[] TagDataEntryHeader_CurveArr = + { + 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly object[][] TagDataEntryHeaderTestData = + { + new object[] { TagDataEntryHeader_UnknownArr, TagDataEntryHeader_UnknownVal }, + new object[] { TagDataEntryHeader_MultiLocalizedUnicodeArr, TagDataEntryHeader_MultiLocalizedUnicodeVal }, + new object[] { TagDataEntryHeader_CurveArr, TagDataEntryHeader_CurveVal }, + }; + + #endregion + + #region UnknownTagDataEntry + + public static readonly IccUnknownTagDataEntry Unknown_Val = new IccUnknownTagDataEntry(new byte[] { 0x00, 0x01, 0x02, 0x03 }); + public static readonly byte[] Unknown_Arr = { 0x00, 0x01, 0x02, 0x03 }; + + public static readonly object[][] UnknownTagDataEntryTestData = + { + new object[] { Unknown_Arr, Unknown_Val, 12u }, + }; + + #endregion + + #region ChromaticityTagDataEntry + + public static readonly IccChromaticityTagDataEntry Chromaticity_Val1 = new IccChromaticityTagDataEntry(IccColorantEncoding.ItuRBt709_2); + public static readonly byte[] Chromaticity_Arr1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_1, + + new byte[] { 0x00, 0x00, 0xA3, 0xD7 }, // 0.640 + new byte[] { 0x00, 0x00, 0x54, 0x7B }, // 0.330 + + new byte[] { 0x00, 0x00, 0x4C, 0xCD }, // 0.300 + new byte[] { 0x00, 0x00, 0x99, 0x9A }, // 0.600 + + new byte[] { 0x00, 0x00, 0x26, 0x66 }, // 0.150 + new byte[] { 0x00, 0x00, 0x0F, 0x5C } // 0.060 + ); + + public static readonly IccChromaticityTagDataEntry Chromaticity_Val2 = new IccChromaticityTagDataEntry + ( + new double[][] + { + new double[] { 1, 2 }, + new double[] { 3, 4 }, + } + ); + public static readonly byte[] Chromaticity_Arr2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_0, + + IccTestDataPrimitives.UFix16_1, + IccTestDataPrimitives.UFix16_2, + + IccTestDataPrimitives.UFix16_3, + IccTestDataPrimitives.UFix16_4 + ); + + /// + /// : channel count must be 3 for any enum other than + /// + public static readonly byte[] Chromaticity_ArrInvalid1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_1 + ); + + /// + /// : invalid enum value + /// + public static readonly byte[] Chromaticity_ArrInvalid2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_9 + ); + + public static readonly object[][] ChromaticityTagDataEntryTestData = + { + new object[] { Chromaticity_Arr1, Chromaticity_Val1 }, + new object[] { Chromaticity_Arr2, Chromaticity_Val2 }, + }; + + #endregion + + #region ColorantOrderTagDataEntry + + public static readonly IccColorantOrderTagDataEntry ColorantOrder_Val = new IccColorantOrderTagDataEntry(new byte[] { 0x00, 0x01, 0x02 }); + public static readonly byte[] ColorantOrder_Arr = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_3, new byte[] { 0x00, 0x01, 0x02 }); + + public static readonly object[][] ColorantOrderTagDataEntryTestData = + { + new object[] { ColorantOrder_Arr, ColorantOrder_Val }, + }; + + #endregion + + #region ColorantTableTagDataEntry + + public static readonly IccColorantTableTagDataEntry ColorantTable_Val = new IccColorantTableTagDataEntry + ( + new IccColorantTableEntry[] + { + IccTestDataNonPrimitives.ColorantTableEntry_ValRand1, + IccTestDataNonPrimitives.ColorantTableEntry_ValRand2 + } + ); + public static readonly byte[] ColorantTable_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + IccTestDataNonPrimitives.ColorantTableEntry_Rand1, + IccTestDataNonPrimitives.ColorantTableEntry_Rand2 + ); + + public static readonly object[][] ColorantTableTagDataEntryTestData = + { + new object[] { ColorantTable_Arr, ColorantTable_Val }, + }; + + #endregion + + #region CurveTagDataEntry + + public static readonly IccCurveTagDataEntry Curve_Val_0 = new IccCurveTagDataEntry(); + public static readonly byte[] Curve_Arr_0 = IccTestDataPrimitives.UInt32_0; + + public static readonly IccCurveTagDataEntry Curve_Val_1 = new IccCurveTagDataEntry(1f); + public static readonly byte[] Curve_Arr_1 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UFix8_1 + ); + + public static readonly IccCurveTagDataEntry Curve_Val_2 = new IccCurveTagDataEntry(new float[] { 1 / 65535f, 2 / 65535f, 3 / 65535f }); + public static readonly byte[] Curve_Arr_2 = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_3, + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3 + ); + + public static readonly object[][] CurveTagDataEntryTestData = + { + new object[] { Curve_Arr_0, Curve_Val_0 }, + new object[] { Curve_Arr_1, Curve_Val_1 }, + new object[] { Curve_Arr_2, Curve_Val_2 }, + }; + + #endregion + + #region DataTagDataEntry + + public static readonly IccDataTagDataEntry Data_ValNoASCII = new IccDataTagDataEntry + ( + new byte[] { 0x01, 0x02, 0x03, 0x04 }, + false + ); + public static readonly byte[] Data_ArrNoASCII = + { + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x03, 0x04 + }; + + public static readonly IccDataTagDataEntry Data_ValASCII = new IccDataTagDataEntry + ( + new byte[] { (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' }, + true + ); + public static readonly byte[] Data_ArrASCII = + { + 0x00, 0x00, 0x00, 0x01, + (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' + }; + + public static readonly object[][] DataTagDataEntryTestData = + { + new object[] { Data_ArrNoASCII, Data_ValNoASCII, 16u }, + new object[] { Data_ArrASCII, Data_ValASCII, 17u }, + }; + + #endregion + + #region DateTimeTagDataEntry + + public static readonly IccDateTimeTagDataEntry DateTime_Val = new IccDateTimeTagDataEntry(IccTestDataNonPrimitives.DateTime_ValRand1); + public static readonly byte[] DateTime_Arr = IccTestDataNonPrimitives.DateTime_Rand1; + + public static readonly object[][] DateTimeTagDataEntryTestData = + { + new object[] { DateTime_Arr, DateTime_Val }, + }; + + #endregion + + #region Lut16TagDataEntry + + public static readonly IccLut16TagDataEntry Lut16_Val = new IccLut16TagDataEntry + ( + new IccLut[] { IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad }, + IccTestDataLut.CLUT16_ValGrad, + new IccLut[] { IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad } + ); + public static readonly byte[] Lut16_Arr = ArrayHelper.Concat + ( + new byte[] { 0x02, 0x03, 0x03, 0x00 }, + IccTestDataMatrix.Fix16_2D_Identity, + new byte[] { 0x00, (byte)IccTestDataLut.LUT16_ValGrad.Values.Length, 0x00, (byte)IccTestDataLut.LUT16_ValGrad.Values.Length }, + + IccTestDataLut.LUT16_Grad, + IccTestDataLut.LUT16_Grad, + + IccTestDataLut.CLUT16_Grad, + + IccTestDataLut.LUT16_Grad, + IccTestDataLut.LUT16_Grad, + IccTestDataLut.LUT16_Grad + ); + + public static readonly object[][] Lut16TagDataEntryTestData = + { + new object[] { Lut16_Arr, Lut16_Val }, + }; + + #endregion + + #region Lut8TagDataEntry + + public static readonly IccLut8TagDataEntry Lut8_Val = new IccLut8TagDataEntry + ( + new IccLut[] { IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad }, + IccTestDataLut.CLUT8_ValGrad, + new IccLut[] { IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad } + ); + public static readonly byte[] Lut8_Arr = ArrayHelper.Concat + ( + new byte[] { 0x02, 0x03, 0x03, 0x00 }, + IccTestDataMatrix.Fix16_2D_Identity, + + IccTestDataLut.LUT8_Grad, + IccTestDataLut.LUT8_Grad, + + IccTestDataLut.CLUT8_Grad, + + IccTestDataLut.LUT8_Grad, + IccTestDataLut.LUT8_Grad, + IccTestDataLut.LUT8_Grad + ); + + public static readonly object[][] Lut8TagDataEntryTestData = + { + new object[] { Lut8_Arr, Lut8_Val }, + }; + + #endregion + + #region LutAToBTagDataEntry + + private static readonly byte[] CurveFull_0 = ArrayHelper.Concat + ( + TagDataEntryHeader_CurveArr, + Curve_Arr_0 + ); + private static readonly byte[] CurveFull_1 = ArrayHelper.Concat + ( + TagDataEntryHeader_CurveArr, + Curve_Arr_1 + ); + private static readonly byte[] CurveFull_2 = ArrayHelper.Concat + ( + TagDataEntryHeader_CurveArr, + Curve_Arr_2 + ); + + public static readonly IccLutAToBTagDataEntry LutAToB_Val = new IccLutAToBTagDataEntry + ( + new IccCurveTagDataEntry[] + { + Curve_Val_0, + Curve_Val_1, + Curve_Val_2, + }, + IccTestDataMatrix.Single_2DArray_ValGrad, + IccTestDataMatrix.Single_1DArray_ValGrad, + new IccCurveTagDataEntry[] + { + Curve_Val_1, + Curve_Val_2, + Curve_Val_0, + }, + IccTestDataLut.CLUT_Val16, + new IccCurveTagDataEntry[] + { + Curve_Val_2, + Curve_Val_1, + } + ); + public static readonly byte[] LutAToB_Arr = ArrayHelper.Concat + ( + new byte[] { 0x02, 0x03, 0x00, 0x00 }, + + new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 + new byte[] { 0x00, 0x00, 0x00, 0x50 }, // matrix: 80 + new byte[] { 0x00, 0x00, 0x00, 0x80 }, // m: 128 + new byte[] { 0x00, 0x00, 0x00, 0xB0 }, // clut: 176 + new byte[] { 0x00, 0x00, 0x00, 0xFC }, // a: 252 + + // B + CurveFull_0, // 12 bytes + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // Matrix + IccTestDataMatrix.Fix16_2D_Grad, // 36 bytes + IccTestDataMatrix.Fix16_1D_Grad, // 12 bytes + + // M + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_0, // 12 bytes + + // CLUT + IccTestDataLut.CLUT_16, // 74 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // A + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 } // Padding + ); + + public static readonly object[][] LutAToBTagDataEntryTestData = + { + new object[] { LutAToB_Arr, LutAToB_Val }, + }; + + #endregion + + #region LutBToATagDataEntry + + public static readonly IccLutBToATagDataEntry LutBToA_Val = new IccLutBToATagDataEntry + ( + new IccCurveTagDataEntry[] + { + Curve_Val_0, + Curve_Val_1, + }, + null, + null, + null, + IccTestDataLut.CLUT_Val16, + new IccCurveTagDataEntry[] + { + Curve_Val_2, + Curve_Val_1, + Curve_Val_0, + } + ); + public static readonly byte[] LutBToA_Arr = ArrayHelper.Concat + ( + new byte[] { 0x02, 0x03, 0x00, 0x00 }, + + new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // matrix: 0 + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // m: 0 + new byte[] { 0x00, 0x00, 0x00, 0x3C }, // clut: 60 + new byte[] { 0x00, 0x00, 0x00, 0x88 }, // a: 136 + + // B + CurveFull_0, //12 bytes + CurveFull_1, //14 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // CLUT + IccTestDataLut.CLUT_16, // 74 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // A + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_0 // 12 bytes + ); + + public static readonly object[][] LutBToATagDataEntryTestData = + { + new object[] { LutBToA_Arr, LutBToA_Val }, + }; + + #endregion + + #region MeasurementTagDataEntry + + public static readonly IccMeasurementTagDataEntry Measurement_Val = new IccMeasurementTagDataEntry + ( + IccStandardObserver.Cie1931Observer, IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccMeasurementGeometry.Degree0ToDOrDTo0, 1f, IccStandardIlluminant.D50 + ); + public static readonly byte[] Measurement_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UFix16_1, + IccTestDataPrimitives.UInt32_1 + ); + + public static readonly object[][] MeasurementTagDataEntryTestData = + { + new object[] { Measurement_Arr, Measurement_Val }, + }; + + #endregion + + #region MultiLocalizedUnicodeTagDataEntry + + private static readonly IccLocalizedString LocalizedString_Rand1 = new IccLocalizedString(new CultureInfo("en-US"), IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand2 = new IccLocalizedString(new CultureInfo("de-DE"), IccTestDataPrimitives.Unicode_ValRand3); + + private static readonly IccLocalizedString[] LocalizedString_RandArr1 = new IccLocalizedString[] + { + LocalizedString_Rand1, + LocalizedString_Rand2, + }; + private static readonly IccLocalizedString[] LocalizedString_RandArr2 = new IccLocalizedString[] + { + LocalizedString_Rand2, + LocalizedString_Rand1, + }; + + public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr1); + public static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + + new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, + new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 + new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 + + new byte[] { (byte)'d', (byte)'e', (byte)'D', (byte)'E' }, + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 + + IccTestDataPrimitives.Unicode_Rand2, + IccTestDataPrimitives.Unicode_Rand3 + ); + + public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestData = + { + new object[] { MultiLocalizedUnicode_Arr, MultiLocalizedUnicode_Val }, + }; + + #endregion + + #region MultiProcessElementsTagDataEntry + + public static readonly IccMultiProcessElementsTagDataEntry MultiProcessElements_Val = new IccMultiProcessElementsTagDataEntry + ( + new IccMultiProcessElement[] + { + IccTestDataMultiProcessElement.MPE_ValCLUT, + IccTestDataMultiProcessElement.MPE_ValCLUT, + } + ); + public static readonly byte[] MultiProcessElements_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt32_2, + + new byte[] { 0x00, 0x00, 0x00, 0x20 }, // 32 + new byte[] { 0x00, 0x00, 0x00, 0x84 }, // 132 + + new byte[] { 0x00, 0x00, 0x00, 0xA4 }, // 164 + new byte[] { 0x00, 0x00, 0x00, 0x84 }, // 132 + + IccTestDataMultiProcessElement.MPE_CLUT, + IccTestDataMultiProcessElement.MPE_CLUT + ); + + public static readonly object[][] MultiProcessElementsTagDataEntryTestData = + { + new object[] { MultiProcessElements_Arr, MultiProcessElements_Val }, + }; + + #endregion + + #region NamedColor2TagDataEntry + + public static readonly IccNamedColor2TagDataEntry NamedColor2_Val = new IccNamedColor2TagDataEntry + ( + 16909060, + ArrayHelper.Fill('A', 31), ArrayHelper.Fill('4', 31), + new IccNamedColor[] + { + IccTestDataNonPrimitives.NamedColor_ValMin, + IccTestDataNonPrimitives.NamedColor_ValMin + } + ); + public static readonly byte[] NamedColor2_Arr = ArrayHelper.Concat + ( + new byte[] { 0x01, 0x02, 0x03, 0x04 }, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_3, + ArrayHelper.Fill((byte)0x41, 31), + new byte[] { 0x00 }, + ArrayHelper.Fill((byte)0x34, 31), + new byte[] { 0x00 }, + IccTestDataNonPrimitives.NamedColor_Min, + IccTestDataNonPrimitives.NamedColor_Min + ); + + public static readonly object[][] NamedColor2TagDataEntryTestData = + { + new object[] { NamedColor2_Arr, NamedColor2_Val }, + }; + + #endregion + + #region ParametricCurveTagDataEntry + + public static readonly IccParametricCurveTagDataEntry ParametricCurve_Val = new IccParametricCurveTagDataEntry(IccTestDataCurves.Parametric_ValVar1); + public static readonly byte[] ParametricCurve_Arr = IccTestDataCurves.Parametric_Var1; + + public static readonly object[][] ParametricCurveTagDataEntryTestData = + { + new object[] { ParametricCurve_Arr, ParametricCurve_Val }, + }; + + #endregion + + #region ProfileSequenceDescTagDataEntry + + public static readonly IccProfileSequenceDescTagDataEntry ProfileSequenceDesc_Val = new IccProfileSequenceDescTagDataEntry + ( + new IccProfileDescription[] + { + IccTestDataNonPrimitives.ProfileDescription_ValRand1, + IccTestDataNonPrimitives.ProfileDescription_ValRand1 + } + ); + public static readonly byte[] ProfileSequenceDesc_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + IccTestDataNonPrimitives.ProfileDescription_Rand1, + IccTestDataNonPrimitives.ProfileDescription_Rand1 + ); + + public static readonly object[][] ProfileSequenceDescTagDataEntryTestData = + { + new object[] { ProfileSequenceDesc_Arr, ProfileSequenceDesc_Val }, + }; + + #endregion + + #region ProfileSequenceIdentifierTagDataEntry + + public static readonly IccProfileSequenceIdentifierTagDataEntry ProfileSequenceIdentifier_Val = new IccProfileSequenceIdentifierTagDataEntry + ( + new IccProfileSequenceIdentifier[] + { + new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileId_ValRand, LocalizedString_RandArr1), + new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileId_ValRand, LocalizedString_RandArr1), + } + ); + public static readonly byte[] ProfileSequenceIdentifier_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_2, + + new byte[] { 0x00, 0x00, 0x00, 0x1C }, // 28 + new byte[] { 0x00, 0x00, 0x00, 0x54 }, // 84 + + new byte[] { 0x00, 0x00, 0x00, 0x70 }, // 112 + new byte[] { 0x00, 0x00, 0x00, 0x54 }, // 84 + + IccTestDataNonPrimitives.ProfileId_Rand, // 16 bytes + TagDataEntryHeader_MultiLocalizedUnicodeArr, // 8 bytes + MultiLocalizedUnicode_Arr, // 58 bytes + new byte[] { 0x00, 0x00 }, // 2 bytes (padding) + + IccTestDataNonPrimitives.ProfileId_Rand, + TagDataEntryHeader_MultiLocalizedUnicodeArr, + MultiLocalizedUnicode_Arr, + new byte[] { 0x00, 0x00 } + ); + + public static readonly object[][] ProfileSequenceIdentifierTagDataEntryTestData = + { + new object[] { ProfileSequenceIdentifier_Arr, ProfileSequenceIdentifier_Val }, + }; + + #endregion + + #region ResponseCurveSet16TagDataEntry + + public static readonly IccResponseCurveSet16TagDataEntry ResponseCurveSet16_Val = new IccResponseCurveSet16TagDataEntry + ( + new IccResponseCurve[] + { + IccTestDataCurves.Response_ValGrad, + IccTestDataCurves.Response_ValGrad, + } + ); + public static readonly byte[] ResponseCurveSet16_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_2, + + new byte[] { 0x00, 0x00, 0x00, 0x14 }, // 20 + new byte[] { 0x00, 0x00, 0x00, 0x6C }, // 108 + + IccTestDataCurves.Response_Grad, // 88 bytes + IccTestDataCurves.Response_Grad // 88 bytes + ); + + public static readonly object[][] ResponseCurveSet16TagDataEntryTestData = + { + new object[] { ResponseCurveSet16_Arr, ResponseCurveSet16_Val }, + }; + + #endregion + + #region Fix16ArrayTagDataEntry + + public static readonly IccFix16ArrayTagDataEntry Fix16Array_Val = new IccFix16ArrayTagDataEntry(new float[] { 1 / 256f, 2 / 256f, 3 / 256f }); + public static readonly byte[] Fix16Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3 + ); + + public static readonly object[][] Fix16ArrayTagDataEntryTestData = + { + new object[] { Fix16Array_Arr, Fix16Array_Val, 20u }, + }; + + #endregion + + #region SignatureTagDataEntry + + public static readonly IccSignatureTagDataEntry Signature_Val = new IccSignatureTagDataEntry("ABCD"); + public static readonly byte[] Signature_Arr = { 0x41, 0x42, 0x43, 0x44, }; + + public static readonly object[][] SignatureTagDataEntryTestData = + { + new object[] { Signature_Arr, Signature_Val }, + }; + + #endregion + + #region TextTagDataEntry + + public static readonly IccTextTagDataEntry Text_Val = new IccTextTagDataEntry("ABCD"); + public static readonly byte[] Text_Arr = { 0x41, 0x42, 0x43, 0x44 }; + + public static readonly object[][] TextTagDataEntryTestData = + { + new object[] { Text_Arr, Text_Val, 12u }, + }; + + #endregion + + #region UFix16ArrayTagDataEntry + + public static readonly IccUFix16ArrayTagDataEntry UFix16Array_Val = new IccUFix16ArrayTagDataEntry(new float[] { 1, 2, 3 }); + public static readonly byte[] UFix16Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UFix16_1, + IccTestDataPrimitives.UFix16_2, + IccTestDataPrimitives.UFix16_3 + ); + + public static readonly object[][] UFix16ArrayTagDataEntryTestData = + { + new object[] { UFix16Array_Arr, UFix16Array_Val, 20u }, + }; + + #endregion + + #region UInt16ArrayTagDataEntry + + public static readonly IccUInt16ArrayTagDataEntry UInt16Array_Val = new IccUInt16ArrayTagDataEntry(new ushort[] { 1, 2, 3 }); + public static readonly byte[] UInt16Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3 + ); + + public static readonly object[][] UInt16ArrayTagDataEntryTestData = + { + new object[] { UInt16Array_Arr, UInt16Array_Val, 14u }, + }; + + #endregion + + #region UInt32ArrayTagDataEntry + + public static readonly IccUInt32ArrayTagDataEntry UInt32Array_Val = new IccUInt32ArrayTagDataEntry(new uint[] { 1, 2, 3 }); + public static readonly byte[] UInt32Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_3 + ); + + public static readonly object[][] UInt32ArrayTagDataEntryTestData = + { + new object[] { UInt32Array_Arr, UInt32Array_Val, 20u }, + }; + + #endregion + + #region UInt64ArrayTagDataEntry + + public static readonly IccUInt64ArrayTagDataEntry UInt64Array_Val = new IccUInt64ArrayTagDataEntry(new ulong[] { 1, 2, 3 }); + public static readonly byte[] UInt64Array_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt64_1, + IccTestDataPrimitives.UInt64_2, + IccTestDataPrimitives.UInt64_3 + ); + + public static readonly object[][] UInt64ArrayTagDataEntryTestData = + { + new object[] { UInt64Array_Arr, UInt64Array_Val, 32u }, + }; + + #endregion + + #region UInt8ArrayTagDataEntry + + public static readonly IccUInt8ArrayTagDataEntry UInt8Array_Val = new IccUInt8ArrayTagDataEntry(new byte[] { 1, 2, 3 }); + public static readonly byte[] UInt8Array_Arr = { 1, 2, 3 }; + + public static readonly object[][] UInt8ArrayTagDataEntryTestData = + { + new object[] { UInt8Array_Arr, UInt8Array_Val, 11u }, + }; + + #endregion + + #region ViewingConditionsTagDataEntry + + public static readonly IccViewingConditionsTagDataEntry ViewingConditions_Val = new IccViewingConditionsTagDataEntry + ( + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccTestDataNonPrimitives.XyzNumber_ValVar2, + IccStandardIlluminant.D50 + ); + public static readonly byte[] ViewingConditions_Arr = ArrayHelper.Concat + ( + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataNonPrimitives.XyzNumber_Var2, + IccTestDataPrimitives.UInt32_1 + ); + + public static readonly object[][] ViewingConditionsTagDataEntryTestData = + { + new object[] { ViewingConditions_Arr, ViewingConditions_Val }, + }; + + #endregion + + #region XYZTagDataEntry + + public static readonly IccXyzTagDataEntry XYZ_Val = new IccXyzTagDataEntry(new Vector3[] + { + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccTestDataNonPrimitives.XyzNumber_ValVar2, + IccTestDataNonPrimitives.XyzNumber_ValVar3, + }); + public static readonly byte[] XYZ_Arr = ArrayHelper.Concat + ( + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataNonPrimitives.XyzNumber_Var2, + IccTestDataNonPrimitives.XyzNumber_Var3 + ); + + public static readonly object[][] XYZTagDataEntryTestData = + { + new object[] { XYZ_Arr, XYZ_Val, 44u }, + }; + + #endregion + + #region TextDescriptionTagDataEntry + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry + ( + IccTestDataPrimitives.Ascii_ValRand, IccTestDataPrimitives.Unicode_ValRand1, ArrayHelper.Fill('A', 66), + 1701729619, 2 + ); + public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat + ( + new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 + IccTestDataPrimitives.Ascii_Rand, + new byte[] { 0x00 }, // Null terminator + + new byte[] { 0x65, 0x6E, 0x55, 0x53 }, // enUS + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + IccTestDataPrimitives.Unicode_Rand1, + new byte[] { 0x00, 0x00 }, // Null terminator + + new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 + ArrayHelper.Fill((byte)0x41, 66), + new byte[] { 0x00 } // Null terminator + ); + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val2 = new IccTextDescriptionTagDataEntry(IccTestDataPrimitives.Ascii_ValRand, null, null, 0, 0); + public static readonly byte[] TextDescription_Arr2 = ArrayHelper.Concat + ( + new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 + IccTestDataPrimitives.Ascii_Rand, + new byte[] { 0x00 }, // Null terminator + + IccTestDataPrimitives.UInt32_0, + IccTestDataPrimitives.UInt32_0, + + new byte[] { 0x00, 0x00, 0x00 }, // 0, 0 + ArrayHelper.Fill((byte)0x00, 67) + ); + + public static readonly object[][] TextDescriptionTagDataEntryTestData = + { + new object[] { TextDescription_Arr1, TextDescription_Val1 }, + new object[] { TextDescription_Arr2, TextDescription_Val2 }, + }; + + #endregion + + #region CrdInfoTagDataEntry + + public static readonly IccCrdInfoTagDataEntry CrdInfo_Val = new IccCrdInfoTagDataEntry( + IccTestDataPrimitives.Ascii_ValRand4, + IccTestDataPrimitives.Ascii_ValRand1, + IccTestDataPrimitives.Ascii_ValRand2, + IccTestDataPrimitives.Ascii_ValRand3, + IccTestDataPrimitives.Ascii_ValRand4 + ); + public static readonly byte[] CrdInfo_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand4, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand1, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand2, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand3, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand4, + new byte[] { 0 } + ); + + public static readonly object[][] CrdInfoTagDataEntryTestData = + { + new object[] { CrdInfo_Arr, CrdInfo_Val }, + }; + + #endregion + + #region ScreeningTagDataEntry + + public static readonly IccScreeningTagDataEntry Screening_Val = new IccScreeningTagDataEntry( + IccScreeningFlag.DefaultScreens | IccScreeningFlag.UnitLinesPerCm, + new IccScreeningChannel[] + { + IccTestDataNonPrimitives.ScreeningChannel_ValRand1, + IccTestDataNonPrimitives.ScreeningChannel_ValRand2, + } + ); + public static readonly byte[] Screening_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.Int32_1, + IccTestDataPrimitives.UInt32_2, + IccTestDataNonPrimitives.ScreeningChannel_Rand1, + IccTestDataNonPrimitives.ScreeningChannel_Rand2 + ); + + public static readonly object[][] ScreeningTagDataEntryTestData = + { + new object[] { Screening_Arr, Screening_Val }, + }; + + #endregion + + #region UcrBgTagDataEntry + + public static readonly IccUcrBgTagDataEntry UcrBg_Val = new IccUcrBgTagDataEntry( + new ushort[] { 3, 4, 6 }, + new ushort[] { 9, 7, 2, 5 }, + IccTestDataPrimitives.Ascii_ValRand + ); + public static readonly byte[] UcrBg_Arr = ArrayHelper.Concat + ( + IccTestDataPrimitives.UInt32_3, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_6, + + IccTestDataPrimitives.UInt32_4, + IccTestDataPrimitives.UInt16_9, + IccTestDataPrimitives.UInt16_7, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_5, + + IccTestDataPrimitives.Ascii_Rand, + new byte[] { 0 } + ); + + public static readonly object[][] UcrBgTagDataEntryTestData = + { + new object[] { UcrBg_Arr, UcrBg_Val, 41 }, + }; + + #endregion + + #region TagDataEntry + + public static readonly IccTagDataEntry TagDataEntry_CurveVal = Curve_Val_2; + public static readonly byte[] TagDataEntry_CurveArr = ArrayHelper.Concat + ( + TagDataEntryHeader_CurveArr, + Curve_Arr_2, + new byte[] { 0x00, 0x00 } // padding + ); + + public static readonly IccTagDataEntry TagDataEntry_MultiLocalizedUnicodeVal = MultiLocalizedUnicode_Val; + public static readonly byte[] TagDataEntry_MultiLocalizedUnicodeArr = ArrayHelper.Concat + ( + TagDataEntryHeader_MultiLocalizedUnicodeArr, + MultiLocalizedUnicode_Arr, + new byte[] { 0x00, 0x00 } // padding + ); + + public static readonly IccTagTableEntry TagDataEntry_MultiLocalizedUnicodeTable = new IccTagTableEntry + ( + IccProfileTag.Unknown, 0, + (uint)TagDataEntry_MultiLocalizedUnicodeArr.Length - 2 + ); + + public static readonly IccTagTableEntry TagDataEntry_CurveTable = new IccTagTableEntry + ( + IccProfileTag.Unknown, 0, + (uint)TagDataEntry_CurveArr.Length - 2 + ); + + public static readonly object[][] TagDataEntryTestData = + { + new object[] { TagDataEntry_CurveArr, TagDataEntry_CurveVal }, + new object[] { TagDataEntry_MultiLocalizedUnicodeArr, TagDataEntry_MultiLocalizedUnicodeVal }, + }; + + #endregion + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs new file mode 100644 index 000000000..d3b4da9b9 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Linq; + + public static class ArrayHelper + { + /// + /// Concatenates multiple arrays of the same type into one + /// + /// The array type + /// The arrays to concatenate. The order is kept + /// The concatenated array + public static T[] Concat(params T[][] arrs) + { + T[] result = new T[arrs.Sum(t => t.Length)]; + int offset = 0; + for (int i = 0; i < arrs.Length; i++) + { + arrs[i].CopyTo(result, offset); + offset += arrs[i].Length; + } + return result; + } + + /// + /// Creates an array filled with the given value + /// + /// The array type + /// The value to fill the array with + /// The wanted length of the array + /// The created array filled with the given value + public static T[] Fill(T value, int length) + { + T[] result = new T[length]; + for (int i = 0; i < length; i++) + { + result[i] = value; + } + return result; + } + + /// + /// Creates a string from a character with a given length + /// + /// The character to fill the string with + /// The wanted length of the string + /// The filled string + public static string Fill(char value, int length) + { + return "".PadRight(length, value); + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/FloatRoundingComparer.cs b/tests/ImageSharp.Tests/TestUtilities/FloatRoundingComparer.cs new file mode 100644 index 000000000..dd3ef3a4f --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/FloatRoundingComparer.cs @@ -0,0 +1,64 @@ +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Numerics; + + /// + /// Allows the comparison of single-precision floating point values by precision. + /// + public struct FloatRoundingComparer : IEqualityComparer, IEqualityComparer + { + /// + /// Initializes a new instance of the struct. + /// + /// The number of decimal places (valid values: 0-7) + public FloatRoundingComparer(int precision) + { + Guard.MustBeBetweenOrEqualTo(precision, 0, 7, nameof(precision)); + this.Precision = precision; + } + + /// + /// Gets the number of decimal places (valid values: 0-7) + /// + public int Precision { get; } + + /// + public bool Equals(float x, float y) + { + float xp = (float)Math.Round(x, this.Precision, MidpointRounding.AwayFromZero); + float yp = (float)Math.Round(y, this.Precision, MidpointRounding.AwayFromZero); + + return Comparer.Default.Compare(xp, yp) == 0; + } + + /// + public bool Equals(Vector4 x, Vector4 y) + { + return Equals(x.X, y.X) && Equals(x.Y, y.Y) && Equals(x.Z, y.Z) && Equals(x.W, y.W); + } + + /// + public int GetHashCode(float obj) + { + unchecked + { + int hashCode = obj.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Precision.GetHashCode(); + return hashCode; + } + } + + /// + public int GetHashCode(Vector4 obj) + { + unchecked + { + int hashCode = obj.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Precision.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file