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