1 module gl3n.ext.hsv;
2 
3 private {
4     import std.conv : to;
5     
6     import gl3n.linalg : vec3, vec4;
7     import gl3n.math : min, max, floor;
8 
9     version(unittest) {
10         import gl3n.math : almost_equal;
11     }
12 }
13 
14 /// Converts a 3 dimensional color-vector from the RGB to the HSV colorspace.
15 /// The function assumes that each component is in the range [0, 1].
16 @safe pure nothrow vec3 rgb2hsv(vec3 inp) {
17     vec3 ret = vec3(0.0f, 0.0f, 0.0f);
18     
19     float h_max = max(inp.r, inp.g, inp.b);
20     float h_min = min(inp.r, inp.g, inp.b);
21     float delta = h_max - h_min;
22 
23    
24     // h
25     if(delta == 0.0f) {
26         ret.x = 0.0f;
27     } else if(inp.r == h_max) {
28         ret.x = (inp.g - inp.b) / delta; // h
29     } else if(inp.g == h_max) {
30         ret.x = 2 + (inp.b - inp.r) / delta; // h
31     } else {
32         ret.x = 4 + (inp.r - inp.g) / delta; // h
33     }
34 
35     ret.x = ret.x * 60;
36     if(ret.x < 0) {
37         ret.x = ret.x + 360;
38     }
39 
40     // s
41     if(h_max == 0.0f) {
42         ret.y = 0.0f;
43     } else {
44         ret.y = delta / h_max;
45     }
46 
47     // v
48     ret.z = h_max;
49 
50     return ret;
51 }
52 
53 /// Converts a 4 dimensional color-vector from the RGB to the HSV colorspace.
54 /// The alpha value is not touched. This function also assumes that each component is in the range [0, 1].
55 @safe pure nothrow vec4 rgb2hsv(vec4 inp) {
56     return vec4(rgb2hsv(vec3(inp.rgb)), inp.a);
57 }
58 
59 unittest {
60     assert(rgb2hsv(vec3(0.0f, 0.0f, 0.0f)) == vec3(0.0f, 0.0f, 0.0f));
61     assert(rgb2hsv(vec3(1.0f, 1.0f, 1.0f)) == vec3(0.0f, 0.0f, 1.0f));
62 
63     vec3 hsv = rgb2hsv(vec3(100.0f/255.0f, 100.0f/255.0f, 100.0f/255.0f));    
64     assert(hsv.x == 0.0f && hsv.y == 0.0f && almost_equal(hsv.z, 0.392157, 0.000001));
65     
66     assert(rgb2hsv(vec3(0.0f, 0.0f, 1.0f)) == vec3(240.0f, 1.0f, 1.0f));
67 }
68 
69 /// Converts a 3 dimensional color-vector from the HSV to the RGB colorspace.
70 /// RGB colors will be in the range [0, 1].
71 /// This function is not marked es pure, since it depends on std.math.floor, which
72 /// is also not pure.
73 @safe nothrow vec3 hsv2rgb(vec3 inp) {
74     if(inp.y == 0.0f) { // s
75         return vec3(inp.zzz); // v
76     } else {
77         float var_h = inp.x * 6;
78         float var_i = to!float(floor(var_h));
79         float var_1 = inp.z * (1 - inp.y);
80         float var_2 = inp.z * (1 - inp.y * (var_h - var_i));
81         float var_3 = inp.z * (1 - inp.y * (1 - (var_h - var_i)));
82 
83         if(var_i == 0.0f)      return vec3(inp.z, var_3, var_1);
84         else if(var_i == 1.0f) return vec3(var_2, inp.z, var_1);
85         else if(var_i == 2.0f) return vec3(var_1, inp.z, var_3);
86         else if(var_i == 3.0f) return vec3(var_1, var_2, inp.z);
87         else if(var_i == 4.0f) return vec3(var_3, var_1, inp.z);
88         else                   return vec3(inp.z, var_1, var_2);
89     }
90 }
91 
92 /// Converts a 4 dimensional color-vector from the HSV to the RGB colorspace.
93 /// The alpha value is not touched and the resulting RGB colors will be in the range [0, 1].
94 @safe nothrow vec4 hsv2rgb(vec4 inp) {
95     return vec4(hsv2rgb(vec3(inp.xyz)), inp.w);
96 }
97 
98 unittest {
99     assert(hsv2rgb(vec3(0.0f, 0.0f, 0.0f)) == vec3(0.0f, 0.0f, 0.0f));
100     assert(hsv2rgb(vec3(0.0f, 0.0f, 1.0f)) == vec3(1.0f, 1.0f, 1.0f));
101 
102     vec3 rgb = hsv2rgb(vec3(0.0f, 0.0f, 0.392157f));
103     assert(rgb == vec3(0.392157f, 0.392157f, 0.392157f));
104 
105     assert(hsv2rgb(vec3(300.0f, 1.0f, 1.0f)) == vec3(1.0f, 0.0f, 1.0f));
106 }