refactor palette_manager: extreu buildCostMatrix, hungarianAssign i buildPaletteFromAssignment
This commit is contained in:
@@ -58,103 +58,123 @@ namespace {
|
|||||||
return count;
|
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).
|
// Asignación óptima de colores mediante el algoritmo húngaro (Kuhn-Munkres).
|
||||||
// Minimiza la distancia RGB total entre la paleta y la referencia.
|
// 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.
|
// 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 {
|
auto sortByOptimal(const Palette& palette, const Palette& reference) -> Palette {
|
||||||
const auto N = static_cast<int>(countActiveColors(palette));
|
const auto N = static_cast<int>(countActiveColors(palette));
|
||||||
const auto M = static_cast<int>(countActiveColors(reference));
|
const auto M = static_cast<int>(countActiveColors(reference));
|
||||||
const int SZ = std::max(N, M);
|
const int SZ = std::max(N, M);
|
||||||
if (SZ == 0) { return palette; }
|
if (SZ == 0) { return palette; }
|
||||||
|
|
||||||
// Matriz de coste NxM (ampliada a SZxSZ con ceros para hacerla cuadrada)
|
const auto COST = buildCostMatrix(N, M, SZ, palette, reference);
|
||||||
std::vector<int> cost(static_cast<size_t>(SZ) * static_cast<size_t>(SZ), 0);
|
const auto P = hungarianAssign(SZ, COST);
|
||||||
for (int i = 0; i < N; ++i) {
|
return buildPaletteFromAssignment(N, M, P, palette);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asignación greedy de colores a la paleta de referencia.
|
// Asignación greedy de colores a la paleta de referencia.
|
||||||
|
|||||||
Reference in New Issue
Block a user