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.

70 lines (57 loc) 2.76 kB
import { rgb2lab, deltaE, sortColors, mulberry32 } from '../utils/colorUtils.js'; export function simulatedAnnealing(colors, selectCount, settings = {}) { console.log('Starting Simulated Annealing calculation...'); const start = performance.now(); const labColors = colors.map(rgb2lab); const maxIterations = 10000; const initialTemp = settings.initialTemp ?? 1000; const coolingRate = settings.coolingRate ?? 0.995; const minTemp = settings.minTemp ?? 0.1; // Use seeded PRNG if settings.seed is provided const prng = typeof settings.seed === 'number' ? mulberry32(settings.seed) : Math.random; // Helper function to calculate minimum distance between selected colors function calculateFitness(selection) { let minDist = Infinity; for (let i = 0; i < selection.length - 1; i++) { for (let j = i + 1; j < selection.length; j++) { const dist = deltaE(labColors[selection[i]], labColors[selection[j]]); minDist = Math.min(minDist, dist); } } return minDist; } // Generate initial solution let currentSolution = Array.from({length: colors.length}, (_, i) => i) .sort(() => prng() - 0.5) .slice(0, selectCount); let currentFitness = calculateFitness(currentSolution); let bestSolution = [...currentSolution]; let bestFitness = currentFitness; let temperature = initialTemp; // Main loop for (let i = 0; i < maxIterations && temperature > minTemp; i++) { // Generate neighbor by swapping one selected color with an unselected one const neighborSolution = [...currentSolution]; const swapIndex = Math.floor(prng() * selectCount); const availableIndices = Array.from({length: colors.length}, (_, i) => i) .filter(i => !currentSolution.includes(i)); const newIndex = availableIndices[Math.floor(prng() * availableIndices.length)]; neighborSolution[swapIndex] = newIndex; const neighborFitness = calculateFitness(neighborSolution); // Decide if we should accept the neighbor const delta = neighborFitness - currentFitness; if (delta > 0 || prng() < Math.exp(delta / temperature)) { currentSolution = neighborSolution; currentFitness = neighborFitness; if (currentFitness > bestFitness) { bestSolution = [...currentSolution]; bestFitness = currentFitness; } } temperature *= coolingRate; } return { colors: sortColors(bestSolution.map(i => colors[i])), time: performance.now() - start }; }