From dcf1fa731be579052da01a85d112002b62d68e22 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 31 Oct 2015 17:02:42 +1100 Subject: [PATCH] Color to YCbCr conversion Former-commit-id: f629f8d2a7660cc80b5be1252717faee05fac091 Former-commit-id: 68e2a4abfef0be9219fa9bfce1810c0b2bf609ac Former-commit-id: b08ed48c5475f1e122cecc956e4f25f8cb46d4c3 --- src/ImageProcessor/Colors/Color.cs | 33 +++-- src/ImageProcessor/Colors/Formats/Bgra32.cs | 23 ---- src/ImageProcessor/Colors/Formats/Cmyk.cs | 10 +- src/ImageProcessor/Colors/Formats/Hsv.cs | 8 +- src/ImageProcessor/Colors/Formats/YCbCr.cs | 124 +++++++----------- .../Colors/ColorConversionTests.cs | 42 +++--- 6 files changed, 98 insertions(+), 142 deletions(-) diff --git a/src/ImageProcessor/Colors/Color.cs b/src/ImageProcessor/Colors/Color.cs index 1cd19a231..716bd07ae 100644 --- a/src/ImageProcessor/Colors/Color.cs +++ b/src/ImageProcessor/Colors/Color.cs @@ -163,9 +163,7 @@ namespace ImageProcessor /// Allows the implicit conversion of an instance of to a /// . /// - /// - /// The instance of to convert. - /// + /// The instance of to convert. /// /// An instance of . /// @@ -178,9 +176,7 @@ namespace ImageProcessor /// Allows the implicit conversion of an instance of to a /// . /// - /// - /// The instance of to convert. - /// + /// The instance of to convert. /// /// An instance of . /// @@ -192,13 +188,32 @@ 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(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); + } + /// /// Allows the implicit conversion of an instance of to a /// . /// - /// - /// The instance of to convert. - /// + /// The instance of to convert. /// /// An instance of . /// diff --git a/src/ImageProcessor/Colors/Formats/Bgra32.cs b/src/ImageProcessor/Colors/Formats/Bgra32.cs index 465e7d377..e86068d62 100644 --- a/src/ImageProcessor/Colors/Formats/Bgra32.cs +++ b/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()); } - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - 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); - } - /// /// Compares two objects. The result specifies whether the values /// of the , , , and diff --git a/src/ImageProcessor/Colors/Formats/Cmyk.cs b/src/ImageProcessor/Colors/Formats/Cmyk.cs index 3425c6ada..e20fe308c 100644 --- a/src/ImageProcessor/Colors/Formats/Cmyk.cs +++ b/src/ImageProcessor/Colors/Formats/Cmyk.cs @@ -141,13 +141,7 @@ namespace ImageProcessor return !left.Equals(right); } - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// - /// true if and this instance are the same type and represent the same value; otherwise, false. - /// - /// Another object to compare to. + /// public override bool Equals(object obj) { if (obj is Cmyk) @@ -194,7 +188,7 @@ namespace ImageProcessor /// private static float Clamp(float value) { - return value.Clamp(0, 100); + return value.Clamp(0, 1); } /// diff --git a/src/ImageProcessor/Colors/Formats/Hsv.cs b/src/ImageProcessor/Colors/Formats/Hsv.cs index 018c9bd18..a954fcf54 100644 --- a/src/ImageProcessor/Colors/Formats/Hsv.cs +++ b/src/ImageProcessor/Colors/Formats/Hsv.cs @@ -155,13 +155,7 @@ namespace ImageProcessor return !left.Equals(right); } - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// - /// true if and this instance are the same type and represent the same value; otherwise, false. - /// - /// Another object to compare to. + /// public override bool Equals(object obj) { if (obj is Hsv) diff --git a/src/ImageProcessor/Colors/Formats/YCbCr.cs b/src/ImageProcessor/Colors/Formats/YCbCr.cs index a0c9a339b..c23d0ae0b 100644 --- a/src/ImageProcessor/Colors/Formats/YCbCr.cs +++ b/src/ImageProcessor/Colors/Formats/YCbCr.cs @@ -7,10 +7,11 @@ namespace ImageProcessor { using System; using System.ComponentModel; + using System.Numerics; /// /// 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. /// /// public struct YCbCr : IEquatable @@ -21,64 +22,64 @@ namespace ImageProcessor public static readonly YCbCr Empty = default(YCbCr); /// - /// Gets the Y luminance component. - /// A value ranging between 0 and 255. + /// The backing vector for SIMD support. /// - public readonly float Y; + private Vector3 backingVector; /// - /// Gets the Cb chroma component. - /// A value ranging between 0 and 255. + /// Initializes a new instance of the struct. /// - public readonly float Cb; + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + 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); + } /// - /// Gets the Cr chroma component. + /// Gets the Y luminance component. /// A value ranging between 0 and 255. /// - public readonly float Cr; + public float Y => this.backingVector.X; /// - /// The epsilon for comparing floating point numbers. + /// Gets the Cb chroma component. + /// A value ranging between 0 and 255. /// - private const float Epsilon = 0.0001f; + public float Cb => this.backingVector.Y; /// - /// Initializes a new instance of the struct. + /// Gets the Cr chroma component. + /// A value ranging between 0 and 255. /// - /// The y luminance component. - /// The cb chroma component. - /// The cr chroma component. - 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; /// /// Gets a value indicating whether this is empty. /// [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)); /// - /// Allows the implicit conversion of an instance of to a + /// Allows the implicit conversion of an instance of to a /// . /// /// - /// The instance of to convert. + /// The instance of to convert. /// /// /// An instance of . /// - 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 } /// - /// Compares two objects. The result specifies whether the values - /// of the , , and - /// properties of the two objects are equal. + /// Compares two objects for equality. /// /// /// The on the left side of the operand. @@ -107,9 +106,7 @@ namespace ImageProcessor } /// - /// Compares two objects. The result specifies whether the values - /// of the , , and - /// properties of the two objects are unequal. + /// Compares two objects for inequality. /// /// /// The on the left side of the operand. @@ -125,50 +122,26 @@ namespace ImageProcessor return !left.Equals(right); } - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// - /// true if and this instance are the same type and represent the same value; otherwise, false. - /// - /// Another object to compare to. + /// 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; } - /// - /// Returns the hash code for this instance. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// + /// 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); } - /// - /// Returns the fully qualified type name of this instance. - /// - /// - /// A containing a fully qualified type name. - /// + /// 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.##} ]"; } + /// + public bool Equals(YCbCr other) + { + return this.backingVector.Equals(other.backingVector); + } + /// - /// Indicates whether the current object is equal to another object of the same type. + /// Returns the hash code for this instance. /// + /// + /// The instance of to return the hash code for. + /// /// - /// True if the current object is equal to the parameter; otherwise, false. + /// A 32-bit signed integer that is the hash code for this instance. /// - /// An object to compare with this object. - 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(); } } diff --git a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs index ecf7f1267..11d2529e8 100644 --- a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs +++ b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs @@ -25,15 +25,15 @@ namespace ImageProcessor.Tests public class ColorConversionTests { /// - /// Tests the implicit conversion from to . + /// Tests the implicit conversion from to . /// [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 } /// - /// Tests the implicit conversion from to . + /// Tests the implicit conversion from to . /// [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); } ///