Browse Source

Color to YCbCr conversion

Former-commit-id: f629f8d2a7660cc80b5be1252717faee05fac091
Former-commit-id: 68e2a4abfef0be9219fa9bfce1810c0b2bf609ac
Former-commit-id: b08ed48c5475f1e122cecc956e4f25f8cb46d4c3
af/merge-core
James Jackson-South 10 years ago
parent
commit
dcf1fa731b
  1. 33
      src/ImageProcessor/Colors/Color.cs
  2. 23
      src/ImageProcessor/Colors/Formats/Bgra32.cs
  3. 10
      src/ImageProcessor/Colors/Formats/Cmyk.cs
  4. 8
      src/ImageProcessor/Colors/Formats/Hsv.cs
  5. 124
      src/ImageProcessor/Colors/Formats/YCbCr.cs
  6. 42
      tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs

33
src/ImageProcessor/Colors/Color.cs

@ -163,9 +163,7 @@ namespace ImageProcessor
/// 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>
/// <param name="color">The instance of <see cref="Color"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Bgra32"/>.
/// </returns>
@ -178,9 +176,7 @@ namespace ImageProcessor
/// 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>
/// <param name="cmykColor">The instance of <see cref="Cmyk"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>
@ -192,13 +188,32 @@ namespace ImageProcessor
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;
float r = (float)(y + (1.402 * cr)) / 255f;
float g = (float)(y - (0.34414 * cb) - (0.71414 * cr)) / 255f;
float b = (float)(y + (1.772 * cb)) / 255f;
return 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>
/// <param name="color">The instance of <see cref="Hsv"/> to convert.</param>
/// <returns>
/// An instance of <see cref="Color"/>.
/// </returns>

23
src/ImageProcessor/Colors/Formats/Bgra32.cs

@ -189,29 +189,6 @@ namespace ImageProcessor
return new Bgra32((255f * color.B).ToByte(), (255f * color.G).ToByte(), (255f * color.R).ToByte(), (255f * color.A).ToByte());
}
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="YCbCr"/> to a
/// <see cref="Bgra32"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="YCbCr"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="Bgra32"/>.
/// </returns>
public static implicit operator Bgra32(YCbCr color)
{
float y = color.Y;
float cb = color.Cb - 128;
float cr = color.Cr - 128;
byte b = (y + (1.772 * cb)).ToByte();
byte g = (y - (0.34414 * cb) - (0.71414 * cr)).ToByte();
byte r = (y + (1.402 * cr)).ToByte();
return new Bgra32(b, g, r, 255);
}
/// <summary>
/// Compares two <see cref="Bgra32"/> objects. The result specifies whether the values
/// of the <see cref="Bgra32.B"/>, <see cref="Bgra32.G"/>, <see cref="Bgra32.R"/>, and <see cref="Bgra32.A"/>

10
src/ImageProcessor/Colors/Formats/Cmyk.cs

@ -141,13 +141,7 @@ namespace ImageProcessor
return !left.Equals(right);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param>
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Cmyk)
@ -194,7 +188,7 @@ namespace ImageProcessor
/// </returns>
private static float Clamp(float value)
{
return value.Clamp(0, 100);
return value.Clamp(0, 1);
}
/// <summary>

8
src/ImageProcessor/Colors/Formats/Hsv.cs

@ -155,13 +155,7 @@ namespace ImageProcessor
return !left.Equals(right);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param>
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Hsv)

124
src/ImageProcessor/Colors/Formats/YCbCr.cs

@ -7,10 +7,11 @@ namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Numerics;
/// <summary>
/// Represents an YCbCr (luminance, chroma, chroma) color conforming to the
/// ITU-R BT.601 standard used in digital imaging systems.
/// Full range standard used in digital imaging systems.
/// <see href="http://en.wikipedia.org/wiki/YCbCr"/>
/// </summary>
public struct YCbCr : IEquatable<YCbCr>
@ -21,64 +22,64 @@ namespace ImageProcessor
public static readonly YCbCr Empty = default(YCbCr);
/// <summary>
/// Gets the Y luminance component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// The backing vector for SIMD support.
/// </summary>
public readonly float Y;
private Vector3 backingVector;
/// <summary>
/// Gets the Cb chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// Initializes a new instance of the <see cref="YCbCr"/> struct.
/// </summary>
public readonly float Cb;
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
public YCbCr(float y, float cb, float cr)
: this()
{
this.backingVector.X = y.Clamp(0, 255);
this.backingVector.Y = cb.Clamp(0, 255);
this.backingVector.Z = cr.Clamp(0, 255);
}
/// <summary>
/// Gets the Cr chroma component.
/// Gets the Y luminance component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
public readonly float Cr;
public float Y => this.backingVector.X;
/// <summary>
/// The epsilon for comparing floating point numbers.
/// Gets the Cb chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
private const float Epsilon = 0.0001f;
public float Cb => this.backingVector.Y;
/// <summary>
/// Initializes a new instance of the <see cref="YCbCr"/> struct.
/// Gets the Cr chroma component.
/// <remarks>A value ranging between 0 and 255.</remarks>
/// </summary>
/// <param name="y">The y luminance component.</param>
/// <param name="cb">The cb chroma component.</param>
/// <param name="cr">The cr chroma component.</param>
public YCbCr(float y, float cb, float cr)
{
this.Y = y.ToByte();
this.Cb = cb.ToByte();
this.Cr = cr.ToByte();
}
public float Cr => this.backingVector.Z;
/// <summary>
/// Gets a value indicating whether this <see cref="YCbCr"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => Math.Abs(this.Y) < Epsilon
&& Math.Abs(this.Cb) < Epsilon
&& Math.Abs(this.Cr) < Epsilon;
public bool IsEmpty => this.backingVector.Equals(default(Vector3));
/// <summary>
/// Allows the implicit conversion of an instance of <see cref="Bgra32"/> to a
/// Allows the implicit conversion of an instance of <see cref="Color"/> to a
/// <see cref="YCbCr"/>.
/// </summary>
/// <param name="color">
/// The instance of <see cref="Bgra32"/> to convert.
/// The instance of <see cref="Color"/> to convert.
/// </param>
/// <returns>
/// An instance of <see cref="YCbCr"/>.
/// </returns>
public static implicit operator YCbCr(Bgra32 color)
public static implicit operator YCbCr(Color color)
{
byte b = color.B;
byte g = color.G;
byte r = color.R;
color = color.Limited;
float r = color.R * 255f;
float g = color.G * 255f;
float b = color.B * 255f;
float y = (float)((0.299 * r) + (0.587 * g) + (0.114 * b));
float cb = 128 + (float)((-0.168736 * r) - (0.331264 * g) + (0.5 * b));
@ -88,9 +89,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="YCbCr"/> objects. The result specifies whether the values
/// of the <see cref="YCbCr.Y"/>, <see cref="YCbCr.Cb"/>, and <see cref="YCbCr.Cr"/>
/// properties of the two <see cref="YCbCr"/> objects are equal.
/// Compares two <see cref="YCbCr"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="YCbCr"/> on the left side of the operand.
@ -107,9 +106,7 @@ namespace ImageProcessor
}
/// <summary>
/// Compares two <see cref="YCbCr"/> objects. The result specifies whether the values
/// of the <see cref="YCbCr.Y"/>, <see cref="YCbCr.Cb"/>, and <see cref="YCbCr.Cr"/>
/// properties of the two <see cref="YCbCr"/> objects are unequal.
/// Compares two <see cref="YCbCr"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="YCbCr"/> on the left side of the operand.
@ -125,50 +122,26 @@ namespace ImageProcessor
return !left.Equals(right);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param>
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is YCbCr)
{
YCbCr color = (YCbCr)obj;
return Math.Abs(this.Y - color.Y) < Epsilon
&& Math.Abs(this.Cb - color.Cb) < Epsilon
&& Math.Abs(this.Cr - color.Cr) < Epsilon;
return this.backingVector == color.backingVector;
}
return false;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
int hashCode = this.Y.GetHashCode();
hashCode = (hashCode * 397) ^ this.Cb.GetHashCode();
hashCode = (hashCode * 397) ^ this.Cr.GetHashCode();
return hashCode;
}
return GetHashCode(this);
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
@ -179,18 +152,21 @@ namespace ImageProcessor
return $"YCbCr [ Y={this.Y:#0.##}, Cb={this.Cb:#0.##}, Cr={this.Cr:#0.##} ]";
}
/// <inheritdoc/>
public bool Equals(YCbCr other)
{
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// 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>
/// True if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(YCbCr other)
{
return Math.Abs(this.Y - other.Y) < Epsilon
&& Math.Abs(this.Cb - other.Cb) < Epsilon
&& Math.Abs(this.Cr - other.Cr) < Epsilon;
}
private static int GetHashCode(YCbCr color) => color.backingVector.GetHashCode();
}
}

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

@ -25,15 +25,15 @@ namespace ImageProcessor.Tests
public class ColorConversionTests
{
/// <summary>
/// Tests the implicit conversion from <see cref="Bgra32"/> to <see cref="YCbCr"/>.
/// 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 BgrToYCbCr()
public void ColorToYCbCr()
{
// White
Bgra32 color = new Bgra32(255, 255, 255, 255);
Color color = new Color(1, 1, 1);
YCbCr yCbCr = color;
Assert.Equal(255, yCbCr.Y);
@ -41,14 +41,14 @@ namespace ImageProcessor.Tests
Assert.Equal(128, yCbCr.Cr);
// Black
Bgra32 color2 = new Bgra32(0, 0, 0, 255);
Color color2 = new Color(0, 0, 0);
YCbCr yCbCr2 = color2;
Assert.Equal(0, yCbCr2.Y);
Assert.Equal(128, yCbCr2.Cb);
Assert.Equal(128, yCbCr2.Cr);
// Grey
Bgra32 color3 = new Bgra32(128, 128, 128, 255);
Color color3 = new Color(.5f, .5f, .5f);
YCbCr yCbCr3 = color3;
Assert.Equal(128, yCbCr3.Y);
Assert.Equal(128, yCbCr3.Cb);
@ -56,39 +56,39 @@ namespace ImageProcessor.Tests
}
/// <summary>
/// Tests the implicit conversion from <see cref="YCbCr"/> to <see cref="Bgra32"/>.
/// 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 YCbCrToBgr()
public void YCbCrToColor()
{
// White
YCbCr yCbCr = new YCbCr(255, 128, 128);
Bgra32 color = yCbCr;
Color color = yCbCr;
Assert.Equal(255, color.B);
Assert.Equal(255, color.G);
Assert.Equal(255, color.R);
Assert.Equal(255, color.A);
Assert.Equal(1f, color.R, 1);
Assert.Equal(1f, color.G, 1);
Assert.Equal(1f, color.B, 1);
Assert.Equal(1f, color.A, 1);
// Black
YCbCr yCbCr2 = new YCbCr(0, 128, 128);
Bgra32 color2 = yCbCr2;
Color color2 = yCbCr2;
Assert.Equal(0, color2.B);
Assert.Equal(0, color2.G);
Assert.Equal(0, color2.R);
Assert.Equal(255, color2.A);
Assert.Equal(0, color2.G);
Assert.Equal(0, color2.B);
Assert.Equal(1, color2.A);
// Grey
YCbCr yCbCr3 = new YCbCr(128, 128, 128);
Bgra32 color3 = yCbCr3;
Color color3 = yCbCr3;
Assert.Equal(128, color3.B);
Assert.Equal(128, color3.G);
Assert.Equal(128, color3.R);
Assert.Equal(255, color3.A);
Assert.Equal(.5f, color3.R, 1);
Assert.Equal(.5f, color3.G, 1);
Assert.Equal(.5f, color3.B, 1);
Assert.Equal(1f, color3.A, 1);
}
/// <summary>

Loading…
Cancel
Save