diff --git a/source/core/rendering/palette_manager.cpp b/source/core/rendering/palette_manager.cpp index f839424..0b9d261 100644 --- a/source/core/rendering/palette_manager.cpp +++ b/source/core/rendering/palette_manager.cpp @@ -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 { + std::vector cost(static_cast(sz) * static_cast(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& cost, int j0, int i0, const std::vector& used, const std::vector& u, const std::vector& v, std::vector& minv, std::vector& 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& used, const std::vector& p, std::vector& u, std::vector& v, std::vector& 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& cost) -> std::vector { + constexpr int INF = INT_MAX / 2; + std::vector u(sz + 1, 0); // Potencials de files + std::vector v(sz + 1, 0); // Potencials de columnes + std::vector p(sz + 1, 0); // p[j] = fila assignada a columna j + std::vector way(sz + 1, 0); + + for (int i = 1; i <= sz; ++i) { + p[0] = i; + int j0 = 0; + std::vector minv(sz + 1, INF); + std::vector 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& 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 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(countActiveColors(palette)); const auto M = static_cast(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 cost(static_cast(SZ) * static_cast(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 u(SZ + 1, 0); // Potenciales de filas - std::vector v(SZ + 1, 0); // Potenciales de columnas - std::vector p(SZ + 1, 0); // p[j] = fila asignada a columna j - std::vector way(SZ + 1, 0); - - for (int i = 1; i <= SZ; ++i) { - p[0] = i; - int j0 = 0; - std::vector minv(SZ + 1, INF); - std::vector 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 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.