From c36a2cc4f034527b2a8b7511638ba09279a87011 Mon Sep 17 00:00:00 2001 From: Rubens Fernandes Date: Tue, 5 Jan 2016 10:24:19 +1030 Subject: [PATCH] Not finished. Former-commit-id: e750623f68a59d5ce646aae196f82b15d4beccf3 Former-commit-id: bbe62d934e4987b5aa9e6959e86cce4773e762da Former-commit-id: 34e990b2c4c5b61ffa90892dac615a57e17f82c1 --- src/ImageProcessor/Colors/ColorTransforms.cs | 31 +++ .../Colors/Colorspaces/CieLab.cs | 218 ++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 src/ImageProcessor/Colors/Colorspaces/CieLab.cs diff --git a/src/ImageProcessor/Colors/ColorTransforms.cs b/src/ImageProcessor/Colors/ColorTransforms.cs index 5c7dc99ef8..fc3a01a78b 100644 --- a/src/ImageProcessor/Colors/ColorTransforms.cs +++ b/src/ImageProcessor/Colors/ColorTransforms.cs @@ -173,6 +173,37 @@ namespace ImageProcessor return new Color(r, g, b); } + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(CieLab cieLabColor) + { + // First convert back to XYZ... + float y = (cieLabColor.L + 16F) / 116F; + float x = cieLabColor.A / 500F + y; + float z = y - cieLabColor.B / 200F; + + float x3 = (float)Math.Pow(x, 3); + float y3 = (float)Math.Pow(y, 3); + float z3 = (float)Math.Pow(z, 3); + + y = (y3 > 0.008856F) ? y3 : (y - 16F / 116F) / 7.787F; + x = (x3 > 0.008856F) ? x3 : (x - 16F / 116F) / 7.787F; + z = (z3 > 0.008856F) ? z3 : (z - 16F / 116F) / 7.787F; + + // Then XYZ to RGB + float r = (x * 3.240969941904521F) + (y * -1.537383177570093F) + (z * -0.498610760293F); + float g = (x * -0.96924363628087F) + (y * 1.87596750150772F) + (z * 0.041555057407175F); + float b = (x * 0.055630079696993F) + (y * -0.20397695888897F) + (z * 1.056971514242878F); + + return Color.InverseCompand(new Color(r, g, b)); + } + /// /// Gets the color component from the given values. /// diff --git a/src/ImageProcessor/Colors/Colorspaces/CieLab.cs b/src/ImageProcessor/Colors/Colorspaces/CieLab.cs new file mode 100644 index 0000000000..978e0f0aef --- /dev/null +++ b/src/ImageProcessor/Colors/Colorspaces/CieLab.cs @@ -0,0 +1,218 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an CIE LAB 1976 color. + /// + public struct CieLab : IEquatable + { + /// + /// Represents a that has L, A, B values set to zero. + /// + public static readonly CieLab Empty = default(CieLab); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.0001f; + + /// + /// The backing vector for SIMD support. + /// + private Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + public CieLab(float l, float a, float b) + : this() + { + this.backingVector.X = ClampL(l); + this.backingVector.Y = ClampAB(a); + this.backingVector.Z = ClampAB(b); + } + + /// + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public float L => this.backingVector.X; + + /// + /// Gets the a color component. + /// Negative is green, positive magenta. + /// + public float A => this.backingVector.Y; + + /// + /// Gets the b color component. + /// Negative is blue, positive is yellow + /// + public float B => this.backingVector.Z; + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.backingVector.Equals(default(Vector4)); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + public static implicit operator CieLab(Color color) + { + // First convert to CIE XYZ + color = Color.InverseCompand(color.Limited); + + float x = (color.R * 0.41239079926595F) + (color.G * 0.35758433938387F) + (color.B * 0.18048078840183F); + float y = (color.R * 0.21263900587151F) + (color.G * 0.71516867876775F) + (color.B * 0.072192315360733F); + float z = (color.R * 0.019330818715591F) + (color.G * 0.11919477979462F) + (color.B * 0.95053215224966F); + + x *= 100F; + y *= 100F; + z *= 100F; + + // Now to LAB + x /= 95.047F; + y /= 100F; + z /= 108.883F; + + x = x > 0.008856 ? (float) Math.Pow(x, 1F / 3F) : (7.787F * x) + (16F / 116F); + y = y > 0.008856 ? (float) Math.Pow(y, 1F / 3F) : (7.787F * y) + (16F / 116F); + z = z > 0.008856 ? (float) Math.Pow(z, 1F / 3F) : (7.787F * z) + (16F / 116F); + + float l = (116 * y) - 16; + float a = 500 * (x - y); + float b = 200 * (y - z); + + return new CieLab(l, a, b); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(CieLab left, CieLab right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(CieLab left, CieLab right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object obj) + { + if (obj is CieLab) + { + CieLab color = (CieLab)obj; + + return this.backingVector == color.backingVector; + } + + return false; + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "CieLab [Empty]"; + } + + return $"CieLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]"; + } + + /// + public bool Equals(CieLab other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + /// Checks the range for lightness. + /// + /// + /// The value to check. + /// + /// + /// The sanitized . + /// + private static float ClampL(float value) + { + return value.Clamp(0, 100); + } + + /// + /// Checks the range for components A or B. + /// + /// + /// The value to check. + /// + /// + /// The sanitized . + /// + private static float ClampAB(float value) + { + return value.Clamp(-100, 100); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private static int GetHashCode(CieLab color) => color.backingVector.GetHashCode(); + } +}