#include "libslic3r.h" #include "Color.hpp" #include static const float INV_255 = 1.0f / 255.0f; namespace Slic3r { // Conversion from RGB to HSV color space // The input RGB values are in the range [0, 1] // The output HSV values are in the ranges h = [0, 360], and s, v = [0, 1] static void RGBtoHSV(float r, float g, float b, float& h, float& s, float& v) { assert(0.0f <= r && r <= 1.0f); assert(0.0f <= g && g <= 1.0f); assert(0.0f <= b && b <= 1.0f); const float max_comp = std::max(std::max(r, g), b); const float min_comp = std::min(std::min(r, g), b); const float delta = max_comp - min_comp; if (delta > 0.0f) { if (max_comp == r) h = 60.0f * (std::fmod(((g - b) / delta), 6.0f)); else if (max_comp == g) h = 60.0f * (((b - r) / delta) + 2.0f); else if (max_comp == b) h = 60.0f * (((r - g) / delta) + 4.0f); s = (max_comp > 0.0f) ? delta / max_comp : 0.0f; } else { h = 0.0f; s = 0.0f; } v = max_comp; while (h < 0.0f) { h += 360.0f; } while (h > 360.0f) { h -= 360.0f; } assert(0.0f <= s && s <= 1.0f); assert(0.0f <= v && v <= 1.0f); assert(0.0f <= h && h <= 360.0f); } // Conversion from HSV to RGB color space // The input HSV values are in the ranges h = [0, 360], and s, v = [0, 1] // The output RGB values are in the range [0, 1] static void HSVtoRGB(float h, float s, float v, float& r, float& g, float& b) { assert(0.0f <= s && s <= 1.0f); assert(0.0f <= v && v <= 1.0f); assert(0.0f <= h && h <= 360.0f); const float chroma = v * s; const float h_prime = std::fmod(h / 60.0f, 6.0f); const float x = chroma * (1.0f - std::abs(std::fmod(h_prime, 2.0f) - 1.0f)); const float m = v - chroma; if (0.0f <= h_prime && h_prime < 1.0f) { r = chroma; g = x; b = 0.0f; } else if (1.0f <= h_prime && h_prime < 2.0f) { r = x; g = chroma; b = 0.0f; } else if (2.0f <= h_prime && h_prime < 3.0f) { r = 0.0f; g = chroma; b = x; } else if (3.0f <= h_prime && h_prime < 4.0f) { r = 0.0f; g = x; b = chroma; } else if (4.0f <= h_prime && h_prime < 5.0f) { r = x; g = 0.0f; b = chroma; } else if (5.0f <= h_prime && h_prime < 6.0f) { r = chroma; g = 0.0f; b = x; } else { r = 0.0f; g = 0.0f; b = 0.0f; } r += m; g += m; b += m; assert(0.0f <= r && r <= 1.0f); assert(0.0f <= g && g <= 1.0f); assert(0.0f <= b && b <= 1.0f); } class Randomizer { std::random_device m_rd; public: float random_float(float min, float max) { std::mt19937 rand_generator(m_rd()); std::uniform_real_distribution distrib(min, max); return distrib(rand_generator); } }; ColorRGB::ColorRGB(float r, float g, float b) : m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f) }) { } ColorRGB::ColorRGB(unsigned char r, unsigned char g, unsigned char b) : m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f) }) { } bool ColorRGB::operator < (const ColorRGB& other) const { for (size_t i = 0; i < 3; ++i) { if (m_data[i] < other.m_data[i]) return true; } return false; } bool ColorRGB::operator > (const ColorRGB& other) const { for (size_t i = 0; i < 3; ++i) { if (m_data[i] > other.m_data[i]) return true; } return false; } ColorRGB ColorRGB::operator + (const ColorRGB& other) const { ColorRGB ret; for (size_t i = 0; i < 3; ++i) { ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f); } return ret; } ColorRGB ColorRGB::operator * (float value) const { assert(value >= 0.0f); ColorRGB ret; for (size_t i = 0; i < 3; ++i) { ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f); } return ret; } ColorRGBA::ColorRGBA(float r, float g, float b, float a) : m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f), std::clamp(a, 0.0f, 1.0f) }) { } ColorRGBA::ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) : m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f), std::clamp(a * INV_255, 0.0f, 1.0f) }) { } bool ColorRGBA::operator < (const ColorRGBA& other) const { for (size_t i = 0; i < 3; ++i) { if (m_data[i] < other.m_data[i]) return true; } return false; } bool ColorRGBA::operator > (const ColorRGBA& other) const { for (size_t i = 0; i < 3; ++i) { if (m_data[i] > other.m_data[i]) return true; } return false; } ColorRGBA ColorRGBA::operator + (const ColorRGBA& other) const { ColorRGBA ret; for (size_t i = 0; i < 3; ++i) { ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f); } return ret; } ColorRGBA ColorRGBA::operator * (float value) const { assert(value >= 0.0f); ColorRGBA ret; for (size_t i = 0; i < 3; ++i) { ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f); } ret.m_data[3] = this->m_data[3]; return ret; } ColorRGB operator * (float value, const ColorRGB& other) { return other * value; } ColorRGBA operator * (float value, const ColorRGBA& other) { return other * value; } ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t) { assert(0.0f <= t && t <= 1.0f); return (1.0f - t) * a + t * b; } ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t) { assert(0.0f <= t && t <= 1.0f); return (1.0f - t) * a + t * b; } ColorRGB complementary(const ColorRGB& color) { return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b() }; } ColorRGBA complementary(const ColorRGBA& color) { return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b(), color.a() }; } ColorRGB saturate(const ColorRGB& color, float factor) { float h, s, v; RGBtoHSV(color.r(), color.g(), color.b(), h, s, v); s = std::clamp(s * factor, 0.0f, 1.0f); float r, g, b; HSVtoRGB(h, s, v, r, g, b); return { r, g, b }; } ColorRGBA saturate(const ColorRGBA& color, float factor) { return to_rgba(saturate(to_rgb(color), factor), color.a()); } ColorRGB opposite(const ColorRGB& color) { float h, s, v; RGBtoHSV(color.r(), color.g(), color.b(), h, s, v); h += 65.0f; // 65 instead 60 to avoid circle values if (h > 360.0f) h -= 360.0f; Randomizer rnd; s = rnd.random_float(0.65f, 1.0f); v = rnd.random_float(0.65f, 1.0f); float r, g, b; HSVtoRGB(h, s, v, r, g, b); return { r, g, b }; } ColorRGB opposite(const ColorRGB& a, const ColorRGB& b) { float ha, sa, va; RGBtoHSV(a.r(), a.g(), a.b(), ha, sa, va); float hb, sb, vb; RGBtoHSV(b.r(), b.g(), b.b(), hb, sb, vb); float delta_h = std::abs(ha - hb); float start_h = (delta_h > 180.0f) ? std::min(ha, hb) : std::max(ha, hb); start_h += 5.0f; // to avoid circle change of colors for 120 deg if (delta_h < 180.0f) delta_h = 360.0f - delta_h; Randomizer rnd; float out_h = start_h + 0.5f * delta_h; if (out_h > 360.0f) out_h -= 360.0f; float out_s = rnd.random_float(0.65f, 1.0f); float out_v = rnd.random_float(0.65f, 1.0f); float out_r, out_g, out_b; HSVtoRGB(out_h, out_s, out_v, out_r, out_g, out_b); return { out_r, out_g, out_b }; } bool can_decode_color(const std::string& color) { return color.size() == 7 && color.front() == '#'; } bool decode_color(const std::string& color_in, ColorRGB& color_out) { auto hex_digit_to_int = [](const char c) { return (c >= '0' && c <= '9') ? int(c - '0') : (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; }; color_out = ColorRGB::BLACK(); if (can_decode_color(color_in)) { const char* c = color_in.data() + 1; for (unsigned int i = 0; i < 3; ++i) { const int digit1 = hex_digit_to_int(*c++); const int digit2 = hex_digit_to_int(*c++); if (digit1 != -1 && digit2 != -1) color_out.set(i, float(digit1 * 16 + digit2) * INV_255); } } else return false; assert(0.0f <= color_out.r() && color_out.r() <= 1.0f); assert(0.0f <= color_out.g() && color_out.g() <= 1.0f); assert(0.0f <= color_out.b() && color_out.b() <= 1.0f); return true; } bool decode_color(const std::string& color_in, ColorRGBA& color_out) { ColorRGB rgb; if (!decode_color(color_in, rgb)) return false; color_out = to_rgba(rgb, color_out.a()); return true; } bool decode_colors(const std::vector& colors_in, std::vector& colors_out) { colors_out = std::vector(colors_in.size(), ColorRGB::BLACK()); for (size_t i = 0; i < colors_in.size(); ++i) { if (!decode_color(colors_in[i], colors_out[i])) return false; } return true; } bool decode_colors(const std::vector& colors_in, std::vector& colors_out) { colors_out = std::vector(colors_in.size(), ColorRGBA::BLACK()); for (size_t i = 0; i < colors_in.size(); ++i) { if (!decode_color(colors_in[i], colors_out[i])) return false; } return true; } std::string encode_color(const ColorRGB& color) { char buffer[64]; ::sprintf(buffer, "#%02X%02X%02X", color.r_uchar(), color.g_uchar(), color.b_uchar()); return std::string(buffer); } std::string encode_color(const ColorRGBA& color) { return encode_color(to_rgb(color)); } ColorRGB to_rgb(const ColorRGBA& other_rgba) { return { other_rgba.r(), other_rgba.g(), other_rgba.b() }; } ColorRGBA to_rgba(const ColorRGB& other_rgb) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), 1.0f }; } ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), alpha }; } ColorRGBA picking_decode(unsigned int id) { return { float((id >> 0) & 0xff) * INV_255, // red float((id >> 8) & 0xff) * INV_255, // green float((id >> 16) & 0xff) * INV_255, // blue float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff)) * INV_255 // checksum for validating against unwanted alpha blending and multi sampling }; } unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b) { return r + (g << 8) + (b << 16); } unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue) { // 8 bit hash for the color unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff; // Increase enthropy by a bit reversal b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; b = (b & 0xCC) >> 2 | (b & 0x33) << 2; b = (b & 0xAA) >> 1 | (b & 0x55) << 1; // Flip every second bit to increase the enthropy even more. b ^= 0x55; return b; } } // namespace Slic3r