Skip to content

Commit 26144c8

Browse files
algobytewisesiriak
andauthored
Add RGB-HSV Conversion (#216)
* add RGBHSVConversion.cs * add RGBHSVConversionTest.cs * Update DIRECTORY.md Co-authored-by: Andrii Siriak <siryaka@gmail.com>
1 parent 9243eb5 commit 26144c8

4 files changed

Lines changed: 234 additions & 0 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using NUnit.Framework;
3+
using FluentAssertions;
4+
5+
namespace Algorithms.Other
6+
{
7+
public static class RgbHsvConversionTest
8+
{
9+
[Test]
10+
public static void HueOutOfRange_ThrowsArgumentOutOfRangeException()
11+
{
12+
Action act = () => Algorithms.Other.RgbHsvConversion.HsvToRgb(400, 0, 0);
13+
act.Should().Throw<ArgumentOutOfRangeException>();
14+
}
15+
16+
[Test]
17+
public static void SaturationOutOfRange_ThrowsArgumentOutOfRangeException()
18+
{
19+
Action act = () => Algorithms.Other.RgbHsvConversion.HsvToRgb(0, 2, 0);
20+
act.Should().Throw<ArgumentOutOfRangeException>();
21+
}
22+
23+
[Test]
24+
public static void ValueOutOfRange_ThrowsArgumentOutOfRangeException()
25+
{
26+
Action act = () => Algorithms.Other.RgbHsvConversion.HsvToRgb(0, 0, 2);
27+
act.Should().Throw<ArgumentOutOfRangeException>();
28+
}
29+
30+
// expected RGB-values taken from https://www.rapidtables.com/convert/color/hsv-to-rgb.html
31+
[Test]
32+
[TestCase(0, 0, 0, 0, 0, 0)]
33+
[TestCase(0, 0, 1, 255, 255, 255)]
34+
[TestCase(0, 1, 1, 255, 0, 0)]
35+
[TestCase(60, 1, 1, 255, 255, 0)]
36+
[TestCase(120, 1, 1, 0, 255, 0)]
37+
[TestCase(240, 1, 1, 0, 0, 255)]
38+
[TestCase(300, 1, 1, 255, 0, 255)]
39+
[TestCase(180, 0.5, 0.5, 64, 128, 128)]
40+
[TestCase(234, 0.14, 0.88, 193, 196, 224)]
41+
[TestCase(330, 0.75, 0.5, 128, 32, 80)]
42+
public static void TestRgbOutput(
43+
double hue,
44+
double saturation,
45+
double value,
46+
byte expectedRed,
47+
byte exptectedGreen,
48+
byte exptectedBlue)
49+
{
50+
var rgb = Algorithms.Other.RgbHsvConversion.HsvToRgb(hue, saturation, value);
51+
rgb.Item1.Should().Be(expectedRed);
52+
rgb.Item2.Should().Be(exptectedGreen);
53+
rgb.Item3.Should().Be(exptectedBlue);
54+
}
55+
56+
// Parameters of test-cases for TestRGBOutput reversed
57+
[Test]
58+
[TestCase(0, 0, 0, 0, 0, 0)]
59+
[TestCase(255, 255, 255, 0, 0, 1)]
60+
[TestCase(255, 0, 0, 0, 1, 1)]
61+
[TestCase(255, 255, 0, 60, 1, 1)]
62+
[TestCase(0, 255, 0, 120, 1, 1)]
63+
[TestCase(0, 0, 255, 240, 1, 1)]
64+
[TestCase(255, 0, 255, 300, 1, 1)]
65+
[TestCase(64, 128, 128, 180, 0.5, 0.5)]
66+
[TestCase(193, 196, 224, 234, 0.14, 0.88)]
67+
[TestCase(128, 32, 80, 330, 0.75, 0.5)]
68+
public static void TestHsvOutput(
69+
byte red,
70+
byte green,
71+
byte blue,
72+
double expectedHue,
73+
double expectedSaturation,
74+
double expectedValue)
75+
{
76+
var hsv = Algorithms.Other.RgbHsvConversion.RgbToHsv(red, green, blue);
77+
78+
// approximate-assertions needed because of small deviations due to converting between byte-values and double-values.
79+
hsv.Item1.Should().BeApproximately(expectedHue, 0.2);
80+
hsv.Item2.Should().BeApproximately(expectedSaturation, 0.002);
81+
hsv.Item3.Should().BeApproximately(expectedValue, 0.002);
82+
}
83+
}
84+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
using System;
2+
using System.Drawing;
3+
4+
namespace Algorithms.Other
5+
{
6+
/// <summary>
7+
/// The RGB color model is an additive color model in which red, green, and
8+
/// blue light are added together in various ways to reproduce a broad array of
9+
/// colors. The name of the model comes from the initials of the three additive
10+
/// primary colors, red, green, and blue. Meanwhile, the HSV representation
11+
/// models how colors appear under light. In it, colors are represented using
12+
/// three components: hue, saturation and (brightness-)value. This class
13+
/// provides methods for converting colors from one representation to the other.
14+
/// (description adapted from https://en.wikipedia.org/wiki/RGB_color_model and https://en.wikipedia.org/wiki/HSL_and_HSV).
15+
/// </summary>
16+
public static class RgbHsvConversion
17+
{
18+
/// <summary>
19+
/// Conversion from the HSV-representation to the RGB-representation.
20+
/// </summary>
21+
/// <param name="hue">Hue of the color.</param>
22+
/// <param name="saturation">Saturation of the color.</param>
23+
/// <param name="value">Brightness-value of the color.</param>
24+
/// <returns>The tuple of RGB-components.</returns>
25+
public static (byte red, byte green, byte blue) HsvToRgb(
26+
double hue,
27+
double saturation,
28+
double value)
29+
{
30+
if (hue < 0 || hue > 360)
31+
{
32+
throw new ArgumentOutOfRangeException(nameof(hue), $"{nameof(hue)} should be between 0 and 360");
33+
}
34+
35+
if (saturation < 0 || saturation > 1)
36+
{
37+
throw new ArgumentOutOfRangeException(nameof(saturation), $"{nameof(saturation)} should be between 0 and 1");
38+
}
39+
40+
if (value < 0 || value > 1)
41+
{
42+
throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} should be between 0 and 1");
43+
}
44+
45+
double chroma = value * saturation;
46+
double hueSection = hue / 60;
47+
double secondLargestComponent = chroma * (1 - Math.Abs(hueSection % 2 - 1));
48+
double matchValue = value - chroma;
49+
50+
return GetRgbBySection(hueSection, chroma, matchValue, secondLargestComponent);
51+
}
52+
53+
/// <summary>
54+
/// Conversion from the RGB-representation to the HSV-representation.
55+
/// </summary>
56+
/// <param name="red">Red-component of the color.</param>
57+
/// <param name="green">Green-component of the color.</param>
58+
/// <param name="blue">Blue-component of the color.</param>
59+
/// <returns>The tuple of HSV-components.</returns>
60+
public static (double hue, double saturation, double value) RgbToHsv(
61+
byte red,
62+
byte green,
63+
byte blue)
64+
{
65+
double dRed = (double)red / 255;
66+
double dGreen = (double)green / 255;
67+
double dBlue = (double)blue / 255;
68+
double value = Math.Max(Math.Max(dRed, dGreen), dBlue);
69+
double chroma = value - Math.Min(Math.Min(dRed, dGreen), dBlue);
70+
double saturation = value.Equals(0) ? 0 : chroma / value;
71+
double hue;
72+
73+
if (chroma.Equals(0))
74+
{
75+
hue = 0;
76+
}
77+
else if (value.Equals(dRed))
78+
{
79+
hue = 60 * (0 + (dGreen - dBlue) / chroma);
80+
}
81+
else if (value.Equals(dGreen))
82+
{
83+
hue = 60 * (2 + (dBlue - dRed) / chroma);
84+
}
85+
else
86+
{
87+
hue = 60 * (4 + (dRed - dGreen) / chroma);
88+
}
89+
90+
hue = (hue + 360) % 360;
91+
92+
return (hue, saturation, value);
93+
}
94+
95+
private static (byte red, byte green, byte blue) GetRgbBySection(double hueSection, double chroma, double matchValue, double secondLargestComponent)
96+
{
97+
byte red;
98+
byte green;
99+
byte blue;
100+
101+
if (hueSection >= 0 && hueSection <= 1)
102+
{
103+
red = ConvertToByte(chroma + matchValue);
104+
green = ConvertToByte(secondLargestComponent + matchValue);
105+
blue = ConvertToByte(matchValue);
106+
}
107+
else if (hueSection > 1 && hueSection <= 2)
108+
{
109+
red = ConvertToByte(secondLargestComponent + matchValue);
110+
green = ConvertToByte(chroma + matchValue);
111+
blue = ConvertToByte(matchValue);
112+
}
113+
else if (hueSection > 2 && hueSection <= 3)
114+
{
115+
red = ConvertToByte(matchValue);
116+
green = ConvertToByte(chroma + matchValue);
117+
blue = ConvertToByte(secondLargestComponent + matchValue);
118+
}
119+
else if (hueSection > 3 && hueSection <= 4)
120+
{
121+
red = ConvertToByte(matchValue);
122+
green = ConvertToByte(secondLargestComponent + matchValue);
123+
blue = ConvertToByte(chroma + matchValue);
124+
}
125+
else if (hueSection > 4 && hueSection <= 5)
126+
{
127+
red = ConvertToByte(secondLargestComponent + matchValue);
128+
green = ConvertToByte(matchValue);
129+
blue = ConvertToByte(chroma + matchValue);
130+
}
131+
else
132+
{
133+
red = ConvertToByte(chroma + matchValue);
134+
green = ConvertToByte(matchValue);
135+
blue = ConvertToByte(secondLargestComponent + matchValue);
136+
}
137+
138+
return (red, green, blue);
139+
}
140+
141+
private static byte ConvertToByte(double input)
142+
{
143+
return (byte)Math.Round(255 * input);
144+
}
145+
}
146+
}

DIRECTORY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@
8282
* [Fermat Prime Checker](https://github.com/TheAlgorithms/C-Sharp/blob/master/Algorithms/Other/FermatPrimeChecker.cs)
8383
* [Sieve of Eratosthenes](https://github.com/TheAlgorithms/C-Sharp/blob/master/Algorithms/Other/SieveOfEratosthenes.cs)
8484
* [Luhn](https://github.com/TheAlgorithms/C-Sharp/blob/master/Algorithms/Other/Luhn.cs)
85+
* [Mandelbrot](./Algorithms/Other/Mandelbrot.cs)
86+
* [Koch Snowflake](./Algorithms/Other/KochSnowflake.cs)
87+
* [RGB-HSV Conversion](./Algorithms/Other/RGBHSVConversion.cs)
8588
## Problems
8689
* [Stable Marriage](https://github.com/TheAlgorithms/C-Sharp/blob/master/Algorithms/Problems/StableMarriage/GaleShapley.cs)
8790
* [N-Queens](https://github.com/TheAlgorithms/C-Sharp/blob/master/Algorithms/Problems/NQueens/BacktrackingNQueensSolver.cs)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ This repository contains algorithms and data structures implemented in C# for ed
9595
* [Luhn](./Algorithms/Other/Luhn.cs)
9696
* [Mandelbrot](./Algorithms/Other/Mandelbrot.cs)
9797
* [Koch Snowflake](./Algorithms/Other/KochSnowflake.cs)
98+
* [RGB-HSV Conversion](./Algorithms/Other/RGBHSVConversion.cs)
9899
* [Problems](./Algorithms/Problems/)
99100
* [Stable Marriage](./Algorithms/Problems/StableMarriage)
100101
* [Gale-Shapley](./Algorithms/Problems/StableMarriage/GaleShapley.cs)

0 commit comments

Comments
 (0)