Use Oklab when auto-selecting filament colors
This commit is contained in:
@@ -362,6 +362,11 @@ std::array<float, 3> mix_color_solver_components(const std::vector<std::array<fl
|
||||
return pigment_painter::mix_srgb(component_colors, safe_weights);
|
||||
}
|
||||
|
||||
std::array<float, 3> color_solver_oklab_from_srgb(const std::array<float, 3> &rgb)
|
||||
{
|
||||
return oklab_from_srgb(rgb);
|
||||
}
|
||||
|
||||
std::string color_solver_candidate_cache_key(const std::vector<std::array<float, 3>> &component_colors,
|
||||
ColorSolverMixModel mix_model,
|
||||
int total_units)
|
||||
|
||||
@@ -82,6 +82,7 @@ std::array<float, 3> mix_color_solver_components(const std::vector<std::array<fl
|
||||
std::array<float, 3> mix_color_solver_components(const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<float> &weights,
|
||||
ColorSolverMixModel mix_model);
|
||||
std::array<float, 3> color_solver_oklab_from_srgb(const std::array<float, 3> &rgb);
|
||||
|
||||
std::string color_solver_candidate_cache_key(const std::vector<std::array<float, 3>> &component_colors,
|
||||
ColorSolverMixModel mix_model,
|
||||
|
||||
@@ -7314,11 +7314,13 @@ static const GCodeUVTextureTriangleCache &uv_texture_triangle_cache_for_gcode(
|
||||
return it->second;
|
||||
}
|
||||
|
||||
static float color_distance_sq_for_gcode(const std::array<float, 3> &lhs, const std::array<float, 3> &rhs)
|
||||
static float perceptual_color_distance_sq_for_gcode(const std::array<float, 3> &lhs, const std::array<float, 3> &rhs)
|
||||
{
|
||||
const float dr = lhs[0] - rhs[0];
|
||||
const float dg = lhs[1] - rhs[1];
|
||||
const float db = lhs[2] - rhs[2];
|
||||
const std::array<float, 3> lhs_oklab = oklab_from_srgb_for_gcode(lhs);
|
||||
const std::array<float, 3> rhs_oklab = oklab_from_srgb_for_gcode(rhs);
|
||||
const float dr = lhs_oklab[0] - rhs_oklab[0];
|
||||
const float dg = lhs_oklab[1] - rhs_oklab[1];
|
||||
const float db = lhs_oklab[2] - rhs_oklab[2];
|
||||
return dr * dr + dg * dg + db * db;
|
||||
}
|
||||
|
||||
@@ -7337,7 +7339,7 @@ static std::vector<size_t> best_matching_component_indices_for_semantic_colors_f
|
||||
do {
|
||||
float error = 0.f;
|
||||
for (size_t role_idx = 0; role_idx < semantic_colors.size(); ++role_idx)
|
||||
error += color_distance_sq_for_gcode(component_colors[permutation[role_idx]], semantic_colors[role_idx]);
|
||||
error += perceptual_color_distance_sq_for_gcode(component_colors[permutation[role_idx]], semantic_colors[role_idx]);
|
||||
|
||||
if (error < best_error) {
|
||||
best_error = error;
|
||||
|
||||
@@ -248,6 +248,71 @@ static std::vector<std::string> collect_texture_mapping_vertex_color_match_warni
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<std::string> collect_texture_mapping_filament_color_match_warnings(const PrintObject &print_object)
|
||||
{
|
||||
const Print *print = print_object.print();
|
||||
if (print == nullptr)
|
||||
return {};
|
||||
|
||||
const ModelObject *model_object = print_object.model_object();
|
||||
if (model_object == nullptr)
|
||||
return {};
|
||||
|
||||
const size_t num_physical = print->config().filament_colour.size();
|
||||
if (num_physical == 0)
|
||||
return {};
|
||||
|
||||
std::vector<std::string> warnings;
|
||||
std::set<unsigned int> seen_zone_ids;
|
||||
for (const ModelVolume *volume : model_object->volumes) {
|
||||
if (volume == nullptr)
|
||||
continue;
|
||||
|
||||
const std::vector<int> used_extruders = volume->get_extruders();
|
||||
for (const int filament_id : used_extruders) {
|
||||
if (filament_id <= 0)
|
||||
continue;
|
||||
|
||||
const unsigned int filament_id_u = unsigned(filament_id);
|
||||
if (seen_zone_ids.find(filament_id_u) != seen_zone_ids.end())
|
||||
continue;
|
||||
seen_zone_ids.insert(filament_id_u);
|
||||
|
||||
const TextureMappingZone *zone = print->texture_mapping_manager().zone_from_id(filament_id_u);
|
||||
if (zone == nullptr || !zone->enabled || zone->deleted || !zone->is_image_texture())
|
||||
continue;
|
||||
|
||||
const std::vector<TextureMappingColorMatch> matches =
|
||||
TextureMappingManager::texture_component_color_matches(*zone, num_physical, print->config().filament_colour.values);
|
||||
if (matches.empty())
|
||||
continue;
|
||||
|
||||
std::vector<std::string> poor_matches;
|
||||
for (const TextureMappingColorMatch &match : matches) {
|
||||
if (match.perceptual_distance <= TextureMappingManager::poor_color_match_distance())
|
||||
continue;
|
||||
poor_matches.emplace_back("F" + std::to_string(match.filament_id) + " for " + match.expected_color_name);
|
||||
}
|
||||
if (poor_matches.empty())
|
||||
continue;
|
||||
|
||||
std::string detail;
|
||||
for (size_t idx = 0; idx < poor_matches.size(); ++idx) {
|
||||
if (idx > 0)
|
||||
detail += ", ";
|
||||
detail += poor_matches[idx];
|
||||
}
|
||||
|
||||
warnings.emplace_back(
|
||||
L("Filaments used in Texture Mapping zone ") +
|
||||
std::to_string(filament_id_u) + L(" do not appear to match the expected colors: ") + detail +
|
||||
L("."));
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
static const char *vertex_color_mode_name_for_error(int filament_color_mode)
|
||||
{
|
||||
switch (filament_color_mode) {
|
||||
@@ -1404,6 +1469,8 @@ void PrintObject::slice_volumes()
|
||||
this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, warning_msg);
|
||||
for (const std::string &warning_msg : collect_texture_mapping_vertex_color_match_warnings(*this))
|
||||
this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, warning_msg);
|
||||
for (const std::string &warning_msg : collect_texture_mapping_filament_color_match_warnings(*this))
|
||||
this->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, warning_msg);
|
||||
for (const std::string &error_msg : collect_texture_mapping_vertex_color_mode_mismatch_errors(*this))
|
||||
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, error_msg);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace {
|
||||
constexpr unsigned int TextureMappingZoneIdBase = 99;
|
||||
constexpr unsigned int MaxTextureMappingZoneId = 255;
|
||||
constexpr const char *TextureMappingGapDisplayColor = "#8C8C8C";
|
||||
constexpr float TextureMappingPoorColorMatchDistance = 0.22f;
|
||||
|
||||
struct RGB {
|
||||
int r = 0;
|
||||
@@ -379,11 +380,22 @@ static RGB filament_color(unsigned int id, const std::vector<std::string> &filam
|
||||
return {};
|
||||
}
|
||||
|
||||
static float color_distance_sq(const RGB &lhs, const RGB &rhs)
|
||||
static std::array<float, 3> rgb_to_srgb01(const RGB &color)
|
||||
{
|
||||
const float dr = float(lhs.r - rhs.r);
|
||||
const float dg = float(lhs.g - rhs.g);
|
||||
const float db = float(lhs.b - rhs.b);
|
||||
return {
|
||||
float(std::clamp(color.r, 0, 255)) / 255.f,
|
||||
float(std::clamp(color.g, 0, 255)) / 255.f,
|
||||
float(std::clamp(color.b, 0, 255)) / 255.f
|
||||
};
|
||||
}
|
||||
|
||||
static float perceptual_color_distance_sq(const RGB &lhs, const RGB &rhs)
|
||||
{
|
||||
const std::array<float, 3> lhs_oklab = color_solver_oklab_from_srgb(rgb_to_srgb01(lhs));
|
||||
const std::array<float, 3> rhs_oklab = color_solver_oklab_from_srgb(rgb_to_srgb01(rhs));
|
||||
const float dr = lhs_oklab[0] - rhs_oklab[0];
|
||||
const float dg = lhs_oklab[1] - rhs_oklab[1];
|
||||
const float db = lhs_oklab[2] - rhs_oklab[2];
|
||||
return dr * dr + dg * dg + db * db;
|
||||
}
|
||||
|
||||
@@ -405,6 +417,24 @@ static std::vector<RGB> semantic_colors(int filament_color_mode)
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<std::string> semantic_color_names(int filament_color_mode)
|
||||
{
|
||||
switch (clamp_int(filament_color_mode,
|
||||
int(TextureMappingZone::FilamentColorAny),
|
||||
int(TextureMappingZone::FilamentColorRGBKW))) {
|
||||
case int(TextureMappingZone::FilamentColorRGB): return {"Red", "Green", "Blue"};
|
||||
case int(TextureMappingZone::FilamentColorCMY): return {"Cyan", "Magenta", "Yellow"};
|
||||
case int(TextureMappingZone::FilamentColorCMYK): return {"Cyan", "Magenta", "Yellow", "Black"};
|
||||
case int(TextureMappingZone::FilamentColorCMYW): return {"Cyan", "Magenta", "Yellow", "White"};
|
||||
case int(TextureMappingZone::FilamentColorRGBK): return {"Red", "Green", "Blue", "Black"};
|
||||
case int(TextureMappingZone::FilamentColorRGBW): return {"Red", "Green", "Blue", "White"};
|
||||
case int(TextureMappingZone::FilamentColorBW): return {"Black", "White"};
|
||||
case int(TextureMappingZone::FilamentColorCMYKW): return {"Cyan", "Magenta", "Yellow", "Black", "White"};
|
||||
case int(TextureMappingZone::FilamentColorRGBKW): return {"Red", "Green", "Blue", "Black", "White"};
|
||||
default: return {};
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<unsigned int> ids_from_json(const nlohmann::json &value, size_t num_physical)
|
||||
{
|
||||
std::vector<unsigned int> ids;
|
||||
@@ -1412,7 +1442,7 @@ std::vector<unsigned int> TextureMappingManager::effective_texture_component_ids
|
||||
for (unsigned int id = 1; id <= num_physical; ++id) {
|
||||
if (id < used.size() && used[id])
|
||||
continue;
|
||||
const float distance = color_distance_sq(filament_color(id, filament_colours), target);
|
||||
const float distance = perceptual_color_distance_sq(filament_color(id, filament_colours), target);
|
||||
if (distance < best_distance) {
|
||||
best_distance = distance;
|
||||
best_id = id;
|
||||
@@ -1448,7 +1478,7 @@ std::vector<unsigned int> TextureMappingManager::effective_texture_component_ids
|
||||
const unsigned int id = selected[i];
|
||||
if (selected_used[i] || id < 1 || id > num_physical)
|
||||
continue;
|
||||
const float distance = color_distance_sq(filament_color(id, filament_colours), role);
|
||||
const float distance = perceptual_color_distance_sq(filament_color(id, filament_colours), role);
|
||||
if (distance < best_distance) {
|
||||
best_distance = distance;
|
||||
best_selected = i;
|
||||
@@ -1502,6 +1532,42 @@ bool TextureMappingManager::auto_adjust_texture_component_ids(TextureMappingZone
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<TextureMappingColorMatch> TextureMappingManager::texture_component_color_matches(
|
||||
const TextureMappingZone &zone,
|
||||
size_t num_physical,
|
||||
const std::vector<std::string> &filament_colours)
|
||||
{
|
||||
if (!zone.enabled || zone.deleted || !zone.is_image_texture() || num_physical == 0)
|
||||
return {};
|
||||
|
||||
const size_t expected = expected_component_count(zone.texture_mapping_mode, zone.filament_color_mode);
|
||||
if (expected == 0)
|
||||
return {};
|
||||
|
||||
const std::vector<RGB> roles = semantic_colors(zone.filament_color_mode);
|
||||
const std::vector<std::string> role_names = semantic_color_names(zone.filament_color_mode);
|
||||
if (roles.size() != expected || role_names.size() != expected)
|
||||
return {};
|
||||
|
||||
const std::vector<unsigned int> component_ids = effective_texture_component_ids(zone, num_physical, filament_colours);
|
||||
const size_t count = std::min(expected, component_ids.size());
|
||||
std::vector<TextureMappingColorMatch> matches;
|
||||
matches.reserve(count);
|
||||
for (size_t idx = 0; idx < count; ++idx) {
|
||||
const unsigned int id = component_ids[idx];
|
||||
if (id < 1 || id > num_physical)
|
||||
continue;
|
||||
const float distance = std::sqrt(perceptual_color_distance_sq(filament_color(id, filament_colours), roles[idx]));
|
||||
matches.push_back({id, role_names[idx], distance});
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
float TextureMappingManager::poor_color_match_distance()
|
||||
{
|
||||
return TextureMappingPoorColorMatchDistance;
|
||||
}
|
||||
|
||||
float TextureMappingManager::max_component_surface_offset_mm(float reference_width_mm)
|
||||
{
|
||||
const float safe_reference = std::max(0.05f, std::abs(reference_width_mm));
|
||||
|
||||
@@ -272,6 +272,13 @@ struct TextureMappingGlobalSettings
|
||||
static bool is_generic_solver_color_mode(const std::string &mode);
|
||||
};
|
||||
|
||||
struct TextureMappingColorMatch
|
||||
{
|
||||
unsigned int filament_id = 0;
|
||||
std::string expected_color_name;
|
||||
float perceptual_distance = 0.f;
|
||||
};
|
||||
|
||||
class TextureMappingManager
|
||||
{
|
||||
public:
|
||||
@@ -325,6 +332,10 @@ public:
|
||||
static bool auto_adjust_texture_component_ids(TextureMappingZone &zone,
|
||||
size_t num_physical,
|
||||
const std::vector<std::string> &filament_colours);
|
||||
static std::vector<TextureMappingColorMatch> texture_component_color_matches(const TextureMappingZone &zone,
|
||||
size_t num_physical,
|
||||
const std::vector<std::string> &filament_colours);
|
||||
static float poor_color_match_distance();
|
||||
|
||||
static std::vector<float> default_offset_distances(size_t component_count, float reference_width_mm = 0.4f);
|
||||
static std::vector<float> default_offset_angles(size_t component_count);
|
||||
|
||||
Reference in New Issue
Block a user