diff --git a/resources/flush/flush_data.txt b/resources/flush/flush_data.txt index e7f9f638e..c0dbe0227 100644 --- a/resources/flush/flush_data.txt +++ b/resources/flush/flush_data.txt @@ -1,3 +1,5 @@ +colors +#000000 #C12E1F #00AE42 #545454 #D1D3D5 #5B6579 #F4EE2A #9D432C #5E43B7 #0A2989 #FF6A13 #8E9089 src dst flush #000000 #545454 236 #000000 #F4EE2A 386 diff --git a/src/libslic3r/FlushVolCalc.cpp b/src/libslic3r/FlushVolCalc.cpp index d8862ae23..17b307ce4 100644 --- a/src/libslic3r/FlushVolCalc.cpp +++ b/src/libslic3r/FlushVolCalc.cpp @@ -49,7 +49,9 @@ int FlushVolCalculator::calc_flush_vol_rgb(unsigned char src_r, unsigned char sr { auto& pd = FlushVolPredictor::get_instance(); float ret_flush_volume = 0; - bool success = pd.predict(src_r, src_g, src_b, dst_r, dst_g, dst_b, ret_flush_volume); + FlushPredict::RGBColor src(src_r, src_g, src_b); + FlushPredict::RGBColor dst(dst_r, dst_g, dst_b); + bool success = pd.predict(src, dst, ret_flush_volume); // if we could find the color pair from dataset, we need to recalculate if (!success) { float src_r_f, src_g_f, src_b_f, dst_r_f, dst_g_f, dst_b_f; diff --git a/src/libslic3r/FlushVolPredictor.cpp b/src/libslic3r/FlushVolPredictor.cpp index d79fefb07..d9805541b 100644 --- a/src/libslic3r/FlushVolPredictor.cpp +++ b/src/libslic3r/FlushVolPredictor.cpp @@ -2,61 +2,245 @@ #include "Utils.hpp" #include #include +#include +#include - -static bool rgb_hex_to_dec(const std::string& hexstr, unsigned char& r, unsigned char& g, unsigned char& b) +namespace FlushPredict { - if (hexstr.empty() || hexstr.length() != 7 || hexstr[0] != '#') - { - assert(false); - r = 0, g = 0, b = 0; - return false; + static double rad_to_deg(double rad) { + return 180.0 / M_PI * rad; + } + + static double deg_to_rad(double deg) { + return deg * M_PI / 180.0; + } + + LABColor RGB2LAB(const RGBColor& color) { + using XYZColor = std::tuple; + auto gamma = [](double x) { + if (x > 0.04045) + return pow((x + 0.055) / 1.055, 2.4); + else + return x / 12.92; + }; + auto RGB2XYZ = [gamma](const RGBColor& color)->XYZColor { + double R = gamma(static_cast(color.r) / 255.0) * 100; + double G = gamma(static_cast(color.g) / 255.0) * 100; + double B = gamma(static_cast(color.b) / 255.0) * 100; + + double x = 0.412453 * R + 0.357580 * G + 0.180423 * B; + double y = 0.212671 * R + 0.715160 * G + 0.072169 * B; + double z = 0.019334 * R + 0.119193 * G + 0.950227 * B; + return { x,y,z }; + }; + + static const double XN = 95.0489; + static const double YN = 100; + static const double ZN = 108.8840; + + auto f = [](double t) { + static const double threshold = 0.008856f; + if (t > threshold) + return pow(t, 1.0 / 3.0); + else + return 7.787 * t + 0.137931; + }; + + auto xyz_color = RGB2XYZ(color); + double x = std::get<0>(xyz_color); + double y = std::get<1>(xyz_color); + double z = std::get<2>(xyz_color); + double xn = f(x / XN); + double yn = f(y / YN); + double zn = f(z / ZN); + + double L = 116.0 * yn - 16.0; + double A = 500.0 * (xn - yn); + double B = 200.0 * (yn - zn); + return LABColor(L, A, B); + } + + float calc_color_distance(const LABColor& lab1, const LABColor& lab2) + { + static const double pow_25_to_7 = pow(25, 7); + + const double C1 = sqrt(lab1.a * lab1.a + lab1.b * lab1.b); + const double C2 = sqrt(lab2.a * lab2.a + lab2.b * lab2.b); + const double CMean = (C1 + C2) / 2.0; + const double pow_CMean_to_7 = pow(CMean, 7); + const double G = 0.5 * (1 - sqrt(pow_CMean_to_7 / (pow_CMean_to_7 + pow_25_to_7))); + + const double p_l1 = lab1.l; + const double p_l2 = lab2.l; + const double p_a1 = (1. + G) * lab1.a; + const double p_a2 = (1. + G) * lab2.a; + const double p_b1 = lab1.b; + const double p_b2 = lab2.b; + const double p_c1 = sqrt(p_a1 * p_a1 + p_b1 * p_b1); + const double p_c2 = sqrt(p_a2 * p_a2 + p_b2 * p_b2); + double p_h1; + if (p_a1 == 0 && p_b1 == 0) + p_h1 = 0; + else { + p_h1 = atan2(p_b1, p_a1); + if (p_h1 < 0) + p_h1 += M_PI * 2; + } + double p_h2; + if (p_a2 == 0 && p_b2 == 0) + p_h2 = 0; + else { + p_h2 = atan2(p_b2, p_a2); + if (p_h2 < 0) + p_h2 += M_PI * 2; + } + + const double delta_L = p_l2 - p_l1; + const double delta_C = p_c2 - p_c1; + + double delta_H; + const double p_c_multi = p_c1 * p_c2; + if (p_c_multi == 0) + delta_H = 0; + else { + delta_H = p_h2 - p_h1; + if (delta_H < -M_PI) + delta_H += 2 * M_PI; + else if (delta_H > M_PI) + delta_H -= 2 * M_PI; + delta_H = 2 * sqrt(p_c_multi) * sin(delta_H / 2.); + } + + + double p_L_mean = (p_l1 + p_l2) / 2.0; + double p_C_mean = (p_c1 + p_c2) / 2.0; + + double p_H_mean, p_H_sum = p_h1 + p_h2; + if (p_c1 * p_c2 == 0) { + p_H_mean = p_H_sum; + } + else { + if (fabs(p_h1 - p_h2) <= M_PI) + p_H_mean = p_H_sum / 2; + else { + if (p_H_sum < 2 * M_PI) + p_H_mean = (p_H_sum + 2 * M_PI) / 2.0; + else + p_H_mean = (p_H_sum - 2 * M_PI) / 2.0; + } + } + + const double T = 1 - 0.17 * cos(p_H_mean - deg_to_rad(30)) + 0.24 * cos(2 * p_H_mean) + 0.32 * cos(3 * p_H_mean + deg_to_rad(6)) - 0.2 * cos(4 * p_H_mean - deg_to_rad(63)); + const double dtheta = deg_to_rad(30) * exp(-pow((p_H_mean - deg_to_rad(275)) / deg_to_rad(25), 2)); + + const double pow_p_cmean_to_7 = pow(p_C_mean, 7); + const double R_C = 2 * sqrt(pow_p_cmean_to_7 / (pow_p_cmean_to_7 + pow_25_to_7)); + + const double pow_p_lmean_to_2 = pow(p_L_mean - 50, 2); + const double S_L = 1 + (0.015 * pow_p_lmean_to_2) / sqrt(20 + pow_p_lmean_to_2); + const double S_C = 1 + 0.045 * p_C_mean; + const double S_H = 1 + 0.015 * p_C_mean * T; + const double R_T = -sin(2 * dtheta) * R_C; + + const double K_L = 1.0, K_C = 1.0, K_H = 1.0; + + double de = sqrt( + pow(delta_L / (K_L * S_L), 2) + pow(delta_C / (K_C * S_C), 2) + pow(delta_H / (K_H * S_H), 2) + (R_T * (delta_C / (K_C * S_C)) * (delta_H / (K_H * S_H))) + ); + return de; + } + + float calc_color_distance(const RGBColor& color1, const RGBColor& color2) { + LABColor lab1 = RGB2LAB(color1); + LABColor lab2 = RGB2LAB(color2); + return calc_color_distance(lab1, lab2); + } + + bool is_similar_color(const RGBColor& from, const RGBColor& to, float distance_threshold) + { + float color_distance = calc_color_distance(from, to); + if (color_distance > distance_threshold) + return false; + return true; } - auto hexToByte = [](const std::string& hex)->int - { - unsigned int byte; - std::istringstream(hex) >> std::hex >> byte; - return byte; - }; - r = hexToByte(hexstr.substr(1, 2)); - g = hexToByte(hexstr.substr(3, 2)); - b = hexToByte(hexstr.substr(5, 2)); - return true; } +uint64_t FlushVolPredictor::generate_hash_key(const RGB& from, const RGB& to) +{ + uint64_t key = 0; + key |= (static_cast(from.r) << 40); + key |= (static_cast(from.g) << 32); + key |= (static_cast(from.b) << 24); + key |= (static_cast(to.r) << 16); + key |= (static_cast(to.g) << 8); + key |= static_cast(to.b); + return key; +} + FlushVolPredictor::FlushVolPredictor(const std::string& data_file) { + auto rgb_hex_to_dec = [](const std::string& hexstr, FlushPredict::RGBColor& color)->bool + { + if (hexstr.empty() || hexstr.length() != 7 || hexstr[0] != '#') + { + assert(false); + color.r = 0, color.g = 0, color.b = 0; + return false; + } + + auto hexToByte = [](const std::string& hex)->int + { + unsigned int byte; + std::istringstream(hex) >> std::hex >> byte; + return byte; + }; + color.r = hexToByte(hexstr.substr(1, 2)); + color.g = hexToByte(hexstr.substr(3, 2)); + color.b = hexToByte(hexstr.substr(5, 2)); + return true; + }; + std::ifstream in(data_file); if (!in.is_open()) { m_valid = false; return; } std::string line; - getline(in, line); // skip the first line + std::getline(in, line); //skip color description line + std::getline(in, line); + // read and save color lists + { + std::istringstream in(line); + std::string color; + while (in >> color) { + RGB c; + if (!rgb_hex_to_dec(color, c)) { + m_valid = false; + return; + } + m_colors.emplace_back(c); + } + } + std::getline(in, line); // skip colume name line while (std::getline(in, line)) { std::istringstream iss(line); - std::string rgb_src, rgb_dst; + std::string rgb_from, rgb_to; float value; - if (iss >> rgb_src >> rgb_dst >> value) { - unsigned char r_src = 0, g_src = 0, b_src = 0; - unsigned char r_dst = 0, g_dst = 0, b_dst = 0; - if (!rgb_hex_to_dec(rgb_src, r_src, g_src, b_src)) { + if (iss >> rgb_from >> rgb_to >> value) { + RGB from,to; + // transfer hex str to rgb format + if (!rgb_hex_to_dec(rgb_from, from)) { m_valid = false; return; } - if (!rgb_hex_to_dec(rgb_dst, r_dst, g_dst, b_dst)) { + if (!rgb_hex_to_dec(rgb_to, to)) { m_valid = false; return; } - uint64_t key = 0; - key |= (static_cast(r_src) << 40); - key |= (static_cast(g_src) << 32); - key |= (static_cast(b_src) << 24); - key |= (static_cast(r_dst) << 16); - key |= (static_cast(g_dst) << 8); - key |= static_cast(b_dst); + // generate hash key for two rgb color + uint64_t key = generate_hash_key(from,to); m_flush_map.emplace(key, value); } else { @@ -67,19 +251,31 @@ FlushVolPredictor::FlushVolPredictor(const std::string& data_file) m_valid = true; } -bool FlushVolPredictor::predict(const unsigned char src_r, const unsigned char src_g, const unsigned char src_b, const unsigned char dst_r, const unsigned char dst_g, const unsigned char dst_b, float& flush) +bool FlushVolPredictor::predict(const RGB& from, const RGB& to, float& flush) { if (!m_valid) return false; - uint64_t key = 0; - key |= (static_cast(src_r) << 40); - key |= (static_cast(src_g) << 32); - key |= (static_cast(src_b) << 24); - key |= (static_cast(dst_r) << 16); - key |= (static_cast(dst_g) << 8); - key |= static_cast(dst_b); + // find similar colors in color list + std::optional similar_from, similar_to; + for (auto& color : m_colors) { + if (FlushPredict::is_similar_color(color, from)) { + similar_from = color; + break; + } + } + for (auto& color : m_colors) { + if (FlushPredict::is_similar_color(color, to)) { + similar_to = color; + break; + } + } + // `from` and `to` should have similar colors in list + if (!similar_from || !similar_to) + return false; + + uint64_t key = generate_hash_key(*similar_from,*similar_to); auto iter = m_flush_map.find(key); if (iter == m_flush_map.end()) return false; diff --git a/src/libslic3r/FlushVolPredictor.hpp b/src/libslic3r/FlushVolPredictor.hpp index 44a0d7caf..0054c70dd 100644 --- a/src/libslic3r/FlushVolPredictor.hpp +++ b/src/libslic3r/FlushVolPredictor.hpp @@ -3,20 +3,56 @@ #include +namespace FlushPredict +{ + struct RGBColor + { + unsigned char r{ 0 }; + unsigned char g{ 0 }; + unsigned char b{ 0 }; + RGBColor(unsigned char r_,unsigned char g_,unsigned char b_) :r(r_),g(g_),b(b_){} + RGBColor() = default; + }; + + struct LABColor + { + double l{ 0 }; + double a{ 0 }; + double b{ 0 }; + LABColor() = default; + LABColor(double l_,double a_,double b_):l(l_),a(a_),b(b_){} + }; + // transfer colour in RGB space to LAB space + LABColor RGB2LAB(const RGBColor& color); + // calculate DeltaE2000 + float calc_color_distance(const LABColor& lab1, const LABColor& lab2); + float calc_color_distance(const RGBColor& rgb1, const RGBColor& rgb2); + // check if DeltaE is within the threshold. We consider colors within the threshold to be the same + bool is_similar_color(const RGBColor& from, const RGBColor& to, float distance_threshold = 5.0); + +} + + +// Singleton pattern class FlushVolPredictor { + using RGB = FlushPredict::RGBColor; public: - bool predict(const unsigned char src_r, const unsigned char src_g, const unsigned char src_b, - const unsigned char dst_r, const unsigned char dst_g, const unsigned char dst_b, float& flush); + bool predict(const RGB& from,const RGB& to , float& flush); static FlushVolPredictor& get_instance(); private: FlushVolPredictor(const std::string& data_file); FlushVolPredictor(const FlushVolPredictor&) = delete; FlushVolPredictor& operator=(const FlushVolPredictor&) = delete; ~FlushVolPredictor() = default; + + uint64_t generate_hash_key(const RGB& from, const RGB& to); private: std::unordered_map m_flush_map; + std::vector m_colors; bool m_valid; }; + + #endif \ No newline at end of file