refactor palette_manager: extreu buildCostMatrix, hungarianAssign i buildPaletteFromAssignment
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user