#pragma once #include #include #include #include #include #include // https://github.com/HowardHinnant/date/blob/master/include/date/date.h #include "date.h" using namespace std; using namespace std::chrono; namespace vp_utils { // string format in C++17 template inline string string_format(const string& format, Args ... args){ size_t size = 1 + snprintf(nullptr, 0, format.c_str(), args ...); // unique_ptr buf(new char[size]); char bytes[size]; snprintf(bytes, size, format.c_str(), args ...); return string(bytes); } // get optimal font scale depend on screen width and height inline double get_optimal_font_scale(string text, int screen_width, int screen_height, cv::Size & text_size, int fontface = cv::FONT_HERSHEY_PLAIN, int thickness = 1) { int baseline = 0; for (int i = 20; i > 0; i--) { auto size = cv::getTextSize(text, fontface, i / 20.0, thickness, &baseline); if (size.width < screen_width && size.height < screen_height) { text_size = size; return i / 20.0; } } return 1; } // draw rounded rectangle, fill the backgound. inline void draw_rounded_rectangle(cv::Mat& src, cv::Point top_left, cv::Point bottom_right, int corner_radius, cv::Scalar color = cv::Scalar(), int thickness = 1, cv::Scalar fill_color = cv::Scalar(255, 255, 255), int line_type = cv::LINE_4) { /* corners: * p1 - p2 * | | * p4 - p3 */ if (corner_radius < 1) { corner_radius = 1; } cv::Point p1 = top_left; cv::Point p2 = cv::Point (bottom_right.x, top_left.y); cv::Point p3 = bottom_right; cv::Point p4 = cv::Point (top_left.x, bottom_right.y); // fill rounded rectangle if (thickness < 0) { cv::rectangle(src, cv::Rect(cv::Point (p1.x + corner_radius, p1.y), cv::Point (p3.x - corner_radius, p3.y)), fill_color, thickness); cv::rectangle(src, cv::Rect(cv::Point (p1.x, p1.y + corner_radius), cv::Point (p3.x, p3.y - corner_radius)), fill_color, thickness); cv::ellipse( src, p1 + cv::Point(corner_radius, corner_radius), cv::Size( corner_radius, corner_radius ), 180.0, 0, 90, fill_color, thickness, line_type ); cv::ellipse( src, p2 + cv::Point(-corner_radius, corner_radius), cv::Size( corner_radius, corner_radius ), 270.0, 0, 90, fill_color, thickness, line_type ); cv::ellipse( src, p3 + cv::Point(-corner_radius, -corner_radius), cv::Size( corner_radius, corner_radius ), 0.0, 0, 90, fill_color, thickness, line_type ); cv::ellipse( src, p4 + cv::Point(corner_radius, -corner_radius), cv::Size( corner_radius, corner_radius ), 90.0, 0, 90, fill_color, thickness, line_type ); thickness = 1; } // draw straight lines cv::line(src, cv::Point (p1.x + corner_radius, p1.y), cv::Point (p2.x - corner_radius, p2.y), color, thickness, line_type); cv::line(src, cv::Point (p2.x, p2.y + corner_radius), cv::Point (p3.x, p3.y - corner_radius), color, thickness, line_type); cv::line(src, cv::Point (p4.x + corner_radius, p4.y), cv::Point (p3.x - corner_radius, p3.y), color, thickness, line_type); cv::line(src, cv::Point (p1.x, p1.y + corner_radius), cv::Point (p4.x, p4.y - corner_radius), color, thickness, line_type); // draw arcs cv::ellipse( src, p1 + cv::Point(corner_radius, corner_radius), cv::Size( corner_radius, corner_radius ), 180.0, 0, 90, color, thickness, line_type ); cv::ellipse( src, p2 + cv::Point(-corner_radius, corner_radius), cv::Size( corner_radius, corner_radius ), 270.0, 0, 90, color, thickness, line_type ); cv::ellipse( src, p3 + cv::Point(-corner_radius, -corner_radius), cv::Size( corner_radius, corner_radius ), 0.0, 0, 90, color, thickness, line_type ); cv::ellipse( src, p4 + cv::Point(corner_radius, -corner_radius), cv::Size( corner_radius, corner_radius ), 90.0, 0, 90, color, thickness, line_type ); } // put text at the center of specific rect on canvas inline void put_text_at_center_of_rect(cv::Mat& canvas, string text, cv::Rect rect, bool fill = false, int font_face = cv::FONT_HERSHEY_PLAIN, int thickness = 1, cv::Scalar color = cv::Scalar(), cv::Scalar border_color = cv::Scalar(201, 201, 205), cv::Scalar fill_color = cv::Scalar(201, 201, 205), int padding = 1) { cv::Size text_size; auto font_scale = get_optimal_font_scale(text, rect.width - padding * 2, rect.height - padding * 2, text_size, font_face, thickness); if (fill) { draw_rounded_rectangle(canvas, cv::Point(rect.x, rect.y), cv::Point(rect.x + rect.width, rect.y + rect.height), 3, border_color, -1, fill_color); } cv::putText(canvas, text, cv::Point(rect.x + (rect.width - text_size.width) / 2, rect.y + rect.height - (rect.height - text_size.height) / 2), font_face, font_scale, color); } // use std::string::end_with(...) in standard library directly for C++20 inline bool ends_with(std::string const & value, std::string const & ending) { if (ending.size() > value.size()) return false; return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); } // round double data using specific precision, return string. inline std::string round_any(double input, int precision) { input = input * std::pow(10, precision) + 0.5; auto output = std::to_string(std::floor(input) / std::pow(10, precision)); output.erase(std::find_if(output.rbegin(), output.rend(), [](unsigned char ch) { return ch != '0'; }).base(), output.end()); // remove the last point `.` if possible if (ends_with(output, ".")) { output.pop_back(); } return output; } inline std::vector string_split(const std::string& s, char delimiter) { std::vector tokens; std::string token; std::istringstream tokenStream(s); while (std::getline(tokenStream, token, delimiter)) { tokens.push_back(token); } return tokens; } // split time point to 7 parts, include year, month, day ... in order inline void time_split(system_clock::time_point tp, std::vector& time_parts, int time_zone = 8) { // right time zone tp = tp + std::chrono::hours{time_zone}; auto dp = date::floor(tp); auto ymd = date::year_month_day{dp}; auto time = date::make_time(std::chrono::duration_cast(tp - dp)); time_parts.clear(); // push year/month/day time_parts.push_back(static_cast(ymd.year())); time_parts.push_back(static_cast(ymd.month())); time_parts.push_back(static_cast(ymd.day())); // push hour/minute/second/milisecond time_parts.push_back(time.hours().count()); time_parts.push_back(time.minutes().count()); time_parts.push_back(time.seconds().count()); time_parts.push_back(time.subseconds().count()); } // set width and fill, like ostream.fill() and ostream.width() inline std::string set_width_and_fill(std::string str, int width, char fill = '0', bool fill_left = true) { while (str.size() < width) { if (fill_left) { str = fill + str; } else { str = str + fill; } } return str; } // format time point to string, chars in <> are keywords. // in: -- ::. // out: 2022-10-08 13:53:07.230 // or // in: [// ::.] // out: [08/10/2022 13:53:07.230] inline std::string time_format(system_clock::time_point tp, std::string template_str = "-- ::.", int time_zone = 8) { // right time zone tp = tp + std::chrono::hours{time_zone}; // 7 parts std::vector time_parts; time_split(tp, time_parts, 0); auto time_str = std::regex_replace(template_str, std::regex(""), std::to_string(time_parts[0])); time_str = std::regex_replace(time_str, std::regex(""), set_width_and_fill(std::to_string(time_parts[1]), 2)); time_str = std::regex_replace(time_str, std::regex(""), set_width_and_fill(std::to_string(time_parts[2]), 2)); time_str = std::regex_replace(time_str, std::regex(""), set_width_and_fill(std::to_string(time_parts[3]), 2)); time_str = std::regex_replace(time_str, std::regex(""), set_width_and_fill(std::to_string(time_parts[4]), 2)); time_str = std::regex_replace(time_str, std::regex(""), set_width_and_fill(std::to_string(time_parts[5]), 2)); time_str = std::regex_replace(time_str, std::regex(""), set_width_and_fill(std::to_string(time_parts[6]), 3)); return time_str; } }