Add TD correction support for top-surface coloring

This commit is contained in:
sentientstardust
2026-05-29 14:40:18 +01:00
parent 5ca2f24732
commit c831e78177
10 changed files with 633 additions and 56 deletions

View File

@@ -124,7 +124,8 @@ int build_color_solver_kd_tree(const std::vector<float> &coor
return build_color_solver_kd_tree(coords, nodes, indices, 0, candidate_count, uint8_t(0));
}
void build_color_solver_kd_trees(ColorSolverCandidateSet &candidates)
template <class CandidateSet>
void build_color_solver_kd_trees(CandidateSet &candidates)
{
candidates.kd_root = build_color_solver_kd_tree(candidates.rgbs, candidates.kd_nodes);
if (candidates.perceptual_coords.size() == candidates.rgbs.size()) {
@@ -159,7 +160,8 @@ void update_color_solver_nearest_result(ColorSolverNearestResult &result, size_t
}
}
float color_solver_candidate_error(const ColorSolverCandidateSet &candidates,
template <class CandidateSet>
float color_solver_candidate_error(const CandidateSet &candidates,
size_t candidate_idx,
const std::array<float, 3> &target_rgb)
{
@@ -170,7 +172,8 @@ float color_solver_candidate_error(const ColorSolverCandidateSet &candidates,
return dr * dr + dg * dg + db * db;
}
ColorSolverNearestResult nearest_color_solver_candidates_linear(const ColorSolverCandidateSet &candidates,
template <class CandidateSet>
ColorSolverNearestResult nearest_color_solver_candidates_linear(const CandidateSet &candidates,
const std::array<float, 3> &target_rgb)
{
ColorSolverNearestResult result;
@@ -184,7 +187,8 @@ ColorSolverNearestResult nearest_color_solver_candidates_linear(const ColorSolve
return result;
}
void query_color_solver_kd_tree(const ColorSolverCandidateSet &candidates,
template <class CandidateSet>
void query_color_solver_kd_tree(const CandidateSet &candidates,
const std::array<float, 3> &target_rgb,
int node_idx,
ColorSolverNearestResult &result)
@@ -216,7 +220,8 @@ void query_color_solver_kd_tree(const ColorSolverCandidateSet &candidates,
query_color_solver_kd_tree(candidates, target_rgb, far_node, result);
}
ColorSolverNearestResult nearest_color_solver_candidates(const ColorSolverCandidateSet &candidates,
template <class CandidateSet>
ColorSolverNearestResult nearest_color_solver_candidates(const CandidateSet &candidates,
const std::array<float, 3> &target_rgb)
{
ColorSolverNearestResult result;
@@ -228,7 +233,8 @@ ColorSolverNearestResult nearest_color_solver_candidates(const ColorSolverCandid
return result;
}
float color_solver_candidate_perceptual_error(const ColorSolverCandidateSet &candidates,
template <class CandidateSet>
float color_solver_candidate_perceptual_error(const CandidateSet &candidates,
size_t candidate_idx,
const std::array<float, 3> &target_oklab,
const std::array<float, 3> &axis_weights)
@@ -240,7 +246,8 @@ float color_solver_candidate_perceptual_error(const ColorSolverCandidateSet &can
return axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db;
}
ColorSolverNearestResult nearest_color_solver_candidates_perceptual_linear(const ColorSolverCandidateSet &candidates,
template <class CandidateSet>
ColorSolverNearestResult nearest_color_solver_candidates_perceptual_linear(const CandidateSet &candidates,
const std::array<float, 3> &target_oklab,
const std::array<float, 3> &axis_weights)
{
@@ -254,7 +261,8 @@ ColorSolverNearestResult nearest_color_solver_candidates_perceptual_linear(const
return result;
}
void query_color_solver_perceptual_kd_tree(const ColorSolverCandidateSet &candidates,
template <class CandidateSet>
void query_color_solver_perceptual_kd_tree(const CandidateSet &candidates,
const std::array<float, 3> &target_oklab,
const std::array<float, 3> &axis_weights,
int node_idx,
@@ -287,7 +295,8 @@ void query_color_solver_perceptual_kd_tree(const ColorSolverCandidateSet &candid
query_color_solver_perceptual_kd_tree(candidates, target_oklab, axis_weights, far_node, result);
}
ColorSolverNearestResult nearest_color_solver_candidates_perceptual(const ColorSolverCandidateSet &candidates,
template <class CandidateSet>
ColorSolverNearestResult nearest_color_solver_candidates_perceptual(const CandidateSet &candidates,
const std::array<float, 3> &target_rgb)
{
ColorSolverNearestResult result;
@@ -304,6 +313,48 @@ ColorSolverNearestResult nearest_color_solver_candidates_perceptual(const ColorS
return result;
}
std::array<float, 3> mix_ordered_stack_with_buffers(const std::vector<std::array<float, 3>> &colors_with_background,
std::vector<float> &weights,
const std::vector<uint16_t> &surface_to_deep,
const std::vector<float> &layer_opacities)
{
if (colors_with_background.empty() || surface_to_deep.empty())
return colors_with_background.empty() ? std::array<float, 3>{ { 0.f, 0.f, 0.f } } : colors_with_background.back();
const size_t component_count = colors_with_background.size() - 1;
weights.assign(colors_with_background.size(), 0.f);
float transmission = 1.f;
for (uint16_t component_idx : surface_to_deep) {
if (size_t(component_idx) >= component_count)
continue;
const float opacity =
size_t(component_idx) < layer_opacities.size() && std::isfinite(layer_opacities[size_t(component_idx)]) ?
std::clamp(layer_opacities[size_t(component_idx)], 1e-4f, 0.9999f) :
0.5f;
weights[size_t(component_idx)] += transmission * opacity;
transmission *= 1.f - opacity;
if (transmission <= 1e-5f)
break;
}
weights.back() = std::max(0.f, transmission);
return pigment_painter::mix_srgb(colors_with_background, weights);
}
size_t ordered_stack_candidate_count(size_t component_count, int stack_depth, size_t candidate_limit)
{
if (component_count == 0 || stack_depth <= 0)
return 0;
size_t count = 1;
for (int idx = 0; idx < stack_depth; ++idx) {
if (candidate_limit > 0 && count > candidate_limit / component_count)
return 0;
if (count > std::numeric_limits<size_t>::max() / component_count)
return 0;
count *= component_count;
}
return candidate_limit > 0 && count > candidate_limit ? 0 : count;
}
} // namespace
ColorSolverMixModel color_solver_mix_model_from_index(int model)
@@ -362,6 +413,22 @@ std::array<float, 3> mix_color_solver_components(const std::vector<std::array<fl
return pigment_painter::mix_srgb(component_colors, safe_weights);
}
std::array<float, 3> mix_color_solver_ordered_stack(const std::vector<std::array<float, 3>> &component_colors,
const std::vector<uint16_t> &surface_to_deep,
const std::vector<float> &layer_opacities,
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model)
{
(void) mix_model;
if (component_colors.empty() || surface_to_deep.empty())
return background_rgb;
std::vector<std::array<float, 3>> colors = component_colors;
colors.emplace_back(background_rgb);
std::vector<float> weights;
return mix_ordered_stack_with_buffers(colors, weights, surface_to_deep, layer_opacities);
}
std::array<float, 3> color_solver_oklab_from_srgb(const std::array<float, 3> &rgb)
{
return oklab_from_srgb(rgb);
@@ -480,4 +547,145 @@ std::vector<float> solve_color_solver_weights_for_target(const ColorSolverCandid
return weights;
}
std::string color_solver_ordered_stack_candidate_cache_key(const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &layer_opacities,
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model,
int stack_depth,
size_t candidate_limit)
{
std::ostringstream key;
key << component_colors.size();
key << "|mx" << mix_model_index(mix_model);
key << "|sd" << stack_depth;
key << "|lim" << candidate_limit;
key << "|bg"
<< int(std::lround(clamp01(background_rgb[0]) * 65535.f)) << ','
<< int(std::lround(clamp01(background_rgb[1]) * 65535.f)) << ','
<< int(std::lround(clamp01(background_rgb[2]) * 65535.f));
for (const std::array<float, 3> &color : component_colors) {
key << '|'
<< int(std::lround(clamp01(color[0]) * 65535.f)) << ','
<< int(std::lround(clamp01(color[1]) * 65535.f)) << ','
<< int(std::lround(clamp01(color[2]) * 65535.f));
}
key << "|op";
for (size_t idx = 0; idx < component_colors.size(); ++idx) {
const float opacity =
idx < layer_opacities.size() && std::isfinite(layer_opacities[idx]) ?
std::clamp(layer_opacities[idx], 1e-4f, 0.9999f) :
0.5f;
key << ',' << int(std::lround(opacity * 1000000.f));
}
return key.str();
}
ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates(
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &layer_opacities,
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model,
int stack_depth,
size_t candidate_limit)
{
(void) mix_model;
ColorSolverOrderedStackCandidateSet candidates;
if (component_colors.empty() || stack_depth <= 0 || component_colors.size() > size_t(std::numeric_limits<uint16_t>::max()))
return candidates;
const size_t component_count = component_colors.size();
const size_t candidate_count = ordered_stack_candidate_count(component_count, stack_depth, candidate_limit);
if (candidate_count == 0)
return candidates;
candidates.component_count = component_count;
candidates.stack_depth = stack_depth;
candidates.rgbs.reserve(candidate_count * 3);
candidates.perceptual_coords.reserve(candidate_count * 3);
candidates.stacks.reserve(candidate_count * size_t(stack_depth));
std::vector<std::array<float, 3>> colors_with_background = component_colors;
colors_with_background.emplace_back(background_rgb);
std::vector<float> weights(colors_with_background.size(), 0.f);
std::vector<uint16_t> surface_to_deep(size_t(stack_depth), 0);
std::function<void(int)> recurse = [&](int depth_idx) {
if (depth_idx == stack_depth) {
const std::array<float, 3> mixed =
mix_ordered_stack_with_buffers(colors_with_background, weights, surface_to_deep, layer_opacities);
const std::array<float, 3> perceptual = oklab_from_srgb(mixed);
candidates.rgbs.emplace_back(mixed[0]);
candidates.rgbs.emplace_back(mixed[1]);
candidates.rgbs.emplace_back(mixed[2]);
candidates.perceptual_coords.emplace_back(perceptual[0]);
candidates.perceptual_coords.emplace_back(perceptual[1]);
candidates.perceptual_coords.emplace_back(perceptual[2]);
candidates.stacks.insert(candidates.stacks.end(), surface_to_deep.begin(), surface_to_deep.end());
return;
}
for (size_t component_idx = 0; component_idx < component_count; ++component_idx) {
surface_to_deep[size_t(depth_idx)] = uint16_t(component_idx);
recurse(depth_idx + 1);
}
};
recurse(0);
build_color_solver_kd_trees(candidates);
return candidates;
}
const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates(
ColorSolverOrderedStackCandidateCache &cache,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &layer_opacities,
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model,
int stack_depth,
size_t candidate_limit)
{
const std::string key =
color_solver_ordered_stack_candidate_cache_key(component_colors,
layer_opacities,
background_rgb,
mix_model,
stack_depth,
candidate_limit);
auto it = cache.find(key);
if (it != cache.end())
return it->second;
return cache.emplace(
key,
build_color_solver_ordered_stack_candidates(component_colors,
layer_opacities,
background_rgb,
mix_model,
stack_depth,
candidate_limit)).first->second;
}
std::vector<uint16_t> solve_color_solver_ordered_stack_for_target(
const ColorSolverOrderedStackCandidateSet &candidates,
const std::array<float, 3> &target_rgb,
ColorSolverMode solver_mode)
{
if (candidates.empty())
return {};
const size_t candidate_count = candidates.rgbs.size() / 3;
ColorSolverNearestResult nearest =
solver_mode == ColorSolverMode::V2 ?
nearest_color_solver_candidates_perceptual(candidates, target_rgb) :
nearest_color_solver_candidates(candidates, target_rgb);
if (nearest.best_idx >= candidate_count && solver_mode == ColorSolverMode::V2)
nearest = nearest_color_solver_candidates(candidates, target_rgb);
if (nearest.best_idx >= candidate_count)
return {};
const size_t stack_begin = nearest.best_idx * size_t(candidates.stack_depth);
if (stack_begin + size_t(candidates.stack_depth) > candidates.stacks.size())
return {};
return std::vector<uint16_t>(candidates.stacks.begin() + stack_begin,
candidates.stacks.begin() + stack_begin + candidates.stack_depth);
}
} // namespace Slic3r

View File

@@ -69,6 +69,28 @@ struct ColorSolverCandidateSet {
using ColorSolverCandidateCache = std::map<std::string, ColorSolverCandidateSet>;
struct ColorSolverOrderedStackCandidateSet {
using KdNode = ColorSolverCandidateSet::KdNode;
size_t component_count { 0 };
int stack_depth { 0 };
std::vector<float> rgbs;
std::vector<float> perceptual_coords;
std::vector<uint16_t> stacks;
std::vector<KdNode> kd_nodes;
std::vector<KdNode> perceptual_kd_nodes;
int kd_root { -1 };
int perceptual_kd_root { -1 };
bool empty() const
{
return component_count == 0 || stack_depth <= 0 || rgbs.empty() || rgbs.size() % 3 != 0 ||
stacks.size() != (rgbs.size() / 3) * size_t(stack_depth);
}
};
using ColorSolverOrderedStackCandidateCache = std::map<std::string, ColorSolverOrderedStackCandidateSet>;
ColorSolverMixModel color_solver_mix_model_from_index(int model);
ColorSolverLookupMode color_solver_lookup_mode_from_index(int mode);
ColorSolverMode color_solver_mode_from_index(int mode);
@@ -82,6 +104,11 @@ std::array<float, 3> mix_color_solver_components(const std::vector<std::array<fl
std::array<float, 3> mix_color_solver_components(const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &weights,
ColorSolverMixModel mix_model);
std::array<float, 3> mix_color_solver_ordered_stack(const std::vector<std::array<float, 3>> &component_colors,
const std::vector<uint16_t> &surface_to_deep,
const std::vector<float> &layer_opacities,
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model);
std::array<float, 3> color_solver_oklab_from_srgb(const std::array<float, 3> &rgb);
std::string color_solver_candidate_cache_key(const std::vector<std::array<float, 3>> &component_colors,
@@ -98,6 +125,31 @@ std::vector<float> solve_color_solver_weights_for_target(const ColorSolverCandid
const std::array<float, 3> &target_rgb,
ColorSolverLookupMode lookup_mode,
ColorSolverMode solver_mode);
std::string color_solver_ordered_stack_candidate_cache_key(const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &layer_opacities,
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model,
int stack_depth,
size_t candidate_limit = 0);
ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates(
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &layer_opacities,
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model,
int stack_depth,
size_t candidate_limit = 0);
const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates(
ColorSolverOrderedStackCandidateCache &cache,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &layer_opacities,
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model,
int stack_depth,
size_t candidate_limit = 0);
std::vector<uint16_t> solve_color_solver_ordered_stack_for_target(
const ColorSolverOrderedStackCandidateSet &candidates,
const std::array<float, 3> &target_rgb,
ColorSolverMode solver_mode);
} // namespace Slic3r

View File

@@ -504,6 +504,7 @@ struct TopSurfaceImageRegionPlan {
bool contoning_polygonize_color_regions_enabled = false;
int contoning_polygonize_resolution = TextureMappingZone::DefaultTopSurfaceContoningPolygonizeResolution;
bool contoning_surface_anchored_stacks_enabled = false;
bool contoning_td_adjustment_enabled = TextureMappingZone::DefaultTopSurfaceContoningTdAdjustmentEnabled;
int contoning_flat_surface_infill_mode = TextureMappingZone::SlicerDefaultTopSurfaceContoningFlatSurfaceInfillMode;
};
@@ -1166,6 +1167,7 @@ struct TopSurfaceImageContoningStackPlanKey {
bool blue_noise { false };
bool polygonize { false };
int polygonize_resolution { 1 };
bool td_adjustment { false };
bool operator<(const TopSurfaceImageContoningStackPlanKey &rhs) const
{
@@ -1192,7 +1194,8 @@ struct TopSurfaceImageContoningStackPlanKey {
supersampled,
blue_noise,
polygonize,
polygonize_resolution) <
polygonize_resolution,
td_adjustment) <
std::tie(rhs.source_layer,
rhs.source_layer_id,
rhs.target_layer,
@@ -1216,7 +1219,8 @@ struct TopSurfaceImageContoningStackPlanKey {
rhs.supersampled,
rhs.blue_noise,
rhs.polygonize,
rhs.polygonize_resolution);
rhs.polygonize_resolution,
rhs.td_adjustment);
}
};
@@ -1320,29 +1324,6 @@ static float top_surface_image_contoning_oklab_error(const std::array<float, 3>
return dl * dl + 4.f * da * da + 4.f * db * db;
}
static std::optional<std::array<float, 3>> top_surface_image_contoning_stack_rgb(
const std::vector<unsigned int> &bottom_to_top,
const PrintConfig &print_config)
{
if (bottom_to_top.empty())
return std::nullopt;
std::vector<std::array<float, 3>> colors;
std::vector<float> weights;
colors.reserve(bottom_to_top.size());
weights.reserve(bottom_to_top.size());
const float weight = 1.f / float(bottom_to_top.size());
for (unsigned int component_id : bottom_to_top) {
if (component_id == 0 || component_id > print_config.filament_colour.values.size())
return std::nullopt;
ColorRGB color;
if (!decode_color(print_config.filament_colour.get_at(size_t(component_id - 1)), color))
return std::nullopt;
colors.push_back({ color.r(), color.g(), color.b() });
weights.emplace_back(weight);
}
return mix_color_solver_components(colors, weights, ColorSolverMixModel::PigmentPainter);
}
static float top_surface_image_contoning_sample_pitch_mm(const TopSurfaceImageRegionPlan &plan,
const BoundingBox &bbox)
{
@@ -1704,6 +1685,7 @@ static std::vector<TopSurfaceImageContoningVectorRegion> top_surface_image_conto
const ExPolygons &area,
float min_feature_mm,
bool polygonize_color_regions,
bool lower_surface,
const ThrowIfCanceled *throw_if_canceled)
{
std::vector<TopSurfaceImageContoningVectorRegion> regions;
@@ -1729,7 +1711,9 @@ static std::vector<TopSurfaceImageContoningVectorRegion> top_surface_image_conto
continue;
const int pattern_depth = depth % int(bottom_to_top.size());
const unsigned int component_id =
bottom_to_top[size_t(int(bottom_to_top.size()) - 1 - pattern_depth)];
lower_surface ?
bottom_to_top[size_t(pattern_depth)] :
bottom_to_top[size_t(int(bottom_to_top.size()) - 1 - pattern_depth)];
if (component_id == 0 || component_id > max_component_id)
continue;
component_grid[idx] = int(component_id);
@@ -1969,15 +1953,15 @@ static std::optional<TopSurfaceImageContoningSolvedLabel> top_surface_image_cont
const std::array<float, 3> &rgb,
int solve_layers,
const TextureMappingContoningSolver &solver,
const PrintConfig &print_config,
bool lower_surface,
std::vector<TopSurfaceImageContoningVectorLabel> &labels,
std::map<std::vector<unsigned int>, int> &label_by_stack)
{
TextureMappingContoningStack stack = solver.solve(rgb, solve_layers);
TextureMappingContoningStack stack = solver.solve(rgb, solve_layers, lower_surface);
if (stack.bottom_to_top.empty())
return std::nullopt;
std::optional<std::array<float, 3>> stack_rgb =
top_surface_image_contoning_stack_rgb(stack.bottom_to_top, print_config);
solver.stack_rgb(stack.bottom_to_top, lower_surface);
if (!stack_rgb)
return std::nullopt;
auto label_it = label_by_stack.find(stack.bottom_to_top);
@@ -2124,7 +2108,13 @@ static std::vector<TopSurfaceImageContoningVectorRegion> top_surface_image_conto
auto solve_cell = [&](int row, int col, const std::array<float, 3> &target_rgb, int solve_layers) {
std::optional<TopSurfaceImageContoningSolvedLabel> solved =
top_surface_image_contoning_solve_label(target_rgb, solve_layers, solver, print_config, labels, label_by_stack);
top_surface_image_contoning_solve_label(target_rgb,
solve_layers,
solver,
source_surface == TopSurfaceImageSourceSurface::Bottom &&
plan.contoning_td_adjustment_enabled,
labels,
label_by_stack);
if (!solved)
return std::optional<TopSurfaceImageContoningSolvedLabel>();
grid[size_t(row * cols + col)] = solved->label;
@@ -2247,6 +2237,8 @@ static std::vector<TopSurfaceImageContoningVectorRegion> top_surface_image_conto
area,
plan.contoning_min_feature_mm,
plan.contoning_polygonize_color_regions_enabled,
source_surface == TopSurfaceImageSourceSurface::Bottom &&
plan.contoning_td_adjustment_enabled,
throw_if_canceled);
}
@@ -2306,7 +2298,13 @@ static std::shared_ptr<const TopSurfaceImageContoningStackPlan> top_surface_imag
auto solve_cell = [&](int row, int col, const std::array<float, 3> &target_rgb, int solve_layers, int available_depth) {
std::optional<TopSurfaceImageContoningSolvedLabel> solved =
top_surface_image_contoning_solve_label(target_rgb, solve_layers, solver, print_config, out->labels, label_by_stack);
top_surface_image_contoning_solve_label(target_rgb,
solve_layers,
solver,
source_surface == TopSurfaceImageSourceSurface::Bottom &&
plan.contoning_td_adjustment_enabled,
out->labels,
label_by_stack);
if (!solved)
return std::optional<TopSurfaceImageContoningSolvedLabel>();
TopSurfaceImageContoningStackPlanCell &cell = out->cells[size_t(row * cols + col)];
@@ -2470,6 +2468,7 @@ static TopSurfaceImageContoningStackPlanKey top_surface_image_contoning_stack_pl
key.polygonize_resolution = plan.contoning_polygonize_color_regions_enabled ?
TextureMappingZone::normalize_top_surface_contoning_polygonize_resolution(plan.contoning_polygonize_resolution) :
1;
key.td_adjustment = plan.contoning_td_adjustment_enabled;
return key;
}
@@ -2511,6 +2510,7 @@ static std::vector<TopSurfaceImageContoningVectorRegion> top_surface_image_conto
const TopSurfaceImageContoningStackPlan &stack_plan,
const ExPolygons &area,
int depth,
TopSurfaceImageSourceSurface source_surface,
const ThrowIfCanceled *throw_if_canceled)
{
std::vector<TopSurfaceImageContoningVectorRegion> regions;
@@ -2550,6 +2550,8 @@ static std::vector<TopSurfaceImageContoningVectorRegion> top_surface_image_conto
area,
plan.contoning_min_feature_mm,
plan.contoning_polygonize_color_regions_enabled,
source_surface == TopSurfaceImageSourceSurface::Bottom &&
plan.contoning_td_adjustment_enabled,
throw_if_canceled);
}
@@ -2889,7 +2891,12 @@ static void top_surface_image_append_contoning_slices(TopSurfaceImageRegionPlan
stack_area_extensions_ptr,
throw_if_canceled);
const std::vector<TopSurfaceImageContoningVectorRegion> stack_regions =
top_surface_image_contoning_vector_regions_from_stack_plan(plan, *stack_plan, vector_area, depth, throw_if_canceled);
top_surface_image_contoning_vector_regions_from_stack_plan(plan,
*stack_plan,
vector_area,
depth,
source_surface,
throw_if_canceled);
append_regions(stack_regions, true, include_perimeter_regions);
}
} else if (plan.contoning_blue_noise_error_diffusion_enabled) {
@@ -3122,7 +3129,8 @@ static std::vector<TopSurfaceImageRegionPlan> top_surface_image_region_plans(
TextureMappingZone::normalize_top_surface_contoning_polygonize_resolution(zone->top_surface_contoning_polygonize_resolution);
plan.contoning_surface_anchored_stacks_enabled =
zone->effective_top_surface_contoning_surface_anchored_stacks_enabled();
const TextureMappingContoningSolver contoning_solver(*zone, print_config, components);
plan.contoning_td_adjustment_enabled = zone->top_surface_contoning_td_adjustment_enabled;
const TextureMappingContoningSolver contoning_solver(*zone, print_config, components, float(layer.height));
const int stack_depth = plan.contoning ?
plan.contoning_stack_layers :

View File

@@ -528,7 +528,10 @@ static std::optional<PerimeterTextureRecolorSampler> perimeter_texture_make_reco
if (zone.top_surface_contoning_perimeters_active()) {
sampler.contoning = true;
sampler.contoning_solver =
TextureMappingContoningSolver(zone, print_config, sampler.image_context->component_ids);
TextureMappingContoningSolver(zone,
print_config,
sampler.image_context->component_ids,
layer != nullptr ? float(layer->height) : 0.f);
sampler.contoning_stack_layers =
std::clamp(zone.top_surface_contoning_stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,

View File

@@ -1148,6 +1148,7 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const
TextureMappingZone::normalize_top_surface_contoning_polygonize_resolution(top_surface_contoning_polygonize_resolution) ==
TextureMappingZone::normalize_top_surface_contoning_polygonize_resolution(rhs.top_surface_contoning_polygonize_resolution) &&
effective_top_surface_contoning_surface_anchored_stacks_enabled() == rhs.effective_top_surface_contoning_surface_anchored_stacks_enabled() &&
top_surface_contoning_td_adjustment_enabled == rhs.top_surface_contoning_td_adjustment_enabled &&
compact_offset_mode == rhs.compact_offset_mode &&
use_legacy_fixed_color_mode == rhs.use_legacy_fixed_color_mode &&
high_speed_image_texture_sampling == rhs.high_speed_image_texture_sampling &&
@@ -1548,6 +1549,8 @@ std::string TextureMappingManager::serialize_entries()
TextureMappingZone::normalize_top_surface_contoning_polygonize_resolution(zone.top_surface_contoning_polygonize_resolution);
texture["top_surface_contoning_surface_anchored_stacks_enabled"] =
zone.effective_top_surface_contoning_surface_anchored_stacks_enabled();
texture["top_surface_contoning_td_adjustment_enabled"] =
zone.top_surface_contoning_td_adjustment_enabled;
texture["compact_offset_mode"] = zone.compact_offset_mode;
texture["use_legacy_fixed_color_mode"] = zone.use_legacy_fixed_color_mode;
texture["high_speed_image_texture_sampling"] = true;
@@ -1844,6 +1847,9 @@ void TextureMappingManager::load_entries(const std::string &serialized,
zone.top_surface_contoning_surface_anchored_stacks_enabled =
texture.value("top_surface_contoning_surface_anchored_stacks_enabled",
TextureMappingZone::DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled);
zone.top_surface_contoning_td_adjustment_enabled =
texture.value("top_surface_contoning_td_adjustment_enabled",
TextureMappingZone::DefaultTopSurfaceContoningTdAdjustmentEnabled);
zone.apply_top_surface_contoning_experimental_defaults();
zone.compact_offset_mode = texture.value("compact_offset_mode", TextureMappingZone::DefaultCompactOffsetMode);
zone.use_legacy_fixed_color_mode =

View File

@@ -204,6 +204,7 @@ struct TextureMappingZone
static constexpr bool DefaultTopSurfaceContoningPolygonizeColorRegionsEnabled = true;
static constexpr int DefaultTopSurfaceContoningPolygonizeResolution = 4;
static constexpr bool DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled = false;
static constexpr bool DefaultTopSurfaceContoningTdAdjustmentEnabled = true;
static constexpr bool DefaultCompactOffsetMode = true;
static constexpr bool DefaultUseLegacyFixedColorMode = false;
static constexpr bool DefaultHighSpeedImageTextureSampling = true;
@@ -362,6 +363,7 @@ struct TextureMappingZone
bool top_surface_contoning_polygonize_color_regions_enabled = DefaultTopSurfaceContoningPolygonizeColorRegionsEnabled;
int top_surface_contoning_polygonize_resolution = DefaultTopSurfaceContoningPolygonizeResolution;
bool top_surface_contoning_surface_anchored_stacks_enabled = DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled;
bool top_surface_contoning_td_adjustment_enabled = DefaultTopSurfaceContoningTdAdjustmentEnabled;
bool compact_offset_mode = DefaultCompactOffsetMode;
bool use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode;
bool high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling;
@@ -553,6 +555,7 @@ struct TextureMappingZone
top_surface_contoning_polygonize_color_regions_enabled = DefaultTopSurfaceContoningPolygonizeColorRegionsEnabled;
top_surface_contoning_polygonize_resolution = DefaultTopSurfaceContoningPolygonizeResolution;
top_surface_contoning_surface_anchored_stacks_enabled = DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled;
top_surface_contoning_td_adjustment_enabled = DefaultTopSurfaceContoningTdAdjustmentEnabled;
compact_offset_mode = DefaultCompactOffsetMode;
use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode;
high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling;

View File

@@ -9,6 +9,7 @@
#include <algorithm>
#include <cmath>
#include <functional>
#include <limits>
#include <numeric>
@@ -17,6 +18,7 @@ namespace {
constexpr float OPAQUE_CONTONING_TD_THRESHOLD_MM = 0.5f;
constexpr float INFERRED_BLACK_TD_MM = 0.1f;
constexpr size_t MAX_TD_ORDERED_CONTONING_CANDIDATES = 2500000;
float clamp01(float value)
{
@@ -81,9 +83,16 @@ bool is_black_color_filament(const PrintConfig &config, unsigned int component_i
return max_channel <= 0.20f && luminance <= 0.12f && max_channel - min_channel <= 0.08f;
}
float estimated_transmission_distance_mm(const PrintConfig &config, unsigned int component_id)
{
const float luminance = std::clamp(filament_luminance(config, component_id), 0.f, 1.f);
return std::clamp(0.1f + 2.9f * std::pow(luminance, 1.35f), 0.12f, 3.f);
}
std::vector<float> effective_transmission_distances_mm(const TextureMappingZone &zone,
const PrintConfig &config,
const std::vector<unsigned int> &component_ids)
const std::vector<unsigned int> &component_ids,
bool estimate_missing)
{
std::vector<float> out(component_ids.size(), 0.f);
for (size_t idx = 0; idx < component_ids.size(); ++idx) {
@@ -93,11 +102,20 @@ std::vector<float> effective_transmission_distances_mm(const TextureMappingZone
} else if (black_role_component(zone.filament_color_mode, idx, component_ids.size()) ||
is_black_color_filament(config, component_ids[idx])) {
out[idx] = INFERRED_BLACK_TD_MM;
} else if (estimate_missing) {
out[idx] = estimated_transmission_distance_mm(config, component_ids[idx]);
}
}
return out;
}
float layer_opacity_from_td(float td_mm, float layer_height_mm)
{
const float safe_td = std::clamp(td_mm, 0.01f, 50.f);
const float safe_layer_height = std::clamp(layer_height_mm, 0.01f, 2.f);
return std::clamp(1.f - std::pow(0.05f, safe_layer_height / safe_td), 1e-4f, 0.9999f);
}
float effective_transmission_distance_for_component(const std::vector<unsigned int> &component_ids,
const std::vector<float> &effective_tds,
unsigned int component_id)
@@ -263,7 +281,8 @@ std::vector<unsigned int> texture_mapping_contoning_components_bottom_to_top(
return component_ids;
const std::vector<unsigned int> td_component_ids = component_ids;
const std::vector<float> effective_tds = effective_transmission_distances_mm(zone, config, td_component_ids);
const std::vector<float> effective_tds =
effective_transmission_distances_mm(zone, config, td_component_ids, zone.top_surface_contoning_td_adjustment_enabled);
const bool has_complete_td =
std::all_of(effective_tds.begin(), effective_tds.end(), [](float td) { return std::isfinite(td) && td > 0.f; });
if (has_complete_td) {
@@ -327,8 +346,14 @@ bool texture_mapping_contoning_normal_eligible(float normal_z, float threshold_d
TextureMappingContoningSolver::TextureMappingContoningSolver(const TextureMappingZone &zone,
const PrintConfig &config,
std::vector<unsigned int> component_ids)
std::vector<unsigned int> component_ids,
float layer_height_mm)
{
m_td_adjustment_enabled = zone.top_surface_contoning_td_adjustment_enabled;
m_layer_height_mm = std::isfinite(layer_height_mm) && layer_height_mm > 0.f ? layer_height_mm : 0.2f;
if (!std::isfinite(m_layer_height_mm) || m_layer_height_mm <= 0.f)
m_layer_height_mm = 0.2f;
component_ids.erase(std::remove_if(component_ids.begin(), component_ids.end(), [&config](unsigned int id) {
return id == 0 || id > config.filament_colour.values.size();
}), component_ids.end());
@@ -342,7 +367,13 @@ TextureMappingContoningSolver::TextureMappingContoningSolver(const TextureMappin
m_component_luminance.reserve(m_component_ids.size());
for (const unsigned int id : m_component_ids)
m_component_luminance.emplace_back(filament_luminance(config, id));
m_effective_transmission_distances_mm = effective_transmission_distances_mm(zone, config, m_component_ids);
std::vector<float> background_weights(m_component_colors.size(), 1.f / float(m_component_colors.size()));
m_background_rgb = mix_color_solver_components(m_component_colors, background_weights, ColorSolverMixModel::PigmentPainter);
m_effective_transmission_distances_mm =
effective_transmission_distances_mm(zone, config, m_component_ids, m_td_adjustment_enabled);
m_component_layer_opacity.reserve(m_effective_transmission_distances_mm.size());
for (float td_mm : m_effective_transmission_distances_mm)
m_component_layer_opacity.emplace_back(layer_opacity_from_td(td_mm > 0.f ? td_mm : 3.f, m_layer_height_mm));
m_components_bottom_to_top = texture_mapping_contoning_components_bottom_to_top(zone, config, m_component_ids);
}
@@ -381,6 +412,63 @@ TextureMappingContoningSolver::candidates_for_depth(int stack_layers) const
return m_candidates_by_depth.emplace(depth, std::move(candidates)).first->second;
}
std::optional<size_t> TextureMappingContoningSolver::component_index(unsigned int component_id) const
{
const auto it = std::find(m_component_ids.begin(), m_component_ids.end(), component_id);
if (it == m_component_ids.end())
return std::nullopt;
return size_t(it - m_component_ids.begin());
}
std::optional<std::array<float, 3>> TextureMappingContoningSolver::stack_rgb(
const std::vector<unsigned int> &bottom_to_top,
bool lower_surface) const
{
if (bottom_to_top.empty() || !valid())
return std::nullopt;
if (!m_td_adjustment_enabled) {
std::vector<std::array<float, 3>> colors;
std::vector<float> weights;
colors.reserve(bottom_to_top.size());
weights.reserve(bottom_to_top.size());
const float weight = 1.f / float(bottom_to_top.size());
for (unsigned int component_id : bottom_to_top) {
const std::optional<size_t> idx = component_index(component_id);
if (!idx || *idx >= m_component_colors.size())
return std::nullopt;
colors.emplace_back(m_component_colors[*idx]);
weights.emplace_back(weight);
}
return mix_color_solver_components(colors, weights, ColorSolverMixModel::PigmentPainter);
}
std::vector<uint16_t> surface_to_deep;
surface_to_deep.reserve(bottom_to_top.size());
auto append_component = [this, &surface_to_deep](unsigned int component_id) {
const std::optional<size_t> idx = component_index(component_id);
if (!idx || *idx > size_t(std::numeric_limits<uint16_t>::max()))
return false;
surface_to_deep.emplace_back(uint16_t(*idx));
return true;
};
if (lower_surface) {
for (unsigned int component_id : bottom_to_top)
if (!append_component(component_id))
return std::nullopt;
} else {
for (auto it = bottom_to_top.rbegin(); it != bottom_to_top.rend(); ++it)
if (!append_component(*it))
return std::nullopt;
}
return mix_color_solver_ordered_stack(m_component_colors,
surface_to_deep,
m_component_layer_opacity,
m_background_rgb,
ColorSolverMixModel::PigmentPainter);
}
void TextureMappingContoningSolver::arrange_stack_for_light_path(std::vector<unsigned int> &bottom_to_top,
const std::array<float, 3> &target_rgb) const
{
@@ -446,7 +534,9 @@ void TextureMappingContoningSolver::arrange_stack_for_light_path(std::vector<uns
bottom_to_top.emplace_back(*it);
}
TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::array<float, 3> &target_rgb, int stack_layers) const
TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::array<float, 3> &target_rgb,
int stack_layers,
bool lower_surface) const
{
TextureMappingContoningStack out;
if (!valid())
@@ -455,6 +545,39 @@ TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::arr
const int depth = std::clamp(stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers);
if (m_td_adjustment_enabled) {
const ColorSolverOrderedStackCandidateSet *ordered_candidates = nullptr;
{
std::lock_guard<std::mutex> lock(*m_ordered_candidate_cache_mutex);
ordered_candidates =
&color_solver_ordered_stack_candidates(*m_ordered_candidate_cache,
m_component_colors,
m_component_layer_opacity,
m_background_rgb,
ColorSolverMixModel::PigmentPainter,
depth,
MAX_TD_ORDERED_CONTONING_CANDIDATES);
}
const std::vector<uint16_t> surface_to_deep =
solve_color_solver_ordered_stack_for_target(*ordered_candidates, target_rgb, ColorSolverMode::V2);
if (!surface_to_deep.empty()) {
out.bottom_to_top.reserve(surface_to_deep.size());
auto append_component = [this, &out](uint16_t component_idx) {
if (size_t(component_idx) < m_component_ids.size())
out.bottom_to_top.emplace_back(m_component_ids[size_t(component_idx)]);
};
if (lower_surface) {
for (uint16_t component_idx : surface_to_deep)
append_component(component_idx);
} else {
for (auto it = surface_to_deep.rbegin(); it != surface_to_deep.rend(); ++it)
append_component(*it);
}
if (!out.bottom_to_top.empty())
return out;
}
}
const std::vector<Candidate> &candidates = candidates_for_depth(depth);
if (candidates.empty())
return out;
@@ -492,18 +615,23 @@ TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::arr
if (int(out.bottom_to_top.size()) > depth)
out.bottom_to_top.resize(size_t(depth));
arrange_stack_for_light_path(out.bottom_to_top, target_rgb);
if (lower_surface && m_td_adjustment_enabled)
std::reverse(out.bottom_to_top.begin(), out.bottom_to_top.end());
return out;
}
unsigned int TextureMappingContoningSolver::component_for_depth(const std::array<float, 3> &target_rgb,
int stack_layers,
int depth_from_top) const
int depth_from_top,
bool lower_surface) const
{
TextureMappingContoningStack stack = solve(target_rgb, stack_layers);
TextureMappingContoningStack stack = solve(target_rgb, stack_layers, lower_surface);
if (stack.bottom_to_top.empty())
return 0;
const int depth = int(stack.bottom_to_top.size());
const int idx = std::clamp(depth - 1 - depth_from_top, 0, depth - 1);
const int idx = lower_surface ?
std::clamp(depth_from_top, 0, depth - 1) :
std::clamp(depth - 1 - depth_from_top, 0, depth - 1);
return stack.bottom_to_top[size_t(idx)];
}

View File

@@ -3,10 +3,14 @@
#ifndef slic3r_TextureMappingContoning_hpp_
#define slic3r_TextureMappingContoning_hpp_
#include "ColorSolver.hpp"
#include "TextureMapping.hpp"
#include <array>
#include <cstdint>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <vector>
@@ -24,14 +28,16 @@ public:
TextureMappingContoningSolver() = default;
TextureMappingContoningSolver(const TextureMappingZone &zone,
const PrintConfig &config,
std::vector<unsigned int> component_ids);
std::vector<unsigned int> component_ids,
float layer_height_mm = 0.f);
bool valid() const { return !m_component_ids.empty() && m_component_ids.size() == m_component_colors.size(); }
const std::vector<unsigned int>& component_ids() const { return m_component_ids; }
const std::vector<unsigned int>& components_bottom_to_top() const { return m_components_bottom_to_top; }
TextureMappingContoningStack solve(const std::array<float, 3> &target_rgb, int stack_layers) const;
unsigned int component_for_depth(const std::array<float, 3> &target_rgb, int stack_layers, int depth_from_top) const;
TextureMappingContoningStack solve(const std::array<float, 3> &target_rgb, int stack_layers, bool lower_surface = false) const;
unsigned int component_for_depth(const std::array<float, 3> &target_rgb, int stack_layers, int depth_from_top, bool lower_surface = false) const;
std::optional<std::array<float, 3>> stack_rgb(const std::vector<unsigned int> &bottom_to_top, bool lower_surface = false) const;
private:
struct Candidate {
@@ -44,13 +50,20 @@ private:
const std::vector<Candidate>& candidates_for_depth(int stack_layers) const;
void arrange_stack_for_light_path(std::vector<unsigned int> &bottom_to_top,
const std::array<float, 3> &target_rgb) const;
std::optional<size_t> component_index(unsigned int component_id) const;
std::vector<unsigned int> m_component_ids;
std::vector<unsigned int> m_components_bottom_to_top;
std::vector<std::array<float, 3>> m_component_colors;
std::array<float, 3> m_background_rgb { { 0.f, 0.f, 0.f } };
std::vector<float> m_component_luminance;
std::vector<float> m_effective_transmission_distances_mm;
std::vector<float> m_component_layer_opacity;
float m_layer_height_mm { 0.2f };
bool m_td_adjustment_enabled { false };
mutable std::map<int, std::vector<Candidate>> m_candidates_by_depth;
mutable std::shared_ptr<ColorSolverOrderedStackCandidateCache> m_ordered_candidate_cache { std::make_shared<ColorSolverOrderedStackCandidateCache>() };
mutable std::shared_ptr<std::mutex> m_ordered_candidate_cache_mutex { std::make_shared<std::mutex>() };
};
std::vector<unsigned int> texture_mapping_contoning_components_bottom_to_top(const TextureMappingZone &zone,

View File

@@ -45,8 +45,10 @@ constexpr size_t k_temporary_simulated_texture_preview_min_source_pixels = k_tem
constexpr size_t k_surface_gradient_preview_max_components = 10;
constexpr size_t k_surface_gradient_preview_lut_size = 33;
constexpr size_t k_contoning_flat_surface_preview_max_candidates = 250000;
constexpr size_t k_contoning_flat_surface_preview_max_ordered_candidates = 2500000;
constexpr double k_contoning_top_surface_preview_lod_max_samples = 350000.0;
constexpr const char *TEXTURE_MAPPING_BACKGROUND_COLOR_CONFIG_KEY = "texture_mapping_background_color";
constexpr float k_contoning_preview_inferred_black_td_mm = 0.1f;
struct TexturePreviewMixCandidate
{
@@ -90,9 +92,13 @@ struct TexturePreviewSimulationSettings
int generic_solver_mix_model = TextureMappingZone::DefaultGenericSolverMixModel;
bool contoning_flat_surface_quantization = false;
bool contoning_flat_surface_pattern_blend = false;
bool contoning_flat_surface_td_adjustment = false;
int contoning_flat_surface_pattern_filaments = TextureMappingZone::DefaultTopSurfaceContoningPatternFilaments;
bool simulate_top_surface_lod = false;
float top_surface_lod_pitch_mm = 0.f;
float contoning_flat_surface_layer_height_mm = 0.2f;
std::array<float, 3> contoning_flat_surface_background_rgb { { 0.f, 0.f, 0.f } };
std::vector<float> contoning_flat_surface_layer_opacities;
float minimum_visibility_offset_factor = 0.f;
std::vector<unsigned int> component_ids;
std::vector<std::array<float, 3>> component_colors;
@@ -521,6 +527,82 @@ float contoning_flat_surface_lod_pitch_for_texture_preview(const TextureMappingZ
return std::clamp(pitch, 0.25f, std::max(0.25f, min_feature));
}
float texture_preview_layer_height_mm()
{
return std::clamp(texture_preview_config_float("layer_height", 0.2f), 0.01f, 2.f);
}
float texture_preview_filament_luminance(const std::array<float, 3> &color)
{
return 0.2126f * clamp01(color[0]) + 0.7152f * clamp01(color[1]) + 0.0722f * clamp01(color[2]);
}
bool texture_preview_black_role_component(int filament_color_mode, size_t component_idx, size_t component_count)
{
switch (std::clamp(filament_color_mode,
int(TextureMappingZone::FilamentColorAny),
int(TextureMappingZone::FilamentColorRGBKW))) {
case int(TextureMappingZone::FilamentColorCMYK):
case int(TextureMappingZone::FilamentColorRGBK):
return component_count == 4 && component_idx == 3;
case int(TextureMappingZone::FilamentColorBW):
return component_count == 2 && component_idx == 0;
case int(TextureMappingZone::FilamentColorCMYKW):
case int(TextureMappingZone::FilamentColorRGBKW):
return component_count == 5 && component_idx == 3;
default:
return false;
}
}
bool texture_preview_black_color_filament(const std::array<float, 3> &color)
{
const float r = clamp01(color[0]);
const float g = clamp01(color[1]);
const float b = clamp01(color[2]);
const float max_channel = std::max({ r, g, b });
const float min_channel = std::min({ r, g, b });
const float luminance = texture_preview_filament_luminance(color);
return max_channel <= 0.20f && luminance <= 0.12f && max_channel - min_channel <= 0.08f;
}
float texture_preview_estimated_transmission_distance_mm(const std::array<float, 3> &color)
{
return std::clamp(0.1f + 2.9f * std::pow(texture_preview_filament_luminance(color), 1.35f), 0.12f, 3.f);
}
float texture_preview_layer_opacity_from_td(float td_mm, float layer_height_mm)
{
const float safe_td = std::clamp(td_mm, 0.01f, 50.f);
const float safe_layer_height = std::clamp(layer_height_mm, 0.01f, 2.f);
return std::clamp(1.f - std::pow(0.05f, safe_layer_height / safe_td), 1e-4f, 0.9999f);
}
std::vector<float> contoning_flat_surface_preview_layer_opacities(
const TextureMappingZone &zone,
const TexturePreviewSimulationSettings &settings)
{
std::vector<float> out;
out.reserve(settings.component_ids.size());
for (size_t idx = 0; idx < settings.component_ids.size() && idx < settings.component_colors.size(); ++idx) {
const unsigned int component_id = settings.component_ids[idx];
float td_mm = 0.f;
if (component_id > 0 && size_t(component_id - 1) < zone.filament_transmission_distances_mm.size()) {
const float explicit_td = zone.filament_transmission_distances_mm[size_t(component_id - 1)];
if (std::isfinite(explicit_td) && explicit_td > 0.f)
td_mm = explicit_td;
}
if (td_mm <= 0.f &&
(texture_preview_black_role_component(zone.filament_color_mode, idx, settings.component_ids.size()) ||
texture_preview_black_color_filament(settings.component_colors[idx])))
td_mm = k_contoning_preview_inferred_black_td_mm;
if (td_mm <= 0.f)
td_mm = texture_preview_estimated_transmission_distance_mm(settings.component_colors[idx]);
out.emplace_back(texture_preview_layer_opacity_from_td(td_mm, settings.contoning_flat_surface_layer_height_mm));
}
return out;
}
std::array<float, 2> raw_offset_visibility_factors_for_texture_preview(const TextureMappingZone &zone)
{
const float base_outer_width_mm =
@@ -2401,12 +2483,23 @@ std::array<float, 3> fallback_contoning_flat_surface_rgb_for_texture_preview(
std::array<float, 3> contoning_flat_surface_rgb_for_texture_preview(
const TexturePreviewSimulationSettings &settings,
const std::array<float, 4> &sample_rgba,
const ColorSolverOrderedStackCandidateSet &ordered_candidates,
const std::vector<TexturePreviewMixCandidate> &candidates,
const std::vector<TexturePreviewMixCandidateKdNode> &nodes,
int root,
int total_units)
{
const std::array<float, 3> target_rgb = texture_preview_target_rgb(settings, sample_rgba);
if (settings.contoning_flat_surface_td_adjustment && !ordered_candidates.empty()) {
const std::vector<uint16_t> surface_to_deep =
solve_color_solver_ordered_stack_for_target(ordered_candidates, target_rgb, ColorSolverMode::V2);
if (!surface_to_deep.empty())
return mix_color_solver_ordered_stack(settings.component_colors,
surface_to_deep,
settings.contoning_flat_surface_layer_opacities,
settings.contoning_flat_surface_background_rgb,
ColorSolverMixModel::PigmentPainter);
}
if (!candidates.empty()) {
const TexturePreviewMixNearestResult nearest =
nearest_contoning_flat_surface_mix_candidate(candidates, nodes, root, target_rgb);
@@ -2544,6 +2637,7 @@ std::optional<TexturePreviewSimulationSettings> texture_preview_simulation_setti
int(TextureMappingZone::GenericSolverLegacy),
int(TextureMappingZone::GenericSolverV2));
settings.generic_solver_mix_model = TextureMappingZone::DefaultGenericSolverMixModel;
settings.contoning_flat_surface_layer_height_mm = texture_preview_layer_height_mm();
if (zone->top_surface_image_printing_enabled) {
const int stack_layers =
std::clamp(zone->top_surface_contoning_stack_layers,
@@ -2554,6 +2648,8 @@ std::optional<TexturePreviewSimulationSettings> texture_preview_simulation_setti
TextureMappingZone::MinTopSurfaceContoningPatternFilaments,
TextureMappingZone::MaxTopSurfaceContoningPatternFilaments);
settings.contoning_flat_surface_pattern_filaments = std::max(1, std::min(stack_layers, pattern_filaments));
settings.contoning_flat_surface_td_adjustment =
zone->top_surface_contoning_active() && zone->top_surface_contoning_td_adjustment_enabled;
}
settings.minimum_visibility_offset_factor = zone->minimum_visibility_offset_enabled ?
std::clamp((std::isfinite(zone->minimum_visibility_offset_pct) ?
@@ -2597,6 +2693,16 @@ std::optional<TexturePreviewSimulationSettings> texture_preview_simulation_setti
settings.component_minimum_offset_factors.emplace_back(std::clamp(safe_minimum_offset_pct / 100.f, 0.f, 1.f));
}
if (settings.contoning_flat_surface_td_adjustment && !settings.component_colors.empty()) {
std::vector<float> background_weights(settings.component_colors.size(), 1.f / float(settings.component_colors.size()));
settings.contoning_flat_surface_background_rgb =
mix_color_solver_components(settings.component_colors, background_weights, ColorSolverMixModel::PigmentPainter);
settings.contoning_flat_surface_layer_opacities =
contoning_flat_surface_preview_layer_opacities(*zone, settings);
if (settings.contoning_flat_surface_layer_opacities.size() != settings.component_colors.size())
settings.contoning_flat_surface_td_adjustment = false;
}
const std::array<float, 2> raw_offset_visibility_factors = raw_offset_visibility_factors_for_texture_preview(*zone);
settings.raw_offset_base_visibility_factor = raw_offset_visibility_factors[0];
settings.raw_offset_visibility_range_factor = raw_offset_visibility_factors[1];
@@ -2632,12 +2738,18 @@ size_t texture_preview_simulation_signature(const ModelVolume &model_volume,
mix(std::hash<int>{}(settings.generic_solver_mix_model));
mix(std::hash<int>{}(settings.contoning_flat_surface_quantization ? 1 : 0));
mix(std::hash<int>{}(settings.contoning_flat_surface_pattern_blend ? 1 : 0));
mix(std::hash<int>{}(settings.contoning_flat_surface_td_adjustment ? 1 : 0));
if (settings.contoning_flat_surface_quantization) {
mix(std::hash<int>{}(settings.contoning_flat_surface_pattern_filaments));
mix(std::hash<int>{}(settings.simulate_top_surface_lod ? 1 : 0));
if (settings.simulate_top_surface_lod)
mix(std::hash<int>{}(int(std::lround(settings.top_surface_lod_pitch_mm * 100000.f))));
}
if (settings.contoning_flat_surface_td_adjustment) {
mix(std::hash<int>{}(int(std::lround(settings.contoning_flat_surface_layer_height_mm * 100000.f))));
for (const float opacity : settings.contoning_flat_surface_layer_opacities)
mix(std::hash<int>{}(int(std::lround(opacity * 1000000.f))));
}
mix(std::hash<int>{}(int(std::lround(settings.contrast_pct * 100.f))));
mix(std::hash<int>{}(int(std::lround(settings.tone_gamma * 1000.f))));
mix(std::hash<int>{}(int(std::lround(settings.minimum_visibility_offset_factor * 100000.f))));
@@ -2709,8 +2821,21 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig
settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) &&
!settings.component_colors.empty() &&
contoning_flat_surface_pattern_filaments > 0;
const bool use_contoning_flat_surface_ordered_quantization =
use_contoning_flat_surface_quantization &&
settings.contoning_flat_surface_td_adjustment &&
settings.contoning_flat_surface_layer_opacities.size() == settings.component_colors.size();
const ColorSolverOrderedStackCandidateSet contoning_flat_surface_ordered_candidates =
use_contoning_flat_surface_ordered_quantization ?
build_color_solver_ordered_stack_candidates(settings.component_colors,
settings.contoning_flat_surface_layer_opacities,
settings.contoning_flat_surface_background_rgb,
ColorSolverMixModel::PigmentPainter,
contoning_flat_surface_pattern_filaments,
k_contoning_flat_surface_preview_max_ordered_candidates) :
ColorSolverOrderedStackCandidateSet{};
const std::vector<TexturePreviewMixCandidate> contoning_flat_surface_candidates =
use_contoning_flat_surface_quantization ?
use_contoning_flat_surface_quantization && contoning_flat_surface_ordered_candidates.empty() ?
build_contoning_flat_surface_mix_candidates(settings.component_colors, contoning_flat_surface_pattern_filaments) :
std::vector<TexturePreviewMixCandidate>{};
std::vector<TexturePreviewMixCandidateKdNode> contoning_flat_surface_nodes;
@@ -2928,6 +3053,7 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig
forced_simulated_rgb =
contoning_flat_surface_rgb_for_texture_preview(settings,
sample_rgba,
contoning_flat_surface_ordered_candidates,
contoning_flat_surface_candidates,
contoning_flat_surface_nodes,
contoning_flat_surface_root,
@@ -5513,6 +5639,7 @@ static size_t texture_preview_settings_signature_impl(size_t num_physical,
signature_mix(std::hash<int>{}(zone.top_surface_contoning_stack_layers));
signature_mix(std::hash<int>{}(zone.top_surface_contoning_pattern_filaments));
signature_mix(std::hash<int>{}(zone.top_surface_contoning_color_lower_surfaces ? 1 : 0));
signature_mix(std::hash<int>{}(zone.top_surface_contoning_td_adjustment_enabled ? 1 : 0));
signature_mix(std::hash<int>{}(zone.nonlinear_offset_adjustment ? 1 : 0));
signature_mix(std::hash<int>{}(zone.compact_offset_mode ? 1 : 0));
signature_mix(std::hash<int>{}(zone.use_legacy_fixed_color_mode ? 1 : 0));
@@ -5533,6 +5660,8 @@ static size_t texture_preview_settings_signature_impl(size_t num_physical,
signature_mix_float(strength_pct, 100.f);
for (const float minimum_offset_pct : zone.filament_minimum_offsets_pct)
signature_mix_float(minimum_offset_pct, 100.f);
for (const float td_mm : zone.filament_transmission_distances_mm)
signature_mix_float(td_mm, 1000.f);
}
return signature;
}
@@ -5663,6 +5792,7 @@ static void texture_preview_mix_zone_baked_model_settings(size_t &signature,
signature_mix(std::hash<int>{}(zone.top_surface_contoning_stack_layers));
signature_mix(std::hash<int>{}(zone.top_surface_contoning_pattern_filaments));
signature_mix(std::hash<int>{}(zone.top_surface_contoning_color_lower_surfaces ? 1 : 0));
signature_mix(std::hash<int>{}(zone.top_surface_contoning_td_adjustment_enabled ? 1 : 0));
signature_mix(std::hash<int>{}(zone.nonlinear_offset_adjustment ? 1 : 0));
signature_mix(std::hash<int>{}(zone.compact_offset_mode ? 1 : 0));
signature_mix(std::hash<int>{}(zone.use_legacy_fixed_color_mode ? 1 : 0));
@@ -5687,6 +5817,8 @@ static void texture_preview_mix_zone_baked_model_settings(size_t &signature,
signature_mix_float(strength_pct, 100.f);
for (const float minimum_offset_pct : zone.filament_minimum_offsets_pct)
signature_mix_float(minimum_offset_pct, 100.f);
for (const float td_mm : zone.filament_transmission_distances_mm)
signature_mix_float(td_mm, 1000.f);
if (zone.is_surface_gradient()) {
signature_mix_float(texture_preview_config_float("texture_mapping_outer_wall_gradient_global_strength", 100.f), 100.f);

View File

@@ -1984,6 +1984,7 @@ public:
bool top_surface_contoning_polygonize_color_regions_enabled,
int top_surface_contoning_polygonize_resolution,
bool top_surface_contoning_surface_anchored_stacks_enabled,
bool top_surface_contoning_td_adjustment_enabled,
const TextureMappingManager &texture_mapping_zones,
const TextureMappingGlobalSettings &global_settings,
const TextureMappingPrimeTowerImage &prime_tower_image,
@@ -2199,7 +2200,7 @@ public:
auto *filament_calibration_note =
new wxStaticText(filament_page,
wxID_ANY,
_L("NOTE: Filament/TD calibration is not currently used in top-surface coloring"),
_L("NOTE: TD calibration is used by overhang modulation and Contoning TD adjustment"),
wxDefaultPosition,
wxSize(FromDIP(390), -1));
filament_calibration_note->Wrap(FromDIP(390));
@@ -2662,6 +2663,15 @@ public:
0,
wxEXPAND | wxTOP | wxBOTTOM,
gap / 2);
m_top_surface_contoning_td_adjustment_checkbox =
new wxCheckBox(m_top_surface_contoning_checkboxes_panel, wxID_ANY, _L("TD adjustment"));
m_top_surface_contoning_td_adjustment_checkbox->SetValue(top_surface_contoning_td_adjustment_enabled);
m_top_surface_contoning_td_adjustment_checkbox->SetMinSize(
wxSize(-1, std::max(m_top_surface_contoning_td_adjustment_checkbox->GetBestSize().GetHeight(), FromDIP(24))));
contoning_checkboxes_root->Add(m_top_surface_contoning_td_adjustment_checkbox,
0,
wxEXPAND | wxTOP | wxBOTTOM,
gap / 2);
m_top_surface_contoning_only_one_perimeter_around_shell_infill_checkbox =
new wxCheckBox(m_top_surface_contoning_checkboxes_panel, wxID_ANY, _L("Only one perimeter around shell infill"));
m_top_surface_contoning_only_one_perimeter_around_shell_infill_checkbox->SetValue(
@@ -3267,6 +3277,12 @@ public:
TextureMappingZone::DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled :
m_top_surface_contoning_surface_anchored_stacks_checkbox->GetValue();
}
bool top_surface_contoning_td_adjustment_enabled() const
{
return m_top_surface_contoning_td_adjustment_checkbox == nullptr ?
TextureMappingZone::DefaultTopSurfaceContoningTdAdjustmentEnabled :
m_top_surface_contoning_td_adjustment_checkbox->GetValue();
}
bool minimum_visibility_offset_enabled() const
{
return m_minimum_visibility_offset_checkbox && m_minimum_visibility_offset_checkbox->GetValue();
@@ -3723,6 +3739,10 @@ private:
m_top_surface_contoning_color_lower_surfaces_checkbox->Show(contoning);
m_top_surface_contoning_color_lower_surfaces_checkbox->Enable(contoning);
}
if (m_top_surface_contoning_td_adjustment_checkbox != nullptr) {
m_top_surface_contoning_td_adjustment_checkbox->Show(contoning);
m_top_surface_contoning_td_adjustment_checkbox->Enable(contoning);
}
if (m_top_surface_contoning_only_one_perimeter_around_shell_infill_checkbox != nullptr) {
m_top_surface_contoning_only_one_perimeter_around_shell_infill_checkbox->Show(contoning);
m_top_surface_contoning_only_one_perimeter_around_shell_infill_checkbox->Enable(contoning);
@@ -3856,6 +3876,7 @@ private:
wxPanel *m_top_surface_contoning_polygonize_resolution_panel {nullptr};
wxChoice *m_top_surface_contoning_polygonize_resolution_choice {nullptr};
wxCheckBox *m_top_surface_contoning_surface_anchored_stacks_checkbox {nullptr};
wxCheckBox *m_top_surface_contoning_td_adjustment_checkbox {nullptr};
wxCheckBox *m_use_legacy_fixed_color_mode_checkbox {nullptr};
wxCheckBox *m_minimum_visibility_offset_checkbox {nullptr};
wxSpinCtrl *m_minimum_visibility_offset_spin {nullptr};
@@ -8880,6 +8901,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
updated.top_surface_contoning_polygonize_color_regions_enabled,
updated.top_surface_contoning_polygonize_resolution,
updated.top_surface_contoning_surface_anchored_stacks_enabled,
updated.top_surface_contoning_td_adjustment_enabled,
bundle->texture_mapping_zones,
bundle->texture_mapping_global_settings,
wxGetApp().model().texture_mapping_prime_tower_image,
@@ -8955,6 +8977,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
dlg.top_surface_contoning_polygonize_resolution();
updated.top_surface_contoning_surface_anchored_stacks_enabled =
dlg.top_surface_contoning_surface_anchored_stacks_enabled();
updated.top_surface_contoning_td_adjustment_enabled =
dlg.top_surface_contoning_td_adjustment_enabled();
if (updated.top_surface_image_printing_enabled &&
updated.top_surface_image_printing_method == int(TextureMappingZone::TopSurfaceImageContoning)) {
updated.modulation_mode = int(TextureMappingZone::ModulationPerimeterPathV2);