From bd28850f7056c3bd8a29cb15a1eb29685eac642f Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Sun, 24 May 2026 11:46:34 +0100 Subject: [PATCH] Make sliced result when using halftone dithering match 3d preview better --- src/libslic3r/GCode.cpp | 11 +- src/libslic3r/TextureMapping.cpp | 5 +- src/libslic3r/TextureMapping.hpp | 5 +- src/libslic3r/TextureMappingOffset.cpp | 111 +++++++++++++++++--- src/libslic3r/TextureMappingOffset.hpp | 1 + src/slic3r/GUI/MMUPaintedTexturePreview.cpp | 34 +++--- src/slic3r/GUI/Plater.cpp | 10 +- 7 files changed, 133 insertions(+), 44 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 95de9dd1459..5b62cdaa2a3 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -7027,9 +7027,10 @@ static bool is_halftone_dithering_method_for_gcode(int method) { const int clamped_method = std::clamp(method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + int(TextureMappingZone::DitheringHalftoneV2)); return clamped_method == int(TextureMappingZone::DitheringHalftone) || - clamped_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail); + clamped_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail) || + clamped_method == int(TextureMappingZone::DitheringHalftoneV2); } static float dither_pitch_for_gcode(float base_outer_width_mm, @@ -7040,7 +7041,7 @@ static float dither_pitch_for_gcode(float base_outer_width_mm, const float high_res_step_mm = std::clamp(base_outer_width_mm * 0.20f, 0.04f, 0.12f); const int clamped_method = std::clamp(dithering_method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + int(TextureMappingZone::DitheringHalftoneV2)); if (is_halftone_dithering_method_for_gcode(clamped_method)) { const float dot_sample_step_mm = std::clamp(std::clamp(halftone_dot_size_mm, @@ -8719,7 +8720,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( const int clamped_binary_dither_method = std::clamp(dithering_method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + int(TextureMappingZone::DitheringHalftoneV2)); std::vector binary_dither_masks; const bool can_binary_dither = dithering_enabled && @@ -9443,7 +9444,7 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const zone->texture_mapping_mode != int(TextureMappingZone::TextureMappingRawValues); const int dithering_method = std::clamp(zone->dithering_method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + int(TextureMappingZone::DitheringHalftoneV2)); const bool halftone_dithering_enabled = dithering_enabled && is_halftone_dithering_method_for_gcode(dithering_method); const float seam_texture_base_width_mm = diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index 5364cbb9bc4..7be48252b44 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -759,11 +759,12 @@ static std::string dithering_method_name(int mode) { switch (clamp_int(mode, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail))) { + int(TextureMappingZone::DitheringHalftoneV2))) { case int(TextureMappingZone::DitheringClosest): return "closest"; case int(TextureMappingZone::DitheringOrderedBayer): return "ordered_bayer"; case int(TextureMappingZone::DitheringHalftone): return "halftone"; case int(TextureMappingZone::DitheringHalftoneIncreasedDetail): return "halftone_increased_detail"; + case int(TextureMappingZone::DitheringHalftoneV2): return "halftone_v2"; default: return "floyd_steinberg"; } } @@ -779,6 +780,8 @@ static int dithering_method_from_name(std::string name) return int(TextureMappingZone::DitheringHalftone); if (name == "halftone_increased_detail" || name == "halftone_detail" || name == "halftone_high_detail") return int(TextureMappingZone::DitheringHalftoneIncreasedDetail); + if (name == "halftone_v2" || name == "halftone2") + return int(TextureMappingZone::DitheringHalftoneV2); return int(TextureMappingZone::DitheringFloydSteinberg); } diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp index 9d8593ad001..45e2fb4e7a1 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -97,7 +97,8 @@ struct TextureMappingZone DitheringFloydSteinberg = 1, DitheringOrderedBayer = 2, DitheringHalftone = 3, - DitheringHalftoneIncreasedDetail = 4 + DitheringHalftoneIncreasedDetail = 4, + DitheringHalftoneV2 = 5 }; enum TransmissionDistanceCalibrationMode : uint8_t { @@ -142,7 +143,7 @@ struct TextureMappingZone static constexpr int DefaultGenericSolverMode = int(GenericSolverV2); static constexpr int DefaultGenericSolverMixModel = int(GenericSolverPigmentPainter); static constexpr bool DefaultDitheringEnabled = false; - static constexpr int DefaultDitheringMethod = int(DitheringHalftone); + static constexpr int DefaultDitheringMethod = int(DitheringHalftoneV2); static constexpr float MinDitheringResolutionMm = 0.04f; static constexpr float MaxDitheringResolutionMm = 10.f; static constexpr float DefaultDitheringResolutionMm = 0.4f; diff --git a/src/libslic3r/TextureMappingOffset.cpp b/src/libslic3r/TextureMappingOffset.cpp index 0947ce6e427..904be4aa3c1 100644 --- a/src/libslic3r/TextureMappingOffset.cpp +++ b/src/libslic3r/TextureMappingOffset.cpp @@ -1078,9 +1078,10 @@ bool is_halftone_dithering_method(int method) { const int clamped_method = std::clamp(method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + int(TextureMappingZone::DitheringHalftoneV2)); return clamped_method == int(TextureMappingZone::DitheringHalftone) || - clamped_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail); + clamped_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail) || + clamped_method == int(TextureMappingZone::DitheringHalftoneV2); } float halftone_threshold(float x_mm, float y_mm, float dot_size_mm) @@ -1134,6 +1135,65 @@ float halftone_screen_angle_deg(int filament_color_mode, size_t component_idx) return fallback_angles[component_idx % (sizeof(fallback_angles) / sizeof(fallback_angles[0]))]; } +int halftone_surface_axis(float normal_x, float normal_y, float normal_z) +{ + const float ax = std::abs(normal_x); + const float ay = std::abs(normal_y); + const float az = std::abs(normal_z); + if (ax >= ay && ax >= az) + return 0; + if (ay >= ax && ay >= az) + return 1; + return 2; +} + +std::array halftone_surface_coords(float x_mm, float y_mm, float z_mm, int axis) +{ + if (axis == 0) + return { y_mm, z_mm }; + if (axis == 1) + return { x_mm, z_mm }; + return { x_mm, y_mm }; +} + +std::array halftone_cell_center_surface_coords(float surface_u_mm, + float surface_v_mm, + float dot_size_mm, + float angle_deg) +{ + const float period = std::clamp(dot_size_mm, + TextureMappingZone::MinHalftoneDotSizeMm, + TextureMappingZone::MaxHalftoneDotSizeMm); + if (!std::isfinite(surface_u_mm) || !std::isfinite(surface_v_mm) || period <= EPSILON) + return { surface_u_mm, surface_v_mm }; + + const float radians = angle_deg * float(PI) / 180.f; + const float c = std::cos(radians); + const float s = std::sin(radians); + const float screen_x = c * surface_u_mm + s * surface_v_mm; + const float screen_y = -s * surface_u_mm + c * surface_v_mm; + const int cell_x = int(std::floor(screen_x / period)); + const int cell_y = int(std::floor(screen_y / period)); + const float center_screen_x = (float(cell_x) + 0.5f) * period; + const float center_screen_y = (float(cell_y) + 0.5f) * period; + return { + c * center_screen_x - s * center_screen_y, + s * center_screen_x + c * center_screen_y + }; +} + +std::array halftone_sample_xy_from_surface_coords(float x_mm, + float y_mm, + const std::array &surface_coords, + int axis) +{ + if (axis == 0) + return { x_mm, surface_coords[0] }; + if (axis == 1) + return { surface_coords[0], y_mm }; + return surface_coords; +} + bool halftone_screen_on(float coverage, float surface_u_mm, float surface_v_mm, @@ -1164,7 +1224,7 @@ float dither_pitch(float base_outer_width_mm, const float high_res_step_mm = std::clamp(base_outer_width_mm * 0.20f, 0.04f, 0.12f); const int clamped_method = std::clamp(dithering_method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + int(TextureMappingZone::DitheringHalftoneV2)); if (is_halftone_dithering_method(clamped_method)) { const float dot_sample_step_mm = std::clamp(std::clamp(halftone_dot_size_mm, @@ -1742,7 +1802,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field( const int clamped_binary_dither_method = std::clamp(dithering_method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + int(TextureMappingZone::DitheringHalftoneV2)); std::vector binary_dither_masks; const bool can_binary_dither = dithering_enabled && @@ -2907,11 +2967,13 @@ std::optional build_texture_mapping_offset_context_ const bool dithering_enabled = zone.dithering_enabled && !raw_texture_mapping_mode; const int dithering_method = std::clamp(zone.dithering_method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + int(TextureMappingZone::DitheringHalftoneV2)); const bool halftone_dithering_enabled = dithering_enabled && is_halftone_dithering_method(dithering_method); const bool halftone_increased_detail_enabled = dithering_enabled && dithering_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail); + const bool halftone_v2_enabled = + dithering_enabled && dithering_method == int(TextureMappingZone::DitheringHalftoneV2); const float dither_pitch_mm = dither_pitch(base_outer_width_mm, dithering_method, @@ -3010,6 +3072,7 @@ std::optional build_texture_mapping_offset_context_ context.dithering_enabled = dithering_enabled; context.halftone_dithering_enabled = halftone_dithering_enabled; context.halftone_increased_detail_enabled = halftone_increased_detail_enabled; + context.halftone_v2_enabled = halftone_v2_enabled; context.nonlinear_offset_adjustment = zone.nonlinear_offset_adjustment; context.object_center = print_object.bounding_box().center(); if (linear_gradient_mode) { @@ -3096,13 +3159,37 @@ float texture_mapping_offset_surface_inset_mm(const TextureMappingOffsetContext float z_mm = context.layer != nullptr ? float(context.layer->print_z) : 0.f; if (!std::isfinite(z_mm)) z_mm = 0.f; - if (!std::isfinite(surface_u_mm)) - surface_u_mm = std::abs(outward_x) >= std::abs(outward_y) ? y_mm : x_mm; - if (!std::isfinite(surface_v_mm)) - surface_v_mm = z_mm; - desired_strength = halftone_screen_on(desired_strength, - surface_u_mm, - surface_v_mm, + float halftone_u_mm = surface_u_mm; + float halftone_v_mm = surface_v_mm; + float coverage = desired_strength; + if (context.halftone_v2_enabled) { + const int surface_axis = halftone_surface_axis(float(outward_x), float(outward_y), 0.f); + const std::array surface_coord = halftone_surface_coords(x_mm, y_mm, z_mm, surface_axis); + halftone_u_mm = surface_coord[0]; + halftone_v_mm = surface_coord[1]; + const std::array cell_center = + halftone_cell_center_surface_coords(halftone_u_mm, + halftone_v_mm, + context.halftone_dot_size_mm, + context.active_halftone_angle_deg); + const std::array sample_xy = + halftone_sample_xy_from_surface_coords(x_mm, y_mm, cell_center, surface_axis); + if (std::isfinite(sample_xy[0]) && std::isfinite(sample_xy[1])) + coverage = sample_weight_field(context.weight_field, + sample_xy[0], + sample_xy[1], + context.active_component_idx, + context.high_resolution_texture_sampling, + context.compact_offset_mode); + } else { + if (!std::isfinite(halftone_u_mm)) + halftone_u_mm = std::abs(outward_x) >= std::abs(outward_y) ? y_mm : x_mm; + if (!std::isfinite(halftone_v_mm)) + halftone_v_mm = z_mm; + } + desired_strength = halftone_screen_on(coverage, + halftone_u_mm, + halftone_v_mm, context.halftone_dot_size_mm, context.active_halftone_angle_deg) ? 1.f : diff --git a/src/libslic3r/TextureMappingOffset.hpp b/src/libslic3r/TextureMappingOffset.hpp index 8ec4324266e..f9b237d04fc 100644 --- a/src/libslic3r/TextureMappingOffset.hpp +++ b/src/libslic3r/TextureMappingOffset.hpp @@ -60,6 +60,7 @@ struct TextureMappingOffsetContext { bool dithering_enabled { false }; bool halftone_dithering_enabled { false }; bool halftone_increased_detail_enabled { false }; + bool halftone_v2_enabled { false }; bool nonlinear_offset_adjustment { false }; Point object_center; Vec3f linear_gradient_start_mm { Vec3f::Zero() }; diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp index bdfe861657b..a3f2989c542 100644 --- a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp +++ b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp @@ -1769,9 +1769,10 @@ bool is_halftone_dithering_method_for_texture_preview(int method) { const int clamped_method = std::clamp(method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + int(TextureMappingZone::DitheringHalftoneV2)); return clamped_method == int(TextureMappingZone::DitheringHalftone) || - clamped_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail); + clamped_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail) || + clamped_method == int(TextureMappingZone::DitheringHalftoneV2); } float halftone_screen_angle_deg_for_texture_preview(int filament_color_mode, size_t component_idx) @@ -2044,8 +2045,7 @@ std::vector component_weights_for_texture_preview(const TexturePreviewSim if (settings.dithering_enabled && settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && - settings.dithering_method != int(TextureMappingZone::DitheringHalftone) && - settings.dithering_method != int(TextureMappingZone::DitheringHalftoneIncreasedDetail)) { + !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); @@ -2222,7 +2222,7 @@ std::optional texture_preview_simulation_setti settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues); settings.dithering_method = std::clamp(zone->dithering_method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + int(TextureMappingZone::DitheringHalftoneV2)); settings.dithering_resolution_mm = std::clamp(zone->dithering_resolution_mm, TextureMappingZone::MinDitheringResolutionMm, TextureMappingZone::MaxDitheringResolutionMm); @@ -2231,8 +2231,7 @@ std::optional texture_preview_simulation_setti TextureMappingZone::MaxHalftoneDotSizeMm); const bool halftone_dithering_enabled = settings.dithering_enabled && - (settings.dithering_method == int(TextureMappingZone::DitheringHalftone) || - settings.dithering_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + is_halftone_dithering_method_for_texture_preview(settings.dithering_method); settings.compact_offset_mode = halftone_dithering_enabled ? false : zone->compact_offset_mode || settings.dithering_enabled; settings.use_legacy_fixed_color_mode = zone->use_legacy_fixed_color_mode; @@ -2311,8 +2310,7 @@ size_t texture_preview_simulation_signature(const ModelVolume &model_volume, mix(std::hash{}(settings.use_legacy_fixed_color_mode ? 1 : 0)); mix(std::hash{}(settings.dithering_enabled ? 1 : 0)); mix(std::hash{}(settings.dithering_method)); - if (settings.dithering_method == int(TextureMappingZone::DitheringHalftone) || - settings.dithering_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail)) + if (is_halftone_dithering_method_for_texture_preview(settings.dithering_method)) mix(std::hash{}(int(std::lround(settings.halftone_dot_size_mm * 1000.f)))); else mix(std::hash{}(int(std::lround(settings.dithering_resolution_mm * 1000.f)))); @@ -2371,8 +2369,7 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig const bool use_binary_dithering = settings.dithering_enabled && settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && - settings.dithering_method != int(TextureMappingZone::DitheringHalftone) && - settings.dithering_method != int(TextureMappingZone::DitheringHalftoneIncreasedDetail) && + !is_halftone_dithering_method_for_texture_preview(settings.dithering_method) && !use_raw_offsets; const bool use_halftone_dithering = settings.dithering_enabled && @@ -2560,7 +2557,7 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig std::array target_oklab = texture_preview_target_oklab(settings, sample_rgba); const int clamped_method = std::clamp(settings.dithering_method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + 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]; @@ -2640,7 +2637,7 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig if (use_binary_dithering && std::clamp(settings.dithering_method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)) == int(TextureMappingZone::DitheringFloydSteinberg)) { + int(TextureMappingZone::DitheringHalftoneV2)) == int(TextureMappingZone::DitheringFloydSteinberg)) { floyd_error_current.swap(floyd_error_next); std::fill(floyd_error_next.begin(), floyd_error_next.end(), std::array{ { 0.f, 0.f, 0.f } }); } @@ -5074,8 +5071,7 @@ static size_t texture_preview_settings_signature_impl(size_t num_physical, signature_mix(std::hash{}(zone.use_legacy_fixed_color_mode ? 1 : 0)); signature_mix(std::hash{}(zone.dithering_enabled ? 1 : 0)); signature_mix(std::hash{}(zone.dithering_method)); - if (zone.dithering_method == int(TextureMappingZone::DitheringHalftone) || - zone.dithering_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail)) + if (is_halftone_dithering_method_for_texture_preview(zone.dithering_method)) signature_mix_float(zone.halftone_dot_size_mm, 1000.f); else signature_mix_float(zone.dithering_resolution_mm, 1000.f); @@ -5134,11 +5130,10 @@ static bool texture_preview_zone_uses_halftone_model(const TextureMappingZone &z int(TextureMappingZone::TextureMappingRawValues)); const int method = std::clamp(zone.dithering_method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + int(TextureMappingZone::DitheringHalftoneV2)); return mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && zone.dithering_enabled && - (method == int(TextureMappingZone::DitheringHalftone) || - method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + is_halftone_dithering_method_for_texture_preview(method); } static bool texture_preview_gradient_zone_uses_model(const TextureMappingZone &zone, size_t num_physical) @@ -5186,8 +5181,7 @@ static void texture_preview_mix_zone_baked_model_settings(size_t &signature, signature_mix(std::hash{}(zone.use_legacy_fixed_color_mode ? 1 : 0)); signature_mix(std::hash{}(zone.dithering_enabled ? 1 : 0)); signature_mix(std::hash{}(zone.dithering_method)); - if (zone.dithering_method == int(TextureMappingZone::DitheringHalftone) || - zone.dithering_method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail)) + if (is_halftone_dithering_method_for_texture_preview(zone.dithering_method)) signature_mix_float(zone.halftone_dot_size_mm, 1000.f); else signature_mix_float(zone.dithering_resolution_mm, 1000.f); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 43ebe4dfbeb..c907236bb91 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1944,10 +1944,11 @@ public: dithering_choices.Add(_L("Ordered Bayer - not recommended")); dithering_choices.Add(_L("Halftone")); dithering_choices.Add(_L("Halftone (increased detail) - not recommended")); + dithering_choices.Add(_L("Halftone v2")); m_dithering_method_choice = new wxChoice(image_page, wxID_ANY, wxDefaultPosition, wxDefaultSize, dithering_choices); m_dithering_method_choice->SetSelection(std::clamp(dithering_method, int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail))); + int(TextureMappingZone::DitheringHalftoneV2))); dithering_row->Add(m_dithering_method_choice, 1, wxALIGN_CENTER_VERTICAL); m_dithering_enabled_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) { update_dithering_options_visibility(true); @@ -2489,7 +2490,7 @@ public: return m_dithering_method_choice ? std::clamp(m_dithering_method_choice->GetSelection(), int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)) : + int(TextureMappingZone::DitheringHalftoneV2)) : TextureMappingZone::DefaultDitheringMethod; } float dithering_resolution_mm() const @@ -2845,11 +2846,12 @@ private: const int method = m_dithering_method_choice != nullptr ? std::clamp(m_dithering_method_choice->GetSelection(), int(TextureMappingZone::DitheringClosest), - int(TextureMappingZone::DitheringHalftoneIncreasedDetail)) : + int(TextureMappingZone::DitheringHalftoneV2)) : TextureMappingZone::DefaultDitheringMethod; const bool halftone = enabled && (method == int(TextureMappingZone::DitheringHalftone) || - method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); + method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail) || + method == int(TextureMappingZone::DitheringHalftoneV2)); if (m_dithering_method_choice != nullptr) m_dithering_method_choice->Enable(enabled); if (m_dithering_resolution_panel != nullptr)