diff --git a/src/ImageProcessorCore/Colors/PackedVector/Color.cs b/src/ImageProcessorCore/Colors/Color.cs
similarity index 72%
rename from src/ImageProcessorCore/Colors/PackedVector/Color.cs
rename to src/ImageProcessorCore/Colors/Color.cs
index 4e4e817a85..69bce48536 100644
--- a/src/ImageProcessorCore/Colors/PackedVector/Color.cs
+++ b/src/ImageProcessorCore/Colors/Color.cs
@@ -58,7 +58,68 @@ namespace ImageProcessorCore
/// The green component.
/// The blue component.
/// The alpha component.
- public Color(float r, float g, float b, float a)
+ public Color(byte r, byte g, byte b, byte a = 255)
+ : this()
+ {
+ this.R = r;
+ this.G = g;
+ this.B = b;
+ this.A = a;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///
+ /// The hexadecimal representation of the combined color components arranged
+ /// in rgb, rrggbb, or aarrggbb format to match web syntax.
+ ///
+ public Color(string hex)
+ : this()
+ {
+ // Hexadecimal representations are layed out AARRGGBB to we need to do some reordering.
+ hex = hex.StartsWith("#") ? hex.Substring(1) : hex;
+
+ if (hex.Length != 8 && hex.Length != 6 && hex.Length != 3)
+ {
+ throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex));
+ }
+
+ if (hex.Length == 8)
+ {
+ this.R = Convert.ToByte(hex.Substring(2, 2), 16);
+ this.G = Convert.ToByte(hex.Substring(4, 2), 16);
+ this.B = Convert.ToByte(hex.Substring(6, 2), 16);
+ this.A = Convert.ToByte(hex.Substring(0, 2), 16);
+ }
+ else if (hex.Length == 6)
+ {
+ this.R = Convert.ToByte(hex.Substring(0, 2), 16);
+ this.G = Convert.ToByte(hex.Substring(2, 2), 16);
+ this.B = Convert.ToByte(hex.Substring(4, 2), 16);
+ this.A = 255;
+ }
+ else
+ {
+ string rh = char.ToString(hex[0]);
+ string gh = char.ToString(hex[1]);
+ string bh = char.ToString(hex[2]);
+
+ this.R = Convert.ToByte(rh + rh, 16);
+ this.G = Convert.ToByte(gh + gh, 16);
+ this.B = Convert.ToByte(bh + bh, 16);
+ this.A = 255;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The red component.
+ /// The green component.
+ /// The blue component.
+ /// The alpha component.
+ public Color(float r, float g, float b, float a = 1)
: this()
{
Vector4 clamped = Vector4.Clamp(new Vector4(r, g, b, a), Vector4.Zero, Vector4.One) * 255F;
@@ -71,17 +132,17 @@ namespace ImageProcessorCore
///
/// Initializes a new instance of the struct.
///
- /// The red component.
- /// The green component.
- /// The blue component.
- /// The alpha component.
- public Color(byte r, byte g, byte b, byte a)
+ ///
+ /// The vector containing the components for the packed vector.
+ ///
+ public Color(Vector3 vector)
: this()
{
- this.R = r;
- this.G = g;
- this.B = b;
- this.A = a;
+ Vector3 clamped = Vector3.Clamp(vector, Vector3.Zero, Vector3.One) * 255F;
+ this.R = (byte)Math.Round(clamped.X);
+ this.G = (byte)Math.Round(clamped.Y);
+ this.B = (byte)Math.Round(clamped.Z);
+ this.A = 255;
}
///
@@ -94,9 +155,9 @@ namespace ImageProcessorCore
: this()
{
Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255F;
- this.B = (byte)Math.Round(clamped.X);
+ this.R = (byte)Math.Round(clamped.X);
this.G = (byte)Math.Round(clamped.Y);
- this.R = (byte)Math.Round(clamped.Z);
+ this.B = (byte)Math.Round(clamped.Z);
this.A = (byte)Math.Round(clamped.W);
}
diff --git a/src/ImageProcessorCore/Colors/ColorConstants.cs b/src/ImageProcessorCore/Colors/ColorConstants.cs
new file mode 100644
index 0000000000..ed9718423d
--- /dev/null
+++ b/src/ImageProcessorCore/Colors/ColorConstants.cs
@@ -0,0 +1,179 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.Collections.Generic;
+
+ ///
+ /// Provides useful color definitions.
+ ///
+ public static class ColorConstants
+ {
+ ///
+ /// Provides a lazy, one time method of returning the colors.
+ ///
+ private static readonly Lazy SafeColors = new Lazy(GetWebSafeColors);
+
+ ///
+ /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4.
+ ///
+ public static Color[] WebSafeColors => SafeColors.Value;
+
+ ///
+ /// Returns an array of web safe colors.
+ ///
+ ///
+ private static Color[] GetWebSafeColors()
+ {
+ return new List
+ {
+ Color.AliceBlue,
+ Color.AntiqueWhite,
+ Color.Aqua,
+ Color.Aquamarine,
+ Color.Azure,
+ Color.Beige,
+ Color.Bisque,
+ Color.Black,
+ Color.BlanchedAlmond,
+ Color.Blue,
+ Color.BlueViolet,
+ Color.Brown,
+ Color.BurlyWood,
+ Color.CadetBlue,
+ Color.Chartreuse,
+ Color.Chocolate,
+ Color.Coral,
+ Color.CornflowerBlue,
+ Color.Cornsilk,
+ Color.Crimson,
+ Color.Cyan,
+ Color.DarkBlue,
+ Color.DarkCyan,
+ Color.DarkGoldenrod,
+ Color.DarkGray,
+ Color.DarkGreen,
+ Color.DarkKhaki,
+ Color.DarkMagenta,
+ Color.DarkOliveGreen,
+ Color.DarkOrange,
+ Color.DarkOrchid,
+ Color.DarkRed,
+ Color.DarkSalmon,
+ Color.DarkSeaGreen,
+ Color.DarkSlateBlue,
+ Color.DarkSlateGray,
+ Color.DarkTurquoise,
+ Color.DarkViolet,
+ Color.DeepPink,
+ Color.DeepSkyBlue,
+ Color.DimGray,
+ Color.DodgerBlue,
+ Color.Firebrick,
+ Color.FloralWhite,
+ Color.ForestGreen,
+ Color.Fuchsia,
+ Color.Gainsboro,
+ Color.GhostWhite,
+ Color.Gold,
+ Color.Goldenrod,
+ Color.Gray,
+ Color.Green,
+ Color.GreenYellow,
+ Color.Honeydew,
+ Color.HotPink,
+ Color.IndianRed,
+ Color.Indigo,
+ Color.Ivory,
+ Color.Khaki,
+ Color.Lavender,
+ Color.LavenderBlush,
+ Color.LawnGreen,
+ Color.LemonChiffon,
+ Color.LightBlue,
+ Color.LightCoral,
+ Color.LightCyan,
+ Color.LightGoldenrodYellow,
+ Color.LightGray,
+ Color.LightGreen,
+ Color.LightPink,
+ Color.LightSalmon,
+ Color.LightSeaGreen,
+ Color.LightSkyBlue,
+ Color.LightSlateGray,
+ Color.LightSteelBlue,
+ Color.LightYellow,
+ Color.Lime,
+ Color.LimeGreen,
+ Color.Linen,
+ Color.Magenta,
+ Color.Maroon,
+ Color.MediumAquamarine,
+ Color.MediumBlue,
+ Color.MediumOrchid,
+ Color.MediumPurple,
+ Color.MediumSeaGreen,
+ Color.MediumSlateBlue,
+ Color.MediumSpringGreen,
+ Color.MediumTurquoise,
+ Color.MediumVioletRed,
+ Color.MidnightBlue,
+ Color.MintCream,
+ Color.MistyRose,
+ Color.Moccasin,
+ Color.NavajoWhite,
+ Color.Navy,
+ Color.OldLace,
+ Color.Olive,
+ Color.OliveDrab,
+ Color.Orange,
+ Color.OrangeRed,
+ Color.Orchid,
+ Color.PaleGoldenrod,
+ Color.PaleGreen,
+ Color.PaleTurquoise,
+ Color.PaleVioletRed,
+ Color.PapayaWhip,
+ Color.PeachPuff,
+ Color.Peru,
+ Color.Pink,
+ Color.Plum,
+ Color.PowderBlue,
+ Color.Purple,
+ Color.RebeccaPurple,
+ Color.Red,
+ Color.RosyBrown,
+ Color.RoyalBlue,
+ Color.SaddleBrown,
+ Color.Salmon,
+ Color.SandyBrown,
+ Color.SeaGreen,
+ Color.SeaShell,
+ Color.Sienna,
+ Color.Silver,
+ Color.SkyBlue,
+ Color.SlateBlue,
+ Color.SlateGray,
+ Color.Snow,
+ Color.SpringGreen,
+ Color.SteelBlue,
+ Color.Tan,
+ Color.Teal,
+ Color.Thistle,
+ Color.Tomato,
+ Color.Transparent,
+ Color.Turquoise,
+ Color.Violet,
+ Color.Wheat,
+ Color.White,
+ Color.WhiteSmoke,
+ Color.Yellow,
+ Color.YellowGreen
+ }.ToArray();
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Colors/PackedVector/ColorDefinitions.cs b/src/ImageProcessorCore/Colors/ColorDefinitions.cs
similarity index 100%
rename from src/ImageProcessorCore/Colors/PackedVector/ColorDefinitions.cs
rename to src/ImageProcessorCore/Colors/ColorDefinitions.cs
diff --git a/src/ImageProcessorCore/Colors/ColorTransforms.cs b/src/ImageProcessorCore/Colors/ColorTransforms.cs
new file mode 100644
index 0000000000..cc05f6bb49
--- /dev/null
+++ b/src/ImageProcessorCore/Colors/ColorTransforms.cs
@@ -0,0 +1,74 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.Numerics;
+
+ ///
+ /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255.
+ /// The color components are stored in red, green, blue, and alpha order.
+ ///
+ ///
+ /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
+ /// as it avoids the need to create new values for modification operations.
+ ///
+ public partial struct Color
+ {
+ ///
+ /// Blends two colors by multiplication.
+ ///
+ /// The source color is multiplied by the destination color and replaces the destination.
+ /// The resultant color is always at least as dark as either the source or destination color.
+ /// Multiplying any color with black results in black. Multiplying any color with white preserves the
+ /// original color.
+ ///
+ ///
+ /// The source color.
+ /// The destination color.
+ ///
+ /// The .
+ ///
+ public static Color Multiply(Color source, Color destination)
+ {
+ if (destination == Color.Black)
+ {
+ return Color.Black;
+ }
+
+ if (destination == Color.White)
+ {
+ return source;
+ }
+
+ // TODO: This will use less memory than using Vector4
+ // but we should test speed vs memory to see which is best balance.
+ byte r = (byte)(source.R * destination.R).Clamp(0, 255);
+ byte g = (byte)(source.G * destination.G).Clamp(0, 255);
+ byte b = (byte)(source.B * destination.B).Clamp(0, 255);
+ byte a = (byte)(source.A * destination.A).Clamp(0, 255);
+
+ return new Color(r, g, b, a);
+ }
+
+ ///
+ /// Linearly interpolates from one color to another based on the given weighting.
+ ///
+ /// The first color value.
+ /// The second color value.
+ ///
+ /// A value between 0 and 1 indicating the weight of the second source vector.
+ /// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
+ ///
+ ///
+ /// The
+ ///
+ public static Color Lerp(Color from, Color to, float amount)
+ {
+ return new Color(Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount));
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Colors/ColorspaceTransforms.cs b/src/ImageProcessorCore/Colors/ColorspaceTransforms.cs
new file mode 100644
index 0000000000..db7e7536fc
--- /dev/null
+++ b/src/ImageProcessorCore/Colors/ColorspaceTransforms.cs
@@ -0,0 +1,292 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.Numerics;
+
+ ///
+ /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255.
+ /// The color components are stored in red, green, blue, and alpha order.
+ ///
+ ///
+ /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
+ /// as it avoids the need to create new values for modification operations.
+ ///
+ public partial struct Color
+ {
+ ///
+ /// The epsilon for comparing floating point numbers.
+ ///
+ private const float Epsilon = 0.001F;
+
+ ///
+ /// Allows the implicit conversion of an instance of to a
+ /// .
+ ///
+ /// The instance of to convert.
+ ///
+ /// An instance of .
+ ///
+ public static implicit operator Color(Bgra32 color)
+ {
+ return new Color(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 Color(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 Color(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 Color(YCbCr color)
+ {
+ float y = color.Y;
+ float cb = color.Cb - 128;
+ float cr = color.Cr - 128;
+
+ byte r = (byte)(y + (1.402 * cr)).Clamp(0, 255);
+ byte g = (byte)(y - (0.34414 * cb) - (0.71414 * cr)).Clamp(0, 255);
+ byte b = (byte)(y + (1.772 * cb)).Clamp(0, 255);
+
+ return new Color(r, g, b, 255);
+ }
+
+ ///
+ /// Allows the implicit conversion of an instance of to a
+ /// .
+ ///
+ /// The instance of to convert.
+ ///
+ /// An instance of .
+ ///
+ public static implicit operator Color(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 Color(vector);
+ }
+
+ ///
+ /// Allows the implicit conversion of an instance of to a
+ /// .
+ ///
+ /// The instance of to convert.
+ ///
+ /// An instance of .
+ ///
+ public static implicit operator Color(Hsv color)
+ {
+ float s = color.S;
+ float v = color.V;
+
+ if (Math.Abs(s) < Epsilon)
+ {
+ return new Color(v, v, v, 1);
+ }
+
+ float h = (Math.Abs(color.H - 360) < 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 Color(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 Color(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 (Math.Abs(l) > Epsilon)
+ {
+ if (Math.Abs(s) < 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 Color(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 Color(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 Color(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/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs b/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs
new file mode 100644
index 0000000000..76a37a7a7b
--- /dev/null
+++ b/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs
@@ -0,0 +1,167 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+
+ ///
+ /// 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);
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private 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), Vector4.Zero, new Vector4(255));
+ }
+
+ ///
+ /// 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(Color 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 GetHashCode(this);
+ }
+
+ ///
+ 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);
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ ///
+ /// The instance of to return the hash code for.
+ ///
+ ///
+ /// A 32-bit signed integer that is the hash code for this instance.
+ ///
+ private static int GetHashCode(Bgra32 color) => color.backingVector.GetHashCode();
+ }
+}
diff --git a/src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs b/src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs
new file mode 100644
index 0000000000..2480262e00
--- /dev/null
+++ b/src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs
@@ -0,0 +1,192 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+
+ ///
+ /// Represents an CIE LAB 1976 color.
+ ///
+ ///
+ public struct CieLab : IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has L, A, B values set to zero.
+ ///
+ public static readonly CieLab Empty = default(CieLab);
+
+ ///
+ /// The epsilon for comparing floating point numbers.
+ ///
+ private const float Epsilon = 0.001f;
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The lightness dimension.
+ /// The a (green - magenta) component.
+ /// The b (blue - yellow) component.
+ public CieLab(float l, float a, float b)
+ : this()
+ {
+ this.backingVector = Vector3.Clamp(new Vector3(l, a, b), new Vector3(0, -100, -100), new Vector3(100));
+ }
+
+ ///
+ /// Gets the lightness dimension.
+ /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).
+ ///
+ public float L => this.backingVector.X;
+
+ ///
+ /// Gets the a color component.
+ /// Negative is green, positive magenta.
+ ///
+ public float A => this.backingVector.Y;
+
+ ///
+ /// Gets the b color component.
+ /// Negative is blue, positive is yellow
+ ///
+ public float B => this.backingVector.Z;
+
+ ///
+ /// 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 CieLab(Color color)
+ {
+ // 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 ? (float)Math.Pow(x, 0.3333333F) : (903.3F * x + 16F) / 116F;
+ y = y > 0.008856F ? (float)Math.Pow(y, 0.3333333F) : (903.3F * y + 16F) / 116F;
+ z = z > 0.008856F ? (float)Math.Pow(z, 0.3333333F) : (903.3F * z + 16F) / 116F;
+
+ float l = Math.Max(0, (116F * y) - 16F);
+ float a = 500F * (x - y);
+ float b = 200F * (y - z);
+
+ return new CieLab(l, a, b);
+ }
+
+ ///
+ /// 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 ==(CieLab left, CieLab 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 !=(CieLab left, CieLab right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return GetHashCode(this);
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "CieLab [Empty]";
+ }
+
+ return $"CieLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is CieLab)
+ {
+ return this.Equals((CieLab)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(CieLab other)
+ {
+ return this.AlmostEquals(other, Epsilon);
+ }
+
+ ///
+ public bool AlmostEquals(CieLab other, float precision)
+ {
+ Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X < precision
+ && result.Y < precision
+ && result.Z < precision;
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ ///
+ /// The instance of to return the hash code for.
+ ///
+ ///
+ /// A 32-bit signed integer that is the hash code for this instance.
+ ///
+ private static int GetHashCode(CieLab color) => color.backingVector.GetHashCode();
+ }
+}
diff --git a/src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs b/src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs
new file mode 100644
index 0000000000..528e4faf34
--- /dev/null
+++ b/src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs
@@ -0,0 +1,184 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+
+ ///
+ /// Represents an CIE 1931 color
+ ///
+ ///
+ public struct CieXyz : IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has Y, Cb, and Cr values set to zero.
+ ///
+ public static readonly CieXyz Empty = default(CieXyz);
+
+ ///
+ /// The epsilon for comparing floating point numbers.
+ ///
+ private const float Epsilon = 0.001f;
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The y luminance component.
+ /// X is a mix (a linear combination) of cone response curves chosen to be nonnegative
+ /// Z is quasi-equal to blue stimulation, or the S cone of the human eye.
+ public CieXyz(float x, float y, float z)
+ : this()
+ {
+ // Not clamping as documentation about this space seems to indicate "usual" ranges
+ this.backingVector = new Vector3(x, y, z);
+ }
+
+ ///
+ /// Gets the Y luminance component.
+ /// A value ranging between 380 and 780.
+ ///
+ public float X => this.backingVector.X;
+
+ ///
+ /// Gets the Cb chroma component.
+ /// A value ranging between 380 and 780.
+ ///
+ public float Y => this.backingVector.Y;
+
+ ///
+ /// Gets the Cr chroma component.
+ /// A value ranging between 380 and 780.
+ ///
+ public float Z => this.backingVector.Z;
+
+ ///
+ /// 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 CieXyz(Color color)
+ {
+ 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);
+ }
+
+ ///
+ /// 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 ==(CieXyz left, CieXyz 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 !=(CieXyz left, CieXyz right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return GetHashCode(this);
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "CieXyz [ Empty ]";
+ }
+
+ return $"CieXyz [ X={this.X:#0.##}, Y={this.Y:#0.##}, Z={this.Z:#0.##} ]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is CieXyz)
+ {
+ return this.Equals((CieXyz)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(CieXyz other)
+ {
+ return this.AlmostEquals(other, Epsilon);
+ }
+
+ ///
+ public bool AlmostEquals(CieXyz other, float precision)
+ {
+ Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X < precision
+ && result.Y < precision
+ && result.Z < precision;
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ ///
+ /// The instance of to return the hash code for.
+ ///
+ ///
+ /// A 32-bit signed integer that is the hash code for this instance.
+ ///
+ private static int GetHashCode(CieXyz color) => color.backingVector.GetHashCode();
+ }
+}
diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs b/src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs
new file mode 100644
index 0000000000..6cb717e624
--- /dev/null
+++ b/src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs
@@ -0,0 +1,195 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+
+ ///
+ /// Represents an CMYK (cyan, magenta, yellow, keyline) color.
+ ///
+ public struct Cmyk : IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has C, M, Y, and K values set to zero.
+ ///
+ public static readonly Cmyk Empty = default(Cmyk);
+
+ ///
+ /// The epsilon for comparing floating point numbers.
+ ///
+ private const float Epsilon = 0.001f;
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private Vector4 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The cyan component.
+ /// The magenta component.
+ /// The yellow component.
+ /// The keyline black component.
+ public Cmyk(float c, float m, float y, float k)
+ : this()
+ {
+ this.backingVector = Vector4.Clamp(new Vector4(c, m, y, k), Vector4.Zero, Vector4.One);
+ }
+
+ ///
+ /// Gets the cyan color component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float C => this.backingVector.X;
+
+ ///
+ /// Gets the magenta color component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float M => this.backingVector.Y;
+
+ ///
+ /// Gets the yellow color component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float Y => this.backingVector.Z;
+
+ ///
+ /// Gets the keyline black color component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float K => this.backingVector.W;
+
+ ///
+ /// 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 Cmyk(Color color)
+ {
+ float c = 1f - color.R / 255F;
+ float m = 1f - color.G / 255F;
+ float y = 1f - color.B / 255F;
+
+ float k = Math.Min(c, Math.Min(m, y));
+
+ if (Math.Abs(k - 1.0f) <= 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.
+ ///
+ ///
+ /// 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 ==(Cmyk left, Cmyk 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 !=(Cmyk left, Cmyk right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return GetHashCode(this);
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "Cmyk [Empty]";
+ }
+
+ return $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={this.K:#0.##}]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is Cmyk)
+ {
+ return this.Equals((Cmyk)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(Cmyk other)
+ {
+ return this.AlmostEquals(other, Epsilon);
+ }
+
+ ///
+ public bool AlmostEquals(Cmyk other, float precision)
+ {
+ Vector4 result = Vector4.Abs(this.backingVector - other.backingVector);
+
+ return result.X < precision
+ && result.Y < precision
+ && result.Z < precision
+ && result.W < precision;
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ ///
+ /// The instance of to return the hash code for.
+ ///
+ ///
+ /// A 32-bit signed integer that is the hash code for this instance.
+ ///
+ private static int GetHashCode(Cmyk color) => color.backingVector.GetHashCode();
+ }
+}
diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs b/src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs
new file mode 100644
index 0000000000..0e8812a257
--- /dev/null
+++ b/src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs
@@ -0,0 +1,213 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+
+ ///
+ /// Represents a Hsl (hue, saturation, lightness) color.
+ ///
+ public struct Hsl : IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has H, S, and L values set to zero.
+ ///
+ public static readonly Hsl Empty = default(Hsl);
+
+ ///
+ /// The epsilon for comparing floating point numbers.
+ ///
+ private const float Epsilon = 0.001F;
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The h hue component.
+ /// The s saturation component.
+ /// The l value (lightness) component.
+ public Hsl(float h, float s, float l)
+ {
+ this.backingVector = Vector3.Clamp(new Vector3(h, s, l), Vector3.Zero, new Vector3(360, 1, 1));
+ }
+
+ ///
+ /// Gets the hue component.
+ /// A value ranging between 0 and 360.
+ ///
+ public float H => this.backingVector.X;
+
+ ///
+ /// Gets the saturation component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float S => this.backingVector.Y;
+
+ ///
+ /// Gets the lightness component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float L => this.backingVector.Z;
+
+ ///
+ /// 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 Hsl(Color color)
+ {
+ float r = color.R / 255F;
+ float g = color.G / 255F;
+ float b = color.B / 255F;
+
+ float max = Math.Max(r, Math.Max(g, b));
+ float min = Math.Min(r, Math.Min(g, b));
+ float chroma = max - min;
+ float h = 0;
+ float s = 0;
+ float l = (max + min) / 2;
+
+ if (Math.Abs(chroma) < Epsilon)
+ {
+ return new Hsl(0, s, l);
+ }
+
+ if (Math.Abs(r - max) < Epsilon)
+ {
+ h = (g - b) / chroma;
+ }
+ else if (Math.Abs(g - max) < Epsilon)
+ {
+ h = 2 + ((b - r) / chroma);
+ }
+ else if (Math.Abs(b - max) < 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);
+ }
+
+ ///
+ /// 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 ==(Hsl left, Hsl 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 !=(Hsl left, Hsl right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return GetHashCode(this);
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "Hsl [ Empty ]";
+ }
+
+ return $"Hsl [ H={this.H:#0.##}, S={this.S:#0.##}, L={this.L:#0.##} ]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is Hsl)
+ {
+ return this.Equals((Hsl)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(Hsl other)
+ {
+ return this.AlmostEquals(other, Epsilon);
+ }
+
+ ///
+ public bool AlmostEquals(Hsl other, float precision)
+ {
+ Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X < precision
+ && result.Y < precision
+ && result.Z < precision;
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ ///
+ /// The instance of to return the hash code for.
+ ///
+ ///
+ /// A 32-bit signed integer that is the hash code for this instance.
+ ///
+ private static int GetHashCode(Hsl color) => color.backingVector.GetHashCode();
+ }
+}
diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs b/src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs
new file mode 100644
index 0000000000..0a22ac2731
--- /dev/null
+++ b/src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs
@@ -0,0 +1,206 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+
+ ///
+ /// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness).
+ ///
+ public struct Hsv : IEquatable, IAlmostEquatable
+ {
+ ///
+ /// Represents a that has H, S, and V values set to zero.
+ ///
+ public static readonly Hsv Empty = default(Hsv);
+
+ ///
+ /// The epsilon for comparing floating point numbers.
+ ///
+ private const float Epsilon = 0.001F;
+
+ ///
+ /// The backing vector for SIMD support.
+ ///
+ private Vector3 backingVector;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The h hue component.
+ /// The s saturation component.
+ /// The v value (brightness) component.
+ public Hsv(float h, float s, float v)
+ {
+ this.backingVector = Vector3.Clamp(new Vector3(h, s, v), Vector3.Zero, new Vector3(360, 1, 1));
+ }
+
+ ///
+ /// Gets the hue component.
+ /// A value ranging between 0 and 360.
+ ///
+ public float H => this.backingVector.X;
+
+ ///
+ /// Gets the saturation component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float S => this.backingVector.Y;
+
+ ///
+ /// Gets the value (brightness) component.
+ /// A value ranging between 0 and 1.
+ ///
+ public float V => this.backingVector.Z;
+
+ ///
+ /// 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 Hsv(Color color)
+ {
+ float r = color.R / 255F;
+ float g = color.G / 255F;
+ float b = color.B / 255F;
+
+ float max = Math.Max(r, Math.Max(g, b));
+ float min = Math.Min(r, Math.Min(g, b));
+ float chroma = max - min;
+ float h = 0;
+ float s = 0;
+ float v = max;
+
+ if (Math.Abs(chroma) < Epsilon)
+ {
+ return new Hsv(0, s, v);
+ }
+
+ if (Math.Abs(r - max) < Epsilon)
+ {
+ h = (g - b) / chroma;
+ }
+ else if (Math.Abs(g - max) < Epsilon)
+ {
+ h = 2 + ((b - r) / chroma);
+ }
+ else if (Math.Abs(b - max) < Epsilon)
+ {
+ h = 4 + ((r - g) / chroma);
+ }
+
+ h *= 60;
+ if (h < 0.0)
+ {
+ h += 360;
+ }
+
+ s = chroma / v;
+
+ return new Hsv(h, s, v);
+ }
+
+ ///
+ /// 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 ==(Hsv left, Hsv 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 !=(Hsv left, Hsv right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return GetHashCode(this);
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "Hsv [ Empty ]";
+ }
+
+ return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is Hsv)
+ {
+ return this.Equals((Hsv)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(Hsv other)
+ {
+ return this.AlmostEquals(other, Epsilon);
+ }
+
+ ///
+ public bool AlmostEquals(Hsv other, float precision)
+ {
+ Vector3 result = Vector3.Abs(this.backingVector - other.backingVector);
+
+ return result.X < precision
+ && result.Y < precision
+ && result.Z < precision;
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ ///
+ /// The instance of to return the hash code for.
+ ///
+ ///
+ /// A 32-bit signed integer that is the hash code for this instance.
+ ///
+ private static int GetHashCode(Hsv color) => color.backingVector.GetHashCode();
+ }
+}
diff --git a/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs b/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs
index faba036ade..64560d7d0d 100644
--- a/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs
+++ b/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs
@@ -24,7 +24,7 @@ namespace ImageProcessorCore
///
/// The epsilon for comparing floating point numbers.
///
- private const float Epsilon = 0.001f;
+ private const float Epsilon = 0.001F;
///
/// The backing vector for SIMD support.
diff --git a/src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs b/src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs
deleted file mode 100644
index 92b06762e5..0000000000
--- a/src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs
+++ /dev/null
@@ -1,285 +0,0 @@
-//
-// Copyright (c) James Jackson-South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-
-namespace ImageProcessorCore
-{
- using System;
-
- ///
- /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255.
- /// The color components are stored in red, green, blue, and alpha order.
- ///
- ///
- /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
- /// as it avoids the need to create new values for modification operations.
- ///
- public partial struct Color
- {
- /////
- ///// Allows the implicit conversion of an instance of to a
- ///// .
- /////
- ///// The instance of to convert.
- /////
- ///// An instance of .
- /////
- //public static implicit operator Color(Bgra32 color)
- //{
- // return new Color(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
- //}
-
- /////
- ///// Allows the implicit conversion of an instance of to a
- ///// .
- /////
- ///// The instance of to convert.
- /////
- ///// An instance of .
- /////
- //public static implicit operator Color(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 Color(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 Color(YCbCr color)
- {
- float y = color.Y;
- float cb = color.Cb - 128;
- float cr = color.Cr - 128;
-
- byte r = (byte)(y + (1.402 * cr)).Clamp(0, 255);
- byte g = (byte)(y - (0.34414 * cb) - (0.71414 * cr)).Clamp(0, 255);
- byte b = (byte)(y + (1.772 * cb)).Clamp(0, 255);
-
- return new Color(r, g, b, 255);
- }
-
- /////
- ///// Allows the implicit conversion of an instance of to a
- ///// .
- /////
- ///// The instance of to convert.
- /////
- ///// An instance of .
- /////
- //public static implicit operator Color(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);
-
- // return Color.Compress(new Color(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 Color(Hsv color)
- //{
- // float s = color.S;
- // float v = color.V;
-
- // if (Math.Abs(s) < Epsilon)
- // {
- // return new Color(v, v, v, 1);
- // }
-
- // float h = (Math.Abs(color.H - 360) < 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 Color(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 Color(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 (Math.Abs(l) > Epsilon)
- // {
- // if (Math.Abs(s) < 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 Color(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 Color(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 Color.Compress(new Color(r, g, b));
- //}
-
- ///
- /// 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/ImageProcessorCore/Filters/DetectEdges.cs b/src/ImageProcessorCore/Filters/DetectEdges.cs
index 2912e04f72..33fa00d30d 100644
--- a/src/ImageProcessorCore/Filters/DetectEdges.cs
+++ b/src/ImageProcessorCore/Filters/DetectEdges.cs
@@ -14,7 +14,7 @@ namespace ImageProcessorCore
{
///
/// Detects any edges within the image. Uses the filter
- /// operating in greyscale mode.
+ /// operating in Grayscale mode.
///
/// The pixel format.
/// The packed format. long, float.
@@ -25,7 +25,7 @@ namespace ImageProcessorCore
where T : IPackedVector
where TP : struct
{
- return DetectEdges(source, source.Bounds, new SobelProcessor { Greyscale = true }, progressHandler);
+ return DetectEdges(source, source.Bounds, new SobelProcessor { Grayscale = true }, progressHandler);
}
///
@@ -35,10 +35,10 @@ namespace ImageProcessorCore
/// The packed format. long, float.
/// The image this method extends.
/// The filter for detecting edges.
- /// Whether to convert the image to greyscale first. Defaults to true.
+ /// Whether to convert the image to Grayscale first. Defaults to true.
/// A delegate which is called as progress is made processing the image.
/// The .
- public static Image DetectEdges(this Image source, EdgeDetection filter, bool greyscale = true, ProgressEventHandler progressHandler = null)
+ public static Image DetectEdges(this Image source, EdgeDetection filter, bool Grayscale = true, ProgressEventHandler progressHandler = null)
where T : IPackedVector
where TP : struct
{
@@ -47,39 +47,39 @@ namespace ImageProcessorCore
switch (filter)
{
case EdgeDetection.Kayyali:
- processor = new KayyaliProcessor { Greyscale = greyscale };
+ processor = new KayyaliProcessor { Grayscale = Grayscale };
break;
case EdgeDetection.Kirsch:
- processor = new KirschProcessor { Greyscale = greyscale };
+ processor = new KirschProcessor { Grayscale = Grayscale };
break;
case EdgeDetection.Lapacian3X3:
- processor = new Laplacian3X3Processor { Greyscale = greyscale };
+ processor = new Laplacian3X3Processor { Grayscale = Grayscale };
break;
case EdgeDetection.Lapacian5X5:
- processor = new Laplacian5X5Processor { Greyscale = greyscale };
+ processor = new Laplacian5X5Processor { Grayscale = Grayscale };
break;
case EdgeDetection.LaplacianOfGaussian:
- processor = new LaplacianOfGaussianProcessor { Greyscale = greyscale };
+ processor = new LaplacianOfGaussianProcessor { Grayscale = Grayscale };
break;
case EdgeDetection.Prewitt:
- processor = new PrewittProcessor { Greyscale = greyscale };
+ processor = new PrewittProcessor { Grayscale = Grayscale };
break;
case EdgeDetection.RobertsCross:
- processor = new RobertsCrossProcessor { Greyscale = greyscale };
+ processor = new RobertsCrossProcessor { Grayscale = Grayscale };
break;
case EdgeDetection.Scharr:
- processor = new ScharrProcessor { Greyscale = greyscale };
+ processor = new ScharrProcessor { Grayscale = Grayscale };
break;
default:
- processor = new ScharrProcessor { Greyscale = greyscale };
+ processor = new ScharrProcessor { Grayscale = Grayscale };
break;
}
diff --git a/src/ImageProcessorCore/Filters/Greyscale.cs b/src/ImageProcessorCore/Filters/Grayscale.cs
similarity index 76%
rename from src/ImageProcessorCore/Filters/Greyscale.cs
rename to src/ImageProcessorCore/Filters/Grayscale.cs
index 90db305541..46ef445266 100644
--- a/src/ImageProcessorCore/Filters/Greyscale.cs
+++ b/src/ImageProcessorCore/Filters/Grayscale.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
@@ -13,7 +13,7 @@ namespace ImageProcessorCore
public static partial class ImageExtensions
{
///
- /// Applies greyscale toning to the image.
+ /// Applies Grayscale toning to the image.
///
/// The pixel format.
/// The packed format. long, float.
@@ -21,15 +21,15 @@ namespace ImageProcessorCore
/// The formula to apply to perform the operation.
/// A delegate which is called as progress is made processing the image.
/// The .
- public static Image Greyscale(this Image source, GreyscaleMode mode = GreyscaleMode.Bt709, ProgressEventHandler progressHandler = null)
+ public static Image Grayscale(this Image source, GrayscaleMode mode = GrayscaleMode.Bt709, ProgressEventHandler progressHandler = null)
where T : IPackedVector
where TP : struct
{
- return Greyscale(source, source.Bounds, mode, progressHandler);
+ return Grayscale(source, source.Bounds, mode, progressHandler);
}
///
- /// Applies greyscale toning to the image.
+ /// Applies Grayscale toning to the image.
///
/// The pixel format.
/// The packed format. long, float.
@@ -40,13 +40,13 @@ namespace ImageProcessorCore
/// The formula to apply to perform the operation.
/// A delegate which is called as progress is made processing the image.
/// The .
- public static Image Greyscale(this Image source, Rectangle rectangle, GreyscaleMode mode = GreyscaleMode.Bt709, ProgressEventHandler progressHandler = null)
+ public static Image Grayscale(this Image source, Rectangle rectangle, GrayscaleMode mode = GrayscaleMode.Bt709, ProgressEventHandler progressHandler = null)
where T : IPackedVector
where TP : struct
{
- IImageProcessor processor = mode == GreyscaleMode.Bt709
- ? (IImageProcessor)new GreyscaleBt709Processor()
- : new GreyscaleBt601Processor();
+ IImageProcessor processor = mode == GrayscaleMode.Bt709
+ ? (IImageProcessor)new GrayscaleBt709Processor()
+ : new GrayscaleBt601Processor();
processor.OnProgress += progressHandler;
diff --git a/src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs b/src/ImageProcessorCore/Filters/Options/GrayscaleMode.cs
similarity index 72%
rename from src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs
rename to src/ImageProcessorCore/Filters/Options/GrayscaleMode.cs
index e1cc5917ec..9c1b9df5d4 100644
--- a/src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs
+++ b/src/ImageProcessorCore/Filters/Options/GrayscaleMode.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
@@ -6,9 +6,9 @@
namespace ImageProcessorCore
{
///
- /// Enumerates the various types of defined greyscale filters.
+ /// Enumerates the various types of defined Grayscale filters.
///
- public enum GreyscaleMode
+ public enum GrayscaleMode
{
///
/// ITU-R Recommendation BT.709
diff --git a/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs
index 78da97f111..ffe5c4452a 100644
--- a/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs
+++ b/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs
@@ -9,7 +9,7 @@ namespace ImageProcessorCore.Processors
///
/// An to perform binary threshold filtering against an
- /// . The image will be converted to greyscale before thresholding
+ /// . The image will be converted to Grayscale before thresholding
/// occurs.
///
/// The pixel format.
@@ -58,7 +58,7 @@ namespace ImageProcessorCore.Processors
///
protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
- new GreyscaleBt709Processor().Apply(source, source, sourceRectangle);
+ new GrayscaleBt709Processor().Apply(source, source, sourceRectangle);
}
///
@@ -90,7 +90,7 @@ namespace ImageProcessorCore.Processors
{
T color = sourcePixels[x, y];
- // Any channel will do since it's greyscale.
+ // Any channel will do since it's Grayscale.
targetPixels[x, y] = color.ToVector4().X >= threshold ? upper : lower;
}
this.OnRowProcessed();
diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs
similarity index 83%
rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs
rename to src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs
index 9e6b25ad68..b650f0cfc0 100644
--- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs
+++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
@@ -8,12 +8,12 @@ namespace ImageProcessorCore.Processors
using System.Numerics;
///
- /// Converts the colors of the image to greyscale applying the formula as specified by
+ /// Converts the colors of the image to Grayscale applying the formula as specified by
/// ITU-R Recommendation BT.601 .
///
/// The pixel format.
/// The packed format. long, float.
- public class GreyscaleBt601Processor : ColorMatrixFilter
+ public class GrayscaleBt601Processor : ColorMatrixFilter
where T : IPackedVector
where TP : struct
{
diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs
similarity index 80%
rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs
rename to src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs
index f8741b00b7..435f00d198 100644
--- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs
+++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
@@ -8,10 +8,10 @@ namespace ImageProcessorCore.Processors
using System.Numerics;
///
- /// Converts the colors of the image to greyscale applying the formula as specified by
+ /// Converts the colors of the image to Grayscale applying the formula as specified by
/// ITU-R Recommendation BT.709 .
///
- public class GreyscaleBt709Processor : ColorMatrixFilter
+ public class GrayscaleBt709Processor : ColorMatrixFilter
where T : IPackedVector
where TP : struct
{
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs
index 4340f4d60c..4effae8b0e 100644
--- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs
@@ -14,14 +14,14 @@ namespace ImageProcessorCore.Processors
where TP : struct
{
///
- public bool Greyscale { get; set; }
+ public bool Grayscale { get; set; }
///
protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
- if (this.Greyscale)
+ if (this.Grayscale)
{
- new GreyscaleBt709Processor().Apply(source, source, sourceRectangle);
+ new GrayscaleBt709Processor().Apply(source, source, sourceRectangle);
}
}
}
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs
index 2762f664fc..a82f0d4caf 100644
--- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs
@@ -14,14 +14,14 @@ namespace ImageProcessorCore.Processors
where TP : struct
{
///
- public bool Greyscale { get; set; }
+ public bool Grayscale { get; set; }
///
protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
- if (this.Greyscale)
+ if (this.Grayscale)
{
- new GreyscaleBt709Processor().Apply(source, source, sourceRectangle);
+ new GrayscaleBt709Processor().Apply(source, source, sourceRectangle);
}
}
}
diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs
index ef0ceea9b4..9d9485875b 100644
--- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs
+++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs
@@ -21,8 +21,8 @@ namespace ImageProcessorCore.Processors
{
///
/// Gets or sets a value indicating whether to convert the
- /// image to greyscale before performing edge detection.
+ /// image to Grayscale before performing edge detection.
///
- bool Greyscale { get; set; }
+ bool Grayscale { get; set; }
}
}
diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs
index fe2f018294..041fcf9406 100644
--- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs
+++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs
@@ -615,7 +615,7 @@ namespace ImageProcessorCore.Formats
this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
this.buffer[3] = (byte)(width >> 8);
this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
- this.buffer[5] = (byte)componentCount; // Number of components (1 byte), usually 1 = grey scaled, 3 = color YCbCr or YIQ, 4 = color CMYK)
+ this.buffer[5] = (byte)componentCount; // Number of components (1 byte), usually 1 = Gray scaled, 3 = color YCbCr or YIQ, 4 = color CMYK)
if (componentCount == 1)
{
this.buffer[6] = 1;
diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoder.cs b/src/ImageProcessorCore/Formats/Png/PngDecoder.cs
index 0ce7c3c887..b2630fb36d 100644
--- a/src/ImageProcessorCore/Formats/Png/PngDecoder.cs
+++ b/src/ImageProcessorCore/Formats/Png/PngDecoder.cs
@@ -21,8 +21,8 @@ namespace ImageProcessorCore.Formats
///
/// - RGBA (True color) with alpha (8 bit).
/// - RGB (True color) without alpha (8 bit).
- /// - Greyscale with alpha (8 bit).
- /// - Greyscale without alpha (8 bit).
+ /// - Grayscale with alpha (8 bit).
+ /// - Grayscale without alpha (8 bit).
/// - Palette Index with alpha (8 bit).
/// - Palette Index without alpha (8 bit).
///
diff --git a/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs
new file mode 100644
index 0000000000..6fafad5f09
--- /dev/null
+++ b/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs
@@ -0,0 +1,145 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Quantizers
+{
+ using System;
+ using System.Collections.Concurrent;
+ using System.Collections.Generic;
+ using System.Linq;
+
+ ///
+ /// Encapsulates methods to create a quantized image based upon the given palette.
+ ///
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ public class PaletteQuantizer : Quantizer
+ where T : IPackedVector
+ where TP : struct
+ {
+ ///
+ /// A lookup table for colors
+ ///
+ private readonly ConcurrentDictionary colorMap = new ConcurrentDictionary();
+
+ ///
+ /// List of all colors in the palette
+ ///
+ private T[] colors;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The color palette. If none is given this will default to the web safe colors defined
+ /// in the CSS Color Module Level 4.
+ ///
+ public PaletteQuantizer(T[] palette = null)
+ : base(true)
+ {
+ if (palette == null)
+ {
+ Color[] constants = ColorConstants.WebSafeColors;
+ List safe = new List { default(T) };
+ foreach (Color c in constants)
+ {
+ T packed = default(T);
+ packed.PackVector(c.ToVector4());
+ safe.Add(packed);
+ }
+
+ this.colors = safe.ToArray();
+ }
+ else
+ {
+ this.colors = palette;
+ }
+ }
+
+ ///
+ public override QuantizedImage Quantize(ImageBase image, int maxColors)
+ {
+ Array.Resize(ref this.colors, maxColors.Clamp(1, 256));
+ return base.Quantize(image, maxColors);
+ }
+
+ ///
+ protected override byte QuantizePixel(T pixel)
+ {
+ byte colorIndex = 0;
+ string colorHash = pixel.ToString();
+
+ // Check if the color is in the lookup table
+ if (this.colorMap.ContainsKey(colorHash))
+ {
+ colorIndex = this.colorMap[colorHash];
+ }
+ else
+ {
+ // Not found - loop through the palette and find the nearest match.
+ // Firstly check the alpha value - if less than the threshold, lookup the transparent color
+ byte[] bytes = pixel.ToBytes();
+ if (!(bytes[3] > this.Threshold))
+ {
+ // Transparent. Lookup the first color with an alpha value of 0
+ for (int index = 0; index < this.colors.Length; index++)
+ {
+ if (this.colors[index].ToBytes()[3] == 0)
+ {
+ colorIndex = (byte)index;
+ this.TransparentIndex = colorIndex;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // Not transparent...
+ int leastDistance = int.MaxValue;
+ int red = bytes[0];
+ int green = bytes[1];
+ int blue = bytes[3];
+
+ // Loop through the entire palette, looking for the closest color match
+ for (int index = 0; index < this.colors.Length; index++)
+ {
+ byte[] paletteColor = this.colors[index].ToBytes();
+ int redDistance = paletteColor[0] - red;
+ int greenDistance = paletteColor[1] - green;
+ int blueDistance = paletteColor[2] - blue;
+
+ int distance = (redDistance * redDistance) +
+ (greenDistance * greenDistance) +
+ (blueDistance * blueDistance);
+
+ if (distance < leastDistance)
+ {
+ colorIndex = (byte)index;
+ leastDistance = distance;
+
+ // And if it's an exact match, exit the loop
+ if (distance == 0)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ // Now I have the color, pop it into the cache for next time
+ this.colorMap.TryAdd(colorHash, colorIndex);
+ }
+
+ return colorIndex;
+ }
+
+ ///
+ protected override List GetPalette()
+ {
+ return this.colors.ToList();
+ }
+ }
+}
diff --git a/tests/ImageProcessorCore.Tests/Colors/Class.cs b/tests/ImageProcessorCore.Tests/Colors/Class.cs
new file mode 100644
index 0000000000..a93aac5f9b
--- /dev/null
+++ b/tests/ImageProcessorCore.Tests/Colors/Class.cs
@@ -0,0 +1,109 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+using System.Numerics;
+
+namespace ImageProcessorCore.Tests
+{
+ using System;
+ using Xunit;
+
+ ///
+ /// Tests the struct.
+ ///
+ public class ColorTests
+ {
+ ///
+ /// Tests the equality operators for equality.
+ ///
+ [Fact]
+ public void AreEqual()
+ {
+ Color color1 = new Color(0, 0, 0);
+ Color color2 = new Color(0, 0, 0, 1F);
+ Color color3 = new Color("#000");
+ Color color4 = new Color("#000000");
+ Color color5 = new Color("#FF000000");
+
+ Assert.Equal(color1, color2);
+ Assert.Equal(color1, color3);
+ Assert.Equal(color1, color4);
+ Assert.Equal(color1, color5);
+ }
+
+ ///
+ /// Tests the equality operators for inequality.
+ ///
+ [Fact]
+ public void AreNotEqual()
+ {
+ Color color1 = new Color(255, 0, 0, 255);
+ Color color2 = new Color(0, 0, 0, 255);
+ Color color3 = new Color("#000");
+ Color color4 = new Color("#000000");
+ Color color5 = new Color("#FF000000");
+
+ Assert.NotEqual(color1, color2);
+ Assert.NotEqual(color1, color3);
+ Assert.NotEqual(color1, color4);
+ Assert.NotEqual(color1, color5);
+ }
+
+ ///
+ /// Tests whether the color constructor correctly assign properties.
+ ///
+ [Fact]
+ public void ConstructorAssignsProperties()
+ {
+ Color color1 = new Color(1, .1f, .133f, .864f);
+ Assert.Equal(255, color1.R);
+ Assert.Equal((byte)Math.Round(.1f * 255), color1.G);
+ Assert.Equal((byte)Math.Round(.133f * 255), color1.B);
+ Assert.Equal((byte)Math.Round(.864f * 255), color1.A);
+
+ Color color2 = new Color(1, .1f, .133f);
+ Assert.Equal(255, color2.R);
+ Assert.Equal(Math.Round(.1f * 255), color2.G);
+ Assert.Equal(Math.Round(.133f * 255), color2.B);
+ Assert.Equal(255, color2.A);
+
+ Color color3 = new Color("#FF0000");
+ Assert.Equal(255, color3.R);
+ Assert.Equal(0, color3.G);
+ Assert.Equal(0, color3.B);
+ Assert.Equal(255, color3.A);
+
+ //Color color4 = new Color(new Vector3(1, .1f, .133f));
+ //Assert.Equal(1, color4.R, 1);
+ //Assert.Equal(.1f, color4.G, 1);
+ //Assert.Equal(.133f, color4.B, 3);
+ //Assert.Equal(1, color4.A, 1);
+
+ //Color color5 = new Color(new Vector3(1, .1f, .133f), .5f);
+ //Assert.Equal(1, color5.R, 1);
+ //Assert.Equal(.1f, color5.G, 1);
+ //Assert.Equal(.133f, color5.B, 3);
+ //Assert.Equal(.5f, color5.A, 1);
+
+ Color color6 = new Color(new Vector4(1, .1f, .133f, .5f));
+ Assert.Equal(255, color6.R);
+ Assert.Equal(Math.Round(.1f * 255), color6.G);
+ Assert.Equal(Math.Round(.133f * 255), color6.B);
+ Assert.Equal(Math.Round(.5f * 255), color6.A);
+ }
+
+ ///
+ /// Tests to see that in the input hex matches that of the output.
+ ///
+ [Fact]
+ public void ConvertHex()
+ {
+ const string First = "FF000000";
+ Color color = Color.Black;
+ string second = color.PackedValue().ToString("X");
+ Assert.Equal(First, second);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs b/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs
new file mode 100644
index 0000000000..091ce2abf5
--- /dev/null
+++ b/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs
@@ -0,0 +1,493 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Tests
+{
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+
+ 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
+ Color color = Color.White;
+ YCbCr yCbCr = color;
+
+ Assert.Equal(255, yCbCr.Y, 0);
+ Assert.Equal(128, yCbCr.Cb, 0);
+ Assert.Equal(128, yCbCr.Cr, 0);
+
+ // Black
+ Color color2 = Color.Black;
+ YCbCr yCbCr2 = color2;
+ Assert.Equal(0, yCbCr2.Y, 0);
+ Assert.Equal(128, yCbCr2.Cb, 0);
+ Assert.Equal(128, yCbCr2.Cr, 0);
+
+ // Gray
+ Color color3 = Color.Gray;
+ YCbCr yCbCr3 = color3;
+ Assert.Equal(128, yCbCr3.Y, 0);
+ Assert.Equal(128, yCbCr3.Cb, 0);
+ Assert.Equal(128, yCbCr3.Cr, 0);
+ }
+
+ ///
+ /// 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);
+ Color 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);
+ Color 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);
+ Color 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
+ Color color = Color.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
+ Color color2 = Color.Black;
+ CieXyz ciexyz2 = color2;
+ Assert.Equal(0, ciexyz2.X, 3);
+ Assert.Equal(0, ciexyz2.Y, 3);
+ Assert.Equal(0, ciexyz2.Z, 3);
+
+ // Gray
+ Color color3 = Color.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
+ Color color4 = Color.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);
+ Color 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);
+ Color 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);
+ Color 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
+ Color b = Color.Black;
+ Hsv h = b;
+
+ Assert.Equal(0, h.H, 1);
+ Assert.Equal(0, h.S, 1);
+ Assert.Equal(0, h.V, 1);
+
+ // White
+ Color color = Color.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.
+ Color color2 = new Color(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.
+ Color color3 = new Color(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);
+ Color 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);
+ Color 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);
+ Color 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
+ Color b = Color.Black;
+ Hsl h = b;
+
+ Assert.Equal(0, h.H, 1);
+ Assert.Equal(0, h.S, 1);
+ Assert.Equal(0, h.L, 1);
+
+ // White
+ Color color = Color.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.
+ Color color2 = new Color(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.
+ Color color3 = new Color(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);
+ Color 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);
+ Color 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);
+ Color 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
+ Color color = Color.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
+ Color color2 = Color.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
+ Color color3 = Color.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
+ Color color4 = Color.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);
+ Color 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);
+ Color 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);
+ Color 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
+ Color color = Color.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
+ Color color2 = Color.Black;
+ CieLab cielab2 = color2;
+ Assert.Equal(0, cielab2.L, 3);
+ Assert.Equal(0, cielab2.A, 3);
+ Assert.Equal(0, cielab2.B, 3);
+
+ // Gray
+ Color color3 = Color.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
+ Color color4 = Color.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);
+ Color 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);
+ Color 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);
+ Color 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/ImageProcessorCore.Tests/Processors/Filters/GreyscaleTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/GrayscaleTest.cs
similarity index 66%
rename from tests/ImageProcessorCore.Tests/Processors/Filters/GreyscaleTest.cs
rename to tests/ImageProcessorCore.Tests/Processors/Filters/GrayscaleTest.cs
index 427cc346a4..da60c06d39 100644
--- a/tests/ImageProcessorCore.Tests/Processors/Filters/GreyscaleTest.cs
+++ b/tests/ImageProcessorCore.Tests/Processors/Filters/GrayscaleTest.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
@@ -10,20 +10,20 @@ namespace ImageProcessorCore.Tests
using Xunit;
- public class GreyscaleTest : FileTestBase
+ public class GrayscaleTest : FileTestBase
{
- public static readonly TheoryData GreyscaleValues
- = new TheoryData
+ public static readonly TheoryData GrayscaleValues
+ = new TheoryData
{
- GreyscaleMode.Bt709 ,
- GreyscaleMode.Bt601 ,
+ GrayscaleMode.Bt709 ,
+ GrayscaleMode.Bt601 ,
};
[Theory]
- [MemberData("GreyscaleValues")]
- public void ImageShouldApplyGreyscaleFilter(GreyscaleMode value)
+ [MemberData("GrayscaleValues")]
+ public void ImageShouldApplyGrayscaleFilter(GrayscaleMode value)
{
- const string path = "TestOutput/Greyscale";
+ const string path = "TestOutput/Grayscale";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
@@ -38,7 +38,7 @@ namespace ImageProcessorCore.Tests
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"{path}/{filename}"))
{
- image.Greyscale(value)
+ image.Grayscale(value)
.Save(output);
}
}