diff --git a/colour.cpp b/colour.cpp index 5001eb85..f7749341 100644 --- a/colour.cpp +++ b/colour.cpp @@ -26,6 +26,7 @@ //----------------------------------------------------------------------------- using util::colour; +using util::colour3f; using util::colour4f; @@ -271,6 +272,78 @@ colour::from_x11 (const std::string &name) } +/////////////////////////////////////////////////////////////////////////////// +colour3f +util::rgb_to_hsv (colour3f rgb) +{ + // Calculate chroma + auto M = max (rgb); + auto m = min (rgb); + auto C = M - m; + + // Undefined for zero chroma + if (almost_zero (C)) + return { -1.f, 0.f, M }; + + // Calculate hue + float H = exactly_equal (rgb.r, M) ? (rgb.g - rgb.b) : + exactly_equal (rgb.g, M) ? 2 + (rgb.b - rgb.r) : + exactly_equal (rgb.b, M) ? 4 + (rgb.r - rgb.g) : + 0 ; + + H /= C; + H *= 60; + + if (H < 0) + H += 360; + + // Calculate value + auto V = M; + + // Calculate saturation + auto S = almost_zero (V) ? 0.f : C / V; + + return { H, S, V }; +} + + +//----------------------------------------------------------------------------- +colour3f +util::hsv_to_rgb (colour3f hsv) +{ + CHECK_GE (hsv.h, 0); + CHECK_LT (hsv.h, 360); + CHECK_GE (hsv.s, 0); + CHECK_LE (hsv.s, 1); + CHECK_GE (hsv.v, 0); + CHECK_LE (hsv.v, 1); + + float C = hsv.v * hsv.s; + float H = hsv.h / 60; + float X = C * (1 - std::abs (std::fmod (H, 2.f) - 1)); + + // monochromatic'ish + if (almost_zero (hsv.s)) + return colour3f { hsv.v }; + + colour3f rgb; + + unsigned hex = (unsigned)H; + switch (hex) { + case 0: rgb = { C, X, 0 }; break; + case 1: rgb = { X, C, 0 }; break; + case 2: rgb = { 0, C, X }; break; + case 3: rgb = { 0, X, C }; break; + case 4: rgb = { X, 0, C }; break; + case 5: rgb = { C, 0, X }; break; + } + + auto m = hsv.v - C; + + return rgb + m; +} + + ///---------------------------------------------------------------------------- //! Extract a colour object from a JSON node. diff --git a/colour.hpp b/colour.hpp index 398b030b..fea51579 100644 --- a/colour.hpp +++ b/colour.hpp @@ -50,6 +50,11 @@ namespace util { // Convenience types typedef colour<4,float> colour4f; + typedef colour<3,float> colour3f; + + // RGB <-> HSV + colour3f rgb_to_hsv (colour3f); + colour3f hsv_to_rgb (colour3f); // Serialisation const json::tree::node& operator>> (const json::tree::node&, util::colour4f&); diff --git a/test/colour.cpp b/test/colour.cpp index 372ba106..023d08d6 100644 --- a/test/colour.cpp +++ b/test/colour.cpp @@ -22,4 +22,32 @@ main (int, char**) // Check lookups are working CHECK_EQ (util::colour4f::from_html ("white"), util::colour4f::WHITE); CHECK_EQ (util::colour4f::from_x11 ("white"), util::colour4f::WHITE); + + // Check HSV conversions + { + // white: hue is undefined + auto white = util::rgb_to_hsv ({1,1,1}); + CHECK_EQ (white.s, 0); + CHECK_EQ (white.v, 1); + + // black: hue is undefined + auto black = util::rgb_to_hsv ({0,0,0}); + CHECK_EQ (black.s, 0); + CHECK_EQ (black.v, 0); + + struct { + util::colour3f rgb; + util::colour3f hsv; + } TESTS[] = { + { { 1, 0, 0, }, { 0, 1, 1, } }, // red + { { 0, 1, 0, }, { 120, 1, 1, } }, // green + { { 0, 0, 1, }, { 240, 1, 1, } }, // blue + { { 0.75f, 0.25f, 0.75f }, { 300, 2/3.f, 0.75f } }, + }; + + for (auto i: TESTS) { + CHECK_EQ (util::rgb_to_hsv (i.rgb), i.hsv); + CHECK_EQ (util::hsv_to_rgb (i.hsv), i.rgb); + } + } }