Support deeper stacks in top-surface coloring

This commit is contained in:
sentientstardust
2026-05-29 15:55:28 +01:00
parent c831e78177
commit 6cd8fec603
8 changed files with 450 additions and 54 deletions

View File

@@ -23,6 +23,7 @@
#include <functional>
#include <limits>
#include <sstream>
#include <utility>
namespace Slic3r {
namespace {
@@ -355,6 +356,176 @@ size_t ordered_stack_candidate_count(size_t component_count, int stack_depth, si
return candidate_limit > 0 && count > candidate_limit ? 0 : count;
}
size_t ordered_stack_candidate_storage_limit(int stack_depth, size_t candidate_limit, size_t stack_item_limit)
{
size_t limit = candidate_limit > 0 ? candidate_limit : std::numeric_limits<size_t>::max();
if (stack_item_limit > 0 && stack_depth > 0)
limit = std::min(limit, stack_item_limit / size_t(stack_depth));
return limit;
}
int ordered_stack_control_depth(size_t component_count, int stack_depth, size_t candidate_limit)
{
if (component_count == 0 || stack_depth <= 0 || candidate_limit == 0)
return 0;
size_t count = 1;
int depth = 0;
for (int idx = 0; idx < stack_depth; ++idx) {
if (count > candidate_limit / component_count)
break;
count *= component_count;
++depth;
}
return depth > 0 ? depth : 1;
}
std::vector<uint16_t> repeat_ordered_stack_to_depth(const std::vector<uint16_t> &surface_to_deep, int depth)
{
std::vector<uint16_t> out;
if (surface_to_deep.empty() || depth <= 0)
return out;
out.reserve(size_t(depth));
for (int idx = 0; idx < depth; ++idx)
out.emplace_back(surface_to_deep[size_t(idx) % surface_to_deep.size()]);
return out;
}
void append_unique_ordered_stack_variant(std::vector<std::vector<uint16_t>> &variants, std::vector<uint16_t> candidate)
{
if (candidate.empty())
return;
if (std::find(variants.begin(), variants.end(), candidate) == variants.end())
variants.emplace_back(std::move(candidate));
}
std::vector<uint16_t> stretch_ordered_stack_tiled(const std::vector<uint16_t> &control, int stack_depth)
{
return repeat_ordered_stack_to_depth(control, stack_depth);
}
std::vector<uint16_t> stretch_ordered_stack_proportional(const std::vector<uint16_t> &control, int stack_depth)
{
std::vector<uint16_t> out;
if (control.empty() || stack_depth <= 0)
return out;
out.reserve(size_t(stack_depth));
const size_t control_depth = control.size();
for (int idx = 0; idx < stack_depth; ++idx) {
const size_t source_idx = std::min(control_depth - 1, size_t(idx) * control_depth / size_t(stack_depth));
out.emplace_back(control[source_idx]);
}
return out;
}
std::vector<uint16_t> stretch_ordered_stack_by_scores(const std::vector<uint16_t> &control,
const std::vector<float> &scores,
int stack_depth)
{
std::vector<uint16_t> out;
if (control.empty() || stack_depth <= 0)
return out;
if (int(control.size()) >= stack_depth) {
out.assign(control.begin(), control.begin() + std::min(control.size(), size_t(stack_depth)));
return out;
}
std::vector<int> duplicates(control.size(), 0);
const int extra = stack_depth - int(control.size());
for (int idx = 0; idx < extra; ++idx) {
size_t best_idx = 0;
float best_score = -std::numeric_limits<float>::max();
for (size_t score_idx = 0; score_idx < control.size(); ++score_idx) {
const float base_score =
score_idx < scores.size() && std::isfinite(scores[score_idx]) ?
std::max(scores[score_idx], 0.f) :
0.f;
const float score = base_score / float(duplicates[score_idx] + 1);
if (score > best_score) {
best_score = score;
best_idx = score_idx;
}
}
++duplicates[best_idx];
}
out.reserve(size_t(stack_depth));
for (size_t idx = 0; idx < control.size(); ++idx) {
out.emplace_back(control[idx]);
for (int duplicate_idx = 0; duplicate_idx < duplicates[idx]; ++duplicate_idx)
out.emplace_back(control[idx]);
}
return out;
}
std::vector<std::vector<uint16_t>> stretched_ordered_stack_variants(const std::vector<uint16_t> &control,
const std::vector<float> &layer_opacities,
int stack_depth)
{
std::vector<std::vector<uint16_t>> variants;
if (control.empty() || stack_depth <= 0)
return variants;
if (int(control.size()) >= stack_depth) {
std::vector<uint16_t> variant(control.begin(), control.begin() + std::min(control.size(), size_t(stack_depth)));
append_unique_ordered_stack_variant(variants, std::move(variant));
return variants;
}
append_unique_ordered_stack_variant(variants, stretch_ordered_stack_tiled(control, stack_depth));
append_unique_ordered_stack_variant(variants, stretch_ordered_stack_proportional(control, stack_depth));
std::vector<float> even(control.size(), 1.f);
std::vector<float> opacity(control.size(), 1.f);
std::vector<float> surface(control.size(), 1.f);
std::vector<float> deep(control.size(), 1.f);
for (size_t idx = 0; idx < control.size(); ++idx) {
const size_t component_idx = size_t(control[idx]);
const float layer_opacity =
component_idx < layer_opacities.size() && std::isfinite(layer_opacities[component_idx]) ?
std::clamp(layer_opacities[component_idx], 1e-4f, 0.9999f) :
0.5f;
opacity[idx] = 1.f - layer_opacity;
surface[idx] = float(control.size() - idx);
deep[idx] = float(idx + 1);
}
append_unique_ordered_stack_variant(variants, stretch_ordered_stack_by_scores(control, even, stack_depth));
append_unique_ordered_stack_variant(variants, stretch_ordered_stack_by_scores(control, opacity, stack_depth));
append_unique_ordered_stack_variant(variants, stretch_ordered_stack_by_scores(control, surface, stack_depth));
append_unique_ordered_stack_variant(variants, stretch_ordered_stack_by_scores(control, deep, stack_depth));
for (size_t idx = 0; idx < control.size(); ++idx) {
surface[idx] *= opacity[idx];
deep[idx] *= opacity[idx];
}
append_unique_ordered_stack_variant(variants, stretch_ordered_stack_by_scores(control, surface, stack_depth));
append_unique_ordered_stack_variant(variants, stretch_ordered_stack_by_scores(control, deep, stack_depth));
return variants;
}
void append_ordered_stack_candidate(ColorSolverOrderedStackCandidateSet &candidates,
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,
int simulated_stack_depth)
{
std::vector<uint16_t> simulated_surface_to_deep;
const std::vector<uint16_t> *mix_stack = &surface_to_deep;
if (simulated_stack_depth > 0 && simulated_stack_depth != int(surface_to_deep.size())) {
simulated_surface_to_deep = repeat_ordered_stack_to_depth(surface_to_deep, simulated_stack_depth);
mix_stack = &simulated_surface_to_deep;
}
const std::array<float, 3> mixed =
mix_ordered_stack_with_buffers(colors_with_background, weights, *mix_stack, 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());
}
} // namespace
ColorSolverMixModel color_solver_mix_model_from_index(int model)
@@ -552,13 +723,17 @@ std::string color_solver_ordered_stack_candidate_cache_key(const std::vector<std
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model,
int stack_depth,
size_t candidate_limit)
int simulated_stack_depth,
size_t candidate_limit,
size_t stack_item_limit)
{
std::ostringstream key;
key << component_colors.size();
key << "|mx" << mix_model_index(mix_model);
key << "|sd" << stack_depth;
key << "|vd" << simulated_stack_depth;
key << "|lim" << candidate_limit;
key << "|item" << stack_item_limit;
key << "|bg"
<< int(std::lround(clamp01(background_rgb[0]) * 65535.f)) << ','
<< int(std::lround(clamp01(background_rgb[1]) * 65535.f)) << ','
@@ -586,47 +761,101 @@ ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates(
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model,
int stack_depth,
size_t candidate_limit)
int simulated_stack_depth,
size_t candidate_limit,
size_t stack_item_limit)
{
(void) mix_model;
ColorSolverOrderedStackCandidateSet candidates;
if (component_colors.empty() || stack_depth <= 0 || component_colors.size() > size_t(std::numeric_limits<uint16_t>::max()))
int simulated_depth = simulated_stack_depth > 0 ? simulated_stack_depth : stack_depth;
if (component_colors.empty() || stack_depth <= 0 || simulated_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)
stack_depth = std::min(stack_depth, simulated_depth);
const size_t max_candidate_count = ordered_stack_candidate_storage_limit(stack_depth, candidate_limit, stack_item_limit);
if (max_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));
candidates.simulated_stack_depth = simulated_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);
const size_t exact_candidate_count = ordered_stack_candidate_count(component_count, stack_depth, candidate_limit);
if (exact_candidate_count > 0 && exact_candidate_count <= max_candidate_count) {
candidates.rgbs.reserve(exact_candidate_count * 3);
candidates.perceptual_coords.reserve(exact_candidate_count * 3);
candidates.stacks.reserve(exact_candidate_count * size_t(stack_depth));
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) {
append_ordered_stack_candidate(candidates,
colors_with_background,
weights,
surface_to_deep,
layer_opacities,
simulated_depth);
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;
}
if (candidate_limit == 0 && stack_item_limit == 0)
return candidates;
const size_t variant_budget = 8;
const size_t max_control_candidates = std::max<size_t>(1, max_candidate_count / variant_budget);
const int control_depth = std::min(stack_depth, ordered_stack_control_depth(component_count, stack_depth, max_control_candidates));
if (control_depth <= 0)
return candidates;
const size_t control_candidate_count = ordered_stack_candidate_count(component_count, control_depth, 0);
const size_t reserve_count = std::min(max_candidate_count, control_candidate_count * variant_budget);
candidates.rgbs.reserve(reserve_count * 3);
candidates.perceptual_coords.reserve(reserve_count * 3);
candidates.stacks.reserve(reserve_count * size_t(stack_depth));
bool limit_reached = false;
std::vector<uint16_t> control(size_t(control_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());
if (limit_reached)
return;
if (depth_idx == control_depth) {
std::vector<std::vector<uint16_t>> variants =
stretched_ordered_stack_variants(control, layer_opacities, stack_depth);
for (const std::vector<uint16_t> &surface_to_deep : variants) {
if (candidates.rgbs.size() / 3 >= max_candidate_count) {
limit_reached = true;
return;
}
append_ordered_stack_candidate(candidates,
colors_with_background,
weights,
surface_to_deep,
layer_opacities,
simulated_depth);
}
return;
}
for (size_t component_idx = 0; component_idx < component_count; ++component_idx) {
surface_to_deep[size_t(depth_idx)] = uint16_t(component_idx);
control[size_t(depth_idx)] = uint16_t(component_idx);
recurse(depth_idx + 1);
if (limit_reached)
return;
}
};
recurse(0);
@@ -641,7 +870,9 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model,
int stack_depth,
size_t candidate_limit)
int simulated_stack_depth,
size_t candidate_limit,
size_t stack_item_limit)
{
const std::string key =
color_solver_ordered_stack_candidate_cache_key(component_colors,
@@ -649,7 +880,9 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates
background_rgb,
mix_model,
stack_depth,
candidate_limit);
simulated_stack_depth,
candidate_limit,
stack_item_limit);
auto it = cache.find(key);
if (it != cache.end())
return it->second;
@@ -660,7 +893,9 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates
background_rgb,
mix_model,
stack_depth,
candidate_limit)).first->second;
simulated_stack_depth,
candidate_limit,
stack_item_limit)).first->second;
}
std::vector<uint16_t> solve_color_solver_ordered_stack_for_target(

View File

@@ -74,6 +74,7 @@ struct ColorSolverOrderedStackCandidateSet {
size_t component_count { 0 };
int stack_depth { 0 };
int simulated_stack_depth { 0 };
std::vector<float> rgbs;
std::vector<float> perceptual_coords;
std::vector<uint16_t> stacks;
@@ -130,14 +131,18 @@ std::string color_solver_ordered_stack_candidate_cache_key(const std::vector<std
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model,
int stack_depth,
size_t candidate_limit = 0);
int simulated_stack_depth = 0,
size_t candidate_limit = 0,
size_t stack_item_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);
int simulated_stack_depth = 0,
size_t candidate_limit = 0,
size_t stack_item_limit = 0);
const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates(
ColorSolverOrderedStackCandidateCache &cache,
const std::vector<std::array<float, 3>> &component_colors,
@@ -145,7 +150,9 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates
const std::array<float, 3> &background_rgb,
ColorSolverMixModel mix_model,
int stack_depth,
size_t candidate_limit = 0);
int simulated_stack_depth = 0,
size_t candidate_limit = 0,
size_t stack_item_limit = 0);
std::vector<uint16_t> solve_color_solver_ordered_stack_for_target(
const ColorSolverOrderedStackCandidateSet &candidates,
const std::array<float, 3> &target_rgb,

View File

@@ -1952,19 +1952,21 @@ static std::optional<TopSurfaceImageContoningCellSample> top_surface_image_conto
static std::optional<TopSurfaceImageContoningSolvedLabel> top_surface_image_contoning_solve_label(
const std::array<float, 3> &rgb,
int solve_layers,
int visible_layers,
const TextureMappingContoningSolver &solver,
bool lower_surface,
std::vector<TopSurfaceImageContoningVectorLabel> &labels,
std::map<std::vector<unsigned int>, int> &label_by_stack)
std::map<std::pair<std::vector<unsigned int>, int>, int> &label_by_stack)
{
TextureMappingContoningStack stack = solver.solve(rgb, solve_layers, lower_surface);
TextureMappingContoningStack stack = solver.solve(rgb, solve_layers, lower_surface, visible_layers);
if (stack.bottom_to_top.empty())
return std::nullopt;
std::optional<std::array<float, 3>> stack_rgb =
solver.stack_rgb(stack.bottom_to_top, lower_surface);
solver.stack_rgb(stack.bottom_to_top, lower_surface, visible_layers);
if (!stack_rgb)
return std::nullopt;
auto label_it = label_by_stack.find(stack.bottom_to_top);
const auto label_key = std::make_pair(stack.bottom_to_top, std::max(0, visible_layers));
auto label_it = label_by_stack.find(label_key);
int label = -1;
if (label_it == label_by_stack.end()) {
TopSurfaceImageContoningVectorLabel label_data;
@@ -1973,7 +1975,7 @@ static std::optional<TopSurfaceImageContoningSolvedLabel> top_surface_image_cont
label_data.oklab = color_solver_oklab_from_srgb(*stack_rgb);
label = int(labels.size());
labels.emplace_back(std::move(label_data));
label_by_stack.emplace(labels.back().bottom_to_top, label);
label_by_stack.emplace(label_key, label);
} else {
label = label_it->second;
}
@@ -2104,12 +2106,13 @@ static std::vector<TopSurfaceImageContoningVectorRegion> top_surface_image_conto
std::vector<int> grid(size_t(cols) * size_t(rows), -1);
std::vector<TopSurfaceImageContoningVectorLabel> labels;
std::map<std::vector<unsigned int>, int> label_by_stack;
std::map<std::pair<std::vector<unsigned int>, int>, int> label_by_stack;
auto solve_cell = [&](int row, int col, const std::array<float, 3> &target_rgb, int solve_layers) {
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,
available_depth,
solver,
source_surface == TopSurfaceImageSourceSurface::Bottom &&
plan.contoning_td_adjustment_enabled,
@@ -2178,7 +2181,7 @@ static std::vector<TopSurfaceImageContoningVectorRegion> top_surface_image_conto
std::clamp(sample->rgb[2] + errors[grid_idx][2] + top_surface_image_contoning_jitter(col, row, depth, 2), 0.f, 1.f)
};
const std::optional<TopSurfaceImageContoningSolvedLabel> solved =
solve_cell(row, col, target_rgb, sample->solve_layers);
solve_cell(row, col, target_rgb, sample->solve_layers, sample->available_depth);
if (!solved)
continue;
const std::array<float, 3> error {
@@ -2207,7 +2210,7 @@ static std::vector<TopSurfaceImageContoningVectorRegion> top_surface_image_conto
const std::optional<TopSurfaceImageContoningCellSample> &sample = cell_samples[size_t(row * cols + col)];
if (!sample)
continue;
solve_cell(row, col, sample->rgb, sample->solve_layers);
solve_cell(row, col, sample->rgb, sample->solve_layers, sample->available_depth);
}
}
}
@@ -2294,12 +2297,13 @@ static std::shared_ptr<const TopSurfaceImageContoningStackPlan> top_surface_imag
out->rows = rows;
out->cells.assign(size_t(cols) * size_t(rows), TopSurfaceImageContoningStackPlanCell());
std::map<std::vector<unsigned int>, int> label_by_stack;
std::map<std::pair<std::vector<unsigned int>, int>, int> label_by_stack;
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,
available_depth,
solver,
source_surface == TopSurfaceImageSourceSurface::Bottom &&
plan.contoning_td_adjustment_enabled,

View File

@@ -181,10 +181,10 @@ struct TextureMappingZone
static constexpr float MaxTopSurfaceContoningAngleThresholdDeg = 180.f;
static constexpr float DefaultTopSurfaceContoningAngleThresholdDeg = 20.f;
static constexpr int MinTopSurfaceContoningStackLayers = 1;
static constexpr int MaxTopSurfaceContoningStackLayers = 20;
static constexpr int DefaultTopSurfaceContoningStackLayers = 15;
static constexpr int MaxTopSurfaceContoningStackLayers = 100;
static constexpr int DefaultTopSurfaceContoningStackLayers = 100;
static constexpr int MinTopSurfaceContoningPatternFilaments = 1;
static constexpr int MaxTopSurfaceContoningPatternFilaments = 20;
static constexpr int MaxTopSurfaceContoningPatternFilaments = 100;
static constexpr int DefaultTopSurfaceContoningPatternFilaments = 5;
static constexpr float MinTopSurfaceContoningMinFeatureMm = 0.f;
static constexpr float MaxTopSurfaceContoningMinFeatureMm = 20.f;

View File

@@ -19,6 +19,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;
constexpr size_t MAX_TD_ORDERED_CONTONING_STACK_ITEMS = 20000000;
float clamp01(float value)
{
@@ -422,7 +423,8 @@ std::optional<size_t> TextureMappingContoningSolver::component_index(unsigned in
std::optional<std::array<float, 3>> TextureMappingContoningSolver::stack_rgb(
const std::vector<unsigned int> &bottom_to_top,
bool lower_surface) const
bool lower_surface,
int visible_stack_layers) const
{
if (bottom_to_top.empty() || !valid())
return std::nullopt;
@@ -443,8 +445,13 @@ std::optional<std::array<float, 3>> TextureMappingContoningSolver::stack_rgb(
return mix_color_solver_components(colors, weights, ColorSolverMixModel::PigmentPainter);
}
const int visible_depth = visible_stack_layers > 0 ?
std::clamp(visible_stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers) :
int(bottom_to_top.size());
std::vector<uint16_t> surface_to_deep;
surface_to_deep.reserve(bottom_to_top.size());
surface_to_deep.reserve(size_t(visible_depth));
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()))
@@ -461,6 +468,13 @@ std::optional<std::array<float, 3>> TextureMappingContoningSolver::stack_rgb(
if (!append_component(*it))
return std::nullopt;
}
if (visible_depth > 0 && int(surface_to_deep.size()) != visible_depth) {
std::vector<uint16_t> repeated;
repeated.reserve(size_t(visible_depth));
for (int idx = 0; idx < visible_depth; ++idx)
repeated.emplace_back(surface_to_deep[size_t(idx) % surface_to_deep.size()]);
surface_to_deep = std::move(repeated);
}
return mix_color_solver_ordered_stack(m_component_colors,
surface_to_deep,
@@ -536,15 +550,22 @@ void TextureMappingContoningSolver::arrange_stack_for_light_path(std::vector<uns
TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::array<float, 3> &target_rgb,
int stack_layers,
bool lower_surface) const
bool lower_surface,
int visible_stack_layers) const
{
TextureMappingContoningStack out;
if (!valid())
return out;
const int depth = std::clamp(stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers);
const int requested_depth = std::clamp(stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers);
const int visible_depth = visible_stack_layers > 0 ?
std::clamp(visible_stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers) :
requested_depth;
const int depth = std::min(requested_depth, visible_depth);
if (m_td_adjustment_enabled) {
const ColorSolverOrderedStackCandidateSet *ordered_candidates = nullptr;
{
@@ -556,7 +577,9 @@ TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::arr
m_background_rgb,
ColorSolverMixModel::PigmentPainter,
depth,
MAX_TD_ORDERED_CONTONING_CANDIDATES);
visible_depth,
MAX_TD_ORDERED_CONTONING_CANDIDATES,
MAX_TD_ORDERED_CONTONING_STACK_ITEMS);
}
const std::vector<uint16_t> surface_to_deep =
solve_color_solver_ordered_stack_for_target(*ordered_candidates, target_rgb, ColorSolverMode::V2);

View File

@@ -35,9 +35,14 @@ public:
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, bool lower_surface = false) const;
TextureMappingContoningStack solve(const std::array<float, 3> &target_rgb,
int stack_layers,
bool lower_surface = false,
int visible_stack_layers = 0) 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;
std::optional<std::array<float, 3>> stack_rgb(const std::vector<unsigned int> &bottom_to_top,
bool lower_surface = false,
int visible_stack_layers = 0) const;
private:
struct Candidate {

View File

@@ -46,6 +46,7 @@ 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 size_t k_contoning_flat_surface_preview_max_ordered_stack_items = 20000000;
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;
@@ -2493,12 +2494,21 @@ std::array<float, 3> contoning_flat_surface_rgb_for_texture_preview(
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())
if (!surface_to_deep.empty()) {
std::vector<uint16_t> simulated_surface_to_deep = surface_to_deep;
if (ordered_candidates.simulated_stack_depth > 0 &&
ordered_candidates.simulated_stack_depth != int(surface_to_deep.size())) {
simulated_surface_to_deep.clear();
simulated_surface_to_deep.reserve(size_t(ordered_candidates.simulated_stack_depth));
for (int idx = 0; idx < ordered_candidates.simulated_stack_depth; ++idx)
simulated_surface_to_deep.emplace_back(surface_to_deep[size_t(idx) % surface_to_deep.size()]);
}
return mix_color_solver_ordered_stack(settings.component_colors,
surface_to_deep,
simulated_surface_to_deep,
settings.contoning_flat_surface_layer_opacities,
settings.contoning_flat_surface_background_rgb,
ColorSolverMixModel::PigmentPainter);
}
}
if (!candidates.empty()) {
const TexturePreviewMixNearestResult nearest =
@@ -2832,7 +2842,9 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig
settings.contoning_flat_surface_background_rgb,
ColorSolverMixModel::PigmentPainter,
contoning_flat_surface_pattern_filaments,
k_contoning_flat_surface_preview_max_ordered_candidates) :
contoning_flat_surface_pattern_filaments,
k_contoning_flat_surface_preview_max_ordered_candidates,
k_contoning_flat_surface_preview_max_ordered_stack_items) :
ColorSolverOrderedStackCandidateSet{};
const std::vector<TexturePreviewMixCandidate> contoning_flat_surface_candidates =
use_contoning_flat_surface_quantization && contoning_flat_surface_ordered_candidates.empty() ?

View File

@@ -6,6 +6,7 @@
#include <cstdint>
#include <algorithm>
#include <array>
#include <cmath>
#include <iomanip>
#include <numeric>
#include <limits>
@@ -20,6 +21,7 @@
#include <future>
#include <memory>
#include <optional>
#include <utility>
#include <boost/algorithm/string.hpp>
#include <boost/iterator/counting_iterator.hpp>
#include <boost/optional.hpp>
@@ -2561,7 +2563,7 @@ public:
contoning_angle_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("deg")), 0, wxALIGN_CENTER_VERTICAL);
top_surface_contoning_root->Add(contoning_angle_row, 0, wxEXPAND | wxBOTTOM, gap);
auto *contoning_layers_row = new wxBoxSizer(wxHORIZONTAL);
contoning_layers_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("Max infill/perimeter layer depth")),
contoning_layers_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("Max infill layer depth")),
0,
wxALIGN_CENTER_VERTICAL | wxRIGHT,
gap);
@@ -2599,7 +2601,11 @@ public:
TextureMappingZone::MaxTopSurfaceContoningPatternFilaments));
contoning_pattern_row->Add(m_top_surface_contoning_pattern_filaments_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2);
contoning_pattern_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("layers")), 0, wxALIGN_CENTER_VERTICAL);
top_surface_contoning_root->Add(contoning_pattern_row, 0, wxEXPAND | wxBOTTOM, gap);
top_surface_contoning_root->Add(contoning_pattern_row, 0, wxEXPAND);
m_top_surface_contoning_pattern_recommendation_text =
new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, wxEmptyString);
m_top_surface_contoning_pattern_recommendation_text->Wrap(FromDIP(520));
top_surface_contoning_root->Add(m_top_surface_contoning_pattern_recommendation_text, 0, wxEXPAND | wxTOP | wxBOTTOM, gap / 2);
auto *contoning_feature_row = new wxBoxSizer(wxHORIZONTAL);
contoning_feature_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("Minimum feature")),
0,
@@ -2841,6 +2847,21 @@ public:
update_top_surface_image_options_visibility(true);
});
}
if (m_top_surface_contoning_td_adjustment_checkbox != nullptr) {
m_top_surface_contoning_td_adjustment_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) {
update_top_surface_contoning_td_recommendation(true);
});
}
for (wxSpinCtrlDouble *spin : m_transmission_distance_spins) {
if (spin == nullptr)
continue;
spin->Bind(wxEVT_SPINCTRLDOUBLE, [this](wxSpinDoubleEvent &) {
update_top_surface_contoning_td_recommendation(true);
});
spin->Bind(wxEVT_TEXT, [this](wxCommandEvent &) {
update_top_surface_contoning_td_recommendation(false);
});
}
m_top_surface_image_printing_enabled_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) {
update_top_surface_image_options_visibility(true);
});
@@ -3588,6 +3609,93 @@ private:
}
}
float top_surface_contoning_layer_height_mm() const
{
auto read_layer_height = [](const DynamicPrintConfig &config, float &value) {
if (!config.has("layer_height"))
return false;
const ConfigOptionFloat *opt = config.option<ConfigOptionFloat>("layer_height");
if (opt == nullptr || !std::isfinite(opt->value) || opt->value <= 0.0)
return false;
value = float(opt->value);
return true;
};
float value = 0.2f;
PresetBundle *bundle = wxGetApp().preset_bundle;
if (bundle != nullptr) {
if (read_layer_height(bundle->project_config, value))
return std::clamp(value, 0.01f, 2.f);
if (read_layer_height(bundle->prints.get_edited_preset().config, value))
return std::clamp(value, 0.01f, 2.f);
}
return value;
}
int top_surface_contoning_layers_for_strength(float td_mm, float strength) const
{
if (!std::isfinite(td_mm) || td_mm <= 0.f)
return 0;
const float layer_height = top_surface_contoning_layer_height_mm();
const float safe_strength = std::clamp(strength, 0.01f, 0.99f);
const float layers = (td_mm / layer_height) * (std::log(1.f - safe_strength) / std::log(0.05f));
return std::max(1, int(std::ceil(layers)));
}
wxString top_surface_contoning_td_recommendation_text() const
{
if (!top_surface_contoning_td_adjustment_enabled() || m_transmission_distance_spins.empty())
return wxEmptyString;
int lower = 0;
int upper = 0;
int strong = 0;
bool has_translucent = false;
for (wxSpinCtrlDouble *spin : m_transmission_distance_spins) {
const double td_value = spin != nullptr ? spin->GetValue() : 0.0;
if (!std::isfinite(td_value) || td_value <= 0.0)
return wxEmptyString;
const float td_mm = float(std::clamp(td_value, 0.01, 50.0));
if (top_surface_contoning_layers_for_strength(td_mm, 0.95f) <= 3)
continue;
has_translucent = true;
lower = std::max(lower, top_surface_contoning_layers_for_strength(td_mm, 0.30f));
upper = std::max(upper, top_surface_contoning_layers_for_strength(td_mm, 0.50f));
strong = std::max(strong, top_surface_contoning_layers_for_strength(td_mm, 0.75f));
}
if (!has_translucent)
return _L("TD recommendation: 1-3 layers.");
upper = std::max(upper, lower);
strong = std::max(strong, upper);
return wxString::Format(_L("TD recommendation: %d-%d layers. %d+ layers for stronger saturation."),
lower,
upper,
strong);
}
void update_top_surface_contoning_td_recommendation(bool fit_dialog)
{
if (m_top_surface_contoning_pattern_recommendation_text == nullptr)
return;
const bool contoning =
m_top_surface_image_printing_enabled_checkbox != nullptr &&
m_top_surface_image_printing_enabled_checkbox->GetValue() &&
m_top_surface_image_method_choice != nullptr &&
m_top_surface_image_method_choice->GetSelection() == int(TextureMappingZone::TopSurfaceImageContoning);
const wxString text = contoning ? top_surface_contoning_td_recommendation_text() : wxString();
m_top_surface_contoning_pattern_recommendation_text->SetLabel(text);
m_top_surface_contoning_pattern_recommendation_text->Show(!text.empty());
if (!fit_dialog)
return;
layout_current_options_page();
update_options_book_min_size();
if (GetSizer() != nullptr) {
Layout();
Fit();
}
}
void update_modulation_mode_options_visibility(bool fit_dialog)
{
const bool perimeter_path_mode = modulation_mode() == int(TextureMappingZone::ModulationPerimeterPath) ||
@@ -3803,6 +3911,7 @@ private:
m_top_surface_image_fixed_coloring_filaments_checkbox->Show(!contoning_selected);
m_top_surface_image_fixed_coloring_filaments_checkbox->Enable(enabled && !contoning_selected);
}
update_top_surface_contoning_td_recommendation(false);
layout_current_options_page();
if (!fit_dialog)
return;
@@ -3858,6 +3967,7 @@ private:
wxSpinCtrlDouble *m_top_surface_contoning_angle_threshold_spin {nullptr};
wxSpinCtrl *m_top_surface_contoning_stack_layers_spin {nullptr};
wxSpinCtrl *m_top_surface_contoning_pattern_filaments_spin {nullptr};
wxStaticText *m_top_surface_contoning_pattern_recommendation_text {nullptr};
wxSpinCtrlDouble *m_top_surface_contoning_min_feature_spin {nullptr};
wxChoice *m_top_surface_contoning_flat_surface_infill_choice {nullptr};
wxPanel *m_top_surface_contoning_checkboxes_panel {nullptr};