diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index 452f0c2a338..9d5899b1e0e 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -2076,7 +2076,7 @@ std::optional texture_mapping_parse_top_surface_ !calibration.depth_kernel_linear.valid() && !calibration.nearest_measured_sample.valid()) { if (error != nullptr) - *error = "The calibration file does not contain any supported color prediction weights."; + *error = "The loaded file does not contain any top-surface calibration data."; return std::nullopt; } diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp index 48a78723e95..c2bf675b9e2 100644 --- a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp +++ b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp @@ -122,6 +122,7 @@ struct TexturePreviewSimulationSettings std::vector component_minimum_offset_factors; std::vector semantic_component_indices; std::shared_ptr generic_mix_candidate_cache; + ColorSolverCandidateSet side_surface_calibrated_candidates; float raw_offset_base_visibility_factor = 1.f; float raw_offset_visibility_range_factor = 0.f; }; @@ -726,6 +727,116 @@ bool generic_solver_mode_is_perceptual(int generic_solver_mode) return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) != int(TextureMappingZone::GenericSolverRGB); } +float apply_texture_tone_gamma(float channel, float tone_gamma); + +std::array texture_preview_target_rgb_without_contrast(const TexturePreviewSimulationSettings &settings, + const std::array &sample_rgba) +{ + std::array target = { + clamp01(sample_rgba[0]), + clamp01(sample_rgba[1]), + clamp01(sample_rgba[2]) + }; + if (std::abs(settings.tone_gamma - 1.f) > 1e-5f) { + target[0] = apply_texture_tone_gamma(target[0], settings.tone_gamma); + target[1] = apply_texture_tone_gamma(target[1], settings.tone_gamma); + target[2] = apply_texture_tone_gamma(target[2], settings.tone_gamma); + } + return target; +} + +bool texture_preview_has_side_surface_calibrated_candidates(const TexturePreviewSimulationSettings &settings) +{ + return settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && + !settings.contoning_flat_surface_quantization && + !settings.contoning_flat_surface_pattern_blend && + !settings.contoning_flat_surface_raw_top_stack_preview && + !settings.side_surface_calibrated_candidates.empty() && + settings.side_surface_calibrated_candidates.component_count == settings.component_colors.size(); +} + +size_t nearest_side_surface_calibrated_candidate_for_texture_preview( + const TexturePreviewSimulationSettings &settings, + const std::array &target_rgb) +{ + const ColorSolverCandidateSet &candidates = settings.side_surface_calibrated_candidates; + const size_t candidate_count = candidates.rgbs.size() / 3; + if (candidate_count == 0) + return size_t(-1); + + const int solver_mode = TextureMappingZone::effective_generic_solver_mode(settings.generic_solver_mode); + const bool perceptual = generic_solver_mode_is_perceptual(solver_mode) && + candidates.perceptual_coords.size() == candidates.rgbs.size(); + const std::array target_oklab = perceptual ? oklab_from_srgb(target_rgb) : std::array{ 0.f, 0.f, 0.f }; + const std::array axis_weights = perceptual ? generic_solver_perceptual_axis_weights(target_oklab, solver_mode) : + std::array{ 1.f, 1.f, 1.f }; + + size_t best_idx = size_t(-1); + float best_error = std::numeric_limits::max(); + for (size_t candidate_idx = 0; candidate_idx < candidate_count; ++candidate_idx) { + float error = 0.f; + if (perceptual) { + const size_t coord_idx = candidate_idx * 3; + const float dl = candidates.perceptual_coords[coord_idx + 0] - target_oklab[0]; + const float da = candidates.perceptual_coords[coord_idx + 1] - target_oklab[1]; + const float db = candidates.perceptual_coords[coord_idx + 2] - target_oklab[2]; + error = axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db; + if (solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) { + const float under_l = std::max(0.f, target_oklab[0] - candidates.perceptual_coords[coord_idx + 0] - 0.04f); + error += 4.f * generic_solver_oklab_chroma_factor(target_oklab) * under_l * under_l; + } + } else { + const size_t rgb_idx = candidate_idx * 3; + const float dr = candidates.rgbs[rgb_idx + 0] - target_rgb[0]; + const float dg = candidates.rgbs[rgb_idx + 1] - target_rgb[1]; + const float db = candidates.rgbs[rgb_idx + 2] - target_rgb[2]; + error = dr * dr + dg * dg + db * db; + } + if (error < best_error) { + best_error = error; + best_idx = candidate_idx; + } + } + return best_idx; +} + +std::optional> side_surface_calibrated_rgb_for_texture_preview( + const TexturePreviewSimulationSettings &settings, + const std::array &sample_rgba) +{ + if (!texture_preview_has_side_surface_calibrated_candidates(settings)) + return std::nullopt; + const std::array target_rgb = texture_preview_target_rgb_without_contrast(settings, sample_rgba); + const size_t candidate_idx = nearest_side_surface_calibrated_candidate_for_texture_preview(settings, target_rgb); + if (candidate_idx >= settings.side_surface_calibrated_candidates.rgbs.size() / 3) + return std::nullopt; + const size_t rgb_idx = candidate_idx * 3; + return std::array{ + settings.side_surface_calibrated_candidates.rgbs[rgb_idx + 0], + settings.side_surface_calibrated_candidates.rgbs[rgb_idx + 1], + settings.side_surface_calibrated_candidates.rgbs[rgb_idx + 2] + }; +} + +std::vector side_surface_calibrated_weights_for_texture_preview( + const TexturePreviewSimulationSettings &settings, + const std::array &sample_rgba) +{ + if (!texture_preview_has_side_surface_calibrated_candidates(settings)) + return {}; + const std::array target_rgb = texture_preview_target_rgb_without_contrast(settings, sample_rgba); + const size_t candidate_idx = nearest_side_surface_calibrated_candidate_for_texture_preview(settings, target_rgb); + const ColorSolverCandidateSet &candidates = settings.side_surface_calibrated_candidates; + const size_t candidate_count = candidates.rgbs.size() / 3; + if (candidate_idx >= candidate_count) + return {}; + const size_t weight_idx = candidate_idx * candidates.component_count; + if (weight_idx + candidates.component_count > candidates.weights.size()) + return {}; + return std::vector(candidates.weights.begin() + weight_idx, + candidates.weights.begin() + weight_idx + candidates.component_count); +} + unsigned char to_u8(float value) { return static_cast(clamp01(value) * 255.f + 0.5f); @@ -1775,6 +1886,8 @@ bool texture_preview_uses_generic_solver(const TexturePreviewSimulationSettings { if (settings.mapping_mode == int(TextureMappingZone::TextureMappingRawValues)) return false; + if (texture_preview_has_side_surface_calibrated_candidates(settings)) + return false; if (settings.use_fixed_color_generic_solver) return true; @@ -2765,6 +2878,7 @@ std::vector component_weights_for_texture_preview(const TexturePreviewSim if (settings.dithering_enabled && settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && + !texture_preview_has_side_surface_calibrated_candidates(settings) && !is_halftone_dithering_method_for_texture_preview(settings.dithering_method)) { const std::vector candidates = binary_dither_candidates_for_texture_preview(settings); const std::array target_color = texture_preview_target_color(settings, sample_rgba); @@ -2793,6 +2907,10 @@ std::vector component_weights_for_texture_preview(const TexturePreviewSim for (size_t channel_idx = 0; channel_idx < channel_count; ++channel_idx) desired[channel_idx] = clamp01(channels[channel_idx]); mapped_component_count = channel_count; + } else if (texture_preview_has_side_surface_calibrated_candidates(settings)) { + std::vector calibrated = side_surface_calibrated_weights_for_texture_preview(settings, sample_rgba); + if (calibrated.size() == component_count) + desired = std::move(calibrated); } else { std::vector optimized; if (!settings.use_fixed_color_generic_solver) @@ -2819,10 +2937,12 @@ std::vector component_weights_for_texture_preview(const TexturePreviewSim } const float contrast_factor = std::clamp(settings.contrast_pct, 25.f, 300.f) / 100.f; - if (std::abs(contrast_factor - 1.f) > 1e-5f) + if (!texture_preview_has_side_surface_calibrated_candidates(settings) && + std::abs(contrast_factor - 1.f) > 1e-5f) apply_texture_contrast_to_mapped_components(desired, contrast_factor, mapped_component_count); - if (settings.compact_offset_mode) { + if (!texture_preview_has_side_surface_calibrated_candidates(settings) && + settings.compact_offset_mode) { float max_weight = 0.f; for (const float value : desired) max_weight = std::max(max_weight, clamp01(value)); @@ -3028,6 +3148,10 @@ ColorRGBA simulated_texture_preview_color_for_vertex_color(const ColorRGBA *sour source_color->b(), source_color->a() }; + if (std::optional> calibrated_rgb = + side_surface_calibrated_rgb_for_texture_preview(*settings, sample_rgba)) + return { (*calibrated_rgb)[0], (*calibrated_rgb)[1], (*calibrated_rgb)[2], source_color->a() }; + const std::vector component_weights = component_weights_for_texture_preview(*settings, sample_rgba); float activity = 0.f; for (const float weight : component_weights) @@ -3171,6 +3295,28 @@ std::optional texture_preview_simulation_setti settings.component_minimum_offset_factors.emplace_back(std::clamp(safe_minimum_offset_pct / 100.f, 0.f, 1.f)); } + if (!raw_values_mode && + zone->transmission_distance_calibration_mode == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)) { + std::vector component_transmission_distances_mm; + component_transmission_distances_mm.reserve(settings.component_ids.size()); + for (const unsigned int component_id : settings.component_ids) { + const size_t idx = component_id > 0 ? size_t(component_id - 1) : size_t(-1); + const float value = idx < zone->filament_transmission_distances_mm.size() ? + zone->filament_transmission_distances_mm[idx] : + 0.f; + component_transmission_distances_mm.emplace_back(std::isfinite(value) && value > 0.f ? std::clamp(value, 0.01f, 50.f) : 0.f); + } + std::optional calibrated_candidates = + texture_mapping_side_surface_color_calibrated_candidates(*zone, + settings.component_colors, + component_transmission_distances_mm); + if (calibrated_candidates && !calibrated_candidates->empty()) { + settings.side_surface_calibrated_candidates = std::move(*calibrated_candidates); + std::fill(settings.component_strength_factors.begin(), settings.component_strength_factors.end(), 1.f); + std::fill(settings.component_minimum_offset_factors.begin(), settings.component_minimum_offset_factors.end(), 0.f); + } + } + if (settings.contoning_flat_surface_td_adjustment && !settings.component_colors.empty()) { std::vector background_weights(settings.component_colors.size(), 1.f / float(settings.component_colors.size())); settings.contoning_flat_surface_background_rgb = @@ -3272,6 +3418,13 @@ size_t texture_preview_simulation_signature(const ModelVolume &model_volume, mix(std::hash{}(int(std::lround(color[1] * 255.f)))); mix(std::hash{}(int(std::lround(color[2] * 255.f)))); } + if (!settings.side_surface_calibrated_candidates.empty()) { + mix(std::hash{}(settings.side_surface_calibrated_candidates.component_count)); + for (const float value : settings.side_surface_calibrated_candidates.rgbs) + mix(std::hash{}(int(std::lround(value * 255.f)))); + for (const float value : settings.side_surface_calibrated_candidates.weights) + mix(std::hash{}(int(std::lround(value * 1000000.f)))); + } for (const float strength_factor : settings.component_strength_factors) mix(std::hash{}(int(std::lround(strength_factor * 1000.f)))); for (const float minimum_offset_factor : settings.component_minimum_offset_factors) @@ -3385,12 +3538,14 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig !use_contoning_flat_surface_quantization && settings.dithering_enabled && settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && + !texture_preview_has_side_surface_calibrated_candidates(settings) && !is_halftone_dithering_method_for_texture_preview(settings.dithering_method) && !use_raw_offsets; const bool use_halftone_dithering = !use_contoning_flat_surface_quantization && settings.dithering_enabled && settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && + !texture_preview_has_side_surface_calibrated_candidates(settings) && is_halftone_dithering_method_for_texture_preview(settings.dithering_method) && !use_raw_offsets; const bool halftone_increased_detail = @@ -3629,6 +3784,9 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig } std::vector component_weights; std::optional> forced_simulated_rgb; + if (std::optional> calibrated_rgb = + side_surface_calibrated_rgb_for_texture_preview(settings, sample_rgba)) + forced_simulated_rgb = *calibrated_rgb; if (use_raw_offsets) { const std::vector raw_sample = sample_texture_preview_raw_offsets_bilinear(source_raw_offsets, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 335bd6bfabf..e3ff52c7bec 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2451,6 +2451,12 @@ static wxString texture_mapping_contoning_color_prediction_default_label() return label; } +static bool texture_mapping_contoning_color_prediction_mode_selectable(int mode) +{ + return TextureMappingZone::effective_top_surface_contoning_color_prediction_mode(mode) != + int(TextureMappingZone::ContoningColorPredictionCalibratedCurrentLinearAffine); +} + static int texture_mapping_contoning_polygonization_mode_choice_selection(int mode) { switch (TextureMappingZone::effective_top_surface_contoning_polygonization_mode(mode)) { @@ -2679,17 +2685,9 @@ public: 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); - wxArrayString td_mode_choices; - td_mode_choices.Add(_L("None")); - td_mode_choices.Add(_L("Absolute TD")); - td_mode_choices.Add(_L("Neighbour TD")); - td_mode_choices.Add(_L("Calibrated: nearest measured sample")); m_transmission_distance_calibration_mode_choice = - new wxChoice(filament_page, wxID_ANY, wxDefaultPosition, wxDefaultSize, td_mode_choices); - m_transmission_distance_calibration_mode_choice->SetSelection( - std::clamp(transmission_distance_calibration_mode, - int(TextureMappingZone::TDCalibrationNone), - int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample))); + new wxChoice(filament_page, wxID_ANY); + rebuild_transmission_distance_calibration_mode_choices(transmission_distance_calibration_mode); m_transmission_distance_calibration_mode_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent &) { update_strength_offsets_visibility(true); }); @@ -3845,10 +3843,11 @@ public: float tone_gamma() const { return float(std::clamp(m_tone_gamma_spin ? m_tone_gamma_spin->GetValue() : 1.0, 0.5, 3.0)); } int transmission_distance_calibration_mode() const { - return m_transmission_distance_calibration_mode_choice ? - std::clamp(m_transmission_distance_calibration_mode_choice->GetSelection(), - int(TextureMappingZone::TDCalibrationNone), - int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)) : + if (m_transmission_distance_calibration_mode_choice == nullptr) + return TextureMappingZone::DefaultTransmissionDistanceCalibrationMode; + const int selection = m_transmission_distance_calibration_mode_choice->GetSelection(); + return selection >= 0 && size_t(selection) < m_transmission_distance_calibration_mode_values.size() ? + m_transmission_distance_calibration_mode_values[size_t(selection)] : TextureMappingZone::DefaultTransmissionDistanceCalibrationMode; } @@ -4484,6 +4483,57 @@ private: return colors; } + bool side_surface_color_calibration_loaded() const + { + return texture_mapping_parse_side_surface_color_calibration(m_side_surface_color_calibration_json).has_value(); + } + + wxString transmission_distance_calibration_mode_label(int mode) const + { + switch (mode) { + case int(TextureMappingZone::TDCalibrationNone): + return _L("None"); + case int(TextureMappingZone::TDCalibrationNeighbor): + return _L("Neighbour TD"); + case int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample): + return _L("Calibrated: nearest measured sample"); + default: + return _L("Absolute TD"); + } + } + + void rebuild_transmission_distance_calibration_mode_choices(int selected_mode) + { + if (m_transmission_distance_calibration_mode_choice == nullptr) + return; + + m_transmission_distance_calibration_mode_values.clear(); + auto add_mode = [this](int mode) { + m_transmission_distance_calibration_mode_values.emplace_back(mode); + m_transmission_distance_calibration_mode_choice->Append( + transmission_distance_calibration_mode_label(mode)); + }; + + m_transmission_distance_calibration_mode_choice->Clear(); + add_mode(int(TextureMappingZone::TDCalibrationNone)); + add_mode(int(TextureMappingZone::TDCalibrationAbsolute)); + add_mode(int(TextureMappingZone::TDCalibrationNeighbor)); + if (side_surface_color_calibration_loaded()) + add_mode(int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)); + + auto it = std::find(m_transmission_distance_calibration_mode_values.begin(), + m_transmission_distance_calibration_mode_values.end(), + selected_mode); + if (it == m_transmission_distance_calibration_mode_values.end()) + it = std::find(m_transmission_distance_calibration_mode_values.begin(), + m_transmission_distance_calibration_mode_values.end(), + TextureMappingZone::DefaultTransmissionDistanceCalibrationMode); + m_transmission_distance_calibration_mode_choice->SetSelection( + it != m_transmission_distance_calibration_mode_values.end() ? + int(it - m_transmission_distance_calibration_mode_values.begin()) : + 0); + } + void rebuild_top_surface_contoning_color_prediction_choices(int selected_mode) { if (m_top_surface_contoning_td_correction_choice == nullptr) @@ -4505,7 +4555,8 @@ private: add_mode(int(TextureMappingZone::ContoningColorPredictionBeerLambertRgb)); add_mode(int(TextureMappingZone::ContoningColorPredictionBasicReflectance)); for (int mode : texture_mapping_top_surface_color_calibration_supported_modes(m_top_surface_color_calibration_json)) - add_mode(mode); + if (texture_mapping_contoning_color_prediction_mode_selectable(mode)) + add_mode(mode); auto it = std::find(m_top_surface_contoning_color_prediction_modes.begin(), m_top_surface_contoning_color_prediction_modes.end(), @@ -4606,7 +4657,12 @@ private: const int nearest_mode = int(TextureMappingZone::ContoningColorPredictionCalibratedNearestMeasuredSample); if (std::find(modes.begin(), modes.end(), nearest_mode) != modes.end()) return nearest_mode; - return texture_mapping_top_surface_color_calibration_first_supported_mode(m_top_surface_color_calibration_json); + const int first_mode = + texture_mapping_top_surface_color_calibration_first_supported_mode(m_top_surface_color_calibration_json); + if (texture_mapping_contoning_color_prediction_mode_selectable(first_mode)) + return first_mode; + auto it = std::find_if(modes.begin(), modes.end(), texture_mapping_contoning_color_prediction_mode_selectable); + return it != modes.end() ? *it : TextureMappingZone::DefaultTopSurfaceContoningColorPredictionMode; } bool apply_transmission_distances_from_calibration_filaments( @@ -4839,9 +4895,8 @@ private: if (side_calibration && m_side_surface_color_calibration_json.empty()) { m_side_surface_color_calibration_json = json_text; m_side_surface_color_calibration_name = filename; - if (m_transmission_distance_calibration_mode_choice != nullptr) - m_transmission_distance_calibration_mode_choice->SetSelection( - int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)); + rebuild_transmission_distance_calibration_mode_choices( + int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)); if (!td_applied) td_applied = apply_transmission_distances_from_calibration_filaments(side_calibration->filaments); side_changed = true; @@ -4849,9 +4904,8 @@ private: } else { m_side_surface_color_calibration_json = json_text; m_side_surface_color_calibration_name = filename; - if (m_transmission_distance_calibration_mode_choice != nullptr) - m_transmission_distance_calibration_mode_choice->SetSelection( - int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)); + rebuild_transmission_distance_calibration_mode_choices( + int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)); bool td_applied = apply_transmission_distances_from_calibration_filaments(side_calibration->filaments); side_changed = true; if (top_calibration && m_top_surface_color_calibration_json.empty()) { @@ -4892,13 +4946,10 @@ private: void clear_side_surface_color_calibration() { + const int selected_mode = transmission_distance_calibration_mode(); m_side_surface_color_calibration_name.clear(); m_side_surface_color_calibration_json.clear(); - if (m_transmission_distance_calibration_mode_choice != nullptr && - transmission_distance_calibration_mode() == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)) { - m_transmission_distance_calibration_mode_choice->SetSelection( - TextureMappingZone::DefaultTransmissionDistanceCalibrationMode); - } + rebuild_transmission_distance_calibration_mode_choices(selected_mode); update_side_surface_color_calibration_label(); update_strength_offsets_visibility(true); } @@ -5645,6 +5696,7 @@ private: wxChoice *m_texture_mapping_mode_choice {nullptr}; wxSpinCtrlDouble *m_tone_gamma_spin {nullptr}; wxChoice *m_transmission_distance_calibration_mode_choice {nullptr}; + std::vector m_transmission_distance_calibration_mode_values; wxButton *m_strength_offsets_toggle_button {nullptr}; wxPanel *m_strength_offsets_panel {nullptr}; wxSlider *m_preview_opacity_slider {nullptr};