diff --git a/src/libslic3r/MixedFilament.cpp b/src/libslic3r/MixedFilament.cpp index f3f6ba6ac0..a6ed6db91c 100644 --- a/src/libslic3r/MixedFilament.cpp +++ b/src/libslic3r/MixedFilament.cpp @@ -25,12 +25,12 @@ struct RGBf { float r = 0.f, g = 0.f, b = 0.f; }; -static float clamp01(float v) +[[maybe_unused]] static float clamp01(float v) { return std::max(0.f, std::min(1.f, v)); } -static RGBf to_rgbf(const RGB &c) +[[maybe_unused]] static RGBf to_rgbf(const RGB &c) { return { clamp01(static_cast(c.r) / 255.f), @@ -39,7 +39,7 @@ static RGBf to_rgbf(const RGB &c) }; } -static RGB to_rgb8(const RGBf &c) +[[maybe_unused]] static RGB to_rgb8(const RGBf &c) { auto to_u8 = [](float v) -> int { return std::clamp(static_cast(std::round(clamp01(v) * 255.f)), 0, 255); @@ -47,10 +47,14 @@ static RGB to_rgb8(const RGBf &c) return { to_u8(c.r), to_u8(c.g), to_u8(c.b) }; } + // Convert RGB to an artist-pigment style RYB space. // This is an approximation, but it gives expected pair mixes: // Red + Blue -> Purple, Blue + Yellow -> Green, Red + Yellow -> Orange. -static RGBf rgb_to_ryb(RGBf in) + +// Legacy RYB conversion helpers kept for reference. +// Active code paths use FilamentMixer. +[[maybe_unused]] static RGBf rgb_to_ryb(RGBf in) { float r = clamp01(in.r); float g = clamp01(in.g); @@ -89,7 +93,7 @@ static RGBf rgb_to_ryb(RGBf in) return { clamp01(r), clamp01(y), clamp01(b) }; } -static RGBf ryb_to_rgb(RGBf in) +[[maybe_unused]] static RGBf ryb_to_rgb(RGBf in) { float r = clamp01(in.r); float y = clamp01(in.g); @@ -151,6 +155,41 @@ static std::string rgb_to_hex(const RGB &c) return std::string(buf); } +[[maybe_unused]] static std::string blend_color_ryb_legacy(const RGB &rgb_a, + const RGB &rgb_b, + int ratio_a, + int ratio_b) +{ + const int safe_a = std::max(0, ratio_a); + const int safe_b = std::max(0, ratio_b); + const float total = static_cast(safe_a + safe_b); + const float wa = (total > 0.f) ? static_cast(safe_a) / total : 0.5f; + const float wb = 1.f - wa; + + const RGBf color_a = to_rgbf(rgb_a); + const RGBf color_b = to_rgbf(rgb_b); + const RGBf ryb_a = rgb_to_ryb(color_a); + const RGBf ryb_b = rgb_to_ryb(color_b); + + RGBf ryb_out; + ryb_out.r = wa * ryb_a.r + wb * ryb_b.r; + ryb_out.g = wa * ryb_a.g + wb * ryb_b.g; + ryb_out.b = wa * ryb_a.b + wb * ryb_b.b; + + RGBf rgb_out = ryb_to_rgb(ryb_out); + const float v_out = std::max({ rgb_out.r, rgb_out.g, rgb_out.b }); + const float v_tgt = wa * std::max({ color_a.r, color_a.g, color_a.b }) + + wb * std::max({ color_b.r, color_b.g, color_b.b }); + if (v_out > 1e-6f && v_tgt > 0.f) { + const float scale = v_tgt / v_out; + rgb_out.r = clamp01(rgb_out.r * scale); + rgb_out.g = clamp01(rgb_out.g * scale); + rgb_out.b = clamp01(rgb_out.b * scale); + } + + return rgb_to_hex(to_rgb8(rgb_out)); +} + static int clamp_int(int v, int lo, int hi) { return std::max(lo, std::min(hi, v)); @@ -1036,32 +1075,24 @@ std::string MixedFilamentManager::blend_color(const std::string &color_a, { const int safe_a = std::max(0, ratio_a); const int safe_b = std::max(0, ratio_b); - const float total = static_cast(safe_a + safe_b); - const float wa = (total > 0.f) ? static_cast(safe_a) / total : 0.5f; - const float wb = 1.f - wa; + const int total = safe_a + safe_b; + const float t = (total > 0) ? (static_cast(safe_b) / static_cast(total)) : 0.5f; - const RGBf rgb_a = to_rgbf(parse_hex_color(color_a)); - const RGBf rgb_b = to_rgbf(parse_hex_color(color_b)); - const RGBf ryb_a = rgb_to_ryb(rgb_a); - const RGBf ryb_b = rgb_to_ryb(rgb_b); + const RGB rgb_a = parse_hex_color(color_a); + const RGB rgb_b = parse_hex_color(color_b); - RGBf ryb_out; - ryb_out.r = wa * ryb_a.r + wb * ryb_b.r; - ryb_out.g = wa * ryb_a.g + wb * ryb_b.g; - ryb_out.b = wa * ryb_a.b + wb * ryb_b.b; + unsigned char out_r = static_cast(rgb_a.r); + unsigned char out_g = static_cast(rgb_a.g); + unsigned char out_b = static_cast(rgb_a.b); + filament_mixer_lerp(static_cast(rgb_a.r), + static_cast(rgb_a.g), + static_cast(rgb_a.b), + static_cast(rgb_b.r), + static_cast(rgb_b.g), + static_cast(rgb_b.b), + t, &out_r, &out_g, &out_b); - RGBf rgb_out = ryb_to_rgb(ryb_out); - const float v_out = std::max({ rgb_out.r, rgb_out.g, rgb_out.b }); - const float v_tgt = wa * std::max({ rgb_a.r, rgb_a.g, rgb_a.b }) + - wb * std::max({ rgb_b.r, rgb_b.g, rgb_b.b }); - if (v_out > 1e-6f && v_tgt > 0.f) { - const float scale = v_tgt / v_out; - rgb_out.r = clamp01(rgb_out.r * scale); - rgb_out.g = clamp01(rgb_out.g * scale); - rgb_out.b = clamp01(rgb_out.b * scale); - } - - return rgb_to_hex(to_rgb8(rgb_out)); + return rgb_to_hex({int(out_r), int(out_g), int(out_b)}); } void MixedFilamentManager::refresh_display_colors(const std::vector &filament_colours) diff --git a/src/libslic3r/MixedFilament.hpp b/src/libslic3r/MixedFilament.hpp index 5207ea4cc8..febc3c6b7b 100644 --- a/src/libslic3r/MixedFilament.hpp +++ b/src/libslic3r/MixedFilament.hpp @@ -10,10 +10,11 @@ namespace Slic3r { // Represents a virtual "mixed" filament created from physical filaments -// (layer cadence and/or same-layer interleaved stripe distribution). The display -// colour uses a FilamentMixer-based multi-color blend (and RYB pair blend) so -// pair previews better match expected print mixing (for example Blue+Yellow -// -> Green, Red+Yellow -> Orange, Red+Blue -> Purple). +// (layer cadence and/or same-layer interleaved stripe distribution). Display +// colour blending uses FilamentMixer so pair previews better +// match expected print mixing +// (for example Blue+Yellow -> Green, Red+Yellow -> Orange, Red+Blue -> Purple). +// Legacy RYB code is retained in source for reference only. struct MixedFilament { enum DistributionMode : uint8_t { @@ -160,7 +161,7 @@ public: const MixedFilament *mixed_filament_from_id(unsigned int filament_id, size_t num_physical) const; - // Compute a display colour by blending in RYB pigment space. + // Compute a display colour by blending two colours with FilamentMixer. static std::string blend_color(const std::string &color_a, const std::string &color_b, int ratio_a, int ratio_b);