Make sliced result when using halftone dithering match 3d preview better

This commit is contained in:
sentientstardust
2026-05-24 11:46:34 +01:00
parent 9e27af3032
commit bd28850f70
7 changed files with 133 additions and 44 deletions

View File

@@ -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<uint32_t> binary_dither_masks;
const bool can_binary_dither =
dithering_enabled &&
@@ -9443,7 +9444,7 @@ std::optional<PreferredSeamPoint> 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 =

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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<float, 2> 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<float, 2> 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<float, 2> halftone_sample_xy_from_surface_coords(float x_mm,
float y_mm,
const std::array<float, 2> &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<uint32_t> binary_dither_masks;
const bool can_binary_dither =
dithering_enabled &&
@@ -2907,11 +2967,13 @@ std::optional<TextureMappingOffsetContext> 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<TextureMappingOffsetContext> 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<float, 2> 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<float, 2> 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<float, 2> 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 :

View File

@@ -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() };

View File

@@ -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<float> 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<TexturePreviewBinaryDitherCandidate> candidates = binary_dither_candidates_for_texture_preview(settings);
const std::array<float, 3> 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<TexturePreviewSimulationSettings> 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<TexturePreviewSimulationSettings> 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<int>{}(settings.use_legacy_fixed_color_mode ? 1 : 0));
mix(std::hash<int>{}(settings.dithering_enabled ? 1 : 0));
mix(std::hash<int>{}(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>{}(int(std::lround(settings.halftone_dot_size_mm * 1000.f))));
else
mix(std::hash<int>{}(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<float, 3> 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<float, 3>{ { 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<int>{}(zone.use_legacy_fixed_color_mode ? 1 : 0));
signature_mix(std::hash<int>{}(zone.dithering_enabled ? 1 : 0));
signature_mix(std::hash<int>{}(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<int>{}(zone.use_legacy_fixed_color_mode ? 1 : 0));
signature_mix(std::hash<int>{}(zone.dithering_enabled ? 1 : 0));
signature_mix(std::hash<int>{}(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);

View File

@@ -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)