From a34aef253d55112728c041e22fcb2005614f1717 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 19:37:16 +1000 Subject: [PATCH] Fix up colors Former-commit-id: 7d1bd38103443c71e8bfd1b0f0188e280302b410 Former-commit-id: 720bc4cad102119f67274389f0f15be803b24ce8 Former-commit-id: b1cf87199086fc50dcdd98b915de13e8c871c688 --- .../Colors/{PackedVector => }/Color.cs | 85 ++- .../Colors/ColorConstants.cs | 179 +++++++ .../{PackedVector => }/ColorDefinitions.cs | 0 .../Colors/ColorTransforms.cs | 74 +++ .../Colors/ColorspaceTransforms.cs | 292 +++++++++++ .../Colors/Colorspaces/Bgra32.cs | 167 ++++++ .../Colors/Colorspaces/CieLab.cs | 192 +++++++ .../Colors/Colorspaces/CieXyz.cs | 184 +++++++ .../Colors/Colorspaces/Cmyk.cs | 195 +++++++ .../Colors/Colorspaces/Hsl.cs | 213 ++++++++ .../Colors/Colorspaces/Hsv.cs | 206 ++++++++ .../Colors/Colorspaces/YCbCr.cs | 2 +- .../PackedVector/ColorspaceTransforms.cs | 285 ---------- src/ImageProcessorCore/Filters/DetectEdges.cs | 26 +- .../Filters/{Greyscale.cs => Grayscale.cs} | 18 +- .../{GreyscaleMode.cs => GrayscaleMode.cs} | 6 +- .../Binarization/BinaryThresholdProcessor.cs | 6 +- ...rocessor.cs => GrayscaleBt601Processor.cs} | 6 +- ...rocessor.cs => GrayscaleBt709Processor.cs} | 6 +- .../EdgeDetection/EdgeDetector2DFilter.cs | 6 +- .../EdgeDetection/EdgeDetectorFilter.cs | 6 +- .../EdgeDetection/IEdgeDetectorFilter.cs | 4 +- .../Formats/Jpg/JpegEncoderCore.cs | 2 +- .../Formats/Png/PngDecoder.cs | 4 +- .../Quantizers/Palette/PaletteQuantizer.cs | 145 ++++++ .../ImageProcessorCore.Tests/Colors/Class.cs | 109 ++++ .../Colors/ColorConversionTests.cs | 493 ++++++++++++++++++ .../{GreyscaleTest.cs => GrayscaleTest.cs} | 20 +- 28 files changed, 2578 insertions(+), 353 deletions(-) rename src/ImageProcessorCore/Colors/{PackedVector => }/Color.cs (72%) create mode 100644 src/ImageProcessorCore/Colors/ColorConstants.cs rename src/ImageProcessorCore/Colors/{PackedVector => }/ColorDefinitions.cs (100%) create mode 100644 src/ImageProcessorCore/Colors/ColorTransforms.cs create mode 100644 src/ImageProcessorCore/Colors/ColorspaceTransforms.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs delete mode 100644 src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs rename src/ImageProcessorCore/Filters/{Greyscale.cs => Grayscale.cs} (76%) rename src/ImageProcessorCore/Filters/Options/{GreyscaleMode.cs => GrayscaleMode.cs} (72%) rename src/ImageProcessorCore/Filters/Processors/ColorMatrix/{GreyscaleBt601Processor.cs => GrayscaleBt601Processor.cs} (83%) rename src/ImageProcessorCore/Filters/Processors/ColorMatrix/{GreyscaleBt709Processor.cs => GrayscaleBt709Processor.cs} (80%) create mode 100644 src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs create mode 100644 tests/ImageProcessorCore.Tests/Colors/Class.cs create mode 100644 tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs rename tests/ImageProcessorCore.Tests/Processors/Filters/{GreyscaleTest.cs => GrayscaleTest.cs} (66%) 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); } }