OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::tex2color::color_utils Namespace Reference

Namespaces

namespace  detail

Classes

struct  ColorPCA
struct  ClusterParameters
struct  SmoothParameters

Typedefs

using CGALKernel = cgalutils::Kernel
typedef CGAL::Aff_transformation_3< CGALKernel > Affine_transformation_3
typedef boost::graph_traits< CGALMesh >::halfedge_descriptor halfedge_descriptor
typedef boost::graph_traits< CGALMesh >::edge_descriptor edge_descriptor
typedef boost::graph_traits< CGALMesh >::vertex_descriptor vertex_descriptor
typedef CGAL::AABB_face_graph_triangle_primitive< CGALMesh > Primitive
typedef CGAL::AABB_traits_3< CGALKernel, Primitive > Traits
typedef CGAL::AABB_tree< Traits > Tree
typedef CGALMesh::template Property_map< vertex_descriptor, CGALKernel::Vector_3 > VNMap
typedef std::array< double, 3 > HSV
typedef std::array< double, 3 > XYZ
typedef std::array< double, 3 > LAB
using WorkingDistFunc = double (*)(const ColorDouble&, const ColorDouble&)
typedef std::array< std::size_t, 3 > Color
typedef std::vector< Color > ColorList
typedef std::array< double, 3 > ColorDouble
typedef std::array< std::size_t, 3 > RGB
using CGALMesh

Enumerations

enum struct  ColorDifferenceMethod : std::size_t { RGB = 0 , Lab = 1 }

Functions

static ColorDouble convert_rgb_uint_to_rgb_double (const Color &color)
static void normalize (CGALKernel::Vector_3 &vec)
static double get_angle_between_vectors (const CGALKernel::Vector_3 &v1, const CGALKernel::Vector_3 &v2)
static void calc_face_normals (const CGALMesh &mesh, std::vector< Eigen::Vector3d > &face_normals)
static bool check_and_repair_self_intersect (CGALMesh &mesh, bool *is_self_intersect_status=nullptr)
static bool save_polylines (const std::string &file_name, const std::vector< std::vector< CGALKernel::Point_3 > > &polylines)
static bool smooth_region_topo_boundary (CGALMesh &mesh, std::vector< std::size_t > &face_labels, std::size_t max_iters=20)
static bool smooth_region_geom_boundary (CGALMesh &mesh, std::vector< std::size_t > &face_labels, const SmoothParameters &smooth_parameters)
static HSV convert_rgb_to_hsv (const RGB &rgb)
 Convert an RGB color to the HSV color space.
static RGB convert_hsv_to_rgb (const HSV &hsv)
 Convert an HSV color to the RGB color space.
static XYZ convert_rgb_to_xyz (const RGB &color_rgb)
static XYZ convert_srgb01_to_xyz (double rs, double gs, double bs)
static LAB convert_xyz_to_lab (const XYZ &xyz)
static LAB convert_srgb01_to_lab (const ColorDouble &srgb01)
static LAB convert_rgb_to_lab (const RGB &rgb)
static Color convert_lab_to_rgb (const LAB &lab)
static double ciede2000 (const std::array< double, 3 > &lab1, const std::array< double, 3 > &lab2)
 CIEDE2000 color-difference computation.
double calc_rgb_color_difference_by_ciede2000 (const RGB &rgb1, const RGB &rgb2)
 Compute the CIEDE2000 color difference between two RGB colors.
double calc_rgb_color_difference_by_ciede2000_srgb01 (const ColorDouble &rgb1, const ColorDouble &rgb2)
 Compute the CIEDE2000 color difference between two sRGB colors (double precision, non-linear channels in [0,1]).
static std::vector< ColorDouble > farthest_point_sampling_init (const std::vector< ColorDouble > &working_colors, std::size_t k, WorkingDistFunc dist_func)
bool remesh_mesh (TriMesh &mesh, std::vector< std::size_t > &face_labels, double target_edge_length_ratio)
 Remesh the mesh while preserving color boundaries.
bool is_closed (const TriMesh &tri_mesh)
 Check whether the mesh is closed (watertight).
static bool smooth_region_labels (TriMesh &tri_mesh, std::vector< std::size_t > &face_labels, const SmoothParameters &smooth_parameters)
bool smooth_region (TriMesh &tri_mesh, std::vector< std::array< std::size_t, 3 > > &face_labels, const SmoothParameters &smooth_parameters=SmoothParameters())
 Smooth region boundaries (RGB color labels).
bool smooth_region (TriMesh &tri_mesh, std::vector< std::size_t > &face_labels, const SmoothParameters &smooth_parameters=SmoothParameters())
 Smooth region boundaries (integer labels).
double calc_rgb_color_difference_by_squared_rgb_double (const ColorDouble &c1, const ColorDouble &c2)
 Compute the squared Euclidean distance between two RGB colors (double precision).
double calc_rgb_color_difference_by_squared_rgb (const RGB &rgb1, const RGB &rgb2)
 Compute the squared Euclidean distance between two RGB colors.
static std::vector< ColorDouble > kmeans_core (const std::vector< ColorDouble > &working_colors, std::size_t k, std::size_t max_iter, WorkingDistFunc dist_func, const std::function< bool()> &cancel_cb=nullptr)
std::vector< Color > cluster_k_means (const std::vector< Color > &colors, const ClusterParameters &cluster_parameters)
 K-Means clustering algorithm that minimizes the sum of squared errors.
std::vector< Color > cluster_to_specified_colors (const std::vector< Color > &colors, const std::vector< Color > &specified_colors)
 Cluster a color list to a set of specified cluster centers.
static std::vector< ColorPCA > sort_colors_by_pca (const std::vector< Color > &colors)
std::vector< Color > cluster_adaptive (const std::vector< Color > &colors, const ClusterParameters &cluster_parameters)
 Adaptive K-Means clustering that determines an appropriate number of clusters under a max color-distance constraint.
static std::vector< std::vector< CGAL::SM_Face_index > > get_connected_face_groups (const CGALMesh &mesh)
bool get_components (const TriMesh &mesh, const std::vector< Vec2f > &vertex_uvs, std::vector< TriMesh > &component_meshes, std::vector< std::vector< Vec2f > > &component_vertex_uvs)
 Split the mesh into connected components.
bool calc_nearest_color_id (const std::vector< RGB > &colors, const RGB &color, std::size_t &nearest_color_id)
 Find the ID of the nearest color in a color list to a given color.
bool mesh_cluster (const TriMesh &mesh, const std::vector< RGB > &cluster_centers, std::vector< RGB > &map_face_to_rgb, std::vector< std::size_t > &map_face_to_cluster_id)
 Cluster mesh face colors based on given cluster centers.

Variables

static constexpr double TOPO_SMOOTH_WEIGHT_THRESHOLD = 0.3
const XYZ D65_WHITE = {0.95047, 1.0, 1.08883}

Class Documentation

◆ Slic3r::tex2color::color_utils::ClusterParameters

struct Slic3r::tex2color::color_utils::ClusterParameters
Class Members
function< bool()> cancel_callback
size_t cluster_k = 10
ColorDifferenceMethod color_difference_method = ColorDifferenceMethod::Lab
size_t max_cluster_k = 32
double max_color_distance = 25
size_t max_iter = 50

◆ Slic3r::tex2color::color_utils::SmoothParameters

struct Slic3r::tex2color::color_utils::SmoothParameters
Class Members
double smooth_weight = 0.5

Typedef Documentation

◆ Affine_transformation_3

◆ CGALKernel

◆ CGALMesh

◆ Color

typedef std::array<std::size_t, 3> Slic3r::tex2color::color_utils::Color

◆ ColorDouble

typedef std::array<double, 3> Slic3r::tex2color::color_utils::ColorDouble

◆ ColorList

◆ edge_descriptor

typedef boost::graph_traits<CGALMesh>::edge_descriptor Slic3r::tex2color::color_utils::edge_descriptor

◆ halfedge_descriptor

typedef boost::graph_traits<CGALMesh>::halfedge_descriptor Slic3r::tex2color::color_utils::halfedge_descriptor

◆ HSV

typedef std::array<double, 3> Slic3r::tex2color::color_utils::HSV

◆ LAB

typedef std::array<double, 3> Slic3r::tex2color::color_utils::LAB

◆ Primitive

typedef CGAL::AABB_face_graph_triangle_primitive<CGALMesh> Slic3r::tex2color::color_utils::Primitive

◆ RGB

typedef std::array<std::size_t, 3> Slic3r::tex2color::color_utils::RGB

◆ Traits

◆ Tree

typedef CGAL::AABB_tree<Traits> Slic3r::tex2color::color_utils::Tree

◆ vertex_descriptor

typedef boost::graph_traits<CGALMesh>::vertex_descriptor Slic3r::tex2color::color_utils::vertex_descriptor

◆ VNMap

typedef CGALMesh::template Property_map<vertex_descriptor, CGALKernel::Vector_3> Slic3r::tex2color::color_utils::VNMap

◆ WorkingDistFunc

◆ XYZ

typedef std::array<double, 3> Slic3r::tex2color::color_utils::XYZ

Enumeration Type Documentation

◆ ColorDifferenceMethod

Enumerator
RGB 
Lab 
21 : std::size_t {
22 RGB = 0, // Simplest and fastest
23 Lab = 1 // Most perceptually accurate
24};
std::array< float, 3 > RGB
Definition Color.hpp:12

Function Documentation

◆ calc_face_normals()

void Slic3r::tex2color::color_utils::calc_face_normals ( const CGALMesh & mesh,
std::vector< Eigen::Vector3d > & face_normals )
static
129 {
130 std::size_t fcnt = mesh.number_of_faces();
131 face_normals.resize(fcnt);
132 for (auto face : mesh.faces()) {
133 std::vector<CGALKernel::Point_3> points;
134 for (auto vtx : mesh.vertices_around_face(mesh.halfedge(face))) {
135 points.push_back(mesh.point(vtx));
136 }
137 Eigen::Vector3d pos1(points[0].x(), points[0].y(), points[0].z());
138 Eigen::Vector3d pos2(points[1].x(), points[1].y(), points[1].z());
139 Eigen::Vector3d pos3(points[2].x(), points[2].y(), points[2].z());
140 face_normals[face] = (pos3 - pos2).cross(pos1 - pos2);
141 face_normals[face].normalize();
142 }
143 return;
144}
T cross(const boost::geometry::model::d2::point_xy< T > &v1, const boost::geometry::model::d2::point_xy< T > &v2)
Definition ExtrusionSimulator.cpp:168

References Slic3r::cross().

Here is the call graph for this function:

◆ calc_nearest_color_id()

bool Slic3r::tex2color::color_utils::calc_nearest_color_id ( const std::vector< RGB > & colors,
const RGB & color,
std::size_t & nearest_color_id )

Find the ID of the nearest color in a color list to a given color.

Parameters
[in]colorsColor list.
[in]colorTarget color.
[out]nearest_color_idID of the nearest color found.
Returns
true on success, false on failure.
1435 {
1436 if (colors.empty()) {
1437 BOOST_LOG_TRIVIAL(warning) << "No color list provided";
1438 return false;
1439 }
1440 double min_dist = std::numeric_limits<double>::max();
1441 nearest_color_id = 0;
1442 for (std::size_t i = 0; i < colors.size(); ++i) {
1443 double dist = calc_rgb_color_difference_by_ciede2000(colors[i], color);
1444 if (dist < min_dist) {
1445 min_dist = dist;
1446 nearest_color_id = i;
1447 }
1448 }
1449 return true;
1450}
T dist(const boost::polygon::point_data< T > &p1, const boost::polygon::point_data< T > &p2)
Definition Geometry.cpp:298
double calc_rgb_color_difference_by_ciede2000(const RGB &rgb1, const RGB &rgb2)
Compute the CIEDE2000 color difference between two RGB colors.
Definition ColorUtils.cpp:643

References calc_nearest_color_id(), calc_rgb_color_difference_by_ciede2000(), and Slic3r::Geometry::dist().

Referenced by calc_nearest_color_id(), and Slic3r::tex2color::quantize_vertex_colors().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ calc_rgb_color_difference_by_ciede2000()

double Slic3r::tex2color::color_utils::calc_rgb_color_difference_by_ciede2000 ( const RGB & rgb1,
const RGB & rgb2 )

Compute the CIEDE2000 color difference between two RGB colors.

Currently the most accurate color-difference formula, recommended by CIE as the industry standard.

  • dE <= 1.0: imperceptible to the human eye, high-precision color matching.
  • dE <= 2.0: slight difference, noticeable by experts; printing / image processing standard.
  • dE <= 3.0: noticeable by ordinary observers; general quality control.
Parameters
[in]rgb1First RGB color [R, G, B], range 0~255.
[in]rgb2Second RGB color [R, G, B], range 0~255.
Returns
CIEDE2000 color difference; smaller values indicate more similar colors.
643 {
644 auto lab1 = convert_rgb_to_lab(rgb1);
645 auto lab2 = convert_rgb_to_lab(rgb2);
646 return ciede2000(lab1, lab2);
647}
static double ciede2000(const std::array< double, 3 > &lab1, const std::array< double, 3 > &lab2)
CIEDE2000 color-difference computation.
Definition ColorUtils.cpp:560
static LAB convert_rgb_to_lab(const RGB &rgb)
Definition ColorUtils.cpp:518

References ciede2000(), and convert_rgb_to_lab().

Referenced by calc_nearest_color_id(), Slic3r::compute_delta_e(), and Slic3r::tex2color::ensure_all_cluster_centers_used().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ calc_rgb_color_difference_by_ciede2000_srgb01()

double Slic3r::tex2color::color_utils::calc_rgb_color_difference_by_ciede2000_srgb01 ( const ColorDouble & rgb1,
const ColorDouble & rgb2 )

Compute the CIEDE2000 color difference between two sRGB colors (double precision, non-linear channels in [0,1]).

Uses the same XYZ/Lab/dE00 pipeline as calc_rgb_color_difference_by_ciede2000 but without uint8 quantization or the intermediate x255 conversion; suitable for bisection, color blending, and other iterative scenarios. Note: ColorDouble here represents [R,G,B] in [0,1], which differs from the 0~255 scale used by other interfaces in this file. Callers should follow the naming convention.

Parameters
[in]rgb1rgb2 sRGB non-linear channel values, recommended range [0,1].
649 {
650 const LAB lab1 = convert_srgb01_to_lab(rgb1);
651 const LAB lab2 = convert_srgb01_to_lab(rgb2);
652 return ciede2000(lab1, lab2);
653}
static LAB convert_srgb01_to_lab(const ColorDouble &srgb01)
Definition ColorUtils.cpp:514
std::array< double, 3 > LAB
Definition ColorUtils.cpp:370

References ciede2000(), and convert_srgb01_to_lab().

Here is the call graph for this function:

◆ calc_rgb_color_difference_by_squared_rgb()

double Slic3r::tex2color::color_utils::calc_rgb_color_difference_by_squared_rgb ( const RGB & rgb1,
const RGB & rgb2 )

Compute the squared Euclidean distance between two RGB colors.

Parameters
[in]rgb1First RGB color [R, G, B], range 0~255.
[in]rgb2Second RGB color [R, G, B], range 0~255.
Returns
Squared Euclidean distance: (R1-R2)^2 + (G1-G2)^2 + (B1-B2)^2.
981 {
982 auto c1d = convert_rgb_uint_to_rgb_double(c1);
983 auto c2d = convert_rgb_uint_to_rgb_double(c2);
985}
double calc_rgb_color_difference_by_squared_rgb_double(const ColorDouble &c1, const ColorDouble &c2)
Compute the squared Euclidean distance between two RGB colors (double precision).
Definition ColorUtils.cpp:973
static ColorDouble convert_rgb_uint_to_rgb_double(const Color &color)
Definition ColorUtils.cpp:105

References calc_rgb_color_difference_by_squared_rgb_double(), and convert_rgb_uint_to_rgb_double().

Here is the call graph for this function:

◆ calc_rgb_color_difference_by_squared_rgb_double()

double Slic3r::tex2color::color_utils::calc_rgb_color_difference_by_squared_rgb_double ( const ColorDouble & c1,
const ColorDouble & c2 )

Compute the squared Euclidean distance between two RGB colors (double precision).

Parameters
[in]c1First RGB color [R, G, B], as double.
[in]c2Second RGB color [R, G, B], as double.
Returns
Squared Euclidean distance: (R1-R2)^2 + (G1-G2)^2 + (B1-B2)^2.
973 {
974 double dr = c1[0] - c2[0];
975 double dg = c1[1] - c2[1];
976 double db = c1[2] - c2[2];
977 return dr * dr + dg * dg + db * db;
978}

Referenced by calc_rgb_color_difference_by_squared_rgb(), cluster_adaptive(), and cluster_k_means().

Here is the caller graph for this function:

◆ check_and_repair_self_intersect()

bool Slic3r::tex2color::color_utils::check_and_repair_self_intersect ( CGALMesh & mesh,
bool * is_self_intersect_status = nullptr )
static
146 {
147 // true means the mesh is no self intersect now
148 // false means the mesh is still self intersect
149 auto is_self_intersect = PMP::does_self_intersect(mesh);
150 if (is_self_intersect_status != nullptr) {
151 *is_self_intersect_status = is_self_intersect;
152 }
153 if (is_self_intersect) {
154 bool repair = PMP::experimental::remove_self_intersections(mesh);
155 if (repair) {
156 return true;
157 } else {
158 return false;
159 }
160 }
161 return true;
162}

◆ ciede2000()

double Slic3r::tex2color::color_utils::ciede2000 ( const std::array< double, 3 > & lab1,
const std::array< double, 3 > & lab2 )
static

CIEDE2000 color-difference computation.

Currently the most accurate color-difference formula, recommended by CIE as the industry standard.

Parameters
lab1LAB values of the first color.
lab2LAB values of the second color.
Returns
Color difference (typically < 1.0 is imperceptible to the human eye).
560 {
561 // Parameters in the CIE L*C*h* formula
562 double L1 = lab1[0], a1 = lab1[1], b1 = lab1[2];
563 double L2 = lab2[0], a2 = lab2[1], b2 = lab2[2];
564
565 // Compute C1 and C2
566 double C1 = std::sqrt(a1 * a1 + b1 * b1);
567 double C2 = std::sqrt(a2 * a2 + b2 * b2);
568 double C_avg = (C1 + C2) / 2.0;
569
570 // G factor (compensates for non-linearity in the mid-low chroma region)
571 double C7 = C_avg * C_avg * C_avg * C_avg * C_avg * C_avg * C_avg;
572 double G = 0.5 * (1.0 - std::sqrt(C7 / (C7 + 6103515625.0)));
573
574 // a1' and a2'
575 double a1_prime = (1.0 + G) * a1;
576 double a2_prime = (1.0 + G) * a2;
577
578 // C'1 and C'2
579 double C1_prime = std::sqrt(a1_prime * a1_prime + b1 * b1);
580 double C2_prime = std::sqrt(a2_prime * a2_prime + b2 * b2);
581 double C_prime_avg = (C1_prime + C2_prime) / 2.0;
582
583 // h'1 and h'2
584 double h1_prime = std::atan2(b1, a1_prime);
585 double h2_prime = std::atan2(b2, a2_prime);
586 if (h1_prime < 0) {
587 h1_prime += 2 * M_PI;
588 }
589 if (h2_prime < 0) {
590 h2_prime += 2 * M_PI;
591 }
592
593 // Compute dh'
594 double dh_prime;
595 if (std::abs(h1_prime - h2_prime) <= M_PI) {
596 dh_prime = h2_prime - h1_prime;
597 } else if (h2_prime <= h1_prime) {
598 dh_prime = h2_prime - h1_prime + 2 * M_PI;
599 } else {
600 dh_prime = h2_prime - h1_prime - 2 * M_PI;
601 }
602
603 // Compute dH'
604 double dH_prime = 2.0 * std::sqrt(C1_prime * C2_prime) * std::sin(dh_prime / 2.0);
605
606 // Compute dL'
607 double dL_prime = L2 - L1;
608
609 // Compute dC'
610 double dC_prime = C2_prime - C1_prime;
611
612 // Compute h_prime_avg
613 double h_prime_avg;
614 if (std::abs(h1_prime - h2_prime) > M_PI) {
615 h_prime_avg = (h1_prime + h2_prime + 2 * M_PI) / 2.0;
616 } else {
617 h_prime_avg = (h1_prime + h2_prime) / 2.0;
618 }
619
620 // Compute T
621 double T = 1.0 - 0.17 * std::cos(h_prime_avg - M_PI / 6.0) + 0.24 * std::cos(2.0 * h_prime_avg) + 0.32 * std::cos(3.0 * h_prime_avg + M_PI / 30.0) -
622 0.20 * std::cos(4.0 * h_prime_avg - 3.0 * M_PI / 6.0);
623
624 // Compute rotation term R_T = -R_C * sin(2*delta_theta), where delta_theta = 30 * exp(-((h_bar'-275)/25)^2)
625 // h_prime_avg is in radians; convert to degrees for delta_theta; 2*delta_theta = 60 * exp(...), convert back to radians for sin
626 double h_prime_avg_deg = h_prime_avg * 180.0 / M_PI;
627 double C_prime_avg_7 = std::pow(C_prime_avg, 7);
628 double R = -2.0 * std::sqrt(C_prime_avg_7 / (C_prime_avg_7 + 6103515625.0)) *
629 std::sin((60.0 * M_PI / 180.0) * std::exp(-std::pow((h_prime_avg_deg - 275.0) / 25.0, 2)));
630
631 // Compute SL, SC, SH
632 double L_prime_avg = (L1 + L2) / 2.0;
633 double SL = 1.0 + 0.015 * std::pow(L_prime_avg - 50.0, 2) / std::sqrt(20 + std::pow(L_prime_avg - 50.0, 2));
634 double SC = 1.0 + 0.045 * C_prime_avg;
635 double SH = 1.0 + 0.015 * C_prime_avg * T;
636
637 // Final color difference
638 double deltaE = std::sqrt(std::pow(dL_prime / SL, 2) + std::pow(dC_prime / SC, 2) + std::pow(dH_prime / SH, 2) + R * (dC_prime / SC) * (dH_prime / SH));
639
640 return deltaE;
641}
#define M_PI
Definition ExtrusionSimulator.cpp:31

References M_PI.

Referenced by calc_rgb_color_difference_by_ciede2000(), calc_rgb_color_difference_by_ciede2000_srgb01(), cluster_adaptive(), and cluster_k_means().

Here is the caller graph for this function:

◆ cluster_adaptive()

std::vector< Color > Slic3r::tex2color::color_utils::cluster_adaptive ( const std::vector< Color > & colors,
const ClusterParameters & cluster_parameters )

Adaptive K-Means clustering that determines an appropriate number of clusters under a max color-distance constraint.

Automatically finds the optimal cluster count via binary search so that max_color_distance is satisfied.

Parameters
[in]colorsInput color list.
[in]cluster_parametersClustering parameters; cluster_k is ignored and determined automatically.
Returns
List of cluster-center colors whose count is determined by the algorithm based on max_color_distance.
1246 {
1247 if (colors.empty()) {
1248 return {};
1249 }
1250
1251 const double max_color_distance = cluster_parameters.max_color_distance;
1252 const std::size_t max_iter = cluster_parameters.max_iter;
1253 const bool use_lab = (cluster_parameters.color_difference_method != ColorDifferenceMethod::RGB);
1255
1256 // ==========================================
1257 // 1. Deduplicate + convert to working space
1258 // ==========================================
1259 std::vector<Color> unique_colors = colors;
1260 std::sort(unique_colors.begin(), unique_colors.end());
1261 auto last = std::unique(unique_colors.begin(), unique_colors.end());
1262 unique_colors.erase(last, unique_colors.end());
1263
1264 BOOST_LOG_TRIVIAL(debug) << "cluster_adaptive: colors=" << colors.size()
1265 << " unique=" << unique_colors.size()
1266 << " max_color_distance=" << max_color_distance;
1267
1268 if (unique_colors.size() <= 1) {
1269 return unique_colors;
1270 }
1271
1272 std::vector<ColorDouble> working_colors(unique_colors.size());
1273 for (std::size_t i = 0; i < unique_colors.size(); ++i) {
1274 if (use_lab) {
1275 working_colors[i] = convert_rgb_to_lab(unique_colors[i]);
1276 } else {
1277 working_colors[i] = {static_cast<double>(unique_colors[i][0]), static_cast<double>(unique_colors[i][1]), static_cast<double>(unique_colors[i][2])};
1278 }
1279 }
1280
1281 // ==========================================
1282 // 2. Binary search k: find the smallest k where P99 radius <= max_color_distance
1283 // ==========================================
1284 const std::size_t max_k = cluster_parameters.max_cluster_k;
1285 std::size_t lo = 1;
1286 std::size_t hi = std::min<std::size_t>(max_k, unique_colors.size());
1287 std::size_t best_k = 0;
1288 std::vector<ColorDouble> best_centers;
1289
1290 constexpr double kRadiusPercentile = 0.99;
1291
1292 auto calc_max_radius = [&](const std::vector<ColorDouble>& centers) -> double {
1293 std::vector<double> distances(working_colors.size());
1294 tbb::parallel_for(tbb::blocked_range<std::size_t>(0, working_colors.size()), [&](const tbb::blocked_range<size_t>& range) {
1295 for (std::size_t i = range.begin(); i < range.end(); ++i) {
1296 double min_dist = std::numeric_limits<double>::max();
1297 for (const auto& center : centers) {
1298 double d = working_dist_func(working_colors[i], center);
1299 if (d < min_dist) {
1300 min_dist = d;
1301 }
1302 }
1303 distances[i] = min_dist;
1304 }
1305 });
1306 if (distances.empty()) {
1307 return 0.0;
1308 }
1309 std::size_t idx = std::min<std::size_t>(static_cast<std::size_t>(distances.size() * kRadiusPercentile), distances.size() - 1);
1310 std::nth_element(distances.begin(), distances.begin() + idx, distances.end());
1311 return distances[idx];
1312 };
1313
1314 const auto& cancel_cb = cluster_parameters.cancel_callback;
1315
1316 while (lo <= hi) {
1317 if (cancel_cb && cancel_cb()) return {};
1318 std::size_t mid = lo + (hi - lo) / 2;
1319 auto centers = kmeans_core(working_colors, mid, max_iter, working_dist_func, cancel_cb);
1320 if (cancel_cb && cancel_cb()) return {};
1321 double max_radius = calc_max_radius(centers);
1322
1323 BOOST_LOG_TRIVIAL(debug) << "cluster_adaptive binary search: k=" << mid << " max_radius=" << max_radius;
1324
1325 if (max_radius <= max_color_distance) {
1326 best_k = mid;
1327 best_centers = std::move(centers);
1328 hi = mid - 1;
1329 } else {
1330 lo = mid + 1;
1331 }
1332 }
1333
1334 if (best_k == 0) {
1335 best_k = std::min<std::size_t>(max_k, unique_colors.size());
1336 best_centers = kmeans_core(working_colors, best_k, max_iter, working_dist_func, cancel_cb);
1337 BOOST_LOG_TRIVIAL(warning) << "cluster_adaptive: binary search found no k satisfying max_radius<="
1338 << max_color_distance << ", fallback to k=" << best_k;
1339 }
1340
1341 BOOST_LOG_TRIVIAL(debug) << "cluster_adaptive: best_k=" << best_k;
1342
1343 // ==========================================
1344 // 3. Convert centers back to RGB
1345 // ==========================================
1346 std::vector<Color> result(best_k);
1347 for (std::size_t i = 0; i < best_k; ++i) {
1348 if (use_lab) {
1349 result[i] = convert_lab_to_rgb(best_centers[i]);
1350 } else {
1351 result[i] = {static_cast<std::size_t>(std::round(best_centers[i][0])), static_cast<std::size_t>(std::round(best_centers[i][1])),
1352 static_cast<std::size_t>(std::round(best_centers[i][2]))};
1353 }
1354 }
1355
1356 return result;
1357}
ColorDifferenceMethod color_difference_method
Definition ColorUtils.hpp:27
static Color convert_lab_to_rgb(const LAB &lab)
Definition ColorUtils.cpp:522
static std::vector< ColorDouble > kmeans_core(const std::vector< ColorDouble > &working_colors, std::size_t k, std::size_t max_iter, WorkingDistFunc dist_func, const std::function< bool()> &cancel_cb=nullptr)
Definition ColorUtils.cpp:988
std::function< bool()> cancel_callback
Definition ColorUtils.hpp:37
std::size_t max_iter
Definition ColorUtils.hpp:35
std::size_t max_cluster_k
Definition ColorUtils.hpp:33
double(*)(const ColorDouble &, const ColorDouble &) WorkingDistFunc
Definition ColorUtils.cpp:656
double max_color_distance
Definition ColorUtils.hpp:29
auto range(Cont &&cont)
Definition libslic3r.h:405

References calc_rgb_color_difference_by_squared_rgb_double(), ciede2000(), cluster_adaptive(), Slic3r::tex2color::color_utils::ClusterParameters::color_difference_method, convert_rgb_to_lab(), Slic3r::tex2color::color_utils::ClusterParameters::max_cluster_k, Slic3r::tex2color::color_utils::ClusterParameters::max_color_distance, Slic3r::tex2color::color_utils::ClusterParameters::max_iter, Slic3r::range(), and RGB.

Referenced by cluster_adaptive(), and Slic3r::tex2color::quantize_vertex_colors().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ cluster_k_means()

std::vector< Color > Slic3r::tex2color::color_utils::cluster_k_means ( const std::vector< Color > & colors,
const ClusterParameters & cluster_parameters )

K-Means clustering algorithm that minimizes the sum of squared errors.

Uses K-Means++ initialization to iteratively find the optimal cluster centers.

Parameters
[in]colorsInput color list.
[in]cluster_parametersClustering parameters including cluster count, max iterations, color-difference method, etc.
Returns
List of cluster-center colors whose size equals cluster_parameters.cluster_k.
1065 {
1066 std::size_t k = cluster_parameters.cluster_k;
1067 std::size_t max_iter = cluster_parameters.max_iter;
1068
1069 if (k == 0 || colors.empty()) {
1070 return {};
1071 }
1072
1073 const bool use_lab = (cluster_parameters.color_difference_method != ColorDifferenceMethod::RGB);
1075
1076 // ==========================================
1077 // 1. Preprocessing: deduplicate + pre-convert to working space
1078 // ==========================================
1079 std::vector<Color> unique_colors = colors;
1080 std::sort(unique_colors.begin(), unique_colors.end());
1081 auto last = std::unique(unique_colors.begin(), unique_colors.end());
1082 unique_colors.erase(last, unique_colors.end());
1083
1084#ifdef DEBUG_FLAG
1085 BOOST_LOG_TRIVIAL(debug) << "Input colors: " << colors.size() << ", Unique colors: " << unique_colors.size();
1086#endif // DEBUG_FLAG
1087
1088 if (unique_colors.size() < k) {
1089 BOOST_LOG_TRIVIAL(warning) << "Unique color count (" << unique_colors.size() << ") is less than target K (" << k << "). Adjusting K.";
1090 k = unique_colors.size();
1091 if (k == 0) {
1092 return {};
1093 }
1094 return unique_colors;
1095 }
1096
1097 std::vector<ColorDouble> working_colors(unique_colors.size());
1098 for (std::size_t i = 0; i < unique_colors.size(); ++i) {
1099 if (use_lab) {
1100 working_colors[i] = convert_rgb_to_lab(unique_colors[i]);
1101 } else {
1102 working_colors[i] = {static_cast<double>(unique_colors[i][0]), static_cast<double>(unique_colors[i][1]), static_cast<double>(unique_colors[i][2])};
1103 }
1104 }
1105
1106 // ==========================================
1107 // 2. K-Means clustering
1108 // ==========================================
1109 auto centers = kmeans_core(working_colors, k, max_iter, working_dist_func, cluster_parameters.cancel_callback);
1110
1111 // ==========================================
1112 // 3. Output: convert from working space back to RGB
1113 // ==========================================
1114 std::vector<Color> result(k);
1115 for (std::size_t i = 0; i < k; ++i) {
1116 if (use_lab) {
1117 result[i] = convert_lab_to_rgb(centers[i]);
1118 } else {
1119 result[i] = {static_cast<std::size_t>(std::round(centers[i][0])), static_cast<std::size_t>(std::round(centers[i][1])),
1120 static_cast<std::size_t>(std::round(centers[i][2]))};
1121 }
1122 }
1123
1124 return result;
1125}
std::size_t cluster_k
Definition ColorUtils.hpp:31

References calc_rgb_color_difference_by_squared_rgb_double(), Slic3r::tex2color::color_utils::ClusterParameters::cancel_callback, ciede2000(), Slic3r::tex2color::color_utils::ClusterParameters::cluster_k, cluster_k_means(), Slic3r::tex2color::color_utils::ClusterParameters::color_difference_method, convert_lab_to_rgb(), convert_rgb_to_lab(), kmeans_core(), Slic3r::tex2color::color_utils::ClusterParameters::max_iter, and RGB.

Referenced by cluster_k_means(), and Slic3r::tex2color::quantize_vertex_colors().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ cluster_to_specified_colors()

std::vector< Color > Slic3r::tex2color::color_utils::cluster_to_specified_colors ( const std::vector< Color > & colors,
const std::vector< Color > & specified_colors )

Cluster a color list to a set of specified cluster centers.

For each input color, find the nearest specified cluster center and replace it.

Parameters
[in]colorsInput color list.
[in]specified_colorsSpecified cluster-center colors.
Returns
Clustered color list where each color is replaced by its nearest center.
1127 {
1128 std::vector<Color> cluster_colors = colors;
1129 std::vector<ColorDouble> specified_double_colors;
1130 specified_double_colors.reserve(specified_colors.size());
1131 for (auto& color : specified_colors) {
1132 specified_double_colors.push_back(ColorDouble{static_cast<double>(color[0]), static_cast<double>(color[1]), static_cast<double>(color[2])});
1133 }
1134
1135 tbb::parallel_for(tbb::blocked_range<std::size_t>(0, cluster_colors.size()), [&](const tbb::blocked_range<size_t>& range) {
1136 for (std::size_t i = range.begin(); i < range.end(); ++i) {
1137 ColorDouble p_color{static_cast<double>(cluster_colors[i][0]), static_cast<double>(cluster_colors[i][1]),
1138 static_cast<double>(cluster_colors[i][2])};
1139
1140 double min_dist = std::numeric_limits<double>::max();
1141 std::size_t best_cluster = 0;
1142
1143 for (std::size_t j = 0; j < specified_double_colors.size(); ++j) {
1144 double d = calc_rgb_color_difference_by_squared_rgb_double(p_color, specified_double_colors[j]);
1145 if (d < min_dist) {
1146 min_dist = d;
1147 best_cluster = j;
1148 }
1149 }
1150
1151 cluster_colors[i] = specified_colors[best_cluster];
1152 }
1153 });
1154
1155 return cluster_colors;
1156}
std::array< double, 3 > ColorDouble
Definition ColorUtils.hpp:17

References cluster_to_specified_colors(), and Slic3r::range().

Referenced by cluster_to_specified_colors().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ convert_hsv_to_rgb()

RGB Slic3r::tex2color::color_utils::convert_hsv_to_rgb ( const HSV & hsv)
static

Convert an HSV color to the RGB color space.

Parameters
hsvInput HSV color [H, S, V], H in 0~360, S and V in 0~1.
Returns
RGB output [R, G, B], range 0~255.
422 {
423 double h = hsv[0];
424 double s = hsv[1];
425 double v = hsv[2];
426
427 double c = v * s;
428 double x = c * (1 - std::abs(fmod(h / 60.0, 2.0) - 1));
429 double m = v - c;
430
431 double r, g, b;
432
433 if (h < 60) {
434 r = c;
435 g = x;
436 b = 0;
437 } else if (h < 120) {
438 r = x;
439 g = c;
440 b = 0;
441 } else if (h < 180) {
442 r = 0;
443 g = c;
444 b = x;
445 } else if (h < 240) {
446 r = 0;
447 g = x;
448 b = c;
449 } else if (h < 300) {
450 r = x;
451 g = 0;
452 b = c;
453 } else {
454 r = c;
455 g = 0;
456 b = x;
457 }
458
459 return {static_cast<std::size_t>((r + m) * 255 + 0.5), static_cast<std::size_t>((g + m) * 255 + 0.5), static_cast<std::size_t>((b + m) * 255 + 0.5)};
460}

◆ convert_lab_to_rgb()

Color Slic3r::tex2color::color_utils::convert_lab_to_rgb ( const LAB & lab)
static
522 {
523 // Lab → XYZ
524 const double delta = 6.0 / 29.0;
525 const double delta2x3 = 3.0 * delta * delta;
526
527 double fy = (lab[0] + 16.0) / 116.0;
528 double fx = lab[1] / 500.0 + fy;
529 double fz = fy - lab[2] / 200.0;
530
531 double x = D65_WHITE[0] * (fx > delta ? fx * fx * fx : delta2x3 * (fx - 4.0 / 29.0));
532 double y = D65_WHITE[1] * (fy > delta ? fy * fy * fy : delta2x3 * (fy - 4.0 / 29.0));
533 double z = D65_WHITE[2] * (fz > delta ? fz * fz * fz : delta2x3 * (fz - 4.0 / 29.0));
534
535 // XYZ -> linear RGB (sRGB inverse matrix)
536 double r_lin = 3.2404542 * x - 1.5371385 * y - 0.4985314 * z;
537 double g_lin = -0.9692660 * x + 1.8760108 * y + 0.0415560 * z;
538 double b_lin = 0.0556434 * x - 0.2040259 * y + 1.0572252 * z;
539
540 // linear RGB -> sRGB (inverse gamma correction)
541 auto inverse_gamma = [](double v) -> double {
542 v = std::max(v, 0.0);
543 return v <= 0.0031308 ? 12.92 * v : 1.055 * std::pow(v, 1.0 / 2.4) - 0.055;
544 };
545
546 auto to_uint8 = [](double v) -> std::size_t { return static_cast<std::size_t>(std::clamp(std::round(v * 255.0), 0.0, 255.0)); };
547
548 return {to_uint8(inverse_gamma(r_lin)), to_uint8(inverse_gamma(g_lin)), to_uint8(inverse_gamma(b_lin))};
549}
const XYZ D65_WHITE
Definition ColorUtils.cpp:372

References D65_WHITE.

Referenced by cluster_k_means().

Here is the caller graph for this function:

◆ convert_rgb_to_hsv()

HSV Slic3r::tex2color::color_utils::convert_rgb_to_hsv ( const RGB & rgb)
static

Convert an RGB color to the HSV color space.

Parameters
rgbInput RGB color [R, G, B], range 0~255.
Returns
HSV output [H, S, V], H in 0~360, S and V in 0~1.
380 {
381 // Normalize to [0, 1]
382 double r = rgb[0] / 255.0;
383 double g = rgb[1] / 255.0;
384 double b = rgb[2] / 255.0;
385
386 double max = std::max({r, g, b});
387 double min = std::min({r, g, b});
388 double delta = max - min;
389
390 // Compute hue H
391 double h = 0;
392 if (delta == 0) {
393 h = 0; // Gray; hue is undefined
394 } else {
395 if (max == r) {
396 h = 60.0 * fmod((g - b) / delta, 6.0);
397 } else if (max == g) {
398 h = 60.0 * ((b - r) / delta + 2.0);
399 } else { // max == b
400 h = 60.0 * ((r - g) / delta + 4.0);
401 }
402 if (h < 0) {
403 h += 360.0;
404 }
405 }
406
407 // Compute saturation S
408 double s = (max == 0) ? 0 : (delta / max);
409
410 // Compute value V
411 double v = max;
412
413 return {h, s, v};
414}

◆ convert_rgb_to_lab()

LAB Slic3r::tex2color::color_utils::convert_rgb_to_lab ( const RGB & rgb)
static
518 {
520}
static XYZ convert_rgb_to_xyz(const RGB &color_rgb)
Definition ColorUtils.cpp:462
static LAB convert_xyz_to_lab(const XYZ &xyz)
Definition ColorUtils.cpp:493

References convert_rgb_to_xyz(), and convert_xyz_to_lab().

Referenced by calc_rgb_color_difference_by_ciede2000(), cluster_adaptive(), and cluster_k_means().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ convert_rgb_to_xyz()

XYZ Slic3r::tex2color::color_utils::convert_rgb_to_xyz ( const RGB & color_rgb)
static
462 {
463 ColorDouble rgb{static_cast<double>(color_rgb[0]), static_cast<double>(color_rgb[1]), static_cast<double>(color_rgb[2])};
464 auto gammaCorrect = [](double v) -> double {
465 v = v / 255.0;
466 if (v > 0.04045) {
467 return std::pow((v + 0.055) / 1.055, 2.4);
468 } else {
469 return v / 12.92;
470 }
471 };
472
473 double r = gammaCorrect(rgb[0]);
474 double g = gammaCorrect(rgb[1]);
475 double b = gammaCorrect(rgb[2]);
476
477 // sRGB to XYZ matrix
478 return {r * 0.4124564 + g * 0.3575761 + b * 0.1804375, r * 0.2126729 + g * 0.7151522 + b * 0.0721750, r * 0.0193339 + g * 0.1191920 + b * 0.9503041};
479}

Referenced by convert_rgb_to_lab().

Here is the caller graph for this function:

◆ convert_rgb_uint_to_rgb_double()

ColorDouble Slic3r::tex2color::color_utils::convert_rgb_uint_to_rgb_double ( const Color & color)
inlinestatic
105 {
106 return ColorDouble{static_cast<double>(color[0]), static_cast<double>(color[1]), static_cast<double>(color[2])};
107}

Referenced by calc_rgb_color_difference_by_squared_rgb().

Here is the caller graph for this function:

◆ convert_srgb01_to_lab()

LAB Slic3r::tex2color::color_utils::convert_srgb01_to_lab ( const ColorDouble & srgb01)
static
514 {
515 return convert_xyz_to_lab(convert_srgb01_to_xyz(srgb01[0], srgb01[1], srgb01[2]));
516}
static XYZ convert_srgb01_to_xyz(double rs, double gs, double bs)
Definition ColorUtils.cpp:482

References convert_srgb01_to_xyz(), and convert_xyz_to_lab().

Referenced by calc_rgb_color_difference_by_ciede2000_srgb01().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ convert_srgb01_to_xyz()

XYZ Slic3r::tex2color::color_utils::convert_srgb01_to_xyz ( double rs,
double gs,
double bs )
static
482 {
483 auto gamma_correct = [](double v) -> double {
484 v = std::clamp(v, 0.0, 1.0);
485 return (v > 0.04045) ? std::pow((v + 0.055) / 1.055, 2.4) : (v / 12.92);
486 };
487 const double r = gamma_correct(rs);
488 const double g = gamma_correct(gs);
489 const double b = gamma_correct(bs);
490 return {r * 0.4124564 + g * 0.3575761 + b * 0.1804375, r * 0.2126729 + g * 0.7151522 + b * 0.0721750, r * 0.0193339 + g * 0.1191920 + b * 0.9503041};
491}

Referenced by convert_srgb01_to_lab().

Here is the caller graph for this function:

◆ convert_xyz_to_lab()

LAB Slic3r::tex2color::color_utils::convert_xyz_to_lab ( const XYZ & xyz)
static
493 {
494 auto f = [](double t) -> double {
495 const double delta = 6.0 / 29.0;
496 if (t > delta * delta * delta) {
497 return std::cbrt(t);
498 } else {
499 return t / (3.0 * delta * delta) + 4.0 / 29.0;
500 }
501 };
502
503 // D65 white point
504 double xn = D65_WHITE[0], yn = D65_WHITE[1], zn = D65_WHITE[2];
505
506 double fx = f(xyz[0] / xn);
507 double fy = f(xyz[1] / yn);
508 double fz = f(xyz[2] / zn);
509
510 return {116.0 * fy - 16.0, 500.0 * (fx - fy), 200.0 * (fy - fz)};
511}
static double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
Definition FillGyroid.cpp:124

References D65_WHITE, and Slic3r::f().

Referenced by convert_rgb_to_lab(), and convert_srgb01_to_lab().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ farthest_point_sampling_init()

std::vector< ColorDouble > Slic3r::tex2color::color_utils::farthest_point_sampling_init ( const std::vector< ColorDouble > & working_colors,
std::size_t k,
WorkingDistFunc dist_func )
static
660 {
661 std::vector<ColorDouble> centers(k);
662
663 // 1. Compute the global centroid and pick the nearest point as the first center
664 ColorDouble centroid = {0.0, 0.0, 0.0};
665 for (const auto& c : working_colors) {
666 centroid[0] += c[0];
667 centroid[1] += c[1];
668 centroid[2] += c[2];
669 }
670 const auto n = static_cast<double>(working_colors.size());
671 centroid[0] /= n;
672 centroid[1] /= n;
673 centroid[2] /= n;
674
675 double best_dist = std::numeric_limits<double>::max();
676 std::size_t first_idx = 0;
677 for (std::size_t i = 0; i < working_colors.size(); ++i) {
678 double d = dist_func(working_colors[i], centroid);
679 if (d < best_dist) {
680 best_dist = d;
681 first_idx = i;
682 }
683 }
684 centers[0] = working_colors[first_idx];
685
686 // Minimum distance from each point to the already-selected center set
687 std::vector<double> min_distances(working_colors.size(), std::numeric_limits<double>::max());
688
689 // 2. Greedily select the remaining K-1 centers: pick the point with the largest min_distance each time
690 for (std::size_t i = 1; i < k; ++i) {
691 const ColorDouble& last_center = centers[i - 1];
692
693 // Update each point's minimum distance with the newly added center
694 double farthest_dist = -1.0;
695 std::size_t farthest_idx = 0;
696 for (std::size_t c_idx = 0; c_idx < working_colors.size(); ++c_idx) {
697 double d = dist_func(working_colors[c_idx], last_center);
698 if (d < min_distances[c_idx]) {
699 min_distances[c_idx] = d;
700 }
701 if (min_distances[c_idx] > farthest_dist) {
702 farthest_dist = min_distances[c_idx];
703 farthest_idx = c_idx;
704 }
705 }
706
707 centers[i] = working_colors[farthest_idx];
708 }
709
710 return centers;
711}

Referenced by kmeans_core().

Here is the caller graph for this function:

◆ get_angle_between_vectors()

double Slic3r::tex2color::color_utils::get_angle_between_vectors ( const CGALKernel::Vector_3 & v1,
const CGALKernel::Vector_3 & v2 )
static
116 {
117 CGALKernel::Vector_3 dir1{v1}, dir2{v2};
118 normalize(dir1);
119 normalize(dir2);
120 double product_dot = dir1.x() * dir2.x() + dir1.y() * dir2.y() + dir1.z() * dir2.z();
121 if (product_dot > 1.0 - EPSILON) {
122 return 0.0;
123 } else if (product_dot < -1.0 + EPSILON) {
124 return 180.0;
125 }
126 return std::acos(product_dot) / M_PI * 180.0;
127}
#define EPSILON
Definition ColorUtils.cpp:64
static void normalize(CGALKernel::Vector_3 &vec)
Definition ColorUtils.cpp:109

References EPSILON, M_PI, and normalize().

Referenced by smooth_region_geom_boundary().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_components()

bool Slic3r::tex2color::color_utils::get_components ( const TriMesh & mesh,
const std::vector< Vec2f > & vertex_uvs,
std::vector< TriMesh > & component_meshes,
std::vector< std::vector< Vec2f > > & component_vertex_uvs )

Split the mesh into connected components.

Based on face connectivity, the mesh is split into independent components, each forming a standalone mesh. Texture coordinates for each component are preserved.

Parameters
[in]meshInput mesh.
[in]vertex_uvsVertex texture coordinates.
[out]component_meshesOutput list of component meshes.
[out]component_vertex_uvsOutput list of texture coordinates per component.
Returns
true on success, false on failure.
1388 {
1389 component_meshes.clear();
1390 component_vertex_uvs.clear();
1391
1392 if (bbs_mesh.vertices.size() != bbs_vertex_uvs.size()) {
1393 BOOST_LOG_TRIVIAL(warning) << "Input mesh vertex count does not match texture coordinate count";
1394 return false;
1395 }
1396
1397 CGALMesh cgal_mesh;
1398 std::vector<Vec2f> cgal_vertex_uvs;
1399 if (!cgalutils::convert_trimesh_to_cgal(bbs_mesh, bbs_vertex_uvs, cgal_mesh, cgal_vertex_uvs)) {
1400 BOOST_LOG_TRIVIAL(warning) << "Mesh conversion failed";
1401 return false;
1402 }
1403
1404 auto face_groups = get_connected_face_groups(cgal_mesh);
1405
1406 for (const auto& faces : face_groups) {
1407 std::unordered_map<CGAL::SM_Vertex_index, std::size_t> map_cgal_vtx_to_bbs_vtx;
1408 TriVertices bbs_vertices;
1409 TriFaces bbs_faces;
1410 std::vector<Vec2f> bbs_vertex_uvs;
1411
1412 for (auto cgal_face : faces) {
1413 TriFace bbs_face;
1414 std::size_t face_vid = 0;
1415 for (auto cgal_vtx : cgal_mesh.vertices_around_face(cgal_mesh.halfedge(cgal_face))) {
1416 if (!map_cgal_vtx_to_bbs_vtx.count(cgal_vtx)) {
1417 map_cgal_vtx_to_bbs_vtx[cgal_vtx] = bbs_vertices.size();
1418 const auto& cgal_point = cgal_mesh.point(cgal_vtx);
1419 bbs_vertices.push_back(TriVertex(cgal_point.x(), cgal_point.y(), cgal_point.z()));
1420 bbs_vertex_uvs.push_back(cgal_vertex_uvs[cgal_vtx]);
1421 }
1422 bbs_face[face_vid] = map_cgal_vtx_to_bbs_vtx[cgal_vtx];
1423 ++face_vid;
1424 }
1425 bbs_faces.push_back(std::move(bbs_face));
1426 }
1427
1428 component_meshes.push_back(TriMesh(bbs_faces, bbs_vertices));
1429 component_vertex_uvs.push_back(std::move(bbs_vertex_uvs));
1430 }
1431
1432 return true;
1433}
CGAL::Surface_mesh< Kernel::Point_3 > CGALMesh
Definition CgalUtils.hpp:20
bool convert_trimesh_to_cgal(const TriMesh &mesh, CGALMesh &cgal_mesh)
Definition CgalUtils.hpp:141
static std::vector< std::vector< CGAL::SM_Face_index > > get_connected_face_groups(const CGALMesh &mesh)
Definition ColorUtils.cpp:1359
stl_triangle_vertex_indices TriFace
Definition TriMesh.hpp:12
std::vector< stl_triangle_vertex_indices > TriFaces
Definition TriMesh.hpp:13
std::vector< stl_vertex > TriVertices
Definition TriMesh.hpp:11
stl_vertex TriVertex
Definition TriMesh.hpp:10

References Slic3r::tex2color::cgalutils::convert_trimesh_to_cgal(), get_components(), and get_connected_face_groups().

Referenced by get_components().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_connected_face_groups()

std::vector< std::vector< CGAL::SM_Face_index > > Slic3r::tex2color::color_utils::get_connected_face_groups ( const CGALMesh & mesh)
static
1359 {
1360 std::vector<std::vector<CGAL::SM_Face_index>> face_groups;
1361 std::unordered_set<CGAL::SM_Face_index> visited_faces;
1362 for (auto src_face : mesh.faces()) {
1363 if (visited_faces.count(src_face)) {
1364 continue;
1365 }
1366 std::vector<CGAL::SM_Face_index> face_group;
1367 std::queue<CGAL::SM_Face_index> que;
1368 que.push(src_face);
1369 visited_faces.insert(src_face);
1370 while (!que.empty()) {
1371 auto curr_face = que.front();
1372 que.pop();
1373 face_group.push_back(curr_face);
1374 for (auto adj_face : mesh.faces_around_face(mesh.halfedge(curr_face))) {
1375 if (!mesh.is_valid(adj_face) || mesh.is_removed(adj_face) || visited_faces.count(adj_face)) {
1376 continue;
1377 }
1378 que.push(adj_face);
1379 visited_faces.insert(adj_face);
1380 }
1381 }
1382 face_groups.push_back(std::move(face_group));
1383 }
1384 return face_groups;
1385}

References get_connected_face_groups().

Referenced by get_components(), and get_connected_face_groups().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ is_closed()

bool Slic3r::tex2color::color_utils::is_closed ( const TriMesh & tri_mesh)

Check whether the mesh is closed (watertight).

A mesh is closed if it has no boundary edges, i.e. every edge is shared by exactly two faces.

Parameters
[in]tri_meshInput mesh.
Returns
true if the mesh is closed, false if it has boundary edges.
855 {
856 CGALMesh cgal_mesh;
857 cgalutils::convert_trimesh_to_cgal(bbs_mesh, cgal_mesh);
858
859#ifdef DEBUG_FLAG
860 std::size_t border_edges_count = 0;
861 std::size_t edges_count = 0;
862 for (auto edge : cgal_mesh.edges()) {
863 if (cgal_mesh.is_border(edge)) {
864 ++border_edges_count;
865 }
866 ++edges_count;
867 }
868
869 BOOST_LOG_TRIVIAL(debug) << "border edges count = " << border_edges_count << "\n";
870 BOOST_LOG_TRIVIAL(debug) << "edges count = " << edges_count << "\n";
871
872 std::size_t border_faces_count = 0;
873 std::size_t faces_count = 0;
874 for (auto face : cgal_mesh.faces()) {
875 for (auto halfedge : cgal_mesh.halfedges_around_face(cgal_mesh.halfedge(face))) {
876 auto edge = cgal_mesh.edge(halfedge);
877 if (cgal_mesh.is_border(edge)) {
878 ++border_faces_count;
879 break;
880 }
881 }
882 ++faces_count;
883 }
884
885 BOOST_LOG_TRIVIAL(debug) << "border faces count = " << border_faces_count << "\n";
886 BOOST_LOG_TRIVIAL(debug) << "faces count = " << faces_count << "\n";
887
888 BOOST_LOG_TRIVIAL(debug) << "vertices count = " << cgal_mesh.number_of_vertices() << "\n";
889 std::size_t num_of_components = 0;
890 std::unordered_set<CGAL::SM_Face_index> visited_faces;
891 for (auto src_face : cgal_mesh.faces()) {
892 if (visited_faces.count(src_face)) {
893 continue;
894 }
895 ++num_of_components;
896 std::queue<CGAL::SM_Face_index> que;
897 que.push(src_face);
898 visited_faces.insert(src_face);
899 while (!que.empty()) {
900 auto curr_face = que.front();
901 que.pop();
902 for (auto adj_face : cgal_mesh.faces_around_face(cgal_mesh.halfedge(curr_face))) {
903 if (!cgal_mesh.is_valid(adj_face) || cgal_mesh.is_removed(adj_face) || visited_faces.count(adj_face)) {
904 continue;
905 }
906 que.push(adj_face);
907 visited_faces.insert(adj_face);
908 }
909 }
910 }
911 BOOST_LOG_TRIVIAL(debug) << "components count = " << num_of_components << "\n";
912#endif // DEBUG_FLAG
913
914 return CGAL::is_closed(cgal_mesh);
915}

References Slic3r::tex2color::cgalutils::convert_trimesh_to_cgal().

Referenced by Slic3r::tex2color::repair_cluster_smooth().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ kmeans_core()

std::vector< ColorDouble > Slic3r::tex2color::color_utils::kmeans_core ( const std::vector< ColorDouble > & working_colors,
std::size_t k,
std::size_t max_iter,
WorkingDistFunc dist_func,
const std::function< bool()> & cancel_cb = nullptr )
static
989 {
990 std::vector<ColorDouble> centers = farthest_point_sampling_init(working_colors, k, dist_func);
991 std::vector<std::size_t> assignments(working_colors.size());
992
993 for (std::size_t iter = 0; iter < max_iter; ++iter) {
994 if (cancel_cb && cancel_cb()) return centers;
995 std::atomic<bool> changed(false);
996
997 tbb::parallel_for(tbb::blocked_range<std::size_t>(0, working_colors.size()), [&](const tbb::blocked_range<size_t>& range) {
998 for (std::size_t i = range.begin(); i < range.end(); ++i) {
999 double min_dist = std::numeric_limits<double>::max();
1000 std::size_t best_cluster = 0;
1001
1002 for (std::size_t j = 0; j < k; ++j) {
1003 double d = dist_func(working_colors[i], centers[j]);
1004 if (d < min_dist) {
1005 min_dist = d;
1006 best_cluster = j;
1007 }
1008 }
1009
1010 if (assignments[i] != best_cluster) {
1011 changed.store(true, std::memory_order_relaxed);
1012 assignments[i] = best_cluster;
1013 }
1014 }
1015 });
1016
1017 if (!changed.load()) {
1018 break;
1019 }
1020
1021 std::vector<ColorDouble> new_centers(k, {0.0, 0.0, 0.0});
1022 std::vector<std::size_t> counts(k, 0);
1023
1024 for (std::size_t i = 0; i < working_colors.size(); ++i) {
1025 std::size_t cluster_id = assignments[i];
1026 ++counts[cluster_id];
1027 new_centers[cluster_id][0] += working_colors[i][0];
1028 new_centers[cluster_id][1] += working_colors[i][1];
1029 new_centers[cluster_id][2] += working_colors[i][2];
1030 }
1031
1032 for (std::size_t i = 0; i < k; ++i) {
1033 if (counts[i] == 0) {
1034 double max_min_dist = -1.0;
1035 std::size_t best_idx = 0;
1036 for (std::size_t p = 0; p < working_colors.size(); ++p) {
1037 double nearest = std::numeric_limits<double>::max();
1038 for (std::size_t c = 0; c < k; ++c) {
1039 if (c == i || counts[c] == 0) {
1040 continue;
1041 }
1042 double d = dist_func(working_colors[p], centers[c]);
1043 if (d < nearest) {
1044 nearest = d;
1045 }
1046 }
1047 if (nearest > max_min_dist) {
1048 max_min_dist = nearest;
1049 best_idx = p;
1050 }
1051 }
1052 centers[i] = working_colors[best_idx];
1053 } else {
1054 centers[i][0] = new_centers[i][0] / counts[i];
1055 centers[i][1] = new_centers[i][1] / counts[i];
1056 centers[i][2] = new_centers[i][2] / counts[i];
1057 }
1058 }
1059 }
1060
1061 return centers;
1062}
static std::vector< ColorDouble > farthest_point_sampling_init(const std::vector< ColorDouble > &working_colors, std::size_t k, WorkingDistFunc dist_func)
Definition ColorUtils.cpp:660

References farthest_point_sampling_init(), and Slic3r::range().

Referenced by cluster_k_means().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ mesh_cluster()

bool Slic3r::tex2color::color_utils::mesh_cluster ( const TriMesh & mesh,
const std::vector< RGB > & cluster_centers,
std::vector< RGB > & map_face_to_rgb,
std::vector< std::size_t > & map_face_to_cluster_id )

Cluster mesh face colors based on given cluster centers.

Parameters
[in]meshInput mesh.
[in]cluster_centersCluster-center RGB colors.
[in,out]map_face_to_rgbRGB color per face; updated to the nearest cluster center after clustering.
[out]map_face_to_cluster_idCluster-center ID per face; updated to the nearest cluster center ID.
Returns
true on success, false on failure.
1453 {
1454 if (cluster_centers.empty()) {
1455 BOOST_LOG_TRIVIAL(warning) << "No cluster centers provided";
1456 return false;
1457 }
1458
1459 CGALMesh mesh;
1460 cgalutils::convert_trimesh_to_cgal(bbs_mesh, mesh);
1461 if (mesh.number_of_faces() != bbs_mesh.indices.size()) {
1462 BOOST_LOG_TRIVIAL(warning) << "CGAL mesh face count does not match BBS mesh face count";
1463 return false;
1464 }
1465 if (mesh.number_of_faces() != map_face_to_rgb.size()) {
1466 BOOST_LOG_TRIVIAL(warning) << "CGAL mesh face count does not match RGB count";
1467 return false;
1468 }
1469
1470 if (cluster_centers.size() == 1) {
1471 std::fill(map_face_to_rgb.begin(), map_face_to_rgb.end(), cluster_centers[0]);
1472#ifdef DEBUG_FLAG
1473 BOOST_LOG_TRIVIAL(debug) << "input cluster centers' size is 1, so we set all face RGB as same with it and return.\n";
1474#endif
1475 return true;
1476 }
1477
1478 std::vector<double> map_face_to_area(mesh.number_of_faces());
1479 tbb::parallel_for(tbb::blocked_range<std::size_t>(0, mesh.number_of_faces()), [&](const tbb::blocked_range<size_t>& range) {
1480 for (std::size_t fid = range.begin(); fid < range.end(); ++fid) {
1481 CGAL::SM_Face_index face(fid);
1482 map_face_to_area[fid] = std::max(PMP::face_area(face, mesh), EPSILON);
1483 }
1484 });
1485
1486 // Step 1: Identify faces that definitely belong to a cluster center.
1487 // A face is definitively assigned when dist1 * absolute_difference_times < dist2 (nearest vs. second-nearest center).
1488 constexpr double absolute_difference_times = 1.5;
1489 // dE <= 1.0: imperceptible to the human eye, high-precision color matching
1490 // dE <= 2.0: slight difference, noticeable by experts; printing / image processing standard
1491 // dE <= 3.0: noticeable by ordinary observers; general quality control
1492 constexpr double difference_epsilon = 3.0;
1493 constexpr std::size_t invalid_cluster_id = std::numeric_limits<std::size_t>::max();
1494 map_face_to_cluster_id.resize(mesh.number_of_faces(), invalid_cluster_id);
1495 std::vector<std::vector<std::pair<double, std::size_t>>> map_face_to_dists(mesh.number_of_faces(),
1496 std::vector<std::pair<double, std::size_t>>(cluster_centers.size()));
1497 tbb::parallel_for(tbb::blocked_range<std::size_t>(0, mesh.number_of_faces()), [&](const tbb::blocked_range<size_t>& range) {
1498 for (std::size_t fid = range.begin(); fid < range.end(); ++fid) {
1499 CGAL::SM_Face_index face(fid);
1500 std::vector<std::pair<double, std::size_t>>& dist_and_cid_vec = map_face_to_dists[fid];
1501 for (std::size_t cluster_id = 0; cluster_id < cluster_centers.size(); ++cluster_id) {
1502 double dist = calc_rgb_color_difference_by_ciede2000(map_face_to_rgb[fid], cluster_centers[cluster_id]);
1503 //dist_and_cid_vec.emplace_back(dist, cluster_id);
1504 dist_and_cid_vec[cluster_id] = std::pair<double, std::size_t>{dist, cluster_id};
1505 }
1506 std::sort(dist_and_cid_vec.begin(), dist_and_cid_vec.end());
1507 if (dist_and_cid_vec[0].first < difference_epsilon || dist_and_cid_vec[0].first * absolute_difference_times < dist_and_cid_vec[1].first) {
1508 map_face_to_cluster_id[fid] = dist_and_cid_vec[0].second;
1509 }
1510 }
1511 });
1512
1513 std::unordered_set<std::size_t> unclusted_fids;
1514 for (std::size_t fid = 0; fid < mesh.number_of_faces(); ++fid) {
1515 if (map_face_to_cluster_id[fid] == invalid_cluster_id) {
1516 unclusted_fids.insert(fid);
1517 }
1518 }
1519
1520 auto convert_unclustered_to_clustered = [&](const std::unordered_set<std::size_t>& iter_clustered_fids) -> bool {
1521 if (iter_clustered_fids.empty()) {
1522 return false;
1523 }
1524 for (auto& fid : iter_clustered_fids) {
1525 unclusted_fids.erase(fid);
1526 }
1527 return true;
1528 };
1529
1530 // Step 2: Flood. Use faces computed in the previous step as seeds and propagate outward.
1531 while (!unclusted_fids.empty()) {
1532 bool changed = false;
1533 std::unordered_set<std::size_t> iter_clustered_fids;
1534 // If an uncolored face has an adjacent color whose count exceeds the sum of all other colors, assign that color
1535 for (auto fid : unclusted_fids) {
1536 CGAL::SM_Face_index face(fid);
1537 std::size_t count = 0;
1538 std::unordered_map<std::size_t, std::size_t> map_cluster_id_to_count;
1539 for (auto adj_face : mesh.faces_around_face(mesh.halfedge(face))) {
1540 if (!mesh.is_valid(adj_face) || mesh.is_removed(adj_face)) {
1541 continue;
1542 }
1543 ++count;
1544 ++map_cluster_id_to_count[map_face_to_cluster_id[adj_face]];
1545 }
1546 for (auto& [cluster_id, cnt] : map_cluster_id_to_count) {
1547 if (cluster_id != invalid_cluster_id && cnt * 2 > count) {
1548 map_face_to_cluster_id[fid] = cluster_id;
1549 iter_clustered_fids.insert(fid);
1550 break;
1551 }
1552 }
1553 }
1554 changed = convert_unclustered_to_clustered(iter_clustered_fids) || changed;
1555 iter_clustered_fids.clear();
1556
1557 // If a face's nearest cluster center (by color distance) happens to have an adjacent face, assign that color too
1558 for (auto fid : unclusted_fids) {
1559 CGAL::SM_Face_index face(fid);
1560 std::unordered_set<std::size_t> adj_cluster_ids;
1561 for (auto adj_face : mesh.faces_around_face(mesh.halfedge(face))) {
1562 if (!mesh.is_valid(adj_face) || mesh.is_removed(adj_face)) {
1563 continue;
1564 }
1565 if (map_face_to_cluster_id[adj_face] == invalid_cluster_id) {
1566 continue;
1567 }
1568 adj_cluster_ids.insert(map_face_to_cluster_id[adj_face]);
1569 }
1570 if (adj_cluster_ids.count(map_face_to_dists[fid].front().second)) {
1571 map_face_to_cluster_id[fid] = map_face_to_dists[fid].front().second;
1572 iter_clustered_fids.insert(fid);
1573 }
1574 }
1575 changed = convert_unclustered_to_clustered(iter_clustered_fids) || changed;
1576 iter_clustered_fids.clear();
1577
1578 // If no face colors were modified in this iteration, stop
1579 if (!changed) {
1580 break;
1581 }
1582 }
1583
1584 // Step 3: Handle remaining unclustered faces (run after the above operations complete)
1585 constexpr bool use_average_color = true;
1586 for (auto src_fid : std::vector<std::size_t>(unclusted_fids.begin(), unclusted_fids.end())) {
1587 if (!unclusted_fids.count(src_fid)) {
1588 continue;
1589 }
1590 // Compute connected unclustered faces
1591 std::queue<std::size_t> que;
1592 std::unordered_set<std::size_t> connected_unclustered_faces;
1593 que.push(src_fid);
1594 connected_unclustered_faces.insert(src_fid);
1595 double sum_r = 0, sum_g = 0, sum_b = 0;
1596 double sum_area = 0.0;
1597 std::unordered_map<std::size_t, double> map_cluster_id_to_adj_area;
1598 while (!que.empty()) {
1599 auto curr_fid = que.front();
1600 que.pop();
1601 sum_r += map_face_to_area[curr_fid] * map_face_to_rgb[curr_fid][0];
1602 sum_g += map_face_to_area[curr_fid] * map_face_to_rgb[curr_fid][1];
1603 sum_b += map_face_to_area[curr_fid] * map_face_to_rgb[curr_fid][2];
1604 sum_area += map_face_to_area[curr_fid];
1605 CGAL::SM_Face_index curr_face(curr_fid);
1606 for (auto adj_face : mesh.faces_around_face(mesh.halfedge(curr_face))) {
1607 if (!mesh.is_valid(adj_face) || mesh.is_removed(adj_face)) { // Invalid face
1608 continue;
1609 }
1610 if (map_face_to_cluster_id[adj_face] != invalid_cluster_id) {
1611 map_cluster_id_to_adj_area[map_face_to_cluster_id[adj_face]] += map_face_to_area[adj_face];
1612 } else {
1613 if (!connected_unclustered_faces.count(adj_face)) { // Already clustered or already recorded
1614 que.push(adj_face);
1615 connected_unclustered_faces.insert(adj_face);
1616 }
1617 }
1618 }
1619 }
1620 std::size_t matched_cluster_id = invalid_cluster_id;
1621 if (use_average_color) {
1622 // Use average color
1623 RGB average_color{static_cast<std::size_t>(sum_r / sum_area), static_cast<std::size_t>(sum_g / sum_area),
1624 static_cast<std::size_t>(sum_b / sum_area)};
1625 double min_dist = std::numeric_limits<double>::max();
1626 for (auto& [cluster_id, area] : map_cluster_id_to_adj_area) {
1627 double dist = calc_rgb_color_difference_by_ciede2000(average_color, cluster_centers[cluster_id]);
1628 if (dist < min_dist) {
1629 min_dist = dist;
1630 matched_cluster_id = cluster_id;
1631 }
1632 }
1633 } else {
1634 // Use adjacent area
1635 double adj_max_area = 0.0;
1636 for (auto& [cluster_id, area] : map_cluster_id_to_adj_area) {
1637 if (area > adj_max_area) {
1638 adj_max_area = area;
1639 matched_cluster_id = cluster_id;
1640 }
1641 }
1642 }
1643 for (auto fid : connected_unclustered_faces) {
1644 map_face_to_cluster_id[fid] = matched_cluster_id;
1645 unclusted_fids.erase(fid);
1646 }
1647 }
1648
1649 // Convert cluster center IDs to colors
1650 for (std::size_t fid = 0; fid < mesh.number_of_faces(); ++fid) {
1651 if (map_face_to_cluster_id[fid] == invalid_cluster_id) {
1652 map_face_to_rgb[fid] = cluster_centers[0];
1653 map_face_to_cluster_id[fid] = 0; // Prevent out-of-bounds errors when using cluster_id later
1654 } else {
1655 map_face_to_rgb[fid] = cluster_centers[map_face_to_cluster_id[fid]];
1656 }
1657 }
1658
1659 return true;
1660}
static unsigned int count
Definition AuxiliaryDataViewModel.cpp:246
std::array< std::size_t, 3 > RGB
Definition ColorUtils.hpp:18
double area(const ExPolygon &poly)
Definition ExPolygon.hpp:511

References Slic3r::tex2color::cgalutils::convert_trimesh_to_cgal(), mesh_cluster(), and Slic3r::range().

Referenced by mesh_cluster().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ normalize()

void Slic3r::tex2color::color_utils::normalize ( CGALKernel::Vector_3 & vec)
static
109 {
110 double squared_length = vec.squared_length();
111 if (squared_length > EPSILON) {
112 vec /= sqrt(squared_length);
113 }
114}

References EPSILON.

Referenced by get_angle_between_vectors().

Here is the caller graph for this function:

◆ remesh_mesh()

bool Slic3r::tex2color::color_utils::remesh_mesh ( TriMesh & mesh,
std::vector< std::size_t > & face_labels,
double target_edge_length_ratio )

Remesh the mesh while preserving color boundaries.

Performs isotropic remeshing while protecting color boundaries. Edges whose two adjacent faces have different colors are marked as feature edges and will not be modified.

Parameters
[in,out]meshInput mesh; modified in-place after remeshing.
[in,out]face_labelsFace color labels; updated to match the new mesh.
[in]target_edge_length_ratioRatio of target average edge length to input average edge length; >1 simplifies, <1 refines.
Returns
true on success, false on failure.
713 {
714 if (face_labels.size() != bbs_mesh.indices.size()) {
715 BOOST_LOG_TRIVIAL(warning) << "Input mesh face count does not match label count";
716 return false;
717 }
718
719 // Back up face labels for recovery after remeshing.
720 std::vector<std::size_t> face_labels_of_original_mesh(face_labels);
721
722 CGALMesh cgal_mesh;
723 cgalutils::convert_trimesh_to_cgal(bbs_mesh, cgal_mesh);
724
725 // AABBTreeIndirect references vertices/faces externally, so snapshot the
726 // pre-remesh geometry by moving it out of bbs_mesh (which is overwritten
727 // below with the post-remesh mesh). std::move on std::vector is O(1).
728 TriVertices old_vertices = std::move(bbs_mesh.vertices);
729 TriFaces old_indices = std::move(bbs_mesh.indices);
730 auto original_mesh_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(old_vertices, old_indices);
731
732 std::unordered_set<CGAL::SM_Edge_index> feature_edges;
733 std::unordered_set<CGAL::SM_Vertex_index> feature_vertices;
734 CGALMesh::Property_map<CGALMesh::Edge_index, bool> constrained_edges =
735 cgal_mesh.add_property_map<CGALMesh::Edge_index, bool>("constrained_edges", false).first;
736 CGALMesh::Property_map<CGALMesh::Vertex_index, bool> constrained_vertices =
737 cgal_mesh.add_property_map<CGALMesh::Vertex_index, bool>("constrained_vertices", false).first;
738
739 // An edge is considered a geometric feature edge if its dihedral angle is less than 135 degrees (loose threshold)
740 constexpr double feature_angle = 135;
741
742 auto is_feature_edge = [&](CGAL::SM_Edge_index edge) -> bool {
743 if (cgal_mesh.is_border(edge)) {
744 return true;
745 }
746 auto halfedge_1 = cgal_mesh.halfedge(edge);
747 auto halfedge_2 = cgal_mesh.opposite(halfedge_1);
748 auto face_1 = cgal_mesh.face(halfedge_1);
749 auto face_2 = cgal_mesh.face(halfedge_2);
750 if (face_labels[face_1] != face_labels[face_2]) {
751 // Boundary between different color regions; treated as a feature edge
752 return true;
753 }
754 // TODO: CGAL remeshing tends to crash when too many constrained edges are added; needs handling
755 //auto normal_1 = PMP::compute_face_normal(face_1, cgal_mesh);
756 //auto normal_2 = PMP::compute_face_normal(face_2, cgal_mesh);
757 //double angle = 180 - get_angle_between_vectors(normal_1, normal_2);
758 //BOOST_LOG_TRIVIAL(debug) << "end.\n";
759 //return angle > feature_angle;
760 return false;
761 };
762
763 for (auto edge : cgal_mesh.edges()) {
764 if (is_feature_edge(edge)) {
765 feature_edges.insert(edge);
766 feature_vertices.insert(cgal_mesh.source(cgal_mesh.halfedge(edge)));
767 feature_vertices.insert(cgal_mesh.target(cgal_mesh.halfedge(edge)));
768 constrained_edges[edge] = true;
769 constrained_vertices[cgal_mesh.source(cgal_mesh.halfedge(edge))] = true;
770 constrained_vertices[cgal_mesh.target(cgal_mesh.halfedge(edge))] = true;
771 }
772 }
773
774#ifdef DEBUG_FLAG
775 BOOST_LOG_TRIVIAL(debug) << "remesh_mesh: feature_edges.size() = " << feature_edges.size() << ".\n";
776
777 std::vector<std::vector<CGALKernel::Point_3>> polylines;
778 for (auto edge : feature_edges) {
779 auto src_vtx = cgal_mesh.source(cgal_mesh.halfedge(edge));
780 auto trg_vtx = cgal_mesh.target(cgal_mesh.halfedge(edge));
781 polylines.push_back({cgal_mesh.point(src_vtx), cgal_mesh.point(trg_vtx)});
782 }
783 save_polylines("ColorUtils_remesh_feature_lines.obj", polylines);
784#endif // DEBUG_FLAG
785
786 std::size_t iters = 5;
787#ifdef DEBUG_FLAG
788 BOOST_LOG_TRIVIAL(debug) << "PMP::isotropic_remeshing start...\n";
789#endif // DEBUG_FLAG
790 // TODO: CGAL remeshing preserves geometric boundaries but does not maintain face labels well; colors need to be recomputed
791 PMP::isotropic_remeshing(cgal_mesh.faces(), target_edge_length_ratio * detail::average_edge_length_impl(cgal_mesh), cgal_mesh,
792 CGAL::parameters::number_of_iterations(iters)
793 .protect_constraints(true)
794 .edge_is_constrained_map(constrained_edges)
795 .vertex_is_constrained_map(constrained_vertices)
796 .collapse_constraints(true));
797#ifdef DEBUG_FLAG
798 BOOST_LOG_TRIVIAL(debug) << "PMP::isotropic_remeshing finshed...\n";
799#endif // DEBUG_FLAG
800 if (PMP::does_self_intersect(cgal_mesh)) {
801 PMP::experimental::remove_self_intersections(cgal_mesh);
802 }
803
804 bbs_mesh.clear();
805
806 std::unordered_map<CGAL::SM_Vertex_index, std::size_t> map_cgal_vtx_to_bbs_vtx;
807 TriVertices bbs_vertices;
808 TriFaces bbs_faces;
809 face_labels.clear();
810 face_labels.reserve(cgal_mesh.number_of_faces());
811
812 for (const auto& cgal_vtx : cgal_mesh.vertices()) {
813 if (!cgal_mesh.is_valid(cgal_vtx) || cgal_mesh.is_removed(cgal_vtx) || cgal_mesh.is_isolated(cgal_vtx)) {
814 continue;
815 }
816 if (!map_cgal_vtx_to_bbs_vtx.count(cgal_vtx)) {
817 map_cgal_vtx_to_bbs_vtx[cgal_vtx] = bbs_vertices.size();
818 const auto& cgal_point = cgal_mesh.point(cgal_vtx);
819 bbs_vertices.emplace_back(TriVertex(cgal_point.x(), cgal_point.y(), cgal_point.z()));
820 }
821 }
822
823 for (const auto& cgal_face : cgal_mesh.faces()) {
824 if (!cgal_mesh.is_valid(cgal_face) || cgal_mesh.is_removed(cgal_face)) {
825 continue;
826 }
827 TriFace bbs_face;
828 std::size_t face_vid = 0;
829 for (auto cgal_vtx : cgal_mesh.vertices_around_face(cgal_mesh.halfedge(cgal_face))) {
830 do { if (!(map_cgal_vtx_to_bbs_vtx.count(cgal_vtx))) { BOOST_LOG_TRIVIAL(warning) << "CGAL mesh contains a face with an invalid vertex"; return false; } } while(0);
831 bbs_face[face_vid] = map_cgal_vtx_to_bbs_vtx[cgal_vtx];
832 ++face_vid;
833 }
834 bbs_faces.push_back(std::move(bbs_face));
835 }
836
837 bbs_mesh = TriMesh(bbs_faces, bbs_vertices);
838
839 face_labels.resize(bbs_mesh.indices.size());
840 tbb::parallel_for(tbb::blocked_range<std::size_t>(0, bbs_mesh.indices.size()), [&](const tbb::blocked_range<size_t>& range) {
841 for (std::size_t fid = range.begin(); fid < range.end(); ++fid) {
842 auto& face = bbs_mesh.indices[fid];
843 Vec3f face_centroid = (bbs_mesh.vertices[face[0]] + bbs_mesh.vertices[face[1]] + bbs_mesh.vertices[face[2]]) / 3.0;
844 size_t hit_idx = 0;
845 Vec3f closest;
846 AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
847 old_vertices, old_indices, original_mesh_tree, face_centroid, hit_idx, closest);
848 face_labels[fid] = face_labels_of_original_mesh[hit_idx];
849 }
850 });
851
852 return true;
853}
Tree< 3, typename VertexType::Scalar > build_aabb_tree_over_indexed_triangle_set(const std::vector< VertexType > &vertices, const std::vector< IndexedFaceType > &faces, const typename VertexType::Scalar eps=0)
Definition AABBTreeIndirect.hpp:677
double average_edge_length_impl(const Mesh &m)
Definition ColorUtils.cpp:82
static bool save_polylines(const std::string &file_name, const std::vector< std::vector< CGALKernel::Point_3 > > &polylines)
Definition ColorUtils.cpp:164

References Slic3r::tex2color::color_utils::detail::average_edge_length_impl(), Slic3r::AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(), Slic3r::tex2color::cgalutils::convert_trimesh_to_cgal(), Slic3r::range(), and save_polylines().

Here is the call graph for this function:

◆ save_polylines()

bool Slic3r::tex2color::color_utils::save_polylines ( const std::string & file_name,
const std::vector< std::vector< CGALKernel::Point_3 > > & polylines )
static
164 {
165 std::vector<CGALKernel::Point_3> points;
166 std::vector<std::pair<std::size_t, std::size_t>> lines;
167 for (auto& polyline : polylines) {
168 std::size_t begin_pt_idx = points.size();
169 for (auto& pt : polyline) {
170 points.push_back(pt);
171 }
172 for (std::size_t i = 1; i < polyline.size(); ++i) {
173 lines.emplace_back(begin_pt_idx + i, begin_pt_idx + i + 1); // obj is begin at 1
174 }
175 }
176 std::ofstream output_file(file_name, std::ios::out);
177 for (auto& point : points) {
178 output_file << "v " << point[0] << " " << point[1] << " " << point[2] << "\n";
179 }
180 for (auto& line : lines) {
181 output_file << "l " << line.first << " " << line.second << "\n";
182 }
183 output_file.close();
184 return true;
185}

Referenced by remesh_mesh().

Here is the caller graph for this function:

◆ smooth_region() [1/2]

bool Slic3r::tex2color::color_utils::smooth_region ( TriMesh & tri_mesh,
std::vector< std::array< std::size_t, 3 > > & face_labels,
const SmoothParameters & smooth_parameters = SmoothParameters() )

Smooth region boundaries (RGB color labels).

Applies topological smoothing (label reassignment) and geometric smoothing (boundary vertex relocation).

Parameters
[in,out]tri_meshInput mesh; modified in-place after smoothing.
[in,out]face_labelsFace color labels (RGB format); updated after smoothing.
[in]smooth_parametersSmoothing control parameters.
Returns
true on success, false on failure.
941 {
942 // Convert colors to labels
943 std::size_t label_next = 0;
944 std::vector<std::size_t> face_labels;
945 face_labels.reserve(face_colors.size());
946 std::map<std::array<std::size_t, 3>, std::size_t> map_color_to_label;
947 std::unordered_map<std::size_t, std::array<std::size_t, 3>> map_label_to_color;
948 for (auto& color : face_colors) {
949 if (!map_color_to_label.count(color)) {
950 map_color_to_label[color] = label_next;
951 map_label_to_color[label_next] = color;
952 ++label_next;
953 }
954 face_labels.push_back(map_color_to_label[color]);
955 }
956
957 if (!smooth_region_labels(tri_mesh, face_labels, smooth_parameters))
958 return false;
959
960 // Convert labels back to colors
961 for (std::size_t fid = 0; fid < face_labels.size(); ++fid) {
962 face_colors[fid] = map_label_to_color[face_labels[fid]];
963 }
964
965 return true;
966}
static bool smooth_region_labels(TriMesh &tri_mesh, std::vector< std::size_t > &face_labels, const SmoothParameters &smooth_parameters)
Definition ColorUtils.cpp:917

References smooth_region_labels().

Here is the call graph for this function:

◆ smooth_region() [2/2]

bool Slic3r::tex2color::color_utils::smooth_region ( TriMesh & tri_mesh,
std::vector< std::size_t > & face_labels,
const SmoothParameters & smooth_parameters = SmoothParameters() )

Smooth region boundaries (integer labels).

Applies topological smoothing (label reassignment) and geometric smoothing (boundary vertex relocation).

Parameters
[in,out]tri_meshInput mesh; modified in-place after smoothing.
[in,out]face_labelsInteger face labels; updated after smoothing.
[in]smooth_parametersSmoothing control parameters.
Returns
true on success, false on failure.
968 {
969 return smooth_region_labels(tri_mesh, face_labels, smooth_parameters);
970}

References smooth_region_labels().

Here is the call graph for this function:

◆ smooth_region_geom_boundary()

bool Slic3r::tex2color::color_utils::smooth_region_geom_boundary ( CGALMesh & mesh,
std::vector< std::size_t > & face_labels,
const SmoothParameters & smooth_parameters )
static
225 {
226 // 1. extract boundary vertices and make polines
227 std::unordered_map<CGAL::SM_Vertex_index, std::size_t> map_vtx_to_degree;
228 std::unordered_set<CGAL::SM_Edge_index> segment_boundary_edges;
229 std::unordered_set<CGAL::SM_Edge_index> feature_edges;
230 std::unordered_set<CGAL::SM_Vertex_index> feature_vertices;
231 constexpr double feature_angle = 45;
232 for (const auto& edge : mesh.edges()) {
233 if (!mesh.is_valid(edge) || mesh.is_border(edge)) {
234 continue;
235 }
236 auto source = mesh.source(mesh.halfedge(edge));
237 auto target = mesh.target(mesh.halfedge(edge));
238 auto face_1 = mesh.face(mesh.halfedge(edge));
239 auto face_2 = mesh.face(mesh.opposite(mesh.halfedge(edge)));
240 auto normal_1 = PMP::compute_face_normal(face_1, mesh);
241 auto normal_2 = PMP::compute_face_normal(face_2, mesh);
242 double angle = get_angle_between_vectors(normal_1, normal_2);
243 if (angle > feature_angle) {
244 feature_edges.insert(edge);
245 feature_vertices.insert(source);
246 feature_vertices.insert(target);
247 }
248
249 if (face_labels[face_1] == face_labels[face_2]) {
250 continue;
251 }
252
253 segment_boundary_edges.insert(edge);
254 ++map_vtx_to_degree[source];
255 ++map_vtx_to_degree[target];
256 }
257
258 // 2. smooth each polyline
259 std::vector<std::vector<CGAL::SM_Vertex_index>> polylines;
260 std::unordered_set<CGAL::SM_Edge_index> visited_edges;
261
262 std::function<void(std::vector<CGAL::SM_Vertex_index>&)> trace_polyline = [&](std::vector<CGAL::SM_Vertex_index>& polyline) -> void {
263 if (polyline.empty()) {
264 return;
265 }
266 CGAL::SM_Vertex_index curr_vtx = polyline.back();
267 if (map_vtx_to_degree[curr_vtx] != 2) {
268 return;
269 }
270 for (const auto& halfedge : mesh.halfedges_around_target(mesh.halfedge(curr_vtx))) {
271 CGAL::SM_Edge_index edge = mesh.edge(halfedge);
272 if (visited_edges.count(edge) || !segment_boundary_edges.count(edge)) {
273 continue;
274 }
275 visited_edges.insert(edge);
276 CGAL::SM_Vertex_index adj_vtx = mesh.source(halfedge);
277 if (!map_vtx_to_degree.count(adj_vtx)) {
278 continue;
279 }
280 polyline.push_back(adj_vtx);
281 return trace_polyline(polyline);
282 }
283 };
284
285 // 2.1. open polyline: from T nodes search other 2-degree nodes
286 for (auto& [src_vtx, degree] : map_vtx_to_degree) {
287 if (degree == 2) {
288 continue;
289 }
290 for (auto& src_halfedge : mesh.halfedges_around_target(mesh.halfedge(src_vtx))) {
291 CGAL::SM_Edge_index src_edge = mesh.edge(src_halfedge);
292 if (visited_edges.count(src_edge) || !segment_boundary_edges.count(src_edge)) {
293 continue;
294 }
295 visited_edges.insert(src_edge);
296 CGAL::SM_Vertex_index adj_vtx = mesh.source(src_halfedge);
297 if (!map_vtx_to_degree.count(adj_vtx)) {
298 continue;
299 }
300 std::vector<CGAL::SM_Vertex_index> polyline{src_vtx, adj_vtx};
301 trace_polyline(polyline);
302 polylines.push_back(std::move(polyline));
303 }
304 }
305
306 // 2.2. closed polylines
307 for (auto edge : segment_boundary_edges) {
308 if (visited_edges.count(edge)) {
309 continue;
310 }
311 visited_edges.insert(edge);
312 CGAL::SM_Halfedge_index halfedge = mesh.halfedge(edge);
313 std::vector<CGAL::SM_Vertex_index> polyline{mesh.source(halfedge), mesh.target(halfedge)};
314 trace_polyline(polyline);
315 if (polyline.front() != polyline.back()) {
316 std::cerr << "[Error]: loop polyline but not closed!!!\n";
317 }
318 polylines.push_back(std::move(polyline));
319 }
320
321 // 3. smooth boundary
322 Tree boundary_tree(mesh.faces().begin(), mesh.faces().end(), mesh);
323 boundary_tree.accelerate_distance_queries();
324
325 constexpr std::size_t max_iters = 5;
326 const double smooth_weight = smooth_parameters.smooth_weight; // Controls smoothing intensity; larger values produce smoother results. Range: 0.1~1.0.
327 double origin_weight = std::max(1.0 - smooth_weight, 0.0);
328 for (std::size_t iter = 0; iter < max_iters; ++iter) {
329 for (const auto& polyline : polylines) {
330 std::size_t pt_cnt = polyline.size();
331 std::vector<CGALKernel::Point_3> points(pt_cnt);
332 for (std::size_t pt_idx = 1; pt_idx + 1 < pt_cnt; ++pt_idx) {
333 std::vector<CGALKernel::Point_3> pts{mesh.point(polyline[pt_idx - 1]), mesh.point(polyline[pt_idx + 1])};
334 CGALKernel::Point_3 smooth_pt = CGAL::ORIGIN + ((CGAL::centroid(pts.begin(), pts.end()) - CGAL::ORIGIN) * smooth_weight +
335 (mesh.point(polyline[pt_idx]) - CGAL::ORIGIN) * origin_weight);
336 points[pt_idx] = boundary_tree.closest_point(smooth_pt);
337 }
338
339 if (polyline.front() == polyline.back()) {
340 if (feature_vertices.count(polyline.front())) {
341 continue;
342 }
343 std::vector<CGALKernel::Point_3> pts{mesh.point(polyline[1]), mesh.point(polyline[pt_cnt - 2])};
344 CGALKernel::Point_3 smooth_pt = CGAL::ORIGIN + ((CGAL::centroid(pts.begin(), pts.end()) - CGAL::ORIGIN) * smooth_weight +
345 (mesh.point(polyline.front()) - CGAL::ORIGIN) * origin_weight);
346 mesh.point(polyline.front()) = boundary_tree.closest_point(smooth_pt);
347 }
348
349 for (std::size_t pt_idx = 1; pt_idx + 1 < pt_cnt; ++pt_idx) {
350 if (feature_vertices.count(polyline[pt_idx])) {
351 continue;
352 }
353 mesh.point(polyline[pt_idx]) = points[pt_idx];
354 }
355 }
356 }
357
358 for (auto& polyline : polylines) {
359 for (auto& vtx : polyline) {
360 mesh.point(vtx) = boundary_tree.closest_point(mesh.point(vtx));
361 }
362 }
363
364 return true;
365}
static double get_angle_between_vectors(const CGALKernel::Vector_3 &v1, const CGALKernel::Vector_3 &v2)
Definition ColorUtils.cpp:116
CGAL::AABB_tree< Traits > Tree
Definition ColorUtils.cpp:102
double smooth_weight
Definition ColorUtils.hpp:41
double angle(const Eigen::MatrixBase< Derived > &v1, const Eigen::MatrixBase< Derived2 > &v2)
Definition Point.hpp:138

References Slic3r::angle(), get_angle_between_vectors(), and Slic3r::tex2color::color_utils::SmoothParameters::smooth_weight.

Referenced by smooth_region_labels().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ smooth_region_labels()

bool Slic3r::tex2color::color_utils::smooth_region_labels ( TriMesh & tri_mesh,
std::vector< std::size_t > & face_labels,
const SmoothParameters & smooth_parameters )
static
917 {
918 CGALMesh mesh;
920
921 if (mesh.number_of_faces() != face_labels.size()) {
922 BOOST_LOG_TRIVIAL(warning) << "Face count does not match label count";
923 return false;
924 }
925
926 if (smooth_parameters.smooth_weight >= TOPO_SMOOTH_WEIGHT_THRESHOLD) {
927 // Topological smoothing: reassign face labels.
928 smooth_region_topo_boundary(mesh, face_labels);
929 }
930
931 if (smooth_parameters.smooth_weight > EPSILON) {
932 // Geometric smoothing: smooth polylines and project back onto the original mesh.
933 smooth_region_geom_boundary(mesh, face_labels, smooth_parameters);
934 }
935
936 tri_mesh = cgalutils::cgal_to_trimesh(mesh);
937
938 return true;
939}
TriMesh cgal_to_trimesh(const CGALMesh &cm)
Definition CgalUtils.hpp:33
static bool smooth_region_geom_boundary(CGALMesh &mesh, std::vector< std::size_t > &face_labels, const SmoothParameters &smooth_parameters)
Definition ColorUtils.cpp:225
static constexpr double TOPO_SMOOTH_WEIGHT_THRESHOLD
Definition ColorUtils.cpp:78
static bool smooth_region_topo_boundary(CGALMesh &mesh, std::vector< std::size_t > &face_labels, std::size_t max_iters=20)
Definition ColorUtils.cpp:187

References Slic3r::tex2color::cgalutils::cgal_to_trimesh(), Slic3r::tex2color::cgalutils::convert_trimesh_to_cgal(), EPSILON, smooth_region_geom_boundary(), smooth_region_topo_boundary(), Slic3r::tex2color::color_utils::SmoothParameters::smooth_weight, and TOPO_SMOOTH_WEIGHT_THRESHOLD.

Referenced by smooth_region(), and smooth_region().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ smooth_region_topo_boundary()

bool Slic3r::tex2color::color_utils::smooth_region_topo_boundary ( CGALMesh & mesh,
std::vector< std::size_t > & face_labels,
std::size_t max_iters = 20 )
static
187 {
188 // Topological smoothing: reassign face labels
189 std::size_t iter = 0;
190 while (iter < max_iters) {
191 ++iter;
192 bool flip_flag = false;
193 for (auto face : mesh.faces()) {
194 std::size_t same_label_count = 0;
195 std::unordered_map<std::size_t, std::size_t> map_label_to_cnt;
196 std::size_t max_adj_cnt = 0;
197 std::size_t max_adj_label = face_labels[face];
198 for (auto adj_face : mesh.faces_around_face(mesh.halfedge(face))) {
199 if (adj_face == CGALMesh::null_face() || !mesh.is_valid(adj_face) || mesh.is_removed(adj_face)) {
200 continue;
201 }
202 if (face_labels[adj_face] == face_labels[face]) {
203 ++same_label_count;
204 } else {
205 ++map_label_to_cnt[face_labels[adj_face]];
206 if (map_label_to_cnt[face_labels[adj_face]] > max_adj_cnt) {
207 max_adj_cnt = map_label_to_cnt[face_labels[adj_face]];
208 max_adj_label = face_labels[adj_face];
209 }
210 }
211 }
212 if (max_adj_cnt > same_label_count) {
213 face_labels[face] = max_adj_label;
214 flip_flag = true;
215 }
216 }
217
218 if (!flip_flag) {
219 break;
220 }
221 }
222 return true;
223}

Referenced by smooth_region_labels().

Here is the caller graph for this function:

◆ sort_colors_by_pca()

std::vector< ColorPCA > Slic3r::tex2color::color_utils::sort_colors_by_pca ( const std::vector< Color > & colors)
static
1174 {
1175 const std::size_t n = colors.size();
1176
1177 if (n == 0) {
1178 return {};
1179 }
1180
1181 if (n == 1) {
1182 return {{0, 0.0}};
1183 }
1184
1185 // Step 1: Data preprocessing - normalize to [0, 1]
1186 Eigen::MatrixXd data(n, 3);
1187
1188 for (std::size_t i = 0; i < n; ++i) {
1189 data(i, 0) = static_cast<double>(colors[i][0]) / 255.0; // R
1190 data(i, 1) = static_cast<double>(colors[i][1]) / 255.0; // G
1191 data(i, 2) = static_cast<double>(colors[i][2]) / 255.0; // B
1192 }
1193
1194 // Step 2: Compute mean and center the data
1195 Eigen::RowVector3d mean = data.colwise().mean();
1196 Eigen::MatrixXd centered = data.rowwise() - mean;
1197
1198 // Step 3: Compute covariance matrix (3x3)
1199 Eigen::Matrix3d cov = (centered.adjoint() * centered) / static_cast<double>(n - 1);
1200
1201 // Step 4: Eigenvalue decomposition
1202 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> solver(cov);
1203
1204 if (solver.info() != Eigen::Success) {
1205#ifdef DEBUG_FLAG
1206 std::cerr << "PCA: Eigenvalue decomposition failed" << std::endl;
1207#endif // DEBUG_FLAG
1208 // Fallback: return an approximate result sorted by luminance.
1209 // Luminance is a key perceptual feature; convert RGB to grayscale (L = 0.299R + 0.587G + 0.114B) and sort in ascending order.
1210 std::vector<ColorPCA> result;
1211 result.reserve(n);
1212 for (std::size_t i = 0; i < n; ++i) {
1213 double luminance = 0.299 * colors[i][0] + 0.587 * colors[i][1] + 0.114 * colors[i][2];
1214 result.push_back({i, luminance});
1215 }
1216 std::sort(result.begin(), result.end());
1217 return result;
1218 }
1219
1220 // Get eigenvalues and eigenvectors (sorted by eigenvalue in descending order)
1221 Eigen::Vector3d eigenvalues = solver.eigenvalues();
1222 Eigen::Matrix3d eigenvectors = solver.eigenvectors();
1223
1224 // Step 5: Find the eigenvector corresponding to the largest eigenvalue (first principal component)
1225 Eigen::MatrixXd::Index max_eigenvalue_idx;
1226 eigenvalues.maxCoeff(&max_eigenvalue_idx);
1227
1228 Eigen::Vector3d first_principal_component = eigenvectors.col(max_eigenvalue_idx);
1229
1230 // Step 6: Project centered data onto the first principal component
1231 Eigen::VectorXd projections = centered * first_principal_component;
1232
1233 // Step 7: Build result and sort
1234 std::vector<ColorPCA> result;
1235 result.reserve(n);
1236
1237 for (std::size_t i = 0; i < n; ++i) {
1238 result.push_back({i, projections(i)});
1239 }
1240
1241 std::sort(result.begin(), result.end());
1242
1243 return result;
1244}

References sort_colors_by_pca().

Referenced by sort_colors_by_pca().

Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ D65_WHITE

const XYZ Slic3r::tex2color::color_utils::D65_WHITE = {0.95047, 1.0, 1.08883}
372{0.95047, 1.0, 1.08883};

Referenced by convert_lab_to_rgb(), and convert_xyz_to_lab().

◆ TOPO_SMOOTH_WEIGHT_THRESHOLD

double Slic3r::tex2color::color_utils::TOPO_SMOOTH_WEIGHT_THRESHOLD = 0.3
staticconstexpr

Referenced by smooth_region_labels().