Support multithreading for surface-anchored stacks
- Add Default color mode which uses current slicer default - Update dithering to use selected color mode.
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
@@ -552,7 +553,7 @@ static void top_surface_image_contoning_report_anchored_progress(const PrintObje
|
||||
std::clamp(current_depth, 0, total_depth),
|
||||
total_depth,
|
||||
source_layer.id() + 1,
|
||||
source_surface == TopSurfaceImageSourceSurface::Bottom ? L(", lower") : L(", upper")));
|
||||
source_surface == TopSurfaceImageSourceSurface::Bottom ? L("- lower") : L("- upper")));
|
||||
}
|
||||
|
||||
static ExPolygons top_surface_clip_union_ex(const Polygons &subject)
|
||||
@@ -1400,6 +1401,139 @@ struct TopSurfaceImageContoningAnchoredSurfacePlan {
|
||||
std::vector<TopSurfaceImageContoningAnchoredSurfaceRegion> regions;
|
||||
};
|
||||
|
||||
class TopSurfaceImageContoningAnchoredDepthWork
|
||||
{
|
||||
public:
|
||||
using BuildFn = std::function<std::vector<TopSurfaceImageContoningVectorRegion>(int)>;
|
||||
using StoreFn = std::function<void(int, std::vector<TopSurfaceImageContoningVectorRegion>&&)>;
|
||||
using ProgressFn = std::function<void(int)>;
|
||||
|
||||
TopSurfaceImageContoningAnchoredDepthWork(std::vector<int> depths,
|
||||
BuildFn build,
|
||||
StoreFn store,
|
||||
ProgressFn progress)
|
||||
: m_depths(std::move(depths))
|
||||
, m_build(std::move(build))
|
||||
, m_store(std::move(store))
|
||||
, m_progress(std::move(progress))
|
||||
{}
|
||||
|
||||
bool run_one()
|
||||
{
|
||||
int depth = -1;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_error || m_next >= m_depths.size())
|
||||
return false;
|
||||
depth = m_depths[m_next++];
|
||||
++m_active;
|
||||
}
|
||||
|
||||
std::vector<TopSurfaceImageContoningVectorRegion> regions;
|
||||
std::exception_ptr error;
|
||||
try {
|
||||
regions = m_build(depth);
|
||||
m_store(depth, std::move(regions));
|
||||
} catch (...) {
|
||||
error = std::current_exception();
|
||||
}
|
||||
|
||||
int completed = 0;
|
||||
bool done = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (error && !m_error)
|
||||
m_error = error;
|
||||
--m_active;
|
||||
completed = ++m_completed;
|
||||
done = (m_error || m_next >= m_depths.size()) && m_active == 0;
|
||||
m_done = m_done || done;
|
||||
}
|
||||
if (done)
|
||||
m_cv.notify_all();
|
||||
if (!error && m_progress)
|
||||
m_progress(completed);
|
||||
return true;
|
||||
}
|
||||
|
||||
void wait()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_cv.wait(lock, [this]() { return m_done; });
|
||||
if (m_error)
|
||||
std::rethrow_exception(m_error);
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
std::vector<int> m_depths;
|
||||
BuildFn m_build;
|
||||
StoreFn m_store;
|
||||
ProgressFn m_progress;
|
||||
size_t m_next { 0 };
|
||||
int m_active { 0 };
|
||||
int m_completed { 0 };
|
||||
bool m_done { false };
|
||||
std::exception_ptr m_error;
|
||||
};
|
||||
|
||||
class TopSurfaceImageContoningAnchoredSurfaceBuildState
|
||||
{
|
||||
public:
|
||||
void set_depth_work(std::shared_ptr<TopSurfaceImageContoningAnchoredDepthWork> work)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_finished)
|
||||
m_depth_work = std::move(work);
|
||||
}
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
void clear_depth_work(const std::shared_ptr<TopSurfaceImageContoningAnchoredDepthWork> &work)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_depth_work == work)
|
||||
m_depth_work.reset();
|
||||
}
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
bool help_one()
|
||||
{
|
||||
std::shared_ptr<TopSurfaceImageContoningAnchoredDepthWork> work;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
if (!m_finished && !m_depth_work)
|
||||
m_cv.wait_for(lock, std::chrono::milliseconds(2), [this]() {
|
||||
return m_finished || m_depth_work != nullptr;
|
||||
});
|
||||
if (m_finished || !m_depth_work)
|
||||
return false;
|
||||
work = m_depth_work;
|
||||
}
|
||||
return work->run_one();
|
||||
}
|
||||
|
||||
void finish()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_finished = true;
|
||||
m_depth_work.reset();
|
||||
}
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
std::shared_ptr<TopSurfaceImageContoningAnchoredDepthWork> m_depth_work;
|
||||
bool m_finished { false };
|
||||
};
|
||||
|
||||
struct TopSurfaceImageContoningSourceContext {
|
||||
TextureMappingOffsetContext offset_context;
|
||||
std::vector<ExPolygons> stack_areas;
|
||||
@@ -1540,7 +1674,7 @@ public:
|
||||
const TopSurfaceImageContoningStackPlanKey &key,
|
||||
Builder &&builder)
|
||||
{
|
||||
return get_or_build_impl(m_anchored_surface_plans, key, std::forward<Builder>(builder));
|
||||
return get_or_build_anchored_surface_impl(key, std::forward<Builder>(builder));
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -1551,6 +1685,7 @@ private:
|
||||
std::shared_ptr<const Plan> plan;
|
||||
std::exception_ptr error;
|
||||
bool ready { false };
|
||||
std::shared_ptr<TopSurfaceImageContoningAnchoredSurfaceBuildState> anchored_build_state;
|
||||
};
|
||||
|
||||
template <class Plan, class Builder>
|
||||
@@ -1605,6 +1740,79 @@ private:
|
||||
return entry->plan;
|
||||
}
|
||||
|
||||
template <class Builder>
|
||||
std::shared_ptr<const TopSurfaceImageContoningAnchoredSurfacePlan> get_or_build_anchored_surface_impl(
|
||||
const TopSurfaceImageContoningStackPlanKey &key,
|
||||
Builder &&builder)
|
||||
{
|
||||
using Plan = TopSurfaceImageContoningAnchoredSurfacePlan;
|
||||
|
||||
std::shared_ptr<Entry<Plan>> entry;
|
||||
bool build = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
auto found = m_anchored_surface_plans.find(key);
|
||||
if (found == m_anchored_surface_plans.end()) {
|
||||
entry = std::make_shared<Entry<Plan>>();
|
||||
entry->anchored_build_state = std::make_shared<TopSurfaceImageContoningAnchoredSurfaceBuildState>();
|
||||
m_anchored_surface_plans.emplace(key, entry);
|
||||
build = true;
|
||||
} else {
|
||||
entry = found->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (build) {
|
||||
std::shared_ptr<const Plan> plan;
|
||||
std::exception_ptr error;
|
||||
try {
|
||||
plan = builder(entry->anchored_build_state.get());
|
||||
} catch (...) {
|
||||
error = std::current_exception();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(entry->mutex);
|
||||
entry->plan = plan;
|
||||
entry->error = error;
|
||||
entry->ready = true;
|
||||
}
|
||||
if (entry->anchored_build_state)
|
||||
entry->anchored_build_state->finish();
|
||||
entry->cv.notify_all();
|
||||
if (error) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
auto found = m_anchored_surface_plans.find(key);
|
||||
if (found != m_anchored_surface_plans.end() && found->second == entry)
|
||||
m_anchored_surface_plans.erase(found);
|
||||
std::rethrow_exception(error);
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(entry->mutex);
|
||||
if (entry->ready) {
|
||||
if (entry->error)
|
||||
std::rethrow_exception(entry->error);
|
||||
return entry->plan;
|
||||
}
|
||||
}
|
||||
|
||||
if (entry->anchored_build_state && entry->anchored_build_state->help_one())
|
||||
continue;
|
||||
|
||||
std::unique_lock<std::mutex> lock(entry->mutex);
|
||||
if (!entry->ready)
|
||||
entry->cv.wait_for(lock, std::chrono::milliseconds(1));
|
||||
if (entry->ready) {
|
||||
if (entry->error)
|
||||
std::rethrow_exception(entry->error);
|
||||
return entry->plan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex m_mutex;
|
||||
std::map<TopSurfaceImageContoningStackPlanKey, std::shared_ptr<Entry<TopSurfaceImageContoningStackPlan>>> m_plans;
|
||||
std::map<TopSurfaceImageContoningStackPlanKey, std::shared_ptr<Entry<TopSurfaceImageContoningDepthRegionPlan>>> m_depth_region_plans;
|
||||
@@ -2799,6 +3007,7 @@ static void top_surface_image_contoning_solve_anchored_region(
|
||||
const TopSurfaceImageContoningSourceContext &source_context,
|
||||
const TextureMappingContoningSolver &solver,
|
||||
TopSurfaceImageSourceSurface source_surface,
|
||||
TopSurfaceImageContoningAnchoredSurfaceBuildState *build_state,
|
||||
const ThrowIfCanceled *throw_if_canceled)
|
||||
{
|
||||
if (anchored_region.union_area.empty() ||
|
||||
@@ -2985,6 +3194,37 @@ static void top_surface_image_contoning_solve_anchored_region(
|
||||
if (active_depths.size() <= 1) {
|
||||
const int depth = active_depths.front();
|
||||
anchored_region.depth_regions[size_t(depth)] = build_depth_regions(depth);
|
||||
} else if (build_state != nullptr) {
|
||||
auto store_depth_regions = [&](int depth, std::vector<TopSurfaceImageContoningVectorRegion> &®ions) {
|
||||
anchored_region.depth_regions[size_t(depth)] = std::move(regions);
|
||||
};
|
||||
auto report_progress = [&](int completed) {
|
||||
if (completed == int(active_depths.size()) || (completed & 3) == 0)
|
||||
top_surface_image_contoning_report_anchored_progress(object,
|
||||
source_layer,
|
||||
source_surface,
|
||||
L("processing"),
|
||||
completed,
|
||||
int(active_depths.size()));
|
||||
};
|
||||
std::shared_ptr<TopSurfaceImageContoningAnchoredDepthWork> depth_work =
|
||||
std::make_shared<TopSurfaceImageContoningAnchoredDepthWork>(active_depths,
|
||||
build_depth_regions,
|
||||
store_depth_regions,
|
||||
report_progress);
|
||||
build_state->set_depth_work(depth_work);
|
||||
try {
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, active_depths.size(), 1),
|
||||
[&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t idx = range.begin(); idx != range.end(); ++idx)
|
||||
depth_work->run_one();
|
||||
});
|
||||
depth_work->wait();
|
||||
} catch (...) {
|
||||
build_state->clear_depth_work(depth_work);
|
||||
throw;
|
||||
}
|
||||
build_state->clear_depth_work(depth_work);
|
||||
} else {
|
||||
std::atomic<int> completed_depths { 0 };
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, active_depths.size(), 1),
|
||||
@@ -3019,6 +3259,7 @@ static std::shared_ptr<TopSurfaceImageContoningAnchoredSurfacePlan> top_surface_
|
||||
const PrintConfig &print_config,
|
||||
const TextureMappingContoningSolver &solver,
|
||||
TopSurfaceImageSourceSurface source_surface,
|
||||
TopSurfaceImageContoningAnchoredSurfaceBuildState *build_state,
|
||||
const ThrowIfCanceled *throw_if_canceled)
|
||||
{
|
||||
std::shared_ptr<TopSurfaceImageContoningAnchoredSurfacePlan> out =
|
||||
@@ -3135,6 +3376,7 @@ static std::shared_ptr<TopSurfaceImageContoningAnchoredSurfacePlan> top_surface_
|
||||
*source_context,
|
||||
solver,
|
||||
source_surface,
|
||||
build_state,
|
||||
throw_if_canceled);
|
||||
const bool has_depth_regions =
|
||||
std::any_of(anchored_region.depth_regions.begin(),
|
||||
@@ -3209,7 +3451,7 @@ static std::shared_ptr<const TopSurfaceImageContoningAnchoredSurfacePlan> top_su
|
||||
{
|
||||
const TopSurfaceImageContoningStackPlanKey key =
|
||||
top_surface_image_contoning_anchored_surface_plan_key(plan, source_layer, zone, solver, source_surface);
|
||||
auto builder = [&]() {
|
||||
auto builder = [&](TopSurfaceImageContoningAnchoredSurfaceBuildState *build_state) {
|
||||
return top_surface_image_contoning_build_anchored_surface_plan(plan,
|
||||
source_layer,
|
||||
object,
|
||||
@@ -3217,9 +3459,10 @@ static std::shared_ptr<const TopSurfaceImageContoningAnchoredSurfacePlan> top_su
|
||||
print_config,
|
||||
solver,
|
||||
source_surface,
|
||||
build_state,
|
||||
throw_if_canceled);
|
||||
};
|
||||
return cache != nullptr ? cache->get_or_build_anchored_surface(key, builder) : builder();
|
||||
return cache != nullptr ? cache->get_or_build_anchored_surface(key, builder) : builder(nullptr);
|
||||
}
|
||||
|
||||
static std::vector<TopSurfaceImageContoningVectorRegion> top_surface_image_contoning_vector_regions_from_anchored_surface_plan(
|
||||
|
||||
@@ -6507,8 +6507,9 @@ static std::array<float, 3> generic_solver_oklab_axis_weights_for_gcode(const st
|
||||
static std::array<float, 3> generic_solver_perceptual_axis_weights_for_gcode(const std::array<float, 3> &target_oklab,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
std::array<float, 3> weights = generic_solver_oklab_axis_weights_for_gcode(target_oklab);
|
||||
if (generic_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) {
|
||||
if (effective_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) {
|
||||
weights[0] = std::max(weights[0], 1.f);
|
||||
weights[1] = std::min(weights[1], 4.f);
|
||||
weights[2] = std::min(weights[2], 4.f);
|
||||
@@ -6518,7 +6519,7 @@ static std::array<float, 3> generic_solver_perceptual_axis_weights_for_gcode(con
|
||||
|
||||
static bool generic_solver_mode_is_perceptual_for_gcode(int generic_solver_mode)
|
||||
{
|
||||
return generic_solver_mode != int(TextureMappingZone::GenericSolverRGB);
|
||||
return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) != int(TextureMappingZone::GenericSolverRGB);
|
||||
}
|
||||
|
||||
static std::string generic_mix_candidate_cache_key_for_gcode(const std::vector<std::array<float, 3>> &component_colors)
|
||||
@@ -6753,7 +6754,7 @@ static float generic_mix_candidate_perceptual_error_for_gcode(const GCodeGeneric
|
||||
const float da = candidates.perceptual_coords[coord_idx + 1] - target_oklab[1];
|
||||
const float db = candidates.perceptual_coords[coord_idx + 2] - target_oklab[2];
|
||||
float error = axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db;
|
||||
if (generic_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) {
|
||||
if (TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) {
|
||||
const float under_l = std::max(0.f, target_oklab[0] - candidates.perceptual_coords[coord_idx + 0] - 0.04f);
|
||||
error += 4.f * generic_solver_oklab_chroma_factor_for_gcode(target_oklab) * under_l * under_l;
|
||||
}
|
||||
@@ -6839,9 +6840,7 @@ static std::vector<float> best_component_mix_weights_for_target_for_gcode(const
|
||||
return {};
|
||||
|
||||
const size_t candidate_count = candidates.rgbs.size() / 3;
|
||||
const int clamped_solver_mode = std::clamp(generic_solver_mode,
|
||||
int(TextureMappingZone::GenericSolverRGB),
|
||||
int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4));
|
||||
const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
GCodeGenericMixNearestResult nearest =
|
||||
generic_solver_mode_is_perceptual_for_gcode(clamped_solver_mode) ?
|
||||
nearest_generic_mix_candidates_perceptual_for_gcode(candidates, target_rgb, clamped_solver_mode) :
|
||||
@@ -6923,6 +6922,39 @@ struct GCodeBinaryDitherCandidate {
|
||||
std::array<float, 3> oklab { { 1.f, 0.f, 0.f } };
|
||||
};
|
||||
|
||||
static const std::array<float, 3> &binary_dither_candidate_color_for_gcode(const GCodeBinaryDitherCandidate &candidate,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ?
|
||||
candidate.rgb :
|
||||
candidate.oklab;
|
||||
}
|
||||
|
||||
static std::array<float, 3> binary_dither_axis_weights_for_gcode(const std::array<float, 3> &target_color,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ?
|
||||
std::array<float, 3>{ { 1.f, 1.f, 1.f } } :
|
||||
generic_solver_perceptual_axis_weights_for_gcode(target_color, generic_solver_mode);
|
||||
}
|
||||
|
||||
static float binary_dither_candidate_error_for_gcode(const GCodeBinaryDitherCandidate &candidate,
|
||||
const std::array<float, 3> &target_color,
|
||||
const std::array<float, 3> &axis_weights,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
const std::array<float, 3> &candidate_color = binary_dither_candidate_color_for_gcode(candidate, generic_solver_mode);
|
||||
const float d0 = candidate_color[0] - target_color[0];
|
||||
const float d1 = candidate_color[1] - target_color[1];
|
||||
const float d2 = candidate_color[2] - target_color[2];
|
||||
float error = axis_weights[0] * d0 * d0 + axis_weights[1] * d1 * d1 + axis_weights[2] * d2 * d2;
|
||||
if (TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) {
|
||||
const float under_l = std::max(0.f, target_color[0] - candidate_color[0] - 0.04f);
|
||||
error += 4.f * generic_solver_oklab_chroma_factor_for_gcode(target_color) * under_l * under_l;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static std::vector<GCodeBinaryDitherCandidate> binary_dither_candidates_for_gcode(
|
||||
const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<float> &component_strength_factors,
|
||||
@@ -6976,16 +7008,17 @@ struct GCodeBinaryDitherNearestResult {
|
||||
|
||||
static GCodeBinaryDitherNearestResult nearest_binary_dither_candidates_for_gcode(
|
||||
const std::vector<GCodeBinaryDitherCandidate> &candidates,
|
||||
const std::array<float, 3> &target_oklab)
|
||||
const std::array<float, 3> &target_color,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
GCodeBinaryDitherNearestResult result;
|
||||
const std::array<float, 3> axis_weights = generic_solver_oklab_axis_weights_for_gcode(target_oklab);
|
||||
const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
const std::array<float, 3> axis_weights = binary_dither_axis_weights_for_gcode(target_color, clamped_solver_mode);
|
||||
for (size_t idx = 0; idx < candidates.size(); ++idx) {
|
||||
const std::array<float, 3> &candidate = candidates[idx].oklab;
|
||||
const float dl = candidate[0] - target_oklab[0];
|
||||
const float da = candidate[1] - target_oklab[1];
|
||||
const float db = candidate[2] - target_oklab[2];
|
||||
const float error = axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db;
|
||||
const float error = binary_dither_candidate_error_for_gcode(candidates[idx],
|
||||
target_color,
|
||||
axis_weights,
|
||||
clamped_solver_mode);
|
||||
if (error < result.best_error) {
|
||||
result.second_error = result.best_error;
|
||||
result.second_idx = result.best_idx;
|
||||
@@ -7000,27 +7033,30 @@ static GCodeBinaryDitherNearestResult nearest_binary_dither_candidates_for_gcode
|
||||
}
|
||||
|
||||
static size_t nearest_binary_dither_candidate_for_gcode(const std::vector<GCodeBinaryDitherCandidate> &candidates,
|
||||
const std::array<float, 3> &target_oklab)
|
||||
const std::array<float, 3> &target_color,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
return nearest_binary_dither_candidates_for_gcode(candidates, target_oklab).best_idx;
|
||||
return nearest_binary_dither_candidates_for_gcode(candidates, target_color, generic_solver_mode).best_idx;
|
||||
}
|
||||
|
||||
static float binary_dither_alternate_fraction_for_gcode(const std::vector<GCodeBinaryDitherCandidate> &candidates,
|
||||
const std::array<float, 3> &target_oklab,
|
||||
const std::array<float, 3> &target_color,
|
||||
size_t base_idx,
|
||||
size_t alternate_idx)
|
||||
size_t alternate_idx,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
if (base_idx >= candidates.size() || alternate_idx >= candidates.size() || base_idx == alternate_idx)
|
||||
return 0.f;
|
||||
|
||||
const std::array<float, 3> axis_weights = generic_solver_oklab_axis_weights_for_gcode(target_oklab);
|
||||
const std::array<float, 3> &base = candidates[base_idx].oklab;
|
||||
const std::array<float, 3> &alternate = candidates[alternate_idx].oklab;
|
||||
const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
const std::array<float, 3> axis_weights = binary_dither_axis_weights_for_gcode(target_color, clamped_solver_mode);
|
||||
const std::array<float, 3> &base = binary_dither_candidate_color_for_gcode(candidates[base_idx], clamped_solver_mode);
|
||||
const std::array<float, 3> &alternate = binary_dither_candidate_color_for_gcode(candidates[alternate_idx], clamped_solver_mode);
|
||||
float numerator = 0.f;
|
||||
float denominator = 0.f;
|
||||
for (size_t axis = 0; axis < 3; ++axis) {
|
||||
const float delta = alternate[axis] - base[axis];
|
||||
numerator += axis_weights[axis] * (target_oklab[axis] - base[axis]) * delta;
|
||||
numerator += axis_weights[axis] * (target_color[axis] - base[axis]) * delta;
|
||||
denominator += axis_weights[axis] * delta * delta;
|
||||
}
|
||||
if (!std::isfinite(numerator) || !std::isfinite(denominator) || denominator <= 1e-12f)
|
||||
@@ -7029,17 +7065,19 @@ static float binary_dither_alternate_fraction_for_gcode(const std::vector<GCodeB
|
||||
}
|
||||
|
||||
static size_t thresholded_binary_dither_candidate_for_gcode(const std::vector<GCodeBinaryDitherCandidate> &candidates,
|
||||
const std::array<float, 3> &target_oklab,
|
||||
float threshold)
|
||||
const std::array<float, 3> &target_color,
|
||||
float threshold,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
const GCodeBinaryDitherNearestResult nearest = nearest_binary_dither_candidates_for_gcode(candidates, target_oklab);
|
||||
const GCodeBinaryDitherNearestResult nearest =
|
||||
nearest_binary_dither_candidates_for_gcode(candidates, target_color, generic_solver_mode);
|
||||
if (nearest.best_idx >= candidates.size())
|
||||
return size_t(-1);
|
||||
if (nearest.second_idx >= candidates.size())
|
||||
return nearest.best_idx;
|
||||
|
||||
const float alternate_fraction =
|
||||
binary_dither_alternate_fraction_for_gcode(candidates, target_oklab, nearest.best_idx, nearest.second_idx);
|
||||
binary_dither_alternate_fraction_for_gcode(candidates, target_color, nearest.best_idx, nearest.second_idx, generic_solver_mode);
|
||||
return std::clamp(threshold, 0.f, 1.f) < alternate_fraction ? nearest.second_idx : nearest.best_idx;
|
||||
}
|
||||
|
||||
@@ -8246,6 +8284,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
(void) halftone_dot_size_mm;
|
||||
if (component_colors.empty())
|
||||
return weight_field;
|
||||
const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
const size_t component_count = component_colors.size();
|
||||
|
||||
const ModelObject *model_object = print_object.model_object();
|
||||
@@ -8787,11 +8826,11 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
int x { 0 };
|
||||
int y { 0 };
|
||||
float weight { 0.f };
|
||||
std::array<float, 3> target_oklab { { 0.f, 0.f, 0.f } };
|
||||
std::array<float, 3> target_color { { 0.f, 0.f, 0.f } };
|
||||
std::vector<size_t> sample_indices;
|
||||
};
|
||||
|
||||
auto sample_target_oklab = [tone_gamma, contrast_factor](const WeightedTextureSample &sample) {
|
||||
auto sample_target_color = [tone_gamma, contrast_factor, effective_solver_mode](const WeightedTextureSample &sample) {
|
||||
std::array<float, 3> target = {
|
||||
clamp01f_for_gcode(sample.rgba[0]),
|
||||
clamp01f_for_gcode(sample.rgba[1]),
|
||||
@@ -8803,7 +8842,9 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
target[2] = apply_texture_tone_gamma_for_gcode(target[2], tone_gamma);
|
||||
}
|
||||
target = apply_texture_contrast_to_rgb_for_gcode(target, contrast_factor);
|
||||
return oklab_from_srgb_for_gcode(target);
|
||||
return effective_solver_mode == int(TextureMappingZone::GenericSolverRGB) ?
|
||||
target :
|
||||
oklab_from_srgb_for_gcode(target);
|
||||
};
|
||||
|
||||
std::vector<BinaryDitherCell> cells;
|
||||
@@ -8826,10 +8867,10 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
}
|
||||
|
||||
BinaryDitherCell &cell = cells[cell_it->second];
|
||||
const std::array<float, 3> target_oklab = sample_target_oklab(sample);
|
||||
const std::array<float, 3> target_color = sample_target_color(sample);
|
||||
const float sample_weight = std::max(sample.weight, 0.f);
|
||||
for (size_t axis = 0; axis < 3; ++axis)
|
||||
cell.target_oklab[axis] += target_oklab[axis] * sample_weight;
|
||||
cell.target_color[axis] += target_color[axis] * sample_weight;
|
||||
cell.weight += sample_weight;
|
||||
cell.sample_indices.emplace_back(sample_idx);
|
||||
}
|
||||
@@ -8837,7 +8878,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
if (!cells.empty()) {
|
||||
for (BinaryDitherCell &cell : cells)
|
||||
if (cell.weight > EPSILON)
|
||||
for (float &value : cell.target_oklab)
|
||||
for (float &value : cell.target_color)
|
||||
value /= cell.weight;
|
||||
|
||||
std::vector<size_t> order(cells.size(), 0);
|
||||
@@ -8852,12 +8893,12 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
std::map<std::pair<int, int>, std::array<float, 3>> floyd_error;
|
||||
for (const size_t cell_idx : order) {
|
||||
BinaryDitherCell &cell = cells[cell_idx];
|
||||
std::array<float, 3> target_oklab = cell.target_oklab;
|
||||
std::array<float, 3> target_color = cell.target_color;
|
||||
if (clamped_binary_dither_method == int(TextureMappingZone::DitheringFloydSteinberg)) {
|
||||
const auto error_it = floyd_error.find({cell.x, cell.y});
|
||||
if (error_it != floyd_error.end())
|
||||
for (size_t axis = 0; axis < 3; ++axis)
|
||||
target_oklab[axis] += error_it->second[axis];
|
||||
target_color[axis] += error_it->second[axis];
|
||||
}
|
||||
|
||||
bool thresholded_dither = false;
|
||||
@@ -8869,8 +8910,8 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
|
||||
const size_t candidate_idx =
|
||||
thresholded_dither ?
|
||||
thresholded_binary_dither_candidate_for_gcode(binary_candidates, target_oklab, threshold) :
|
||||
nearest_binary_dither_candidate_for_gcode(binary_candidates, target_oklab);
|
||||
thresholded_binary_dither_candidate_for_gcode(binary_candidates, target_color, threshold, effective_solver_mode) :
|
||||
nearest_binary_dither_candidate_for_gcode(binary_candidates, target_color, effective_solver_mode);
|
||||
if (candidate_idx >= binary_candidates.size())
|
||||
continue;
|
||||
|
||||
@@ -8880,10 +8921,12 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
binary_dither_masks[sample_idx] = candidate.mask;
|
||||
|
||||
if (clamped_binary_dither_method == int(TextureMappingZone::DitheringFloydSteinberg)) {
|
||||
const std::array<float, 3> &candidate_color =
|
||||
binary_dither_candidate_color_for_gcode(candidate, effective_solver_mode);
|
||||
std::array<float, 3> error = {
|
||||
target_oklab[0] - candidate.oklab[0],
|
||||
target_oklab[1] - candidate.oklab[1],
|
||||
target_oklab[2] - candidate.oklab[2]
|
||||
target_color[0] - candidate_color[0],
|
||||
target_color[1] - candidate_color[1],
|
||||
target_color[2] - candidate_color[2]
|
||||
};
|
||||
auto add_error = [&floyd_error, &cell_index_by_coord, &error](int x, int y, float factor) {
|
||||
if (cell_index_by_coord.find({x, y}) == cell_index_by_coord.end())
|
||||
@@ -9008,7 +9051,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode(
|
||||
best_component_mix_weights_for_target_for_gcode(*generic_mix_candidates,
|
||||
target,
|
||||
generic_solver_lookup_mode,
|
||||
generic_solver_mode) :
|
||||
effective_solver_mode) :
|
||||
std::vector<float>{};
|
||||
if (best.size() == component_count)
|
||||
desired = std::move(best);
|
||||
@@ -9472,9 +9515,7 @@ std::optional<PreferredSeamPoint> GCode::texture_mapping_seam_hiding_hint(const
|
||||
const int generic_solver_lookup_mode = std::clamp(zone->generic_solver_lookup_mode,
|
||||
int(TextureMappingZone::GenericSolverClosestMix),
|
||||
int(TextureMappingZone::GenericSolverBlendClosestTwo));
|
||||
const int generic_solver_mode = std::clamp(zone->generic_solver_mode,
|
||||
int(TextureMappingZone::GenericSolverRGB),
|
||||
int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4));
|
||||
const int generic_solver_mode = TextureMappingZone::effective_generic_solver_mode(zone->generic_solver_mode);
|
||||
const int generic_solver_mix_model = std::clamp(zone->generic_solver_mix_model,
|
||||
int(TextureMappingZone::GenericSolverPigmentPainter),
|
||||
int(TextureMappingZone::GenericSolverPrusaFdmMixer));
|
||||
|
||||
@@ -405,7 +405,7 @@ private:
|
||||
return solve_color_solver_weights_for_target(candidates,
|
||||
target,
|
||||
color_solver_lookup_mode_from_index(generic_solver_lookup_mode),
|
||||
color_solver_mode_from_index(generic_solver_mode));
|
||||
color_solver_mode_from_index(TextureMappingZone::effective_generic_solver_mode(generic_solver_mode)));
|
||||
}
|
||||
|
||||
std::vector<size_t> generic_solver_tool_candidates(const std::vector<size_t> *preferred_tools = nullptr) const
|
||||
|
||||
@@ -798,6 +798,8 @@ static int generic_solver_lookup_mode_from_name(const std::string &name)
|
||||
|
||||
static std::string generic_solver_mode_name(int mode)
|
||||
{
|
||||
if (mode == int(TextureMappingZone::GenericSolverDefault))
|
||||
return "default";
|
||||
switch (clamp_int(mode,
|
||||
int(TextureMappingZone::GenericSolverRGB),
|
||||
int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4))) {
|
||||
@@ -816,16 +818,13 @@ static int generic_solver_mode_from_name(std::string name)
|
||||
const char lowered = char(std::tolower(c));
|
||||
return lowered == '-' || lowered == ' ' ? '_' : lowered;
|
||||
});
|
||||
if (name == "rgb" || name == "legacy" || name == "v1")
|
||||
if (name == "rgb")
|
||||
return int(TextureMappingZone::GenericSolverRGB);
|
||||
if (name == "oklab_soft_cap4_dark4" ||
|
||||
name == "oklab_soft_cap" ||
|
||||
name == "oklab_soft" ||
|
||||
name == "v2_soft_cap4_dark4" ||
|
||||
name == "v2_soft_cap" ||
|
||||
name == "v2_soft")
|
||||
name == "oklab_soft")
|
||||
return int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4);
|
||||
if (name == "oklab" || name == "v2")
|
||||
if (name == "oklab")
|
||||
return int(TextureMappingZone::GenericSolverOklab);
|
||||
return TextureMappingZone::DefaultGenericSolverMode;
|
||||
}
|
||||
@@ -1931,13 +1930,8 @@ void TextureMappingManager::load_entries(const std::string &serialized,
|
||||
100.f);
|
||||
zone.generic_solver_lookup_mode =
|
||||
generic_solver_lookup_mode_from_name(texture.value("generic_solver_lookup", std::string("closest_mix")));
|
||||
const auto generic_solver_mode_it = texture.find("generic_solver_mode");
|
||||
zone.generic_solver_mode =
|
||||
generic_solver_mode_it != texture.end() && generic_solver_mode_it->is_string() ?
|
||||
generic_solver_mode_from_name(generic_solver_mode_it->get<std::string>()) :
|
||||
(zone.filament_color_mode == int(TextureMappingZone::FilamentColorAny) ?
|
||||
int(TextureMappingZone::GenericSolverRGB) :
|
||||
int(TextureMappingZone::GenericSolverOklab));
|
||||
generic_solver_mode_from_name(texture.value("generic_solver_mode", std::string("default")));
|
||||
zone.generic_solver_mix_model =
|
||||
generic_solver_mix_model_from_name(texture.value("generic_solver_mix_model", std::string("pigment_painter")));
|
||||
zone.dithering_enabled = texture.value("dithering_enabled", TextureMappingZone::DefaultDitheringEnabled);
|
||||
|
||||
@@ -110,7 +110,8 @@ struct TextureMappingZone
|
||||
enum GenericSolverMode : uint8_t {
|
||||
GenericSolverRGB = 0,
|
||||
GenericSolverOklab = 1,
|
||||
GenericSolverOklabSoftCap4Dark4 = 2
|
||||
GenericSolverOklabSoftCap4Dark4 = 2,
|
||||
GenericSolverDefault = 255
|
||||
};
|
||||
|
||||
enum GenericSolverMixModel : uint8_t {
|
||||
@@ -201,7 +202,7 @@ struct TextureMappingZone
|
||||
static constexpr bool DefaultTopSurfaceContoningSupersampledCellsEnabled = false;
|
||||
static constexpr bool DefaultTopSurfaceContoningPolygonizeColorRegionsEnabled = true;
|
||||
static constexpr int DefaultTopSurfaceContoningPolygonizeResolution = 4;
|
||||
static constexpr bool DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled = false;
|
||||
static constexpr bool DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled = true;
|
||||
static constexpr bool DefaultTopSurfaceContoningSurfaceAnchoredStackOptimizationsEnabled = true;
|
||||
static constexpr bool DefaultTopSurfaceContoningTdAdjustmentEnabled = true;
|
||||
static constexpr bool DefaultTopSurfaceContoningSurfaceScatterEnabled = false;
|
||||
@@ -214,7 +215,8 @@ struct TextureMappingZone
|
||||
static constexpr bool DefaultMinimumVisibilityOffsetEnabled = true;
|
||||
static constexpr float DefaultMinimumVisibilityOffsetPct = 30.f;
|
||||
static constexpr int DefaultGenericSolverLookupMode = int(GenericSolverClosestMix);
|
||||
static constexpr int DefaultGenericSolverMode = int(GenericSolverOklabSoftCap4Dark4);
|
||||
static constexpr int SlicerDefaultGenericSolverMode = int(GenericSolverOklabSoftCap4Dark4);
|
||||
static constexpr int DefaultGenericSolverMode = int(GenericSolverDefault);
|
||||
static constexpr int DefaultGenericSolverMixModel = int(GenericSolverPigmentPainter);
|
||||
static constexpr bool DefaultDitheringEnabled = false;
|
||||
static constexpr int DefaultDitheringMethod = int(DitheringHalftoneV2);
|
||||
@@ -243,6 +245,13 @@ struct TextureMappingZone
|
||||
}
|
||||
}
|
||||
|
||||
static int effective_generic_solver_mode(int mode)
|
||||
{
|
||||
if (mode == int(GenericSolverDefault))
|
||||
return SlicerDefaultGenericSolverMode;
|
||||
return std::clamp(mode, int(GenericSolverRGB), int(GenericSolverOklabSoftCap4Dark4));
|
||||
}
|
||||
|
||||
static int effective_top_surface_contoning_flat_surface_infill_mode(int mode)
|
||||
{
|
||||
if (!ShowExperimentalTopSurfaceContoningOptions)
|
||||
@@ -284,7 +293,7 @@ struct TextureMappingZone
|
||||
|
||||
static bool effective_top_surface_contoning_surface_anchored_stacks_enabled(bool value)
|
||||
{
|
||||
return ShowExperimentalTopSurfaceContoningOptions && value;
|
||||
return ShowExperimentalTopSurfaceContoningOptions ? value : true;
|
||||
}
|
||||
|
||||
static bool effective_top_surface_contoning_surface_anchored_stack_optimizations_enabled(bool value)
|
||||
@@ -578,7 +587,7 @@ struct TextureMappingZone
|
||||
top_surface_contoning_recolor_surrounding_perimeters = false;
|
||||
top_surface_contoning_perimeter_mode = DefaultTopSurfaceContoningPerimeterMode;
|
||||
top_surface_contoning_flat_surface_infill_mode = DefaultTopSurfaceContoningFlatSurfaceInfillMode;
|
||||
top_surface_contoning_surface_anchored_stacks_enabled = false;
|
||||
top_surface_contoning_surface_anchored_stacks_enabled = true;
|
||||
top_surface_contoning_surface_anchored_stack_optimizations_enabled = true;
|
||||
top_surface_contoning_beam_search_stack_expansion_enabled = true;
|
||||
top_surface_contoning_layer_phase_enabled = false;
|
||||
|
||||
@@ -933,12 +933,64 @@ std::array<float, 3> generic_solver_oklab_axis_weights(const std::array<float, 3
|
||||
};
|
||||
}
|
||||
|
||||
float generic_solver_oklab_chroma_factor(const std::array<float, 3> &target_oklab)
|
||||
{
|
||||
const float chroma = std::hypot(target_oklab[1], target_oklab[2]);
|
||||
return std::clamp((chroma - 0.015f) / 0.13f, 0.f, 1.f);
|
||||
}
|
||||
|
||||
std::array<float, 3> generic_solver_perceptual_axis_weights(const std::array<float, 3> &target_oklab,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
std::array<float, 3> weights = generic_solver_oklab_axis_weights(target_oklab);
|
||||
if (effective_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) {
|
||||
weights[0] = std::max(weights[0], 1.f);
|
||||
weights[1] = std::min(weights[1], 4.f);
|
||||
weights[2] = std::min(weights[2], 4.f);
|
||||
}
|
||||
return weights;
|
||||
}
|
||||
|
||||
struct TextureMappingBinaryDitherCandidate {
|
||||
uint32_t mask { 0 };
|
||||
std::array<float, 3> rgb { { 1.f, 1.f, 1.f } };
|
||||
std::array<float, 3> oklab { { 1.f, 0.f, 0.f } };
|
||||
};
|
||||
|
||||
const std::array<float, 3> &binary_dither_candidate_color(const TextureMappingBinaryDitherCandidate &candidate,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ?
|
||||
candidate.rgb :
|
||||
candidate.oklab;
|
||||
}
|
||||
|
||||
std::array<float, 3> binary_dither_axis_weights(const std::array<float, 3> &target_color,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ?
|
||||
std::array<float, 3>{ { 1.f, 1.f, 1.f } } :
|
||||
generic_solver_perceptual_axis_weights(target_color, generic_solver_mode);
|
||||
}
|
||||
|
||||
float binary_dither_candidate_error(const TextureMappingBinaryDitherCandidate &candidate,
|
||||
const std::array<float, 3> &target_color,
|
||||
const std::array<float, 3> &axis_weights,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
const std::array<float, 3> &candidate_color = binary_dither_candidate_color(candidate, generic_solver_mode);
|
||||
const float d0 = candidate_color[0] - target_color[0];
|
||||
const float d1 = candidate_color[1] - target_color[1];
|
||||
const float d2 = candidate_color[2] - target_color[2];
|
||||
float error = axis_weights[0] * d0 * d0 + axis_weights[1] * d1 * d1 + axis_weights[2] * d2 * d2;
|
||||
if (TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) {
|
||||
const float under_l = std::max(0.f, target_color[0] - candidate_color[0] - 0.04f);
|
||||
error += 4.f * generic_solver_oklab_chroma_factor(target_color) * under_l * under_l;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
std::vector<TextureMappingBinaryDitherCandidate> binary_dither_candidates(
|
||||
const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<float> &component_strength_factors,
|
||||
@@ -992,16 +1044,17 @@ struct TextureMappingBinaryDitherNearestResult {
|
||||
|
||||
TextureMappingBinaryDitherNearestResult nearest_binary_dither_candidates(
|
||||
const std::vector<TextureMappingBinaryDitherCandidate> &candidates,
|
||||
const std::array<float, 3> &target_oklab)
|
||||
const std::array<float, 3> &target_color,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
TextureMappingBinaryDitherNearestResult result;
|
||||
const std::array<float, 3> axis_weights = generic_solver_oklab_axis_weights(target_oklab);
|
||||
const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
const std::array<float, 3> axis_weights = binary_dither_axis_weights(target_color, clamped_solver_mode);
|
||||
for (size_t idx = 0; idx < candidates.size(); ++idx) {
|
||||
const std::array<float, 3> &candidate = candidates[idx].oklab;
|
||||
const float dl = candidate[0] - target_oklab[0];
|
||||
const float da = candidate[1] - target_oklab[1];
|
||||
const float db = candidate[2] - target_oklab[2];
|
||||
const float error = axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db;
|
||||
const float error = binary_dither_candidate_error(candidates[idx],
|
||||
target_color,
|
||||
axis_weights,
|
||||
clamped_solver_mode);
|
||||
if (error < result.best_error) {
|
||||
result.second_error = result.best_error;
|
||||
result.second_idx = result.best_idx;
|
||||
@@ -1016,27 +1069,30 @@ TextureMappingBinaryDitherNearestResult nearest_binary_dither_candidates(
|
||||
}
|
||||
|
||||
size_t nearest_binary_dither_candidate(const std::vector<TextureMappingBinaryDitherCandidate> &candidates,
|
||||
const std::array<float, 3> &target_oklab)
|
||||
const std::array<float, 3> &target_color,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
return nearest_binary_dither_candidates(candidates, target_oklab).best_idx;
|
||||
return nearest_binary_dither_candidates(candidates, target_color, generic_solver_mode).best_idx;
|
||||
}
|
||||
|
||||
float binary_dither_alternate_fraction(const std::vector<TextureMappingBinaryDitherCandidate> &candidates,
|
||||
const std::array<float, 3> &target_oklab,
|
||||
const std::array<float, 3> &target_color,
|
||||
size_t base_idx,
|
||||
size_t alternate_idx)
|
||||
size_t alternate_idx,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
if (base_idx >= candidates.size() || alternate_idx >= candidates.size() || base_idx == alternate_idx)
|
||||
return 0.f;
|
||||
|
||||
const std::array<float, 3> axis_weights = generic_solver_oklab_axis_weights(target_oklab);
|
||||
const std::array<float, 3> &base = candidates[base_idx].oklab;
|
||||
const std::array<float, 3> &alternate = candidates[alternate_idx].oklab;
|
||||
const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
const std::array<float, 3> axis_weights = binary_dither_axis_weights(target_color, clamped_solver_mode);
|
||||
const std::array<float, 3> &base = binary_dither_candidate_color(candidates[base_idx], clamped_solver_mode);
|
||||
const std::array<float, 3> &alternate = binary_dither_candidate_color(candidates[alternate_idx], clamped_solver_mode);
|
||||
float numerator = 0.f;
|
||||
float denominator = 0.f;
|
||||
for (size_t axis = 0; axis < 3; ++axis) {
|
||||
const float delta = alternate[axis] - base[axis];
|
||||
numerator += axis_weights[axis] * (target_oklab[axis] - base[axis]) * delta;
|
||||
numerator += axis_weights[axis] * (target_color[axis] - base[axis]) * delta;
|
||||
denominator += axis_weights[axis] * delta * delta;
|
||||
}
|
||||
if (!std::isfinite(numerator) || !std::isfinite(denominator) || denominator <= 1e-12f)
|
||||
@@ -1045,17 +1101,19 @@ float binary_dither_alternate_fraction(const std::vector<TextureMappingBinaryDit
|
||||
}
|
||||
|
||||
size_t thresholded_binary_dither_candidate(const std::vector<TextureMappingBinaryDitherCandidate> &candidates,
|
||||
const std::array<float, 3> &target_oklab,
|
||||
float threshold)
|
||||
const std::array<float, 3> &target_color,
|
||||
float threshold,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
const TextureMappingBinaryDitherNearestResult nearest = nearest_binary_dither_candidates(candidates, target_oklab);
|
||||
const TextureMappingBinaryDitherNearestResult nearest =
|
||||
nearest_binary_dither_candidates(candidates, target_color, generic_solver_mode);
|
||||
if (nearest.best_idx >= candidates.size())
|
||||
return size_t(-1);
|
||||
if (nearest.second_idx >= candidates.size())
|
||||
return nearest.best_idx;
|
||||
|
||||
const float alternate_fraction =
|
||||
binary_dither_alternate_fraction(candidates, target_oklab, nearest.best_idx, nearest.second_idx);
|
||||
binary_dither_alternate_fraction(candidates, target_color, nearest.best_idx, nearest.second_idx, generic_solver_mode);
|
||||
return std::clamp(threshold, 0.f, 1.f) < alternate_fraction ? nearest.second_idx : nearest.best_idx;
|
||||
}
|
||||
|
||||
@@ -1437,6 +1495,7 @@ std::vector<float> component_weights_for_sample(const WeightedTextureSample &sam
|
||||
std::vector<float> desired(component_count, 0.f);
|
||||
size_t mapped_component_count = component_count;
|
||||
const bool has_raw_component_weights = sample.raw_component_weights.size() == component_count;
|
||||
const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
if (has_raw_component_weights) {
|
||||
float raw_activity = 0.f;
|
||||
for (size_t component_idx = 0; component_idx < component_count; ++component_idx)
|
||||
@@ -1478,7 +1537,7 @@ std::vector<float> component_weights_for_sample(const WeightedTextureSample &sam
|
||||
solve_color_solver_weights_for_target(*generic_mix_candidates,
|
||||
target,
|
||||
color_solver_lookup_mode_from_index(generic_solver_lookup_mode),
|
||||
color_solver_mode_from_index(generic_solver_mode));
|
||||
color_solver_mode_from_index(effective_solver_mode));
|
||||
if (best.size() == component_count)
|
||||
desired = std::move(best);
|
||||
}
|
||||
@@ -1520,6 +1579,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
|
||||
if (component_colors.empty())
|
||||
return weight_field;
|
||||
|
||||
const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
const size_t component_count = component_colors.size();
|
||||
const ModelObject *model_object = print_object.model_object();
|
||||
if (model_object == nullptr)
|
||||
@@ -1845,12 +1905,15 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
|
||||
int x { 0 };
|
||||
int y { 0 };
|
||||
float weight { 0.f };
|
||||
std::array<float, 3> target_oklab { { 0.f, 0.f, 0.f } };
|
||||
std::array<float, 3> target_color { { 0.f, 0.f, 0.f } };
|
||||
std::vector<size_t> sample_indices;
|
||||
};
|
||||
|
||||
auto sample_target_oklab = [tone_gamma, contrast_factor](const WeightedTextureSample &sample) {
|
||||
return color_solver_oklab_from_srgb(texture_sample_target_rgb(sample, tone_gamma, contrast_factor));
|
||||
auto sample_target_color = [tone_gamma, contrast_factor, effective_solver_mode](const WeightedTextureSample &sample) {
|
||||
const std::array<float, 3> target_rgb = texture_sample_target_rgb(sample, tone_gamma, contrast_factor);
|
||||
return effective_solver_mode == int(TextureMappingZone::GenericSolverRGB) ?
|
||||
target_rgb :
|
||||
color_solver_oklab_from_srgb(target_rgb);
|
||||
};
|
||||
|
||||
std::vector<BinaryDitherCell> cells;
|
||||
@@ -1873,10 +1936,10 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
|
||||
}
|
||||
|
||||
BinaryDitherCell &cell = cells[cell_it->second];
|
||||
const std::array<float, 3> target_oklab = sample_target_oklab(sample);
|
||||
const std::array<float, 3> target_color = sample_target_color(sample);
|
||||
const float sample_weight = std::max(sample.weight, 0.f);
|
||||
for (size_t axis = 0; axis < 3; ++axis)
|
||||
cell.target_oklab[axis] += target_oklab[axis] * sample_weight;
|
||||
cell.target_color[axis] += target_color[axis] * sample_weight;
|
||||
cell.weight += sample_weight;
|
||||
cell.sample_indices.emplace_back(sample_idx);
|
||||
}
|
||||
@@ -1884,7 +1947,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
|
||||
if (!cells.empty()) {
|
||||
for (BinaryDitherCell &cell : cells)
|
||||
if (cell.weight > EPSILON)
|
||||
for (float &value : cell.target_oklab)
|
||||
for (float &value : cell.target_color)
|
||||
value /= cell.weight;
|
||||
|
||||
std::vector<size_t> order(cells.size(), 0);
|
||||
@@ -1899,12 +1962,12 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
|
||||
std::map<std::pair<int, int>, std::array<float, 3>> floyd_error;
|
||||
for (const size_t cell_idx : order) {
|
||||
BinaryDitherCell &cell = cells[cell_idx];
|
||||
std::array<float, 3> target_oklab = cell.target_oklab;
|
||||
std::array<float, 3> target_color = cell.target_color;
|
||||
if (clamped_binary_dither_method == int(TextureMappingZone::DitheringFloydSteinberg)) {
|
||||
const auto error_it = floyd_error.find({ cell.x, cell.y });
|
||||
if (error_it != floyd_error.end())
|
||||
for (size_t axis = 0; axis < 3; ++axis)
|
||||
target_oklab[axis] += error_it->second[axis];
|
||||
target_color[axis] += error_it->second[axis];
|
||||
}
|
||||
|
||||
bool thresholded_dither = false;
|
||||
@@ -1916,8 +1979,8 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
|
||||
|
||||
const size_t candidate_idx =
|
||||
thresholded_dither ?
|
||||
thresholded_binary_dither_candidate(binary_candidates, target_oklab, threshold) :
|
||||
nearest_binary_dither_candidate(binary_candidates, target_oklab);
|
||||
thresholded_binary_dither_candidate(binary_candidates, target_color, threshold, effective_solver_mode) :
|
||||
nearest_binary_dither_candidate(binary_candidates, target_color, effective_solver_mode);
|
||||
if (candidate_idx >= binary_candidates.size())
|
||||
continue;
|
||||
|
||||
@@ -1927,10 +1990,12 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
|
||||
binary_dither_masks[sample_idx] = candidate.mask;
|
||||
|
||||
if (clamped_binary_dither_method == int(TextureMappingZone::DitheringFloydSteinberg)) {
|
||||
const std::array<float, 3> &candidate_color =
|
||||
binary_dither_candidate_color(candidate, effective_solver_mode);
|
||||
std::array<float, 3> error = {
|
||||
target_oklab[0] - candidate.oklab[0],
|
||||
target_oklab[1] - candidate.oklab[1],
|
||||
target_oklab[2] - candidate.oklab[2]
|
||||
target_color[0] - candidate_color[0],
|
||||
target_color[1] - candidate_color[1],
|
||||
target_color[2] - candidate_color[2]
|
||||
};
|
||||
auto add_error = [&floyd_error, &cell_index_by_coord, &error](int x, int y, float factor) {
|
||||
if (cell_index_by_coord.find({ x, y }) == cell_index_by_coord.end())
|
||||
@@ -2006,7 +2071,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
|
||||
filament_color_mode,
|
||||
force_sequential_filaments,
|
||||
generic_solver_lookup_mode,
|
||||
generic_solver_mode,
|
||||
effective_solver_mode,
|
||||
use_fixed_color_generic_solver,
|
||||
contrast_factor,
|
||||
tone_gamma,
|
||||
@@ -3080,9 +3145,7 @@ std::optional<TextureMappingOffsetContext> build_texture_mapping_offset_context_
|
||||
const int generic_solver_lookup_mode = std::clamp(zone.generic_solver_lookup_mode,
|
||||
int(TextureMappingZone::GenericSolverClosestMix),
|
||||
int(TextureMappingZone::GenericSolverBlendClosestTwo));
|
||||
const int generic_solver_mode = std::clamp(zone.generic_solver_mode,
|
||||
int(TextureMappingZone::GenericSolverRGB),
|
||||
int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4));
|
||||
const int generic_solver_mode = TextureMappingZone::effective_generic_solver_mode(zone.generic_solver_mode);
|
||||
const int generic_solver_mix_model = std::clamp(zone.generic_solver_mix_model,
|
||||
int(TextureMappingZone::GenericSolverPigmentPainter),
|
||||
int(TextureMappingZone::GenericSolverPrusaFdmMixer));
|
||||
|
||||
@@ -677,8 +677,9 @@ float generic_solver_oklab_chroma_factor(const std::array<float, 3> &target_okla
|
||||
std::array<float, 3> generic_solver_perceptual_axis_weights(const std::array<float, 3> &target_oklab,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
std::array<float, 3> weights = generic_solver_oklab_axis_weights(target_oklab);
|
||||
if (generic_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) {
|
||||
if (effective_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) {
|
||||
weights[0] = std::max(weights[0], 1.f);
|
||||
weights[1] = std::min(weights[1], 4.f);
|
||||
weights[2] = std::min(weights[2], 4.f);
|
||||
@@ -688,7 +689,7 @@ std::array<float, 3> generic_solver_perceptual_axis_weights(const std::array<flo
|
||||
|
||||
bool generic_solver_mode_is_perceptual(int generic_solver_mode)
|
||||
{
|
||||
return generic_solver_mode != int(TextureMappingZone::GenericSolverRGB);
|
||||
return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) != int(TextureMappingZone::GenericSolverRGB);
|
||||
}
|
||||
|
||||
unsigned char to_u8(float value)
|
||||
@@ -1657,7 +1658,7 @@ float texture_preview_mix_candidate_perceptual_error(const TexturePreviewMixCand
|
||||
const float da = candidate.perceptual[1] - target_oklab[1];
|
||||
const float db = candidate.perceptual[2] - target_oklab[2];
|
||||
float error = axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db;
|
||||
if (generic_solver_mode == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) {
|
||||
if (TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) {
|
||||
const float under_l = std::max(0.f, target_oklab[0] - candidate.perceptual[0] - 0.04f);
|
||||
error += 4.f * generic_solver_oklab_chroma_factor(target_oklab) * under_l * under_l;
|
||||
}
|
||||
@@ -1744,9 +1745,7 @@ std::vector<float> best_component_mix_weights_for_target(const std::vector<Textu
|
||||
if (candidates.empty())
|
||||
return {};
|
||||
|
||||
const int clamped_solver_mode = std::clamp(generic_solver_mode,
|
||||
int(TextureMappingZone::GenericSolverRGB),
|
||||
int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4));
|
||||
const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
TexturePreviewMixNearestResult nearest =
|
||||
generic_solver_mode_is_perceptual(clamped_solver_mode) ?
|
||||
nearest_texture_preview_mix_candidates_perceptual(candidates, perceptual_nodes, perceptual_root, target_rgb, clamped_solver_mode) :
|
||||
@@ -1904,6 +1903,41 @@ struct TexturePreviewBinaryDitherCandidate
|
||||
std::array<float, 3> oklab { { 1.f, 0.f, 0.f } };
|
||||
};
|
||||
|
||||
const std::array<float, 3> &binary_dither_candidate_color_for_texture_preview(
|
||||
const TexturePreviewBinaryDitherCandidate &candidate,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ?
|
||||
candidate.rgb :
|
||||
candidate.oklab;
|
||||
}
|
||||
|
||||
std::array<float, 3> binary_dither_axis_weights_for_texture_preview(const std::array<float, 3> &target_color,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
return TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ?
|
||||
std::array<float, 3>{ { 1.f, 1.f, 1.f } } :
|
||||
generic_solver_perceptual_axis_weights(target_color, generic_solver_mode);
|
||||
}
|
||||
|
||||
float binary_dither_candidate_error_for_texture_preview(const TexturePreviewBinaryDitherCandidate &candidate,
|
||||
const std::array<float, 3> &target_color,
|
||||
const std::array<float, 3> &axis_weights,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
const std::array<float, 3> &candidate_color =
|
||||
binary_dither_candidate_color_for_texture_preview(candidate, generic_solver_mode);
|
||||
const float d0 = candidate_color[0] - target_color[0];
|
||||
const float d1 = candidate_color[1] - target_color[1];
|
||||
const float d2 = candidate_color[2] - target_color[2];
|
||||
float error = axis_weights[0] * d0 * d0 + axis_weights[1] * d1 * d1 + axis_weights[2] * d2 * d2;
|
||||
if (TextureMappingZone::effective_generic_solver_mode(generic_solver_mode) == int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) {
|
||||
const float under_l = std::max(0.f, target_color[0] - candidate_color[0] - 0.04f);
|
||||
error += 4.f * generic_solver_oklab_chroma_factor(target_color) * under_l * under_l;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
std::vector<float> binary_component_visibility_weights_for_texture_preview(const TexturePreviewSimulationSettings &settings,
|
||||
uint32_t mask)
|
||||
{
|
||||
@@ -2003,10 +2037,13 @@ std::array<float, 3> texture_preview_target_rgb(const TexturePreviewSimulationSe
|
||||
return apply_texture_contrast_to_rgb(target, std::clamp(settings.contrast_pct, 25.f, 300.f) / 100.f);
|
||||
}
|
||||
|
||||
std::array<float, 3> texture_preview_target_oklab(const TexturePreviewSimulationSettings &settings,
|
||||
std::array<float, 3> texture_preview_target_color(const TexturePreviewSimulationSettings &settings,
|
||||
const std::array<float, 4> &sample_rgba)
|
||||
{
|
||||
return oklab_from_srgb(texture_preview_target_rgb(settings, sample_rgba));
|
||||
const std::array<float, 3> target_rgb = texture_preview_target_rgb(settings, sample_rgba);
|
||||
return TextureMappingZone::effective_generic_solver_mode(settings.generic_solver_mode) == int(TextureMappingZone::GenericSolverRGB) ?
|
||||
target_rgb :
|
||||
oklab_from_srgb(target_rgb);
|
||||
}
|
||||
|
||||
struct TexturePreviewBinaryDitherNearestResult {
|
||||
@@ -2018,16 +2055,18 @@ struct TexturePreviewBinaryDitherNearestResult {
|
||||
|
||||
TexturePreviewBinaryDitherNearestResult nearest_binary_dither_candidates_for_texture_preview(
|
||||
const std::vector<TexturePreviewBinaryDitherCandidate> &candidates,
|
||||
const std::array<float, 3> &target_oklab)
|
||||
const std::array<float, 3> &target_color,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
TexturePreviewBinaryDitherNearestResult result;
|
||||
const std::array<float, 3> axis_weights = generic_solver_oklab_axis_weights(target_oklab);
|
||||
const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
const std::array<float, 3> axis_weights =
|
||||
binary_dither_axis_weights_for_texture_preview(target_color, clamped_solver_mode);
|
||||
for (size_t idx = 0; idx < candidates.size(); ++idx) {
|
||||
const std::array<float, 3> &candidate = candidates[idx].oklab;
|
||||
const float dl = candidate[0] - target_oklab[0];
|
||||
const float da = candidate[1] - target_oklab[1];
|
||||
const float db = candidate[2] - target_oklab[2];
|
||||
const float error = axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db;
|
||||
const float error = binary_dither_candidate_error_for_texture_preview(candidates[idx],
|
||||
target_color,
|
||||
axis_weights,
|
||||
clamped_solver_mode);
|
||||
if (error < result.best_error) {
|
||||
result.second_error = result.best_error;
|
||||
result.second_idx = result.best_idx;
|
||||
@@ -2042,27 +2081,33 @@ TexturePreviewBinaryDitherNearestResult nearest_binary_dither_candidates_for_tex
|
||||
}
|
||||
|
||||
size_t nearest_binary_dither_candidate_for_texture_preview(const std::vector<TexturePreviewBinaryDitherCandidate> &candidates,
|
||||
const std::array<float, 3> &target_oklab)
|
||||
const std::array<float, 3> &target_color,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
return nearest_binary_dither_candidates_for_texture_preview(candidates, target_oklab).best_idx;
|
||||
return nearest_binary_dither_candidates_for_texture_preview(candidates, target_color, generic_solver_mode).best_idx;
|
||||
}
|
||||
|
||||
float binary_dither_alternate_fraction_for_texture_preview(const std::vector<TexturePreviewBinaryDitherCandidate> &candidates,
|
||||
const std::array<float, 3> &target_oklab,
|
||||
const std::array<float, 3> &target_color,
|
||||
size_t base_idx,
|
||||
size_t alternate_idx)
|
||||
size_t alternate_idx,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
if (base_idx >= candidates.size() || alternate_idx >= candidates.size() || base_idx == alternate_idx)
|
||||
return 0.f;
|
||||
|
||||
const std::array<float, 3> axis_weights = generic_solver_oklab_axis_weights(target_oklab);
|
||||
const std::array<float, 3> &base = candidates[base_idx].oklab;
|
||||
const std::array<float, 3> &alternate = candidates[alternate_idx].oklab;
|
||||
const int clamped_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode);
|
||||
const std::array<float, 3> axis_weights =
|
||||
binary_dither_axis_weights_for_texture_preview(target_color, clamped_solver_mode);
|
||||
const std::array<float, 3> &base =
|
||||
binary_dither_candidate_color_for_texture_preview(candidates[base_idx], clamped_solver_mode);
|
||||
const std::array<float, 3> &alternate =
|
||||
binary_dither_candidate_color_for_texture_preview(candidates[alternate_idx], clamped_solver_mode);
|
||||
float numerator = 0.f;
|
||||
float denominator = 0.f;
|
||||
for (size_t axis = 0; axis < 3; ++axis) {
|
||||
const float delta = alternate[axis] - base[axis];
|
||||
numerator += axis_weights[axis] * (target_oklab[axis] - base[axis]) * delta;
|
||||
numerator += axis_weights[axis] * (target_color[axis] - base[axis]) * delta;
|
||||
denominator += axis_weights[axis] * delta * delta;
|
||||
}
|
||||
if (!std::isfinite(numerator) || !std::isfinite(denominator) || denominator <= 1e-12f)
|
||||
@@ -2071,18 +2116,20 @@ float binary_dither_alternate_fraction_for_texture_preview(const std::vector<Tex
|
||||
}
|
||||
|
||||
size_t thresholded_binary_dither_candidate_for_texture_preview(const std::vector<TexturePreviewBinaryDitherCandidate> &candidates,
|
||||
const std::array<float, 3> &target_oklab,
|
||||
float threshold)
|
||||
const std::array<float, 3> &target_color,
|
||||
float threshold,
|
||||
int generic_solver_mode)
|
||||
{
|
||||
const TexturePreviewBinaryDitherNearestResult nearest =
|
||||
nearest_binary_dither_candidates_for_texture_preview(candidates, target_oklab);
|
||||
nearest_binary_dither_candidates_for_texture_preview(candidates, target_color, generic_solver_mode);
|
||||
if (nearest.best_idx >= candidates.size())
|
||||
return size_t(-1);
|
||||
if (nearest.second_idx >= candidates.size())
|
||||
return nearest.best_idx;
|
||||
|
||||
const float alternate_fraction =
|
||||
binary_dither_alternate_fraction_for_texture_preview(candidates, target_oklab, nearest.best_idx, nearest.second_idx);
|
||||
binary_dither_alternate_fraction_for_texture_preview(
|
||||
candidates, target_color, nearest.best_idx, nearest.second_idx, generic_solver_mode);
|
||||
return std::clamp(threshold, 0.f, 1.f) < alternate_fraction ? nearest.second_idx : nearest.best_idx;
|
||||
}
|
||||
|
||||
@@ -2385,8 +2432,9 @@ std::vector<float> component_weights_for_texture_preview(const TexturePreviewSim
|
||||
settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) &&
|
||||
!is_halftone_dithering_method_for_texture_preview(settings.dithering_method)) {
|
||||
const std::vector<TexturePreviewBinaryDitherCandidate> candidates = binary_dither_candidates_for_texture_preview(settings);
|
||||
const std::array<float, 3> target_oklab = texture_preview_target_oklab(settings, sample_rgba);
|
||||
const size_t candidate_idx = nearest_binary_dither_candidate_for_texture_preview(candidates, target_oklab);
|
||||
const std::array<float, 3> target_color = texture_preview_target_color(settings, sample_rgba);
|
||||
const size_t candidate_idx =
|
||||
nearest_binary_dither_candidate_for_texture_preview(candidates, target_color, settings.generic_solver_mode);
|
||||
if (candidate_idx < candidates.size())
|
||||
return binary_component_visibility_weights_for_texture_preview(settings, candidates[candidate_idx].mask);
|
||||
}
|
||||
@@ -2428,7 +2476,7 @@ std::vector<float> component_weights_for_texture_preview(const TexturePreviewSim
|
||||
settings.generic_mix_candidate_cache->candidates,
|
||||
target,
|
||||
color_solver_lookup_mode_from_index(settings.generic_solver_lookup_mode),
|
||||
color_solver_mode_from_index(settings.generic_solver_mode)) :
|
||||
color_solver_mode_from_index(TextureMappingZone::effective_generic_solver_mode(settings.generic_solver_mode))) :
|
||||
std::vector<float>{};
|
||||
if (best.size() == component_count)
|
||||
desired = std::move(best);
|
||||
@@ -2698,9 +2746,7 @@ std::optional<TexturePreviewSimulationSettings> texture_preview_simulation_setti
|
||||
settings.generic_solver_lookup_mode = std::clamp(zone->generic_solver_lookup_mode,
|
||||
int(TextureMappingZone::GenericSolverClosestMix),
|
||||
int(TextureMappingZone::GenericSolverBlendClosestTwo));
|
||||
settings.generic_solver_mode = std::clamp(zone->generic_solver_mode,
|
||||
int(TextureMappingZone::GenericSolverRGB),
|
||||
int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4));
|
||||
settings.generic_solver_mode = TextureMappingZone::effective_generic_solver_mode(zone->generic_solver_mode);
|
||||
settings.generic_solver_mix_model = std::clamp(zone->generic_solver_mix_model,
|
||||
int(TextureMappingZone::GenericSolverPigmentPainter),
|
||||
int(TextureMappingZone::GenericSolverPrusaFdmMixer));
|
||||
@@ -3186,13 +3232,13 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig
|
||||
}
|
||||
component_weights = halftone_component_visibility_weights_for_texture_preview(settings, mask);
|
||||
} else if (use_binary_dithering && !binary_dither_candidates.empty()) {
|
||||
std::array<float, 3> target_oklab = texture_preview_target_oklab(settings, sample_rgba);
|
||||
std::array<float, 3> target_color = texture_preview_target_color(settings, sample_rgba);
|
||||
const int clamped_method = std::clamp(settings.dithering_method,
|
||||
int(TextureMappingZone::DitheringClosest),
|
||||
int(TextureMappingZone::DitheringHalftoneV2));
|
||||
if (clamped_method == int(TextureMappingZone::DitheringFloydSteinberg)) {
|
||||
for (size_t axis = 0; axis < 3; ++axis)
|
||||
target_oklab[axis] += floyd_error_current[x][axis];
|
||||
target_color[axis] += floyd_error_current[x][axis];
|
||||
}
|
||||
|
||||
bool thresholded_dither = false;
|
||||
@@ -3204,16 +3250,20 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig
|
||||
|
||||
const size_t candidate_idx =
|
||||
thresholded_dither ?
|
||||
thresholded_binary_dither_candidate_for_texture_preview(binary_dither_candidates, target_oklab, threshold) :
|
||||
nearest_binary_dither_candidate_for_texture_preview(binary_dither_candidates, target_oklab);
|
||||
thresholded_binary_dither_candidate_for_texture_preview(
|
||||
binary_dither_candidates, target_color, threshold, settings.generic_solver_mode) :
|
||||
nearest_binary_dither_candidate_for_texture_preview(
|
||||
binary_dither_candidates, target_color, settings.generic_solver_mode);
|
||||
if (candidate_idx < binary_dither_candidates.size()) {
|
||||
const TexturePreviewBinaryDitherCandidate &candidate = binary_dither_candidates[candidate_idx];
|
||||
component_weights = binary_component_visibility_weights_for_texture_preview(settings, candidate.mask);
|
||||
if (clamped_method == int(TextureMappingZone::DitheringFloydSteinberg)) {
|
||||
const std::array<float, 3> &candidate_color =
|
||||
binary_dither_candidate_color_for_texture_preview(candidate, settings.generic_solver_mode);
|
||||
const std::array<float, 3> error = {
|
||||
target_oklab[0] - candidate.oklab[0],
|
||||
target_oklab[1] - candidate.oklab[1],
|
||||
target_oklab[2] - candidate.oklab[2]
|
||||
target_color[0] - candidate_color[0],
|
||||
target_color[1] - candidate_color[1],
|
||||
target_color[2] - candidate_color[2]
|
||||
};
|
||||
auto add_error = [&error](std::array<float, 3> &dst, float factor) {
|
||||
for (size_t axis = 0; axis < 3; ++axis)
|
||||
|
||||
@@ -2045,6 +2045,55 @@ static bool texture_mapping_prime_tower_images_equal(const TextureMappingPrimeTo
|
||||
lhs.rgba == rhs.rgba;
|
||||
}
|
||||
|
||||
static wxString texture_mapping_generic_solver_mode_label(int mode)
|
||||
{
|
||||
switch (TextureMappingZone::effective_generic_solver_mode(mode)) {
|
||||
case int(TextureMappingZone::GenericSolverRGB):
|
||||
return _L("RGB");
|
||||
case int(TextureMappingZone::GenericSolverOklab):
|
||||
return _L("Oklab");
|
||||
default:
|
||||
return _L("Oklab soft cap dark correction");
|
||||
}
|
||||
}
|
||||
|
||||
static wxString texture_mapping_generic_solver_default_label()
|
||||
{
|
||||
wxString label = _L("Default");
|
||||
label += " (";
|
||||
label += texture_mapping_generic_solver_mode_label(TextureMappingZone::SlicerDefaultGenericSolverMode);
|
||||
label += ")";
|
||||
return label;
|
||||
}
|
||||
|
||||
static int texture_mapping_generic_solver_mode_choice_selection(int mode)
|
||||
{
|
||||
if (mode == int(TextureMappingZone::GenericSolverDefault))
|
||||
return 0;
|
||||
switch (TextureMappingZone::effective_generic_solver_mode(mode)) {
|
||||
case int(TextureMappingZone::GenericSolverRGB):
|
||||
return 1;
|
||||
case int(TextureMappingZone::GenericSolverOklab):
|
||||
return 2;
|
||||
default:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
static int texture_mapping_generic_solver_mode_from_choice_selection(int selection)
|
||||
{
|
||||
switch (selection) {
|
||||
case 1:
|
||||
return int(TextureMappingZone::GenericSolverRGB);
|
||||
case 2:
|
||||
return int(TextureMappingZone::GenericSolverOklab);
|
||||
case 3:
|
||||
return int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4);
|
||||
default:
|
||||
return TextureMappingZone::DefaultGenericSolverMode;
|
||||
}
|
||||
}
|
||||
|
||||
class TextureMappingAdvancedOptionsDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
@@ -2538,13 +2587,12 @@ public:
|
||||
auto *generic_solver_mode_row = new wxBoxSizer(wxHORIZONTAL);
|
||||
generic_solver_mode_row->Add(new wxStaticText(experimental_page, wxID_ANY, _L("Generic solver")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap);
|
||||
wxArrayString generic_solver_mode_choices;
|
||||
generic_solver_mode_choices.Add(_L("RGB"));
|
||||
generic_solver_mode_choices.Add(_L("Oklab"));
|
||||
generic_solver_mode_choices.Add(_L("Oklab soft cap dark correction"));
|
||||
generic_solver_mode_choices.Add(texture_mapping_generic_solver_default_label());
|
||||
generic_solver_mode_choices.Add(texture_mapping_generic_solver_mode_label(int(TextureMappingZone::GenericSolverRGB)));
|
||||
generic_solver_mode_choices.Add(texture_mapping_generic_solver_mode_label(int(TextureMappingZone::GenericSolverOklab)));
|
||||
generic_solver_mode_choices.Add(texture_mapping_generic_solver_mode_label(int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)));
|
||||
m_generic_solver_mode_choice = new wxChoice(experimental_page, wxID_ANY, wxDefaultPosition, wxDefaultSize, generic_solver_mode_choices);
|
||||
m_generic_solver_mode_choice->SetSelection(std::clamp(generic_solver_mode,
|
||||
int(TextureMappingZone::GenericSolverRGB),
|
||||
int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)));
|
||||
m_generic_solver_mode_choice->SetSelection(texture_mapping_generic_solver_mode_choice_selection(generic_solver_mode));
|
||||
generic_solver_mode_row->Add(m_generic_solver_mode_choice, 1, wxALIGN_CENTER_VERTICAL);
|
||||
experimental_box->Add(generic_solver_mode_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap);
|
||||
|
||||
@@ -3535,7 +3583,7 @@ public:
|
||||
bool top_surface_contoning_surface_anchored_stacks_enabled() const
|
||||
{
|
||||
if (!TextureMappingZone::ShowExperimentalTopSurfaceContoningOptions)
|
||||
return false;
|
||||
return true;
|
||||
return m_top_surface_contoning_surface_anchored_stacks_checkbox == nullptr ?
|
||||
TextureMappingZone::DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled :
|
||||
m_top_surface_contoning_surface_anchored_stacks_checkbox->GetValue();
|
||||
@@ -3607,9 +3655,7 @@ public:
|
||||
int generic_solver_mode() const
|
||||
{
|
||||
return m_generic_solver_mode_choice ?
|
||||
std::clamp(m_generic_solver_mode_choice->GetSelection(),
|
||||
int(TextureMappingZone::GenericSolverRGB),
|
||||
int(TextureMappingZone::GenericSolverOklabSoftCap4Dark4)) :
|
||||
texture_mapping_generic_solver_mode_from_choice_selection(m_generic_solver_mode_choice->GetSelection()) :
|
||||
TextureMappingZone::DefaultGenericSolverMode;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user