diff --git a/RGB.NET.Core/Color/HclColor.cs b/RGB.NET.Core/Color/HclColor.cs new file mode 100644 index 0000000..ee05be7 --- /dev/null +++ b/RGB.NET.Core/Color/HclColor.cs @@ -0,0 +1,203 @@ +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedMember.Global +using System; + +namespace RGB.NET.Core +{ + public static class HclColor + { + #region Getter + + /// + /// Gets the H component value (Hcl-color space) of this in the range [0..360]. + /// + /// + /// + public static float GetHclH(this in Color color) => color.GetHcl().l; + + /// + /// Gets the c component value (Hcl-color space) of this in the range [0..1]. + /// + /// + /// + public static float GetHclC(this in Color color) => color.GetHcl().c; + + /// + /// Gets the l component value (Hcl-color space) of this in the range [0..1]. + /// + /// + /// + public static float GetHclL(this in Color color) => color.GetHcl().h; + + /// + /// Gets the H, c and l component values (Hcl-color space) of this . + /// H in the range [0..360]. + /// c in the range [0..1]. + /// l in the range [0..1]. + /// + /// + /// + public static (float h, float c, float l) GetHcl(this in Color color) + => CalculateHclFromRGB(color.R, color.G, color.B); + + #endregion + + #region Manipulation + + /// + /// Adds the given Hcl values to this color. + /// + /// The H value to add. + /// The c value to add. + /// The l value to add. + /// The new color after the modification. + public static Color AddHcl(this in Color color, float h = 0, float c = 0, float l = 0) + { + (float cH, float cC, float cL) = color.GetHcl(); + return Create(color.A, cH + h, cC + c, cL + l); + } + + /// + /// Subtracts the given Hcl values to this color. + /// + /// The H value to subtract. + /// The c value to subtract. + /// The l value to subtract. + /// The new color after the modification. + public static Color SubtractHcl(this in Color color, float h = 0, float c = 0, float l = 0) + { + (float cH, float cC, float cL) = color.GetHcl(); + return Create(color.A, cH - h, cC - c, cL - l); + } + + /// + /// Multiplies the given Hcl values to this color. + /// + /// The H value to multiply. + /// The c value to multiply. + /// The l value to multiply. + /// The new color after the modification. + public static Color MultiplyHcl(this in Color color, float h = 1, float c = 1, float l = 1) + { + (float cH, float cC, float cL) = color.GetHcl(); + return Create(color.A, cH * h, cC * c, cL * l); + } + + /// + /// Divides the given Hcl values to this color. + /// + /// The H value to divide. + /// The c value to divide. + /// The l value to divide. + /// The new color after the modification. + public static Color DivideHcl(this in Color color, float h = 1, float c = 1, float l = 1) + { + (float cH, float cC, float cL) = color.GetHcl(); + return Create(color.A, cH / h, cC / c, cL / l); + } + + /// + /// Sets the given X value of this color. + /// + /// The H value to set. + /// The c value to set. + /// The l value to set. + /// The new color after the modification. + public static Color SetHcl(this in Color color, float? h = null, float? c = null, float? l = null) + { + (float cH, float cC, float cL) = color.GetHcl(); + return Create(color.A, h ?? cH, c ?? cC, l ?? cL); + } + + #endregion + + #region Factory + + /// + /// Creates a new instance of the struct using Hcl-Values. + /// + /// The H component value of this . + /// The c component value of this . + /// The l component value of this . + /// The color created from the values. + public static Color Create(float h, float c, float l) + => Create(1.0f, h, c, l); + + /// + /// Creates a new instance of the struct using alpha and Hcl-Values. + /// + /// The alphc component value of this . + /// The H component value of this . + /// The c component value of this . + /// The l component value of this . + /// The color created from the values. + public static Color Create(byte alpha, float h, float c, float l) + => Create((float)alpha / byte.MaxValue, h, c, l); + + /// + /// Creates a new instance of the struct using alpha and Hcl-Values. + /// + /// The alphc component value of this . + /// The H component value of this . + /// The c component value of this . + /// The l component value of this . + /// The color created from the values. + public static Color Create(int alpha, float h, float c, float l) + => Create((float)alpha / byte.MaxValue, h, c, l); + + /// + /// Creates a new instance of the struct using alpha and Hcl-Values. + /// + /// The alphc component value of this . + /// The H component value of this . + /// The c component value of this . + /// The l component value of this . + /// The color created from the values. + public static Color Create(float alpha, float h, float c, float l) + { + (float r, float g, float _b) = CalculateRGBFromHcl(h, c, l); + return new Color(alpha, r, g, _b); + } + + #endregion + + #region Helper + + private static (float h, float c, float l) CalculateHclFromRGB(float r, float g, float b) + { + const float RADIANS_DEGREES_CONVERSION = 180.0f / MathF.PI; + + (float l, float a, float _b) = LabColor.CalculateLabFromRGB(r, g, b); + + float h, c; + if (r.EqualsInTolerance(g) && r.EqualsInTolerance(b)) //DarthAffe 26.02.2021: The cumulated rounding errors are big enough to cause problems in that case + { + h = 0; + c = 0; + } + else + { + h = MathF.Atan2(_b, a); + if (h >= 0) h *= RADIANS_DEGREES_CONVERSION; + else h = 360 - (-h * RADIANS_DEGREES_CONVERSION); + + c = MathF.Sqrt((a * a) + (_b * _b)); + } + + return (h, c, l); + } + + private static (float r, float g, float b) CalculateRGBFromHcl(float h, float c, float l) + { + const float DEGREES_RADIANS_CONVERSION = MathF.PI / 180.0f; + + h *= DEGREES_RADIANS_CONVERSION; + float a = c * MathF.Cos(h); + float b = c * MathF.Sign(h); + + return LabColor.CalculateRGBFromLab(l, a, b); + } + + #endregion + } +} diff --git a/RGB.NET.Core/Color/LabColor.cs b/RGB.NET.Core/Color/LabColor.cs new file mode 100644 index 0000000..6b2b17b --- /dev/null +++ b/RGB.NET.Core/Color/LabColor.cs @@ -0,0 +1,228 @@ +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedMember.Global +using System; + +namespace RGB.NET.Core +{ + public static class LabColor + { + #region Getter + + /// + /// Gets the L component value (Lab-color space) of this in the range [0..100]. + /// + /// + /// + public static float GetLabL(this in Color color) => color.GetLab().l; + + /// + /// Gets the a component value (Lab-color space) of this in the range [0..1]. + /// + /// + /// + public static float GetLabA(this in Color color) => color.GetLab().a; + + /// + /// Gets the b component value (Lab-color space) of this in the range [0..1]. + /// + /// + /// + public static float GetLabB(this in Color color) => color.GetLab().b; + + /// + /// Gets the L, a and b component values (Lab-color space) of this . + /// L in the range [0..100]. + /// a in the range [0..1]. + /// b in the range [0..1]. + /// + /// + /// + public static (float l, float a, float b) GetLab(this in Color color) + => CalculateLabFromRGB(color.R, color.G, color.B); + + #endregion + + #region Manipulation + + /// + /// Adds the given Lab values to this color. + /// + /// The L value to add. + /// The a value to add. + /// The b value to add. + /// The new color after the modification. + public static Color AddLab(this in Color color, float l = 0, float a = 0, float b = 0) + { + (float cL, float cA, float cB) = color.GetLab(); + return Create(color.A, cL + l, cA + a, cB + b); + } + + /// + /// Subtracts the given Lab values to this color. + /// + /// The L value to subtract. + /// The a value to subtract. + /// The b value to subtract. + /// The new color after the modification. + public static Color SubtractLab(this in Color color, float l = 0, float a = 0, float b = 0) + { + (float cL, float cA, float cB) = color.GetLab(); + return Create(color.A, cL - l, cA - a, cB - b); + } + + /// + /// Multiplies the given Lab values to this color. + /// + /// The L value to multiply. + /// The a value to multiply. + /// The b value to multiply. + /// The new color after the modification. + public static Color MultiplyLab(this in Color color, float l = 1, float a = 1, float b = 1) + { + (float cL, float cA, float cB) = color.GetLab(); + return Create(color.A, cL * l, cA * a, cB * b); + } + + /// + /// Divides the given Lab values to this color. + /// + /// The L value to divide. + /// The a value to divide. + /// The b value to divide. + /// The new color after the modification. + public static Color DivideLab(this in Color color, float l = 1, float a = 1, float b = 1) + { + (float cL, float cA, float cB) = color.GetLab(); + return Create(color.A, cL / l, cA / a, cB / b); + } + + /// + /// Sets the given X valueof this color. + /// + /// The L value to set. + /// The a value to set. + /// The b value to set. + /// The new color after the modification. + public static Color SetLab(this in Color color, float? l = null, float? a = null, float? b = null) + { + (float cL, float cA, float cB) = color.GetLab(); + return Create(color.A, l ?? cL, a ?? cA, b ?? cB); + } + + #endregion + + #region Factory + + /// + /// Creates a new instance of the struct using Lab-Values. + /// + /// The L component value of this . + /// The a component value of this . + /// The b component value of this . + /// The color created from the values. + public static Color Create(float l, float a, float b) + => Create(1.0f, l, a, b); + + /// + /// Creates a new instance of the struct using alpha and Lab-Values. + /// + /// The alpha component value of this . + /// The L component value of this . + /// The a component value of this . + /// The b component value of this . + /// The color created from the values. + public static Color Create(byte alpha, float l, float a, float b) + => Create((float)alpha / byte.MaxValue, l, a, b); + + /// + /// Creates a new instance of the struct using alpha and Lab-Values. + /// + /// The alpha component value of this . + /// The L component value of this . + /// The a component value of this . + /// The b component value of this . + /// The color created from the values. + public static Color Create(int alpha, float l, float a, float b) + => Create((float)alpha / byte.MaxValue, l, a, b); + + /// + /// Creates a new instance of the struct using alpha and Lab-Values. + /// + /// The alpha component value of this . + /// The L component value of this . + /// The a component value of this . + /// The b component value of this . + /// The color created from the values. + public static Color Create(float alpha, float l, float a, float b) + { + (float r, float g, float _b) = CalculateRGBFromLab(l, a, b); + return new Color(alpha, r, g, _b); + } + + #endregion + + #region Helper + + internal static (float l, float a, float b) CalculateLabFromRGB(float r, float g, float b) + { + (float x, float y, float z) = XYZColor.CaclulateXYZFromRGB(r, g, b); + return CaclulateLabFromXYZ(x, y, z); + + //const float LAB_MULTIPLIER = (1f / 127f) * 0.5f; + + //(float x, float y, float z) = XYZColor.CaclulateXYZFromRGB(r, g, b); + //(float l, float a, float _b) = CaclulateLabFromXYZ(x, y, z); + //return (l / 100.0f, 0.5f + (a * LAB_MULTIPLIER), 0.5f + (_b * LAB_MULTIPLIER)); + } + + internal static (float r, float g, float b) CalculateRGBFromLab(float l, float a, float b) + { + (float x, float y, float z) = CalculateXYZFromLab(l, a, b); + return XYZColor.CalculateRGBFromXYZ(x, y, z); + + //(float x, float y, float z) = CalculateXYZFromLab(100.0f * l, 254.0f * (a - 0.5f), 2.0f * 254.0f * (b - 0.5f)); + //return XYZColor.CalculateRGBFromXYZ(x, y, z); + } + + private static (float l, float a, float b) CaclulateLabFromXYZ(float x, float y, float z) + { + const float ONETHRID = 1.0f / 3.0f; + const float FACTOR2 = 16.0f / 116.0f; + + x /= 95.047f; + y /= 100.0f; + z /= 108.883f; + + x = ((x > 0.008856f) ? (MathF.Pow(x, ONETHRID)) : ((7.787f * x) + FACTOR2)); + y = ((y > 0.008856f) ? (MathF.Pow(y, ONETHRID)) : ((7.787f * y) + FACTOR2)); + z = ((z > 0.008856f) ? (MathF.Pow(z, ONETHRID)) : ((7.787f * z) + FACTOR2)); + + float l = (116.0f * y) - 16.0f; + float a = 500.0f * (x - y); + float b = 200.0f * (y - z); + + return (l, a, b); + } + + private static (float x, float y, float z) CalculateXYZFromLab(float l, float a, float b) + { + const float FACTOR2 = 16.0f / 116.0f; + + float y = (l + 16.0f) / 116.0f; + float x = (a / 500.0f) + y; + float z = y - (b / 200.0f); + + float powX = MathF.Pow(y, 3.0f); + float powY = MathF.Pow(y, 3.0f); + float powZ = MathF.Pow(y, 3.0f); + + y = ((powY > 0.008856f) ? (powY) : ((y - FACTOR2) / 7.787f)); + x = ((powX > 0.008856f) ? (powX) : ((x - FACTOR2) / 7.787f)); + z = ((powZ > 0.008856f) ? (powZ) : ((z - FACTOR2) / 7.787f)); + + return (95.047f * x, 100.0f * y, 108.883f * z); + } + + #endregion + } +} diff --git a/RGB.NET.Core/Color/XYZColor.cs b/RGB.NET.Core/Color/XYZColor.cs new file mode 100644 index 0000000..d02329e --- /dev/null +++ b/RGB.NET.Core/Color/XYZColor.cs @@ -0,0 +1,200 @@ +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedMember.Global +using System; + +namespace RGB.NET.Core +{ + public static class XYZColor + { + #region Getter + + /// + /// Gets the X component value (XYZ-color space) of this in the range [0..95.047]. + /// + /// + /// + public static float GetX(this in Color color) => color.GetXYZ().x; + + /// + /// Gets the Y component value (XYZ-color space) of this in the range [0..100]. + /// + /// + /// + public static float GetY(this in Color color) => color.GetXYZ().y; + + /// + /// Gets the Z component value (XYZ-color space) of this in the range [0..108.883]. + /// + /// + /// + public static float GetZ(this in Color color) => color.GetXYZ().z; + + /// + /// Gets the X, Y and Z component values (XYZ-color space) of this . + /// X in the range [0..95.047]. + /// Y in the range [0..100]. + /// Z in the range [0..108.883]. + /// + /// + /// + public static (float x, float y, float z) GetXYZ(this in Color color) + => CaclulateXYZFromRGB(color.R, color.G, color.B); + + #endregion + + #region Manipulation + + /// + /// Adds the given XYZ values to this color. + /// + /// The X value to add. + /// The Y value to add. + /// The Z value to add. + /// The new color after the modification. + public static Color AddXYZ(this in Color color, float x = 0, float y = 0, float z = 0) + { + (float cX, float cY, float cZ) = color.GetXYZ(); + return Create(color.A, cX + x, cY + y, cZ + z); + } + + /// + /// Subtracts the given XYZ values to this color. + /// + /// The X value to subtract. + /// The Y value to subtract. + /// The Z value to subtract. + /// The new color after the modification. + public static Color SubtractXYZ(this in Color color, float x = 0, float y = 0, float z = 0) + { + (float cX, float cY, float cZ) = color.GetXYZ(); + return Create(color.A, cX - x, cY - y, cZ - z); + } + + /// + /// Multiplies the given XYZ values to this color. + /// + /// The X value to multiply. + /// The Y value to multiply. + /// The Z value to multiply. + /// The new color after the modification. + public static Color MultiplyXYZ(this in Color color, float x = 1, float y = 1, float z = 1) + { + (float cX, float cY, float cZ) = color.GetXYZ(); + return Create(color.A, cX * x, cY * y, cZ * z); + } + + /// + /// Divides the given XYZ values to this color. + /// + /// The X value to divide. + /// The Y value to divide. + /// The Z value to divide. + /// The new color after the modification. + public static Color DivideXYZ(this in Color color, float x = 1, float y = 1, float z = 1) + { + (float cX, float cY, float cZ) = color.GetXYZ(); + return Create(color.A, cX / x, cY / y, cZ / z); + } + + /// + /// Sets the given X valueof this color. + /// + /// The X value to set. + /// The Y value to set. + /// The Z value to set. + /// The new color after the modification. + public static Color SetXYZ(this in Color color, float? x = null, float? y = null, float? value = null) + { + (float cX, float cY, float cZ) = color.GetXYZ(); + return Create(color.A, x ?? cX, y ?? cY, value ?? cZ); + } + + #endregion + + #region Factory + + /// + /// Creates a new instance of the struct using XYZ-Values. + /// + /// The X component value of this . + /// The Y component value of this . + /// The Z component value of this . + /// The color created from the values. + public static Color Create(float x, float y, float z) + => Create(1.0f, x, y, z); + + /// + /// Creates a new instance of the struct using alpha and XYZ-Values. + /// + /// The alpha component value of this . + /// The X component value of this . + /// The Y component value of this . + /// The Z component value of this . + /// The color created from the values. + public static Color Create(byte a, float x, float y, float z) + => Create((float)a / byte.MaxValue, x, y, z); + + /// + /// Creates a new instance of the struct using alpha and XYZ-Values. + /// + /// The alpha component value of this . + /// The X component value of this . + /// The Y component value of this . + /// The Z component value of this . + /// The color created from the values. + public static Color Create(int a, float x, float y, float z) + => Create((float)a / byte.MaxValue, x, y, z); + + /// + /// Creates a new instance of the struct using alpha and XYZ-Values. + /// + /// The alpha component value of this . + /// The X component value of this . + /// The Y component value of this . + /// The Z component value of this . + /// The color created from the values. + public static Color Create(float a, float x, float y, float z) + { + (float r, float g, float b) = CalculateRGBFromXYZ(x, y, z); + return new Color(a, r, g, b); + } + + #endregion + + #region Helper + + internal static (float x, float y, float z) CaclulateXYZFromRGB(float r, float g, float b) + { + r = ((r > 0.04045f) ? MathF.Pow(((r + 0.055f) / 1.055f), 2.4f) : (r / 12.92f)) * 100.0f; + g = ((g > 0.04045f) ? MathF.Pow(((g + 0.055f) / 1.055f), 2.4f) : (g / 12.92f)) * 100.0f; + b = ((b > 0.04045f) ? MathF.Pow(((b + 0.055f) / 1.055f), 2.4f) : (b / 12.92f)) * 100.0f; + + float x = (r * 0.4124f) + (g * 0.3576f) + (b * 0.1805f); + float y = (r * 0.2126f) + (g * 0.7152f) + (b * 0.0722f); + float z = (r * 0.0193f) + (g * 0.1192f) + (b * 0.9505f); + + return (x, y, z); + } + + internal static (float r, float g, float b) CalculateRGBFromXYZ(float x, float y, float z) + { + const float INVERSE_EXPONENT = 1.0f / 2.4f; + + x /= 100.0f; + y /= 100.0f; + z /= 100.0f; + + 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); + + r = ((r > 0.0031308f) ? ((1.055f * (MathF.Pow(r, INVERSE_EXPONENT))) - 0.055f) : (12.92f * r)); + g = ((g > 0.0031308f) ? ((1.055f * (MathF.Pow(g, INVERSE_EXPONENT))) - 0.055f) : (12.92f * g)); + b = ((b > 0.0031308f) ? ((1.055f * (MathF.Pow(b, INVERSE_EXPONENT))) - 0.055f) : (12.92f * b)); + + return (r, g, b); + } + + #endregion + } +}