diff --git a/src/ImageProcessor/Colors/Color.cs b/src/ImageProcessor/Colors/Color.cs index 716bd07ae..1ffda3c46 100644 --- a/src/ImageProcessor/Colors/Color.cs +++ b/src/ImageProcessor/Colors/Color.cs @@ -42,9 +42,6 @@ namespace ImageProcessor public Color(float r, float g, float b) : this(r, g, b, 1) { - this.backingVector.X = r; - this.backingVector.Y = g; - this.backingVector.Z = b; } /// @@ -63,6 +60,51 @@ namespace ImageProcessor this.backingVector.W = a; } + /// + /// Initializes a new instance of the struct. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rrggbb, or aarrggbb format to match web syntax. + /// + public Color(string hex) + : this() + { + // Hexadecimal representations are layed out AARRGGBB to we need to do some reordering. + hex = hex.StartsWith("#") ? hex.Substring(1) : hex; + + if (hex.Length != 8 && hex.Length != 6 && hex.Length != 3) + { + throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + } + + if (hex.Length == 8) + { + this.R = Convert.ToByte(hex.Substring(2, 2), 16) / 255f; + this.G = Convert.ToByte(hex.Substring(4, 2), 16) / 255f; + this.B = Convert.ToByte(hex.Substring(6, 2), 16) / 255f; + this.A = Convert.ToByte(hex.Substring(0, 2), 16) / 255f; + } + else if (hex.Length == 6) + { + this.R = Convert.ToByte(hex.Substring(0, 2), 16) / 255f; + this.G = Convert.ToByte(hex.Substring(2, 2), 16) / 255f; + this.B = Convert.ToByte(hex.Substring(4, 2), 16) / 255f; + this.A = 1; + } + else + { + string r = char.ToString(hex[0]); + string g = char.ToString(hex[1]); + string b = char.ToString(hex[2]); + + this.B = Convert.ToByte(b + b, 16) / 255f; + this.G = Convert.ToByte(g + g, 16) / 255f; + this.R = Convert.ToByte(r + r, 16) / 255f; + this.A = 1; + } + } + /// /// Initializes a new instance of the struct. /// diff --git a/src/ImageProcessor/Colors/Formats/Bgra32.cs b/src/ImageProcessor/Colors/Formats/Bgra32.cs index e86068d62..237888e61 100644 --- a/src/ImageProcessor/Colors/Formats/Bgra32.cs +++ b/src/ImageProcessor/Colors/Formats/Bgra32.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -7,12 +7,11 @@ namespace ImageProcessor { using System; using System.ComponentModel; - using System.Runtime.InteropServices; + using System.Numerics; /// /// Represents an BGRA (blue, green, red, alpha) color. /// - [StructLayout(LayoutKind.Explicit)] public struct Bgra32 : IEquatable { /// @@ -21,157 +20,67 @@ namespace ImageProcessor public static readonly Bgra32 Empty = default(Bgra32); /// - /// Represents a transparent that has B, G, R, and A values set to 255, 255, 255, 0. + /// The backing vector for SIMD support. /// - public static readonly Bgra32 Transparent = new Bgra32(255, 255, 255, 0); - - /// - /// Represents a black that has B, G, R, and A values set to 0, 0, 0, 255. - /// - public static readonly Bgra32 Black = new Bgra32(0, 0, 0, 255); - - /// - /// Represents a white that has B, G, R, and A values set to 255, 255, 255, 255. - /// - public static readonly Bgra32 White = new Bgra32(255, 255, 255, 255); - - /// - /// Holds the blue component of the color - /// - [FieldOffset(0)] - public readonly byte B; - - /// - /// Holds the green component of the color - /// - [FieldOffset(1)] - public readonly byte G; - - /// - /// Holds the red component of the color - /// - [FieldOffset(2)] - public readonly byte R; - - /// - /// Holds the alpha component of the color - /// - [FieldOffset(3)] - public readonly byte A; - - /// - /// Permits the to be treated as a 32 bit integer. - /// - [FieldOffset(0)] - public readonly int BGRA; + private Vector4 backingVector; /// /// Initializes a new instance of the struct. /// - /// - /// The blue component of this . - /// - /// - /// The green component of this . - /// - /// - /// The red component of this . - /// + /// The blue component of this . + /// The green component of this . + /// The red component of this . public Bgra32(byte b, byte g, byte r) - : this() + : this(b, g, r, 255) { - this.B = b; - this.G = g; - this.R = r; - this.A = 255; } /// /// Initializes a new instance of the struct. /// - /// - /// The blue component of this . - /// - /// - /// The green component of this . - /// - /// - /// The red component of this . - /// - /// - /// The alpha component of this . - /// + /// The blue component of this . + /// The green component of this . + /// The red component of this . + /// The alpha component of this . public Bgra32(byte b, byte g, byte r, byte a) : this() { - this.B = b; - this.G = g; - this.R = r; - this.A = a; + this.backingVector.X = b.Clamp(0, 255); + this.backingVector.Y = g.Clamp(0, 255); + this.backingVector.Z = r.Clamp(0, 255); + this.backingVector.W = a.Clamp(0, 255); } /// - /// Initializes a new instance of the struct. + /// Gets the blue component of the color /// - /// - /// The combined color components. - /// - public Bgra32(int bgra) - : this() - { - this.BGRA = bgra; - } + public byte B => (byte)this.backingVector.X; /// - /// Initializes a new instance of the struct. + /// Gets the green component of the color /// - /// - /// The hexadecimal representation of the combined color components arranged - /// in rgb, rrggbb, or aarrggbb format to match web syntax. - /// - public Bgra32(string hex) - : this() - { - // Hexadecimal representations are layed out AARRGGBB to we need to do some reordering. - hex = hex.StartsWith("#") ? hex.Substring(1) : hex; + public byte G => (byte)this.backingVector.Y; - if (hex.Length != 8 && hex.Length != 6 && hex.Length != 3) - { - throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); - } + /// + /// Gets the red component of the color + /// + public byte R => (byte)this.backingVector.Z; - if (hex.Length == 8) - { - this.B = Convert.ToByte(hex.Substring(6, 2), 16); - this.G = Convert.ToByte(hex.Substring(4, 2), 16); - this.R = Convert.ToByte(hex.Substring(2, 2), 16); - this.A = Convert.ToByte(hex.Substring(0, 2), 16); - } - else if (hex.Length == 6) - { - this.B = Convert.ToByte(hex.Substring(4, 2), 16); - this.G = Convert.ToByte(hex.Substring(2, 2), 16); - this.R = Convert.ToByte(hex.Substring(0, 2), 16); - this.A = 255; - } - else - { - string b = char.ToString(hex[2]); - string g = char.ToString(hex[1]); - string r = char.ToString(hex[0]); + /// + /// Gets the alpha component of the color + /// + public byte A => (byte)this.backingVector.W; - this.B = Convert.ToByte(b + b, 16); - this.G = Convert.ToByte(g + g, 16); - this.R = Convert.ToByte(r + r, 16); - this.A = 255; - } - } + /// + /// Gets the integer representation of the color. + /// + public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24); /// /// Gets a value indicating whether this is empty. /// [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.B == 0 && this.G == 0 && this.R == 0 && this.A == 0; + public bool IsEmpty => this.backingVector.Equals(default(Vector4)); /// /// Allows the implicit conversion of an instance of to a @@ -190,9 +99,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. @@ -209,9 +116,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. @@ -240,7 +145,7 @@ namespace ImageProcessor { Bgra32 color = (Bgra32)obj; - return this.BGRA == color.BGRA; + return this.backingVector == color.backingVector; } return false; @@ -254,14 +159,7 @@ namespace ImageProcessor /// public override int GetHashCode() { - unchecked - { - int hashCode = this.B.GetHashCode(); - hashCode = (hashCode * 397) ^ this.G.GetHashCode(); - hashCode = (hashCode * 397) ^ this.R.GetHashCode(); - hashCode = (hashCode * 397) ^ this.A.GetHashCode(); - return hashCode; - } + return GetHashCode(this); } /// @@ -289,7 +187,18 @@ namespace ImageProcessor /// An object to compare with this object. public bool Equals(Bgra32 other) { - return this.BGRA == other.BGRA; + return this.backingVector.Equals(other.backingVector); } + + /// + /// 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(Bgra32 color) => color.backingVector.GetHashCode(); } } diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs index 6efdae1be..6f6f375e9 100644 --- a/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs +++ b/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs @@ -195,13 +195,13 @@ namespace ImageProcessor.Formats public void AddColor(Bgra32 pixel) { // Check if this request is for the same color as the last - if (this.previousColor == pixel.BGRA) + if (this.previousColor == pixel.Bgra) { // If so, check if I have a previous node setup. This will only occur if the first color in the image // happens to be black, with an alpha component of zero. if (this.previousNode == null) { - this.previousColor = pixel.BGRA; + this.previousColor = pixel.Bgra; this.root.AddColor(pixel, this.maxColorBits, 0, this); } else @@ -212,7 +212,7 @@ namespace ImageProcessor.Formats } else { - this.previousColor = pixel.BGRA; + this.previousColor = pixel.Bgra; this.root.AddColor(pixel, this.maxColorBits, 0, this); } } diff --git a/tests/ImageProcessor.Tests/Colors/ColorTests.cs b/tests/ImageProcessor.Tests/Colors/ColorTests.cs index 626cb14d0..58a442901 100644 --- a/tests/ImageProcessor.Tests/Colors/ColorTests.cs +++ b/tests/ImageProcessor.Tests/Colors/ColorTests.cs @@ -13,7 +13,7 @@ namespace ImageProcessor.Tests using Xunit; /// - /// Tests the struct. + /// Tests the struct. /// public class ColorTests { @@ -23,18 +23,16 @@ namespace ImageProcessor.Tests [Fact] public void AreEqual() { - Bgra32 color1 = new Bgra32(0, 0, 0, 255); - Bgra32 color2 = new Bgra32(0, 0, 0, 255); - Bgra32 color3 = new Bgra32("#000"); - Bgra32 color4 = new Bgra32("#000000"); - Bgra32 color5 = new Bgra32("#FF000000"); - Bgra32 color6 = new Bgra32(-16777216); + Color color1 = new Color(0, 0, 0); + Color color2 = new Color(0, 0, 0, 1); + Color color3 = new Color("#000"); + Color color4 = new Color("#000000"); + Color color5 = new Color("#FF000000"); Assert.Equal(color1, color2); Assert.Equal(color1, color3); Assert.Equal(color1, color4); Assert.Equal(color1, color5); - Assert.Equal(color1, color6); } /// @@ -43,18 +41,16 @@ namespace ImageProcessor.Tests [Fact] public void AreNotEqual() { - Bgra32 color1 = new Bgra32(255, 0, 0, 255); - Bgra32 color2 = new Bgra32(0, 0, 0, 255); - Bgra32 color3 = new Bgra32("#000"); - Bgra32 color4 = new Bgra32("#000000"); - Bgra32 color5 = new Bgra32("#FF000000"); - Bgra32 color6 = new Bgra32(-16777216); + Color color1 = new Color(255, 0, 0, 255); + Color color2 = new Color(0, 0, 0, 255); + Color color3 = new Color("#000"); + Color color4 = new Color("#000000"); + Color color5 = new Color("#FF000000"); Assert.NotEqual(color1, color2); Assert.NotEqual(color1, color3); Assert.NotEqual(color1, color4); Assert.NotEqual(color1, color5); - Assert.NotEqual(color1, color6); } /// @@ -63,29 +59,23 @@ namespace ImageProcessor.Tests [Fact] public void ConstructorAssignsProperties() { - Bgra32 color1 = new Bgra32(255, 10, 34, 220); - Assert.Equal(255, color1.B); - Assert.Equal(10, color1.G); - Assert.Equal(34, color1.R); - Assert.Equal(220, color1.A); + Color color1 = new Color(1, .1f, .133f, .864f); + Assert.Equal(1, color1.R, 1); + Assert.Equal(.1f, color1.G, 1); + Assert.Equal(.133f, color1.B, 3); + Assert.Equal(.864f, color1.A, 3); - Bgra32 color2 = new Bgra32(255, 10, 34); - Assert.Equal(255, color2.B); - Assert.Equal(10, color2.G); - Assert.Equal(34, color2.R); - Assert.Equal(255, color2.A); + Color color2 = new Color(1, .1f, .133f); + Assert.Equal(1, color2.R, 1); + Assert.Equal(.1f, color2.G, 1); + Assert.Equal(.133f, color2.B, 3); + Assert.Equal(1, color2.A, 1); - Bgra32 color3 = new Bgra32(-1); - Assert.Equal(255, color3.B); - Assert.Equal(255, color3.G); - Assert.Equal(255, color3.R); - Assert.Equal(255, color3.A); - - Bgra32 color4 = new Bgra32("#FF0000"); - Assert.Equal(0, color4.B); - Assert.Equal(0, color4.G); - Assert.Equal(255, color4.R); - Assert.Equal(255, color4.A); + Color color3 = new Color("#FF0000"); + Assert.Equal(1, color3.R, 1); + Assert.Equal(0, color3.G, 1); + Assert.Equal(0, color3.B, 3); + Assert.Equal(1, color3.A, 1); } /// @@ -95,7 +85,8 @@ namespace ImageProcessor.Tests public void ConvertHex() { const string First = "FF000000"; - string second = new Bgra32(0, 0, 0, 255).BGRA.ToString("X"); + Bgra32 bgra = new Color(0, 0, 0, 1); + string second = bgra.Bgra.ToString("X"); Assert.Equal(First, second); } }