/* 88888888 888888888888 88888888888888 8888888888888888 888888888888888888 888888 8888 888888 88888 88 88888 888888 8888 888888 88888888888888888888 88888888888888888888 8888888888888888888888 8888888888888888888888888888 88888888888888888888888888888888 88888888888888888888 888888888888888888888888 888888 8888888888 888888 888 8888 8888 888 888 888 OCTOBANANA Licensed under the MIT License Copyright (c) 2019 Brett Robinson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OB_COLOR_HH #define OB_COLOR_HH #include "ob/string.hh" #include "ob/term.hh" namespace aec = OB::Term::ANSI_Escape_Codes; #include #include #include #include #include #include #include #include #include #include namespace OB { class Color { inline static const std::unordered_map color_fg { {"black", "\x1b[30m"}, {"black bright", "\x1b[90m"}, {"red", "\x1b[31m"}, {"red bright", "\x1b[91m"}, {"green", "\x1b[32m"}, {"green bright", "\x1b[92m"}, {"yellow", "\x1b[33m"}, {"yellow bright", "\x1b[93m"}, {"blue", "\x1b[34m"}, {"blue bright", "\x1b[94m"}, {"magenta", "\x1b[35m"}, {"magenta bright", "\x1b[95m"}, {"cyan", "\x1b[36m"}, {"cyan bright", "\x1b[96m"}, {"white", "\x1b[37m"}, {"white bright", "\x1b[97m"}, }; inline static const std::unordered_map color_bg { {"black", "\x1b[40m"}, {"black bright", "\x1b[100m"}, {"red", "\x1b[41m"}, {"red bright", "\x1b[101m"}, {"green", "\x1b[42m"}, {"green bright", "\x1b[102m"}, {"yellow", "\x1b[43m"}, {"yellow bright", "\x1b[103m"}, {"blue", "\x1b[44m"}, {"blue bright", "\x1b[104m"}, {"magenta", "\x1b[45m"}, {"magenta bright", "\x1b[105m"}, {"cyan", "\x1b[46m"}, {"cyan bright", "\x1b[106m"}, {"white", "\x1b[47m"}, {"white bright", "\x1b[107m"}, }; public: struct Mode { enum Type { null = 0, rainbow, candy, party }; }; struct RGB { // range [0-255] double r {0}; double g {0}; double b {0}; }; struct HSL { // range [0-1] double h {0}; double s {0}; double l {0}; }; struct Type { enum value { bg = 0, fg }; }; Color() = default; Color(Type::value const fg) noexcept : _fg {static_cast(fg)} { } Color(std::string const& k, Type::value const fg = Type::value::fg) : _fg {static_cast(fg)} { key(k); } Color(Color&&) = default; Color(Color const&) = default; ~Color() = default; Color& operator=(Color&& rhs) = default; Color& operator=(Color const&) = default; Color& operator=(std::string const& k) { clear(); key(k); return *this; } operator bool() const { return _valid; } operator std::string() const { return _key; } friend std::ostream& operator<<(std::ostream& os, Color const& obj) { os << obj.value(); return os; } friend std::string& operator+=(std::string& str, Color const& obj) { return str += obj.value(); } bool is_valid() const { return _valid; } Color& clear() { _key = "clear"; _value.clear(); _hsl = HSL {50, 50, 50}; _valid = false; return *this; } Color& fg() { if (! _fg) { _fg = true; key(_key); } return *this; } Color& bg() { if (_fg) { _fg = false; key(_key); } return *this; } bool is_fg() const { return _fg; } bool is_bg() const { return ! _fg; } Mode::Type mode() const { return _mode; } Color& step(double const val = 0) { if (val != 0) { _hsl.h += val; if (_hsl.h < 0) { _hsl.h += 100; } else if (_hsl.h > 100) { _hsl.h -= 100; } _value = _fg ? aec::fg_true(hsl_to_hex(_hsl)) : aec::bg_true(hsl_to_hex(_hsl)); return *this; } switch(_mode) { case Mode::rainbow: { _hsl.h = _hsl.h < 100 ? _hsl.h + 0.2 : _hsl.h + 0.2 - 100; _value = _fg ? aec::fg_true(hsl_to_hex(_hsl)) : aec::bg_true(hsl_to_hex(_hsl)); break; } case Mode::candy: { _hsl.h = _hsl.h < 100 ? _hsl.h + 0.4 : _hsl.h + 0.4 - 100; _value = _fg ? aec::fg_true(hsl_to_hex(_hsl)) : aec::bg_true(hsl_to_hex(_hsl)); break; } case Mode::party: { _hsl.h = random(0, 100); _value = _fg ? aec::fg_true(hsl_to_hex(_hsl)) : aec::bg_true(hsl_to_hex(_hsl)); break; } case Mode::null: default: { break; } } return *this; } double hue() const { return _hsl.h; } Color& hue(double const val) { if (val < 0) { _hsl.h = 100; } else if (val > 100) { _hsl.h = 0; } else { _hsl.h = val; } _key = hsl_to_hex(_hsl); _value = _fg ? aec::fg_true(_key) : aec::bg_true(_key); return *this; } double sat() const { return _hsl.s; } Color& sat(double const val) { if (val < 0) { _hsl.s = 100; } else if (val > 100) { _hsl.s = 0; } else { _hsl.s = val; } _key = hsl_to_hex(_hsl); _value = _fg ? aec::fg_true(_key) : aec::bg_true(_key); return *this; } double lum() const { return _hsl.l; } Color& lum(double const val) { if (val < 0) { _hsl.l = 100; } else if (val > 100) { _hsl.l = 0; } else { _hsl.l = val; } _key = hsl_to_hex(_hsl); _value = _fg ? aec::fg_true(_key) : aec::bg_true(_key); return *this; } std::string key() const { return _key; } bool key(std::string const& k) { if (! k.empty()) { if (k == "clear") { // clear _key = k; _value = ""; _valid = true; } else if (k == "reverse") { // reverse _key = k; _value = "\x1b[7m"; _valid = true; } else if (k == "rainbow" || k == "candy" || k == "party") { switch(k.at(0)) { case 'r': { _mode = Mode::rainbow; break; } case 'c': { _mode = Mode::candy; break; } case 'p': { _mode = Mode::party; break; } default: { break; } } _key = k; _hsl = HSL {50, 50, 50}; _hsl.h = random(0, 100); _value = _fg ? aec::fg_true(hsl_to_hex(_hsl)) : aec::bg_true(hsl_to_hex(_hsl)); _valid = true; } else { // 21-bit color if (k.at(0) == '#' && OB::String::assert_rx(k, std::regex("^#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?$"))) { _key = k; _value = _fg ? aec::fg_true(k) : aec::bg_true(k); _hsl = hex_to_hsl(k); _valid = true; } // 8-bit color else if (OB::String::assert_rx(k, std::regex("^[0-9]{1,3}$")) && std::stoi(k) >= 0 && std::stoi(k) <= 255) { _key = k; _value = _fg ? aec::fg_256(k) : aec::bg_256(k); _valid = true; } // 4-bit color else if (color_fg.find(k) != color_fg.end()) { _key = k; if (_fg) { _value = color_fg.at(k); } else { _value = color_bg.at(k); } _valid = true; } } } return _valid; } std::string value() const { return _value; } std::string hex() const { return hsl_to_hex(_hsl); } RGB rgb() const { return hsl_to_rgb(_hsl); } HSL hsl() const { return _hsl; } static HSL hex_to_hsl(std::string const& hex) { return rgb_to_hsl(hex_to_rgb(hex)); } static std::string hsl_to_hex(HSL hsl) { return rgb_to_hex(hsl_to_rgb(hsl)); } static std::string hex_encode(char const c) { char s[3]; if (c & 0x80) { std::snprintf(&s[0], 3, "%02x", static_cast(c & 0xff) ); } else { std::snprintf(&s[0], 3, "%02x", static_cast(c) ); } return std::string(s); } static std::size_t hex_decode(std::string const& str_) { std::stringstream ss; ss << str_; std::size_t val; ss >> std::hex >> val; return val; } static bool valid_hstr(std::string& str) { std::smatch m; std::regex rx {"^#?((?:[0-9a-fA-F]{3}){1,2})$"}; if (std::regex_match(str, m, rx, std::regex_constants::match_not_null)) { std::string hstr {m[1]}; if (hstr.size() == 3) { std::stringstream ss; ss << hstr[0] << hstr[0] << hstr[1] << hstr[1] << hstr[2] << hstr[2]; hstr = ss.str(); } str = hstr; return true; } return false; } static RGB hex_to_rgb(std::string str) { if (! valid_hstr(str)) { return {}; } return RGB {static_cast(hex_decode(str.substr(0, 2))), static_cast(hex_decode(str.substr(2, 2))), static_cast(hex_decode(str.substr(4, 2)))}; } static std::string rgb_to_hex(RGB rgb) { std::ostringstream os; os << "#" << hex_encode(static_cast(rgb.r)) << hex_encode(static_cast(rgb.g)) << hex_encode(static_cast(rgb.b)); return os.str(); } static double hue_to_rgb(double j, double i, double h) { double r; if (h < 0) { h += 1; } if (h > 1) { h -= 1; } if (h < 1 / 6.0) { r = j + (i - j) * 6 * h; } else if (h < 1 / 2.0) { r = i; } else if (h < 2 / 3.0) { r = j + (i - j) * (2 / 3.0 - h) * 6; } else { r = j; } return r; } static RGB hsl_to_rgb(HSL hsl) { RGB rgb; hsl.h /= 100; hsl.s /= 100; hsl.l /= 100; if (hsl.s <= 0.0001) { rgb.r = hsl.l; rgb.g = hsl.l; rgb.b = hsl.l; } else { double const i {(hsl.l < 0.5) ? (hsl.l * (1 + hsl.s)) : ((hsl.l + hsl.s) - (hsl.l * hsl.s))}; double const j {2 * hsl.l - i}; rgb.r = hue_to_rgb(j, i, (hsl.h + 1 / 3.0)); rgb.g = hue_to_rgb(j, i, hsl.h); rgb.b = hue_to_rgb(j, i, (hsl.h - 1 / 3.0)); } rgb.r *= 255; rgb.g *= 255; rgb.b *= 255; return rgb; } static HSL rgb_to_hsl(RGB rgb) { rgb.r /= 255; rgb.g /= 255; rgb.b /= 255; auto const min = std::min(rgb.r, std::min(rgb.g, rgb.b)); auto const max = std::max(rgb.r, std::max(rgb.g, rgb.b)); auto const init {(max + min) / 2.0}; HSL hsl {init, init, init}; if (max == min) { hsl.h = 0; hsl.s = 0; } else { auto const v {max - min}; hsl.s = (hsl.l > 0.5) ? v / (2 - (max - min)) : v / (max + min); if (max == rgb.r) { hsl.h = (rgb.g - rgb.b) / v + (rgb.g < rgb.b ? 6 : 0); } else if (max == rgb.g) { hsl.h = (rgb.b - rgb.r) / v + 2; } else { hsl.h = (rgb.r - rgb.g) / v + 4; } hsl.h /= 6.0; } hsl.h *= 100; hsl.s *= 100; hsl.l *= 100; return hsl; } private: std::size_t random(std::size_t min, std::size_t max) const { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution distr(min, max); return distr(gen); } bool _valid {false}; bool _fg {true}; std::string _key {"clear"}; std::string _value; Mode::Type _mode {Mode::null}; HSL _hsl {50, 50, 50}; }; // Color } // namespace OB #endif // OB_COLOR_HH