UNPKG

pick-distinct-colors

Version:

A collection of algorithms and utilities for analyzing and selecting maximally distinct colors. Now includes a unified pickDistinctColors API for easy color selection.

107 lines (88 loc) 4.15 kB
import { rgb2lab, deltaE, sortColors, mulberry32 } from '../utils/colorUtils.js'; export function antColonyOptimization(colors, selectCount, settings = {}) { console.log('Starting Ant Colony Optimization...'); const start = performance.now(); const labColors = colors.map(rgb2lab); const numAnts = settings.numAnts ?? 20; const maxIterations = settings.acoIterations ?? 100; const evaporationRate = settings.evaporationRate ?? 0.1; const alpha = settings.pheromoneImportance ?? 1; // pheromone importance const beta = settings.heuristicImportance ?? 2; // heuristic importance // Use seeded PRNG if settings.seed is provided const prng = typeof settings.seed === 'number' ? mulberry32(settings.seed) : Math.random; // Initialize pheromone trails const pheromones = Array(colors.length).fill(1); // Calculate heuristic information (distances between colors) const distances = Array(colors.length).fill().map(() => Array(colors.length)); for (let i = 0; i < colors.length; i++) { for (let j = i + 1; j < colors.length; j++) { const distance = deltaE(labColors[i], labColors[j]); distances[i][j] = distance; distances[j][i] = distance; } } let bestSolution = null; let bestFitness = -Infinity; // Main ACO loop for (let iteration = 0; iteration < maxIterations; iteration++) { // Solutions found by ants in this iteration const solutions = []; // Each ant constructs a solution for (let ant = 0; ant < numAnts; ant++) { const available = Array.from({length: colors.length}, (_, i) => i); const solution = []; // Randomly select first color const firstIndex = Math.floor(prng() * available.length); solution.push(available[firstIndex]); available.splice(firstIndex, 1); // Select remaining colors while (solution.length < selectCount) { // Calculate probabilities for each available color const probabilities = available.map(i => { const pheromone = Math.pow(pheromones[i], alpha); const minDist = Math.min(...solution.map(j => distances[i][j])); const heuristic = Math.pow(minDist, beta); return pheromone * heuristic; }); // Select next color using roulette wheel selection const total = probabilities.reduce((a, b) => a + b, 0); let random = prng() * total; let selectedIndex = 0; while (random > 0 && selectedIndex < probabilities.length) { random -= probabilities[selectedIndex]; if (random > 0) selectedIndex++; } solution.push(available[selectedIndex]); available.splice(selectedIndex, 1); } solutions.push(solution); } // Evaluate solutions and update best for (const solution of solutions) { const fitness = Math.min(...solution.map((i, idx) => solution.slice(idx + 1).map(j => deltaE(labColors[i], labColors[j]) ) ).flat()); if (fitness > bestFitness) { bestFitness = fitness; bestSolution = solution; } } // Update pheromones for (let i = 0; i < pheromones.length; i++) { pheromones[i] *= (1 - evaporationRate); } // Add new pheromones from solutions for (const solution of solutions) { const deposit = 1 / solution.length; for (const i of solution) { pheromones[i] += deposit; } } } return { colors: sortColors(bestSolution.map(i => colors[i])), time: performance.now() - start }; }