From f6335d1df5ebfbc813ffd4866b9817588440a85e Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 16 Sep 2014 22:59:20 +0100 Subject: [PATCH] Making color structures immutable Former-commit-id: a3812a627992dc8b0c350d57c51f970986b808c5 --- .../Imaging/ColorUnitTests.cs | 14 +- src/ImageProcessor/ImageProcessor.csproj | 4 +- .../Imaging/Colors/HSLAColor.cs | 236 +++++++++++------- .../Imaging/Colors/RGBAColor.cs | 188 ++++++++++---- src/ImageProcessor/Processors/Hue.cs | 5 +- src/ImageProcessor/Processors/HueRotate.cs | 6 +- src/ImageProcessorConsole/Program.cs | 13 +- 7 files changed, 303 insertions(+), 163 deletions(-) diff --git a/src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs b/src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs index 512b93d6e..c522c7f65 100644 --- a/src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs +++ b/src/ImageProcessor.UnitTests/Imaging/ColorUnitTests.cs @@ -21,25 +21,25 @@ namespace ImageProcessor.UnitTests.Imaging public class ColorUnitTests { /// - /// Tests the struct equality operators. + /// Tests the struct equality operators. /// [Test] public void TestRGBAEquality() { - RGBAColor first = new RGBAColor(Color.White); - RGBAColor second = new RGBAColor(Color.White); + RgbaColor first = RgbaColor.FromColor(Color.White); + RgbaColor second = RgbaColor.FromColor(Color.White); Assert.AreEqual(first, second); } /// - /// Tests the struct equality operators. + /// Tests the struct equality operators. /// [Test] public void TestHSLAEquality() { - HSLAColor first = new HSLAColor(Color.White); - HSLAColor second = new HSLAColor(Color.White); + HslaColor first = HslaColor.FromColor(Color.White); + HslaColor second = HslaColor.FromColor(Color.White); Assert.AreEqual(first, second); } @@ -53,7 +53,7 @@ namespace ImageProcessor.UnitTests.Imaging const string Hex = "#FEFFFE"; Color color = ColorTranslator.FromHtml(Hex); - HSLAColor hslaColor = new HSLAColor(color); + HslaColor hslaColor = HslaColor.FromColor(color); string outPut = ColorTranslator.ToHtml(hslaColor); Assert.AreEqual(Hex, outPut); } diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index 7c028707d..50a87b97a 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -70,8 +70,8 @@ - - + + diff --git a/src/ImageProcessor/Imaging/Colors/HSLAColor.cs b/src/ImageProcessor/Imaging/Colors/HSLAColor.cs index 19c12e54a..706608f2a 100644 --- a/src/ImageProcessor/Imaging/Colors/HSLAColor.cs +++ b/src/ImageProcessor/Imaging/Colors/HSLAColor.cs @@ -14,61 +14,68 @@ namespace ImageProcessor.Imaging.Colors /// Represents an HSLA (hue, saturation, luminosity, alpha) color. /// Adapted from /// - public struct HSLAColor + public struct HslaColor { + /// + /// Represents a that is null. + /// + public static readonly HslaColor Empty = new HslaColor(); + // Private data members below are on scale 0-1 // They are scaled for use externally based on scale /// /// The hue component. /// - private double h; + private readonly double h; /// /// The luminosity component. /// - private double l; + private readonly double l; /// /// The saturation component. /// - private double s; + private readonly double s; /// /// The alpha component. /// - private double a; + private readonly double a; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// - /// The hue. + /// The hue component. /// /// - /// The saturation. + /// The saturation component. /// /// - /// The luminosity. + /// The luminosity component. /// - public HSLAColor(double hue, double saturation, double luminosity) - : this() + /// + /// The alpha component. + /// + private HslaColor(double hue, double saturation, double luminosity, double alpha) { - this.H = hue; - this.S = saturation; - this.L = luminosity; + this.h = Clamp(hue); + this.s = Clamp(saturation); + this.l = Clamp(luminosity); + this.a = Clamp(alpha); } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// /// The to initialize from. /// - public HSLAColor(Color color) - : this() + private HslaColor(Color color) { - HSLAColor hslColor = color; + HslaColor hslColor = color; this.h = hslColor.h; this.s = hslColor.s; this.l = hslColor.l; @@ -76,7 +83,7 @@ namespace ImageProcessor.Imaging.Colors } /// - /// Gets or sets the hue component. + /// Gets the hue component. /// public double H { @@ -84,15 +91,10 @@ namespace ImageProcessor.Imaging.Colors { return this.h; } - - set - { - this.h = this.CheckRange(value); - } } /// - /// Gets or sets the luminosity component. + /// Gets the luminosity component. /// public double L { @@ -100,15 +102,10 @@ namespace ImageProcessor.Imaging.Colors { return this.l; } - - set - { - this.l = this.CheckRange(value); - } } /// - /// Gets or sets the saturation component. + /// Gets the saturation component. /// public double S { @@ -116,15 +113,10 @@ namespace ImageProcessor.Imaging.Colors { return this.s; } - - set - { - this.s = this.CheckRange(value); - } } /// - /// Gets or sets the alpha component. + /// Gets the alpha component. /// public double A { @@ -132,71 +124,121 @@ namespace ImageProcessor.Imaging.Colors { return this.a; } + } - set - { - this.a = this.CheckRange(value); - } + /// + /// Creates a structure from the three 64-bit HSLA + /// components (hue, saturation, and luminosity) values. + /// + /// + /// The hue component. + /// + /// + /// The saturation component. + /// + /// + /// The luminosity component. + /// + /// + /// The . + /// + public static HslaColor FromHslaColor(double hue, double saturation, double luminosity) + { + return new HslaColor(hue, saturation, luminosity, 1.0); + } + + /// + /// Creates a structure from the four 64-bit HSLA + /// components (hue, saturation, luminosity, and alpha) values. + /// + /// + /// The hue component. + /// + /// + /// The saturation component. + /// + /// + /// The luminosity component. + /// + /// + /// The alpha component. + /// + /// + /// The . + /// + public static HslaColor FromHslaColor(double hue, double saturation, double luminosity, double alpha) + { + return new HslaColor(hue, saturation, luminosity, alpha); + } + + /// + /// Creates a structure from the specified structure + /// + /// + /// The from which to create the new . + /// + /// + /// The . + /// + public static HslaColor FromColor(Color color) + { + return new HslaColor(color); } /// /// Allows the implicit conversion of an instance of to a - /// . + /// . /// /// /// The instance of to convert. /// /// - /// An instance of . + /// An instance of . /// - public static implicit operator HSLAColor(Color color) + public static implicit operator HslaColor(Color color) { - HSLAColor hslColor = new HSLAColor - { - h = color.GetHue() / 360.0, - l = color.GetBrightness(), - s = color.GetSaturation(), - a = color.A / 255f - }; + HslaColor hslColor = new HslaColor( + color.GetHue() / 360.0, + color.GetSaturation(), + color.GetBrightness(), + color.A / 255f); return hslColor; } /// /// Allows the implicit conversion of an instance of to a - /// . + /// . /// /// - /// The instance of to convert. + /// The instance of to convert. /// /// - /// An instance of . + /// An instance of . /// - public static implicit operator HSLAColor(RGBAColor rgbaColor) + public static implicit operator HslaColor(RgbaColor rgbaColor) { Color color = rgbaColor; - HSLAColor hslColor = new HSLAColor - { - h = color.GetHue() / 360.0, - l = color.GetBrightness(), - s = color.GetSaturation(), - a = color.A / 255f - }; + HslaColor hslColor = new HslaColor( + color.GetHue() / 360.0, + color.GetSaturation(), + color.GetBrightness(), + color.A / 255f); return hslColor; } /// - /// 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 Color(HSLAColor hslaColor) + public static implicit operator Color(HslaColor hslaColor) { double r = 0, g = 0, b = 0; if (Math.Abs(hslaColor.l - 0) > .0001) @@ -216,20 +258,24 @@ namespace ImageProcessor.Imaging.Colors } } - return Color.FromArgb(Convert.ToInt32(255 * hslaColor.a), Convert.ToInt32(255 * r), Convert.ToInt32(255 * g), Convert.ToInt32(255 * b)); + return Color.FromArgb( + Convert.ToInt32(255 * hslaColor.a), + Convert.ToInt32(255 * r), + Convert.ToInt32(255 * g), + Convert.ToInt32(255 * b)); } /// - /// 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 . + /// An instance of . /// - public static implicit operator RGBAColor(HSLAColor hslaColor) + public static implicit operator RgbaColor(HslaColor hslaColor) { double r = 0, g = 0, b = 0; if (Math.Abs(hslaColor.l - 0) > .0001) @@ -249,19 +295,11 @@ namespace ImageProcessor.Imaging.Colors } } - return new RGBAColor(Convert.ToByte(255 * r), Convert.ToByte(255 * g), Convert.ToByte(255 * b), Convert.ToByte(255 * hslaColor.a)); - } - - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public string ToRGBAString() - { - Color color = this; - return string.Format("R={0}, G={1}, B={2}, A={3}", color.R, color.G, color.B, color.A); + return RgbaColor.FromRgba( + Convert.ToByte(255 * r), + Convert.ToByte(255 * g), + Convert.ToByte(255 * b), + Convert.ToByte(255 * hslaColor.a)); } /// @@ -272,7 +310,12 @@ namespace ImageProcessor.Imaging.Colors /// public override string ToString() { - return string.Format("H={0:#0.##}, S={1:#0.##}, L={2:#0.##}, A={3:#0.##}", this.H, this.S, this.L, this.A); + if (this.IsEmpty()) + { + return "HSLAColor [Empty]"; + } + + return string.Format("HSLAColor [ H={0:#0.##}, S={1:#0.##}, L={2:#0.##}, A={3:#0.##}]", this.H, this.S, this.L, this.A); } /// @@ -284,10 +327,10 @@ namespace ImageProcessor.Imaging.Colors /// Another object to compare to. public override bool Equals(object obj) { - if (obj is HSLAColor) + if (obj is HslaColor) { Color thisColor = this; - Color otherColor = (HSLAColor)obj; + Color otherColor = (HslaColor)obj; return thisColor.Equals(otherColor); } @@ -347,12 +390,12 @@ namespace ImageProcessor.Imaging.Colors /// The get temp 2. /// /// - /// The color. + /// The color. /// /// /// The . /// - private static double GetTemp2(HSLAColor hslColor) + private static double GetTemp2(HslaColor hslColor) { double temp2; if (hslColor.l <= 0.5) @@ -399,7 +442,7 @@ namespace ImageProcessor.Imaging.Colors /// /// The sanitized . /// - private double CheckRange(double value) + private static double Clamp(double value) { if (value < 0.0) { @@ -412,5 +455,18 @@ namespace ImageProcessor.Imaging.Colors return value; } + + /// + /// Returns a value indicating whether the current instance is empty. + /// + /// + /// The true if this instance is empty; otherwise, false. + /// + private bool IsEmpty() + { + const double Epsilon = .0001; + return Math.Abs(this.h - 0) <= Epsilon && Math.Abs(this.s - 0) <= Epsilon && + Math.Abs(this.l - 0) <= Epsilon && Math.Abs(this.a - 0) <= Epsilon; + } } } \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Colors/RGBAColor.cs b/src/ImageProcessor/Imaging/Colors/RGBAColor.cs index a9127fae7..38976154a 100644 --- a/src/ImageProcessor/Imaging/Colors/RGBAColor.cs +++ b/src/ImageProcessor/Imaging/Colors/RGBAColor.cs @@ -1,5 +1,5 @@ // -------------------------------------------------------------------------------------------------------------------- -// +// // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // @@ -8,35 +8,121 @@ namespace ImageProcessor.Imaging.Colors { using System.Drawing; - using System.Text; /// /// Represents an RGBA (red, green, blue, alpha) color. /// - public struct RGBAColor + public struct RgbaColor { /// - /// The alpha component. + /// Represents a that is null. /// - public byte A; + public static readonly RgbaColor Empty = new RgbaColor(); /// - /// The blue component. + /// The red component. /// - public byte B; + private readonly byte r; /// /// The green component. /// - public byte G; + private readonly byte g; + + /// + /// The blue component. + /// + private readonly byte b; + + /// + /// The alpha component. + /// + private readonly byte a; /// + /// Initializes a new instance of the struct. + /// + /// /// The red component. + /// + /// + /// The green component. + /// + /// + /// The blue component. + /// + /// + /// The alpha component. + /// + private RgbaColor(byte red, byte green, byte blue, byte alpha) + { + this.r = red; + this.g = green; + this.b = blue; + this.a = alpha; + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The color. + /// + private RgbaColor(Color color) + { + this.r = color.R; + this.g = color.G; + this.b = color.B; + this.a = color.A; + } + + /// + /// Gets the red component. /// - public byte R; + public byte R + { + get + { + return this.r; + } + } + + /// + /// Gets the green component. + /// + public byte G + { + get + { + return this.g; + } + } + + /// + /// Gets the blue component. + /// + public byte B + { + get + { + return this.b; + } + } /// - /// Initializes a new instance of the struct. + /// Gets the alpha component. + /// + public byte A + { + get + { + return this.a; + } + } + + /// + /// Creates a structure from the three 8-bit RGBA + /// components (red, green, and blue) values. /// /// /// The red component. @@ -47,16 +133,17 @@ namespace ImageProcessor.Imaging.Colors /// /// The blue component. /// - public RGBAColor(byte red, byte green, byte blue) + /// + /// The . + /// + public static RgbaColor FromRgba(byte red, byte green, byte blue) { - this.R = red; - this.G = green; - this.B = blue; - this.A = 255; + return new RgbaColor(red, green, blue, 255); } /// - /// Initializes a new instance of the struct. + /// Creates a structure from the four 8-bit RGBA + /// components (red, green, blue, and alpha) values. /// /// /// The red component. @@ -70,86 +157,86 @@ namespace ImageProcessor.Imaging.Colors /// /// The alpha component. /// - public RGBAColor(byte red, byte green, byte blue, byte alpha) + /// + /// The . + /// + public static RgbaColor FromRgba(byte red, byte green, byte blue, byte alpha) { - this.R = red; - this.G = green; - this.B = blue; - this.A = alpha; + return new RgbaColor(red, green, blue, alpha); } /// - /// Initializes a new instance of the struct. + /// Creates a structure from the specified structure /// /// - /// The color. + /// The from which to create the new . /// - public RGBAColor(Color color) + /// + /// The . + /// + public static RgbaColor FromColor(Color color) { - this.R = color.R; - this.G = color.G; - this.B = color.B; - this.A = color.A; + return new RgbaColor(color); } /// /// Allows the implicit conversion of an instance of to a - /// . + /// . /// /// /// The instance of to convert. /// /// - /// An instance of . + /// An instance of . /// - public static implicit operator RGBAColor(Color color) + public static implicit operator RgbaColor(Color color) { - return new RGBAColor(color); + return new RgbaColor(color); } /// /// Allows the implicit conversion of an instance of to a - /// . + /// . /// /// /// The instance of to convert. /// /// - /// An instance of . + /// An instance of . /// - public static implicit operator RGBAColor(HSLAColor color) + public static implicit operator RgbaColor(HslaColor color) { - return new RGBAColor(color); + return new RgbaColor(color); } /// - /// 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 Color(RGBAColor rgba) + public static implicit operator Color(RgbaColor rgba) { return Color.FromArgb(rgba.A, rgba.R, rgba.G, rgba.B); } /// - /// 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 . + /// An instance of . /// - public static implicit operator HSLAColor(RGBAColor rgba) + public static implicit operator HslaColor(RgbaColor rgba) { - return new HSLAColor(rgba); + return HslaColor.FromColor(rgba); } /// @@ -160,7 +247,12 @@ namespace ImageProcessor.Imaging.Colors /// public override string ToString() { - return string.Format("R={0}, G={1}, B={2}, A={3}", this.R, this.G, this.B, this.A); + if (this.R == 0 && this.G == 0 && this.B == 0 && this.A == 0) + { + return "RGBA [Empty]"; + } + + return string.Format("RGBA [R={0}, G={1}, B={2}, A={3}]", this.R, this.G, this.B, this.A); } /// @@ -172,10 +264,10 @@ namespace ImageProcessor.Imaging.Colors /// Another object to compare to. public override bool Equals(object obj) { - if (obj is RGBAColor) + if (obj is RgbaColor) { Color thisColor = this; - Color otherColor = (RGBAColor)obj; + Color otherColor = (RgbaColor)obj; return thisColor.Equals(otherColor); } diff --git a/src/ImageProcessor/Processors/Hue.cs b/src/ImageProcessor/Processors/Hue.cs index d13b1217f..0ddc7b98d 100644 --- a/src/ImageProcessor/Processors/Hue.cs +++ b/src/ImageProcessor/Processors/Hue.cs @@ -69,8 +69,9 @@ namespace ImageProcessor.Processors { for (int j = 0; j < height; j++) { - HSLAColor hsla = new HSLAColor(fastBitmap.GetPixel(i, j)) { H = degrees / 360f }; - fastBitmap.SetPixel(i, j, hsla); + HslaColor original = HslaColor.FromColor(fastBitmap.GetPixel(i, j)); + HslaColor altered = HslaColor.FromHslaColor(degrees / 360D, original.S, original.L, original.A); + fastBitmap.SetPixel(i, j, altered); } } } diff --git a/src/ImageProcessor/Processors/HueRotate.cs b/src/ImageProcessor/Processors/HueRotate.cs index 975b681c6..9d99af892 100644 --- a/src/ImageProcessor/Processors/HueRotate.cs +++ b/src/ImageProcessor/Processors/HueRotate.cs @@ -69,9 +69,9 @@ namespace ImageProcessor.Processors { for (int j = 0; j < height; j++) { - HSLAColor hsla = new HSLAColor(fastBitmap.GetPixel(i, j)); - hsla.H = (hsla.H + (degrees / 360f)) % 1; - fastBitmap.SetPixel(i, j, hsla); + HslaColor original = HslaColor.FromColor(fastBitmap.GetPixel(i, j)); + HslaColor altered = HslaColor.FromHslaColor((original.H + (degrees / 360f)) % 1, original.S, original.L, original.A); + fastBitmap.SetPixel(i, j, altered); } } } diff --git a/src/ImageProcessorConsole/Program.cs b/src/ImageProcessorConsole/Program.cs index 169eecd9e..5b605dfca 100644 --- a/src/ImageProcessorConsole/Program.cs +++ b/src/ImageProcessorConsole/Program.cs @@ -50,16 +50,7 @@ namespace ImageProcessorConsole foreach (FileInfo fileInfo in files) { byte[] photoBytes = File.ReadAllBytes(fileInfo.FullName); - - //Color color = Color.Green; - //HSLAColor hslaColor = HSLAColor.FromRGBA(color); - //Console.WriteLine("H:" + hslaColor.H + " S:" + hslaColor.S + " L:" + hslaColor.L); - - //Color color2 = HSLAColor.ToRGBA(hslaColor); - - //Console.WriteLine("Color Out: " + "R:" + color2.R + " G:" + color2.G + " B:" + color2.B + " A:" + color2.A); - //Console.WriteLine(color2 == Color.Green); - //Console.WriteLine("Processing: " + fileInfo.Name); + Console.WriteLine("Processing: " + fileInfo.Name); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); @@ -82,7 +73,7 @@ namespace ImageProcessorConsole //.Resize(new Size((int)(size.Width * 1.1), 0)) //.ContentAwareResize(layer) .Constrain(size) - .HueRotate(270) + .Hue(180) .Quality(100) .Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name)));