diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 5977309373..c9d777c59d 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -5,10 +5,9 @@ namespace ImageSharp { - using System; - using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; /// /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. @@ -18,8 +17,33 @@ namespace ImageSharp /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, /// as it avoids the need to create new values for modification operations. /// - public partial struct Color : IPixel, IPackedVector + [StructLayout(LayoutKind.Explicit)] + public partial struct Color : IPixel { + /// + /// Gets or sets the red component. + /// + [FieldOffset(0)] + public byte R; + + /// + /// Gets or sets the green component. + /// + [FieldOffset(1)] + public byte G; + + /// + /// Gets or sets the blue component. + /// + [FieldOffset(2)] + public byte B; + + /// + /// Gets or sets the alpha component. + /// + [FieldOffset(3)] + public byte A; + /// /// The shift count for the red component /// @@ -50,11 +74,6 @@ namespace ImageSharp /// private static readonly Vector4 Half = new Vector4(0.5F); - /// - /// The packed value. - /// - private uint packedValue; - /// /// Initializes a new instance of the struct. /// @@ -63,8 +82,12 @@ namespace ImageSharp /// The blue component. /// The alpha component. public Color(byte r, byte g, byte b, byte a = 255) + : this() { - this.packedValue = Pack(r, g, b, a); + this.R = r; + this.G = g; + this.B = b; + this.A = a; } /// @@ -75,8 +98,9 @@ namespace ImageSharp /// The blue component. /// The alpha component. public Color(float r, float g, float b, float a = 1) + : this() { - this.packedValue = Pack(r, g, b, a); + this = Pack(r, g, b, a); } /// @@ -86,8 +110,9 @@ namespace ImageSharp /// The vector containing the components for the packed vector. /// public Color(Vector3 vector) + : this() { - this.packedValue = Pack(ref vector); + this = Pack(ref vector); } /// @@ -97,105 +122,9 @@ namespace ImageSharp /// The vector containing the components for the packed vector. /// public Color(Vector4 vector) + : this() { - this.packedValue = Pack(ref vector); - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The packed value. - /// - public Color(uint packed) - { - this.packedValue = packed; - } - - /// - /// Gets or sets the red component. - /// - public byte R - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return (byte)(this.packedValue >> RedShift); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.packedValue = this.packedValue & 0xFFFFFF00 | (uint)value << RedShift; - } - } - - /// - /// Gets or sets the green component. - /// - public byte G - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return (byte)(this.packedValue >> GreenShift); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.packedValue = this.packedValue & 0xFFFF00FF | (uint)value << GreenShift; - } - } - - /// - /// Gets or sets the blue component. - /// - public byte B - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return (byte)(this.packedValue >> BlueShift); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.packedValue = this.packedValue & 0xFF00FFFF | (uint)value << BlueShift; - } - } - - /// - /// Gets or sets the alpha component. - /// - public byte A - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return (byte)(this.packedValue >> AlphaShift); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.packedValue = this.packedValue & 0x00FFFFFF | (uint)value << AlphaShift; - } - } - - /// - public uint PackedValue - { - get - { - return this.packedValue; - } - - set - { - this.packedValue = value; - } + this = Pack(ref vector); } /// @@ -213,7 +142,10 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Color left, Color right) { - return left.packedValue == right.packedValue; + return left.R == right.R + && left.G == right.G + && left.B == right.B + && left.A == right.A; } /// @@ -227,7 +159,10 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Color left, Color right) { - return left.packedValue != right.packedValue; + return left.R != right.R + && left.G != right.G + && left.B != right.B + && left.A != right.A; } /// @@ -246,13 +181,16 @@ namespace ImageSharp } /// - public BulkPixelOperations CreateBulkOperations() => new Color.BulkOperations(); + public BulkPixelOperations CreateBulkOperations() => new BulkOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromBytes(byte x, byte y, byte z, byte w) { - this.packedValue = Pack(x, y, z, w); + this.R = x; + this.G = y; + this.B = z; + this.A = w; } /// @@ -307,7 +245,7 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { - this.packedValue = Pack(ref vector); + this = Pack(ref vector); } /// @@ -327,7 +265,10 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Color other) { - return this.packedValue == other.packedValue; + return this.R == other.R + && this.G == other.G + && this.B == other.B + && this.A == other.A; } /// @@ -342,33 +283,52 @@ namespace ImageSharp /// public override int GetHashCode() { - return this.packedValue.GetHashCode(); + unchecked + { + int hashCode = this.R.GetHashCode(); + hashCode = (hashCode * 397) ^ this.G.GetHashCode(); + hashCode = (hashCode * 397) ^ this.B.GetHashCode(); + hashCode = (hashCode * 397) ^ this.A.GetHashCode(); + return hashCode; + } + } + + /// + /// Packs the four floats into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Pack(byte x, byte y, byte z, byte w) + { + return (uint)(x << RedShift | y << GreenShift | z << BlueShift | w << AlphaShift); } /// /// Packs a into a uint. /// /// The vector containing the values to pack. - /// The containing the packed values. + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(ref Vector4 vector) + private static Color Pack(ref Vector4 vector) { vector *= MaxBytes; vector += Half; vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - return (uint)(((byte)vector.X << RedShift) - | ((byte)vector.Y << GreenShift) - | ((byte)vector.Z << BlueShift) - | (byte)vector.W << AlphaShift); + + return new Color((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W); } /// /// Packs a into a uint. /// /// The vector containing the values to pack. - /// The containing the packed values. + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(ref Vector3 vector) + private static Color Pack(ref Vector3 vector) { Vector4 value = new Vector4(vector, 1); return Pack(ref value); @@ -381,26 +341,12 @@ namespace ImageSharp /// The y-component /// The z-component /// The w-component - /// The + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(float x, float y, float z, float w) + private static Color Pack(float x, float y, float z, float w) { Vector4 value = new Vector4(x, y, z, w); return Pack(ref value); } - - /// - /// Packs the four floats into a . - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Pack(byte x, byte y, byte z, byte w) - { - return (uint)(x << RedShift | y << GreenShift | z << BlueShift | w << AlphaShift); - } } } \ No newline at end of file diff --git a/src/ImageSharp/Colors/ColorTransforms.cs b/src/ImageSharp/Colors/ColorTransforms.cs index f61afbf5b2..e392d7d981 100644 --- a/src/ImageSharp/Colors/ColorTransforms.cs +++ b/src/ImageSharp/Colors/ColorTransforms.cs @@ -28,7 +28,7 @@ namespace ImageSharp public static Color operator +(Color left, Color right) { Vector4 add = left.ToVector4() + right.ToVector4(); - return new Color(Pack(ref add)); + return Pack(ref add); } /// @@ -42,7 +42,7 @@ namespace ImageSharp public static Color operator -(Color left, Color right) { Vector4 sub = left.ToVector4() - right.ToVector4(); - return new Color(Pack(ref sub)); + return Pack(ref sub); } /// @@ -56,7 +56,7 @@ namespace ImageSharp public static Color Normal(Color backdrop, Color source) { Vector4 normal = Vector4BlendTransforms.Normal(backdrop.ToVector4(), source.ToVector4()); - return new Color(Pack(ref normal)); + return Pack(ref normal); } /// @@ -76,7 +76,7 @@ namespace ImageSharp public static Color Multiply(Color backdrop, Color source) { Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.ToVector4(), source.ToVector4()); - return new Color(Pack(ref multiply)); + return Pack(ref multiply); } /// @@ -95,7 +95,7 @@ namespace ImageSharp public static Color Screen(Color backdrop, Color source) { Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.ToVector4(), source.ToVector4()); - return new Color(Pack(ref subtract)); + return Pack(ref subtract); } /// @@ -110,7 +110,7 @@ namespace ImageSharp public static Color HardLight(Color backdrop, Color source) { Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.ToVector4(), source.ToVector4()); - return new Color(Pack(ref hardlight)); + return Pack(ref hardlight); } /// @@ -129,7 +129,7 @@ namespace ImageSharp public static Color Overlay(Color backdrop, Color source) { Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.ToVector4(), source.ToVector4()); - return new Color(Pack(ref overlay)); + return Pack(ref overlay); } /// @@ -144,7 +144,7 @@ namespace ImageSharp public static Color Darken(Color backdrop, Color source) { Vector4 darken = Vector4BlendTransforms.Darken(backdrop.ToVector4(), source.ToVector4()); - return new Color(Pack(ref darken)); + return Pack(ref darken); } /// @@ -159,7 +159,7 @@ namespace ImageSharp public static Color Lighten(Color backdrop, Color source) { Vector4 lighten = Vector4BlendTransforms.Lighten(backdrop.ToVector4(), source.ToVector4()); - return new Color(Pack(ref lighten)); + return Pack(ref lighten); } /// @@ -174,7 +174,7 @@ namespace ImageSharp public static Color SoftLight(Color backdrop, Color source) { Vector4 softlight = Vector4BlendTransforms.SoftLight(backdrop.ToVector4(), source.ToVector4()); - return new Color(Pack(ref softlight)); + return Pack(ref softlight); } /// @@ -188,7 +188,7 @@ namespace ImageSharp public static Color ColorDodge(Color backdrop, Color source) { Vector4 dodge = Vector4BlendTransforms.Dodge(backdrop.ToVector4(), source.ToVector4()); - return new Color(Pack(ref dodge)); + return Pack(ref dodge); } /// @@ -202,7 +202,7 @@ namespace ImageSharp public static Color ColorBurn(Color backdrop, Color source) { Vector4 burn = Vector4BlendTransforms.Burn(backdrop.ToVector4(), source.ToVector4()); - return new Color(Pack(ref burn)); + return Pack(ref burn); } /// @@ -217,7 +217,7 @@ namespace ImageSharp public static Color Difference(Color backdrop, Color source) { Vector4 difference = Vector4BlendTransforms.Difference(backdrop.ToVector4(), source.ToVector4()); - return new Color(Pack(ref difference)); + return Pack(ref difference); } /// @@ -232,7 +232,7 @@ namespace ImageSharp public static Color Exclusion(Color backdrop, Color source) { Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.ToVector4(), source.ToVector4()); - return new Color(Pack(ref exclusion)); + return Pack(ref exclusion); } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba32.cs b/src/ImageSharp/Colors/PackedPixel/Rgba32.cs new file mode 100644 index 0000000000..727d91c93c --- /dev/null +++ b/src/ImageSharp/Colors/PackedPixel/Rgba32.cs @@ -0,0 +1,398 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in red, green, blue, and alpha order. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct Rgba32 : IPixel, IPackedVector + { + /// + /// The shift count for the red component + /// + private const int RedShift = 0; + + /// + /// The shift count for the green component + /// + private const int GreenShift = 8; + + /// + /// The shift count for the blue component + /// + private const int BlueShift = 16; + + /// + /// The shift count for the alpha component + /// + private const int AlphaShift = 24; + + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new Vector4(0.5F); + + /// + /// The packed value. + /// + private uint packedValue; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public Rgba32(byte r, byte g, byte b, byte a = 255) + { + this.packedValue = Pack(r, g, b, a); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public Rgba32(float r, float g, float b, float a = 1) + { + this.packedValue = Pack(r, g, b, a); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Rgba32(Vector3 vector) + { + this.packedValue = Pack(ref vector); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Rgba32(Vector4 vector) + { + this.packedValue = Pack(ref vector); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The packed value. + /// + public Rgba32(uint packed) + { + this.packedValue = packed; + } + + /// + /// Gets or sets the red component. + /// + public byte R + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return (byte)(this.packedValue >> RedShift); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.packedValue = this.packedValue & 0xFFFFFF00 | (uint)value << RedShift; + } + } + + /// + /// Gets or sets the green component. + /// + public byte G + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return (byte)(this.packedValue >> GreenShift); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.packedValue = this.packedValue & 0xFFFF00FF | (uint)value << GreenShift; + } + } + + /// + /// Gets or sets the blue component. + /// + public byte B + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return (byte)(this.packedValue >> BlueShift); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.packedValue = this.packedValue & 0xFF00FFFF | (uint)value << BlueShift; + } + } + + /// + /// Gets or sets the alpha component. + /// + public byte A + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return (byte)(this.packedValue >> AlphaShift); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.packedValue = this.packedValue & 0x00FFFFFF | (uint)value << AlphaShift; + } + } + + /// + public uint PackedValue + { + get => this.packedValue; + + set => this.packedValue = value; + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgba32 left, Rgba32 right) + { + return left.packedValue == right.packedValue; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgba32 left, Rgba32 right) + { + return left.packedValue != right.packedValue; + } + + /// + /// Creates a new instance of the struct. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// + /// The . + /// + public static Rgba32 FromHex(string hex) + { + return ColorBuilder.FromHex(hex); + } + + /// + public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromBytes(byte x, byte y, byte z, byte w) + { + this.packedValue = Pack(x, y, z, w); + } + + /// + /// Converts the value of this instance to a hexadecimal string. + /// + /// A hexadecimal string representation of the value. + public string ToHex() + { + uint hexOrder = Pack(this.A, this.B, this.G, this.R); + return hexOrder.ToString("X8"); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToXyzBytes(byte[] bytes, int startIndex) + { + bytes[startIndex] = this.R; + bytes[startIndex + 1] = this.G; + bytes[startIndex + 2] = this.B; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToXyzwBytes(byte[] bytes, int startIndex) + { + bytes[startIndex] = this.R; + bytes[startIndex + 1] = this.G; + bytes[startIndex + 2] = this.B; + bytes[startIndex + 3] = this.A; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToZyxBytes(byte[] bytes, int startIndex) + { + bytes[startIndex] = this.B; + bytes[startIndex + 1] = this.G; + bytes[startIndex + 2] = this.R; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToZyxwBytes(byte[] bytes, int startIndex) + { + bytes[startIndex] = this.B; + bytes[startIndex + 1] = this.G; + bytes[startIndex + 2] = this.R; + bytes[startIndex + 3] = this.A; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PackFromVector4(Vector4 vector) + { + this.packedValue = Pack(ref vector); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() + { + return new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + } + + /// + public override bool Equals(object obj) + { + return (obj is Rgba32) && this.Equals((Rgba32)obj); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Rgba32 other) + { + return this.packedValue == other.packedValue; + } + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override string ToString() + { + return this.ToVector4().ToString(); + } + + /// + public override int GetHashCode() + { + return this.packedValue.GetHashCode(); + } + + /// + /// Packs a into a uint. + /// + /// The vector containing the values to pack. + /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Pack(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + return (uint)(((byte)vector.X << RedShift) + | ((byte)vector.Y << GreenShift) + | ((byte)vector.Z << BlueShift) + | (byte)vector.W << AlphaShift); + } + + /// + /// Packs a into a uint. + /// + /// The vector containing the values to pack. + /// The containing the packed values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Pack(ref Vector3 vector) + { + Vector4 value = new Vector4(vector, 1); + return Pack(ref value); + } + + /// + /// Packs the four floats into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Pack(float x, float y, float z, float w) + { + Vector4 value = new Vector4(x, y, z, w); + return Pack(ref value); + } + + /// + /// Packs the four floats into a . + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Pack(byte x, byte y, byte z, byte w) + { + return (uint)(x << RedShift | y << GreenShift | z << BlueShift | w << AlphaShift); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 39ce614956..c9312eed1b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -61,7 +61,7 @@ namespace ImageSharp.Tests BlackWhiteChecker(pixels); // top left VirticalBars(pixels); // top right TransparentGradients(pixels); // bottom left - Rainbow(pixels); // bottom right + Rainbow(pixels); // bottom right } } /// @@ -70,7 +70,7 @@ namespace ImageSharp.Tests /// private static void VirticalBars(PixelAccessor pixels) { - // topLeft + // topLeft int left = pixels.Width / 2; int right = pixels.Width; int top = 0; @@ -101,7 +101,7 @@ namespace ImageSharp.Tests /// private static void BlackWhiteChecker(PixelAccessor pixels) { - // topLeft + // topLeft int left = 0; int right = pixels.Width / 2; int top = 0; @@ -140,7 +140,7 @@ namespace ImageSharp.Tests /// private static void TransparentGradients(PixelAccessor pixels) { - // topLeft + // topLeft int left = 0; int right = pixels.Width / 2; int top = pixels.Height / 2; @@ -193,7 +193,7 @@ namespace ImageSharp.Tests int pixelCount = left * top; uint stepsPerPixel = (uint)(uint.MaxValue / pixelCount); TColor c = default(TColor); - Color t = new Color(0); + Rgba32 t = new Rgba32(0); for (int x = left; x < right; x++) for (int y = top; y < bottom; y++)