From 9aed9a4e6ad9693ed3df28c1c2c98670d7b61d10 Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Sat, 30 May 2026 23:01:03 +0100 Subject: [PATCH] Support multithreading for surface-anchored stacks - Add Default color mode which uses current slicer default - Update dithering to use selected color mode. --- src/libslic3r/Fill/Fill.cpp | 251 +++++++++++++++++++- src/libslic3r/GCode.cpp | 125 ++++++---- src/libslic3r/GCode/WipeTower.hpp | 2 +- src/libslic3r/TextureMapping.cpp | 18 +- src/libslic3r/TextureMapping.hpp | 19 +- src/libslic3r/TextureMappingOffset.cpp | 137 ++++++++--- src/slic3r/GUI/MMUPaintedTexturePreview.cpp | 130 ++++++---- src/slic3r/GUI/Plater.cpp | 66 ++++- 8 files changed, 597 insertions(+), 151 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 035092dbfb3..448d7c9a001 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -552,7 +553,7 @@ static void top_surface_image_contoning_report_anchored_progress(const PrintObje std::clamp(current_depth, 0, total_depth), total_depth, source_layer.id() + 1, - source_surface == TopSurfaceImageSourceSurface::Bottom ? L(", lower") : L(", upper"))); + source_surface == TopSurfaceImageSourceSurface::Bottom ? L("- lower") : L("- upper"))); } static ExPolygons top_surface_clip_union_ex(const Polygons &subject) @@ -1400,6 +1401,139 @@ struct TopSurfaceImageContoningAnchoredSurfacePlan { std::vector regions; }; +class TopSurfaceImageContoningAnchoredDepthWork +{ +public: + using BuildFn = std::function(int)>; + using StoreFn = std::function&&)>; + using ProgressFn = std::function; + + TopSurfaceImageContoningAnchoredDepthWork(std::vector depths, + BuildFn build, + StoreFn store, + ProgressFn progress) + : m_depths(std::move(depths)) + , m_build(std::move(build)) + , m_store(std::move(store)) + , m_progress(std::move(progress)) + {} + + bool run_one() + { + int depth = -1; + { + std::lock_guard lock(m_mutex); + if (m_error || m_next >= m_depths.size()) + return false; + depth = m_depths[m_next++]; + ++m_active; + } + + std::vector regions; + std::exception_ptr error; + try { + regions = m_build(depth); + m_store(depth, std::move(regions)); + } catch (...) { + error = std::current_exception(); + } + + int completed = 0; + bool done = false; + { + std::lock_guard lock(m_mutex); + if (error && !m_error) + m_error = error; + --m_active; + completed = ++m_completed; + done = (m_error || m_next >= m_depths.size()) && m_active == 0; + m_done = m_done || done; + } + if (done) + m_cv.notify_all(); + if (!error && m_progress) + m_progress(completed); + return true; + } + + void wait() + { + std::unique_lock lock(m_mutex); + m_cv.wait(lock, [this]() { return m_done; }); + if (m_error) + std::rethrow_exception(m_error); + } + +private: + std::mutex m_mutex; + std::condition_variable m_cv; + std::vector m_depths; + BuildFn m_build; + StoreFn m_store; + ProgressFn m_progress; + size_t m_next { 0 }; + int m_active { 0 }; + int m_completed { 0 }; + bool m_done { false }; + std::exception_ptr m_error; +}; + +class TopSurfaceImageContoningAnchoredSurfaceBuildState +{ +public: + void set_depth_work(std::shared_ptr work) + { + { + std::lock_guard lock(m_mutex); + if (!m_finished) + m_depth_work = std::move(work); + } + m_cv.notify_all(); + } + + void clear_depth_work(const std::shared_ptr &work) + { + { + std::lock_guard lock(m_mutex); + if (m_depth_work == work) + m_depth_work.reset(); + } + m_cv.notify_all(); + } + + bool help_one() + { + std::shared_ptr work; + { + std::unique_lock lock(m_mutex); + if (!m_finished && !m_depth_work) + m_cv.wait_for(lock, std::chrono::milliseconds(2), [this]() { + return m_finished || m_depth_work != nullptr; + }); + if (m_finished || !m_depth_work) + return false; + work = m_depth_work; + } + return work->run_one(); + } + + void finish() + { + { + std::lock_guard lock(m_mutex); + m_finished = true; + m_depth_work.reset(); + } + m_cv.notify_all(); + } + +private: + std::mutex m_mutex; + std::condition_variable m_cv; + std::shared_ptr m_depth_work; + bool m_finished { false }; +}; + struct TopSurfaceImageContoningSourceContext { TextureMappingOffsetContext offset_context; std::vector stack_areas; @@ -1540,7 +1674,7 @@ public: const TopSurfaceImageContoningStackPlanKey &key, Builder &&builder) { - return get_or_build_impl(m_anchored_surface_plans, key, std::forward(builder)); + return get_or_build_anchored_surface_impl(key, std::forward(builder)); } private: @@ -1551,6 +1685,7 @@ private: std::shared_ptr plan; std::exception_ptr error; bool ready { false }; + std::shared_ptr anchored_build_state; }; template @@ -1605,6 +1740,79 @@ private: return entry->plan; } + template + std::shared_ptr get_or_build_anchored_surface_impl( + const TopSurfaceImageContoningStackPlanKey &key, + Builder &&builder) + { + using Plan = TopSurfaceImageContoningAnchoredSurfacePlan; + + std::shared_ptr> entry; + bool build = false; + { + std::lock_guard lock(m_mutex); + auto found = m_anchored_surface_plans.find(key); + if (found == m_anchored_surface_plans.end()) { + entry = std::make_shared>(); + entry->anchored_build_state = std::make_shared(); + m_anchored_surface_plans.emplace(key, entry); + build = true; + } else { + entry = found->second; + } + } + + if (build) { + std::shared_ptr plan; + std::exception_ptr error; + try { + plan = builder(entry->anchored_build_state.get()); + } catch (...) { + error = std::current_exception(); + } + { + std::lock_guard lock(entry->mutex); + entry->plan = plan; + entry->error = error; + entry->ready = true; + } + if (entry->anchored_build_state) + entry->anchored_build_state->finish(); + entry->cv.notify_all(); + if (error) { + std::lock_guard lock(m_mutex); + auto found = m_anchored_surface_plans.find(key); + if (found != m_anchored_surface_plans.end() && found->second == entry) + m_anchored_surface_plans.erase(found); + std::rethrow_exception(error); + } + return plan; + } + + for (;;) { + { + std::lock_guard lock(entry->mutex); + if (entry->ready) { + if (entry->error) + std::rethrow_exception(entry->error); + return entry->plan; + } + } + + if (entry->anchored_build_state && entry->anchored_build_state->help_one()) + continue; + + std::unique_lock lock(entry->mutex); + if (!entry->ready) + entry->cv.wait_for(lock, std::chrono::milliseconds(1)); + if (entry->ready) { + if (entry->error) + std::rethrow_exception(entry->error); + return entry->plan; + } + } + } + std::mutex m_mutex; std::map>> m_plans; std::map>> m_depth_region_plans; @@ -2799,6 +3007,7 @@ static void top_surface_image_contoning_solve_anchored_region( const TopSurfaceImageContoningSourceContext &source_context, const TextureMappingContoningSolver &solver, TopSurfaceImageSourceSurface source_surface, + TopSurfaceImageContoningAnchoredSurfaceBuildState *build_state, const ThrowIfCanceled *throw_if_canceled) { if (anchored_region.union_area.empty() || @@ -2985,6 +3194,37 @@ static void top_surface_image_contoning_solve_anchored_region( if (active_depths.size() <= 1) { const int depth = active_depths.front(); anchored_region.depth_regions[size_t(depth)] = build_depth_regions(depth); + } else if (build_state != nullptr) { + auto store_depth_regions = [&](int depth, std::vector &®ions) { + anchored_region.depth_regions[size_t(depth)] = std::move(regions); + }; + auto report_progress = [&](int completed) { + if (completed == int(active_depths.size()) || (completed & 3) == 0) + top_surface_image_contoning_report_anchored_progress(object, + source_layer, + source_surface, + L("processing"), + completed, + int(active_depths.size())); + }; + std::shared_ptr depth_work = + std::make_shared(active_depths, + build_depth_regions, + store_depth_regions, + report_progress); + build_state->set_depth_work(depth_work); + try { + tbb::parallel_for(tbb::blocked_range(0, active_depths.size(), 1), + [&](const tbb::blocked_range &range) { + for (size_t idx = range.begin(); idx != range.end(); ++idx) + depth_work->run_one(); + }); + depth_work->wait(); + } catch (...) { + build_state->clear_depth_work(depth_work); + throw; + } + build_state->clear_depth_work(depth_work); } else { std::atomic completed_depths { 0 }; tbb::parallel_for(tbb::blocked_range(0, active_depths.size(), 1), @@ -3019,6 +3259,7 @@ static std::shared_ptr top_surface_ const PrintConfig &print_config, const TextureMappingContoningSolver &solver, TopSurfaceImageSourceSurface source_surface, + TopSurfaceImageContoningAnchoredSurfaceBuildState *build_state, const ThrowIfCanceled *throw_if_canceled) { std::shared_ptr out = @@ -3135,6 +3376,7 @@ static std::shared_ptr top_surface_ *source_context, solver, source_surface, + build_state, throw_if_canceled); const bool has_depth_regions = std::any_of(anchored_region.depth_regions.begin(), @@ -3209,7 +3451,7 @@ static std::shared_ptr top_su { const TopSurfaceImageContoningStackPlanKey key = top_surface_image_contoning_anchored_surface_plan_key(plan, source_layer, zone, solver, source_surface); - auto builder = [&]() { + auto builder = [&](TopSurfaceImageContoningAnchoredSurfaceBuildState *build_state) { return top_surface_image_contoning_build_anchored_surface_plan(plan, source_layer, object, @@ -3217,9 +3459,10 @@ static std::shared_ptr top_su print_config, solver, source_surface, + build_state, throw_if_canceled); }; - return cache != nullptr ? cache->get_or_build_anchored_surface(key, builder) : builder(); + return cache != nullptr ? cache->get_or_build_anchored_surface(key, builder) : builder(nullptr); } static std::vector top_surface_image_contoning_vector_regions_from_anchored_surface_plan( diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 1f04c938791..d1b2b46dcf4 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6507,8 +6507,9 @@ static std::array generic_solver_oklab_axis_weights_for_gcode(const st static std::array generic_solver_perceptual_axis_weights_for_gcode(const std::array &target_oklab, int generic_solver_mode) { + const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); std::array weights = generic_solver_oklab_axis_weights_for_gcode(target_oklab); - if (generic_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) { + if (effective_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) { weights[0] = std::max(weights[0], 1.f); weights[1] = std::min(weights[1], 4.f); weights[2] = std::min(weights[2], 4.f); @@ -6518,7 +6519,7 @@ static std::array generic_solver_perceptual_axis_weights_for_gcode(con static bool generic_solver_mode_is_perceptual_for_gcode(int generic_solver_mode) { - return generic_solver_mode != int(TextureMappingZone::GenericSolverRGB); + return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) != int(TextureMappingZone::GenericSolverRGB); } static std::string generic_mix_candidate_cache_key_for_gcode(const std::vector> &component_colors) @@ -6753,7 +6754,7 @@ static float generic_mix_candidate_perceptual_error_for_gcode(const GCodeGeneric const float da = candidates.perceptual_coords[coord_idx + 1] - target_oklab[1]; const float db = candidates.perceptual_coords[coord_idx + 2] - target_oklab[2]; float error = axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db; - if (generic_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) { + if (TextureMappingZone::effective_generic_solver_mode(generic_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_for_gcode(target_oklab) * under_l * under_l; } @@ -6839,9 +6840,7 @@ static std::vector best_component_mix_weights_for_target_for_gcode(const return {}; const size_t candidate_count = candidates.rgbs.size() / 3; - const int clamped_solver_mode = std::clamp(generic_solver_mode, - int(TextureMappingZone::GenericSolverRGB), - int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)); + const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); GCodeGenericMixNearestResult nearest = generic_solver_mode_is_perceptual_for_gcode(clamped_solver_mode) ? nearest_generic_mix_candidates_perceptual_for_gcode(candidates, target_rgb, clamped_solver_mode) : @@ -6923,6 +6922,39 @@ struct GCodeBinaryDitherCandidate { std::array oklab { { 1.f, 0.f, 0.f } }; }; +static const std::array &binary_dither_candidate_color_for_gcode(const GCodeBinaryDitherCandidate &candidate, + int generic_solver_mode) +{ + return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ? + candidate.rgb : + candidate.oklab; +} + +static std::array binary_dither_axis_weights_for_gcode(const std::array &target_color, + int generic_solver_mode) +{ + return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ? + std::array{ { 1.f, 1.f, 1.f } } : + generic_solver_perceptual_axis_weights_for_gcode(target_color, generic_solver_mode); +} + +static float binary_dither_candidate_error_for_gcode(const GCodeBinaryDitherCandidate &candidate, + const std::array &target_color, + const std::array &axis_weights, + int generic_solver_mode) +{ + const std::array &candidate_color = binary_dither_candidate_color_for_gcode(candidate, generic_solver_mode); + const float d0 = candidate_color[0] - target_color[0]; + const float d1 = candidate_color[1] - target_color[1]; + const float d2 = candidate_color[2] - target_color[2]; + float error = axis_weights[0] * d0 * d0 + axis_weights[1] * d1 * d1 + axis_weights[2] * d2 * d2; + if (TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) { + const float under_l = std::max(0.f, target_color[0] - candidate_color[0] - 0.04f); + error += 4.f * generic_solver_oklab_chroma_factor_for_gcode(target_color) * under_l * under_l; + } + return error; +} + static std::vector binary_dither_candidates_for_gcode( const std::vector> &component_colors, const std::vector &component_strength_factors, @@ -6976,16 +7008,17 @@ struct GCodeBinaryDitherNearestResult { static GCodeBinaryDitherNearestResult nearest_binary_dither_candidates_for_gcode( const std::vector &candidates, - const std::array &target_oklab) + const std::array &target_color, + int generic_solver_mode) { GCodeBinaryDitherNearestResult result; - const std::array axis_weights = generic_solver_oklab_axis_weights_for_gcode(target_oklab); + const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); + const std::array axis_weights = binary_dither_axis_weights_for_gcode(target_color, clamped_solver_mode); for (size_t idx = 0; idx < candidates.size(); ++idx) { - const std::array &candidate = candidates[idx].oklab; - const float dl = candidate[0] - target_oklab[0]; - const float da = candidate[1] - target_oklab[1]; - const float db = candidate[2] - target_oklab[2]; - const float error = axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db; + const float error = binary_dither_candidate_error_for_gcode(candidates[idx], + target_color, + axis_weights, + clamped_solver_mode); if (error < result.best_error) { result.second_error = result.best_error; result.second_idx = result.best_idx; @@ -7000,27 +7033,30 @@ static GCodeBinaryDitherNearestResult nearest_binary_dither_candidates_for_gcode } static size_t nearest_binary_dither_candidate_for_gcode(const std::vector &candidates, - const std::array &target_oklab) + const std::array &target_color, + int generic_solver_mode) { - return nearest_binary_dither_candidates_for_gcode(candidates, target_oklab).best_idx; + return nearest_binary_dither_candidates_for_gcode(candidates, target_color, generic_solver_mode).best_idx; } static float binary_dither_alternate_fraction_for_gcode(const std::vector &candidates, - const std::array &target_oklab, + const std::array &target_color, size_t base_idx, - size_t alternate_idx) + size_t alternate_idx, + int generic_solver_mode) { if (base_idx >= candidates.size() || alternate_idx >= candidates.size() || base_idx == alternate_idx) return 0.f; - const std::array axis_weights = generic_solver_oklab_axis_weights_for_gcode(target_oklab); - const std::array &base = candidates[base_idx].oklab; - const std::array &alternate = candidates[alternate_idx].oklab; + const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); + const std::array axis_weights = binary_dither_axis_weights_for_gcode(target_color, clamped_solver_mode); + const std::array &base = binary_dither_candidate_color_for_gcode(candidates[base_idx], clamped_solver_mode); + const std::array &alternate = binary_dither_candidate_color_for_gcode(candidates[alternate_idx], clamped_solver_mode); float numerator = 0.f; float denominator = 0.f; for (size_t axis = 0; axis < 3; ++axis) { const float delta = alternate[axis] - base[axis]; - numerator += axis_weights[axis] * (target_oklab[axis] - base[axis]) * delta; + numerator += axis_weights[axis] * (target_color[axis] - base[axis]) * delta; denominator += axis_weights[axis] * delta * delta; } if (!std::isfinite(numerator) || !std::isfinite(denominator) || denominator <= 1e-12f) @@ -7029,17 +7065,19 @@ static float binary_dither_alternate_fraction_for_gcode(const std::vector &candidates, - const std::array &target_oklab, - float threshold) + const std::array &target_color, + float threshold, + int generic_solver_mode) { - const GCodeBinaryDitherNearestResult nearest = nearest_binary_dither_candidates_for_gcode(candidates, target_oklab); + const GCodeBinaryDitherNearestResult nearest = + nearest_binary_dither_candidates_for_gcode(candidates, target_color, generic_solver_mode); if (nearest.best_idx >= candidates.size()) return size_t(-1); if (nearest.second_idx >= candidates.size()) return nearest.best_idx; const float alternate_fraction = - binary_dither_alternate_fraction_for_gcode(candidates, target_oklab, nearest.best_idx, nearest.second_idx); + binary_dither_alternate_fraction_for_gcode(candidates, target_color, nearest.best_idx, nearest.second_idx, generic_solver_mode); return std::clamp(threshold, 0.f, 1.f) < alternate_fraction ? nearest.second_idx : nearest.best_idx; } @@ -8246,6 +8284,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( (void) halftone_dot_size_mm; if (component_colors.empty()) return weight_field; + const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); const size_t component_count = component_colors.size(); const ModelObject *model_object = print_object.model_object(); @@ -8787,11 +8826,11 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( int x { 0 }; int y { 0 }; float weight { 0.f }; - std::array target_oklab { { 0.f, 0.f, 0.f } }; + std::array target_color { { 0.f, 0.f, 0.f } }; std::vector sample_indices; }; - auto sample_target_oklab = [tone_gamma, contrast_factor](const WeightedTextureSample &sample) { + auto sample_target_color = [tone_gamma, contrast_factor, effective_solver_mode](const WeightedTextureSample &sample) { std::array target = { clamp01f_for_gcode(sample.rgba[0]), clamp01f_for_gcode(sample.rgba[1]), @@ -8803,7 +8842,9 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( target[2] = apply_texture_tone_gamma_for_gcode(target[2], tone_gamma); } target = apply_texture_contrast_to_rgb_for_gcode(target, contrast_factor); - return oklab_from_srgb_for_gcode(target); + return effective_solver_mode == int(TextureMappingZone::GenericSolverRGB) ? + target : + oklab_from_srgb_for_gcode(target); }; std::vector cells; @@ -8826,10 +8867,10 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( } BinaryDitherCell &cell = cells[cell_it->second]; - const std::array target_oklab = sample_target_oklab(sample); + const std::array target_color = sample_target_color(sample); const float sample_weight = std::max(sample.weight, 0.f); for (size_t axis = 0; axis < 3; ++axis) - cell.target_oklab[axis] += target_oklab[axis] * sample_weight; + cell.target_color[axis] += target_color[axis] * sample_weight; cell.weight += sample_weight; cell.sample_indices.emplace_back(sample_idx); } @@ -8837,7 +8878,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( if (!cells.empty()) { for (BinaryDitherCell &cell : cells) if (cell.weight > EPSILON) - for (float &value : cell.target_oklab) + for (float &value : cell.target_color) value /= cell.weight; std::vector order(cells.size(), 0); @@ -8852,12 +8893,12 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( std::map, std::array> floyd_error; for (const size_t cell_idx : order) { BinaryDitherCell &cell = cells[cell_idx]; - std::array target_oklab = cell.target_oklab; + std::array target_color = cell.target_color; if (clamped_binary_dither_method == int(TextureMappingZone::DitheringFloydSteinberg)) { const auto error_it = floyd_error.find({cell.x, cell.y}); if (error_it != floyd_error.end()) for (size_t axis = 0; axis < 3; ++axis) - target_oklab[axis] += error_it->second[axis]; + target_color[axis] += error_it->second[axis]; } bool thresholded_dither = false; @@ -8869,8 +8910,8 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( const size_t candidate_idx = thresholded_dither ? - thresholded_binary_dither_candidate_for_gcode(binary_candidates, target_oklab, threshold) : - nearest_binary_dither_candidate_for_gcode(binary_candidates, target_oklab); + thresholded_binary_dither_candidate_for_gcode(binary_candidates, target_color, threshold, effective_solver_mode) : + nearest_binary_dither_candidate_for_gcode(binary_candidates, target_color, effective_solver_mode); if (candidate_idx >= binary_candidates.size()) continue; @@ -8880,10 +8921,12 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( binary_dither_masks[sample_idx] = candidate.mask; if (clamped_binary_dither_method == int(TextureMappingZone::DitheringFloydSteinberg)) { + const std::array &candidate_color = + binary_dither_candidate_color_for_gcode(candidate, effective_solver_mode); std::array error = { - target_oklab[0] - candidate.oklab[0], - target_oklab[1] - candidate.oklab[1], - target_oklab[2] - candidate.oklab[2] + target_color[0] - candidate_color[0], + target_color[1] - candidate_color[1], + target_color[2] - candidate_color[2] }; auto add_error = [&floyd_error, &cell_index_by_coord, &error](int x, int y, float factor) { if (cell_index_by_coord.find({x, y}) == cell_index_by_coord.end()) @@ -9008,7 +9051,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( best_component_mix_weights_for_target_for_gcode(*generic_mix_candidates, target, generic_solver_lookup_mode, - generic_solver_mode) : + effective_solver_mode) : std::vector{}; if (best.size() == component_count) desired = std::move(best); @@ -9472,9 +9515,7 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const const int generic_solver_lookup_mode = std::clamp(zone->generic_solver_lookup_mode, int(TextureMappingZone::GenericSolverClosestMix), int(TextureMappingZone::GenericSolverBlendClosestTwo)); - const int generic_solver_mode = std::clamp(zone->generic_solver_mode, - int(TextureMappingZone::GenericSolverRGB), - int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)); + const int generic_solver_mode = TextureMappingZone::effective_generic_solver_mode(zone->generic_solver_mode); const int generic_solver_mix_model = std::clamp(zone->generic_solver_mix_model, int(TextureMappingZone::GenericSolverPigmentPainter), int(TextureMappingZone::GenericSolverPrusaFdmMixer)); diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 68d00585e6c..93a35695a1d 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -405,7 +405,7 @@ private: return solve_color_solver_weights_for_target(candidates, target, color_solver_lookup_mode_from_index(generic_solver_lookup_mode), - color_solver_mode_from_index(generic_solver_mode)); + color_solver_mode_from_index(TextureMappingZone::effective_generic_solver_mode(generic_solver_mode))); } std::vector generic_solver_tool_candidates(const std::vector *preferred_tools = nullptr) const diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index f5dcfb20ffa..44a5d4c1f54 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -798,6 +798,8 @@ static int generic_solver_lookup_mode_from_name(const std::string &name) static std::string generic_solver_mode_name(int mode) { + if (mode == int(TextureMappingZone::GenericSolverDefault)) + return "default"; switch (clamp_int(mode, int(TextureMappingZone::GenericSolverRGB), int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4))) { @@ -816,16 +818,13 @@ static int generic_solver_mode_from_name(std::string name) const char lowered = char(std::tolower(c)); return lowered == '-' || lowered == ' ' ? '_' : lowered; }); - if (name == "rgb" || name == "legacy" || name == "v1") + if (name == "rgb") return int(TextureMappingZone::GenericSolverRGB); if (name == "oklab_soft_cap4_dark4" || name == "oklab_soft_cap" || - name == "oklab_soft" || - name == "v2_soft_cap4_dark4" || - name == "v2_soft_cap" || - name == "v2_soft") + name == "oklab_soft") return int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4); - if (name == "oklab" || name == "v2") + if (name == "oklab") return int(TextureMappingZone::GenericSolverOklab); return TextureMappingZone::DefaultGenericSolverMode; } @@ -1931,13 +1930,8 @@ void TextureMappingManager::load_entries(const std::string &serialized, 100.f); zone.generic_solver_lookup_mode = generic_solver_lookup_mode_from_name(texture.value("generic_solver_lookup", std::string("closest_mix"))); - const auto generic_solver_mode_it = texture.find("generic_solver_mode"); zone.generic_solver_mode = - generic_solver_mode_it != texture.end() && generic_solver_mode_it->is_string() ? - generic_solver_mode_from_name(generic_solver_mode_it->get()) : - (zone.filament_color_mode == int(TextureMappingZone::FilamentColorAny) ? - int(TextureMappingZone::GenericSolverRGB) : - int(TextureMappingZone::GenericSolverOklab)); + generic_solver_mode_from_name(texture.value("generic_solver_mode", std::string("default"))); zone.generic_solver_mix_model = generic_solver_mix_model_from_name(texture.value("generic_solver_mix_model", std::string("pigment_painter"))); zone.dithering_enabled = texture.value("dithering_enabled", TextureMappingZone::DefaultDitheringEnabled); diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp index 3653c1aba3c..a8ff25b836c 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -110,7 +110,8 @@ struct TextureMappingZone enum GenericSolverMode : uint8_t { GenericSolverRGB = 0, GenericSolverOklab = 1, - GenericSolverOklabSoftCap4Dark4 = 2 + GenericSolverOklabSoftCap4Dark4 = 2, + GenericSolverDefault = 255 }; enum GenericSolverMixModel : uint8_t { @@ -201,7 +202,7 @@ struct TextureMappingZone static constexpr bool DefaultTopSurfaceContoningSupersampledCellsEnabled = false; static constexpr bool DefaultTopSurfaceContoningPolygonizeColorRegionsEnabled = true; static constexpr int DefaultTopSurfaceContoningPolygonizeResolution = 4; - static constexpr bool DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled = false; + static constexpr bool DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled = true; static constexpr bool DefaultTopSurfaceContoningSurfaceAnchoredStackOptimizationsEnabled = true; static constexpr bool DefaultTopSurfaceContoningTdAdjustmentEnabled = true; static constexpr bool DefaultTopSurfaceContoningSurfaceScatterEnabled = false; @@ -214,7 +215,8 @@ struct TextureMappingZone static constexpr bool DefaultMinimumVisibilityOffsetEnabled = true; static constexpr float DefaultMinimumVisibilityOffsetPct = 30.f; static constexpr int DefaultGenericSolverLookupMode = int(GenericSolverClosestMix); - static constexpr int DefaultGenericSolverMode = int(GenericSolverOklabSoftCap4Dark4); + static constexpr int SlicerDefaultGenericSolverMode = int(GenericSolverOklabSoftCap4Dark4); + static constexpr int DefaultGenericSolverMode = int(GenericSolverDefault); static constexpr int DefaultGenericSolverMixModel = int(GenericSolverPigmentPainter); static constexpr bool DefaultDitheringEnabled = false; static constexpr int DefaultDitheringMethod = int(DitheringHalftoneV2); @@ -243,6 +245,13 @@ struct TextureMappingZone } } + static int effective_generic_solver_mode(int mode) + { + if (mode == int(GenericSolverDefault)) + return SlicerDefaultGenericSolverMode; + return std::clamp(mode, int(GenericSolverRGB), int(GenericSolverOklabSoftCap4Dark4)); + } + static int effective_top_surface_contoning_flat_surface_infill_mode(int mode) { if (!ShowExperimentalTopSurfaceContoningOptions) @@ -284,7 +293,7 @@ struct TextureMappingZone static bool effective_top_surface_contoning_surface_anchored_stacks_enabled(bool value) { - return ShowExperimentalTopSurfaceContoningOptions && value; + return ShowExperimentalTopSurfaceContoningOptions ? value : true; } static bool effective_top_surface_contoning_surface_anchored_stack_optimizations_enabled(bool value) @@ -578,7 +587,7 @@ struct TextureMappingZone top_surface_contoning_recolor_surrounding_perimeters = false; top_surface_contoning_perimeter_mode = DefaultTopSurfaceContoningPerimeterMode; top_surface_contoning_flat_surface_infill_mode = DefaultTopSurfaceContoningFlatSurfaceInfillMode; - top_surface_contoning_surface_anchored_stacks_enabled = false; + top_surface_contoning_surface_anchored_stacks_enabled = true; top_surface_contoning_surface_anchored_stack_optimizations_enabled = true; top_surface_contoning_beam_search_stack_expansion_enabled = true; top_surface_contoning_layer_phase_enabled = false; diff --git a/src/libslic3r/TextureMappingOffset.cpp b/src/libslic3r/TextureMappingOffset.cpp index 8d007713f92..6f590b23d23 100644 --- a/src/libslic3r/TextureMappingOffset.cpp +++ b/src/libslic3r/TextureMappingOffset.cpp @@ -933,12 +933,64 @@ std::array generic_solver_oklab_axis_weights(const std::array &target_oklab) +{ + const float chroma = std::hypot(target_oklab[1], target_oklab[2]); + return std::clamp((chroma - 0.015f) / 0.13f, 0.f, 1.f); +} + +std::array generic_solver_perceptual_axis_weights(const std::array &target_oklab, + int generic_solver_mode) +{ + const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); + std::array weights = generic_solver_oklab_axis_weights(target_oklab); + if (effective_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) { + weights[0] = std::max(weights[0], 1.f); + weights[1] = std::min(weights[1], 4.f); + weights[2] = std::min(weights[2], 4.f); + } + return weights; +} + struct TextureMappingBinaryDitherCandidate { uint32_t mask { 0 }; std::array rgb { { 1.f, 1.f, 1.f } }; std::array oklab { { 1.f, 0.f, 0.f } }; }; +const std::array &binary_dither_candidate_color(const TextureMappingBinaryDitherCandidate &candidate, + int generic_solver_mode) +{ + return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ? + candidate.rgb : + candidate.oklab; +} + +std::array binary_dither_axis_weights(const std::array &target_color, + int generic_solver_mode) +{ + return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ? + std::array{ { 1.f, 1.f, 1.f } } : + generic_solver_perceptual_axis_weights(target_color, generic_solver_mode); +} + +float binary_dither_candidate_error(const TextureMappingBinaryDitherCandidate &candidate, + const std::array &target_color, + const std::array &axis_weights, + int generic_solver_mode) +{ + const std::array &candidate_color = binary_dither_candidate_color(candidate, generic_solver_mode); + const float d0 = candidate_color[0] - target_color[0]; + const float d1 = candidate_color[1] - target_color[1]; + const float d2 = candidate_color[2] - target_color[2]; + float error = axis_weights[0] * d0 * d0 + axis_weights[1] * d1 * d1 + axis_weights[2] * d2 * d2; + if (TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) { + const float under_l = std::max(0.f, target_color[0] - candidate_color[0] - 0.04f); + error += 4.f * generic_solver_oklab_chroma_factor(target_color) * under_l * under_l; + } + return error; +} + std::vector binary_dither_candidates( const std::vector> &component_colors, const std::vector &component_strength_factors, @@ -992,16 +1044,17 @@ struct TextureMappingBinaryDitherNearestResult { TextureMappingBinaryDitherNearestResult nearest_binary_dither_candidates( const std::vector &candidates, - const std::array &target_oklab) + const std::array &target_color, + int generic_solver_mode) { TextureMappingBinaryDitherNearestResult result; - const std::array axis_weights = generic_solver_oklab_axis_weights(target_oklab); + const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); + const std::array axis_weights = binary_dither_axis_weights(target_color, clamped_solver_mode); for (size_t idx = 0; idx < candidates.size(); ++idx) { - const std::array &candidate = candidates[idx].oklab; - const float dl = candidate[0] - target_oklab[0]; - const float da = candidate[1] - target_oklab[1]; - const float db = candidate[2] - target_oklab[2]; - const float error = axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db; + const float error = binary_dither_candidate_error(candidates[idx], + target_color, + axis_weights, + clamped_solver_mode); if (error < result.best_error) { result.second_error = result.best_error; result.second_idx = result.best_idx; @@ -1016,27 +1069,30 @@ TextureMappingBinaryDitherNearestResult nearest_binary_dither_candidates( } size_t nearest_binary_dither_candidate(const std::vector &candidates, - const std::array &target_oklab) + const std::array &target_color, + int generic_solver_mode) { - return nearest_binary_dither_candidates(candidates, target_oklab).best_idx; + return nearest_binary_dither_candidates(candidates, target_color, generic_solver_mode).best_idx; } float binary_dither_alternate_fraction(const std::vector &candidates, - const std::array &target_oklab, + const std::array &target_color, size_t base_idx, - size_t alternate_idx) + size_t alternate_idx, + int generic_solver_mode) { if (base_idx >= candidates.size() || alternate_idx >= candidates.size() || base_idx == alternate_idx) return 0.f; - const std::array axis_weights = generic_solver_oklab_axis_weights(target_oklab); - const std::array &base = candidates[base_idx].oklab; - const std::array &alternate = candidates[alternate_idx].oklab; + const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); + const std::array axis_weights = binary_dither_axis_weights(target_color, clamped_solver_mode); + const std::array &base = binary_dither_candidate_color(candidates[base_idx], clamped_solver_mode); + const std::array &alternate = binary_dither_candidate_color(candidates[alternate_idx], clamped_solver_mode); float numerator = 0.f; float denominator = 0.f; for (size_t axis = 0; axis < 3; ++axis) { const float delta = alternate[axis] - base[axis]; - numerator += axis_weights[axis] * (target_oklab[axis] - base[axis]) * delta; + numerator += axis_weights[axis] * (target_color[axis] - base[axis]) * delta; denominator += axis_weights[axis] * delta * delta; } if (!std::isfinite(numerator) || !std::isfinite(denominator) || denominator <= 1e-12f) @@ -1045,17 +1101,19 @@ float binary_dither_alternate_fraction(const std::vector &candidates, - const std::array &target_oklab, - float threshold) + const std::array &target_color, + float threshold, + int generic_solver_mode) { - const TextureMappingBinaryDitherNearestResult nearest = nearest_binary_dither_candidates(candidates, target_oklab); + const TextureMappingBinaryDitherNearestResult nearest = + nearest_binary_dither_candidates(candidates, target_color, generic_solver_mode); if (nearest.best_idx >= candidates.size()) return size_t(-1); if (nearest.second_idx >= candidates.size()) return nearest.best_idx; const float alternate_fraction = - binary_dither_alternate_fraction(candidates, target_oklab, nearest.best_idx, nearest.second_idx); + binary_dither_alternate_fraction(candidates, target_color, nearest.best_idx, nearest.second_idx, generic_solver_mode); return std::clamp(threshold, 0.f, 1.f) < alternate_fraction ? nearest.second_idx : nearest.best_idx; } @@ -1437,6 +1495,7 @@ std::vector component_weights_for_sample(const WeightedTextureSample &sam std::vector desired(component_count, 0.f); size_t mapped_component_count = component_count; const bool has_raw_component_weights = sample.raw_component_weights.size() == component_count; + const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); if (has_raw_component_weights) { float raw_activity = 0.f; for (size_t component_idx = 0; component_idx < component_count; ++component_idx) @@ -1478,7 +1537,7 @@ std::vector component_weights_for_sample(const WeightedTextureSample &sam solve_color_solver_weights_for_target(*generic_mix_candidates, target, color_solver_lookup_mode_from_index(generic_solver_lookup_mode), - color_solver_mode_from_index(generic_solver_mode)); + color_solver_mode_from_index(effective_solver_mode)); if (best.size() == component_count) desired = std::move(best); } @@ -1520,6 +1579,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field( if (component_colors.empty()) return weight_field; + const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); const size_t component_count = component_colors.size(); const ModelObject *model_object = print_object.model_object(); if (model_object == nullptr) @@ -1845,12 +1905,15 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field( int x { 0 }; int y { 0 }; float weight { 0.f }; - std::array target_oklab { { 0.f, 0.f, 0.f } }; + std::array target_color { { 0.f, 0.f, 0.f } }; std::vector sample_indices; }; - auto sample_target_oklab = [tone_gamma, contrast_factor](const WeightedTextureSample &sample) { - return color_solver_oklab_from_srgb(texture_sample_target_rgb(sample, tone_gamma, contrast_factor)); + auto sample_target_color = [tone_gamma, contrast_factor, effective_solver_mode](const WeightedTextureSample &sample) { + const std::array target_rgb = texture_sample_target_rgb(sample, tone_gamma, contrast_factor); + return effective_solver_mode == int(TextureMappingZone::GenericSolverRGB) ? + target_rgb : + color_solver_oklab_from_srgb(target_rgb); }; std::vector cells; @@ -1873,10 +1936,10 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field( } BinaryDitherCell &cell = cells[cell_it->second]; - const std::array target_oklab = sample_target_oklab(sample); + const std::array target_color = sample_target_color(sample); const float sample_weight = std::max(sample.weight, 0.f); for (size_t axis = 0; axis < 3; ++axis) - cell.target_oklab[axis] += target_oklab[axis] * sample_weight; + cell.target_color[axis] += target_color[axis] * sample_weight; cell.weight += sample_weight; cell.sample_indices.emplace_back(sample_idx); } @@ -1884,7 +1947,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field( if (!cells.empty()) { for (BinaryDitherCell &cell : cells) if (cell.weight > EPSILON) - for (float &value : cell.target_oklab) + for (float &value : cell.target_color) value /= cell.weight; std::vector order(cells.size(), 0); @@ -1899,12 +1962,12 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field( std::map, std::array> floyd_error; for (const size_t cell_idx : order) { BinaryDitherCell &cell = cells[cell_idx]; - std::array target_oklab = cell.target_oklab; + std::array target_color = cell.target_color; if (clamped_binary_dither_method == int(TextureMappingZone::DitheringFloydSteinberg)) { const auto error_it = floyd_error.find({ cell.x, cell.y }); if (error_it != floyd_error.end()) for (size_t axis = 0; axis < 3; ++axis) - target_oklab[axis] += error_it->second[axis]; + target_color[axis] += error_it->second[axis]; } bool thresholded_dither = false; @@ -1916,8 +1979,8 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field( const size_t candidate_idx = thresholded_dither ? - thresholded_binary_dither_candidate(binary_candidates, target_oklab, threshold) : - nearest_binary_dither_candidate(binary_candidates, target_oklab); + thresholded_binary_dither_candidate(binary_candidates, target_color, threshold, effective_solver_mode) : + nearest_binary_dither_candidate(binary_candidates, target_color, effective_solver_mode); if (candidate_idx >= binary_candidates.size()) continue; @@ -1927,10 +1990,12 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field( binary_dither_masks[sample_idx] = candidate.mask; if (clamped_binary_dither_method == int(TextureMappingZone::DitheringFloydSteinberg)) { + const std::array &candidate_color = + binary_dither_candidate_color(candidate, effective_solver_mode); std::array error = { - target_oklab[0] - candidate.oklab[0], - target_oklab[1] - candidate.oklab[1], - target_oklab[2] - candidate.oklab[2] + target_color[0] - candidate_color[0], + target_color[1] - candidate_color[1], + target_color[2] - candidate_color[2] }; auto add_error = [&floyd_error, &cell_index_by_coord, &error](int x, int y, float factor) { if (cell_index_by_coord.find({ x, y }) == cell_index_by_coord.end()) @@ -2006,7 +2071,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field( filament_color_mode, force_sequential_filaments, generic_solver_lookup_mode, - generic_solver_mode, + effective_solver_mode, use_fixed_color_generic_solver, contrast_factor, tone_gamma, @@ -3080,9 +3145,7 @@ std::optional build_texture_mapping_offset_context_ const int generic_solver_lookup_mode = std::clamp(zone.generic_solver_lookup_mode, int(TextureMappingZone::GenericSolverClosestMix), int(TextureMappingZone::GenericSolverBlendClosestTwo)); - const int generic_solver_mode = std::clamp(zone.generic_solver_mode, - int(TextureMappingZone::GenericSolverRGB), - int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)); + const int generic_solver_mode = TextureMappingZone::effective_generic_solver_mode(zone.generic_solver_mode); const int generic_solver_mix_model = std::clamp(zone.generic_solver_mix_model, int(TextureMappingZone::GenericSolverPigmentPainter), int(TextureMappingZone::GenericSolverPrusaFdmMixer)); diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp index 8c54efeed7a..19248c12d98 100644 --- a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp +++ b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp @@ -677,8 +677,9 @@ float generic_solver_oklab_chroma_factor(const std::array &target_okla std::array generic_solver_perceptual_axis_weights(const std::array &target_oklab, int generic_solver_mode) { + const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); std::array weights = generic_solver_oklab_axis_weights(target_oklab); - if (generic_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) { + if (effective_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) { weights[0] = std::max(weights[0], 1.f); weights[1] = std::min(weights[1], 4.f); weights[2] = std::min(weights[2], 4.f); @@ -688,7 +689,7 @@ std::array generic_solver_perceptual_axis_weights(const std::array best_component_mix_weights_for_target(const std::vector oklab { { 1.f, 0.f, 0.f } }; }; +const std::array &binary_dither_candidate_color_for_texture_preview( + const TexturePreviewBinaryDitherCandidate &candidate, + int generic_solver_mode) +{ + return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ? + candidate.rgb : + candidate.oklab; +} + +std::array binary_dither_axis_weights_for_texture_preview(const std::array &target_color, + int generic_solver_mode) +{ + return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ? + std::array{ { 1.f, 1.f, 1.f } } : + generic_solver_perceptual_axis_weights(target_color, generic_solver_mode); +} + +float binary_dither_candidate_error_for_texture_preview(const TexturePreviewBinaryDitherCandidate &candidate, + const std::array &target_color, + const std::array &axis_weights, + int generic_solver_mode) +{ + const std::array &candidate_color = + binary_dither_candidate_color_for_texture_preview(candidate, generic_solver_mode); + const float d0 = candidate_color[0] - target_color[0]; + const float d1 = candidate_color[1] - target_color[1]; + const float d2 = candidate_color[2] - target_color[2]; + float error = axis_weights[0] * d0 * d0 + axis_weights[1] * d1 * d1 + axis_weights[2] * d2 * d2; + if (TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) { + const float under_l = std::max(0.f, target_color[0] - candidate_color[0] - 0.04f); + error += 4.f * generic_solver_oklab_chroma_factor(target_color) * under_l * under_l; + } + return error; +} + std::vector binary_component_visibility_weights_for_texture_preview(const TexturePreviewSimulationSettings &settings, uint32_t mask) { @@ -2003,10 +2037,13 @@ std::array texture_preview_target_rgb(const TexturePreviewSimulationSe return apply_texture_contrast_to_rgb(target, std::clamp(settings.contrast_pct, 25.f, 300.f) / 100.f); } -std::array texture_preview_target_oklab(const TexturePreviewSimulationSettings &settings, +std::array texture_preview_target_color(const TexturePreviewSimulationSettings &settings, const std::array &sample_rgba) { - return oklab_from_srgb(texture_preview_target_rgb(settings, sample_rgba)); + const std::array target_rgb = texture_preview_target_rgb(settings, sample_rgba); + return TextureMappingZone::effective_generic_solver_mode(settings.generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ? + target_rgb : + oklab_from_srgb(target_rgb); } struct TexturePreviewBinaryDitherNearestResult { @@ -2018,16 +2055,18 @@ struct TexturePreviewBinaryDitherNearestResult { TexturePreviewBinaryDitherNearestResult nearest_binary_dither_candidates_for_texture_preview( const std::vector &candidates, - const std::array &target_oklab) + const std::array &target_color, + int generic_solver_mode) { TexturePreviewBinaryDitherNearestResult result; - const std::array axis_weights = generic_solver_oklab_axis_weights(target_oklab); + const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); + const std::array axis_weights = + binary_dither_axis_weights_for_texture_preview(target_color, clamped_solver_mode); for (size_t idx = 0; idx < candidates.size(); ++idx) { - const std::array &candidate = candidates[idx].oklab; - const float dl = candidate[0] - target_oklab[0]; - const float da = candidate[1] - target_oklab[1]; - const float db = candidate[2] - target_oklab[2]; - const float error = axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db; + const float error = binary_dither_candidate_error_for_texture_preview(candidates[idx], + target_color, + axis_weights, + clamped_solver_mode); if (error < result.best_error) { result.second_error = result.best_error; result.second_idx = result.best_idx; @@ -2042,27 +2081,33 @@ TexturePreviewBinaryDitherNearestResult nearest_binary_dither_candidates_for_tex } size_t nearest_binary_dither_candidate_for_texture_preview(const std::vector &candidates, - const std::array &target_oklab) + const std::array &target_color, + int generic_solver_mode) { - return nearest_binary_dither_candidates_for_texture_preview(candidates, target_oklab).best_idx; + return nearest_binary_dither_candidates_for_texture_preview(candidates, target_color, generic_solver_mode).best_idx; } float binary_dither_alternate_fraction_for_texture_preview(const std::vector &candidates, - const std::array &target_oklab, + const std::array &target_color, size_t base_idx, - size_t alternate_idx) + size_t alternate_idx, + int generic_solver_mode) { if (base_idx >= candidates.size() || alternate_idx >= candidates.size() || base_idx == alternate_idx) return 0.f; - const std::array axis_weights = generic_solver_oklab_axis_weights(target_oklab); - const std::array &base = candidates[base_idx].oklab; - const std::array &alternate = candidates[alternate_idx].oklab; + const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); + const std::array axis_weights = + binary_dither_axis_weights_for_texture_preview(target_color, clamped_solver_mode); + const std::array &base = + binary_dither_candidate_color_for_texture_preview(candidates[base_idx], clamped_solver_mode); + const std::array &alternate = + binary_dither_candidate_color_for_texture_preview(candidates[alternate_idx], clamped_solver_mode); float numerator = 0.f; float denominator = 0.f; for (size_t axis = 0; axis < 3; ++axis) { const float delta = alternate[axis] - base[axis]; - numerator += axis_weights[axis] * (target_oklab[axis] - base[axis]) * delta; + numerator += axis_weights[axis] * (target_color[axis] - base[axis]) * delta; denominator += axis_weights[axis] * delta * delta; } if (!std::isfinite(numerator) || !std::isfinite(denominator) || denominator <= 1e-12f) @@ -2071,18 +2116,20 @@ float binary_dither_alternate_fraction_for_texture_preview(const std::vector &candidates, - const std::array &target_oklab, - float threshold) + const std::array &target_color, + float threshold, + int generic_solver_mode) { const TexturePreviewBinaryDitherNearestResult nearest = - nearest_binary_dither_candidates_for_texture_preview(candidates, target_oklab); + nearest_binary_dither_candidates_for_texture_preview(candidates, target_color, generic_solver_mode); if (nearest.best_idx >= candidates.size()) return size_t(-1); if (nearest.second_idx >= candidates.size()) return nearest.best_idx; const float alternate_fraction = - binary_dither_alternate_fraction_for_texture_preview(candidates, target_oklab, nearest.best_idx, nearest.second_idx); + binary_dither_alternate_fraction_for_texture_preview( + candidates, target_color, nearest.best_idx, nearest.second_idx, generic_solver_mode); return std::clamp(threshold, 0.f, 1.f) < alternate_fraction ? nearest.second_idx : nearest.best_idx; } @@ -2385,8 +2432,9 @@ std::vector component_weights_for_texture_preview(const TexturePreviewSim settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && !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_oklab = texture_preview_target_oklab(settings, sample_rgba); - const size_t candidate_idx = nearest_binary_dither_candidate_for_texture_preview(candidates, target_oklab); + const std::array target_color = texture_preview_target_color(settings, sample_rgba); + const size_t candidate_idx = + nearest_binary_dither_candidate_for_texture_preview(candidates, target_color, settings.generic_solver_mode); if (candidate_idx < candidates.size()) return binary_component_visibility_weights_for_texture_preview(settings, candidates[candidate_idx].mask); } @@ -2428,7 +2476,7 @@ std::vector component_weights_for_texture_preview(const TexturePreviewSim settings.generic_mix_candidate_cache->candidates, target, color_solver_lookup_mode_from_index(settings.generic_solver_lookup_mode), - color_solver_mode_from_index(settings.generic_solver_mode)) : + color_solver_mode_from_index(TextureMappingZone::effective_generic_solver_mode(settings.generic_solver_mode))) : std::vector{}; if (best.size() == component_count) desired = std::move(best); @@ -2698,9 +2746,7 @@ std::optional texture_preview_simulation_setti settings.generic_solver_lookup_mode = std::clamp(zone->generic_solver_lookup_mode, int(TextureMappingZone::GenericSolverClosestMix), int(TextureMappingZone::GenericSolverBlendClosestTwo)); - settings.generic_solver_mode = std::clamp(zone->generic_solver_mode, - int(TextureMappingZone::GenericSolverRGB), - int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)); + settings.generic_solver_mode = TextureMappingZone::effective_generic_solver_mode(zone->generic_solver_mode); settings.generic_solver_mix_model = std::clamp(zone->generic_solver_mix_model, int(TextureMappingZone::GenericSolverPigmentPainter), int(TextureMappingZone::GenericSolverPrusaFdmMixer)); @@ -3186,13 +3232,13 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig } component_weights = halftone_component_visibility_weights_for_texture_preview(settings, mask); } else if (use_binary_dithering && !binary_dither_candidates.empty()) { - std::array target_oklab = texture_preview_target_oklab(settings, sample_rgba); + std::array target_color = texture_preview_target_color(settings, sample_rgba); const int clamped_method = std::clamp(settings.dithering_method, int(TextureMappingZone::DitheringClosest), int(TextureMappingZone::DitheringHalftoneV2)); if (clamped_method == int(TextureMappingZone::DitheringFloydSteinberg)) { for (size_t axis = 0; axis < 3; ++axis) - target_oklab[axis] += floyd_error_current[x][axis]; + target_color[axis] += floyd_error_current[x][axis]; } bool thresholded_dither = false; @@ -3204,16 +3250,20 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig const size_t candidate_idx = thresholded_dither ? - thresholded_binary_dither_candidate_for_texture_preview(binary_dither_candidates, target_oklab, threshold) : - nearest_binary_dither_candidate_for_texture_preview(binary_dither_candidates, target_oklab); + thresholded_binary_dither_candidate_for_texture_preview( + binary_dither_candidates, target_color, threshold, settings.generic_solver_mode) : + nearest_binary_dither_candidate_for_texture_preview( + binary_dither_candidates, target_color, settings.generic_solver_mode); if (candidate_idx < binary_dither_candidates.size()) { const TexturePreviewBinaryDitherCandidate &candidate = binary_dither_candidates[candidate_idx]; component_weights = binary_component_visibility_weights_for_texture_preview(settings, candidate.mask); if (clamped_method == int(TextureMappingZone::DitheringFloydSteinberg)) { + const std::array &candidate_color = + binary_dither_candidate_color_for_texture_preview(candidate, settings.generic_solver_mode); const std::array error = { - target_oklab[0] - candidate.oklab[0], - target_oklab[1] - candidate.oklab[1], - target_oklab[2] - candidate.oklab[2] + target_color[0] - candidate_color[0], + target_color[1] - candidate_color[1], + target_color[2] - candidate_color[2] }; auto add_error = [&error](std::array &dst, float factor) { for (size_t axis = 0; axis < 3; ++axis) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 92e33e3c502..2d183a73a67 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2045,6 +2045,55 @@ static bool texture_mapping_prime_tower_images_equal(const TextureMappingPrimeTo lhs.rgba == rhs.rgba; } +static wxString texture_mapping_generic_solver_mode_label(int mode) +{ + switch (TextureMappingZone::effective_generic_solver_mode(mode)) { + case int(TextureMappingZone::GenericSolverRGB): + return _L("RGB"); + case int(TextureMappingZone::GenericSolverOklab): + return _L("Oklab"); + default: + return _L("Oklab soft cap dark correction"); + } +} + +static wxString texture_mapping_generic_solver_default_label() +{ + wxString label = _L("Default"); + label += " ("; + label += texture_mapping_generic_solver_mode_label(TextureMappingZone::SlicerDefaultGenericSolverMode); + label += ")"; + return label; +} + +static int texture_mapping_generic_solver_mode_choice_selection(int mode) +{ + if (mode == int(TextureMappingZone::GenericSolverDefault)) + return 0; + switch (TextureMappingZone::effective_generic_solver_mode(mode)) { + case int(TextureMappingZone::GenericSolverRGB): + return 1; + case int(TextureMappingZone::GenericSolverOklab): + return 2; + default: + return 3; + } +} + +static int texture_mapping_generic_solver_mode_from_choice_selection(int selection) +{ + switch (selection) { + case 1: + return int(TextureMappingZone::GenericSolverRGB); + case 2: + return int(TextureMappingZone::GenericSolverOklab); + case 3: + return int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4); + default: + return TextureMappingZone::DefaultGenericSolverMode; + } +} + class TextureMappingAdvancedOptionsDialog : public wxDialog { public: @@ -2538,13 +2587,12 @@ public: auto *generic_solver_mode_row = new wxBoxSizer(wxHORIZONTAL); generic_solver_mode_row->Add(new wxStaticText(experimental_page, wxID_ANY, _L("Generic solver")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); wxArrayString generic_solver_mode_choices; - generic_solver_mode_choices.Add(_L("RGB")); - generic_solver_mode_choices.Add(_L("Oklab")); - generic_solver_mode_choices.Add(_L("Oklab soft cap dark correction")); + generic_solver_mode_choices.Add(texture_mapping_generic_solver_default_label()); + generic_solver_mode_choices.Add(texture_mapping_generic_solver_mode_label(int(TextureMappingZone::GenericSolverRGB))); + generic_solver_mode_choices.Add(texture_mapping_generic_solver_mode_label(int(TextureMappingZone::GenericSolverOklab))); + generic_solver_mode_choices.Add(texture_mapping_generic_solver_mode_label(int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4))); m_generic_solver_mode_choice = new wxChoice(experimental_page, wxID_ANY, wxDefaultPosition, wxDefaultSize, generic_solver_mode_choices); - m_generic_solver_mode_choice->SetSelection(std::clamp(generic_solver_mode, - int(TextureMappingZone::GenericSolverRGB), - int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4))); + m_generic_solver_mode_choice->SetSelection(texture_mapping_generic_solver_mode_choice_selection(generic_solver_mode)); generic_solver_mode_row->Add(m_generic_solver_mode_choice, 1, wxALIGN_CENTER_VERTICAL); experimental_box->Add(generic_solver_mode_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); @@ -3535,7 +3583,7 @@ public: bool top_surface_contoning_surface_anchored_stacks_enabled() const { if (!TextureMappingZone::ShowExperimentalTopSurfaceContoningOptions) - return false; + return true; return m_top_surface_contoning_surface_anchored_stacks_checkbox == nullptr ? TextureMappingZone::DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled : m_top_surface_contoning_surface_anchored_stacks_checkbox->GetValue(); @@ -3607,9 +3655,7 @@ public: int generic_solver_mode() const { return m_generic_solver_mode_choice ? - std::clamp(m_generic_solver_mode_choice->GetSelection(), - int(TextureMappingZone::GenericSolverRGB), - int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) : + texture_mapping_generic_solver_mode_from_choice_selection(m_generic_solver_mode_choice->GetSelection()) : TextureMappingZone::DefaultGenericSolverMode; }