Allow loading filament calibration model (top-surface coloring)

This commit is contained in:
sentientstardust
2026-06-01 14:36:38 +01:00
parent f6c56bf2bb
commit 5540971e7f
8 changed files with 1223 additions and 79 deletions

View File

@@ -80,6 +80,106 @@ std::array<float, 3> linear_to_srgb_color(const std::array<float, 3> &rgb)
};
}
std::array<float, 3> eval_linear_rgb_model(const std::vector<float> &features,
const std::vector<float> &coefficients)
{
if (features.empty() || coefficients.size() != features.size() * 3)
return { 0.f, 0.f, 0.f };
std::array<float, 3> linear { 0.f, 0.f, 0.f };
for (size_t feature_idx = 0; feature_idx < features.size(); ++feature_idx) {
const float feature = std::isfinite(features[feature_idx]) ? features[feature_idx] : 0.f;
for (size_t channel = 0; channel < 3; ++channel) {
const float coeff = coefficients[feature_idx * 3 + channel];
if (std::isfinite(coeff))
linear[channel] += feature * coeff;
}
}
for (float &channel : linear)
channel = clamp01(channel);
return linear_to_srgb_color(linear);
}
std::array<float, 3> eval_current_linear_affine_model(const std::array<float, 3> &base_srgb,
const std::vector<float> &coefficients)
{
if (coefficients.size() != 12)
return base_srgb;
const std::array<float, 3> base_linear = srgb_to_linear_color(base_srgb);
return eval_linear_rgb_model({ base_linear[0], base_linear[1], base_linear[2], 1.f }, coefficients);
}
std::array<float, 3> eval_alpha_effective_model(const std::vector<uint16_t> &surface_to_deep,
const ColorSolverCalibratedStackModel &model)
{
const size_t component_count = model.component_count;
if (component_count == 0 || model.alphas.size() < component_count ||
model.coefficients_linear_rgb.size() != (component_count + 1) * 3)
return { 0.f, 0.f, 0.f };
std::vector<float> features(component_count + 1, 0.f);
float remaining = 1.f;
for (uint16_t component_idx : surface_to_deep) {
const size_t component = size_t(component_idx);
if (component >= component_count)
continue;
const float alpha = std::clamp(std::isfinite(model.alphas[component]) ? model.alphas[component] : 0.f, 0.f, 0.9999f);
features[component] += remaining * alpha;
remaining *= 1.f - alpha;
if (remaining <= 1e-5f)
break;
}
features[component_count] = std::max(0.f, remaining);
return eval_linear_rgb_model(features, model.coefficients_linear_rgb);
}
std::array<float, 3> eval_depth_kernel_linear_model(const std::vector<uint16_t> &surface_to_deep,
const ColorSolverCalibratedStackModel &model)
{
const size_t component_count = model.component_count;
const size_t kernel_count = model.taus.size();
const size_t feature_count = 1 + component_count + component_count * kernel_count;
if (component_count == 0 || kernel_count == 0 ||
model.coefficients_linear_rgb.size() != feature_count * 3)
return { 0.f, 0.f, 0.f };
std::vector<float> features(feature_count, 0.f);
features[0] = 1.f;
if (!surface_to_deep.empty() && size_t(surface_to_deep.front()) < component_count)
features[1 + size_t(surface_to_deep.front())] = 1.f;
for (size_t kernel_idx = 0; kernel_idx < kernel_count; ++kernel_idx) {
const float tau = model.taus[kernel_idx];
const size_t base = 1 + component_count + kernel_idx * component_count;
float norm = 0.f;
for (size_t depth_idx = 0; depth_idx < surface_to_deep.size(); ++depth_idx) {
const size_t component = size_t(surface_to_deep[depth_idx]);
if (component >= component_count)
continue;
const float weight =
std::isfinite(tau) && tau > 0.f ?
std::exp(-float(depth_idx) / tau) :
1.f;
norm += weight;
features[base + component] += weight;
}
if (norm > 1e-12f) {
const float inv_norm = 1.f / norm;
for (size_t component = 0; component < component_count; ++component)
features[base + component] *= inv_norm;
}
}
return eval_linear_rgb_model(features, model.coefficients_linear_rgb);
}
bool calibrated_stack_model_valid_for_mix(const ColorSolverCalibratedStackModel *model,
size_t component_count)
{
return model != nullptr && model->valid() &&
(model->kind == ColorSolverCalibratedStackModelKind::CurrentLinearAffine ||
model->component_count == component_count);
}
float linear_luminance(const std::array<float, 3> &rgb)
{
return 0.2126f * rgb[0] + 0.7152f * rgb[1] + 0.0722f * rgb[2];
@@ -688,17 +788,44 @@ std::array<float, 3> mix_ordered_stack_with_buffers(const std::vector<std::array
bool beer_lambert_rgb_correction,
bool td_effective_alpha_correction,
const std::vector<ColorSolverStackComponentRole> &component_roles,
const std::vector<float> &layer_opacities_by_depth)
const std::vector<float> &layer_opacities_by_depth,
const ColorSolverCalibratedStackModel *calibrated_stack_model)
{
if (colors_with_background.empty() || surface_to_deep.empty())
return colors_with_background.empty() ? std::array<float, 3>{ { 0.f, 0.f, 0.f } } : colors_with_background.back();
const size_t component_count = colors_with_background.size() - 1;
if (calibrated_stack_model_valid_for_mix(calibrated_stack_model, component_count)) {
switch (calibrated_stack_model->kind) {
case ColorSolverCalibratedStackModelKind::CurrentLinearAffine: {
const std::array<float, 3> base =
mix_ordered_stack_with_buffers(colors_with_background,
weights,
surface_to_deep,
layer_opacities,
mix_model,
surface_scatter,
beer_lambert_rgb_correction,
td_effective_alpha_correction,
component_roles,
layer_opacities_by_depth,
nullptr);
return eval_current_linear_affine_model(base, calibrated_stack_model->coefficients_linear_rgb);
}
case ColorSolverCalibratedStackModelKind::AlphaEffective:
return eval_alpha_effective_model(surface_to_deep, *calibrated_stack_model);
case ColorSolverCalibratedStackModelKind::DepthKernelLinear:
return eval_depth_kernel_linear_model(surface_to_deep, *calibrated_stack_model);
default:
break;
}
}
if (td_effective_alpha_correction)
return mix_ordered_stack_td_effective_alpha(colors_with_background, surface_to_deep, layer_opacities, 0.f, component_roles, layer_opacities_by_depth);
if (beer_lambert_rgb_correction)
return mix_ordered_stack_beer_lambert_rgb(colors_with_background, surface_to_deep, layer_opacities, surface_scatter, layer_opacities_by_depth);
const size_t component_count = colors_with_background.size() - 1;
weights.assign(colors_with_background.size(), 0.f);
float transmission = 1.f;
bool surface_layer = true;
@@ -1041,7 +1168,8 @@ void append_ordered_stack_candidate(ColorSolverOrderedStackCandidateSet &c
bool beer_lambert_rgb_correction,
bool td_effective_alpha_correction,
const std::vector<ColorSolverStackComponentRole> &component_roles,
const std::vector<float> &layer_opacities_by_depth)
const std::vector<float> &layer_opacities_by_depth,
const ColorSolverCalibratedStackModel *calibrated_stack_model)
{
std::vector<uint16_t> simulated_surface_to_deep;
const std::vector<uint16_t> *mix_stack = &surface_to_deep;
@@ -1059,7 +1187,8 @@ void append_ordered_stack_candidate(ColorSolverOrderedStackCandidateSet &c
beer_lambert_rgb_correction,
td_effective_alpha_correction,
component_roles,
layer_opacities_by_depth);
layer_opacities_by_depth,
calibrated_stack_model);
const std::array<float, 3> perceptual = oklab_from_srgb(mixed);
candidates.rgbs.emplace_back(mixed[0]);
candidates.rgbs.emplace_back(mixed[1]);
@@ -1087,6 +1216,43 @@ ColorSolverMode color_solver_mode_from_index(int mode)
return ColorSolverMode(std::clamp(mode, int(ColorSolverMode::RGB), int(ColorSolverMode::OklabSoftCap4Dark4)));
}
bool ColorSolverCalibratedStackModel::valid() const
{
switch (kind) {
case ColorSolverCalibratedStackModelKind::CurrentLinearAffine:
return coefficients_linear_rgb.size() == 12;
case ColorSolverCalibratedStackModelKind::AlphaEffective:
return component_count > 0 &&
alphas.size() >= component_count &&
coefficients_linear_rgb.size() == (component_count + 1) * 3;
case ColorSolverCalibratedStackModelKind::DepthKernelLinear:
return component_count > 0 &&
!taus.empty() &&
coefficients_linear_rgb.size() == (1 + component_count + component_count * taus.size()) * 3;
default:
return false;
}
}
std::string ColorSolverCalibratedStackModel::cache_key() const
{
if (!valid())
return std::string();
std::ostringstream key;
key << "cal" << int(kind) << "n" << component_count;
auto append_values = [&key](const char *label, const std::vector<float> &values) {
key << '|' << label;
for (float value : values) {
const float safe = std::isfinite(value) ? std::clamp(value, -1000.f, 1000.f) : 0.f;
key << ',' << int(std::lround(safe * 1000000.f));
}
};
append_values("a", alphas);
append_values("t", taus);
append_values("w", coefficients_linear_rgb);
return key.str();
}
int color_solver_total_units_for_component_count(size_t component_count)
{
return component_count <= 4 ? 40 : (component_count == 5 ? 24 : (component_count == 6 ? 20 : 12));
@@ -1138,7 +1304,8 @@ std::array<float, 3> mix_color_solver_ordered_stack(const std::vector<std::array
bool beer_lambert_rgb_correction,
bool td_effective_alpha_correction,
const std::vector<ColorSolverStackComponentRole> &component_roles,
const std::vector<float> &layer_opacities_by_depth)
const std::vector<float> &layer_opacities_by_depth,
const ColorSolverCalibratedStackModel *calibrated_stack_model)
{
if (component_colors.empty() || surface_to_deep.empty())
return background_rgb;
@@ -1155,7 +1322,8 @@ std::array<float, 3> mix_color_solver_ordered_stack(const std::vector<std::array
beer_lambert_rgb_correction,
td_effective_alpha_correction,
component_roles,
layer_opacities_by_depth);
layer_opacities_by_depth,
calibrated_stack_model);
}
std::array<float, 3> color_solver_oklab_from_srgb(const std::array<float, 3> &rgb)
@@ -1294,7 +1462,8 @@ std::string color_solver_ordered_stack_candidate_cache_key(const std::vector<std
bool td_effective_alpha_correction,
const std::vector<ColorSolverStackComponentRole> &component_roles,
bool beam_search_stack_expansion,
const std::vector<float> &layer_opacities_by_depth)
const std::vector<float> &layer_opacities_by_depth,
const ColorSolverCalibratedStackModel *calibrated_stack_model)
{
std::ostringstream key;
key << component_colors.size();
@@ -1346,6 +1515,8 @@ std::string color_solver_ordered_stack_candidate_cache_key(const std::vector<std
key << ',' << int(std::lround(opacity * 1000000.f));
}
}
if (calibrated_stack_model != nullptr && calibrated_stack_model->valid())
key << '|' << calibrated_stack_model->cache_key();
return key.str();
}
@@ -1363,7 +1534,8 @@ ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates(
bool td_effective_alpha_correction,
const std::vector<ColorSolverStackComponentRole> &component_roles,
bool beam_search_stack_expansion,
const std::vector<float> &layer_opacities_by_depth)
const std::vector<float> &layer_opacities_by_depth,
const ColorSolverCalibratedStackModel *calibrated_stack_model)
{
ColorSolverOrderedStackCandidateSet candidates;
int simulated_depth = simulated_stack_depth > 0 ? simulated_stack_depth : stack_depth;
@@ -1405,7 +1577,8 @@ ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates(
beer_lambert_rgb_correction,
td_effective_alpha_correction,
component_roles,
layer_opacities_by_depth);
layer_opacities_by_depth,
calibrated_stack_model);
return;
}
@@ -1459,7 +1632,8 @@ ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates(
beer_lambert_rgb_correction,
td_effective_alpha_correction,
component_roles,
layer_opacities_by_depth);
layer_opacities_by_depth,
calibrated_stack_model);
}
return;
}
@@ -1491,7 +1665,8 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates
bool td_effective_alpha_correction,
const std::vector<ColorSolverStackComponentRole> &component_roles,
bool beam_search_stack_expansion,
const std::vector<float> &layer_opacities_by_depth)
const std::vector<float> &layer_opacities_by_depth,
const ColorSolverCalibratedStackModel *calibrated_stack_model)
{
const std::string key =
color_solver_ordered_stack_candidate_cache_key(component_colors,
@@ -1507,7 +1682,8 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates
td_effective_alpha_correction,
component_roles,
beam_search_stack_expansion,
layer_opacities_by_depth);
layer_opacities_by_depth,
calibrated_stack_model);
auto it = cache.find(key);
if (it != cache.end())
return it->second;
@@ -1526,7 +1702,8 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates
td_effective_alpha_correction,
component_roles,
beam_search_stack_expansion,
layer_opacities_by_depth)).first->second;
layer_opacities_by_depth,
calibrated_stack_model)).first->second;
}
ColorSolverOrderedStackResult solve_color_solver_ordered_stack_result_for_target(

View File

@@ -55,6 +55,25 @@ enum class ColorSolverStackComponentRole : uint8_t
White = 5
};
enum class ColorSolverCalibratedStackModelKind : uint8_t
{
None = 0,
CurrentLinearAffine = 1,
AlphaEffective = 2,
DepthKernelLinear = 3
};
struct ColorSolverCalibratedStackModel {
ColorSolverCalibratedStackModelKind kind { ColorSolverCalibratedStackModelKind::None };
size_t component_count { 0 };
std::vector<float> alphas;
std::vector<float> taus;
std::vector<float> coefficients_linear_rgb;
bool valid() const;
std::string cache_key() const;
};
struct ColorSolverCandidateSet {
struct KdNode {
uint32_t candidate_idx { 0 };
@@ -132,7 +151,8 @@ std::array<float, 3> mix_color_solver_ordered_stack(const std::vector<std::array
bool beer_lambert_rgb_correction = false,
bool td_effective_alpha_correction = false,
const std::vector<ColorSolverStackComponentRole> &component_roles = {},
const std::vector<float> &layer_opacities_by_depth = {});
const std::vector<float> &layer_opacities_by_depth = {},
const ColorSolverCalibratedStackModel *calibrated_stack_model = nullptr);
std::array<float, 3> color_solver_oklab_from_srgb(const std::array<float, 3> &rgb);
std::array<float, 3> color_solver_srgb_from_oklab(const std::array<float, 3> &oklab);
@@ -163,7 +183,8 @@ std::string color_solver_ordered_stack_candidate_cache_key(const std::vector<std
bool td_effective_alpha_correction = false,
const std::vector<ColorSolverStackComponentRole> &component_roles = {},
bool beam_search_stack_expansion = false,
const std::vector<float> &layer_opacities_by_depth = {});
const std::vector<float> &layer_opacities_by_depth = {},
const ColorSolverCalibratedStackModel *calibrated_stack_model = nullptr);
ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates(
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &layer_opacities,
@@ -178,7 +199,8 @@ ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates(
bool td_effective_alpha_correction = false,
const std::vector<ColorSolverStackComponentRole> &component_roles = {},
bool beam_search_stack_expansion = false,
const std::vector<float> &layer_opacities_by_depth = {});
const std::vector<float> &layer_opacities_by_depth = {},
const ColorSolverCalibratedStackModel *calibrated_stack_model = nullptr);
const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates(
ColorSolverOrderedStackCandidateCache &cache,
const std::vector<std::array<float, 3>> &component_colors,
@@ -194,7 +216,8 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates
bool td_effective_alpha_correction = false,
const std::vector<ColorSolverStackComponentRole> &component_roles = {},
bool beam_search_stack_expansion = false,
const std::vector<float> &layer_opacities_by_depth = {});
const std::vector<float> &layer_opacities_by_depth = {},
const ColorSolverCalibratedStackModel *calibrated_stack_model = nullptr);
ColorSolverOrderedStackResult solve_color_solver_ordered_stack_result_for_target(
const ColorSolverOrderedStackCandidateSet &candidates,
const std::array<float, 3> &target_rgb,

View File

@@ -10,6 +10,7 @@
#include <cstdio>
#include <iomanip>
#include <limits>
#include <map>
#include <nlohmann/json.hpp>
#include <numeric>
#include <sstream>
@@ -484,6 +485,80 @@ static nlohmann::json floats_to_json(const std::vector<float> &values)
return out;
}
static std::array<float, 3> color_array_from_hex(const std::string &hex)
{
const RGB rgb = parse_hex_color(hex);
return { { float(rgb.r) / 255.f, float(rgb.g) / 255.f, float(rgb.b) / 255.f } };
}
static std::string json_string_or_empty(const nlohmann::json &object, const std::initializer_list<const char*> &keys)
{
if (!object.is_object())
return std::string();
for (const char *key : keys) {
auto it = object.find(key);
if (it != object.end() && it->is_string())
return it->get<std::string>();
}
return std::string();
}
static float json_float_or(const nlohmann::json &object, const std::initializer_list<const char*> &keys, float fallback)
{
if (!object.is_object())
return fallback;
for (const char *key : keys) {
auto it = object.find(key);
if (it != object.end() && it->is_number())
return it->get<float>();
}
return fallback;
}
static std::vector<float> json_float_vector_or_empty(const nlohmann::json &object, const std::initializer_list<const char*> &keys)
{
if (!object.is_object())
return {};
for (const char *key : keys) {
auto it = object.find(key);
if (it != object.end())
return floats_from_json(*it);
}
return {};
}
static std::vector<float> calibration_taus_from_json(const nlohmann::json &object)
{
std::vector<float> taus;
if (!object.is_object())
return taus;
auto taus_it = object.find("taus");
if (taus_it != object.end() && taus_it->is_array()) {
for (const nlohmann::json &item : *taus_it)
taus.emplace_back(item.is_number() ? item.get<float>() : -1.f);
return taus;
}
auto kernels_it = object.find("depth_kernels");
if (kernels_it != object.end() && kernels_it->is_array()) {
for (const nlohmann::json &item : *kernels_it) {
if (item.is_object()) {
auto tau_it = item.find("tau_layers");
taus.emplace_back(tau_it != item.end() && tau_it->is_number() ? tau_it->get<float>() : -1.f);
}
}
}
return taus;
}
static std::string lower_ascii(std::string value)
{
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) {
const char lowered = char(std::tolower(c));
return lowered == '-' || lowered == ' ' ? '_' : lowered;
});
return value;
}
static nlohmann::json point3_to_json(const std::array<float, 3> &point)
{
return nlohmann::json::array({ point[0], point[1], point[2] });
@@ -737,6 +812,14 @@ static std::string top_surface_contoning_color_prediction_mode_name(int mode)
return "rgb_beer_lambert";
if (mode == int(TextureMappingZone::ContoningColorPredictionBasicReflectance))
return "basic_reflectance";
if (mode == int(TextureMappingZone::ContoningColorPredictionCalibratedCurrentLinearAffine))
return "calibrated_current_linear_affine";
if (mode == int(TextureMappingZone::ContoningColorPredictionCalibratedTdAlphaEffective))
return "calibrated_td_alpha_effective";
if (mode == int(TextureMappingZone::ContoningColorPredictionCalibratedFreeAlphaEffective))
return "calibrated_free_alpha_effective";
if (mode == int(TextureMappingZone::ContoningColorPredictionCalibratedDepthKernelLinear))
return "calibrated_depth_kernel_linear";
return "default";
}
@@ -754,6 +837,23 @@ static int top_surface_contoning_color_prediction_mode_from_name(std::string nam
name == "basic_reflectance_no_td_correction" ||
name == "no_td_correction")
return int(TextureMappingZone::ContoningColorPredictionBasicReflectance);
if (name == "calibrated_current_linear_affine" ||
name == "calibrated_current_affine" ||
name == "current_linear_affine" ||
name == "current_linear_affine_global")
return int(TextureMappingZone::ContoningColorPredictionCalibratedCurrentLinearAffine);
if (name == "calibrated_td_alpha_effective" ||
name == "td_alpha_effective" ||
name == "td_alpha_effective_global")
return int(TextureMappingZone::ContoningColorPredictionCalibratedTdAlphaEffective);
if (name == "calibrated_free_alpha_effective" ||
name == "free_alpha_effective" ||
name == "free_alpha_effective_global")
return int(TextureMappingZone::ContoningColorPredictionCalibratedFreeAlphaEffective);
if (name == "calibrated_depth_kernel_linear" ||
name == "depth_kernel_linear" ||
name == "depth_kernel_linear_global")
return int(TextureMappingZone::ContoningColorPredictionCalibratedDepthKernelLinear);
return TextureMappingZone::DefaultTopSurfaceContoningColorPredictionMode;
}
@@ -1123,6 +1223,476 @@ bool TextureMappingGlobalSettings::is_generic_solver_color_mode(const std::strin
return normalize_color_mode_name(mode) == "generic_solver";
}
bool TextureMappingColorCalibration::has_mode(int mode) const
{
switch (TextureMappingZone::effective_top_surface_contoning_color_prediction_mode(mode)) {
case int(TextureMappingZone::ContoningColorPredictionCalibratedCurrentLinearAffine):
return current_linear_affine.valid();
case int(TextureMappingZone::ContoningColorPredictionCalibratedTdAlphaEffective):
return td_alpha_effective.valid();
case int(TextureMappingZone::ContoningColorPredictionCalibratedFreeAlphaEffective):
return free_alpha_effective.valid();
case int(TextureMappingZone::ContoningColorPredictionCalibratedDepthKernelLinear):
return depth_kernel_linear.valid();
default:
return false;
}
}
namespace {
static const nlohmann::json *calibration_model_json(const nlohmann::json &models, const char *base_key)
{
if (!models.is_object())
return nullptr;
auto it = models.find(base_key);
if (it != models.end())
return &*it;
const std::string global_key = std::string(base_key) + "_global";
it = models.find(global_key);
return it != models.end() ? &*it : nullptr;
}
static ColorSolverCalibratedStackModel calibration_current_linear_affine_model(const nlohmann::json &raw)
{
ColorSolverCalibratedStackModel model;
model.kind = ColorSolverCalibratedStackModelKind::CurrentLinearAffine;
model.coefficients_linear_rgb = raw.is_array() ?
floats_from_json(raw) :
json_float_vector_or_empty(raw, { "weights", "coefficients_linear_rgb" });
if (!model.valid())
model = {};
return model;
}
static ColorSolverCalibratedStackModel calibration_alpha_model(const nlohmann::json &raw)
{
ColorSolverCalibratedStackModel model;
model.kind = ColorSolverCalibratedStackModelKind::AlphaEffective;
model.alphas = json_float_vector_or_empty(raw, {
"alphas",
"alphas_at_nominal_layer_height",
"learned_alphas"
});
model.component_count = model.alphas.size();
model.coefficients_linear_rgb = json_float_vector_or_empty(raw, { "weights", "coefficients_linear_rgb" });
if (!model.valid())
model = {};
return model;
}
static ColorSolverCalibratedStackModel calibration_depth_kernel_model(const nlohmann::json &raw, size_t fallback_component_count)
{
ColorSolverCalibratedStackModel model;
model.kind = ColorSolverCalibratedStackModelKind::DepthKernelLinear;
model.component_count = size_t(std::max(0, raw.value("component_count", int(fallback_component_count))));
model.taus = calibration_taus_from_json(raw);
model.coefficients_linear_rgb = json_float_vector_or_empty(raw, { "weights", "coefficients_linear_rgb" });
if (!model.valid())
model = {};
return model;
}
static TextureMappingColorCalibrationFilament calibration_filament_from_json(const std::string &fallback_name,
const nlohmann::json &raw)
{
TextureMappingColorCalibrationFilament filament;
filament.name = json_string_or_empty(raw, { "name", "role", "id" });
if (filament.name.empty())
filament.name = fallback_name;
const std::string hex = json_string_or_empty(raw, { "color", "display_color_hex", "display_color" });
if (!hex.empty())
filament.color = color_array_from_hex(hex);
else
filament.color = point3_from_json(raw.value("display_color_srgb", nlohmann::json::array()));
filament.td_mm = json_float_or(raw, { "td_mm", "transmission_distance_mm" }, 0.f);
return filament;
}
static std::vector<std::string> calibration_component_order_from_json(const nlohmann::json &root)
{
const nlohmann::json *order = nullptr;
auto order_it = root.find("component_order");
if (order_it != root.end() && order_it->is_array())
order = &*order_it;
auto applicability_it = root.find("applicability");
if (order == nullptr && applicability_it != root.end() && applicability_it->is_object()) {
auto app_order_it = applicability_it->find("component_order");
if (app_order_it != applicability_it->end() && app_order_it->is_array())
order = &*app_order_it;
}
std::vector<std::string> out;
if (order == nullptr)
return out;
for (const nlohmann::json &item : *order)
if (item.is_string())
out.emplace_back(item.get<std::string>());
return out;
}
static std::vector<TextureMappingColorCalibrationFilament> calibration_filaments_from_json(const nlohmann::json &root)
{
std::vector<TextureMappingColorCalibrationFilament> filaments;
auto filaments_it = root.find("filaments");
if (filaments_it == root.end())
return filaments;
if (filaments_it->is_array()) {
filaments.reserve(filaments_it->size());
for (const nlohmann::json &item : *filaments_it)
filaments.emplace_back(calibration_filament_from_json(std::string(), item));
return filaments;
}
if (!filaments_it->is_object())
return filaments;
std::map<std::string, TextureMappingColorCalibrationFilament> by_name;
for (auto it = filaments_it->begin(); it != filaments_it->end(); ++it)
by_name.emplace(lower_ascii(it.key()), calibration_filament_from_json(it.key(), it.value()));
for (const std::string &name : calibration_component_order_from_json(root)) {
auto it = by_name.find(lower_ascii(name));
if (it == by_name.end())
continue;
filaments.emplace_back(it->second);
by_name.erase(it);
}
for (const auto &item : by_name)
filaments.emplace_back(item.second);
return filaments;
}
static const ColorSolverCalibratedStackModel &calibration_model_for_mode(const TextureMappingColorCalibration &calibration,
int mode)
{
switch (TextureMappingZone::effective_top_surface_contoning_color_prediction_mode(mode)) {
case int(TextureMappingZone::ContoningColorPredictionCalibratedCurrentLinearAffine):
return calibration.current_linear_affine;
case int(TextureMappingZone::ContoningColorPredictionCalibratedTdAlphaEffective):
return calibration.td_alpha_effective;
case int(TextureMappingZone::ContoningColorPredictionCalibratedFreeAlphaEffective):
return calibration.free_alpha_effective;
default:
return calibration.depth_kernel_linear;
}
}
struct TextureMappingCalibrationMatch {
std::vector<size_t> calibration_index_by_component;
std::string warning;
bool complete { false };
};
static float calibration_color_distance(const std::array<float, 3> &lhs, const std::array<float, 3> &rhs)
{
const std::array<float, 3> lhs_oklab = color_solver_oklab_from_srgb(lhs);
const std::array<float, 3> rhs_oklab = color_solver_oklab_from_srgb(rhs);
const float dl = lhs_oklab[0] - rhs_oklab[0];
const float da = lhs_oklab[1] - rhs_oklab[1];
const float db = lhs_oklab[2] - rhs_oklab[2];
return std::sqrt(dl * dl + da * da + db * db);
}
static TextureMappingCalibrationMatch match_calibration_filaments(const TextureMappingColorCalibration &calibration,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm)
{
TextureMappingCalibrationMatch match;
match.calibration_index_by_component.assign(component_colors.size(), size_t(-1));
if (component_colors.empty()) {
match.complete = true;
return match;
}
if (calibration.filaments.empty()) {
match.warning = "The calibration file does not include filament color/TD metadata.";
return match;
}
std::vector<bool> used(calibration.filaments.size(), false);
std::vector<std::string> notes;
for (size_t component_idx = 0; component_idx < component_colors.size(); ++component_idx) {
size_t best_idx = size_t(-1);
float best_score = std::numeric_limits<float>::max();
float best_color_distance = std::numeric_limits<float>::max();
for (size_t calibration_idx = 0; calibration_idx < calibration.filaments.size(); ++calibration_idx) {
if (used[calibration_idx])
continue;
const TextureMappingColorCalibrationFilament &filament = calibration.filaments[calibration_idx];
const float color_distance = calibration_color_distance(component_colors[component_idx], filament.color);
float td_score = 0.f;
const float component_td = component_idx < component_tds_mm.size() ? component_tds_mm[component_idx] : 0.f;
if (component_td > 0.f && filament.td_mm > 0.f)
td_score = std::min(0.12f, std::abs(component_td - filament.td_mm) / std::max({ component_td, filament.td_mm, 1.f }) * 0.12f);
const float score = color_distance + td_score;
if (score < best_score) {
best_score = score;
best_idx = calibration_idx;
best_color_distance = color_distance;
}
}
if (best_idx == size_t(-1))
continue;
used[best_idx] = true;
match.calibration_index_by_component[component_idx] = best_idx;
const TextureMappingColorCalibrationFilament &filament = calibration.filaments[best_idx];
const float component_td = component_idx < component_tds_mm.size() ? component_tds_mm[component_idx] : 0.f;
if (best_color_distance > 0.16f) {
notes.emplace_back("component " + std::to_string(component_idx + 1) +
" color does not closely match calibrated " + filament.name);
}
if (component_td > 0.f && filament.td_mm > 0.f) {
const float td_delta = std::abs(component_td - filament.td_mm);
if (td_delta > std::max(1.f, std::max(component_td, filament.td_mm) * 0.35f)) {
notes.emplace_back("component " + std::to_string(component_idx + 1) +
" TD differs from calibrated " + filament.name);
}
}
}
match.complete = std::all_of(match.calibration_index_by_component.begin(),
match.calibration_index_by_component.end(),
[](size_t idx) { return idx != size_t(-1); });
if (!match.complete)
notes.emplace_back("the calibration file does not contain enough matching filaments");
if (!notes.empty()) {
match.warning = "Top-surface color calibration mismatch: ";
for (size_t idx = 0; idx < notes.size(); ++idx) {
if (idx > 0)
match.warning += "; ";
match.warning += notes[idx];
}
match.warning += ".";
}
return match;
}
static std::optional<ColorSolverCalibratedStackModel> remap_calibration_model(
const ColorSolverCalibratedStackModel &source,
int mode,
const TextureMappingCalibrationMatch &match,
size_t target_component_count)
{
if (!source.valid())
return std::nullopt;
if (TextureMappingZone::effective_top_surface_contoning_color_prediction_mode(mode) ==
int(TextureMappingZone::ContoningColorPredictionCalibratedCurrentLinearAffine))
return source;
if (!match.complete || target_component_count == 0)
return std::nullopt;
if (source.kind == ColorSolverCalibratedStackModelKind::AlphaEffective) {
ColorSolverCalibratedStackModel out;
out.kind = ColorSolverCalibratedStackModelKind::AlphaEffective;
out.component_count = target_component_count;
out.alphas.assign(target_component_count, 0.f);
out.coefficients_linear_rgb.assign((target_component_count + 1) * 3, 0.f);
const size_t source_component_count = source.component_count;
if (source.alphas.size() < source_component_count ||
source.coefficients_linear_rgb.size() != (source_component_count + 1) * 3)
return std::nullopt;
for (size_t component_idx = 0; component_idx < target_component_count; ++component_idx) {
const size_t source_idx = match.calibration_index_by_component[component_idx];
if (source_idx >= source_component_count)
return std::nullopt;
out.alphas[component_idx] = source.alphas[source_idx];
for (size_t channel = 0; channel < 3; ++channel)
out.coefficients_linear_rgb[component_idx * 3 + channel] = source.coefficients_linear_rgb[source_idx * 3 + channel];
}
for (size_t channel = 0; channel < 3; ++channel)
out.coefficients_linear_rgb[target_component_count * 3 + channel] =
source.coefficients_linear_rgb[source_component_count * 3 + channel];
return out.valid() ? std::optional<ColorSolverCalibratedStackModel>(out) : std::nullopt;
}
if (source.kind == ColorSolverCalibratedStackModelKind::DepthKernelLinear) {
ColorSolverCalibratedStackModel out;
out.kind = ColorSolverCalibratedStackModelKind::DepthKernelLinear;
out.component_count = target_component_count;
out.taus = source.taus;
out.coefficients_linear_rgb.assign((1 + target_component_count + target_component_count * out.taus.size()) * 3, 0.f);
const size_t source_component_count = source.component_count;
if (source.coefficients_linear_rgb.size() != (1 + source_component_count + source_component_count * source.taus.size()) * 3)
return std::nullopt;
auto copy_row = [&source, &out](size_t dst_row, size_t src_row) {
for (size_t channel = 0; channel < 3; ++channel)
out.coefficients_linear_rgb[dst_row * 3 + channel] = source.coefficients_linear_rgb[src_row * 3 + channel];
};
copy_row(0, 0);
for (size_t component_idx = 0; component_idx < target_component_count; ++component_idx) {
const size_t source_idx = match.calibration_index_by_component[component_idx];
if (source_idx >= source_component_count)
return std::nullopt;
copy_row(1 + component_idx, 1 + source_idx);
}
for (size_t tau_idx = 0; tau_idx < out.taus.size(); ++tau_idx) {
for (size_t component_idx = 0; component_idx < target_component_count; ++component_idx) {
const size_t source_idx = match.calibration_index_by_component[component_idx];
if (source_idx >= source_component_count)
return std::nullopt;
const size_t dst_row = 1 + target_component_count + tau_idx * target_component_count + component_idx;
const size_t src_row = 1 + source_component_count + tau_idx * source_component_count + source_idx;
copy_row(dst_row, src_row);
}
}
return out.valid() ? std::optional<ColorSolverCalibratedStackModel>(out) : std::nullopt;
}
return std::nullopt;
}
} // namespace
std::optional<TextureMappingColorCalibration> texture_mapping_parse_top_surface_color_calibration(
const std::string &json_text,
std::string *error)
{
if (error != nullptr)
error->clear();
if (json_text.empty())
return std::nullopt;
nlohmann::json root = nlohmann::json::parse(json_text, nullptr, false);
if (root.is_discarded() || !root.is_object()) {
if (error != nullptr)
*error = "The calibration file is not valid JSON.";
return std::nullopt;
}
TextureMappingColorCalibration calibration;
calibration.display_name = json_string_or_empty(root, { "display_name", "name", "profile_id" });
calibration.filaments = calibration_filaments_from_json(root);
const nlohmann::json models = root.contains("weights") && root["weights"].is_object() ?
root["weights"] :
root.value("models", nlohmann::json::object());
if (const nlohmann::json *raw = calibration_model_json(models, "current_linear_affine"))
calibration.current_linear_affine = calibration_current_linear_affine_model(*raw);
if (const nlohmann::json *raw = calibration_model_json(models, "td_alpha_effective"))
calibration.td_alpha_effective = calibration_alpha_model(*raw);
if (const nlohmann::json *raw = calibration_model_json(models, "free_alpha_effective"))
calibration.free_alpha_effective = calibration_alpha_model(*raw);
if (const nlohmann::json *raw = calibration_model_json(models, "depth_kernel_linear"))
calibration.depth_kernel_linear = calibration_depth_kernel_model(*raw, calibration.filaments.size());
if (!calibration.current_linear_affine.valid() &&
!calibration.td_alpha_effective.valid() &&
!calibration.free_alpha_effective.valid() &&
!calibration.depth_kernel_linear.valid()) {
if (error != nullptr)
*error = "The calibration file does not contain any supported color prediction weights.";
return std::nullopt;
}
return calibration;
}
std::vector<int> texture_mapping_top_surface_color_calibration_supported_modes(const std::string &json_text)
{
std::vector<int> out;
std::optional<TextureMappingColorCalibration> calibration =
texture_mapping_parse_top_surface_color_calibration(json_text);
if (!calibration)
return out;
const std::array<int, 4> modes = {
int(TextureMappingZone::ContoningColorPredictionCalibratedCurrentLinearAffine),
int(TextureMappingZone::ContoningColorPredictionCalibratedTdAlphaEffective),
int(TextureMappingZone::ContoningColorPredictionCalibratedFreeAlphaEffective),
int(TextureMappingZone::ContoningColorPredictionCalibratedDepthKernelLinear)
};
for (int mode : modes)
if (calibration->has_mode(mode))
out.emplace_back(mode);
return out;
}
int texture_mapping_top_surface_color_calibration_first_supported_mode(const std::string &json_text)
{
std::optional<TextureMappingColorCalibration> calibration =
texture_mapping_parse_top_surface_color_calibration(json_text);
if (!calibration)
return TextureMappingZone::DefaultTopSurfaceContoningColorPredictionMode;
const std::array<std::pair<int, const char *>, 4> modes = {
std::pair<int, const char *>(int(TextureMappingZone::ContoningColorPredictionCalibratedCurrentLinearAffine), "current_linear_affine"),
std::pair<int, const char *>(int(TextureMappingZone::ContoningColorPredictionCalibratedTdAlphaEffective), "td_alpha_effective"),
std::pair<int, const char *>(int(TextureMappingZone::ContoningColorPredictionCalibratedFreeAlphaEffective), "free_alpha_effective"),
std::pair<int, const char *>(int(TextureMappingZone::ContoningColorPredictionCalibratedDepthKernelLinear), "depth_kernel_linear")
};
size_t best_pos = std::numeric_limits<size_t>::max();
int best_mode = TextureMappingZone::DefaultTopSurfaceContoningColorPredictionMode;
for (const auto &item : modes) {
if (!calibration->has_mode(item.first))
continue;
const size_t pos = json_text.find(std::string("\"") + item.second);
const size_t effective_pos = pos == std::string::npos ? std::numeric_limits<size_t>::max() - size_t(item.first) : pos;
if (effective_pos < best_pos) {
best_pos = effective_pos;
best_mode = item.first;
}
}
return best_mode;
}
std::string texture_mapping_top_surface_color_calibration_display_name(const std::string &json_text)
{
std::optional<TextureMappingColorCalibration> calibration =
texture_mapping_parse_top_surface_color_calibration(json_text);
return calibration ? calibration->display_name : std::string();
}
std::string texture_mapping_top_surface_color_calibration_warning(const TextureMappingZone &zone,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm)
{
if (zone.top_surface_color_calibration_json.empty())
return std::string();
std::string error;
std::optional<TextureMappingColorCalibration> calibration =
texture_mapping_parse_top_surface_color_calibration(zone.top_surface_color_calibration_json, &error);
if (!calibration)
return error.empty() ? "Top-surface color calibration could not be loaded." : error;
const TextureMappingCalibrationMatch match =
match_calibration_filaments(*calibration, component_colors, component_tds_mm);
if (!match.warning.empty())
return match.warning;
const int mode = TextureMappingZone::effective_top_surface_contoning_color_prediction_mode(zone.top_surface_contoning_color_prediction_mode);
if (TextureMappingZone::top_surface_contoning_color_prediction_mode_is_calibrated(mode) &&
!calibration->has_mode(mode))
return "The selected calibrated color prediction mode is not present in the loaded calibration file.";
return std::string();
}
std::optional<ColorSolverCalibratedStackModel> texture_mapping_top_surface_color_calibrated_model(
const TextureMappingZone &zone,
int mode,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm,
std::string *warning)
{
if (warning != nullptr)
warning->clear();
std::string error;
std::optional<TextureMappingColorCalibration> calibration =
texture_mapping_parse_top_surface_color_calibration(zone.top_surface_color_calibration_json, &error);
if (!calibration) {
if (warning != nullptr)
*warning = error;
return std::nullopt;
}
const ColorSolverCalibratedStackModel &source = calibration_model_for_mode(*calibration, mode);
if (!source.valid())
return std::nullopt;
const TextureMappingCalibrationMatch match =
match_calibration_filaments(*calibration, component_colors, component_tds_mm);
if (warning != nullptr)
*warning = match.warning;
return remap_calibration_model(source, mode, match, component_colors.size());
}
bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const
{
constexpr float eps = 1e-6f;
@@ -1238,6 +1808,8 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const
top_surface_contoning_td_effective_alpha_correction_enabled == rhs.top_surface_contoning_td_effective_alpha_correction_enabled &&
top_surface_contoning_variable_layer_height_compensation_enabled == rhs.top_surface_contoning_variable_layer_height_compensation_enabled &&
effective_top_surface_contoning_beam_search_stack_expansion_enabled() == rhs.effective_top_surface_contoning_beam_search_stack_expansion_enabled() &&
top_surface_color_calibration_name == rhs.top_surface_color_calibration_name &&
top_surface_color_calibration_json == rhs.top_surface_color_calibration_json &&
compact_offset_mode == rhs.compact_offset_mode &&
use_legacy_fixed_color_mode == rhs.use_legacy_fixed_color_mode &&
high_speed_image_texture_sampling == rhs.high_speed_image_texture_sampling &&
@@ -1660,6 +2232,12 @@ std::string TextureMappingManager::serialize_entries()
zone.top_surface_contoning_variable_layer_height_compensation_enabled;
texture["top_surface_contoning_beam_search_stack_expansion_enabled"] =
zone.effective_top_surface_contoning_beam_search_stack_expansion_enabled();
if (!zone.top_surface_color_calibration_json.empty()) {
texture["top_surface_color_calibration_name"] = zone.top_surface_color_calibration_name;
nlohmann::json calibration = nlohmann::json::parse(zone.top_surface_color_calibration_json, nullptr, false);
texture["top_surface_color_calibration"] =
calibration.is_discarded() ? nlohmann::json(zone.top_surface_color_calibration_json) : std::move(calibration);
}
texture["compact_offset_mode"] = zone.compact_offset_mode;
texture["use_legacy_fixed_color_mode"] = zone.use_legacy_fixed_color_mode;
texture["high_speed_image_texture_sampling"] = true;
@@ -1985,9 +2563,9 @@ void TextureMappingManager::load_entries(const std::string &serialized,
top_surface_contoning_color_prediction_mode_from_name(color_prediction_mode_it->get<std::string>()) :
clamp_int(color_prediction_mode_it != texture.end() && color_prediction_mode_it->is_number_integer() ?
color_prediction_mode_it->get<int>() :
TextureMappingZone::DefaultTopSurfaceContoningColorPredictionMode,
TextureMappingZone::DefaultTopSurfaceContoningColorPredictionMode,
int(TextureMappingZone::ContoningColorPredictionDefault),
int(TextureMappingZone::ContoningColorPredictionBasicReflectance));
int(TextureMappingZone::ContoningColorPredictionCalibratedDepthKernelLinear));
zone.top_surface_contoning_beer_lambert_rgb_correction_enabled =
TextureMappingZone::DefaultTopSurfaceContoningBeerLambertRgbCorrectionEnabled;
zone.top_surface_contoning_td_effective_alpha_correction_enabled =
@@ -1998,6 +2576,30 @@ void TextureMappingManager::load_entries(const std::string &serialized,
zone.top_surface_contoning_beam_search_stack_expansion_enabled =
texture.value("top_surface_contoning_beam_search_stack_expansion_enabled",
TextureMappingZone::DefaultTopSurfaceContoningBeamSearchStackExpansionEnabled);
zone.top_surface_color_calibration_name =
texture.value("top_surface_color_calibration_name", std::string());
zone.top_surface_color_calibration_json.clear();
auto color_calibration_it = texture.find("top_surface_color_calibration");
if (color_calibration_it != texture.end()) {
zone.top_surface_color_calibration_json =
color_calibration_it->is_string() ?
color_calibration_it->get<std::string>() :
color_calibration_it->dump();
if (zone.top_surface_color_calibration_name.empty())
zone.top_surface_color_calibration_name =
texture_mapping_top_surface_color_calibration_display_name(zone.top_surface_color_calibration_json);
}
if (TextureMappingZone::top_surface_contoning_color_prediction_mode_is_calibrated(zone.top_surface_contoning_color_prediction_mode) &&
!texture_mapping_parse_top_surface_color_calibration(zone.top_surface_color_calibration_json)) {
zone.top_surface_contoning_color_prediction_mode =
TextureMappingZone::DefaultTopSurfaceContoningColorPredictionMode;
} else if (TextureMappingZone::top_surface_contoning_color_prediction_mode_is_calibrated(zone.top_surface_contoning_color_prediction_mode)) {
const std::vector<int> modes =
texture_mapping_top_surface_color_calibration_supported_modes(zone.top_surface_color_calibration_json);
if (std::find(modes.begin(), modes.end(), zone.top_surface_contoning_color_prediction_mode) == modes.end())
zone.top_surface_contoning_color_prediction_mode =
texture_mapping_top_surface_color_calibration_first_supported_mode(zone.top_surface_color_calibration_json);
}
zone.apply_top_surface_contoning_experimental_defaults();
zone.compact_offset_mode = texture.value("compact_offset_mode", TextureMappingZone::DefaultCompactOffsetMode);
zone.use_legacy_fixed_color_mode =

View File

@@ -8,10 +8,13 @@
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "ColorSolver.hpp"
namespace Slic3r {
struct TextureMappingZone
@@ -93,7 +96,11 @@ struct TextureMappingZone
ContoningColorPredictionDefault = 0,
ContoningColorPredictionTdEffectiveAlpha = 1,
ContoningColorPredictionBeerLambertRgb = 2,
ContoningColorPredictionBasicReflectance = 3
ContoningColorPredictionBasicReflectance = 3,
ContoningColorPredictionCalibratedCurrentLinearAffine = 4,
ContoningColorPredictionCalibratedTdAlphaEffective = 5,
ContoningColorPredictionCalibratedFreeAlphaEffective = 6,
ContoningColorPredictionCalibratedDepthKernelLinear = 7
};
enum TopSurfaceContoningPolygonizationMode : uint8_t {
@@ -379,7 +386,14 @@ struct TextureMappingZone
return SlicerDefaultTopSurfaceContoningColorPredictionMode;
return std::clamp(mode,
int(ContoningColorPredictionTdEffectiveAlpha),
int(ContoningColorPredictionBasicReflectance));
int(ContoningColorPredictionCalibratedDepthKernelLinear));
}
static bool top_surface_contoning_color_prediction_mode_is_calibrated(int mode)
{
const int effective_mode = effective_top_surface_contoning_color_prediction_mode(mode);
return effective_mode >= int(ContoningColorPredictionCalibratedCurrentLinearAffine) &&
effective_mode <= int(ContoningColorPredictionCalibratedDepthKernelLinear);
}
static bool effective_top_surface_contoning_layer_phase_enabled(bool value, bool surface_anchored_stacks)
@@ -488,6 +502,8 @@ struct TextureMappingZone
int top_surface_contoning_color_prediction_mode = DefaultTopSurfaceContoningColorPredictionMode;
bool top_surface_contoning_variable_layer_height_compensation_enabled = DefaultTopSurfaceContoningVariableLayerHeightCompensationEnabled;
bool top_surface_contoning_beam_search_stack_expansion_enabled = DefaultTopSurfaceContoningBeamSearchStackExpansionEnabled;
std::string top_surface_color_calibration_name;
std::string top_surface_color_calibration_json;
bool compact_offset_mode = DefaultCompactOffsetMode;
bool use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode;
bool high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling;
@@ -672,7 +688,8 @@ struct TextureMappingZone
mode == int(ContoningColorPredictionBeerLambertRgb);
top_surface_contoning_beer_lambert_rgb_correction_enabled = beer_lambert_rgb;
top_surface_contoning_td_effective_alpha_correction_enabled =
mode == int(ContoningColorPredictionTdEffectiveAlpha);
mode == int(ContoningColorPredictionTdEffectiveAlpha) ||
mode == int(ContoningColorPredictionCalibratedCurrentLinearAffine);
}
void apply_top_surface_contoning_experimental_defaults()
@@ -782,6 +799,8 @@ struct TextureMappingZone
top_surface_contoning_color_prediction_mode = DefaultTopSurfaceContoningColorPredictionMode;
top_surface_contoning_variable_layer_height_compensation_enabled = DefaultTopSurfaceContoningVariableLayerHeightCompensationEnabled;
top_surface_contoning_beam_search_stack_expansion_enabled = DefaultTopSurfaceContoningBeamSearchStackExpansionEnabled;
top_surface_color_calibration_name.clear();
top_surface_color_calibration_json.clear();
compact_offset_mode = DefaultCompactOffsetMode;
use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode;
high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling;
@@ -834,6 +853,39 @@ struct TextureMappingZone
bool operator!=(const TextureMappingZone &rhs) const { return !(*this == rhs); }
};
struct TextureMappingColorCalibrationFilament {
std::string name;
std::array<float, 3> color { { 0.f, 0.f, 0.f } };
float td_mm { 0.f };
};
struct TextureMappingColorCalibration {
std::string display_name;
std::vector<TextureMappingColorCalibrationFilament> filaments;
ColorSolverCalibratedStackModel current_linear_affine;
ColorSolverCalibratedStackModel td_alpha_effective;
ColorSolverCalibratedStackModel free_alpha_effective;
ColorSolverCalibratedStackModel depth_kernel_linear;
bool has_mode(int mode) const;
};
std::optional<TextureMappingColorCalibration> texture_mapping_parse_top_surface_color_calibration(
const std::string &json_text,
std::string *error = nullptr);
std::vector<int> texture_mapping_top_surface_color_calibration_supported_modes(const std::string &json_text);
int texture_mapping_top_surface_color_calibration_first_supported_mode(const std::string &json_text);
std::string texture_mapping_top_surface_color_calibration_display_name(const std::string &json_text);
std::string texture_mapping_top_surface_color_calibration_warning(const TextureMappingZone &zone,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm);
std::optional<ColorSolverCalibratedStackModel> texture_mapping_top_surface_color_calibrated_model(
const TextureMappingZone &zone,
int mode,
const std::vector<std::array<float, 3>> &component_colors,
const std::vector<float> &component_tds_mm,
std::string *warning = nullptr);
struct TextureMappingPrimeTowerImage
{
std::vector<uint8_t> rgba;

View File

@@ -407,12 +407,17 @@ TextureMappingContoningSolver::TextureMappingContoningSolver(const TextureMappin
{
m_mix_model = color_solver_mix_model_from_index(zone.generic_solver_mix_model);
m_td_adjustment_enabled = zone.top_surface_contoning_td_adjustment_enabled;
const int effective_color_prediction_mode =
TextureMappingZone::effective_top_surface_contoning_color_prediction_mode(
zone.top_surface_contoning_color_prediction_mode);
m_td_effective_alpha_correction_enabled =
m_td_adjustment_enabled && zone.top_surface_contoning_td_effective_alpha_correction_enabled;
m_td_adjustment_enabled &&
(effective_color_prediction_mode == int(TextureMappingZone::ContoningColorPredictionTdEffectiveAlpha) ||
effective_color_prediction_mode == int(TextureMappingZone::ContoningColorPredictionCalibratedCurrentLinearAffine));
m_beer_lambert_rgb_correction_enabled =
m_td_adjustment_enabled &&
!m_td_effective_alpha_correction_enabled &&
zone.top_surface_contoning_beer_lambert_rgb_correction_enabled;
effective_color_prediction_mode == int(TextureMappingZone::ContoningColorPredictionBeerLambertRgb);
m_variable_layer_height_compensation_enabled =
m_td_adjustment_enabled && zone.top_surface_contoning_variable_layer_height_compensation_enabled;
m_beam_search_stack_expansion_enabled = zone.effective_top_surface_contoning_beam_search_stack_expansion_enabled();
@@ -439,6 +444,17 @@ TextureMappingContoningSolver::TextureMappingContoningSolver(const TextureMappin
m_background_rgb = mix_color_solver_components(m_component_colors, background_weights, m_mix_model);
m_effective_transmission_distances_mm =
effective_transmission_distances_mm(zone, config, m_component_ids, m_td_adjustment_enabled);
if (TextureMappingZone::top_surface_contoning_color_prediction_mode_is_calibrated(effective_color_prediction_mode)) {
std::string warning;
std::optional<ColorSolverCalibratedStackModel> calibrated_model =
texture_mapping_top_surface_color_calibrated_model(zone,
effective_color_prediction_mode,
m_component_colors,
m_effective_transmission_distances_mm,
&warning);
if (calibrated_model && calibrated_model->valid())
m_calibrated_stack_model = *calibrated_model;
}
m_component_layer_opacity.reserve(m_effective_transmission_distances_mm.size());
for (float td_mm : m_effective_transmission_distances_mm)
m_component_layer_opacity.emplace_back(layer_opacity_from_td(td_mm > 0.f ? td_mm : 3.f, m_layer_height_mm));
@@ -556,7 +572,8 @@ std::optional<std::array<float, 3>> TextureMappingContoningSolver::stack_rgb(
m_beer_lambert_rgb_correction_enabled,
m_td_effective_alpha_correction_enabled,
m_component_roles,
depth_layer_opacities);
depth_layer_opacities,
m_calibrated_stack_model.valid() ? &m_calibrated_stack_model : nullptr);
}
std::vector<float> TextureMappingContoningSolver::layer_opacities_by_depth(
@@ -690,7 +707,8 @@ TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::arr
m_td_effective_alpha_correction_enabled,
m_component_roles,
m_beam_search_stack_expansion_enabled,
depth_layer_opacities);
depth_layer_opacities,
m_calibrated_stack_model.valid() ? &m_calibrated_stack_model : nullptr);
}
const ColorSolverOrderedStackResult solved =
solve_color_solver_ordered_stack_result_for_target(*ordered_candidates, target_rgb, ColorSolverMode::OklabSoftCap4Dark4);

View File

@@ -77,6 +77,7 @@ private:
bool m_td_effective_alpha_correction_enabled { false };
bool m_variable_layer_height_compensation_enabled { false };
bool m_beam_search_stack_expansion_enabled { false };
ColorSolverCalibratedStackModel m_calibrated_stack_model;
std::vector<ColorSolverStackComponentRole> m_component_roles;
mutable std::map<int, std::vector<Candidate>> m_candidates_by_depth;
mutable std::shared_ptr<std::mutex> m_candidate_mutex { std::make_shared<std::mutex>() };

View File

@@ -106,6 +106,8 @@ struct TexturePreviewSimulationSettings
float contoning_flat_surface_surface_scatter = k_contoning_preview_surface_scatter;
std::array<float, 3> contoning_flat_surface_background_rgb { { 0.f, 0.f, 0.f } };
std::vector<float> contoning_flat_surface_layer_opacities;
std::vector<float> contoning_flat_surface_transmission_distances_mm;
ColorSolverCalibratedStackModel contoning_flat_surface_calibrated_stack_model;
float minimum_visibility_offset_factor = 0.f;
std::vector<unsigned int> component_ids;
std::vector<std::array<float, 3>> component_colors;
@@ -588,7 +590,7 @@ float texture_preview_layer_opacity_from_td(float td_mm, float layer_height_mm)
return std::clamp(1.f - std::pow(0.05f, 2.f * safe_layer_height / safe_td), 1e-4f, 0.9999f);
}
std::vector<float> contoning_flat_surface_preview_layer_opacities(
std::vector<float> contoning_flat_surface_preview_transmission_distances(
const TextureMappingZone &zone,
const TexturePreviewSimulationSettings &settings)
{
@@ -608,11 +610,21 @@ std::vector<float> contoning_flat_surface_preview_layer_opacities(
td_mm = k_contoning_preview_inferred_black_td_mm;
if (td_mm <= 0.f)
td_mm = texture_preview_estimated_transmission_distance_mm(settings.component_colors[idx]);
out.emplace_back(texture_preview_layer_opacity_from_td(td_mm, settings.contoning_flat_surface_layer_height_mm));
out.emplace_back(td_mm);
}
return out;
}
std::vector<float> contoning_flat_surface_preview_layer_opacities(
const TexturePreviewSimulationSettings &settings)
{
std::vector<float> out;
out.reserve(settings.contoning_flat_surface_transmission_distances_mm.size());
for (const float td_mm : settings.contoning_flat_surface_transmission_distances_mm)
out.emplace_back(texture_preview_layer_opacity_from_td(td_mm, settings.contoning_flat_surface_layer_height_mm));
return out;
}
std::array<float, 2> raw_offset_visibility_factors_for_texture_preview(const TextureMappingZone &zone)
{
const float base_outer_width_mm =
@@ -2610,7 +2622,11 @@ std::array<float, 3> contoning_flat_surface_rgb_for_texture_preview(
settings.contoning_flat_surface_surface_scatter,
settings.contoning_flat_surface_beer_lambert_rgb_correction,
settings.contoning_flat_surface_td_effective_alpha_correction,
settings.component_roles);
settings.component_roles,
std::vector<float>{},
settings.contoning_flat_surface_calibrated_stack_model.valid() ?
&settings.contoning_flat_surface_calibrated_stack_model :
nullptr);
}
}
if (!candidates.empty()) {
@@ -2763,13 +2779,16 @@ std::optional<TexturePreviewSimulationSettings> texture_preview_simulation_setti
settings.contoning_flat_surface_pattern_filaments = std::max(1, std::min(stack_layers, pattern_filaments));
settings.contoning_flat_surface_td_adjustment =
zone->top_surface_contoning_active() && zone->top_surface_contoning_td_adjustment_enabled;
const int effective_color_prediction_mode =
TextureMappingZone::effective_top_surface_contoning_color_prediction_mode(
zone->top_surface_contoning_color_prediction_mode);
settings.contoning_flat_surface_beer_lambert_rgb_correction =
settings.contoning_flat_surface_td_adjustment &&
!zone->top_surface_contoning_td_effective_alpha_correction_enabled &&
zone->top_surface_contoning_beer_lambert_rgb_correction_enabled;
effective_color_prediction_mode == int(TextureMappingZone::ContoningColorPredictionBeerLambertRgb);
settings.contoning_flat_surface_td_effective_alpha_correction =
settings.contoning_flat_surface_td_adjustment &&
zone->top_surface_contoning_td_effective_alpha_correction_enabled;
(effective_color_prediction_mode == int(TextureMappingZone::ContoningColorPredictionTdEffectiveAlpha) ||
effective_color_prediction_mode == int(TextureMappingZone::ContoningColorPredictionCalibratedCurrentLinearAffine));
settings.contoning_flat_surface_beam_search_stack_expansion =
zone->effective_top_surface_contoning_beam_search_stack_expansion_enabled();
settings.contoning_flat_surface_surface_scatter =
@@ -2826,12 +2845,27 @@ std::optional<TexturePreviewSimulationSettings> texture_preview_simulation_setti
mix_color_solver_components(settings.component_colors,
background_weights,
color_solver_mix_model_from_index(settings.generic_solver_mix_model));
settings.contoning_flat_surface_transmission_distances_mm =
contoning_flat_surface_preview_transmission_distances(*zone, settings);
settings.contoning_flat_surface_layer_opacities =
contoning_flat_surface_preview_layer_opacities(*zone, settings);
contoning_flat_surface_preview_layer_opacities(settings);
if (settings.contoning_flat_surface_layer_opacities.size() != settings.component_colors.size()) {
settings.contoning_flat_surface_td_adjustment = false;
settings.contoning_flat_surface_beer_lambert_rgb_correction = false;
settings.contoning_flat_surface_td_effective_alpha_correction = false;
} else {
const int effective_color_prediction_mode =
TextureMappingZone::effective_top_surface_contoning_color_prediction_mode(
zone->top_surface_contoning_color_prediction_mode);
if (TextureMappingZone::top_surface_contoning_color_prediction_mode_is_calibrated(effective_color_prediction_mode)) {
std::optional<ColorSolverCalibratedStackModel> calibrated_model =
texture_mapping_top_surface_color_calibrated_model(*zone,
effective_color_prediction_mode,
settings.component_colors,
settings.contoning_flat_surface_transmission_distances_mm);
if (calibrated_model && calibrated_model->valid())
settings.contoning_flat_surface_calibrated_stack_model = *calibrated_model;
}
}
}
@@ -2888,6 +2922,8 @@ size_t texture_preview_simulation_signature(const ModelVolume &model_volume,
if (settings.contoning_flat_surface_td_effective_alpha_correction)
for (const ColorSolverStackComponentRole role : settings.component_roles)
mix(std::hash<int>{}(int(role)));
if (settings.contoning_flat_surface_calibrated_stack_model.valid())
mix(std::hash<std::string>{}(settings.contoning_flat_surface_calibrated_stack_model.cache_key()));
}
mix(std::hash<int>{}(int(std::lround(settings.contrast_pct * 100.f))));
mix(std::hash<int>{}(int(std::lround(settings.tone_gamma * 1000.f))));
@@ -2980,7 +3016,11 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig
settings.contoning_flat_surface_beer_lambert_rgb_correction,
settings.contoning_flat_surface_td_effective_alpha_correction,
settings.component_roles,
settings.contoning_flat_surface_beam_search_stack_expansion) :
settings.contoning_flat_surface_beam_search_stack_expansion,
std::vector<float>{},
settings.contoning_flat_surface_calibrated_stack_model.valid() ?
&settings.contoning_flat_surface_calibrated_stack_model :
nullptr) :
ColorSolverOrderedStackCandidateSet{};
const std::vector<TexturePreviewMixCandidate> contoning_flat_surface_candidates =
use_contoning_flat_surface_quantization && contoning_flat_surface_ordered_candidates.empty() ?

View File

@@ -17,6 +17,7 @@
#include <vector>
#include <string>
#include <regex>
#include <fstream>
#include <functional>
#include <future>
#include <memory>
@@ -29,6 +30,7 @@
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
@@ -1038,6 +1040,50 @@ static wxColour parse_texture_mapping_color(const std::string &hex)
return wxColour(rgba[0], rgba[1], rgba[2], rgba[3]);
}
static std::array<float, 3> texture_mapping_color_array(const std::string &hex)
{
const wxColour color = parse_texture_mapping_color(hex);
return { { float(color.Red()) / 255.f, float(color.Green()) / 255.f, float(color.Blue()) / 255.f } };
}
static std::vector<std::array<float, 3>> texture_mapping_component_color_arrays(const std::vector<unsigned int> &ids,
const std::vector<std::string> &physical_colors)
{
std::vector<std::array<float, 3>> out;
out.reserve(ids.size());
for (unsigned int id : ids) {
if (id > 0 && size_t(id - 1) < physical_colors.size())
out.emplace_back(texture_mapping_color_array(physical_colors[size_t(id - 1)]));
else
out.emplace_back(std::array<float, 3>{ { 0.5f, 0.5f, 0.5f } });
}
return out;
}
static std::vector<float> texture_mapping_component_tds(const TextureMappingZone &zone,
const std::vector<unsigned int> &ids)
{
std::vector<float> out;
out.reserve(ids.size());
for (unsigned int id : ids) {
const size_t idx = id > 0 ? size_t(id - 1) : size_t(-1);
out.emplace_back(idx < zone.filament_transmission_distances_mm.size() ?
zone.filament_transmission_distances_mm[idx] :
0.f);
}
return out;
}
static wxString texture_mapping_zone_color_calibration_warning(const TextureMappingZone &zone,
const std::vector<unsigned int> &ids,
const std::vector<std::string> &physical_colors)
{
return from_u8(texture_mapping_top_surface_color_calibration_warning(
zone,
texture_mapping_component_color_arrays(ids, physical_colors),
texture_mapping_component_tds(zone, ids)));
}
static std::string encode_texture_mapping_component_ids(const std::vector<unsigned int> &ids)
{
std::string out;
@@ -2101,6 +2147,14 @@ static wxString texture_mapping_contoning_color_prediction_mode_label(int mode)
return _L("RGB Beer-Lambert - not recommended");
case int(TextureMappingZone::ContoningColorPredictionBasicReflectance):
return _L("Basic Reflectance");
case int(TextureMappingZone::ContoningColorPredictionCalibratedCurrentLinearAffine):
return _L("Calibrated: current linear affine");
case int(TextureMappingZone::ContoningColorPredictionCalibratedTdAlphaEffective):
return _L("Calibrated: TD alpha effective");
case int(TextureMappingZone::ContoningColorPredictionCalibratedFreeAlphaEffective):
return _L("Calibrated: free alpha effective");
case int(TextureMappingZone::ContoningColorPredictionCalibratedDepthKernelLinear):
return _L("Calibrated: depth kernel linear");
default:
return _L("TD Effective Alpha");
}
@@ -2116,34 +2170,6 @@ static wxString texture_mapping_contoning_color_prediction_default_label()
return label;
}
static int texture_mapping_contoning_color_prediction_mode_choice_selection(int mode)
{
if (mode == int(TextureMappingZone::ContoningColorPredictionDefault))
return 0;
switch (TextureMappingZone::effective_top_surface_contoning_color_prediction_mode(mode)) {
case int(TextureMappingZone::ContoningColorPredictionBeerLambertRgb):
return 2;
case int(TextureMappingZone::ContoningColorPredictionBasicReflectance):
return 3;
default:
return 1;
}
}
static int texture_mapping_contoning_color_prediction_mode_from_choice_selection(int selection)
{
switch (selection) {
case 1:
return int(TextureMappingZone::ContoningColorPredictionTdEffectiveAlpha);
case 2:
return int(TextureMappingZone::ContoningColorPredictionBeerLambertRgb);
case 3:
return int(TextureMappingZone::ContoningColorPredictionBasicReflectance);
default:
return TextureMappingZone::DefaultTopSurfaceContoningColorPredictionMode;
}
}
static int texture_mapping_contoning_polygonization_mode_choice_selection(int mode)
{
return TextureMappingZone::effective_top_surface_contoning_polygonization_mode(mode) ==
@@ -2164,6 +2190,7 @@ public:
int texture_mapping_mode,
int filament_color_mode,
const std::vector<unsigned int> &component_ids,
const std::vector<std::string> &component_color_hexes,
const std::vector<float> &component_strengths_pct,
const std::vector<float> &component_minimum_offsets_pct,
const std::vector<float> &component_transmission_distances_mm,
@@ -2227,6 +2254,8 @@ public:
bool top_surface_contoning_beer_lambert_rgb_correction_enabled,
bool top_surface_contoning_td_effective_alpha_correction_enabled,
bool top_surface_contoning_variable_layer_height_compensation_enabled,
const std::string &top_surface_color_calibration_name,
const std::string &top_surface_color_calibration_json,
const TextureMappingZoneShellUsageSummary &top_surface_contoning_shell_usage,
const TextureMappingManager &texture_mapping_zones,
const TextureMappingGlobalSettings &global_settings,
@@ -2238,6 +2267,9 @@ public:
, m_global_settings(global_settings)
, m_prime_tower_image(prime_tower_image)
, m_prime_tower_image_back(prime_tower_image_back)
, m_component_color_hexes(component_color_hexes)
, m_top_surface_color_calibration_name(top_surface_color_calibration_name)
, m_top_surface_color_calibration_json(top_surface_color_calibration_json)
{
(void) reduce_outer_surface_texture;
(void) top_surface_contoning_min_feature_mm;
@@ -2946,20 +2978,40 @@ public:
0,
wxEXPAND | wxTOP | wxBOTTOM,
gap / 2);
auto *color_calibration_row = new wxBoxSizer(wxHORIZONTAL);
color_calibration_row->Add(new wxStaticText(m_top_surface_contoning_checkboxes_panel, wxID_ANY, _L("Top-surface color calibration")),
0,
wxALIGN_CENTER_VERTICAL | wxRIGHT,
gap);
m_top_surface_color_calibration_label =
new wxStaticText(m_top_surface_contoning_checkboxes_panel, wxID_ANY, wxEmptyString);
color_calibration_row->Add(m_top_surface_color_calibration_label, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap);
auto *load_top_surface_color_calibration_button =
new wxButton(m_top_surface_contoning_checkboxes_panel, wxID_ANY, _L("Load..."));
auto *clear_top_surface_color_calibration_button =
new wxButton(m_top_surface_contoning_checkboxes_panel, wxID_ANY, _L("Clear"));
color_calibration_row->Add(load_top_surface_color_calibration_button, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, std::max(FromDIP(2), gap / 2));
color_calibration_row->Add(clear_top_surface_color_calibration_button, 0, wxALIGN_CENTER_VERTICAL);
contoning_checkboxes_root->Add(color_calibration_row, 0, wxEXPAND | wxTOP | wxBOTTOM, gap / 2);
m_top_surface_color_calibration_warning_text =
new wxStaticText(m_top_surface_contoning_checkboxes_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE);
m_top_surface_color_calibration_warning_text->SetForegroundColour(wxColour(180, 86, 0));
contoning_checkboxes_root->Add(m_top_surface_color_calibration_warning_text, 0, wxEXPAND | wxBOTTOM, gap / 2);
load_top_surface_color_calibration_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
load_top_surface_color_calibration();
});
clear_top_surface_color_calibration_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
clear_top_surface_color_calibration();
});
auto *contoning_td_correction_row = new wxBoxSizer(wxHORIZONTAL);
contoning_td_correction_row->Add(new wxStaticText(m_top_surface_contoning_checkboxes_panel, wxID_ANY, _L("Color prediction mode")),
0,
wxALIGN_CENTER_VERTICAL | wxRIGHT,
gap);
wxArrayString contoning_td_correction_choices;
contoning_td_correction_choices.Add(texture_mapping_contoning_color_prediction_default_label());
contoning_td_correction_choices.Add(texture_mapping_contoning_color_prediction_mode_label(int(TextureMappingZone::ContoningColorPredictionTdEffectiveAlpha)));
contoning_td_correction_choices.Add(texture_mapping_contoning_color_prediction_mode_label(int(TextureMappingZone::ContoningColorPredictionBeerLambertRgb)));
contoning_td_correction_choices.Add(texture_mapping_contoning_color_prediction_mode_label(int(TextureMappingZone::ContoningColorPredictionBasicReflectance)));
m_top_surface_contoning_td_correction_choice =
new wxChoice(m_top_surface_contoning_checkboxes_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, contoning_td_correction_choices);
m_top_surface_contoning_td_correction_choice->SetSelection(
texture_mapping_contoning_color_prediction_mode_choice_selection(top_surface_contoning_color_prediction_mode));
new wxChoice(m_top_surface_contoning_checkboxes_panel, wxID_ANY);
rebuild_top_surface_contoning_color_prediction_choices(top_surface_contoning_color_prediction_mode);
update_top_surface_color_calibration_label();
contoning_td_correction_row->Add(m_top_surface_contoning_td_correction_choice, 1, wxALIGN_CENTER_VERTICAL);
contoning_checkboxes_root->Add(contoning_td_correction_row, 0, wxEXPAND | wxTOP | wxBOTTOM, gap / 2);
m_top_surface_contoning_variable_layer_height_compensation_checkbox =
@@ -3202,6 +3254,7 @@ public:
}
if (m_top_surface_contoning_td_correction_choice != nullptr) {
m_top_surface_contoning_td_correction_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent &) {
update_top_surface_color_calibration_label();
update_top_surface_image_options_visibility(false);
queue_top_surface_contoning_message_update(true);
});
@@ -3240,9 +3293,11 @@ public:
if (spin == nullptr)
continue;
spin->Bind(wxEVT_SPINCTRLDOUBLE, [this](wxSpinDoubleEvent &) {
update_top_surface_color_calibration_label();
queue_top_surface_contoning_message_update(true);
});
spin->Bind(wxEVT_TEXT, [this](wxCommandEvent &) {
update_top_surface_color_calibration_label();
queue_top_surface_contoning_message_update(true);
});
}
@@ -3743,16 +3798,27 @@ public:
}
int top_surface_contoning_color_prediction_mode() const
{
return m_top_surface_contoning_td_correction_choice == nullptr ?
TextureMappingZone::DefaultTopSurfaceContoningColorPredictionMode :
texture_mapping_contoning_color_prediction_mode_from_choice_selection(
m_top_surface_contoning_td_correction_choice->GetSelection());
if (m_top_surface_contoning_td_correction_choice == nullptr)
return TextureMappingZone::DefaultTopSurfaceContoningColorPredictionMode;
const int selection = m_top_surface_contoning_td_correction_choice->GetSelection();
return selection >= 0 && size_t(selection) < m_top_surface_contoning_color_prediction_modes.size() ?
m_top_surface_contoning_color_prediction_modes[size_t(selection)] :
TextureMappingZone::DefaultTopSurfaceContoningColorPredictionMode;
}
std::string top_surface_color_calibration_name() const
{
return m_top_surface_color_calibration_name;
}
std::string top_surface_color_calibration_json() const
{
return m_top_surface_color_calibration_json;
}
bool top_surface_contoning_td_effective_alpha_correction_selected() const
{
return TextureMappingZone::effective_top_surface_contoning_color_prediction_mode(
top_surface_contoning_color_prediction_mode()) ==
int(TextureMappingZone::ContoningColorPredictionTdEffectiveAlpha);
const int effective_mode = TextureMappingZone::effective_top_surface_contoning_color_prediction_mode(
top_surface_contoning_color_prediction_mode());
return effective_mode == int(TextureMappingZone::ContoningColorPredictionTdEffectiveAlpha) ||
effective_mode == int(TextureMappingZone::ContoningColorPredictionCalibratedCurrentLinearAffine);
}
bool top_surface_contoning_surface_scatter_enabled() const
{
@@ -4014,6 +4080,135 @@ private:
update_prime_tower_image_label();
}
std::vector<std::array<float, 3>> top_surface_color_calibration_component_colors() const
{
std::vector<std::array<float, 3>> colors;
colors.reserve(m_component_color_hexes.size());
for (const std::string &hex : m_component_color_hexes)
colors.emplace_back(texture_mapping_color_array(hex));
return colors;
}
void rebuild_top_surface_contoning_color_prediction_choices(int selected_mode)
{
if (m_top_surface_contoning_td_correction_choice == nullptr)
return;
m_top_surface_contoning_color_prediction_modes.clear();
auto add_mode = [this](int mode) {
m_top_surface_contoning_color_prediction_modes.emplace_back(mode);
m_top_surface_contoning_td_correction_choice->Append(
mode == int(TextureMappingZone::ContoningColorPredictionDefault) ?
texture_mapping_contoning_color_prediction_default_label() :
texture_mapping_contoning_color_prediction_mode_label(mode));
};
m_top_surface_contoning_td_correction_choice->Clear();
add_mode(int(TextureMappingZone::ContoningColorPredictionDefault));
add_mode(int(TextureMappingZone::ContoningColorPredictionTdEffectiveAlpha));
add_mode(int(TextureMappingZone::ContoningColorPredictionBeerLambertRgb));
add_mode(int(TextureMappingZone::ContoningColorPredictionBasicReflectance));
for (int mode : texture_mapping_top_surface_color_calibration_supported_modes(m_top_surface_color_calibration_json))
add_mode(mode);
auto it = std::find(m_top_surface_contoning_color_prediction_modes.begin(),
m_top_surface_contoning_color_prediction_modes.end(),
selected_mode);
if (it == m_top_surface_contoning_color_prediction_modes.end())
it = std::find(m_top_surface_contoning_color_prediction_modes.begin(),
m_top_surface_contoning_color_prediction_modes.end(),
int(TextureMappingZone::ContoningColorPredictionDefault));
m_top_surface_contoning_td_correction_choice->SetSelection(
it != m_top_surface_contoning_color_prediction_modes.end() ?
int(it - m_top_surface_contoning_color_prediction_modes.begin()) :
0);
}
void update_top_surface_color_calibration_label()
{
if (m_top_surface_color_calibration_label != nullptr) {
wxString label = _L("No calibration loaded");
wxString tooltip;
if (!m_top_surface_color_calibration_json.empty()) {
std::string name = m_top_surface_color_calibration_name;
if (name.empty())
name = texture_mapping_top_surface_color_calibration_display_name(m_top_surface_color_calibration_json);
if (name.empty())
name = "Loaded calibration";
tooltip = from_u8(name);
wxString display = from_u8(name);
if (display.length() > 42)
display = display.Left(19) + "..." + display.Right(20);
label = display;
}
m_top_surface_color_calibration_label->SetLabel(label);
m_top_surface_color_calibration_label->SetToolTip(tooltip);
}
if (m_top_surface_color_calibration_warning_text != nullptr) {
TextureMappingZone tmp;
tmp.top_surface_color_calibration_name = m_top_surface_color_calibration_name;
tmp.top_surface_color_calibration_json = m_top_surface_color_calibration_json;
tmp.top_surface_contoning_color_prediction_mode = top_surface_contoning_color_prediction_mode();
const wxString warning = from_u8(texture_mapping_top_surface_color_calibration_warning(
tmp,
top_surface_color_calibration_component_colors(),
component_transmission_distances_mm()));
m_top_surface_color_calibration_warning_text->SetLabel(warning);
m_top_surface_color_calibration_warning_text->Show(!warning.empty());
m_top_surface_color_calibration_warning_text->Wrap(FromDIP(390));
}
Layout();
}
void load_top_surface_color_calibration()
{
wxFileDialog dlg(this,
_L("Load top-surface color calibration"),
wxEmptyString,
wxEmptyString,
_L("JSON files (*.json)|*.json"),
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dlg.ShowModal() != wxID_OK)
return;
boost::nowide::ifstream file(into_u8(dlg.GetPath()), std::ios::binary);
if (!file) {
MessageDialog msg(this, _L("Could not read the selected calibration file."), _L("Invalid calibration"), wxOK | wxICON_WARNING);
msg.ShowModal();
return;
}
std::ostringstream ss;
ss << file.rdbuf();
const std::string json_text = ss.str();
std::string error;
if (!texture_mapping_parse_top_surface_color_calibration(json_text, &error)) {
MessageDialog msg(this,
error.empty() ? _L("The selected file is not a supported top-surface color calibration.") : from_u8(error),
_L("Invalid calibration"),
wxOK | wxICON_WARNING);
msg.ShowModal();
return;
}
m_top_surface_color_calibration_json = json_text;
m_top_surface_color_calibration_name =
boost::filesystem::path(into_u8(dlg.GetPath())).filename().string();
const int mode = texture_mapping_top_surface_color_calibration_first_supported_mode(m_top_surface_color_calibration_json);
rebuild_top_surface_contoning_color_prediction_choices(mode);
update_top_surface_color_calibration_label();
update_top_surface_image_options_visibility(true);
}
void clear_top_surface_color_calibration()
{
m_top_surface_color_calibration_name.clear();
m_top_surface_color_calibration_json.clear();
rebuild_top_surface_contoning_color_prediction_choices(TextureMappingZone::DefaultTopSurfaceContoningColorPredictionMode);
update_top_surface_color_calibration_label();
update_top_surface_image_options_visibility(true);
}
void reset_strengths_and_offsets()
{
for (wxSlider *slider : m_minimum_offset_sliders)
@@ -4770,6 +4965,9 @@ private:
wxCheckBox *m_top_surface_contoning_beam_search_stack_expansion_checkbox {nullptr};
wxCheckBox *m_top_surface_contoning_surface_scatter_checkbox {nullptr};
wxChoice *m_top_surface_contoning_td_correction_choice {nullptr};
std::vector<int> m_top_surface_contoning_color_prediction_modes;
wxStaticText *m_top_surface_color_calibration_label {nullptr};
wxStaticText *m_top_surface_color_calibration_warning_text {nullptr};
wxCheckBox *m_top_surface_contoning_variable_layer_height_compensation_checkbox {nullptr};
wxCheckBox *m_use_legacy_fixed_color_mode_checkbox {nullptr};
wxCheckBox *m_minimum_visibility_offset_checkbox {nullptr};
@@ -4789,6 +4987,9 @@ private:
TextureMappingGlobalSettings m_global_settings;
TextureMappingPrimeTowerImage m_prime_tower_image;
TextureMappingPrimeTowerImage m_prime_tower_image_back;
std::vector<std::string> m_component_color_hexes;
std::string m_top_surface_color_calibration_name;
std::string m_top_surface_color_calibration_json;
std::vector<wxSlider*> m_minimum_offset_sliders;
std::vector<wxSpinCtrl*> m_minimum_offset_spins;
std::vector<wxSlider*> m_strength_sliders;
@@ -9128,6 +9329,21 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
_L("Preview Result Colors"),
bundle->texture_mapping_global_settings.preview_simulate_colors);
editor_sizer->Add(preview_colors_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap);
auto *color_calibration_warning_text = new wxStaticText(editor, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE);
color_calibration_warning_text->SetForegroundColour(wxColour(180, 86, 0));
editor_sizer->Add(color_calibration_warning_text, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap);
auto update_color_calibration_warning_text = [this, color_calibration_warning_text, num_physical, physical_colors](const TextureMappingZone &zone) {
if (color_calibration_warning_text == nullptr)
return;
const std::vector<unsigned int> warning_ids = zone.is_image_texture() ?
TextureMappingManager::effective_texture_component_ids(zone, num_physical, physical_colors) :
texture_mapping_selected_ids(zone, num_physical);
const wxString warning = texture_mapping_zone_color_calibration_warning(zone, warning_ids, physical_colors);
color_calibration_warning_text->SetLabel(warning);
color_calibration_warning_text->Show(!warning.empty());
color_calibration_warning_text->Wrap(FromDIP(300));
};
update_color_calibration_warning_text(entry);
auto *button_row = new wxBoxSizer(wxHORIZONTAL);
auto *offset_btn = new wxButton(editor, wxID_ANY, _L("2D Gradient Settings"));
@@ -9141,7 +9357,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
linear_gradient_stops_bar, linear_gradient_mode_choice, linear_gradient_radius_spin,
linear_gradient_radius_percent_chk,
surface_pattern_from_selection, linear_gradient_mode_from_selection, mode_choice,
show_direction_arrow_chk, contrast_spin, apply_zone]() {
show_direction_arrow_chk, contrast_spin, apply_zone, update_color_calibration_warning_text]() {
if (mgr_ptr == nullptr)
return;
auto &rows = mgr_ptr->zones();
@@ -9193,6 +9409,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
updated.show_linear_gradient_direction_arrow = show_direction_arrow_chk != nullptr && show_direction_arrow_chk->GetValue();
updated.contrast_pct = contrast_spin != nullptr ? std::clamp(float(contrast_spin->GetValue()), 25.f, 300.f) : updated.contrast_pct;
updated.high_resolution_sampling = true;
update_color_calibration_warning_text(updated);
apply_zone(std::move(updated));
};
@@ -9216,7 +9433,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
}
evt.Skip();
});
auto update_pattern_visibility = [this, editor_sizer, filaments_row, linear_gradient_mode_row, linear_gradient_mode_choice, linear_gradient_stops_bar, linear_gradient_row, linear_gradient_point_row, linear_gradient_buttons_row, linear_gradient_radius_row, show_direction_arrow_row, mode_row, contrast_row, preview_colors_row, offset_btn, advanced_btn, surface_choice, row, editor, update_texture_mapping_area_height, set_linear_gradient_picker_button_labels, sync_linear_gradient_stop_controls]() {
auto update_pattern_visibility = [this, editor_sizer, filaments_row, linear_gradient_mode_row, linear_gradient_mode_choice, linear_gradient_stops_bar, linear_gradient_row, linear_gradient_point_row, linear_gradient_buttons_row, linear_gradient_radius_row, show_direction_arrow_row, mode_row, contrast_row, preview_colors_row, color_calibration_warning_text, offset_btn, advanced_btn, surface_choice, row, editor, update_texture_mapping_area_height, set_linear_gradient_picker_button_labels, sync_linear_gradient_stop_controls]() {
const int selection = surface_choice == nullptr ? 0 : surface_choice->GetSelection();
const bool image_texture = selection == 0;
const bool linear_gradient = selection == 1;
@@ -9233,6 +9450,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
editor_sizer->Show(mode_row, image_texture, true);
editor_sizer->Show(contrast_row, image_texture, true);
editor_sizer->Show(preview_colors_row, image_texture, true);
if (color_calibration_warning_text != nullptr)
editor_sizer->Show(color_calibration_warning_text, !color_calibration_warning_text->GetLabel().empty(), true);
if (offset_btn != nullptr)
offset_btn->Show(selection == 2);
if (advanced_btn != nullptr)
@@ -9306,6 +9525,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
surface_choice,
contrast_spin,
set_sidebar_checkbox_value,
update_color_calibration_warning_text,
surface_pattern_from_selection,
apply_zone,
last_mode_selection](wxCommandEvent &) mutable {
@@ -9388,6 +9608,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
if (filament_checks[idx] != nullptr)
set_sidebar_checkbox_value(filament_checks[idx],
std::find(ids.begin(), ids.end(), unsigned(idx + 1)) != ids.end());
update_color_calibration_warning_text(updated);
apply_zone(std::move(updated));
};
mode_choice->Bind(wxEVT_COMBOBOX, on_mode_choice);
@@ -9733,8 +9954,11 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
strengths.reserve(ids.size());
offsets.reserve(ids.size());
transmission_distances.reserve(ids.size());
std::vector<std::string> component_color_hexes;
component_color_hexes.reserve(ids.size());
for (const unsigned int id : ids) {
const size_t idx = id > 0 ? size_t(id - 1) : size_t(0);
component_color_hexes.emplace_back(idx < physical_colors.size() ? physical_colors[idx] : std::string("#808080"));
strengths.emplace_back(idx < updated.filament_strengths_pct.size() ? updated.filament_strengths_pct[idx] : 100.f);
offsets.emplace_back(idx < updated.filament_minimum_offsets_pct.size() ? updated.filament_minimum_offsets_pct[idx] : 0.f);
transmission_distances.emplace_back(idx < updated.filament_transmission_distances_mm.size() ?
@@ -9747,6 +9971,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
updated.texture_mapping_mode,
updated.filament_color_mode,
ids,
component_color_hexes,
strengths,
offsets,
transmission_distances,
@@ -9810,6 +10035,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
updated.top_surface_contoning_beer_lambert_rgb_correction_enabled,
updated.top_surface_contoning_td_effective_alpha_correction_enabled,
updated.top_surface_contoning_variable_layer_height_compensation_enabled,
updated.top_surface_color_calibration_name,
updated.top_surface_color_calibration_json,
shell_usage,
bundle->texture_mapping_zones,
bundle->texture_mapping_global_settings,
@@ -9906,6 +10133,10 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
dlg.top_surface_contoning_td_effective_alpha_correction_enabled();
updated.top_surface_contoning_variable_layer_height_compensation_enabled =
dlg.top_surface_contoning_variable_layer_height_compensation_enabled();
updated.top_surface_color_calibration_name =
dlg.top_surface_color_calibration_name();
updated.top_surface_color_calibration_json =
dlg.top_surface_color_calibration_json();
if (updated.top_surface_image_printing_enabled &&
updated.top_surface_image_printing_method == int(TextureMappingZone::TopSurfaceImageContoning)) {
updated.modulation_mode = int(TextureMappingZone::ModulationPerimeterPathV2);