├── .clang-tidy ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── icon.png ├── include └── cvplot │ ├── color.h │ ├── cvplot.h │ ├── figure.h │ ├── highgui.h │ └── window.h ├── res ├── demo.jpg └── line.jpg ├── src ├── cvplot │ ├── color.cc │ ├── figure.cc │ ├── highgui.cc │ ├── internal.h │ └── window.cc └── demo │ └── demo.cc └── test └── cvplot ├── color_test.cc ├── figure_test.cc └── window_test.cc /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: '*,-llvmlibc-*,-bugprone-*,-hicpp-*,-fuchsia-*,-altera-*,-*-magic-numbers' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | build/ 35 | bin/ 36 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | ### master (untagged) 5 | 6 | * Remove window tick 7 | * Remove paleness 8 | * Remove color uniq 9 | * Add lint rules 10 | 11 | ### 0.0.4 (2022-06-03) 12 | 13 | * Fix for axis color 14 | * Add draw to PNG file 15 | * More contrast, bye pale 16 | * Text sizing and drop shadow 17 | * Build and format fixes (thanks @palerikm) 18 | 19 | ### 0.0.3 (2018-01-05) 20 | 21 | * Add highgui wrapper 22 | * Add mouse 23 | * Add auto flush 24 | 25 | ### 0.0.2 (2018-01-03) 26 | 27 | * Add transparency demo 28 | * Add fill line type 29 | 30 | ### 0.0.1 (2017-12-22) 31 | 32 | * Add dynamic color 33 | 34 | ### 0.0.0 (2017-12-19) 35 | 36 | * Initial release 37 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project (cvplot) 4 | 5 | find_package(OpenCV REQUIRED) 6 | 7 | include_directories(include) 8 | include_directories(${OpenCV_INCLUDE_DIRS}) 9 | 10 | set(CMAKE_CXX_STANDARD 11) 11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC ") 12 | set(CVPLOT_LIB cvplot) 13 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin") 14 | set(GOOGLE_TEST_VERSION "1.11.0") 15 | 16 | find_program(CLANG_TIDY_EXISTS clang-tidy) 17 | if(CLANG_TIDY_EXISTS AND NOT DEFINED CVPLOT_TEST) 18 | set(CMAKE_CXX_CLANG_TIDY clang-tidy) 19 | endif() 20 | 21 | if (DEFINED CVPLOT_LIB) 22 | file(GLOB LIB_SOURCES "${PROJECT_SOURCE_DIR}/src/cvplot/*.cc") 23 | add_library(${CVPLOT_LIB} ${LIB_SOURCES}) 24 | endif() 25 | 26 | if (${CVPLOT_DEMO}) 27 | file(GLOB BIN_SOURCES "${PROJECT_SOURCE_DIR}/src/demo/*.cc") 28 | foreach(filename ${BIN_SOURCES}) 29 | get_filename_component(name ${filename} NAME_WE) 30 | add_executable(${name} ${filename}) 31 | target_link_libraries(${name} ${CVPLOT_LIB} ${OpenCV_LIBS}) 32 | endforeach() 33 | endif() 34 | 35 | target_include_directories ( cvplot PUBLIC ../include ) 36 | target_include_directories ( cvplot PRIVATE ../src ) 37 | 38 | install ( TARGETS cvplot 39 | LIBRARY DESTINATION lib 40 | ARCHIVE DESTINATION lib 41 | RUNTIME DESTINATION bin) 42 | 43 | install ( DIRECTORY include DESTINATION . 44 | PATTERN CMakeLists.txt EXCLUDE ) 45 | 46 | if (${CVPLOT_TEST}) 47 | include(FetchContent) 48 | FetchContent_Declare(googletest URL "https://github.com/google/googletest/archive/refs/tags/release-${GOOGLE_TEST_VERSION}.zip") 49 | FetchContent_MakeAvailable(googletest) 50 | enable_testing() 51 | include(GoogleTest) 52 | 53 | file(GLOB TEST_SOURCES "${PROJECT_SOURCE_DIR}/test/cvplot/*_test.cc") 54 | foreach(filename ${TEST_SOURCES}) 55 | get_filename_component(name ${filename} NAME_WE) 56 | add_executable(${name} ${filename}) 57 | target_link_libraries(${name} gtest_main ${CVPLOT_LIB} ${OpenCV_LIBS}) 58 | gtest_discover_tests(${name}) 59 | endforeach() 60 | endif() 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Leo Vandriel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Proxy to CMake 2 | 3 | .PHONY: all clean demo test format 4 | 5 | all: 6 | @mkdir -p build && cd build && cmake .. && make 7 | 8 | debug: 9 | @rm -rf build && mkdir -p build && cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. && make 10 | 11 | clean: 12 | @rm -rf build bin 13 | 14 | demo: 15 | @mkdir -p build && cd build && cmake -DCVPLOT_DEMO=1 .. >/dev/null && make >/dev/null 16 | @./bin/demo 17 | 18 | test: 19 | @mkdir -p build && cd build && cmake -DCVPLOT_TEST=1 .. >/dev/null && make >/dev/null 20 | @find bin -regex "bin/[a-z_]*_test" -type f -exec ./{} \; 21 | 22 | format: 23 | @find . -iname "*.h" -o -iname "*.cc" | xargs clang-format --style=Google -i 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cvplot Icon 2 | 3 | # cvplot 4 | 5 | *Graph plots, drawing, layout and windows in OpenCV.* 6 | 7 | 8 | ## About 9 | 10 | Yet another cvplot library? Yes. Because they're all pretty bad. Like this one. 11 | 12 | 13 | ## Build 14 | 15 | Install the dependencies CMake and [OpenCV](https://github.com/opencv/opencv). If you're on macOS, use Homebrew: 16 | 17 | brew install cmake opencv 18 | 19 | On Ubuntu: 20 | 21 | apt-get install cmake libopencv-dev 22 | 23 | Next build using CMake. The easiest way: 24 | 25 | make 26 | 27 | Internally it creates a `build` folder and runs CMake from there. 28 | 29 | This project is developed and tested on macOS and Ubuntu. 30 | 31 | 32 | ## Example 33 | 34 | To draw a simple line graph: 35 | 36 | cvplot::figure("myplot").series("myline") 37 | .addValue({1., 3., 2., 5., 4.}); 38 | cvplot::figure("myplot").show(); 39 | 40 | cvplot example 41 | 42 | 43 | ## Features 44 | 45 | - Graphs: line, histogram, scatter 46 | - Time series, parametric, range 47 | - Automatic and dynamic coloring 48 | - Transparency (yes, really) 49 | - Image and text drawing 50 | - Sub-windows (views) 51 | - Window and view layout 52 | - Green view frame 53 | - Mouse support 54 | - OpenCV-like API (highgui) 55 | 56 | 57 | ## Demo 58 | 59 | To see some of the plotting in action, run the demo: 60 | 61 | make demo 62 | 63 | To learn more about these examples, take a look at `src/demo/demo.cc`. 64 | 65 | cvplot demo 66 | 67 | ## Test 68 | 69 | Run tests with: 70 | 71 | make test 72 | 73 | 74 | ## Contributing 75 | 76 | Your contributions to cvplot are welcome! cvplot is small and nimble, with lots of missing features. If you would like to see a new feature, get your code merged, or report a bug, please don't hesitate to reach out by filing a PR or issue. 77 | 78 | 79 | ## License 80 | 81 | MIT 82 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leovandriel/cvplot/2175092e95fce25464f849a84d3347da374ea813/icon.png -------------------------------------------------------------------------------- /include/cvplot/color.h: -------------------------------------------------------------------------------- 1 | #ifndef CVPLOT_COLOR_H 2 | #define CVPLOT_COLOR_H 3 | 4 | #include 5 | 6 | namespace cvplot { 7 | 8 | struct Color { 9 | uint8_t r, g, b, a; 10 | Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) 11 | : r(r), g(g), b(b), a(a) {} 12 | Color(const uint8_t *rgb, uint8_t a = 255) 13 | : Color(rgb[0], rgb[1], rgb[2], a) {} 14 | Color() : Color(0, 0, 0) {} 15 | 16 | auto alpha(uint8_t alpha) const -> Color; 17 | auto gamma(double gamma) const -> Color; 18 | auto hue() const -> double; 19 | 20 | static auto gray(uint8_t v) -> Color; 21 | static auto hue(double hue) -> Color; 22 | static auto cos(double hue) -> Color; 23 | static auto index(uint8_t index, uint8_t density = 16, double avoid = 2., 24 | double range = 2.) -> Color; 25 | static auto hash(const std::string &seed) -> Color; 26 | }; 27 | 28 | static const Color Red = Color::hue(0.); 29 | static const Color Orange = Color::hue(.5); 30 | static const Color Yellow = Color::hue(1.); 31 | static const Color Lawn = Color::hue(1.5); 32 | static const Color Green = Color::hue(2.); 33 | static const Color Aqua = Color::hue(2.5); 34 | static const Color Cyan = Color::hue(3.); 35 | static const Color Sky = Color::hue(3.5); 36 | static const Color Blue = Color::hue(4.); 37 | static const Color Purple = Color::hue(4.5); 38 | static const Color Magenta = Color::hue(5.); 39 | static const Color Pink = Color::hue(5.5); 40 | static const Color Black = Color::gray(0); 41 | static const Color Dark = Color::gray(32); 42 | static const Color Gray = Color::gray(128); 43 | static const Color Light = Color::gray(223); 44 | static const Color White = Color::gray(255); 45 | 46 | } // namespace cvplot 47 | 48 | #endif // CVPLOT_COLOR_H 49 | -------------------------------------------------------------------------------- /include/cvplot/cvplot.h: -------------------------------------------------------------------------------- 1 | #ifndef CVPLOT_H 2 | #define CVPLOT_H 3 | 4 | #include "color.h" 5 | #include "figure.h" 6 | #include "highgui.h" 7 | #include "window.h" 8 | 9 | #endif // CVPLOT_H 10 | -------------------------------------------------------------------------------- /include/cvplot/figure.h: -------------------------------------------------------------------------------- 1 | #ifndef CVPLOT_FIGURE_H 2 | #define CVPLOT_FIGURE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "color.h" 10 | #include "window.h" 11 | 12 | namespace cvplot { 13 | 14 | struct Point2 { 15 | double x, y; 16 | Point2() : Point2(0, 0) {} 17 | Point2(double x, double y) : x(x), y(y) {} 18 | }; 19 | 20 | struct Point3 { 21 | double x, y, z; 22 | Point3() : Point3(0, 0, 0) {} 23 | Point3(double x, double y, double z) : x(x), y(y), z(z) {} 24 | }; 25 | 26 | enum Type { 27 | Line, 28 | DotLine, 29 | Dots, 30 | FillLine, 31 | RangeLine, 32 | Histogram, 33 | Vistogram, 34 | Horizontal, 35 | Vertical, 36 | Range, 37 | Circle, 38 | }; 39 | 40 | class Series { 41 | public: 42 | Series(std::string label, enum Type type, Color color) 43 | : label_(std::move(label)), 44 | type_(type), 45 | color_(color), 46 | dims_(0), 47 | depth_(0), 48 | legend_(true), 49 | dynamic_color_(false) {} 50 | 51 | auto type(enum Type type) -> Series &; 52 | auto color(Color color) -> Series &; 53 | auto dynamicColor(bool dynamic_color) -> Series &; 54 | auto legend(bool legend) -> Series &; 55 | auto add(const std::vector> &data) -> Series &; 56 | auto add(const std::vector> &data) -> Series &; 57 | auto add(const std::vector> &data) -> Series &; 58 | auto addValue(const std::vector &values) -> Series &; 59 | auto addValue(const std::vector &values) -> Series &; 60 | auto addValue(const std::vector &values) -> Series &; 61 | auto add(double key, double value) -> Series &; 62 | auto add(double key, Point2 value) -> Series &; 63 | auto add(double key, Point3 value) -> Series &; 64 | auto addValue(double value) -> Series &; 65 | auto addValue(double value_a, double value_b) -> Series &; 66 | auto addValue(double value_a, double value_b, double value_c) -> Series &; 67 | auto set(const std::vector> &data) -> Series &; 68 | auto set(const std::vector> &data) -> Series &; 69 | auto set(const std::vector> &data) -> Series &; 70 | auto setValue(const std::vector &values) -> Series &; 71 | auto setValue(const std::vector &values) -> Series &; 72 | auto setValue(const std::vector &values) -> Series &; 73 | auto set(double key, double value) -> Series &; 74 | auto set(double key, double value_a, double value_b) -> Series &; 75 | auto set(double key, double value_a, double value_b, double value_c) 76 | -> Series &; 77 | auto setValue(double value) -> Series &; 78 | auto setValue(double value_a, double value_b) -> Series &; 79 | auto setValue(double value_a, double value_b, double value_c) -> Series &; 80 | auto clear() -> Series &; 81 | 82 | auto label() const -> const std::string &; 83 | auto legend() const -> bool; 84 | auto color() const -> Color; 85 | void draw(void *buffer, double x_min, double x_max, double y_min, 86 | double y_max, double xs, double xd, double ys, double yd, 87 | double x_axis, double y_axis, int unit, double offset) const; 88 | auto collides() const -> bool; 89 | void dot(void *b, int x, int y, int r) const; 90 | void bounds(double &x_min, double &x_max, double &y_min, double &y_max, 91 | int &n_max, int &p_max) const; 92 | void verifyParams() const; 93 | 94 | protected: 95 | void ensureDimsDepth(int dims, int depth); 96 | auto flipAxis() const -> bool; 97 | 98 | protected: 99 | std::vector entries_; 100 | std::vector data_; 101 | enum Type type_; 102 | Color color_; 103 | std::string label_; 104 | int dims_; 105 | int depth_; 106 | bool legend_; 107 | bool dynamic_color_; 108 | }; 109 | 110 | class Figure { 111 | public: 112 | Figure(View &view) 113 | : view_(view), 114 | border_size_(50), 115 | background_color_(White), 116 | axis_color_(Black), 117 | sub_axis_color_(Light), 118 | text_color_(Black), 119 | include_zero_x_(true), 120 | include_zero_y_(true), 121 | aspect_square_(false), 122 | grid_size_(60), 123 | grid_padding_(20) {} 124 | 125 | auto clear() -> Figure &; 126 | auto origin(bool x, bool y) -> Figure &; 127 | auto square(bool square) -> Figure &; 128 | auto border(int size) -> Figure &; 129 | auto alpha(int alpha) -> Figure &; 130 | auto gridSize(int size) -> Figure &; 131 | auto backgroundColor(Color color) -> Figure &; 132 | auto axisColor(Color color) -> Figure &; 133 | auto subaxisColor(Color color) -> Figure &; 134 | auto textColor(Color color) -> Figure &; 135 | auto backgroundColor() -> Color; 136 | auto axisColor() -> Color; 137 | auto subaxisColor() -> Color; 138 | auto textColor() -> Color; 139 | 140 | void draw(void *buffer, double x_min, double x_max, double y_min, 141 | double y_max, int n_max, int p_max) const; 142 | auto drawFit(void *buffer) const -> int; 143 | auto drawFile(const std::string &filename, Size size) const -> bool; 144 | void show(bool flush = true) const; 145 | auto series(const std::string &label) -> Series &; 146 | 147 | protected: 148 | View &view_; 149 | std::vector series_; 150 | int border_size_; 151 | Color background_color_; 152 | Color axis_color_; 153 | Color sub_axis_color_; 154 | Color text_color_; 155 | bool include_zero_x_; 156 | bool include_zero_y_; 157 | bool aspect_square_; 158 | int grid_size_; 159 | int grid_padding_; 160 | }; 161 | 162 | auto figure(const std::string &name) -> Figure &; 163 | 164 | } // namespace cvplot 165 | 166 | #endif // CVPLOT_FIGURE_H 167 | -------------------------------------------------------------------------------- /include/cvplot/highgui.h: -------------------------------------------------------------------------------- 1 | #ifndef CVPLOT_HIGHGUI_H 2 | #define CVPLOT_HIGHGUI_H 3 | 4 | #include 5 | #include 6 | 7 | #include "window.h" 8 | 9 | namespace cvplot { 10 | 11 | auto createTrackbar(const std::string &trackbarname, const std::string &winname, 12 | int *value, int count, TrackbarCallback onChange = nullptr, 13 | void *userdata = nullptr) -> int; 14 | void destroyAllWindows(); 15 | void destroyWindow(const std::string &view); 16 | auto getMouseWheelDelta(int flags) -> int; 17 | auto getTrackbarPos(const std::string &trackbarname, const std::string &winname) 18 | -> int; 19 | auto getWindowImageRect(const std::string &winname) -> Rect; 20 | auto getWindowProperty(const std::string &winname, int prop_id) -> double; 21 | void imshow(const std::string &view, void *img); 22 | void moveWindow(const std::string &view, int x, int y); 23 | void namedWindow(const std::string &view, int flags = 0); 24 | auto pollKey() -> int; 25 | void resizeWindow(const std::string &view, int width, int height); 26 | void resizeWindow(const std::string &view, const Size &size); 27 | auto selectROI(const std::string &windowName, void *img, 28 | bool showCrosshair = true, bool fromCenter = false) -> Rect; 29 | auto selectROI(void *img, bool showCrosshair = true, bool fromCenter = false) 30 | -> Rect; 31 | void selectROIs(const std::string &windowName, void *img, 32 | std::vector &boundingBoxes, bool showCrosshair = true, 33 | bool fromCenter = false); 34 | void setMouseCallback(const std::string &view, MouseCallback onMouse, 35 | void *userdata = nullptr); 36 | void setTrackbarMax(const std::string &trackbarname, const std::string &winname, 37 | int maxval); 38 | void setTrackbarMin(const std::string &trackbarname, const std::string &winname, 39 | int minval); 40 | void setTrackbarPos(const std::string &trackbarname, const std::string &winname, 41 | int pos); 42 | void setWindowProperty(const std::string &winname, int prop_id, 43 | double prop_value); 44 | void setWindowTitle(const std::string &view, const std::string &title); 45 | auto startWindowThread() -> int; 46 | void updateWindow(const std::string &winname); 47 | auto waitKey(int delay = 0) -> int; 48 | auto waitKeyEx(int delay = 0) -> int; 49 | 50 | } // namespace cvplot 51 | 52 | #endif // CVPLOT_HIGHGUI_H 53 | -------------------------------------------------------------------------------- /include/cvplot/window.h: -------------------------------------------------------------------------------- 1 | #ifndef CVPLOT_WINDOW_H 2 | #define CVPLOT_WINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "color.h" 9 | 10 | namespace cvplot { 11 | 12 | struct Rect { 13 | int x, y, width, height; 14 | Rect(int x, int y, int width, int height) 15 | : x(x), y(y), width(width), height(height) {} 16 | }; 17 | 18 | struct Size { 19 | int width, height; 20 | Size(int width, int height) : width(width), height(height) {} 21 | }; 22 | 23 | struct Offset { 24 | int x, y; 25 | Offset(int x, int y) : x(x), y(y) {} 26 | }; 27 | 28 | using MouseCallback = void (*)(int, int, int, int, void *); 29 | using TrackbarCallback = void (*)(int, void *); 30 | 31 | class Window; 32 | 33 | class View { 34 | public: 35 | View(Window &window, std::string title = "", Size size = {300, 300}) 36 | : window_(window), 37 | title_(std::move(title)), 38 | rect_(0, 0, size.width, size.height), 39 | frameless_(false), 40 | background_color_(Black), 41 | frame_color_(Green), 42 | text_color_(Black), 43 | mouse_callback_(nullptr), 44 | mouse_param_(nullptr) {} 45 | auto resize(Rect rect) -> View &; 46 | auto size(Size size) -> View &; 47 | auto offset(Offset offset) -> View &; 48 | auto autosize() -> View &; 49 | auto title(const std::string &title) -> View &; 50 | auto alpha(int alpha) -> View &; 51 | auto backgroundColor(Color color) -> View &; 52 | auto frameColor(Color color) -> View &; 53 | auto textColor(Color color) -> View &; 54 | auto mouse(MouseCallback callback, void *param = nullptr) -> View &; 55 | void onmouse(int event, int x, int y, int flags); 56 | 57 | auto backgroundColor() -> Color; 58 | auto frameColor() -> Color; 59 | auto textColor() -> Color; 60 | auto title() -> std::string &; 61 | auto has(Offset offset) const -> bool; 62 | 63 | void drawRect(Rect rect, Color color); 64 | void drawFill(Color background = White); 65 | void drawImage(const void *image, int alpha = 255); 66 | void drawText(const std::string &text, Offset offset, Color color, 67 | double height = 12.) const; 68 | void drawTextShadow(const std::string &text, Offset offset, Color color, 69 | double height) const; 70 | void drawFrame(const std::string &title) const; 71 | auto buffer(Rect &rect) -> void *; 72 | void finish(); 73 | void flush(); 74 | void hide(bool hidden = true); 75 | 76 | auto operator=(const View &) -> View & = delete; 77 | 78 | protected: 79 | Rect rect_; 80 | std::string title_; 81 | bool frameless_; 82 | Window &window_; 83 | Color background_color_; 84 | Color frame_color_; 85 | Color text_color_; 86 | MouseCallback mouse_callback_; 87 | void *mouse_param_; 88 | bool hidden_; 89 | }; 90 | 91 | class Window { 92 | public: 93 | Window(std::string title = ""); 94 | ~Window(); 95 | auto resize(Rect rect) -> Window &; 96 | auto size(Size size) -> Window &; 97 | auto offset(Offset offset) -> Window &; 98 | auto title(const std::string &title) -> Window &; 99 | auto ensure(Rect rect) -> Window &; 100 | auto cursor(bool cursor) -> Window &; 101 | auto buffer() -> void *; 102 | void flush(); 103 | auto view(const std::string &name, Size size = {300, 300}) -> View &; 104 | void dirty(); 105 | void hide(bool hidden = true); 106 | void onmouse(int event, int x, int y, int flags); 107 | auto name() const -> const std::string & { return name_; } 108 | 109 | auto operator=(const Window &) -> Window & = delete; 110 | 111 | static auto current() -> Window &; 112 | static void current(Window &window); 113 | static auto current(const std::string &title) -> Window &; 114 | 115 | protected: 116 | Offset offset_; 117 | void *buffer_{nullptr}; 118 | std::string title_; 119 | std::string name_; 120 | std::map views_; 121 | bool dirty_{false}; 122 | bool hidden_{false}; 123 | bool show_cursor_{false}; 124 | Offset cursor_; 125 | }; 126 | 127 | class Util { 128 | public: 129 | static void sleep(double seconds = 0); 130 | static auto key(double timeout = 0) -> int; 131 | static auto line(double timeout = 0) -> std::string; 132 | }; 133 | 134 | auto window(const std::string &name) -> Window &; 135 | 136 | } // namespace cvplot 137 | 138 | #endif // CVPLOT_WINDOW_H 139 | -------------------------------------------------------------------------------- /res/demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leovandriel/cvplot/2175092e95fce25464f849a84d3347da374ea813/res/demo.jpg -------------------------------------------------------------------------------- /res/line.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leovandriel/cvplot/2175092e95fce25464f849a84d3347da374ea813/res/line.jpg -------------------------------------------------------------------------------- /src/cvplot/color.cc: -------------------------------------------------------------------------------- 1 | #include "cvplot/color.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace cvplot { 7 | 8 | auto Color::alpha(uint8_t alpha) const -> Color { return {r, g, b, alpha}; } 9 | 10 | auto Color::gamma(double gamma) const -> Color { 11 | return {static_cast(pow(r / 255., 1 / gamma) * 255), 12 | static_cast(pow(g / 255., 1 / gamma) * 255), 13 | static_cast(pow(b / 255., 1 / gamma) * 255), a}; 14 | } 15 | 16 | auto Color::gray(uint8_t v) -> Color { return {v, v, v}; } 17 | 18 | auto Color::index(uint8_t index, uint8_t density, double avoid, 19 | double range) -> Color { // avoid greens by default 20 | if (avoid > 0) { 21 | auto step = density / (6 - range); 22 | auto offset = (avoid + range / 2) * static_cast(step); 23 | index = static_cast(offset) + index % density; 24 | density += static_cast(step * range); 25 | } 26 | auto hue = index % density * 6. / density; 27 | return Color::cos(hue); 28 | } 29 | 30 | auto Color::hash(const std::string &seed) -> Color { 31 | unsigned hash = 37; 32 | for (char c : seed) { 33 | hash = (hash * 54059) ^ (c * 76963); 34 | } 35 | return Color::index(hash); 36 | } 37 | 38 | auto Color::hue(double hue) -> Color { 39 | Color color; 40 | auto i = static_cast(hue); 41 | auto f = static_cast((hue - i) * 255); 42 | switch (i % 6) { 43 | case 0: 44 | return {255, f, 0}; 45 | case 1: 46 | return {static_cast(255 - f), 255, 0}; 47 | case 2: 48 | return {0, 255, f}; 49 | case 3: 50 | return {0, static_cast(255 - f), 255}; 51 | case 4: 52 | return {f, 0, 255}; 53 | case 5: 54 | return {255, 0, static_cast(255 - f)}; 55 | } 56 | return {}; 57 | } 58 | 59 | auto Color::cos(double hue) -> Color { 60 | return {static_cast((std::cos(hue * 1.047) + 1) * 127.9), 61 | static_cast((std::cos((hue - 2) * 1.047) + 1) * 127.9), 62 | static_cast((std::cos((hue - 4) * 1.047) + 1) * 127.9)}; 63 | } 64 | 65 | auto Color::hue() const -> double { 66 | auto min = std::min(std::min(r, g), b); 67 | auto max = std::max(std::max(r, g), b); 68 | if (min == max) { 69 | return 0; 70 | } 71 | auto hue = 0.; 72 | if (r == max) { 73 | hue = (g - b) / static_cast(max - min); 74 | } else if (g == max) { 75 | hue = 2. + (b - r) / static_cast(max - min); 76 | } else { 77 | hue = 4. + (r - g) / static_cast(max - min); 78 | } 79 | if (hue < 0) { 80 | hue += 6; 81 | } 82 | return hue; 83 | } 84 | 85 | } // namespace cvplot 86 | -------------------------------------------------------------------------------- /src/cvplot/figure.cc: -------------------------------------------------------------------------------- 1 | #include "cvplot/figure.h" 2 | 3 | #include 4 | #include 5 | #if CV_MAJOR_VERSION >= 3 6 | #include 7 | #endif 8 | 9 | #include "cvplot/window.h" 10 | #include "internal.h" 11 | 12 | #if CV_MAJOR_VERSION >= 3 13 | constexpr int LINE_AA = cv::LINE_AA; 14 | #else 15 | constexpr int LINE_AA = CV_AA; 16 | #endif 17 | 18 | namespace cvplot { 19 | 20 | namespace { 21 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 22 | std::map shared_figures_; 23 | } // namespace 24 | 25 | void Series::verifyParams() const { 26 | auto dims = 1; 27 | auto depth = 0; 28 | switch (type_) { 29 | case Line: 30 | case DotLine: 31 | case Dots: 32 | case FillLine: 33 | case Vistogram: 34 | case Histogram: 35 | case Horizontal: 36 | case Vertical: { 37 | depth = 1; 38 | break; 39 | } 40 | case RangeLine: { 41 | depth = 3; 42 | break; 43 | } 44 | case Range: 45 | case Circle: { 46 | depth = 2; 47 | break; 48 | } 49 | } 50 | if (dynamic_color_) { 51 | depth += 1; 52 | } 53 | if (!entries_.empty()) { 54 | EXPECT_EQ(dims_, dims); 55 | EXPECT_EQ(depth_, depth); 56 | } 57 | } 58 | 59 | void Series::ensureDimsDepth(int dims, int depth) { 60 | if (dims_ != dims) { 61 | if (dims_ != 0) { 62 | std::cerr << "incorrect dims (input dimensions), was " << dims_ << " now " 63 | << dims << std::endl; 64 | } 65 | dims_ = dims; 66 | } 67 | if (depth_ != depth) { 68 | if (depth_ != 0) { 69 | std::cerr << "incorrect depth (output dimensions), was " << depth_ 70 | << " now " << depth << std::endl; 71 | } 72 | depth_ = depth; 73 | } 74 | } 75 | 76 | auto Series::clear() -> Series & { 77 | entries_.clear(); 78 | data_.clear(); 79 | dims_ = 0; 80 | depth_ = 0; 81 | return *this; 82 | } 83 | 84 | auto Series::type(enum Type type) -> Series & { 85 | type_ = type; 86 | return *this; 87 | } 88 | 89 | auto Series::color(Color color) -> Series & { 90 | color_ = color; 91 | return *this; 92 | } 93 | 94 | auto Series::dynamicColor(bool dynamic_color) -> Series & { 95 | dynamic_color_ = dynamic_color; 96 | return *this; 97 | } 98 | 99 | auto Series::legend(bool legend) -> Series & { 100 | legend_ = legend; 101 | return *this; 102 | } 103 | 104 | auto Series::add(const std::vector> &data) 105 | -> Series & { 106 | ensureDimsDepth(1, 1); 107 | for (const auto &d : data) { 108 | entries_.push_back(static_cast(data_.size())); 109 | data_.push_back(d.first); 110 | data_.push_back(d.second); 111 | } 112 | return *this; 113 | } 114 | 115 | auto Series::add(const std::vector> &data) 116 | -> Series & { 117 | ensureDimsDepth(1, 2); 118 | for (const auto &d : data) { 119 | entries_.push_back(static_cast(data_.size())); 120 | data_.push_back(d.first); 121 | data_.push_back(d.second.x); 122 | data_.push_back(d.second.y); 123 | } 124 | return *this; 125 | } 126 | 127 | auto Series::add(const std::vector> &data) 128 | -> Series & { 129 | ensureDimsDepth(1, 3); 130 | for (const auto &d : data) { 131 | entries_.push_back(static_cast(data_.size())); 132 | data_.push_back(d.first); 133 | data_.push_back(d.second.x); 134 | data_.push_back(d.second.y); 135 | data_.push_back(d.second.z); 136 | } 137 | return *this; 138 | } 139 | 140 | auto Series::addValue(const std::vector &values) -> Series & { 141 | std::vector> data(values.size()); 142 | auto i = 0; 143 | for (auto &d : data) { 144 | d.first = static_cast(i + entries_.size()); 145 | d.second = values[i++]; 146 | } 147 | return add(data); 148 | } 149 | 150 | auto Series::addValue(const std::vector &values) -> Series & { 151 | std::vector> data(values.size()); 152 | auto i = 0; 153 | for (auto &d : data) { 154 | d.first = static_cast(i + entries_.size()); 155 | d.second = values[i++]; 156 | } 157 | return add(data); 158 | } 159 | 160 | auto Series::addValue(const std::vector &values) -> Series & { 161 | std::vector> data(values.size()); 162 | auto i = 0; 163 | for (auto &d : data) { 164 | d.first = static_cast(i + entries_.size()); 165 | d.second = values[i++]; 166 | } 167 | return add(data); 168 | } 169 | 170 | auto Series::add(double key, double value) -> Series & { 171 | return add(std::vector>({{key, value}})); 172 | } 173 | 174 | auto Series::add(double key, Point2 value) -> Series & { 175 | return add(std::vector>({{key, value}})); 176 | } 177 | 178 | auto Series::add(double key, Point3 value) -> Series & { 179 | return add(std::vector>({{key, value}})); 180 | } 181 | 182 | auto Series::addValue(double value) -> Series & { 183 | return addValue(std::vector({value})); 184 | } 185 | 186 | auto Series::addValue(double value_a, double value_b) -> Series & { 187 | return addValue(std::vector({{value_a, value_b}})); 188 | } 189 | 190 | auto Series::addValue(double value_a, double value_b, double value_c) 191 | -> Series & { 192 | return addValue(std::vector({{value_a, value_b, value_c}})); 193 | } 194 | 195 | auto Series::set(const std::vector> &data) 196 | -> Series & { 197 | clear(); 198 | return add(data); 199 | } 200 | 201 | auto Series::set(const std::vector> &data) 202 | -> Series & { 203 | clear(); 204 | return add(data); 205 | } 206 | 207 | auto Series::set(const std::vector> &data) 208 | -> Series & { 209 | clear(); 210 | return add(data); 211 | } 212 | 213 | auto Series::setValue(const std::vector &values) -> Series & { 214 | std::vector> data(values.size()); 215 | auto i = 0; 216 | for (auto &d : data) { 217 | d.first = i; 218 | d.second = values[i++]; 219 | } 220 | return set(data); 221 | } 222 | 223 | auto Series::setValue(const std::vector &values) -> Series & { 224 | std::vector> data(values.size()); 225 | auto i = 0; 226 | for (auto &d : data) { 227 | d.first = i; 228 | d.second = values[i++]; 229 | } 230 | return set(data); 231 | } 232 | 233 | auto Series::setValue(const std::vector &values) -> Series & { 234 | std::vector> data(values.size()); 235 | auto i = 0; 236 | for (auto &d : data) { 237 | d.first = i; 238 | d.second = values[i++]; 239 | } 240 | return set(data); 241 | } 242 | 243 | auto Series::set(double key, double value) -> Series & { 244 | return set(std::vector>({{key, value}})); 245 | } 246 | 247 | auto Series::set(double key, double value_a, double value_b) -> Series & { 248 | return set( 249 | std::vector>({{key, {value_a, value_b}}})); 250 | } 251 | 252 | auto Series::set(double key, double value_a, double value_b, double value_c) 253 | -> Series & { 254 | return set(std::vector>( 255 | {{key, {value_a, value_b, value_c}}})); 256 | } 257 | 258 | auto Series::setValue(double value) -> Series & { 259 | return setValue(std::vector({value})); 260 | } 261 | 262 | auto Series::setValue(double value_a, double value_b) -> Series & { 263 | return setValue(std::vector({{value_a, value_b}})); 264 | } 265 | 266 | auto Series::setValue(double value_a, double value_b, double value_c) 267 | -> Series & { 268 | return setValue(std::vector({{value_a, value_b, value_c}})); 269 | } 270 | 271 | auto Series::label() const -> const std::string & { return label_; } 272 | 273 | auto Series::legend() const -> bool { return legend_; } 274 | 275 | auto Series::color() const -> Color { return color_; } 276 | 277 | auto Series::collides() const -> bool { 278 | return type_ == Histogram || type_ == Vistogram; 279 | } 280 | 281 | auto Series::flipAxis() const -> bool { 282 | return type_ == Vertical || type_ == Vistogram; 283 | } 284 | 285 | // NOLINTNEXTLINE(readability-function-cognitive-complexity) 286 | void Series::bounds(double &x_min, double &x_max, double &y_min, double &y_max, 287 | int &n_max, int &p_max) const { 288 | for (const auto &e : entries_) { 289 | auto xe = e; 290 | auto xd = dims_; 291 | auto ye = e + dims_; 292 | auto yd = depth_ - (dynamic_color_ ? 1 : 0); 293 | if (type_ == Circle) { 294 | yd = 1; 295 | } 296 | if (flipAxis()) { 297 | auto s = xe; 298 | xe = ye; 299 | ye = s; 300 | s = xd; 301 | xd = yd; 302 | yd = s; 303 | } 304 | if (type_ != Horizontal) { // TODO(leo): check Horizontal/Vertical logic 305 | EXPECT_EQ(xd, 1); 306 | const auto &x = data_[xe]; 307 | if (x_min > x) { 308 | x_min = x; 309 | } 310 | if (x_max < x) { 311 | x_max = x; 312 | } 313 | } 314 | if (type_ != Vertical) { 315 | for (auto yi = ye, _y = yi + yd; yi != _y; yi++) { 316 | const auto &y = data_[yi]; 317 | if (y_min > y) { 318 | y_min = y; 319 | } 320 | if (y_max < y) { 321 | y_max = y; 322 | } 323 | } 324 | } 325 | } 326 | if (n_max < entries_.size()) { 327 | n_max = static_cast(entries_.size()); 328 | } 329 | if (type_ == Histogram || type_ == Vistogram) { 330 | p_max = std::max(30, p_max); 331 | } 332 | } 333 | 334 | void Series::dot(void *b, int x, int y, int r) const { 335 | Trans trans(b); 336 | cv::circle(trans.with(color_), {x, y}, r, color2scalar(color_), -1, LINE_AA); 337 | } 338 | 339 | // NOLINTNEXTLINE(readability-function-cognitive-complexity) 340 | void Series::draw(void *buffer, double x_min, double x_max, double y_min, 341 | double y_max, double xs, double xd, double ys, double yd, 342 | double x_axis, double y_axis, int unit, double offset) const { 343 | if (dims_ == 0 || depth_ == 0) { 344 | return; 345 | } 346 | Trans trans(*static_cast(buffer)); 347 | auto color = color2scalar(color_); 348 | switch (type_) { 349 | case Line: 350 | case DotLine: 351 | case Dots: 352 | case FillLine: 353 | case RangeLine: { 354 | if (type_ == FillLine) { 355 | bool has_last = false; 356 | double last_x = NAN; 357 | double last_y = NAN; 358 | for (const auto &e : entries_) { 359 | auto x = data_[e]; 360 | auto y = data_[e + dims_]; 361 | if (dynamic_color_) { 362 | color = color2scalar(Color::cos(data_[e + dims_ + 1])); 363 | } 364 | cv::Point point(static_cast(x * xs + xd), 365 | static_cast(y * ys + yd)); 366 | if (has_last) { 367 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) 368 | cv::Point points[4] = { 369 | point, 370 | {point.x, static_cast(y_axis * ys + yd)}, 371 | {static_cast(last_x * xs + xd), 372 | static_cast(y_axis * ys + yd)}, 373 | {static_cast(last_x * xs + xd), 374 | static_cast(last_y * ys + yd)}, 375 | }; 376 | cv::fillConvexPoly(trans.with(color_.a / 2), 377 | static_cast(points), 4, color, 378 | LINE_AA); 379 | } else { 380 | has_last = true; 381 | } 382 | last_x = x, last_y = y; 383 | } 384 | } else if (type_ == RangeLine) { 385 | bool has_last = false; 386 | double last_x = NAN; 387 | double last_y1 = NAN; 388 | double last_y2 = NAN; 389 | for (const auto &e : entries_) { 390 | auto x = data_[e]; 391 | auto y1 = data_[e + dims_ + 1]; 392 | auto y2 = data_[e + dims_ + 2]; 393 | if (dynamic_color_) { 394 | color = color2scalar(Color::cos(data_[e + dims_ + 1])); 395 | } 396 | if (has_last) { 397 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) 398 | cv::Point points[4] = { 399 | {static_cast(x * xs + xd), static_cast(y1 * ys + yd)}, 400 | {static_cast(x * xs + xd), static_cast(y2 * ys + yd)}, 401 | {static_cast(last_x * xs + xd), 402 | static_cast(last_y2 * ys + yd)}, 403 | {static_cast(last_x * xs + xd), 404 | static_cast(last_y1 * ys + yd)}, 405 | }; 406 | cv::fillConvexPoly(trans.with(color_.a / 2), 407 | static_cast(points), 4, color, 408 | LINE_AA); 409 | } else { 410 | has_last = true; 411 | } 412 | last_x = x, last_y1 = y1, last_y2 = y2; 413 | } 414 | } 415 | bool has_last = false; 416 | double last_x = NAN; 417 | double last_y = NAN; 418 | for (const auto &e : entries_) { 419 | auto x = data_[e]; 420 | auto y = data_[e + dims_]; 421 | if (dynamic_color_) { 422 | color = color2scalar(Color::cos(data_[e + dims_ + 1])); 423 | } 424 | cv::Point point(static_cast(x * xs + xd), 425 | static_cast(y * ys + yd)); 426 | if (has_last) { 427 | if (type_ == DotLine || type_ == Line || type_ == FillLine || 428 | type_ == RangeLine) { 429 | cv::line(trans.with(color_), 430 | {static_cast(last_x * xs + xd), 431 | static_cast(last_y * ys + yd)}, 432 | point, color, 1, LINE_AA); 433 | } 434 | } else { 435 | has_last = true; 436 | } 437 | if (type_ == DotLine || type_ == Dots) { 438 | cv::circle(trans.with(color_), point, 2, color, 1, LINE_AA); 439 | } 440 | last_x = x, last_y = y; 441 | } 442 | } break; 443 | case Vistogram: 444 | case Histogram: { 445 | auto u = 2 * unit; 446 | auto o = static_cast(2 * u * offset); 447 | for (const auto &e : entries_) { 448 | auto x = data_[e]; 449 | auto y = data_[e + dims_]; 450 | if (dynamic_color_) { 451 | color = color2scalar(Color::cos(data_[e + dims_ + 1])); 452 | } 453 | if (type_ == Histogram) { 454 | cv::rectangle(trans.with(color_), 455 | {static_cast(x * xs + xd) - u + o, 456 | static_cast(y_axis * ys + yd)}, 457 | {static_cast(x * xs + xd) + u + o, 458 | static_cast(y * ys + yd)}, 459 | color, -1, LINE_AA); 460 | } else if (type_ == Vistogram) { 461 | cv::rectangle(trans.with(color_), 462 | {static_cast(x_axis * xs + xd), 463 | static_cast(x * ys + yd) - u + o}, 464 | {static_cast(y * xs + xd), 465 | static_cast(x * ys + yd) + u + o}, 466 | color, -1, LINE_AA); 467 | } 468 | } 469 | 470 | } break; 471 | case Horizontal: 472 | case Vertical: { 473 | for (const auto &e : entries_) { 474 | auto y = data_[e + dims_]; 475 | if (dynamic_color_) { 476 | color = color2scalar(Color::cos(data_[e + dims_ + 1])); 477 | } 478 | if (type_ == Horizontal) { 479 | cv::line(trans.with(color_), 480 | {static_cast(x_min * xs + xd), 481 | static_cast(y * ys + yd)}, 482 | {static_cast(x_max * xs + xd), 483 | static_cast(y * ys + yd)}, 484 | color, 1, LINE_AA); 485 | } else if (type_ == Vertical) { 486 | cv::line(trans.with(color_), 487 | {static_cast(y * xs + xd), 488 | static_cast(y_min * ys + yd)}, 489 | {static_cast(y * xs + xd), 490 | static_cast(y_max * ys + yd)}, 491 | color, 1, LINE_AA); 492 | } 493 | } 494 | } break; 495 | case Range: { 496 | bool has_last = false; 497 | cv::Point last_a; 498 | cv::Point last_b; 499 | for (const auto &e : entries_) { 500 | auto x = data_[e]; 501 | auto y_a = data_[e + dims_]; 502 | auto y_b = data_[e + dims_ + 1]; 503 | if (dynamic_color_) { 504 | color = color2scalar(Color::cos(data_[e + dims_ + 2])); 505 | } 506 | cv::Point point_a(static_cast(x * xs + xd), 507 | static_cast(y_a * ys + yd)); 508 | cv::Point point_b(static_cast(x * xs + xd), 509 | static_cast(y_b * ys + yd)); 510 | if (has_last) { 511 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) 512 | cv::Point points[4] = {point_a, point_b, last_b, last_a}; 513 | cv::fillConvexPoly(trans.with(color_), 514 | static_cast(points), 4, color, 515 | LINE_AA); 516 | } else { 517 | has_last = true; 518 | } 519 | last_a = point_a, last_b = point_b; 520 | } 521 | } break; 522 | case Circle: { 523 | for (const auto &e : entries_) { 524 | auto x = data_[e]; 525 | auto y = data_[e + dims_]; 526 | auto r = data_[e + dims_ + 1]; 527 | if (dynamic_color_) { 528 | color = color2scalar(Color::cos(data_[e + dims_ + 2])); 529 | } 530 | cv::Point point(static_cast(x * xs + xd), 531 | static_cast(y * ys + yd)); 532 | cv::circle(trans.with(color_), point, static_cast(r), color, -1, 533 | LINE_AA); 534 | } 535 | } break; 536 | } 537 | } 538 | 539 | auto Figure::clear() -> Figure & { 540 | series_.clear(); 541 | return *this; 542 | } 543 | 544 | auto Figure::origin(bool x, bool y) -> Figure & { 545 | include_zero_x_ = x, include_zero_y_ = y; 546 | return *this; 547 | } 548 | 549 | auto Figure::square(bool square) -> Figure & { 550 | aspect_square_ = square; 551 | return *this; 552 | } 553 | 554 | auto Figure::border(int size) -> Figure & { 555 | border_size_ = size; 556 | return *this; 557 | } 558 | 559 | auto Figure::alpha(int alpha) -> Figure & { 560 | background_color_ = background_color_.alpha(alpha); 561 | axis_color_ = axis_color_.alpha(alpha); 562 | sub_axis_color_ = sub_axis_color_.alpha(alpha); 563 | text_color_ = text_color_.alpha(alpha); 564 | return *this; 565 | } 566 | 567 | auto Figure::gridSize(int size) -> Figure & { 568 | grid_size_ = size; 569 | return *this; 570 | } 571 | 572 | auto Figure::backgroundColor(Color color) -> Figure & { 573 | background_color_ = color; 574 | return *this; 575 | } 576 | 577 | auto Figure::axisColor(Color color) -> Figure & { 578 | axis_color_ = color; 579 | return *this; 580 | } 581 | 582 | auto Figure::subaxisColor(Color color) -> Figure & { 583 | sub_axis_color_ = color; 584 | return *this; 585 | } 586 | 587 | auto Figure::textColor(Color color) -> Figure & { 588 | text_color_ = color; 589 | return *this; 590 | } 591 | 592 | auto Figure::backgroundColor() -> Color { return background_color_; } 593 | 594 | auto Figure::axisColor() -> Color { return axis_color_; } 595 | 596 | auto Figure::subaxisColor() -> Color { return sub_axis_color_; } 597 | 598 | auto Figure::textColor() -> Color { return text_color_; } 599 | 600 | auto Figure::series(const std::string &label) -> Series & { 601 | for (auto &s : series_) { 602 | if (s.label() == label) { 603 | return s; 604 | } 605 | } 606 | Series s(label, Line, Color::hash(label)); 607 | series_.push_back(s); 608 | return series_.back(); 609 | } 610 | 611 | // NOLINTNEXTLINE(readability-function-cognitive-complexity) 612 | void Figure::draw(void *b, double x_min, double x_max, double y_min, 613 | double y_max, int n_max, int p_max) const { 614 | auto &buffer = *static_cast(b); 615 | Trans trans(b); 616 | 617 | // draw background and sub axis square 618 | cv::rectangle(trans.with(background_color_), {0, 0}, 619 | {buffer.cols, buffer.rows}, color2scalar(background_color_), -1, 620 | LINE_AA); 621 | cv::rectangle(trans.with(sub_axis_color_), {border_size_, border_size_}, 622 | {buffer.cols - border_size_, buffer.rows - border_size_}, 623 | color2scalar(sub_axis_color_), 1, LINE_AA); 624 | 625 | // size of the plotting area 626 | auto w_plot = buffer.cols - 2 * border_size_; 627 | auto h_plot = buffer.rows - 2 * border_size_; 628 | 629 | // add padding inside graph (histograms get extra) 630 | if (p_max != 0) { 631 | auto dx = p_max * (x_max - x_min) / w_plot; 632 | auto dy = p_max * (y_max - y_min) / h_plot; 633 | x_min -= dx; 634 | x_max += dx; 635 | y_min -= dy; 636 | y_max += dy; 637 | } 638 | 639 | // adjust value range if aspect ratio square 640 | if (aspect_square_) { 641 | if (h_plot * (x_max - x_min) < w_plot * (y_max - y_min)) { 642 | auto dx = w_plot * (y_max - y_min) / h_plot - (x_max - x_min); 643 | x_min -= dx / 2; 644 | x_max += dx / 2; 645 | } else if (w_plot * (y_max - y_min) < h_plot * (x_max - x_min)) { 646 | auto dy = h_plot * (x_max - x_min) / w_plot - (y_max - y_min); 647 | y_min -= dy / 2; 648 | y_max += dy / 2; 649 | } 650 | } 651 | 652 | // calc where to draw axis 653 | auto x_axis = std::max(x_min, std::min(x_max, 0.)); 654 | auto y_axis = std::max(y_min, std::min(y_max, 0.)); 655 | 656 | // calc sub axis grid size 657 | auto x_grid = 658 | (x_max != x_min ? value2snap((x_max - x_min) / floor(w_plot / grid_size_)) 659 | : 1); 660 | auto y_grid = 661 | (y_max != x_min ? value2snap((y_max - y_min) / floor(h_plot / grid_size_)) 662 | : 1); 663 | 664 | // calc affine transform value space to plot space 665 | auto xs = (x_max != x_min ? (buffer.cols - 2 * border_size_) / (x_max - x_min) 666 | : 1.); 667 | auto xd = border_size_ - x_min * xs; 668 | auto ys = (y_max != y_min ? (buffer.rows - 2 * border_size_) / (y_min - y_max) 669 | : 1.); 670 | auto yd = buffer.rows - y_min * ys - border_size_; 671 | 672 | // safe unit for showing points 673 | auto unit = 674 | std::max(1, (static_cast(std::min(buffer.cols, buffer.rows)) - 675 | 2 * border_size_) / 676 | n_max / 10); 677 | 678 | // draw sub axis 679 | for (int i = ceil(x_min / x_grid), e = floor(x_max / x_grid); i <= e; i++) { 680 | auto x = i * x_grid; 681 | cv::line(trans.with(sub_axis_color_), 682 | {static_cast(x * xs + xd), border_size_}, 683 | {static_cast(x * xs + xd), buffer.rows - border_size_}, 684 | color2scalar(sub_axis_color_), 1, LINE_AA); 685 | } 686 | for (int i = ceil(y_min / y_grid), e = floor(y_max / y_grid); i <= e; i++) { 687 | auto y = i * y_grid; 688 | cv::line(trans.with(sub_axis_color_), 689 | {border_size_, static_cast(y * ys + yd)}, 690 | {buffer.cols - border_size_, static_cast(y * ys + yd)}, 691 | color2scalar(sub_axis_color_), 1, LINE_AA); 692 | } 693 | if (std::abs(x_grid * xs) < 30) { 694 | x_grid *= std::ceil(30. / std::abs(x_grid * xs)); 695 | } 696 | for (int i = std::ceil(x_min / x_grid), e = floor(x_max / x_grid); i <= e; 697 | i++) { 698 | auto x = i * x_grid; 699 | std::ostringstream out; 700 | out << std::setprecision(4) << (x == 0 ? 0 : x); 701 | int baseline = 0; 702 | cv::Size size = 703 | getTextSize(out.str(), cv::FONT_HERSHEY_SIMPLEX, 0.3, 1., &baseline); 704 | cv::Point org(static_cast(x * xs + xd - size.width / 2), 705 | (buffer.rows - border_size_ + 5 + size.height)); 706 | cv::putText(trans.with(text_color_), out.str(), org, 707 | cv::FONT_HERSHEY_SIMPLEX, 0.3, color2scalar(text_color_), 1.); 708 | } 709 | if (std::abs(y_grid * ys) < 20) { 710 | y_grid *= std::ceil(20. / std::abs(y_grid * ys)); 711 | } 712 | for (int i = std::ceil(y_min / y_grid), e = floor(y_max / y_grid); i <= e; 713 | i++) { 714 | auto y = i * y_grid; 715 | std::ostringstream out; 716 | out << std::setprecision(4) << (y == 0 ? 0 : y); 717 | int baseline = 0; 718 | cv::Size size = 719 | getTextSize(out.str(), cv::FONT_HERSHEY_SIMPLEX, 0.3, 1., &baseline); 720 | cv::Point org(border_size_ - 5 - size.width, 721 | static_cast(y * ys + yd + size.height / 2)); 722 | cv::putText(trans.with(text_color_), out.str(), org, 723 | cv::FONT_HERSHEY_SIMPLEX, 0.3, color2scalar(text_color_), 1.); 724 | } 725 | 726 | // draw axis 727 | cv::line(trans.with(axis_color_), 728 | {border_size_, static_cast(y_axis * ys + yd)}, 729 | {buffer.cols - border_size_, static_cast(y_axis * ys + yd)}, 730 | color2scalar(axis_color_), 1, LINE_AA); 731 | cv::line(trans.with(axis_color_), 732 | {static_cast(x_axis * xs + xd), border_size_}, 733 | {static_cast(x_axis * xs + xd), buffer.rows - border_size_}, 734 | color2scalar(axis_color_), 1, LINE_AA); 735 | 736 | // draw plot 737 | auto index = 0; 738 | for (const auto &s : series_) { 739 | if (s.collides()) { 740 | index++; 741 | } 742 | } 743 | std::max(static_cast(series_.size()) - 1, 1); 744 | for (auto s = series_.rbegin(); s != series_.rend(); ++s) { 745 | if (s->collides()) { 746 | index--; 747 | } 748 | s->draw(&trans.with(s->color()), x_min, x_max, y_min, y_max, xs, xd, ys, yd, 749 | x_axis, y_axis, unit, 750 | static_cast(index) / static_cast(series_.size())); 751 | } 752 | 753 | // draw label names 754 | index = 0; 755 | for (const auto &s : series_) { 756 | if (!s.legend()) { 757 | continue; 758 | } 759 | auto name = s.label(); 760 | int baseline = 0; 761 | cv::Size size = 762 | getTextSize(name, cv::FONT_HERSHEY_SIMPLEX, 0.4, 1., &baseline); 763 | cv::Point org(buffer.cols - border_size_ - size.width - 17, 764 | border_size_ + 15 * index + 15); 765 | auto shadow = true; 766 | cv::putText(trans.with(background_color_), name, 767 | {org.x + (shadow ? 1 : 0), org.y + (shadow ? 1 : 0)}, 768 | cv::FONT_HERSHEY_SIMPLEX, 0.4, color2scalar(background_color_), 769 | (shadow ? 1 : 2)); 770 | cv::circle(trans.with(background_color_), 771 | {buffer.cols - border_size_ - 10 + 1, org.y - 3 + 1}, 3, 772 | color2scalar(background_color_), -1, LINE_AA); 773 | cv::putText(trans.with(text_color_), name, org, cv::FONT_HERSHEY_SIMPLEX, 774 | 0.4, color2scalar(text_color_), 1.); 775 | s.dot(&trans.with(s.color()), buffer.cols - border_size_ - 10, org.y - 3, 776 | 3); 777 | index++; 778 | } 779 | } 780 | 781 | auto Figure::drawFit(void *buffer) const -> int { 782 | auto x_min = (include_zero_x_ ? 0. : FLT_MAX); 783 | auto x_max = (include_zero_x_ ? 0. : FLT_MIN); 784 | auto y_min = (include_zero_y_ ? 0. : FLT_MAX); 785 | auto y_max = (include_zero_y_ ? 0. : FLT_MIN); 786 | auto n_max = 0; 787 | auto p_max = grid_padding_; 788 | 789 | // find value bounds 790 | for (const auto &s : series_) { 791 | s.verifyParams(); 792 | s.bounds(x_min, x_max, y_min, y_max, n_max, p_max); 793 | } 794 | 795 | if (n_max != 0) { 796 | draw(buffer, x_min, x_max, y_min, y_max, n_max, p_max); 797 | } 798 | 799 | return n_max; 800 | } 801 | 802 | auto Figure::drawFile(const std::string &filename, Size size) const -> bool { 803 | #if CV_MAJOR_VERSION >= 3 804 | cv::Mat mat(cv::Size(size.width, size.height), CV_8UC3); 805 | int n_max = drawFit(&mat); 806 | if (n_max != 0) { 807 | std::vector compression_params; 808 | compression_params.push_back(cv::IMWRITE_PNG_COMPRESSION); 809 | compression_params.push_back(9); 810 | return cv::imwrite(filename, mat, compression_params); 811 | } 812 | #endif 813 | return false; 814 | } 815 | 816 | void Figure::show(bool flush) const { 817 | Rect rect(0, 0, 0, 0); 818 | auto &buffer = *static_cast(view_.buffer(rect)); 819 | auto sub = buffer({rect.x, rect.y, rect.width, rect.height}); 820 | int n_max = drawFit(&sub); 821 | if (n_max != 0) { 822 | view_.finish(); 823 | if (flush) { 824 | view_.flush(); 825 | } 826 | } 827 | } 828 | 829 | auto figure(const std::string &name) -> Figure & { 830 | if (shared_figures_.count(name) == 0) { 831 | auto &view = Window::current().view(name); 832 | shared_figures_.insert( 833 | std::map::value_type(name, Figure(view))); 834 | } 835 | return shared_figures_.at(name); 836 | } 837 | 838 | } // namespace cvplot 839 | -------------------------------------------------------------------------------- /src/cvplot/highgui.cc: -------------------------------------------------------------------------------- 1 | #include "cvplot/highgui.h" 2 | 3 | #include 4 | 5 | namespace cvplot { 6 | 7 | auto createTrackbar(const std::string &trackbarname, const std::string &winname, 8 | int *value, int count, TrackbarCallback onChange, 9 | void *userdata) -> int { 10 | return cv::createTrackbar(trackbarname, winname, value, count, onChange, 11 | userdata); 12 | } 13 | 14 | void destroyAllWindows() { cv::destroyAllWindows(); } 15 | 16 | void destroyWindow(const std::string &view) { 17 | Window::current().view(view).hide(); 18 | } 19 | 20 | auto getMouseWheelDelta(int flags) -> int { 21 | #if CV_MAJOR_VERSION >= 3 22 | return cv::getMouseWheelDelta(flags); 23 | #else 24 | return -1; 25 | #endif 26 | } 27 | 28 | auto getTrackbarPos(const std::string &trackbarname, const std::string &winname) 29 | -> int { 30 | return cv::getTrackbarPos(trackbarname, winname); 31 | } 32 | 33 | auto getWindowImageRect(const std::string &winname) -> Rect { 34 | #if CV_MAJOR_VERSION >= 3 35 | auto rect = cv::getWindowImageRect(winname); 36 | return {rect.x, rect.y, rect.width, rect.height}; 37 | #else 38 | return {-1, -1, -1, -1}; 39 | #endif 40 | } 41 | 42 | auto getWindowProperty(const std::string &winname, int prop_id) -> double { 43 | return cv::getWindowProperty(winname, prop_id); 44 | } 45 | 46 | void imshow(const std::string &view, void *img) { 47 | Window::current().view(view).drawImage(img); 48 | Window::current().view(view).finish(); 49 | Window::current().view(view).flush(); 50 | } 51 | 52 | void moveWindow(const std::string &view, int x, int y) { 53 | Window::current().view(view).offset({x, y}); 54 | } 55 | 56 | void namedWindow(const std::string &view, int /*flags*/) { 57 | Window::current().view(view); 58 | } 59 | 60 | auto pollKey() -> int { 61 | #if CV_MAJOR_VERSION >= 4 62 | return cv::pollKey(); 63 | #else 64 | return -1; 65 | #endif 66 | } 67 | 68 | void resizeWindow(const std::string &view, int width, int height) { 69 | Window::current().view(view).size({width, height}); 70 | } 71 | 72 | void resizeWindow(const std::string &view, const Size &size) { 73 | Window::current().view(view).size({size.width, size.height}); 74 | } 75 | 76 | auto selectROI(const std::string &windowName, void *img, bool showCrosshair, 77 | bool fromCenter) -> Rect { 78 | #if CV_MAJOR_VERSION >= 3 79 | auto rect = cv::selectROI(windowName, *static_cast(img), 80 | showCrosshair, fromCenter); 81 | return {rect.x, rect.y, rect.width, rect.height}; 82 | #else 83 | return {-1, -1, -1, -1}; 84 | #endif 85 | } 86 | 87 | auto selectROI(void *img, bool showCrosshair, bool fromCenter) -> Rect { 88 | #if CV_MAJOR_VERSION >= 3 89 | auto rect = 90 | cv::selectROI(*static_cast(img), showCrosshair, fromCenter); 91 | return {rect.x, rect.y, rect.width, rect.height}; 92 | #else 93 | return {-1, -1, -1, -1}; 94 | #endif 95 | } 96 | 97 | void selectROIs(const std::string &windowName, void *img, 98 | std::vector &boundingBoxes, bool showCrosshair, 99 | bool fromCenter) { 100 | #if CV_MAJOR_VERSION >= 3 101 | std::vector boxes; 102 | boxes.reserve(boundingBoxes.size()); 103 | for (auto b : boundingBoxes) { 104 | boxes.emplace_back(b.x, b.y, b.width, b.height); 105 | } 106 | cv::selectROIs(windowName, *static_cast(img), boxes, showCrosshair, 107 | fromCenter); 108 | #endif 109 | } 110 | 111 | void setMouseCallback(const std::string &view, MouseCallback onMouse, 112 | void *userdata) { 113 | Window::current().view(view).mouse(onMouse, userdata); 114 | } 115 | 116 | void setTrackbarMax(const std::string &trackbarname, const std::string &winname, 117 | int maxval) { 118 | #if CV_MAJOR_VERSION >= 3 119 | cv::setTrackbarMax(trackbarname, winname, maxval); 120 | #endif 121 | } 122 | 123 | void setTrackbarMin(const std::string &trackbarname, const std::string &winname, 124 | int minval) { 125 | #if CV_MAJOR_VERSION >= 3 126 | cv::setTrackbarMin(trackbarname, winname, minval); 127 | #endif 128 | } 129 | 130 | void setTrackbarPos(const std::string &trackbarname, const std::string &winname, 131 | int pos) { 132 | cv::setTrackbarPos(trackbarname, winname, pos); 133 | } 134 | 135 | void setWindowProperty(const std::string &winname, int prop_id, 136 | double prop_value) { 137 | cv::setWindowProperty(winname, prop_id, prop_value); 138 | } 139 | 140 | void setWindowTitle(const std::string &view, const std::string &title) { 141 | Window::current().view(view).title(title); 142 | } 143 | 144 | auto startWindowThread() -> int { return cv::startWindowThread(); } 145 | 146 | void updateWindow(const std::string &name) { cv::updateWindow(name); } 147 | 148 | auto waitKey(int delay) -> int { return Util::key(delay); } 149 | 150 | auto waitKeyEx(int delay) -> int { return Util::key(delay); } 151 | 152 | } // namespace cvplot 153 | -------------------------------------------------------------------------------- /src/cvplot/internal.h: -------------------------------------------------------------------------------- 1 | #ifndef CVPLOT_INTERNAL_H 2 | #define CVPLOT_INTERNAL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define EXPECT_EQ(a__, b__) \ 9 | do { \ 10 | if ((a__) != (b__)) { \ 11 | std::cerr << "Incorrect " << #a__ << " (" << (a__) << "), should equal " \ 12 | << (b__) << std::endl; \ 13 | } \ 14 | } while (0) 15 | 16 | namespace cvplot { 17 | 18 | static auto color2scalar(const Color &color) -> cv::Scalar { 19 | return {(double)color.b, (double)color.g, (double)color.r}; 20 | } 21 | 22 | static auto value2snap(double value) -> double { 23 | return std::max({pow(10, floor(log10(value))), 24 | pow(10, floor(log10(value / 2))) * 2, 25 | pow(10, floor(log10(value / 5))) * 5}); 26 | } 27 | 28 | class Trans { 29 | public: 30 | Trans(void *buffer) : Trans(*(cv::Mat *)buffer) {} 31 | 32 | Trans(cv::Mat &buffer) : original_(buffer), alpha_(0), interim_(nullptr) {} 33 | 34 | Trans(cv::Mat &buffer, int alpha) : Trans(buffer) { setup(alpha); } 35 | 36 | ~Trans() { flush(); } 37 | 38 | auto get() const -> cv::Mat & { 39 | return (interim_ != nullptr ? *interim_ : original_); 40 | } 41 | 42 | void setup(int alpha) { 43 | bool transparent = (alpha != 255); 44 | if (transparent) { 45 | interim_ = new cv::Mat(); 46 | original_.copyTo(*interim_); 47 | } 48 | alpha_ = alpha; 49 | } 50 | 51 | void flush() { 52 | if (interim_) { 53 | auto weight = alpha_ / 255.; 54 | cv::addWeighted(*interim_, weight, original_, 1 - weight, 0, original_); 55 | delete interim_; 56 | interim_ = nullptr; 57 | } 58 | } 59 | 60 | auto with(int alpha) -> cv::Mat & { 61 | if (alpha != alpha_) { 62 | flush(); 63 | setup(alpha); 64 | } 65 | return get(); 66 | } 67 | 68 | auto with(const Color &color) -> cv::Mat & { return with(color.a); } 69 | 70 | protected: 71 | int alpha_; 72 | cv::Mat &original_; 73 | cv::Mat *interim_; 74 | }; 75 | 76 | } // namespace cvplot 77 | 78 | #endif // CVPLOT_INTERNAL_H 79 | -------------------------------------------------------------------------------- /src/cvplot/window.cc: -------------------------------------------------------------------------------- 1 | #include "cvplot/window.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "internal.h" 9 | 10 | namespace cvplot { 11 | 12 | namespace { 13 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 14 | std::unique_ptr shared_window_; 15 | } // namespace 16 | 17 | void mouse_callback(int event, int x, int y, int flags, void *window) { 18 | if (window != nullptr) { 19 | (static_cast(window))->onmouse(event, x, y, flags); 20 | } 21 | } 22 | 23 | // View 24 | 25 | auto View::resize(Rect rect) -> View & { 26 | rect_ = rect; 27 | window_.dirty(); 28 | return *this; 29 | } 30 | 31 | auto View::size(Size size) -> View & { 32 | rect_.width = size.width; 33 | rect_.height = size.height; 34 | window_.dirty(); 35 | return *this; 36 | } 37 | 38 | auto View::offset(Offset offset) -> View & { 39 | rect_.x = offset.x; 40 | rect_.y = offset.y; 41 | window_.dirty(); 42 | return *this; 43 | } 44 | 45 | auto View::autosize() -> View & { 46 | size({0, 0}); 47 | return *this; 48 | } 49 | 50 | auto View::title(const std::string &title) -> View & { 51 | title_ = title; 52 | window_.dirty(); 53 | return *this; 54 | } 55 | 56 | auto View::alpha(int alpha) -> View & { 57 | background_color_ = background_color_.alpha(alpha); 58 | frame_color_ = frame_color_.alpha(alpha); 59 | text_color_ = text_color_.alpha(alpha); 60 | window_.dirty(); 61 | return *this; 62 | } 63 | 64 | auto View::backgroundColor(Color color) -> View & { 65 | background_color_ = color; 66 | window_.dirty(); 67 | return *this; 68 | } 69 | 70 | auto View::frameColor(Color color) -> View & { 71 | frame_color_ = color; 72 | window_.dirty(); 73 | return *this; 74 | } 75 | 76 | auto View::textColor(Color color) -> View & { 77 | text_color_ = color; 78 | window_.dirty(); 79 | return *this; 80 | } 81 | 82 | auto View::mouse(MouseCallback callback, void *param) -> View & { 83 | mouse_callback_ = callback; 84 | mouse_param_ = (param == nullptr ? this : param); 85 | return *this; 86 | } 87 | 88 | auto View::backgroundColor() -> Color { return background_color_; } 89 | 90 | auto View::frameColor() -> Color { return frame_color_; } 91 | 92 | auto View::textColor() -> Color { return text_color_; } 93 | auto View::title() -> std::string & { return title_; } 94 | 95 | void View::drawRect(Rect rect, Color color) { 96 | Trans trans(window_.buffer()); 97 | cv::rectangle(trans.with(color), {rect_.x + rect.x, rect_.y + rect.y}, 98 | {rect_.x + rect.x + rect.width, rect_.y + rect.y + rect.height}, 99 | color2scalar(color), -1); 100 | window_.dirty(); 101 | } 102 | 103 | void View::drawText(const std::string &text, Offset offset, Color color, 104 | double height) const { 105 | auto face = cv::FONT_HERSHEY_SIMPLEX; 106 | auto scale = height / 30.; 107 | auto thickness = height / 12.; 108 | int baseline = 0; 109 | cv::Size size = 110 | getTextSize(text, face, scale, static_cast(thickness), &baseline); 111 | cv::Point org(rect_.x + offset.x, rect_.y + size.height + offset.y); 112 | Trans trans(window_.buffer()); 113 | cv::putText(trans.with(color), text, org, face, scale, color2scalar(color), 114 | static_cast(thickness)); 115 | window_.dirty(); 116 | } 117 | 118 | void View::drawTextShadow(const std::string &text, Offset offset, Color color, 119 | double height) const { 120 | int off = static_cast(height / 20); 121 | drawText(text, {offset.x + off, offset.y + off}, cvplot::Black.alpha(100), 122 | height); 123 | drawText(text, offset, color, height); 124 | } 125 | 126 | void View::drawFrame(const std::string &title) const { 127 | Trans trans(window_.buffer()); 128 | cv::rectangle(trans.with(background_color_), {rect_.x, rect_.y}, 129 | {rect_.x + rect_.width - 1, rect_.y + rect_.height - 1}, 130 | color2scalar(background_color_), 1); 131 | cv::rectangle(trans.with(frame_color_), {rect_.x + 1, rect_.y + 1}, 132 | {rect_.x + rect_.width - 2, rect_.y + rect_.height - 2}, 133 | color2scalar(frame_color_), 1); 134 | cv::rectangle(trans.with(frame_color_), {rect_.x + 2, rect_.y + 2}, 135 | {rect_.x + rect_.width - 3, rect_.y + 16}, 136 | color2scalar(frame_color_), -1); 137 | int baseline = 0; 138 | cv::Size size = getTextSize(title, cv::FONT_HERSHEY_PLAIN, 1., 1., &baseline); 139 | cv::putText(trans.with(text_color_), title, 140 | {rect_.x + 2 + (rect_.width - size.width) / 2, rect_.y + 14}, 141 | cv::FONT_HERSHEY_PLAIN, 1., color2scalar(text_color_), 1.); 142 | window_.dirty(); 143 | } 144 | 145 | void View::drawImage(const void *image, int alpha) { 146 | const auto &img = *static_cast(image); 147 | if (rect_.width == 0 && rect_.height == 0) { 148 | rect_.width = img.cols; 149 | rect_.height = img.rows; 150 | } 151 | window_.ensure(rect_); 152 | Trans trans(window_.buffer()); 153 | if (img.cols != rect_.width || img.rows != rect_.height) { 154 | cv::Mat resized; 155 | cv::resize(img, resized, {rect_.width, rect_.height}); 156 | resized.copyTo( 157 | trans.with(alpha)({rect_.x, rect_.y, rect_.width, rect_.height})); 158 | } else { 159 | img.copyTo( 160 | trans.with(alpha)({rect_.x, rect_.y, rect_.width, rect_.height})); 161 | } 162 | window_.dirty(); 163 | } 164 | 165 | void View::drawFill(Color background) { 166 | Trans trans(window_.buffer()); 167 | cv::rectangle(trans.with(background), {rect_.x, rect_.y}, 168 | {rect_.x + rect_.width - 1, rect_.y + rect_.height - 1}, 169 | color2scalar(background), -1); 170 | window_.dirty(); 171 | } 172 | 173 | auto View::buffer(Rect &rect) -> void * { 174 | window_.ensure(rect_); 175 | rect = rect_; 176 | return window_.buffer(); 177 | } 178 | 179 | void View::finish() { 180 | if (!frameless_) { 181 | drawFrame(title_); 182 | } 183 | window_.dirty(); 184 | } 185 | 186 | void View::flush() { window_.flush(); } 187 | 188 | auto View::has(Offset offset) const -> bool { 189 | return offset.x >= rect_.x && offset.y >= rect_.y && 190 | offset.x < rect_.x + rect_.width && offset.y < rect_.y + rect_.height; 191 | } 192 | 193 | void View::onmouse(int event, int x, int y, int flags) { 194 | if (mouse_callback_ != nullptr) { 195 | mouse_callback_(event, x, y, flags, mouse_param_); 196 | } 197 | } 198 | 199 | void View::hide(bool hidden) { 200 | if (hidden_ != hidden) { 201 | hidden_ = hidden; 202 | drawFill(); 203 | } 204 | } 205 | 206 | // Window 207 | 208 | Window::Window(std::string title) 209 | : offset_(0, 0), 210 | title_(std::move(title)), 211 | cursor_(-10, -10), 212 | name_("cvplot_" + std::to_string(clock())) { 213 | cv::namedWindow(name_, cv::WINDOW_AUTOSIZE); 214 | cv::setMouseCallback(name_, mouse_callback, this); 215 | } 216 | 217 | Window::~Window() { cv::setMouseCallback(name_, mouse_callback, nullptr); } 218 | 219 | auto Window::buffer() -> void * { return buffer_; } 220 | 221 | auto Window::resize(Rect rect) -> Window & { 222 | offset({rect.x, rect.y}); 223 | size({rect.width, rect.height}); 224 | return *this; 225 | } 226 | 227 | auto Window::size(Size size) -> Window & { 228 | auto &buffer = *(new cv::Mat(cv::Size(size.width, size.height), CV_8UC3, 229 | color2scalar(Gray))); 230 | if (buffer_ != nullptr) { 231 | auto ¤t = *static_cast(buffer_); 232 | if (current.cols > 0 && current.rows > 0 && size.width > 0 && 233 | size.height > 0) { 234 | cv::Rect inter(0, 0, std::min(current.cols, size.width), 235 | std::min(current.rows, size.height)); 236 | current(inter).copyTo(buffer(inter)); 237 | } 238 | // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) 239 | delete ¤t; 240 | } 241 | buffer_ = &buffer; 242 | dirty(); 243 | return *this; 244 | } 245 | 246 | auto Window::offset(Offset offset) -> Window & { 247 | offset_ = offset; 248 | cv::moveWindow(name_, offset.x, offset.y); 249 | return *this; 250 | } 251 | 252 | auto Window::title(const std::string &title) -> Window & { 253 | title_ = title; 254 | return *this; 255 | } 256 | 257 | auto Window::cursor(bool cursor) -> Window & { 258 | show_cursor_ = cursor; 259 | return *this; 260 | } 261 | 262 | auto Window::ensure(Rect rect) -> Window & { 263 | if (buffer_ == nullptr) { 264 | size({rect.x + rect.width, rect.y + rect.height}); 265 | } else { 266 | auto &b = *static_cast(buffer_); 267 | if (rect.x + rect.width > b.cols || rect.y + rect.height > b.rows) { 268 | size({std::max(b.cols, rect.x + rect.width), 269 | std::max(b.rows, rect.y + rect.height)}); 270 | } 271 | } 272 | return *this; 273 | } 274 | 275 | void Window::onmouse(int event, int x, int y, int flags) { 276 | for (auto iter = views_.rbegin(); iter != views_.rend(); ++iter) { 277 | auto &view = iter->second; 278 | if (view.has({x, y})) { 279 | view.onmouse(event, x, y, flags); 280 | break; 281 | } 282 | } 283 | cursor_ = {x, y}; 284 | if (show_cursor_) { 285 | dirty(); 286 | flush(); 287 | } 288 | } 289 | 290 | void Window::flush() { 291 | if (dirty_ && buffer_ != nullptr) { 292 | auto *b = static_cast(buffer_); 293 | if (b->cols > 0 && b->rows > 0) { 294 | cv::Mat mat; 295 | if (show_cursor_) { 296 | b->copyTo(mat); 297 | cv::line(mat, {cursor_.x - 4, cursor_.y + 1}, 298 | {cursor_.x + 6, cursor_.y + 1}, color2scalar(White), 1); 299 | cv::line(mat, {cursor_.x + 1, cursor_.y - 4}, 300 | {cursor_.x + 1, cursor_.y + 6}, color2scalar(White), 1); 301 | cv::line(mat, {cursor_.x - 5, cursor_.y}, {cursor_.x + 5, cursor_.y}, 302 | color2scalar(Black), 1); 303 | cv::line(mat, {cursor_.x, cursor_.y - 5}, {cursor_.x, cursor_.y + 5}, 304 | color2scalar(Black), 1); 305 | b = &mat; 306 | } 307 | #if CV_MAJOR_VERSION >= 3 308 | cv::setWindowTitle(name_, title_); 309 | #endif 310 | cv::imshow(name_, *b); 311 | Util::sleep(); 312 | } 313 | } 314 | dirty_ = false; 315 | } 316 | 317 | auto Window::view(const std::string &name, Size size) -> View & { 318 | if (views_.count(name) == 0) { 319 | views_.insert( 320 | std::map::value_type(name, View(*this, name, size))); 321 | } 322 | return views_.at(name); 323 | } 324 | 325 | void Window::dirty() { dirty_ = true; } 326 | 327 | void Window::hide(bool hidden) { 328 | if (hidden_ != hidden) { 329 | hidden_ = hidden; 330 | if (hidden) { 331 | cv::destroyWindow(name_); 332 | } else { 333 | dirty(); 334 | flush(); 335 | } 336 | } 337 | } 338 | 339 | auto Window::current() -> Window & { 340 | if (shared_window_ == nullptr) { 341 | shared_window_ = std::unique_ptr(new Window("")); 342 | } 343 | return *shared_window_; 344 | } 345 | 346 | auto Window::current(const std::string &title) -> Window & { 347 | shared_window_ = std::unique_ptr(new Window(title)); 348 | return *shared_window_; 349 | } 350 | 351 | void Window::current(Window &window) { 352 | shared_window_ = std::unique_ptr(&window); 353 | } 354 | 355 | // Util 356 | 357 | void Util::sleep(double seconds) { 358 | cv::waitKey(std::max(1, static_cast(seconds * 1000))); 359 | } 360 | 361 | auto Util::key(double timeout) -> int { 362 | return cv::waitKey(std::max(0, static_cast(timeout * 1000))); 363 | } 364 | 365 | auto Util::line(double timeout) -> std::string { 366 | std::stringstream stream; 367 | auto ms = (timeout > 0 ? std::max(1, static_cast(timeout * 1000)) : -1); 368 | while (ms != 0) { 369 | auto key = cv::waitKey(1); 370 | if (key == -1) { 371 | ms--; 372 | continue; 373 | } 374 | if (key == '\r' || key <= '\n') { 375 | break; 376 | } 377 | if (key == '\b' || key == 127) { 378 | auto s = stream.str(); 379 | stream = std::stringstream(); 380 | if (s.length() > 0) { 381 | stream << s.substr(0, s.length() - 1); 382 | } 383 | } else { 384 | stream << static_cast(key); 385 | } 386 | } 387 | return stream.str(); 388 | } 389 | 390 | auto window(const std::string &name) -> Window & { 391 | return Window::current(name); 392 | } 393 | 394 | } // namespace cvplot 395 | -------------------------------------------------------------------------------- /src/demo/demo.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cvplot/cvplot.h" 4 | 5 | namespace demo { 6 | 7 | auto uniform() -> double { 8 | // NOLINTNEXTLINE 9 | return std::rand() / static_cast(RAND_MAX); 10 | } 11 | 12 | void basic() { 13 | cvplot::window("mywindow"); 14 | cvplot::figure("myplot").series("myline").addValue({1., 3., 2., 5., 4.}); 15 | cvplot::figure("myplot").show(); 16 | } 17 | 18 | void highgui() { 19 | cvplot::Window::current("cvplot highgui").offset({300, 0}); 20 | 21 | const auto *name = "highgui"; 22 | cvplot::setWindowTitle(name, "line and histogram"); 23 | cvplot::moveWindow(name, 0, 0); 24 | cvplot::resizeWindow(name, 300, 300); 25 | auto &figure = cvplot::figure(name); 26 | figure.series("line") 27 | .setValue({1., 2., 3., 4., 5.}) 28 | .type(cvplot::DotLine) 29 | .color(cvplot::Blue); 30 | figure.series("histogram") 31 | .setValue({1., 2., 3., 4., 5.}) 32 | .type(cvplot::Histogram) 33 | .color(cvplot::Red); 34 | figure.show(true); 35 | } 36 | 37 | void mouse_callback(int event, int x, int y, int flags, void *param); 38 | 39 | auto transparency() -> std::unique_ptr { 40 | const std::string title = "cvplot transparency and mouse"; 41 | std::unique_ptr window_ptr(new cvplot::Window(title)); 42 | 43 | auto &window = (*window_ptr).offset({600, 0}).cursor(true); 44 | 45 | { 46 | auto &view = window.view("opaque", {300, 300}); 47 | view.mouse(mouse_callback).frameColor(cvplot::Sky); 48 | auto figure = cvplot::Figure(view); 49 | figure.series("histogram") 50 | .setValue({1., 2., 3., 4., 5.}) 51 | .type(cvplot::Histogram) 52 | .color(cvplot::Red) 53 | .legend(false); 54 | figure.show(false); 55 | } 56 | 57 | { 58 | auto &view = window.view("transparent", {300, 300}); 59 | view.mouse(mouse_callback) 60 | .frameColor(cvplot::Sky) 61 | .alpha(200) 62 | .offset({100, 100}); 63 | auto figure = cvplot::Figure(view); 64 | figure.alpha(200); 65 | figure.series("histogram") 66 | .setValue({5., 4., 3., 2., 1.}) 67 | .type(cvplot::Histogram) 68 | .color(cvplot::Blue.alpha(200)) 69 | .legend(false); 70 | figure.show(true); 71 | } 72 | 73 | return window_ptr; 74 | } 75 | 76 | void figures() { 77 | std::vector> data; 78 | std::vector values; 79 | 80 | auto window = cvplot::Window("cvplot demo").offset({50, 50}); 81 | 82 | { 83 | auto &view = window.view("math curves", {300, 300}).offset({0, 0}); 84 | auto figure = cvplot::Figure(view); 85 | values.clear(); 86 | for (auto i = 0; i <= 10; i++) { 87 | values.push_back((i - 4) * (i - 4) - 6); 88 | } 89 | figure.series("parabola") 90 | .setValue(values) 91 | .type(cvplot::DotLine) 92 | .color(cvplot::Green); 93 | values.clear(); 94 | for (auto i = 0; i <= 10; i++) { 95 | values.push_back(sin(i / 1.5) * 5); 96 | } 97 | figure.series("sine") 98 | .setValue(values) 99 | .type(cvplot::DotLine) 100 | .color(cvplot::Blue); 101 | values.clear(); 102 | values.push_back(15); 103 | figure.series("threshold") 104 | .setValue(values) 105 | .type(cvplot::Horizontal) 106 | .color(cvplot::Red); 107 | figure.show(false); 108 | } 109 | 110 | { 111 | auto &view = window.view("scatter plots", {300, 300}).offset({300, 0}); 112 | auto figure = cvplot::Figure(view); 113 | data.clear(); 114 | for (auto i = 0; i <= 100; i++) { 115 | data.emplace_back(uniform() * 10., uniform() * 10.); 116 | } 117 | figure.series("uniform").set(data).type(cvplot::Dots).color(cvplot::Orange); 118 | data.clear(); 119 | for (auto i = 0; i <= 100; i++) { 120 | data.emplace_back(exp(uniform() * 3.33) - 1, exp(uniform() * 3.33) - 1); 121 | } 122 | figure.series("exponential") 123 | .set(data) 124 | .type(cvplot::Dots) 125 | .color(cvplot::Magenta); 126 | figure.show(false); 127 | } 128 | 129 | { 130 | auto &view = window.view("auto color", {300, 300}).offset({600, 0}); 131 | auto figure = cvplot::Figure(view); 132 | figure.series("color") 133 | .dynamicColor(true) 134 | .type(cvplot::Vistogram) 135 | .legend(false); 136 | for (auto i = 0; i < 16; i++) { 137 | figure.series("color").addValue(6, cvplot::Color::index(i).hue()); 138 | } 139 | figure.show(false); 140 | } 141 | 142 | { 143 | auto &view = window.view("filled line", {300, 300}).offset({900, 0}); 144 | auto figure = cvplot::Figure(view); 145 | figure.gridSize(20); 146 | figure.series("fossil").type(cvplot::FillLine).color(cvplot::Orange); 147 | figure.series("electric") 148 | .type(cvplot::FillLine) 149 | .color(cvplot::Green.gamma(.5)); 150 | for (auto i = 0; i < 16; i++) { 151 | figure.series("fossil").addValue(10 - i + 10. * uniform()); 152 | figure.series("electric").addValue(i - 10 + 10. * uniform()); 153 | } 154 | figure.show(false); 155 | } 156 | 157 | { 158 | auto &view = 159 | window.view("multiple histograms", {300, 300}).offset({0, 300}); 160 | auto figure = cvplot::Figure(view); 161 | figure.series("1") 162 | .setValue({1., 2., 3., 4., 5.}) 163 | .type(cvplot::Histogram) 164 | .color(cvplot::Blue.alpha(201)); 165 | figure.series("2") 166 | .setValue({6., 5., 4., 3., 2., 1.}) 167 | .type(cvplot::Histogram) 168 | .color(cvplot::Green.alpha(201)); 169 | figure.series("3") 170 | .setValue({3., 1., -1., 1., 3., 7.}) 171 | .type(cvplot::Histogram) 172 | .color(cvplot::Red.alpha(201)); 173 | figure.show(false); 174 | } 175 | 176 | { 177 | auto &view = window.view("range plot", {300, 300}).offset({900, 300}); 178 | auto figure = cvplot::Figure(view); 179 | values.clear(); 180 | figure.series("apples").type(cvplot::RangeLine).color(cvplot::Orange); 181 | figure.series("pears").type(cvplot::RangeLine).color(cvplot::Sky); 182 | for (auto i = 0; i <= 10; i++) { 183 | double v = (i - 4) * (i - 4) - 6; 184 | figure.series("apples").addValue(v + 10. + 5. * uniform(), 185 | v + 5. * uniform(), 186 | v + 20. + 5. * uniform()); 187 | v = -(i - 6) * (i - 6) + 30; 188 | figure.series("pears").addValue(v + 10. + 5. * uniform(), 189 | v + 5. * uniform(), 190 | v + 20. + 5. * uniform()); 191 | } 192 | figure.show(false); 193 | } 194 | 195 | { 196 | auto &view = window.view("parametric plots", {300, 300}).offset({0, 600}); 197 | auto figure = cvplot::Figure(view); 198 | figure.square(true); 199 | data.clear(); 200 | for (auto i = 0; i <= 100; i++) { 201 | data.emplace_back(cos(i * .0628 + 4) * 2, sin(i * .0628 + 4) * 2); 202 | } 203 | figure.series("circle").add(data); 204 | data.clear(); 205 | for (auto i = 0; i <= 100; i++) { 206 | data.emplace_back(cos(i * .2513 + 1), sin(i * .0628 + 4)); 207 | } 208 | figure.series("lissajous").add(data); 209 | figure.show(false); 210 | } 211 | 212 | { 213 | auto &view = 214 | window.view("transparent circles", {300, 300}).offset({300, 600}); 215 | auto figure = cvplot::Figure(view); 216 | figure.series("purple") 217 | .type(cvplot::Circle) 218 | .color(cvplot::Purple.alpha(192)); 219 | figure.series("aqua").type(cvplot::Circle).color(cvplot::Aqua.alpha(193)); 220 | for (auto i = 0; i <= 20; i++) { 221 | figure.series("purple").add(uniform() * 10., 222 | {uniform() * 10., uniform() * 20.}); 223 | figure.series("aqua").add(uniform() * 10., 224 | {uniform() * 10., uniform() * 20.}); 225 | } 226 | figure.show(false); 227 | } 228 | 229 | { 230 | auto &view = window.view("hidden axis", {300, 300}).offset({600, 600}); 231 | auto figure = cvplot::Figure(view); 232 | figure.origin(false, false); 233 | figure.series("histogram") 234 | .setValue({4., 5., 7., 6.}) 235 | .type(cvplot::Vistogram) 236 | .color(cvplot::Blue); 237 | figure.series("min") 238 | .setValue(4.) 239 | .type(cvplot::Vertical) 240 | .color(cvplot::Pink); 241 | figure.series("max") 242 | .setValue(7.) 243 | .type(cvplot::Vertical) 244 | .color(cvplot::Purple); 245 | figure.show(false); 246 | } 247 | 248 | { 249 | auto &view = window.view("image and text", {300, 300}).offset({900, 600}); 250 | auto figure = cvplot::Figure(view); 251 | auto image = cv::imread("res/demo.jpg"); 252 | if (image.data != nullptr) { 253 | cv::copyMakeBorder(image, image, 100, 100, 0, 0, cv::BORDER_REPLICATE); 254 | view.drawImage(&image); 255 | } 256 | view.drawText("..and text", {13, 273}, cvplot::Black.alpha(127)); 257 | view.finish(); 258 | } 259 | 260 | { 261 | auto &view = window.view("dynamic plotting", {600, 300}).offset({300, 300}); 262 | auto figure = cvplot::Figure(view); 263 | figure.square(true); 264 | figure.origin(false, false); 265 | auto x = 0.; 266 | auto y = 0.; 267 | auto dx = 1.; 268 | auto dy = 0.; 269 | auto f = 0.; 270 | auto df = 0.; 271 | figure.series("random").dynamicColor(true).legend(false); 272 | clock_t time = 0; 273 | for (int i = 0; i < 1000; i++) { 274 | auto fps = CLOCKS_PER_SEC / static_cast(clock() - time); 275 | time = clock(); 276 | auto l = sqrt((dx * dx + dy * dy) * (f * f + 1)) * 10; 277 | dx = (dx + f * dy) / l; 278 | dy = (dy - f * dx) / l; 279 | f = (f + df) * 0.8; 280 | df = (df + uniform() * .11 - .05) * 0.8; 281 | figure.series("random").add(x += dx, {y += dy, i / 10.}); 282 | figure.show(false); 283 | auto string = std::to_string(fps).substr(0, 4) + " fps " + 284 | std::to_string(i / 10.).substr(0, 4) + "%"; 285 | view.drawText(string, {480, 277}, cvplot::Gray); 286 | view.finish(); 287 | view.flush(); 288 | } 289 | view.drawTextShadow("Press any key to exit", {70, 130}, cvplot::Red, 40.); 290 | view.flush(); 291 | } 292 | } 293 | 294 | void mouse_callback(int event, int x, int y, int flags, void *param) { 295 | auto &view = *static_cast(param); 296 | std::ostringstream stream; 297 | if (event == cv::EVENT_MOUSEMOVE) { 298 | stream << "mousemove"; 299 | } 300 | if (event == cv::EVENT_LBUTTONDOWN) { 301 | stream << "lbuttondown"; 302 | } 303 | if (event == cv::EVENT_RBUTTONDOWN) { 304 | stream << "rbuttondown"; 305 | } 306 | if (event == cv::EVENT_MBUTTONDOWN) { 307 | stream << "mbuttondown"; 308 | } 309 | if (event == cv::EVENT_LBUTTONUP) { 310 | stream << "lbuttonup"; 311 | } 312 | if (event == cv::EVENT_RBUTTONUP) { 313 | stream << "rbuttonup"; 314 | } 315 | if (event == cv::EVENT_MBUTTONUP) { 316 | stream << "mbuttonup"; 317 | } 318 | if (event == cv::EVENT_LBUTTONDBLCLK) { 319 | stream << "lbuttondblclk"; 320 | } 321 | if (event == cv::EVENT_RBUTTONDBLCLK) { 322 | stream << "rbuttondblclk"; 323 | } 324 | if (event == cv::EVENT_MBUTTONDBLCLK) { 325 | stream << "mbuttondblclk"; 326 | } 327 | #if CV_MAJOR_VERSION >= 3 328 | if (event == cv::EVENT_MOUSEWHEEL) { 329 | stream << "mousewheel"; 330 | } 331 | if (event == cv::EVENT_MOUSEHWHEEL) { 332 | stream << "mousehwheel"; 333 | } 334 | #endif 335 | if ((flags & cv::EVENT_FLAG_LBUTTON) != 0) { 336 | stream << " lbutton"; 337 | } 338 | if ((flags & cv::EVENT_FLAG_RBUTTON) != 0) { 339 | stream << " rbutton"; 340 | } 341 | if ((flags & cv::EVENT_FLAG_MBUTTON) != 0) { 342 | stream << " mbutton"; 343 | } 344 | if ((flags & cv::EVENT_FLAG_CTRLKEY) != 0) { 345 | stream << " ctrlkey"; 346 | } 347 | if ((flags & cv::EVENT_FLAG_SHIFTKEY) != 0) { 348 | stream << " shiftkey"; 349 | } 350 | if ((flags & cv::EVENT_FLAG_ALTKEY) != 0) { 351 | stream << " altkey"; 352 | } 353 | stream << " " << x << "," << y; 354 | view.drawRect({10, 24, 280, 12}, cvplot::Light); 355 | view.drawText(stream.str(), {10, 25}, cvplot::Black); 356 | } 357 | 358 | } // namespace demo 359 | 360 | auto main(int /*argc*/, char ** /*argv*/) -> int { 361 | demo::basic(); 362 | demo::highgui(); 363 | auto window = demo::transparency(); 364 | demo::figures(); 365 | std::cout << "Demo completed. Ctrl+C to exit." << std::endl; 366 | cvplot::waitKey(); 367 | window.release(); 368 | return 0; 369 | } 370 | -------------------------------------------------------------------------------- /test/cvplot/color_test.cc: -------------------------------------------------------------------------------- 1 | #include "cvplot/color.h" 2 | 3 | #include 4 | 5 | namespace cvplot { 6 | 7 | TEST(ColorTest, Init) { 8 | Color c; 9 | EXPECT_EQ(c.r, 0); 10 | EXPECT_EQ(c.g, 0); 11 | EXPECT_EQ(c.b, 0); 12 | EXPECT_EQ(c.a, 255); 13 | } 14 | 15 | TEST(ColorTest, Init1) { 16 | uint8_t v[3] = {1, 2, 3}; 17 | Color c(v, 4); 18 | EXPECT_EQ(c.r, 1); 19 | EXPECT_EQ(c.g, 2); 20 | EXPECT_EQ(c.b, 3); 21 | EXPECT_EQ(c.a, 4); 22 | Color d(v); 23 | EXPECT_EQ(d.a, 255); 24 | } 25 | 26 | TEST(ColorTest, Init2) { 27 | Color c(1, 2, 3, 4); 28 | EXPECT_EQ(c.r, 1); 29 | EXPECT_EQ(c.g, 2); 30 | EXPECT_EQ(c.b, 3); 31 | EXPECT_EQ(c.a, 4); 32 | Color d(1, 2, 3); 33 | EXPECT_EQ(d.a, 255); 34 | } 35 | 36 | TEST(ColorTest, Alpha) { 37 | Color c(1, 2, 3, 4); 38 | EXPECT_EQ(c.alpha(5).a, 5); 39 | } 40 | 41 | TEST(ColorTest, Gray) { 42 | Color c = Color::gray(3); 43 | EXPECT_EQ(c.r, 3); 44 | EXPECT_EQ(c.g, 3); 45 | EXPECT_EQ(c.b, 3); 46 | EXPECT_EQ(c.a, 255); 47 | } 48 | 49 | TEST(ColorTest, Hue) { 50 | Color c = Color::hue(3); 51 | EXPECT_EQ(c.r, 0); 52 | EXPECT_EQ(c.g, 255); 53 | EXPECT_EQ(c.b, 255); 54 | EXPECT_EQ(c.a, 255); 55 | } 56 | 57 | TEST(ColorTest, Cos) { 58 | Color c = Color::cos(3); 59 | EXPECT_EQ(c.r, 0); 60 | EXPECT_EQ(c.g, 191); 61 | EXPECT_EQ(c.b, 191); 62 | EXPECT_EQ(c.a, 255); 63 | } 64 | 65 | TEST(ColorTest, Index) { 66 | Color c = Color::index(3); 67 | EXPECT_EQ(c.r, 37); 68 | EXPECT_EQ(c.g, 94); 69 | EXPECT_EQ(c.b, 251); 70 | EXPECT_EQ(c.a, 255); 71 | } 72 | 73 | TEST(ColorTest, Hash) { 74 | Color c = Color::hash("test"); 75 | EXPECT_EQ(c.r, 94); 76 | EXPECT_EQ(c.g, 37); 77 | EXPECT_EQ(c.b, 251); 78 | EXPECT_EQ(c.a, 255); 79 | } 80 | 81 | TEST(ColorTest, Red) { 82 | EXPECT_EQ(Red.r, 255); 83 | EXPECT_EQ(Red.g, 0); 84 | EXPECT_EQ(Red.b, 0); 85 | EXPECT_EQ(Red.a, 255); 86 | } 87 | 88 | TEST(ColorTest, Green) { 89 | EXPECT_EQ(Green.r, 0); 90 | EXPECT_EQ(Green.g, 255); 91 | EXPECT_EQ(Green.b, 0); 92 | EXPECT_EQ(Green.a, 255); 93 | } 94 | 95 | TEST(ColorTest, Blue) { 96 | EXPECT_EQ(Blue.r, 0); 97 | EXPECT_EQ(Blue.g, 0); 98 | EXPECT_EQ(Blue.b, 255); 99 | EXPECT_EQ(Blue.a, 255); 100 | } 101 | 102 | } // namespace cvplot 103 | 104 | auto main(int argc, char **argv) -> int { 105 | ::testing::InitGoogleTest(&argc, argv); 106 | return RUN_ALL_TESTS(); 107 | } 108 | -------------------------------------------------------------------------------- /test/cvplot/figure_test.cc: -------------------------------------------------------------------------------- 1 | #include "cvplot/figure.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace cvplot { 8 | 9 | TEST(FigureTest, Init) { 10 | Window w; 11 | View v(w); 12 | Figure f(v); 13 | } 14 | 15 | TEST(FigureTest, File) { 16 | const auto *filename = "test/figure.png"; 17 | auto f = figure("test-figure"); 18 | f.series("test-series").addValue({1., 3., 2., 5., 4.}); 19 | auto result = f.drawFile(filename, {500, 500}); 20 | EXPECT_EQ(result, true); 21 | EXPECT_EQ(remove(filename), 0); 22 | } 23 | 24 | } // namespace cvplot 25 | 26 | auto main(int argc, char **argv) -> int { 27 | ::testing::InitGoogleTest(&argc, argv); 28 | return RUN_ALL_TESTS(); 29 | } 30 | -------------------------------------------------------------------------------- /test/cvplot/window_test.cc: -------------------------------------------------------------------------------- 1 | #include "cvplot/window.h" 2 | 3 | #include 4 | 5 | namespace cvplot { 6 | 7 | TEST(WindowTest, Init) { Window w; } 8 | 9 | } // namespace cvplot 10 | 11 | auto main(int argc, char **argv) -> int { 12 | ::testing::InitGoogleTest(&argc, argv); 13 | return RUN_ALL_TESTS(); 14 | } 15 | --------------------------------------------------------------------------------