Browse Source

Merge pull request #290 from rubensr/colorspace-cielab

Colorspace CIE Lab

Former-commit-id: 5f46fc9e9df90d19eaed92efe633e68c6fc56334
Former-commit-id: e5dc877bf33710204b0e8519041db1485f2125cb
Former-commit-id: 5462479a0854eff8399077df9b6e5b341b82ea95
pull/17/head
James Jackson-South 10 years ago
parent
commit
7cae15e620
  1. 36
      src/ImageProcessor/Colors/ColorTransforms.cs
  2. 214
      src/ImageProcessor/Colors/Colorspaces/CieLab.cs
  3. 80
      tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs

36
src/ImageProcessor/Colors/ColorTransforms.cs

@ -173,6 +173,42 @@ namespace ImageProcessor
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 - 16F / 116F) / 7.787F;
y = (cieLabColor.L > 0.008856F * 903.3F) ? y3 : (cieLabColor.L / 903.3F);
z = (z3 > 0.008856F) ? z3 : (z - 16F / 116F) / 7.787F;
x *= 0.95047F;
//y *= 1F;
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.Compand(new Color(r, g, b));
}
/// <summary>
/// Gets the color component from the given values.
/// </summary>

214
src/ImageProcessor/Colors/Colorspaces/CieLab.cs

@ -0,0 +1,214 @@
// <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 ImageProcessor
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an CIE LAB 1976 color.
/// </summary>
public struct CieLab : IEquatable<CieLab>
{
/// <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.0001f;
/// <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.X = ClampL(l);
this.backingVector.Y = ClampAB(a);
this.backingVector.Z = ClampAB(b);
}
/// <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.backingVector.Equals(default(Vector4));
/// <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="Bgra32"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="CieLab"/>.
/// </returns>
public static implicit operator CieLab(Color color)
{
// First convert to CIE XYZ
color = Color.InverseCompand(color.Limited);
float x = (color.R * 0.4124F) + (color.G * 0.3576F) + (color.B * 0.1805F);
float y = (color.R * 0.2126F) + (color.G * 0.7152F) + (color.B * 0.0722F);
float z = (color.R * 0.0193F) + (color.G * 0.1192F) + (color.B * 0.9505F);
// Now to LAB
x /= 0.95047F;
//y /= 1F;
z /= 1.08883F;
x = x > 0.008856F ? (float) Math.Pow(x, 1F / 3F) : (903.3F * x + 16F) / 116F;
y = y > 0.008856F ? (float) Math.Pow(y, 1F / 3F) : (903.3F * y + 16F) / 116F;
z = z > 0.008856F ? (float) Math.Pow(z, 1F / 3F) : (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 bool Equals(object obj)
{
if (obj is CieLab)
{
CieLab color = (CieLab)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 "CieLab [Empty]";
}
return $"CieLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]";
}
/// <inheritdoc/>
public bool Equals(CieLab other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
/// Checks the range for lightness.
/// </summary>
/// <param name="value">
/// The value to check.
/// </param>
/// <returns>
/// The sanitized <see cref="float"/>.
/// </returns>
private static float ClampL(float value)
{
return value.Clamp(0, 100);
}
/// <summary>
/// Checks the range for components A or B.
/// </summary>
/// <param name="value">
/// The value to check.
/// </param>
/// <returns>
/// The sanitized <see cref="float"/>.
/// </returns>
private static float ClampAB(float value)
{
return value.Clamp(-100, 100);
}
/// <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();
}
}

80
tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs

@ -334,5 +334,85 @@ namespace ImageProcessor.Tests
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 = new Color(1, 1, 1);
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 = new Color(0, 0, 0);
CieLab cielab2 = color2;
Assert.Equal(0, cielab2.L, 3);
Assert.Equal(0, cielab2.A, 3);
Assert.Equal(0, cielab2.B, 3);
//// Grey
Color color3 = new Color(128 / 255f, 128 / 255f, 128 / 255f);
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 = new Color(0, 1, 1);
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 / 255f, 3);
Assert.Equal(color.G, 64 / 255f, 3);
Assert.Equal(color.B, 106 / 255f, 3);
// Ochre
CieLab cielab2 = new CieLab(58.1758f, 27.3399f, 56.8240f);
Color color2 = cielab2;
Assert.Equal(color2.R, 204 / 255f, 3);
Assert.Equal(color2.G, 119 / 255f, 3);
Assert.Equal(color2.B, 34 / 255f, 3);
//// White
CieLab cielab3 = new CieLab(0, 0, 0);
Color color3 = cielab3;
Assert.Equal(color3.R, 0f, 3);
Assert.Equal(color3.G, 0f, 3);
Assert.Equal(color3.B, 0f, 3);
//// Check others.
Random random = new Random(0);
for (int i = 0; i < 1000; i++)
{
Color color4 = new Color(random.Next(1), random.Next(1), random.Next(1));
CieLab cielab4 = color4;
Assert.Equal(color4, (Color)cielab4);
}
}
}
}

Loading…
Cancel
Save