Support deeper stacks in top-surface coloring
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() ?
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user