refactor palette_manager: extreu buildCostMatrix, hungarianAssign i buildPaletteFromAssignment

This commit is contained in:
2026-05-17 21:59:57 +02:00
parent 62bf99f174
commit 3f4ead40e1
+108 -88
View File
@@ -58,103 +58,123 @@ namespace {
return count;
}
namespace {
// Construeix la matriu de cost NxM (ampliada a SZxSZ amb zeros) per a l'algoritme hongarès.
auto buildCostMatrix(int n_rows, int m_cols, int sz, const Palette& palette, const Palette& reference) -> std::vector<int> {
std::vector<int> cost(static_cast<size_t>(sz) * static_cast<size_t>(sz), 0);
for (int i = 0; i < n_rows; ++i) {
for (int j = 0; j < m_cols; ++j) {
cost[(i * sz) + j] = rgbDistanceSq(palette[i], reference[j]);
}
}
return cost;
}
// Estat compartit entre les fases d'una iteració del Kuhn-Munkres
struct HungarianStep {
int j0;
int delta;
int j1;
};
// Cerca la columna j1 que minimitza el cost reduït, actualitzant minv[] i way[].
auto relaxColumns(int sz, const std::vector<int>& cost, int j0, int i0, const std::vector<bool>& used, const std::vector<int>& u, const std::vector<int>& v, std::vector<int>& minv, std::vector<int>& way) -> HungarianStep {
constexpr int INF = INT_MAX / 2;
HungarianStep step{.j0 = j0, .delta = INF, .j1 = 0};
for (int j = 1; j <= sz; ++j) {
if (used[j]) { continue; }
const int CUR = cost[((i0 - 1) * sz) + (j - 1)] - u[i0] - v[j];
if (CUR < minv[j]) {
minv[j] = CUR;
way[j] = j0;
}
if (minv[j] < step.delta) {
step.delta = minv[j];
step.j1 = j;
}
}
return step;
}
// Aplica el delta calculat a potencials (u, v) i a minv[].
void applyDelta(int sz, int delta, const std::vector<bool>& used, const std::vector<int>& p, std::vector<int>& u, std::vector<int>& v, std::vector<int>& minv) {
for (int j = 0; j <= sz; ++j) {
if (used[j]) {
u[p[j]] += delta;
v[j] -= delta;
} else {
minv[j] -= delta;
}
}
}
// Algoritme hongarès (Kuhn-Munkres) basat en potencials. Retorna p[],
// on p[j] = fila assignada a la columna j (índexs 1-based; p[0] no s'usa).
auto hungarianAssign(int sz, const std::vector<int>& cost) -> std::vector<int> {
constexpr int INF = INT_MAX / 2;
std::vector<int> u(sz + 1, 0); // Potencials de files
std::vector<int> v(sz + 1, 0); // Potencials de columnes
std::vector<int> p(sz + 1, 0); // p[j] = fila assignada a columna j
std::vector<int> way(sz + 1, 0);
for (int i = 1; i <= sz; ++i) {
p[0] = i;
int j0 = 0;
std::vector<int> minv(sz + 1, INF);
std::vector<bool> used(sz + 1, false);
do {
used[j0] = true;
const auto STEP = relaxColumns(sz, cost, j0, p[j0], used, u, v, minv, way);
applyDelta(sz, STEP.delta, used, p, u, v, minv);
j0 = STEP.j1;
} while (p[j0] != 0);
do {
const int J1 = way[j0];
p[j0] = p[J1];
j0 = J1;
} while (j0 != 0);
}
return p;
}
// Construeix la paleta resultant a partir de l'assignació p[]. Afegeix els colors
// de palette sense parella en reference al final (cas N > M).
auto buildPaletteFromAssignment(int n_rows, int m_cols, const std::vector<int>& p, const Palette& palette) -> Palette {
Palette out{};
out.fill(0);
for (int j = 1; j <= m_cols && j <= n_rows; ++j) {
const int ROW = p[j] - 1;
if (ROW >= 0 && ROW < n_rows) { out[j - 1] = palette[ROW]; }
}
if (n_rows <= m_cols) { return out; }
std::vector<bool> used_rows(n_rows, false);
for (int j = 1; j <= m_cols; ++j) {
const int ROW = p[j] - 1;
if (ROW >= 0 && ROW < n_rows) { used_rows[ROW] = true; }
}
int out_idx = m_cols;
for (int i = 0; i < n_rows; ++i) {
if (!used_rows[i]) { out[out_idx++] = palette[i]; }
}
return out;
}
} // namespace
// Asignación óptima de colores mediante el algoritmo húngaro (Kuhn-Munkres).
// Minimiza la distancia RGB total entre la paleta y la referencia.
// O(N³) con N = número de colores activos — para N ≤ 256 es instantáneo.
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
auto sortByOptimal(const Palette& palette, const Palette& reference) -> Palette {
const auto N = static_cast<int>(countActiveColors(palette));
const auto M = static_cast<int>(countActiveColors(reference));
const int SZ = std::max(N, M);
if (SZ == 0) { return palette; }
// Matriz de coste NxM (ampliada a SZxSZ con ceros para hacerla cuadrada)
std::vector<int> cost(static_cast<size_t>(SZ) * static_cast<size_t>(SZ), 0);
for (int i = 0; i < N; ++i) {
for (int j = 0; j < M; ++j) {
cost[(i * SZ) + j] = rgbDistanceSq(palette[i], reference[j]);
}
}
// Hungarian algorithm (Kuhn-Munkres) — versión basada en potenciales
constexpr int INF = INT_MAX / 2;
std::vector<int> u(SZ + 1, 0); // Potenciales de filas
std::vector<int> v(SZ + 1, 0); // Potenciales de columnas
std::vector<int> p(SZ + 1, 0); // p[j] = fila asignada a columna j
std::vector<int> way(SZ + 1, 0);
for (int i = 1; i <= SZ; ++i) {
p[0] = i;
int j0 = 0;
std::vector<int> minv(SZ + 1, INF);
std::vector<bool> used(SZ + 1, false);
do {
used[j0] = true;
int i0 = p[j0];
int delta = INF;
int j1 = 0;
for (int j = 1; j <= SZ; ++j) {
if (!used[j]) {
int cur = cost[((i0 - 1) * SZ) + (j - 1)] - u[i0] - v[j];
if (cur < minv[j]) {
minv[j] = cur;
way[j] = j0;
}
if (minv[j] < delta) {
delta = minv[j];
j1 = j;
}
}
}
for (int j = 0; j <= SZ; ++j) {
if (used[j]) {
u[p[j]] += delta;
v[j] -= delta;
} else {
minv[j] -= delta;
}
}
j0 = j1;
} while (p[j0] != 0);
do {
int j1 = way[j0];
p[j0] = p[j1];
j0 = j1;
} while (j0 != 0);
}
// Construir la paleta resultante: assignment[j] = fila asignada a columna j
// Queremos result[j] = palette[fila asignada a j]
Palette out{};
out.fill(0);
for (int j = 1; j <= M && j <= N; ++j) {
int row = p[j] - 1; // Índice 0-based en palette
if (row >= 0 && row < N) {
out[j - 1] = palette[row];
}
}
// Colores extra de palette que no tienen pareja en reference (N > M)
if (N > M) {
std::vector<bool> used_rows(N, false);
for (int j = 1; j <= M; ++j) {
int row = p[j] - 1;
if (row >= 0 && row < N) { used_rows[row] = true; }
}
int out_idx = M;
for (int i = 0; i < N; ++i) {
if (!used_rows[i]) {
out[out_idx++] = palette[i];
}
}
}
return out;
const auto COST = buildCostMatrix(N, M, SZ, palette, reference);
const auto P = hungarianAssign(SZ, COST);
return buildPaletteFromAssignment(N, M, P, palette);
}
// Asignación greedy de colores a la paleta de referencia.