diff --git a/src/ImageProcessor/Colors/Hsv.cs b/src/ImageProcessor/Colors/Hsv.cs index d85dd3520..cb71ed3ee 100644 --- a/src/ImageProcessor/Colors/Hsv.cs +++ b/src/ImageProcessor/Colors/Hsv.cs @@ -52,7 +52,7 @@ namespace ImageProcessor /// A value ranging between 0 and 100. /// public readonly float S; - + /// /// Gets the V value (brightness) component. /// A value ranging between 0 and 100. @@ -117,30 +117,33 @@ namespace ImageProcessor /// public static implicit operator Hsv(Bgra color) { - float max = Math.Max(color.R, Math.Max(color.G, color.B)); - float min = Math.Min(color.R, Math.Min(color.G, color.B)); - float delta = max - min; - - if (Math.Abs(max) < Epsilon) + float r = color.R / 255f; + float g = color.G / 255f; + float b = color.B / 255f; + + float max = Math.Max(r, Math.Max(g, b)); + float min = Math.Min(r, Math.Min(g, b)); + float chroma = max - min; + float h = 0; + float s = 0; + float v = max; + + if (Math.Abs(chroma) < Epsilon) { - return new Hsv(0, 0, 0); + return new Hsv(0, s * 100, v * 100); } - float h = 0.0F; - float s; - float v; - - if (Math.Abs(delta) < Epsilon) { h = 0; } - else if (Math.Abs(color.R - max) < Epsilon) { h = (color.G - color.B) / delta; } - else if (Math.Abs(color.G - max) < Epsilon) { h = 2 + (color.B - color.R) / delta; } - else if (Math.Abs(color.B - max) < Epsilon) { h = 4 + (color.R - color.G) / delta; } + if (Math.Abs(chroma) < Epsilon) { h = 0; } + else if (Math.Abs(r - max) < Epsilon) { h = (g - b) / chroma; } + else if (Math.Abs(g - max) < Epsilon) { h = 2 + (b - r) / chroma; } + else if (Math.Abs(b - max) < Epsilon) { h = 4 + (r - g) / chroma; } h *= 60; if (h < 0.0) { h += 360; } - s = delta / max; - v = max / 255F; - return new Hsv(h, s, v); + s = (chroma / v); + + return new Hsv(h, s * 100, v * 100); } /// @@ -155,9 +158,12 @@ namespace ImageProcessor /// public static implicit operator Bgra(Hsv color) { - if (Math.Abs(color.S) < Epsilon) + float s = color.S / 100; + float v = color.V / 100; + + if (Math.Abs(s) < Epsilon) { - byte component = (byte)(color.V * 255); + byte component = (byte)(v * 255); return new Bgra(component, component, component, 255); } @@ -165,51 +171,51 @@ namespace ImageProcessor int i = (int)(Math.Truncate(h)); float f = h - i; - float p = color.V * (1.0f - color.S); - float q = color.V * (1.0f - (color.S * f)); - float t = color.V * (1.0f - (color.S * (1.0f - f))); + float p = v * (1.0f - s); + float q = v * (1.0f - (s * f)); + float t = v * (1.0f - (s * (1.0f - f))); float r, g, b; switch (i) { case 0: - r = color.V; + r = v; g = t; b = p; break; case 1: r = q; - g = color.V; + g = v; b = p; break; case 2: r = p; - g = color.V; + g = v; b = t; break; case 3: r = p; g = q; - b = color.V; + b = v; break; case 4: r = t; g = p; - b = color.V; + b = v; break; default: - r = color.V; + r = v; g = p; b = q; break; } - return new Bgra((byte)b, (byte)g, (byte)r); + return new Bgra((byte)(Math.Round(b * 255)), (byte)(Math.Round(g * 255)), (byte)(Math.Round(r * 255))); } /// diff --git a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs index 788e33a60..c175ba0d0 100644 --- a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs +++ b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs @@ -10,6 +10,7 @@ namespace ImageProcessor.Tests { + using System; using System.Diagnostics.CodeAnalysis; using Xunit; @@ -94,13 +95,21 @@ namespace ImageProcessor.Tests Justification = "Reviewed. Suppression is OK here.")] public void BgrToHsv() { + // Black + Bgra b = new Bgra(0, 0, 0, 255); + Hsv h = b; + + Assert.Equal(0, h.H); + Assert.Equal(0, h.S); + Assert.Equal(0, h.V); + // White Bgra color = new Bgra(255, 255, 255, 255); Hsv hsv = color; Assert.Equal(0, hsv.H); Assert.Equal(0, hsv.S); - Assert.Equal(1, hsv.V); + Assert.Equal(100, hsv.V); // Dark moderate pink. Bgra color2 = new Bgra(106, 64, 128, 255); @@ -109,6 +118,51 @@ namespace ImageProcessor.Tests Assert.Equal(320.6, hsv2.H, 1); Assert.Equal(50, hsv2.S, 1); Assert.Equal(50.2, hsv2.V, 1); + + // Ochre. + Bgra color3 = new Bgra(34, 119, 204, 255); + Hsv hsv3 = color3; + + Assert.Equal(30, hsv3.H, 1); + Assert.Equal(83.3, hsv3.S, 1); + Assert.Equal(80, hsv3.V, 1); + } + + [Fact] + public void HsvToBgr() + { + // Dark moderate pink. + Hsv hsv = new Hsv(320.6f, 50, 50.2f); + Bgra bgra = hsv; + + Assert.Equal(bgra.B, 106); + Assert.Equal(bgra.G, 64); + Assert.Equal(bgra.R, 128); + + // Ochre + Hsv hsv2 = new Hsv(30, 83.3f, 80); + Bgra bgra2 = hsv2; + + Assert.Equal(bgra2.B, 34); + Assert.Equal(bgra2.G, 119); + Assert.Equal(bgra2.R, 204); + + // White + Hsv hsv3 = new Hsv(0, 0, 100); + Bgra bgra3 = hsv3; + + Assert.Equal(bgra3.B, 255); + Assert.Equal(bgra3.G, 255); + Assert.Equal(bgra3.R, 255); + + // Check others. + Random random = new Random(0); + for (var i = 0; i < 1000; i++) + { + Bgra bgra4 = new Bgra((byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255)); + Hsv hsb4 = bgra4; + Assert.Equal(bgra4, (Bgra)hsb4); + } } } }