diff --git a/examples/00_graphics/graphic_examples.py b/examples/00_graphics/graphic_examples.py index ae9dcb45..b4089801 100644 --- a/examples/00_graphics/graphic_examples.py +++ b/examples/00_graphics/graphic_examples.py @@ -23,7 +23,7 @@ fig1.set_window_properties([50,50],[500,500]) # position, window size fig1.set_axes(axis(0,[-10,10]), axis(1,[-10,10])) # (axis_id,[range_of_values_on_this_axis]) fig1.draw_box([[-1,1],[-1,1]],[Color.green(),Color.red(0.2)]) # drawing a green box with red opacity values inside -fig1.draw_circle([1,1],0.5,Color(255,155,5)) # drawing a circle at (1,1) of radius 0.5 with a custom RGB color +fig1.draw_circle([1,1],0.5,Color([255,155,5])) # drawing a circle at (1,1) of radius 0.5 with a custom RGB color fig1.draw_ring([1,1],[4,6],Color.red()) # drawing a ring at (1,1) of radius [4,6] with a predefined red color fig2 = Figure2D("My figure 2", GraphicOutput.VIBES | GraphicOutput.IPE) @@ -44,5 +44,31 @@ fig2.draw_polygone([[2,4.5],[4,4.5],[4.2,3.5],[3.5,3]], [Color.none(),Color.green(0.5)]) fig2.draw_polyline([[-0.8,0],[0,1.5]], 0.2, [Color.red(),Color.black(0.3)]) fig2.draw_ellipse([1,1],[0.5,2], 0.2, [Color.blue(),Color.blue(0.3)]) + +# Colors +# predefined colors without and with opacity fig2.draw_point([2,2], [Color.red(),Color.yellow(0.5)]) -fig2.draw_box([[2.4,2.9],[2.4,2.9]],[Color("#da3907"),Color("#da390755")]) \ No newline at end of file +# HTML color without and with opacity +fig2.draw_box([[2.4,2.9],[2.4,2.9]],[Color("#da3907"),Color("#da390755")]) +# HSV color without and with opacity +fig2.draw_box([[2.6,3.1],[2.6,3.1]],[Color([108,90,78],Model.HSV),Color([108,90,78,20],Model.HSV)]) +# RGB color auto cast from list without and with opacity +fig2.draw_box([[2.,2.3],[2.6,2.9]],[[255,0,255],[255,0,255,100]]) + +fig3 = Figure2D("ColorMap figure", GraphicOutput.VIBES | GraphicOutput.IPE) +fig3.set_axes(axis(0,[-1,21]), axis(1,[-5.5,0.5])) +fig3.set_window_properties([800,250],[500,500]) + +cmap_haxby=ColorMap.haxby() +cmap_default=ColorMap.basic() +cmap_blue_tube=ColorMap.blue_tube() +cmap_red_tube=ColorMap.red_tube() +cmap_rainbow=ColorMap.rainbow() + +for i in range (20): + ratio=i/20 + fig3.draw_box([[i,i+1],[-1,0]],[Color.black(),cmap_haxby.color(ratio)]) + fig3.draw_box([[i,i+1],[-2,-1]],[Color.black(),cmap_default.color(ratio)]) + fig3.draw_box([[i,i+1],[-3,-2]],[Color.black(),cmap_blue_tube.color(ratio)]) + fig3.draw_box([[i,i+1],[-4,-3]],[Color.black(),cmap_red_tube.color(ratio)]) + fig3.draw_box([[i,i+1],[-5,-4]],[Color.black(),cmap_rainbow.color(ratio)]) \ No newline at end of file diff --git a/python/src/graphics/CMakeLists.txt b/python/src/graphics/CMakeLists.txt index 29bd2047..8b5add11 100644 --- a/python/src/graphics/CMakeLists.txt +++ b/python/src/graphics/CMakeLists.txt @@ -12,6 +12,7 @@ paver/codac2_py_drawwhilepaving.cpp styles/codac2_py_Color.cpp + styles/codac2_py_ColorMap.cpp styles/codac2_py_StyleProperties.cpp ) diff --git a/python/src/graphics/codac2_py_graphics.cpp b/python/src/graphics/codac2_py_graphics.cpp index 5ae28b1d..c5f9b520 100644 --- a/python/src/graphics/codac2_py_graphics.cpp +++ b/python/src/graphics/codac2_py_graphics.cpp @@ -22,6 +22,7 @@ void export_drawwhilepaving(py::module& m); // styles void export_Color(py::module& m); +void export_ColorMap(py::module& m); void export_StyleProperties(py::module& m); @@ -31,6 +32,7 @@ PYBIND11_MODULE(_graphics, m) // styles export_Color(m); + export_ColorMap(m); export_StyleProperties(m); // figures diff --git a/python/src/graphics/styles/codac2_py_Color.cpp b/python/src/graphics/styles/codac2_py_Color.cpp index 2227835e..d960b3ac 100644 --- a/python/src/graphics/styles/codac2_py_Color.cpp +++ b/python/src/graphics/styles/codac2_py_Color.cpp @@ -19,29 +19,60 @@ using namespace codac2; namespace py = pybind11; using namespace pybind11::literals; + void export_Color(py::module& m) { + + py::enum_(m, "Model") + .value("RGB", Model::RGB) + .value("HSV", Model::HSV) + ; + py::class_ exported_color(m, "Color", COLOR_MAIN); exported_color + + .def(py::init<>(),COLOR_COLOR) - .def_readwrite("r", &Color::r) - .def_readwrite("g", &Color::g) - .def_readwrite("b", &Color::b) - .def_readwrite("alpha", &Color::alpha) - .def_readwrite("hex_str", &Color::hex_str) + .def(py::init&,Model>(), + COLOR_COLOR_CONST_ARRAY_FLOAT3_REF_MODEL, + "xyz"_a, "m_"_a=Model::RGB) - .def(py::init(), - COLOR_COLOR_INT_INT_INT_INT, - "r"_a, "g"_a, "b"_a, "alpha"_a=255) - - .def(py::init(), - COLOR_COLOR_FLOAT_FLOAT_FLOAT_FLOAT, - "r"_a, "g"_a, "b"_a, "alpha"_a=1.) + .def(py::init&,Model>(), + COLOR_COLOR_CONST_ARRAY_FLOAT4_REF_MODEL, + "xyza"_a, "m_"_a=Model::RGB) .def(py::init(), COLOR_COLOR_CONST_STRING_REF, "hex_str"_a) + .def("model", &Color::model, + CONST_MODEL_REF_COLOR_MODEL_CONST) + + + // Other formats + + .def("hex_str", &Color::hex_str, + STRING_COLOR_HEX_STR_CONST) + + .def("vec", &Color::vec, + VECTOR_COLOR_VEC_CONST) + + // Conversions + + .def("rgb", &Color::rgb, + COLOR_COLOR_RGB_CONST) + + .def("hsv", &Color::hsv, + COLOR_COLOR_HSV_CONST) + + // Overload flux operator + + .def("__str__", [](const Color& c) { + std::ostringstream oss; + oss << c; + return oss.str(); + }) + // Predefined colors .def_static("none", &Color::none, @@ -79,4 +110,6 @@ void export_Color(py::module& m) STATIC_COLOR_COLOR_DARK_GRAY_FLOAT, "alpha"_a=1.) ; + + py::implicitly_convertible(); } \ No newline at end of file diff --git a/python/src/graphics/styles/codac2_py_ColorMap.cpp b/python/src/graphics/styles/codac2_py_ColorMap.cpp new file mode 100644 index 00000000..e127777e --- /dev/null +++ b/python/src/graphics/styles/codac2_py_ColorMap.cpp @@ -0,0 +1,55 @@ +/** + * Codac binding (core) + * ---------------------------------------------------------------------------- + * \date 2024 + * \author Simon Rohou, Maël Godard + * \copyright Copyright 2024 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include +#include +#include +#include "codac2_py_ColorMap_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py): + +using namespace std; +using namespace codac2; +namespace py = pybind11; +using namespace pybind11::literals; + +void export_ColorMap(py::module& m) +{ + py::class_ exported_colormap(m, "ColorMap", COLORMAP_MAIN); + exported_colormap + + .def(py::init(), + COLORMAP_COLORMAP_MODEL, + "m"_a=Model::RGB) + + .def("model", &ColorMap::model, + CONST_MODEL_REF_COLORMAP_MODEL_CONST) + + .def("color", &ColorMap::color, + COLOR_COLORMAP_COLOR_FLOAT_CONST, + "r"_a) + + // Predifined color maps + + .def_static("haxby", &ColorMap::haxby, + STATIC_COLORMAP_COLORMAP_HAXBY) + + .def_static("basic", &ColorMap::basic, + STATIC_COLORMAP_COLORMAP_BASIC) + + .def_static("blue_tube", &ColorMap::blue_tube, + STATIC_COLORMAP_COLORMAP_BLUE_TUBE) + + .def_static("red_tube", &ColorMap::red_tube, + STATIC_COLORMAP_COLORMAP_RED_TUBE) + + .def_static("rainbow", &ColorMap::rainbow, + STATIC_COLORMAP_COLORMAP_RAINBOW) + + ; +} \ No newline at end of file diff --git a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp index 5dab8020..64aa7143 100644 --- a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp +++ b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp @@ -35,7 +35,7 @@ Figure2D_IPE::Figure2D_IPE(const Figure2D& fig) for(const auto& ci : codac_colors) // substr is needed to remove the "#" at the beginning of hex_str (deprecated by IPE) - _colors.emplace(ci.hex_str.substr(1), ci); + _colors.emplace(ci.hex_str().substr(1), ci); } Figure2D_IPE::~Figure2D_IPE() @@ -75,18 +75,28 @@ void Figure2D_IPE::center_viewbox([[maybe_unused]] const Vector& c, [[maybe_unus assert(r.min_coeff() > 0.); } +std::string ipe_str(const Color& c) +{ + return " codac_color_" + c.hex_str().substr(1); +} + +int ipe_opacity(const Color& c) +{ + return (int)(10.*round(10.*(c.model()==Model::RGB ? (c[3]/255.):(c[3]/100.)))); +} + void Figure2D_IPE::begin_path(const StyleProperties& s, bool tip=false) { // substr is needed to remove the "#" at the beginning of hex_str (deprecated by IPE) - _colors.emplace(s.stroke_color.hex_str.substr(1), s.stroke_color); - _colors.emplace(s.fill_color.hex_str.substr(1), s.fill_color); + _colors.emplace(ipe_str(s.stroke_color), s.stroke_color); + _colors.emplace(ipe_str(s.fill_color), s.fill_color); _f_temp_content << "\n \ "; } @@ -543,9 +553,12 @@ void Figure2D_IPE::print_header_page() \n \ \n"; - for(const auto& [k,c] : _colors) - _f << " \n"; + for(auto& [k,c] : _colors) + { + Color c_rgb = c.rgb(); + _f << " \n"; + } _f << " \n \ \n \ diff --git a/src/graphics/3rd/vibes/codac2_Figure2D_VIBes.cpp b/src/graphics/3rd/vibes/codac2_Figure2D_VIBes.cpp index 3b5a9d94..582e5a97 100644 --- a/src/graphics/3rd/vibes/codac2_Figure2D_VIBes.cpp +++ b/src/graphics/3rd/vibes/codac2_Figure2D_VIBes.cpp @@ -144,5 +144,5 @@ void Figure2D_VIBes::draw_AUV(const Vector& x, float size, const StyleProperties string Figure2D_VIBes::to_vibes_style(const StyleProperties& s) { - return s.stroke_color.hex_str + "[" + s.fill_color.hex_str + "]"; + return s.stroke_color.hex_str() + "[" + s.fill_color.hex_str() + "]"; } \ No newline at end of file diff --git a/src/graphics/CMakeLists.txt b/src/graphics/CMakeLists.txt index 7b38751b..c18ba4b8 100644 --- a/src/graphics/CMakeLists.txt +++ b/src/graphics/CMakeLists.txt @@ -22,6 +22,8 @@ ${CMAKE_CURRENT_SOURCE_DIR}/styles/codac2_Color.cpp ${CMAKE_CURRENT_SOURCE_DIR}/styles/codac2_Color.h + ${CMAKE_CURRENT_SOURCE_DIR}/styles/codac2_ColorMap.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/styles/codac2_ColorMap.h ${CMAKE_CURRENT_SOURCE_DIR}/styles/codac2_StyleProperties.cpp ${CMAKE_CURRENT_SOURCE_DIR}/styles/codac2_StyleProperties.h ) diff --git a/src/graphics/styles/codac2_Color.cpp b/src/graphics/styles/codac2_Color.cpp index 879e0c27..55b5860d 100644 --- a/src/graphics/styles/codac2_Color.cpp +++ b/src/graphics/styles/codac2_Color.cpp @@ -12,46 +12,146 @@ using namespace std; using namespace codac2; -Color::Color(float r_, float g_, float b_, float alpha_) - : r(r_), g(g_), b(b_), alpha(alpha_), - hex_str([&] - { - std::stringstream s; - s << std::hex << std::setfill('0'); - s << std::setw(2) << (int)(r*255) << std::setw(2) << (int)(g*255) << std::setw(2) << (int)(b*255); - if(alpha != 1.) - s << std::setw(2) << (int)(alpha*255); - return "#"+s.str(); - }()) -{ - assert(r_ >= 0. && r_ <= 1. && g_ >= 0. && g_ <= 1. && b_ >= 0. && b_ <= 1. && alpha_ >= 0. && alpha_ <= 1.); -} +Color::Color() + : Color({0.,0.,0.,0.}) +{ } -Color::Color(int r_, int g_, int b_, int alpha_) - : Color((float)(r_/255.), (float)(g_/255.), (float)(b_/255.), (float)(alpha_/255.)) +Color::Color(const std::array& xyz, Model m_) + : Color({xyz[0],xyz[1],xyz[2],(m_ == Model::RGB ? (float) 255. : (float) 100.)}, m_) +{ } + +Color::Color(const std::array& xyza, Model m_) + : std::array(xyza), m(m_) { - assert(r_ >= 0 && r_ <= 255 && g_ >= 0 && g_ <= 255 && b_ >= 0 && b_ <= 255 && alpha_ >= 0 && alpha_ <= 255); + if (m_==Model::RGB) + assert(xyza[0] >= 0. && xyza[0] <= 255. && xyza[1]>=0. && xyza[1] <= 255. && xyza[2]>=0. && xyza[2] <= 255. && xyza[3]>=0. && xyza[3] <= 255.); + else if (m_==Model::HSV) + assert(xyza[0] >= 0. && xyza[0] <= 360. && xyza[1]>=0. && xyza[1] <= 100. && xyza[2]>=0. && xyza[2] <= 100. && xyza[3]>=0. && xyza[3] <= 100.); } -Color::Color(const std::string& hex_str_) - : hex_str(hex_str_) +Color::Color(const std::initializer_list xyza, Model m_) + : Color(xyza.size() == 3 ? Color(to_array<3>(xyza), m_) : Color(to_array<4>(xyza), m_)) +{ } + +Color::Color(const std::string& hex_str) : m(Model::RGB) { - assert(hex_str_.size() == 7 || hex_str_.size() == 9); - assert(hex_str_[0] == '#'); + assert(hex_str.size() == 7 || hex_str.size() == 9); + assert(hex_str[0] == '#'); int red,green,blue,a; - std::istringstream(hex_str_.substr(1,2)) >> std::hex >> red; - std::istringstream(hex_str_.substr(3,2)) >> std::hex >> green; - std::istringstream(hex_str_.substr(5,2)) >> std::hex >> blue; - r = (float)red/255.; - g = (float)green/255.; - b = (float)blue/255.; + std::istringstream(hex_str.substr(1,2)) >> std::hex >> red; + std::istringstream(hex_str.substr(3,2)) >> std::hex >> green; + std::istringstream(hex_str.substr(5,2)) >> std::hex >> blue; + (*this)[0] = (float) (red); + (*this)[1] = (float) (green); + (*this)[2] = (float) (blue); // Alpha (transparency) component may be appended to the #hexa notation. - // Value is '1' (max opacity) by default. - if(hex_str_.size() == 9) + // Value is '255.' (max opacity) by default. + if(hex_str.size() == 9) + { + std::istringstream(hex_str.substr(7,2)) >> std::hex >> a; + (*this)[3] = (float) (a); + } + else + (*this)[3] = 255.; +} + +Color Color::rgb() const +{ + if (m==Model::RGB) + return *this; + else { - std::istringstream(hex_str_.substr(7,2)) >> std::hex >> a; - alpha = (float)a/255.; + float r = 0., g = 0., b = 0.; + + // Normalisation des valeurs + float h = (*this)[0] / 360.; // Hue normalisée (0 à 1) + float s = (*this)[1] / 100.; // Saturation normalisée (0 à 1) + float v = (*this)[2] / 100.; // Value normalisée (0 à 1) + + int i = static_cast(h * 6); + float f = (h * 6) - i; + i = i % 6; + + float p = v * (1 - s); + float q = v * (1 - f * s); + float t = v * (1 - (1 - f) * s); + + switch (i) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + } + + // Conversion vers l'échelle [0, 255] + r *= 255.; + g *= 255.; + b *= 255.; + + return Color({r, g, b,std::min(255.,((*this)[3]*2.55))},Model::RGB); } +} + +Color Color::hsv() const +{ + if (m==Model::HSV) + return *this; + else + { + float r = (*this)[0]/255.; + float g = (*this)[1]/255.; + float b = (*this)[2]/255.; + float c_max = std::max({r, g, b}); + float c_min = std::min({r, g, b}); + float delta = c_max - c_min; + + float h = 0.0; + if (delta != 0) { + if (c_max == r) { + h = fmod((g - b) / delta, 6.0); + } else if (c_max == g) { + h = (b - r) / delta + 2.0; + } else if (c_max == b) { + h = (r - g) / delta + 4.0; + } + h /= 6.0; + if (h < 0) { + h += 1.0; + } + } + + float s = (c_max == 0) ? 0 : (delta / c_max); + + float v = c_max; + + h*=360.; + s*=100.; + v*=100.; + + return Color({h, s, v,std::min(100.,((*this)[3]/2.55))},Model::HSV); + } +} + +std::string Color::hex_str() const +{ + if (m == Model::RGB) + { + std::stringstream s; + s << std::hex << std::setfill('0'); + s << std::setw(2) << (int)((*this)[0]) << std::setw(2) << (int)((*this)[1]) << std::setw(2) << (int)((*this)[2]); + if((*this)[3] != 1.) + s << std::setw(2) << (int)((*this)[3]); + return "#"+s.str(); + } + else + return rgb().hex_str(); +} + +codac2::Vector Color::vec() const +{ + return codac2::Vector({(*this)[0], (*this)[1], (*this)[2], (*this)[3]}); } \ No newline at end of file diff --git a/src/graphics/styles/codac2_Color.h b/src/graphics/styles/codac2_Color.h index 6d8a992c..a7ae40c6 100644 --- a/src/graphics/styles/codac2_Color.h +++ b/src/graphics/styles/codac2_Color.h @@ -11,36 +11,76 @@ #include #include +#include + #include"codac2_assert.h" +#include"codac2_Vector.h" namespace codac2 { - /** - * \struct Color - * \brief Represents an RGB value - */ - struct Color + + enum Model { RGB, HSV }; + + struct Color : public std::array { - float r; ///< red, value between 0. and 1. - float g; ///< green, value between 0. and 1. - float b; ///< blue, value between 0. and 1. - float alpha = 1.; ///< opacity, value between 0. (transparent) and 1. (opaque) - std::string hex_str; ///< represents an RGB value in a HTML standard - - explicit Color(float r, float g, float b, float alpha = 1.); - explicit Color(int r, int g, int b, int alpha = 255); - explicit Color(const std::string& hex_str); - - static Color none() { return Color(255, 255, 255, 0 ); }; - static Color black(float alpha = 1) { return Color(0, 0, 0, (int)(alpha*255)); }; - static Color white(float alpha = 1) { return Color(255, 255, 255, (int)(alpha*255)); }; - static Color green(float alpha = 1) { return Color(144, 242, 0, (int)(alpha*255)); }; - static Color blue(float alpha = 1) { return Color(0, 98, 198, (int)(alpha*255)); }; - static Color cyan(float alpha = 1) { return Color(75, 207, 250, (int)(alpha*255)); }; - static Color yellow(float alpha = 1) { return Color(255, 211, 42, (int)(alpha*255)); }; - static Color red(float alpha = 1) { return Color(209, 59, 0, (int)(alpha*255)); }; - static Color dark_gray(float alpha = 1) { return Color(112, 112, 112, (int)(alpha*255)); }; - static Color purple(float alpha = 1) { return Color(154, 0, 170, (int)(alpha*255)); }; - static Color dark_green(float alpha = 1) { return Color(94, 158, 0, (int)(alpha*255)); }; + protected: + Model m; + + public: + + // Constructors + + explicit Color(); + explicit Color(const std::array& xyz, Model m_ = Model::RGB); + explicit Color(const std::array& xyza, Model m_ = Model::RGB); + explicit Color(const std::initializer_list xyza, Model m_ = Model::RGB); + explicit Color(const std::string& hex_str); + + const Model& model() const { return m; } + + + // other formats + + std::string hex_str() const; + + codac2::Vector vec() const; + + // Conversions + + Color rgb() const; + Color hsv() const; + + // Overload flux operator + + friend std::ostream& operator<<(std::ostream& os, const Color& c) + { + if (c.m == Model::RGB) + os << "RGB Color (" << c[0] << "," << c[1] << "," << c[2] << "," << c[3] << ")"; + else if (c.m == Model::HSV) + os << "HSV Color (" << c[0] << "," << c[1] << "," << c[2] << "," << c[3] << ")"; + return os; + } + + // Predefined colors + + static Color none() { return Color({255., 255., 255., 0.}); }; + static Color black(float alpha = 1.) { return Color({0., 0., 0., (float) (alpha*255.)}); }; + static Color white(float alpha = 1.) { return Color({255., 255., 255., (float) (alpha*255.)}); }; + static Color green(float alpha = 1.) { return Color({144., 242., 0., (float) (alpha*255.)}); }; + static Color blue(float alpha = 1.) { return Color({0., 98., 198., (float) (alpha*255.)}); }; + static Color cyan(float alpha = 1.) { return Color({75., 207., 250., (float) (alpha*255.)}); }; + static Color yellow(float alpha = 1.) { return Color({255., 211., 42., (float) (alpha*255.)}); }; + static Color red(float alpha = 1.) { return Color({209., 59., 0., (float) (alpha*255.)}); }; + static Color dark_gray(float alpha = 1.) { return Color({112., 112., 112., (float) (alpha*255.)}); }; + static Color purple(float alpha = 1.) { return Color({154., 0., 170., (float) (alpha*255.)}); }; + static Color dark_green(float alpha = 1.) { return Color({94., 158., 0., (float) (alpha*255.)}); }; }; + + template + static std::array to_array(const std::initializer_list& list) { + assert(list.size() == N); + std::array arr; + std::copy(list.begin(), list.end(), arr.begin()); + return arr; + } } \ No newline at end of file diff --git a/src/graphics/styles/codac2_ColorMap.cpp b/src/graphics/styles/codac2_ColorMap.cpp new file mode 100644 index 00000000..728264b9 --- /dev/null +++ b/src/graphics/styles/codac2_ColorMap.cpp @@ -0,0 +1,62 @@ +/** + * codac2_ColorMap.cpp + * ---------------------------------------------------------------------------- + * \date 2024 + * \author Simon Rohou, Maël Godard + * \copyright Copyright 2024 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include "codac2_ColorMap.h" + +using namespace std; +using namespace codac2; + +ColorMap::ColorMap(Model m_) : + m(m_) +{ } + +Color ColorMap::color(float r) const +{ + assert (this->size() >= 2); + if(std::isnan(r)) // undefined ratio + return Color({0.5, 0.5, 0.5}); + assert(Interval(0.,1.).contains(r)); + + Interval map_domain = Interval(this->begin()->first,prev(this->end())->first); + float real_index = map_domain.lb() + r*map_domain.diam(); + + if(this->find(real_index) == this->end()) // color interpolation + { + typename map::const_iterator it_ub; + it_ub = this->lower_bound(real_index); + Color color_lb = prev(it_ub)->second; + Color color_ub = it_ub->second; + + // Interpolation according to the ColorMap model + + if (m == Model::RGB) + { + color_lb = color_lb.rgb(); + color_ub = color_ub.rgb(); + } + + else if (m == Model::HSV) + { + color_lb = color_lb.hsv(); + color_ub = color_ub.hsv(); + } + + + float local_ratio = (real_index - prev(it_ub)->first) / (it_ub->first - prev(it_ub)->first); + + return Color({(color_lb[0] + (color_ub[0] - color_lb[0]) * local_ratio), + (color_lb[1] + (color_ub[1] - color_lb[1]) * local_ratio), + (color_lb[2] + (color_ub[2] - color_lb[2]) * local_ratio), + (color_lb[3] + (color_ub[3] - color_lb[3]) * local_ratio)},color_lb.model()); + + } + + else // color key + return this->at(real_index); +} \ No newline at end of file diff --git a/src/graphics/styles/codac2_ColorMap.h b/src/graphics/styles/codac2_ColorMap.h new file mode 100644 index 00000000..c8d9e09a --- /dev/null +++ b/src/graphics/styles/codac2_ColorMap.h @@ -0,0 +1,126 @@ +/** + * \file codac2_ColorMap.h + * ---------------------------------------------------------------------------- + * \date 2024 + * \author Simon Rohou, Maël Godard + * \copyright Copyright 2024 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#pragma once + +#include +#include +#include "codac2_Color.h" +#include "codac2_Interval.h" +#include"codac2_assert.h" + +namespace codac2 +{ + /** + * \struct ColorMap + * \brief Represents a set of RGB values + */ + struct ColorMap : public std::map + { + protected: + Model m; //RGB or HSV + + public: + + explicit ColorMap(Model m_ = Model::RGB); + + const Model& model() const { return m; } + + Color color (float r) const; + + static ColorMap haxby() + { + ColorMap cmap( Model::RGB ); + cmap[0]=Color({39.,90.,211.}); + cmap[1]=Color({40.,123.,245.}); + cmap[2]=Color({45.,155.,253.}); + cmap[3]=Color({73.,209.,255.}); + cmap[4]=Color({100.,230.,254.}); + cmap[5]=Color({118.,235.,226.}); + cmap[6]=Color({135.,236.,187.}); + cmap[7]=Color({194.,252.,165.}); + cmap[8]=Color({217.,251.,151.}); + cmap[9]=Color({233.,241.,131.}); + cmap[10]=Color({252.,201.,96.}); + cmap[11]=Color({255.,184.,84.}); + cmap[12]=Color({255.,170.,75.}); + cmap[13]=Color({255.,167.,83.}); + cmap[14]=Color({255.,200.,158.}); + cmap[15]=Color({255.,233.,217.}); + return cmap; + } + + static ColorMap basic() // Can't use default as name + { + ColorMap cmap( Model::RGB ); + cmap[0]=Color({10.,0.,121.}); + cmap[1]=Color({40.,0.,150.}); + cmap[2]=Color({20.,5.,175.}); + cmap[3]=Color({0.,10.,200.}); + cmap[4]=Color({0.,25.,212.}); + cmap[5]=Color({0.,40.,224.}); + cmap[6]=Color({26.,102.,240.}); + cmap[7]=Color({13.,129.,248.}); + cmap[8]=Color({25.,175.,255.}); + cmap[9]=Color({50.,190.,255.}); + cmap[10]=Color({68.,202.,255.}); + cmap[11]=Color({97.,225.,240.}); + cmap[12]=Color({106.,235.,225.}); + cmap[13]=Color({124.,235.,200.}); + cmap[14]=Color({138.,236.,174.}); + cmap[15]=Color({172.,245.,168.}); + cmap[16]=Color({205.,255.,162.}); + cmap[17]=Color({223.,245.,141.}); + cmap[18]=Color({240.,236.,121.}); + cmap[19]=Color({247.,215.,104.}); + cmap[20]=Color({255.,189.,87.}); + cmap[21]=Color({255.,160.,69.}); + cmap[22]=Color({244.,117.,75.}); + cmap[23]=Color({238.,80.,78.}); + cmap[24]=Color({255.,90.,90.}); + cmap[25]=Color({255.,124.,124.}); + cmap[26]=Color({255.,158.,158.}); + cmap[27]=Color({245.,179.,174.}); + cmap[28]=Color({255.,196.,196.}); + cmap[29]=Color({255.,215.,215.}); + cmap[30]=Color({255.,235.,235.}); + cmap[31]=Color({255.,254.,253.}); + return cmap; + } + + static ColorMap blue_tube() + { + ColorMap cmap( Model::RGB ); + cmap[0]=Color({76.,110.,127.}); + cmap[1]=Color({136.,197.,228.}); + return cmap; + } + + static ColorMap red_tube() + { + ColorMap cmap( Model::RGB ); + cmap[0]=Color({169.,55.,0.}); + cmap[1]=Color({241.,140.,54.}); + return cmap; + } + + static ColorMap rainbow() + { + ColorMap cmap( Model::HSV ); + int i = 0; + for(int h = 300 ; h > 0 ; h-=10) + { + cmap[i]=Color({(float)h,100.,100.},Model::HSV); + i++; + } + return cmap; + } + + }; +} \ No newline at end of file diff --git a/src/graphics/styles/codac2_StyleProperties.cpp b/src/graphics/styles/codac2_StyleProperties.cpp index 38e15389..d6b42b8b 100644 --- a/src/graphics/styles/codac2_StyleProperties.cpp +++ b/src/graphics/styles/codac2_StyleProperties.cpp @@ -20,9 +20,13 @@ StyleProperties::StyleProperties(const Color& stroke_color_) { } StyleProperties::StyleProperties(std::initializer_list colors) - : stroke_color(*colors.begin()), fill_color(*std::prev(colors.end())) + : stroke_color(*colors.begin()) { assert(colors.size() <= 2); + if (colors.size() == 1) + fill_color = Color::none(); + else + fill_color = *std::prev(colors.end()); } StyleProperties StyleProperties::inside() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0ac83aa7..1b6c4607 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -78,6 +78,8 @@ list(APPEND SRC_TESTS # listing files without extension core/tools/codac2_tests_Approx + graphics/styles/codac2_tests_Color + ) diff --git a/tests/graphics/styles/codac2_tests_Color.cpp b/tests/graphics/styles/codac2_tests_Color.cpp new file mode 100644 index 00000000..8ae4e0dd --- /dev/null +++ b/tests/graphics/styles/codac2_tests_Color.cpp @@ -0,0 +1,87 @@ +/** + * Codac tests + * ---------------------------------------------------------------------------- + * \date 2024 + * \author Simon Rohou, Maël Godard + * \copyright Copyright 2024 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include +#include + +using namespace std; +using namespace codac2; + +TEST_CASE("Color") +{ + { + // Red + + std::array d_rgb{255., 0., 0.}; + std::array d_rgba{255., 0., 0., 255.}; + std::array d_hsv{0., 100., 100.}; + std::array d_hsva{0., 100., 100., 100.}; + + vector v{ + Color(d_rgb, Model::RGB), + Color(d_rgba, Model::RGB), + Color(d_hsv, Model::HSV), + Color(d_hsva, Model::HSV), + Color("#FF0000")}; + + for (const auto &c : v) + { + CHECK(Approx(c.rgb().vec(),1.) == Color({255., 0., 0.}).vec()); + CHECK(Approx(c.rgb().vec(),1.) == Color({255., 0., 0., 255.}).vec()); + CHECK(Approx(c.hsv().vec(),1.) == Color({0., 100., 100.}, Model::HSV).vec()); + CHECK(Approx(c.hsv().vec(),1.) == Color({0., 100., 100., 100.}, Model::HSV).vec()); + } + } + + { + // Pink full opacity + + float a = 255.; + std::array d_rgb{229., 128., 255.}; + std::array d_rgba{229., 128., 255., a}; + std::array d_hsv{288., 50., 100.}; + std::array d_hsva{288., 50., 100., 100.}; + + vector v{ + Color(d_rgb, Model::RGB), + Color(d_rgba, Model::RGB), + Color(d_hsv, Model::HSV), + Color(d_hsva, Model::HSV), + Color("#e580ff")}; + + for (const auto &c : v) + { + CHECK(Approx(c.rgb().vec(),1.) == Color({229., 128., 255.}).vec()); + CHECK(Approx(c.rgb().vec(),1.) == Color({229., 128., 255., a}).vec()); + CHECK(Approx(c.hsv().vec(),1.) == Color({288., 50., 100.}, Model::HSV).vec()); + CHECK(Approx(c.hsv().vec(),1.) == Color({288., 50., 100., 100.}, Model::HSV).vec()); + } + } + + { + // Pink 40% opacity + + float a = 0.4*255.; + std::array d_rgba { 229.,128.,255.,a }; + std::array d_hsva { 288.,50.,100.,40. }; + + vector v { + Color(d_rgba, Model::RGB), + Color(d_hsva, Model::HSV), + Color("#e580ff66") + }; + + for(const auto& c : v) + { + CHECK(Approx(c.rgb().vec(),1.) == Color({229.,128.,255.,a}).vec()); + CHECK(Approx(c.hsv().vec(),1.) == Color({288.,50.,100.,40.},Model::HSV).vec()); + } + } +} \ No newline at end of file diff --git a/tests/graphics/styles/codac2_tests_Color.py b/tests/graphics/styles/codac2_tests_Color.py new file mode 100644 index 00000000..bd9e4c8e --- /dev/null +++ b/tests/graphics/styles/codac2_tests_Color.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python + +# Codac tests +# ---------------------------------------------------------------------------- +# \date 2024 +# \author Simon Rohou, Maël Godard +# \copyright Copyright 2024 Codac Team +# \license GNU Lesser General Public License (LGPL) + +import unittest +from codac import * + + +class TestColor(unittest.TestCase): + + def test_Color(self): + # Red + + d_rgb = [255, 0, 0] + d_rgba = [255, 0, 0, 255] + d_hsv = [0, 100, 100] + d_hsva = [0, 100, 100, 100] + + + colors = [ + Color(d_rgb, Model.RGB), + Color(d_rgba, Model.RGB), + Color(d_hsv, Model.HSV), + Color(d_hsva, Model.HSV), + Color("#FF0000") + ] + + for c in colors: + self.assertTrue(Approx(c.rgb().vec(), 1.0)==Color([255, 0, 0]).rgb().vec()) + self.assertTrue(Approx(c.rgb().vec(), 1.0)==Color([255, 0, 0, 255]).rgb().vec()) + self.assertTrue(Approx(c.hsv().vec(), 1.0)==Color([0, 100, 100],Model.HSV).hsv().vec()) + self.assertTrue(Approx(c.hsv().vec(), 1.0)==Color([0, 100, 100, 100],Model.HSV).hsv().vec()) + + # Pink full opacity + + d_rgb = [229,128,255] + d_rgba = [229,128,255,255] + d_hsv = [288,50,100] + d_hsva = [288,50,100,100] + + colors = [ + Color(d_rgb, Model.RGB), + Color(d_rgba, Model.RGB), + Color(d_hsv, Model.HSV), + Color(d_hsva, Model.HSV), + Color("#E580FF") + ] + + for c in colors: + self.assertTrue(Approx(c.rgb().vec(), 1.0)==Color([229,128,255]).rgb().vec()) + self.assertTrue(Approx(c.rgb().vec(), 1.0)==Color([229,128,255,255]).rgb().vec()) + self.assertTrue(Approx(c.hsv().vec(), 1.0)==Color([288,50,100],Model.HSV).hsv().vec()) + self.assertTrue(Approx(c.hsv().vec(), 1.0)==Color([288,50,100,100],Model.HSV).hsv().vec()) + + # Pink 40% opacity + + a_rgb=102 + a_hsv=40 + d_rgba = [229,128,255,a_rgb] + d_hsva = [288,50,100,a_hsv] + + + colors = [ + Color(d_rgba, Model.RGB), + Color(d_hsva, Model.HSV), + Color("#E580FF66") + ] + for c in colors: + self.assertTrue(Approx(c.rgb().vec(), 1.0)==Color([229,128,255,a_rgb]).rgb().vec()) + self.assertTrue(Approx(c.hsv().vec(), 1.0)==Color([288,50,100,a_hsv],Model.HSV).hsv().vec()) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file