Browse Source

Fix up colors

Former-commit-id: 7d1bd38103443c71e8bfd1b0f0188e280302b410
Former-commit-id: 720bc4cad102119f67274389f0f15be803b24ce8
Former-commit-id: b1cf87199086fc50dcdd98b915de13e8c871c688
af/merge-core
James Jackson-South 10 years ago
parent
commit
a34aef253d
  1. 85
      src/ImageProcessorCore/Colors/Color.cs
  2. 179
      src/ImageProcessorCore/Colors/ColorConstants.cs
  3. 0
      src/ImageProcessorCore/Colors/ColorDefinitions.cs
  4. 74
      src/ImageProcessorCore/Colors/ColorTransforms.cs
  5. 292
      src/ImageProcessorCore/Colors/ColorspaceTransforms.cs
  6. 167
      src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs
  7. 192
      src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs
  8. 184
      src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs
  9. 195
      src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs
  10. 213
      src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs
  11. 206
      src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs
  12. 2
      src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs
  13. 285
      src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs
  14. 26
      src/ImageProcessorCore/Filters/DetectEdges.cs
  15. 18
      src/ImageProcessorCore/Filters/Grayscale.cs
  16. 6
      src/ImageProcessorCore/Filters/Options/GrayscaleMode.cs
  17. 6
      src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs
  18. 6
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs
  19. 6
      src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs
  20. 6
      src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs
  21. 6
      src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs
  22. 4
      src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs
  23. 2
      src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs
  24. 4
      src/ImageProcessorCore/Formats/Png/PngDecoder.cs
  25. 145
      src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs
  26. 109
      tests/ImageProcessorCore.Tests/Colors/Class.cs
  27. 493
      tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs
  28. 20
      tests/ImageProcessorCore.Tests/Processors/Filters/GrayscaleTest.cs

85
src/ImageProcessorCore/Colors/PackedVector/Color.cs → src/ImageProcessorCore/Colors/Color.cs

@ -58,7 +58,68 @@ namespace ImageProcessorCore
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
/// <param name="a">The alpha component.</param>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="hex">
/// The hexadecimal representation of the combined color components arranged
/// in rgb, rrggbb, or aarrggbb format to match web syntax.
/// </param>
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;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
/// <param name="a">The alpha component.</param>
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
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="r">The red component.</param>
/// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param>
/// <param name="a">The alpha component.</param>
public Color(byte r, byte g, byte b, byte a)
/// <param name="vector">
/// The vector containing the components for the packed vector.
/// </param>
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;
}
/// <summary>
@ -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);
}

179
src/ImageProcessorCore/Colors/ColorConstants.cs

@ -0,0 +1,179 @@
// <copyright file="ColorConstants.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.Collections.Generic;
/// <summary>
/// Provides useful color definitions.
/// </summary>
public static class ColorConstants
{
/// <summary>
/// Provides a lazy, one time method of returning the colors.
/// </summary>
private static readonly Lazy<Color[]> SafeColors = new Lazy<Color[]>(GetWebSafeColors);
/// <summary>
/// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4.
/// </summary>
public static Color[] WebSafeColors => SafeColors.Value;
/// <summary>
/// Returns an array of web safe colors.
/// </summary>
/// <returns></returns>
private static Color[] GetWebSafeColors()
{
return new List<Color>
{
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();
}
}
}

0
src/ImageProcessorCore/Colors/PackedVector/ColorDefinitions.cs → src/ImageProcessorCore/Colors/ColorDefinitions.cs

74
src/ImageProcessorCore/Colors/ColorTransforms.cs

@ -0,0 +1,74 @@
// <copyright file="ColorTransforms.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.Numerics;
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public partial struct Color
{
/// <summary>
/// Blends two colors by multiplication.
/// <remarks>
/// 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.
/// </remarks>
/// </summary>
/// <param name="source">The source color.</param>
/// <param name="destination">The destination color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
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);
}
/// <summary>
/// Linearly interpolates from one color to another based on the given weighting.
/// </summary>
/// <param name="from">The first color value.</param>
/// <param name="to">The second color value.</param>
/// <param name="amount">
/// 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.
/// </param>
/// <returns>
/// The <see cref="Color"/>
/// </returns>
public static Color Lerp(Color from, Color to, float amount)
{
return new Color(Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount));
}
}
}

292
src/ImageProcessorCore/Colors/ColorspaceTransforms.cs

@ -0,0 +1,292 @@
// <copyright file="ColorspaceTransforms.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.Numerics;
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public partial struct Color
{
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.001F;
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="Bgra32"/>.
/// </summary>
/// <param name="color">The instance of <see cref="Color"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Bgra32"/>.
/// </returns>
public static implicit operator Color(Bgra32 color)
{
return new Color(color.R, color.G, color.B, color.A);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Cmyk"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="cmykColor">The instance of <see cref="Cmyk"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
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);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="YCbCr"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="color">The instance of <see cref="YCbCr"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
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);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="CieXyz"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="color">The instance of <see cref="CieXyz"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
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);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Hsv"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="color">The instance of <see cref="Hsv"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
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);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Hsl"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="color">The instance of <see cref="Hsl"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
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);
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="CieLab"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="cieLabColor">The instance of <see cref="CieLab"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
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());
}
/// <summary>
/// Gets the color component from the given values.
/// </summary>
/// <param name="first">The first value.</param>
/// <param name="second">The second value.</param>
/// <param name="third">The third value.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
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;
}
/// <summary>
/// Moves the specific value within the acceptable range for
/// conversion.
/// <remarks>Used for converting <see cref="Hsl"/> colors to this type.</remarks>
/// </summary>
/// <param name="value">The value to shift.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
private static float MoveIntoRange(float value)
{
if (value < 0.0)
{
value += 1.0f;
}
else if (value > 1.0)
{
value -= 1.0f;
}
return value;
}
}
}

167
src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs

@ -0,0 +1,167 @@
// <copyright file="Bgra32.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an BGRA (blue, green, red, alpha) color.
/// </summary>
public struct Bgra32 : IEquatable<Bgra32>
{
/// <summary>
/// Represents a 32 bit <see cref="Bgra32"/> that has B, G, R, and A values set to zero.
/// </summary>
public static readonly Bgra32 Empty = default(Bgra32);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Bgra32"/> struct.
/// </summary>
/// <param name="b">The blue component of this <see cref="Bgra32"/>.</param>
/// <param name="g">The green component of this <see cref="Bgra32"/>.</param>
/// <param name="r">The red component of this <see cref="Bgra32"/>.</param>
/// <param name="a">The alpha component of this <see cref="Bgra32"/>.</param>
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));
}
/// <summary>
/// Gets the blue component of the color
/// </summary>
public byte B => (byte)this.backingVector.X;
/// <summary>
/// Gets the green component of the color
/// </summary>
public byte G => (byte)this.backingVector.Y;
/// <summary>
/// Gets the red component of the color
/// </summary>
public byte R => (byte)this.backingVector.Z;
/// <summary>
/// Gets the alpha component of the color
/// </summary>
public byte A => (byte)this.backingVector.W;
/// <summary>
/// Gets the <see cref="Bgra32"/> integer representation of the color.
/// </summary>
public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24);
/// <summary>
/// Gets a value indicating whether this <see cref="Bgra32"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="Bgra32"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Color"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Bgra32"/>.
/// </returns>
public static implicit operator Bgra32(Color color)
{
return new Bgra32(color.B, color.G, color.R, color.A);
}
/// <summary>
/// Compares two <see cref="Bgra32"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Bgra32"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Bgra32"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(Bgra32 left, Bgra32 right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Bgra32"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Bgra32"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Bgra32"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Bgra32 left, Bgra32 right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Bgra32)
{
Bgra32 color = (Bgra32)obj;
return this.backingVector == color.backingVector;
}
return false;
}
/// <inheritdoc/>
public override int GetHashCode()
{
return GetHashCode(this);
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Bgra32 [ Empty ]";
}
return $"Bgra32 [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]";
}
/// <inheritdoc/>
public bool Equals(Bgra32 other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Bgra32"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private static int GetHashCode(Bgra32 color) => color.backingVector.GetHashCode();
}
}

192
src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs

@ -0,0 +1,192 @@
// <copyright file="CieLab.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an CIE LAB 1976 color.
/// <see href="https://en.wikipedia.org/wiki/Lab_color_space"/>
/// </summary>
public struct CieLab : IEquatable<CieLab>, IAlmostEquatable<CieLab, float>
{
/// <summary>
/// Represents a <see cref="CieLab"/> that has L, A, B values set to zero.
/// </summary>
public static readonly CieLab Empty = default(CieLab);
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.001f;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="CieLab"/> struct.
/// </summary>
/// <param name="l">The lightness dimension.</param>
/// <param name="a">The a (green - magenta) component.</param>
/// <param name="b">The b (blue - yellow) component.</param>
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));
}
/// <summary>
/// Gets the lightness dimension.
/// <remarks>A value ranging between 0 (black), 100 (diffuse white) or higher (specular white).</remarks>
/// </summary>
public float L => this.backingVector.X;
/// <summary>
/// Gets the a color component.
/// <remarks>Negative is green, positive magenta.</remarks>
/// </summary>
public float A => this.backingVector.Y;
/// <summary>
/// Gets the b color component.
/// <remarks>Negative is blue, positive is yellow</remarks>
/// </summary>
public float B => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="CieLab"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="CieLab"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Color"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="CieLab"/>.
/// </returns>
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);
}
/// <summary>
/// Compares two <see cref="CieLab"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="CieLab"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLab"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(CieLab left, CieLab right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="CieLab"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="CieLab"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieLab"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(CieLab left, CieLab right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return GetHashCode(this);
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "CieLab [Empty]";
}
return $"CieLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is CieLab)
{
return this.Equals((CieLab)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(CieLab other)
{
return this.AlmostEquals(other, Epsilon);
}
/// <inheritdoc/>
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;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="CieLab"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private static int GetHashCode(CieLab color) => color.backingVector.GetHashCode();
}
}

184
src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs

@ -0,0 +1,184 @@
// <copyright file="CieXyz.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an CIE 1931 color
/// <see href="https://en.wikipedia.org/wiki/CIE_1931_color_space"/>
/// </summary>
public struct CieXyz : IEquatable<CieXyz>, IAlmostEquatable<CieXyz, float>
{
/// <summary>
/// Represents a <see cref="CieXyz"/> that has Y, Cb, and Cr values set to zero.
/// </summary>
public static readonly CieXyz Empty = default(CieXyz);
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.001f;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="CieXyz"/> struct.
/// </summary>
/// <param name="y">The y luminance component.</param>
/// <param name="x">X is a mix (a linear combination) of cone response curves chosen to be nonnegative</param>
/// <param name="z">Z is quasi-equal to blue stimulation, or the S cone of the human eye.</param>
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);
}
/// <summary>
/// Gets the Y luminance component.
/// <remarks>A value ranging between 380 and 780.</remarks>
/// </summary>
public float X => this.backingVector.X;
/// <summary>
/// Gets the Cb chroma component.
/// <remarks>A value ranging between 380 and 780.</remarks>
/// </summary>
public float Y => this.backingVector.Y;
/// <summary>
/// Gets the Cr chroma component.
/// <remarks>A value ranging between 380 and 780.</remarks>
/// </summary>
public float Z => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="CieXyz"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="CieXyz"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Color"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="CieXyz"/>.
/// </returns>
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);
}
/// <summary>
/// Compares two <see cref="CieXyz"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="CieXyz"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieXyz"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(CieXyz left, CieXyz right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="CieXyz"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="CieXyz"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="CieXyz"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(CieXyz left, CieXyz right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return GetHashCode(this);
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "CieXyz [ Empty ]";
}
return $"CieXyz [ X={this.X:#0.##}, Y={this.Y:#0.##}, Z={this.Z:#0.##} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is CieXyz)
{
return this.Equals((CieXyz)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(CieXyz other)
{
return this.AlmostEquals(other, Epsilon);
}
/// <inheritdoc/>
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;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Hsv"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private static int GetHashCode(CieXyz color) => color.backingVector.GetHashCode();
}
}

195
src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs

@ -0,0 +1,195 @@
// <copyright file="Cmyk.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an CMYK (cyan, magenta, yellow, keyline) color.
/// </summary>
public struct Cmyk : IEquatable<Cmyk>, IAlmostEquatable<Cmyk, float>
{
/// <summary>
/// Represents a <see cref="Cmyk"/> that has C, M, Y, and K values set to zero.
/// </summary>
public static readonly Cmyk Empty = default(Cmyk);
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.001f;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Cmyk"/> struct.
/// </summary>
/// <param name="c">The cyan component.</param>
/// <param name="m">The magenta component.</param>
/// <param name="y">The yellow component.</param>
/// <param name="k">The keyline black component.</param>
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);
}
/// <summary>
/// Gets the cyan color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float C => this.backingVector.X;
/// <summary>
/// Gets the magenta color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float M => this.backingVector.Y;
/// <summary>
/// Gets the yellow color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float Y => this.backingVector.Z;
/// <summary>
/// Gets the keyline black color component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float K => this.backingVector.W;
/// <summary>
/// Gets a value indicating whether this <see cref="Cmyk"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="Cmyk"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Bgra32"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Cmyk"/>.
/// </returns>
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);
}
/// <summary>
/// Compares two <see cref="Cmyk"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Cmyk"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Cmyk"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(Cmyk left, Cmyk right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Cmyk"/> objects for inequality
/// </summary>
/// <param name="left">
/// The <see cref="Cmyk"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Cmyk"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Cmyk left, Cmyk right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return GetHashCode(this);
}
/// <inheritdoc/>
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.##}]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Cmyk)
{
return this.Equals((Cmyk)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(Cmyk other)
{
return this.AlmostEquals(other, Epsilon);
}
/// <inheritdoc/>
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;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Cmyk"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private static int GetHashCode(Cmyk color) => color.backingVector.GetHashCode();
}
}

213
src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs

@ -0,0 +1,213 @@
// <copyright file="Hsl.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents a Hsl (hue, saturation, lightness) color.
/// </summary>
public struct Hsl : IEquatable<Hsl>, IAlmostEquatable<Hsl, float>
{
/// <summary>
/// Represents a <see cref="Hsl"/> that has H, S, and L values set to zero.
/// </summary>
public static readonly Hsl Empty = default(Hsl);
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.001F;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Hsl"/> struct.
/// </summary>
/// <param name="h">The h hue component.</param>
/// <param name="s">The s saturation component.</param>
/// <param name="l">The l value (lightness) component.</param>
public Hsl(float h, float s, float l)
{
this.backingVector = Vector3.Clamp(new Vector3(h, s, l), Vector3.Zero, new Vector3(360, 1, 1));
}
/// <summary>
/// Gets the hue component.
/// <remarks>A value ranging between 0 and 360.</remarks>
/// </summary>
public float H => this.backingVector.X;
/// <summary>
/// Gets the saturation component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float S => this.backingVector.Y;
/// <summary>
/// Gets the lightness component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float L => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="Hsl"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="Hsl"/>.
/// </summary>
/// <param name="color">The instance of <see cref="Color"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Hsl"/>.
/// </returns>
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);
}
/// <summary>
/// Compares two <see cref="Hsl"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Hsl"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Hsl"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(Hsl left, Hsl right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Hsl"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Hsl"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Hsl"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Hsl left, Hsl right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return GetHashCode(this);
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Hsl [ Empty ]";
}
return $"Hsl [ H={this.H:#0.##}, S={this.S:#0.##}, L={this.L:#0.##} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Hsl)
{
return this.Equals((Hsl)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(Hsl other)
{
return this.AlmostEquals(other, Epsilon);
}
/// <inheritdoc/>
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;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Hsl"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private static int GetHashCode(Hsl color) => color.backingVector.GetHashCode();
}
}

206
src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs

@ -0,0 +1,206 @@
// <copyright file="Hsv.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness).
/// </summary>
public struct Hsv : IEquatable<Hsv>, IAlmostEquatable<Hsv, float>
{
/// <summary>
/// Represents a <see cref="Hsv"/> that has H, S, and V values set to zero.
/// </summary>
public static readonly Hsv Empty = default(Hsv);
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.001F;
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector3 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Hsv"/> struct.
/// </summary>
/// <param name="h">The h hue component.</param>
/// <param name="s">The s saturation component.</param>
/// <param name="v">The v value (brightness) component.</param>
public Hsv(float h, float s, float v)
{
this.backingVector = Vector3.Clamp(new Vector3(h, s, v), Vector3.Zero, new Vector3(360, 1, 1));
}
/// <summary>
/// Gets the hue component.
/// <remarks>A value ranging between 0 and 360.</remarks>
/// </summary>
public float H => this.backingVector.X;
/// <summary>
/// Gets the saturation component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float S => this.backingVector.Y;
/// <summary>
/// Gets the value (brightness) component.
/// <remarks>A value ranging between 0 and 1.</remarks>
/// </summary>
public float V => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="Hsv"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="Hsv"/>.
/// </summary>
/// <param name="color">The instance of <see cref="Color"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Hsv"/>.
/// </returns>
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);
}
/// <summary>
/// Compares two <see cref="Hsv"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Hsv"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Hsv"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(Hsv left, Hsv right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Hsv"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Hsv"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Hsv"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Hsv left, Hsv right)
{
return !left.Equals(right);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return GetHashCode(this);
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Hsv [ Empty ]";
}
return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Hsv)
{
return this.Equals((Hsv)obj);
}
return false;
}
/// <inheritdoc/>
public bool Equals(Hsv other)
{
return this.AlmostEquals(other, Epsilon);
}
/// <inheritdoc/>
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;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Hsv"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private static int GetHashCode(Hsv color) => color.backingVector.GetHashCode();
}
}

2
src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs

@ -24,7 +24,7 @@ namespace ImageProcessorCore
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.001f;
private const float Epsilon = 0.001F;
/// <summary>
/// The backing vector for SIMD support.

285
src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs

@ -1,285 +0,0 @@
// <copyright file="ColorspaceTransforms.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public partial struct Color
{
///// <summary>
///// Allows the implicit conversion of an instance of <see cref="Color"/> to a
///// <see cref="Bgra32"/>.
///// </summary>
///// <param name="color">The instance of <see cref="Color"/> to convert.</param>
///// <returns>
///// An instance of <see cref="Bgra32"/>.
///// </returns>
//public static implicit operator Color(Bgra32 color)
//{
// return new Color(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
//}
///// <summary>
///// Allows the implicit conversion of an instance of <see cref="Cmyk"/> to a
///// <see cref="Color"/>.
///// </summary>
///// <param name="cmykColor">The instance of <see cref="Cmyk"/> to convert.</param>
///// <returns>
///// An instance of <see cref="Color"/>.
///// </returns>
//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);
//}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="YCbCr"/> to a
/// <see cref="Color"/>.
/// </summary>
/// <param name="color">The instance of <see cref="YCbCr"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
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);
}
///// <summary>
///// Allows the implicit conversion of an instance of <see cref="CieXyz"/> to a
///// <see cref="Color"/>.
///// </summary>
///// <param name="color">The instance of <see cref="CieXyz"/> to convert.</param>
///// <returns>
///// An instance of <see cref="Color"/>.
///// </returns>
//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));
//}
///// <summary>
///// Allows the implicit conversion of an instance of <see cref="Hsv"/> to a
///// <see cref="Color"/>.
///// </summary>
///// <param name="color">The instance of <see cref="Hsv"/> to convert.</param>
///// <returns>
///// An instance of <see cref="Color"/>.
///// </returns>
//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);
//}
///// <summary>
///// Allows the implicit conversion of an instance of <see cref="Hsl"/> to a
///// <see cref="Color"/>.
///// </summary>
///// <param name="color">The instance of <see cref="Hsl"/> to convert.</param>
///// <returns>
///// An instance of <see cref="Color"/>.
///// </returns>
//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);
//}
///// <summary>
///// Allows the implicit conversion of an instance of <see cref="CieLab"/> to a
///// <see cref="Color"/>.
///// </summary>
///// <param name="cieLabColor">The instance of <see cref="CieLab"/> to convert.</param>
///// <returns>
///// An instance of <see cref="Color"/>.
///// </returns>
//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));
//}
/// <summary>
/// Gets the color component from the given values.
/// </summary>
/// <param name="first">The first value.</param>
/// <param name="second">The second value.</param>
/// <param name="third">The third value.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
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;
}
/// <summary>
/// Moves the specific value within the acceptable range for
/// conversion.
/// <remarks>Used for converting <see cref="Hsl"/> colors to this type.</remarks>
/// </summary>
/// <param name="value">The value to shift.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
private static float MoveIntoRange(float value)
{
if (value < 0.0)
{
value += 1.0f;
}
else if (value > 1.0)
{
value -= 1.0f;
}
return value;
}
}
}

26
src/ImageProcessorCore/Filters/DetectEdges.cs

@ -14,7 +14,7 @@ namespace ImageProcessorCore
{
/// <summary>
/// Detects any edges within the image. Uses the <see cref="SobelProcessor"/> filter
/// operating in greyscale mode.
/// operating in Grayscale mode.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
@ -25,7 +25,7 @@ namespace ImageProcessorCore
where T : IPackedVector<TP>
where TP : struct
{
return DetectEdges(source, source.Bounds, new SobelProcessor<T, TP> { Greyscale = true }, progressHandler);
return DetectEdges(source, source.Bounds, new SobelProcessor<T, TP> { Grayscale = true }, progressHandler);
}
/// <summary>
@ -35,10 +35,10 @@ namespace ImageProcessorCore
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="filter">The filter for detecting edges.</param>
/// <param name="greyscale">Whether to convert the image to greyscale first. Defaults to true.</param>
/// <param name="Grayscale">Whether to convert the image to Grayscale first. Defaults to true.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T,TP}"/>.</returns>
public static Image<T, TP> DetectEdges<T, TP>(this Image<T, TP> source, EdgeDetection filter, bool greyscale = true, ProgressEventHandler progressHandler = null)
public static Image<T, TP> DetectEdges<T, TP>(this Image<T, TP> source, EdgeDetection filter, bool Grayscale = true, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
@ -47,39 +47,39 @@ namespace ImageProcessorCore
switch (filter)
{
case EdgeDetection.Kayyali:
processor = new KayyaliProcessor<T, TP> { Greyscale = greyscale };
processor = new KayyaliProcessor<T, TP> { Grayscale = Grayscale };
break;
case EdgeDetection.Kirsch:
processor = new KirschProcessor<T, TP> { Greyscale = greyscale };
processor = new KirschProcessor<T, TP> { Grayscale = Grayscale };
break;
case EdgeDetection.Lapacian3X3:
processor = new Laplacian3X3Processor<T, TP> { Greyscale = greyscale };
processor = new Laplacian3X3Processor<T, TP> { Grayscale = Grayscale };
break;
case EdgeDetection.Lapacian5X5:
processor = new Laplacian5X5Processor<T, TP> { Greyscale = greyscale };
processor = new Laplacian5X5Processor<T, TP> { Grayscale = Grayscale };
break;
case EdgeDetection.LaplacianOfGaussian:
processor = new LaplacianOfGaussianProcessor<T, TP> { Greyscale = greyscale };
processor = new LaplacianOfGaussianProcessor<T, TP> { Grayscale = Grayscale };
break;
case EdgeDetection.Prewitt:
processor = new PrewittProcessor<T, TP> { Greyscale = greyscale };
processor = new PrewittProcessor<T, TP> { Grayscale = Grayscale };
break;
case EdgeDetection.RobertsCross:
processor = new RobertsCrossProcessor<T, TP> { Greyscale = greyscale };
processor = new RobertsCrossProcessor<T, TP> { Grayscale = Grayscale };
break;
case EdgeDetection.Scharr:
processor = new ScharrProcessor<T, TP> { Greyscale = greyscale };
processor = new ScharrProcessor<T, TP> { Grayscale = Grayscale };
break;
default:
processor = new ScharrProcessor<T, TP> { Greyscale = greyscale };
processor = new ScharrProcessor<T, TP> { Grayscale = Grayscale };
break;
}

18
src/ImageProcessorCore/Filters/Greyscale.cs → src/ImageProcessorCore/Filters/Grayscale.cs

@ -1,4 +1,4 @@
// <copyright file="Greyscale.cs" company="James Jackson-South">
// <copyright file="Grayscale.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -13,7 +13,7 @@ namespace ImageProcessorCore
public static partial class ImageExtensions
{
/// <summary>
/// Applies greyscale toning to the image.
/// Applies Grayscale toning to the image.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
@ -21,15 +21,15 @@ namespace ImageProcessorCore
/// <param name="mode">The formula to apply to perform the operation.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T,TP}"/>.</returns>
public static Image<T, TP> Greyscale<T, TP>(this Image<T, TP> source, GreyscaleMode mode = GreyscaleMode.Bt709, ProgressEventHandler progressHandler = null)
public static Image<T, TP> Grayscale<T, TP>(this Image<T, TP> source, GrayscaleMode mode = GrayscaleMode.Bt709, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
return Greyscale(source, source.Bounds, mode, progressHandler);
return Grayscale(source, source.Bounds, mode, progressHandler);
}
/// <summary>
/// Applies greyscale toning to the image.
/// Applies Grayscale toning to the image.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
@ -40,13 +40,13 @@ namespace ImageProcessorCore
/// <param name="mode">The formula to apply to perform the operation.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image{T,TP}"/>.</returns>
public static Image<T, TP> Greyscale<T, TP>(this Image<T, TP> source, Rectangle rectangle, GreyscaleMode mode = GreyscaleMode.Bt709, ProgressEventHandler progressHandler = null)
public static Image<T, TP> Grayscale<T, TP>(this Image<T, TP> source, Rectangle rectangle, GrayscaleMode mode = GrayscaleMode.Bt709, ProgressEventHandler progressHandler = null)
where T : IPackedVector<TP>
where TP : struct
{
IImageProcessor<T, TP> processor = mode == GreyscaleMode.Bt709
? (IImageProcessor<T, TP>)new GreyscaleBt709Processor<T, TP>()
: new GreyscaleBt601Processor<T, TP>();
IImageProcessor<T, TP> processor = mode == GrayscaleMode.Bt709
? (IImageProcessor<T, TP>)new GrayscaleBt709Processor<T, TP>()
: new GrayscaleBt601Processor<T, TP>();
processor.OnProgress += progressHandler;

6
src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs → src/ImageProcessorCore/Filters/Options/GrayscaleMode.cs

@ -1,4 +1,4 @@
// <copyright file="GreyscaleMode.cs" company="James Jackson-South">
// <copyright file="GrayscaleMode.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -6,9 +6,9 @@
namespace ImageProcessorCore
{
/// <summary>
/// Enumerates the various types of defined greyscale filters.
/// Enumerates the various types of defined Grayscale filters.
/// </summary>
public enum GreyscaleMode
public enum GrayscaleMode
{
/// <summary>
/// ITU-R Recommendation BT.709

6
src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs

@ -9,7 +9,7 @@ namespace ImageProcessorCore.Processors
/// <summary>
/// An <see cref="IImageProcessor{T,TP}"/> to perform binary threshold filtering against an
/// <see cref="Image"/>. The image will be converted to greyscale before thresholding
/// <see cref="Image"/>. The image will be converted to Grayscale before thresholding
/// occurs.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
@ -58,7 +58,7 @@ namespace ImageProcessorCore.Processors
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
new GreyscaleBt709Processor<T, TP>().Apply(source, source, sourceRectangle);
new GrayscaleBt709Processor<T, TP>().Apply(source, source, sourceRectangle);
}
/// <inheritdoc/>
@ -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();

6
src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs → src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs

@ -1,4 +1,4 @@
// <copyright file="GreyscaleBt601Processor.cs" company="James Jackson-South">
// <copyright file="GrayscaleBt601Processor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -8,12 +8,12 @@ namespace ImageProcessorCore.Processors
using System.Numerics;
/// <summary>
/// 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 <see href="https://en.wikipedia.org/wiki/Luma_%28video%29#Rec._601_luma_versus_Rec._709_luma_coefficients"/>.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class GreyscaleBt601Processor<T, TP> : ColorMatrixFilter<T, TP>
public class GrayscaleBt601Processor<T, TP> : ColorMatrixFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{

6
src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs → src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs

@ -1,4 +1,4 @@
// <copyright file="GreyscaleBt709Processor.cs" company="James Jackson-South">
// <copyright file="GrayscaleBt709Processor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -8,10 +8,10 @@ namespace ImageProcessorCore.Processors
using System.Numerics;
/// <summary>
/// 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 <see href="https://en.wikipedia.org/wiki/Rec._709#Luma_coefficients"/>.
/// </summary>
public class GreyscaleBt709Processor<T, TP> : ColorMatrixFilter<T, TP>
public class GrayscaleBt709Processor<T, TP> : ColorMatrixFilter<T, TP>
where T : IPackedVector<TP>
where TP : struct
{

6
src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs

@ -14,14 +14,14 @@ namespace ImageProcessorCore.Processors
where TP : struct
{
/// <inheritdoc/>
public bool Greyscale { get; set; }
public bool Grayscale { get; set; }
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (this.Greyscale)
if (this.Grayscale)
{
new GreyscaleBt709Processor<T, TP>().Apply(source, source, sourceRectangle);
new GrayscaleBt709Processor<T, TP>().Apply(source, source, sourceRectangle);
}
}
}

6
src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs

@ -14,14 +14,14 @@ namespace ImageProcessorCore.Processors
where TP : struct
{
/// <inheritdoc/>
public bool Greyscale { get; set; }
public bool Grayscale { get; set; }
/// <inheritdoc/>
protected override void OnApply(ImageBase<T, TP> target, ImageBase<T, TP> source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (this.Greyscale)
if (this.Grayscale)
{
new GreyscaleBt709Processor<T, TP>().Apply(source, source, sourceRectangle);
new GrayscaleBt709Processor<T, TP>().Apply(source, source, sourceRectangle);
}
}
}

4
src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs

@ -21,8 +21,8 @@ namespace ImageProcessorCore.Processors
{
/// <summary>
/// Gets or sets a value indicating whether to convert the
/// image to greyscale before performing edge detection.
/// image to Grayscale before performing edge detection.
/// </summary>
bool Greyscale { get; set; }
bool Grayscale { get; set; }
}
}

2
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;

4
src/ImageProcessorCore/Formats/Png/PngDecoder.cs

@ -21,8 +21,8 @@ namespace ImageProcessorCore.Formats
/// <list type="bullet">
/// <item>RGBA (True color) with alpha (8 bit).</item>
/// <item>RGB (True color) without alpha (8 bit).</item>
/// <item>Greyscale with alpha (8 bit).</item>
/// <item>Greyscale without alpha (8 bit).</item>
/// <item>Grayscale with alpha (8 bit).</item>
/// <item>Grayscale without alpha (8 bit).</item>
/// <item>Palette Index with alpha (8 bit).</item>
/// <item>Palette Index without alpha (8 bit).</item>
/// </list>

145
src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs

@ -0,0 +1,145 @@
// <copyright file="PaletteQuantizer.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Quantizers
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Encapsulates methods to create a quantized image based upon the given palette.
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public class PaletteQuantizer<T, TP> : Quantizer<T, TP>
where T : IPackedVector<TP>
where TP : struct
{
/// <summary>
/// A lookup table for colors
/// </summary>
private readonly ConcurrentDictionary<string, byte> colorMap = new ConcurrentDictionary<string, byte>();
/// <summary>
/// List of all colors in the palette
/// </summary>
private T[] colors;
/// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{T,TP}"/> class.
/// </summary>
/// <param name="palette">
/// The color palette. If none is given this will default to the web safe colors defined
/// in the CSS Color Module Level 4.
/// </param>
public PaletteQuantizer(T[] palette = null)
: base(true)
{
if (palette == null)
{
Color[] constants = ColorConstants.WebSafeColors;
List<T> safe = new List<T> { 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;
}
}
/// <inheritdoc/>
public override QuantizedImage<T, TP> Quantize(ImageBase<T, TP> image, int maxColors)
{
Array.Resize(ref this.colors, maxColors.Clamp(1, 256));
return base.Quantize(image, maxColors);
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
protected override List<T> GetPalette()
{
return this.colors.ToList();
}
}
}

109
tests/ImageProcessorCore.Tests/Colors/Class.cs

@ -0,0 +1,109 @@
// <copyright file="ColorTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System.Numerics;
namespace ImageProcessorCore.Tests
{
using System;
using Xunit;
/// <summary>
/// Tests the <see cref="Color"/> struct.
/// </summary>
public class ColorTests
{
/// <summary>
/// Tests the equality operators for equality.
/// </summary>
[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);
}
/// <summary>
/// Tests the equality operators for inequality.
/// </summary>
[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);
}
/// <summary>
/// Tests whether the color constructor correctly assign properties.
/// </summary>
[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);
}
/// <summary>
/// Tests to see that in the input hex matches that of the output.
/// </summary>
[Fact]
public void ConvertHex()
{
const string First = "FF000000";
Color color = Color.Black;
string second = color.PackedValue().ToString("X");
Assert.Equal(First, second);
}
}
}

493
tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs

@ -0,0 +1,493 @@
// <copyright file="ColorConversionTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Tests
{
using System;
using System.Diagnostics.CodeAnalysis;
using Xunit;
/// <summary>
/// Test conversion between the various color structs.
/// </summary>
/// <remarks>
/// Output values have been compared with <see cref="http://colormine.org/color-converter"/>
/// and <see cref="http://www.colorhexa.com/"/> for accuracy.
/// </remarks>
public class ColorConversionTests
{
/// <summary>
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="YCbCr"/>.
/// </summary>
[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);
}
/// <summary>
/// Tests the implicit conversion from <see cref="YCbCr"/> to <see cref="Color"/>.
/// </summary>
[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);
}
/// <summary>
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="CieXyz"/>.
/// Comparison values obtained from
/// http://colormine.org/convert/rgb-to-xyz
/// </summary>
[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);
}
/// <summary>
/// Tests the implicit conversion from <see cref="CieXyz"/> to <see cref="Color"/>.
/// Comparison values obtained from
/// http://colormine.org/convert/rgb-to-xyz
/// </summary>
[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);
//}
}
/// <summary>
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="Hsv"/>.
/// </summary>
[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);
}
/// <summary>
/// Tests the implicit conversion from <see cref="Hsv"/> to <see cref="Color"/>.
/// </summary>
[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);
//}
}
/// <summary>
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="Hsl"/>.
/// </summary>
[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);
}
/// <summary>
/// Tests the implicit conversion from <see cref="Hsl"/> to <see cref="Color"/>.
/// </summary>
[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);
//}
}
/// <summary>
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="Cmyk"/>.
/// </summary>
[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);
}
/// <summary>
/// Tests the implicit conversion from <see cref="Cmyk"/> to <see cref="Color"/>.
/// </summary>
[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);
//}
}
/// <summary>
/// Tests the implicit conversion from <see cref="Color"/> to <see cref="CieLab"/>.
/// Comparison values obtained from
/// http://colormine.org/convert/rgb-to-lab
/// </summary>
[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);
}
/// <summary>
/// Tests the implicit conversion from <see cref="CieLab"/> to <see cref="Color"/>.
/// </summary>
/// 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);
//}
}
}
}

20
tests/ImageProcessorCore.Tests/Processors/Filters/GreyscaleTest.cs → tests/ImageProcessorCore.Tests/Processors/Filters/GrayscaleTest.cs

@ -1,4 +1,4 @@
// <copyright file="GreyscaleTest.cs" company="James Jackson-South">
// <copyright file="GrayscaleTest.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -10,20 +10,20 @@ namespace ImageProcessorCore.Tests
using Xunit;
public class GreyscaleTest : FileTestBase
public class GrayscaleTest : FileTestBase
{
public static readonly TheoryData<GreyscaleMode> GreyscaleValues
= new TheoryData<GreyscaleMode>
public static readonly TheoryData<GrayscaleMode> GrayscaleValues
= new TheoryData<GrayscaleMode>
{
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);
}
}
Loading…
Cancel
Save