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.
1 lines • 83.5 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../js/utils/colorUtils.js","../js/algorithms/maxSumDistances.js","../js/algorithms/greedy.js","../js/algorithms/kmeans.js","../js/algorithms/simulatedAnnealing.js","../js/algorithms/genetic.js","../js/algorithms/particleSwarm.js","../js/algorithms/antColony.js","../js/algorithms/tabu.js","../js/algorithms/exactMaximum.js","../js/algorithms/exactMinimum.js","../js/algorithms/random.js","../js/utils/pickDistinctColors.js"],"sourcesContent":["export function rgb2lab(rgb) {\n let r = rgb[0] / 255,\n g = rgb[1] / 255,\n b = rgb[2] / 255;\n\n r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;\n g = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;\n b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;\n\n let x = (r * 0.4124 + g * 0.3576 + b * 0.1805) * 100;\n let y = (r * 0.2126 + g * 0.7152 + b * 0.0722) * 100;\n let z = (r * 0.0193 + g * 0.1192 + b * 0.9505) * 100;\n\n x /= 95.047;\n y /= 100;\n z /= 108.883;\n\n x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + 16/116;\n y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + 16/116;\n z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + 16/116;\n\n return [(116 * y) - 16, 500 * (x - y), 200 * (y - z)];\n}\n\nexport function deltaE(labA, labB) {\n let deltaL = labA[0] - labB[0];\n let deltaA = labA[1] - labB[1];\n let deltaB = labA[2] - labB[2];\n return Math.sqrt(deltaL * deltaL + deltaA * deltaA + deltaB * deltaB);\n}\n\n// Seedable PRNG (mulberry32)\nexport function mulberry32(seed) {\n let t = seed >>> 0;\n return function() {\n t += 0x6D2B79F5;\n let r = Math.imul(t ^ t >>> 15, 1 | t);\n r ^= r + Math.imul(r ^ r >>> 7, 61 | r);\n return ((r ^ r >>> 14) >>> 0) / 4294967296;\n };\n}\n\n/**\n * Generate a random RGB color using the provided PRNG.\n * @param {function} [prng=Math.random] - Optional PRNG function.\n * @returns {number[]} RGB color [r, g, b]\n */\nexport function randomColor(prng = Math.random) {\n return [\n Math.floor(prng() * 256),\n Math.floor(prng() * 256),\n Math.floor(prng() * 256)\n ];\n}\n\nexport function sortColors(colors) {\n const labColors = colors.map(rgb2lab);\n const indices = Array.from({length: colors.length}, (_, i) => i);\n \n // Sort by L, then a, then b\n indices.sort((i, j) => {\n const [L1, a1, b1] = labColors[i];\n const [L2, a2, b2] = labColors[j];\n if (L1 !== L2) return L2 - L1;\n if (a1 !== a2) return a2 - a1;\n return b2 - b1;\n });\n \n return indices.map(i => colors[i]);\n}\n\nexport function calculateMetrics(colors) {\n const labColors = colors.map(rgb2lab);\n let minDist = Infinity;\n let maxDist = -Infinity;\n let sumDist = 0;\n let count = 0;\n \n for (let i = 0; i < colors.length - 1; i++) {\n for (let j = i + 1; j < colors.length; j++) {\n const dist = deltaE(labColors[i], labColors[j]);\n minDist = Math.min(minDist, dist);\n maxDist = Math.max(maxDist, dist);\n sumDist += dist;\n count++;\n }\n }\n \n return {\n min: minDist,\n max: maxDist,\n avg: sumDist / count,\n sum: sumDist\n };\n}\n\nexport function analyzeColorDistribution(colors) {\n if (!colors || colors.length === 0) return 'No colors to analyze';\n \n try {\n const labColors = colors.map(rgb2lab);\n \n // Initialize stats with first color\n const stats = {\n L: { min: labColors[0][0], max: labColors[0][0], range: [0, 100] },\n a: { min: labColors[0][1], max: labColors[0][1], range: [-128, 127] },\n b: { min: labColors[0][2], max: labColors[0][2], range: [-128, 127] }\n };\n \n // Process colors in chunks\n const chunkSize = 500;\n for (let i = 1; i < labColors.length; i += chunkSize) {\n const chunk = labColors.slice(i, i + chunkSize);\n for (const lab of chunk) {\n stats.L.min = Math.min(stats.L.min, lab[0]);\n stats.L.max = Math.max(stats.L.max, lab[0]);\n stats.a.min = Math.min(stats.a.min, lab[1]);\n stats.a.max = Math.max(stats.a.max, lab[1]);\n stats.b.min = Math.min(stats.b.min, lab[2]);\n stats.b.max = Math.max(stats.b.max, lab[2]);\n }\n }\n \n // Calculate coverage percentages\n const coverage = {\n L: ((stats.L.max - stats.L.min) / (stats.L.range[1] - stats.L.range[0]) * 100).toFixed(1),\n a: ((stats.a.max - stats.a.min) / (stats.a.range[1] - stats.a.range[0]) * 100).toFixed(1),\n b: ((stats.b.max - stats.b.min) / (stats.b.range[1] - stats.b.range[0]) * 100).toFixed(1)\n };\n \n return `\n <strong>Color Space Coverage:</strong><br>\n Lightness (L*): ${coverage.L}%<br>\n Green-Red (a*): ${coverage.a}%<br>\n Blue-Yellow (b*): ${coverage.b}%\n `;\n } catch (error) {\n console.error('Error in analyzeColorDistribution:', error);\n return 'Error analyzing color distribution';\n }\n}\n\nexport function rgbToHex(rgb) {\n return '#' + rgb.map(x => {\n const hex = x.toString(16);\n return hex.length === 1 ? '0' + hex : hex;\n }).join('');\n}\n\nexport function calculateDistanceMatrix(colors) {\n const labColors = colors.map(rgb2lab);\n const matrix = [];\n for (let i = 0; i < colors.length; i++) {\n matrix[i] = [];\n for (let j = 0; j < colors.length; j++) {\n matrix[i][j] = deltaE(labColors[i], labColors[j]);\n }\n }\n return matrix;\n}\n\nexport function findClosestPair(colors) {\n const labColors = colors.map(rgb2lab);\n let minDist = Infinity;\n let closestPair = [0, 1];\n \n for (let i = 0; i < colors.length; i++) {\n for (let j = i + 1; j < colors.length; j++) {\n const dist = deltaE(labColors[i], labColors[j]);\n if (dist < minDist) {\n minDist = dist;\n closestPair = [i, j];\n }\n }\n }\n \n return {\n colors: [colors[closestPair[0]], colors[closestPair[1]]],\n distance: minDist\n };\n} ","import { rgb2lab, deltaE, sortColors, mulberry32 } from '../utils/colorUtils.js';\n\nexport function maxSumDistancesGlobal(colors, selectCount) {\n return new Promise((resolve, reject) => {\n // Create worker code with utility functions in scope\n const workerCode = `\n // Import utility functions from parent\n const rgb2lab = ${rgb2lab.toString()};\n const deltaE = ${deltaE.toString()};\n const sortColors = ${sortColors.toString()};\n\n // Main worker function\n function maxSumDistancesGlobal(colors, selectCount) {\n const start = performance.now();\n const labColors = colors.map(rgb2lab);\n \n // Calculate total distances from each color to all other colors\n const totalDistances = colors.map((_, i) => {\n let sum = 0;\n for (let j = 0; j < colors.length; j++) {\n if (i !== j) {\n sum += deltaE(labColors[i], labColors[j]);\n }\n }\n return { index: i, sum };\n });\n \n // Sort colors by their total distance to all other colors\n totalDistances.sort((a, b) => b.sum - a.sum);\n \n // Take the top selectCount colors that have the highest total distances\n const selectedIndices = totalDistances.slice(0, selectCount).map(item => item.index);\n const selectedColors = selectedIndices.map(i => colors[i]);\n \n return {\n colors: sortColors(selectedColors),\n time: performance.now() - start\n };\n }\n\n // Worker message handler\n self.onmessage = function(e) {\n const { colors, selectCount } = e.data;\n try {\n const result = maxSumDistancesGlobal(colors, selectCount);\n self.postMessage({ type: 'complete', result });\n } catch (error) {\n self.postMessage({ type: 'error', error: error.message });\n }\n };\n `;\n\n // Create blob and worker\n const blob = new Blob([workerCode], { type: 'application/javascript' });\n const worker = new Worker(URL.createObjectURL(blob));\n\n // Set up worker message handlers\n worker.onmessage = function(e) {\n if (e.data.type === 'complete') {\n resolve(e.data.result);\n } else if (e.data.type === 'error') {\n reject(new Error(e.data.error));\n }\n worker.terminate();\n };\n\n worker.onerror = function(error) {\n reject(error);\n worker.terminate();\n };\n\n // Start the worker\n worker.postMessage({ colors, selectCount });\n });\n}\n\nexport function maxSumDistancesSequential(colors, selectCount, seed) {\n console.log('Starting Maximum Sum (Sequential) calculation...');\n const start = performance.now();\n \n const labColors = colors.map(rgb2lab);\n const selected = [];\n const available = Array.from({length: colors.length}, (_, i) => i);\n \n // Use seeded PRNG if seed is provided\n const prng = typeof seed === 'number' ? mulberry32(seed) : Math.random;\n \n // Helper function to calculate total distance from a point to selected points\n function calculateTotalDistance(index) {\n return selected.reduce((sum, selectedIndex) => \n sum + deltaE(labColors[index], labColors[selectedIndex]), 0);\n }\n \n // Select first point randomly\n const firstIndex = Math.floor(prng() * available.length);\n selected.push(available[firstIndex]);\n available.splice(firstIndex, 1);\n \n // Select remaining points\n while (selected.length < selectCount) {\n let bestIndex = 0;\n let bestDistance = -Infinity;\n \n // Find point with maximum sum of distances to selected points\n for (let i = 0; i < available.length; i++) {\n const totalDistance = calculateTotalDistance(available[i]);\n if (totalDistance > bestDistance) {\n bestDistance = totalDistance;\n bestIndex = i;\n }\n }\n \n selected.push(available[bestIndex]);\n available.splice(bestIndex, 1);\n }\n \n return {\n colors: sortColors(selected.map(i => colors[i])),\n time: performance.now() - start\n };\n} ","import { rgb2lab, deltaE, sortColors, mulberry32 } from '../utils/colorUtils.js';\n\nexport function greedySelection(colors, selectCount, seed) {\n console.log('Starting Greedy calculation...');\n const start = performance.now();\n \n const labColors = colors.map(rgb2lab);\n const selected = [];\n const available = Array.from({length: colors.length}, (_, i) => i);\n \n // Use seeded PRNG if seed is provided\n const prng = typeof seed === 'number' ? mulberry32(seed) : Math.random;\n \n // Helper function to calculate minimum distance from a point to selected points\n function calculateMinDistance(index) {\n if (selected.length === 0) return Infinity;\n return Math.min(...selected.map(selectedIndex => \n deltaE(labColors[index], labColors[selectedIndex])\n ));\n }\n \n // Select first point randomly\n const firstIndex = Math.floor(prng() * available.length);\n selected.push(available[firstIndex]);\n available.splice(firstIndex, 1);\n \n // Select remaining points\n while (selected.length < selectCount) {\n let bestIndex = 0;\n let bestMinDistance = -Infinity;\n \n // Find point with maximum minimum distance to selected points\n for (let i = 0; i < available.length; i++) {\n const minDistance = calculateMinDistance(available[i]);\n if (minDistance > bestMinDistance) {\n bestMinDistance = minDistance;\n bestIndex = i;\n }\n }\n \n selected.push(available[bestIndex]);\n available.splice(bestIndex, 1);\n }\n \n return {\n colors: sortColors(selected.map(i => colors[i])),\n time: performance.now() - start\n };\n} ","import { rgb2lab, deltaE, sortColors, mulberry32 } from '../utils/colorUtils.js';\n\nexport function kmeansppSelection(colors, selectCount, seed) {\n console.log('Starting K-means++ calculation...');\n const start = performance.now();\n \n const labColors = colors.map(rgb2lab);\n \n // Use seeded PRNG if seed is provided\n const prng = typeof seed === 'number' ? mulberry32(seed) : Math.random;\n \n // Helper function to find minimum distance to existing centers\n function minDistanceToCenters(point, centers) {\n if (centers.length === 0) return Infinity;\n return Math.min(...centers.map(center => \n deltaE(labColors[point], labColors[center])\n ));\n }\n \n // Select initial center randomly\n const selected = [Math.floor(prng() * colors.length)];\n \n // Select remaining centers using k-means++ initialization\n while (selected.length < selectCount) {\n const distances = Array.from({length: colors.length}, (_, i) => {\n if (selected.includes(i)) return 0;\n const dist = minDistanceToCenters(i, selected);\n return dist * dist; // Square distances for k-means++\n });\n \n const sum = distances.reduce((a, b) => a + b, 0);\n let random = prng() * sum;\n let selectedIndex = 0;\n \n while (random > 0 && selectedIndex < distances.length) {\n if (!selected.includes(selectedIndex)) {\n random -= distances[selectedIndex];\n }\n if (random > 0) selectedIndex++;\n }\n \n selected.push(selectedIndex);\n }\n \n return {\n colors: sortColors(selected.map(i => colors[i])),\n time: performance.now() - start\n };\n} ","import { rgb2lab, deltaE, sortColors, mulberry32 } from '../utils/colorUtils.js';\n\nexport function simulatedAnnealing(colors, selectCount, settings = {}) {\n console.log('Starting Simulated Annealing calculation...');\n const start = performance.now();\n \n const labColors = colors.map(rgb2lab);\n const maxIterations = 10000;\n const initialTemp = settings.initialTemp ?? 1000;\n const coolingRate = settings.coolingRate ?? 0.995;\n const minTemp = settings.minTemp ?? 0.1;\n \n // Use seeded PRNG if settings.seed is provided\n const prng = typeof settings.seed === 'number' ? mulberry32(settings.seed) : Math.random;\n \n // Helper function to calculate minimum distance between selected colors\n function calculateFitness(selection) {\n let minDist = Infinity;\n for (let i = 0; i < selection.length - 1; i++) {\n for (let j = i + 1; j < selection.length; j++) {\n const dist = deltaE(labColors[selection[i]], labColors[selection[j]]);\n minDist = Math.min(minDist, dist);\n }\n }\n return minDist;\n }\n \n // Generate initial solution\n let currentSolution = Array.from({length: colors.length}, (_, i) => i)\n .sort(() => prng() - 0.5)\n .slice(0, selectCount);\n let currentFitness = calculateFitness(currentSolution);\n \n let bestSolution = [...currentSolution];\n let bestFitness = currentFitness;\n \n let temperature = initialTemp;\n \n // Main loop\n for (let i = 0; i < maxIterations && temperature > minTemp; i++) {\n // Generate neighbor by swapping one selected color with an unselected one\n const neighborSolution = [...currentSolution];\n const swapIndex = Math.floor(prng() * selectCount);\n const availableIndices = Array.from({length: colors.length}, (_, i) => i)\n .filter(i => !currentSolution.includes(i));\n const newIndex = availableIndices[Math.floor(prng() * availableIndices.length)];\n neighborSolution[swapIndex] = newIndex;\n \n const neighborFitness = calculateFitness(neighborSolution);\n \n // Decide if we should accept the neighbor\n const delta = neighborFitness - currentFitness;\n if (delta > 0 || prng() < Math.exp(delta / temperature)) {\n currentSolution = neighborSolution;\n currentFitness = neighborFitness;\n \n if (currentFitness > bestFitness) {\n bestSolution = [...currentSolution];\n bestFitness = currentFitness;\n }\n }\n \n temperature *= coolingRate;\n }\n \n return {\n colors: sortColors(bestSolution.map(i => colors[i])),\n time: performance.now() - start\n };\n} ","import { rgb2lab, deltaE, sortColors, mulberry32 } from '../utils/colorUtils.js';\n\nexport function geneticAlgorithm(colors, selectCount, settings = {}) {\n console.log('Starting Genetic Algorithm calculation...');\n const start = performance.now();\n \n const labColors = colors.map(rgb2lab);\n const populationSize = settings.populationSize ?? 100;\n const generations = settings.generations ?? 100;\n const mutationRate = settings.mutationRate ?? 0.1;\n \n // Use seeded PRNG if settings.seed is provided\n const prng = typeof settings.seed === 'number' ? mulberry32(settings.seed) : Math.random;\n \n // Helper function to calculate minimum distance between selected colors\n function calculateFitness(selection) {\n let minDist = Infinity;\n for (let i = 0; i < selection.length - 1; i++) {\n for (let j = i + 1; j < selection.length; j++) {\n const dist = deltaE(labColors[selection[i]], labColors[selection[j]]);\n minDist = Math.min(minDist, dist);\n }\n }\n return minDist;\n }\n \n // Generate initial population\n let population = Array(populationSize).fill().map(() => \n Array.from({length: colors.length}, (_, i) => i)\n .sort(() => prng() - 0.5)\n .slice(0, selectCount)\n );\n \n let bestSolution = population[0];\n let bestFitness = calculateFitness(bestSolution);\n \n // Main loop\n for (let generation = 0; generation < generations; generation++) {\n // Calculate fitness for each solution\n const fitnesses = population.map(calculateFitness);\n \n // Update best solution\n const maxFitnessIndex = fitnesses.indexOf(Math.max(...fitnesses));\n if (fitnesses[maxFitnessIndex] > bestFitness) {\n bestSolution = [...population[maxFitnessIndex]];\n bestFitness = fitnesses[maxFitnessIndex];\n }\n \n // Create new population through selection and crossover\n const newPopulation = [];\n \n while (newPopulation.length < populationSize) {\n // Tournament selection\n const tournament1 = Array(3).fill().map(() => Math.floor(prng() * populationSize));\n const tournament2 = Array(3).fill().map(() => Math.floor(prng() * populationSize));\n \n const parent1 = population[tournament1.reduce((a, b) => \n fitnesses[a] > fitnesses[b] ? a : b)];\n const parent2 = population[tournament2.reduce((a, b) => \n fitnesses[a] > fitnesses[b] ? a : b)];\n \n // Crossover\n const crossoverPoint = Math.floor(prng() * selectCount);\n const child = [...new Set([\n ...parent1.slice(0, crossoverPoint),\n ...parent2.slice(crossoverPoint)\n ])];\n \n // Fill up with random colors if needed\n while (child.length < selectCount) {\n const available = Array.from({length: colors.length}, (_, i) => i)\n .filter(i => !child.includes(i));\n child.push(available[Math.floor(prng() * available.length)]);\n }\n \n // Mutation\n if (prng() < mutationRate) {\n const mutationIndex = Math.floor(prng() * selectCount);\n const available = Array.from({length: colors.length}, (_, i) => i)\n .filter(i => !child.includes(i));\n child[mutationIndex] = available[Math.floor(prng() * available.length)];\n }\n \n newPopulation.push(child);\n }\n \n population = newPopulation;\n }\n \n return {\n colors: sortColors(bestSolution.map(i => colors[i])),\n time: performance.now() - start\n };\n} ","import { rgb2lab, deltaE, sortColors, mulberry32 } from '../utils/colorUtils.js';\n\nexport function particleSwarmOptimization(colors, selectCount, settings = {}) {\n console.log('Starting Particle Swarm Optimization...');\n const start = performance.now();\n \n const labColors = colors.map(rgb2lab);\n const numParticles = settings.numParticles ?? 30;\n const maxIterations = settings.psoIterations ?? 100;\n const w = settings.inertiaWeight ?? 0.7; // inertia weight\n const c1 = settings.cognitiveWeight ?? 1.5; // cognitive weight\n const c2 = settings.socialWeight ?? 1.5; // social weight\n \n // Use seeded PRNG if settings.seed is provided\n const prng = typeof settings.seed === 'number' ? mulberry32(settings.seed) : Math.random;\n \n // Helper function to calculate minimum distance between selected colors\n function calculateFitness(selection) {\n let minDist = Infinity;\n for (let i = 0; i < selection.length - 1; i++) {\n for (let j = i + 1; j < selection.length; j++) {\n const dist = deltaE(labColors[selection[i]], labColors[selection[j]]);\n minDist = Math.min(minDist, dist);\n }\n }\n return minDist;\n }\n \n // Initialize particles\n const particles = Array(numParticles).fill().map(() => ({\n position: Array.from({length: colors.length}, (_, i) => i)\n .sort(() => prng() - 0.5)\n .slice(0, selectCount),\n velocity: Array(selectCount).fill(0),\n bestPosition: null,\n bestFitness: -Infinity\n }));\n \n let globalBestPosition = null;\n let globalBestFitness = -Infinity;\n \n // Main loop\n for (let iteration = 0; iteration < maxIterations; iteration++) {\n for (const particle of particles) {\n // Calculate fitness\n const fitness = calculateFitness(particle.position);\n \n // Update particle's best\n if (fitness > particle.bestFitness) {\n particle.bestPosition = [...particle.position];\n particle.bestFitness = fitness;\n \n // Update global best\n if (fitness > globalBestFitness) {\n globalBestPosition = [...particle.position];\n globalBestFitness = fitness;\n }\n }\n \n // Update velocity and position\n for (let i = 0; i < selectCount; i++) {\n const r1 = prng();\n const r2 = prng();\n \n particle.velocity[i] = Math.floor(\n w * particle.velocity[i] +\n c1 * r1 * (particle.bestPosition[i] - particle.position[i]) +\n c2 * r2 * (globalBestPosition[i] - particle.position[i])\n );\n \n // Apply velocity (swap with another color)\n if (particle.velocity[i] !== 0) {\n const available = Array.from({length: colors.length}, (_, i) => i)\n .filter(j => !particle.position.includes(j));\n if (available.length > 0) {\n const swapIndex = Math.floor(prng() * available.length);\n particle.position[i] = available[swapIndex];\n }\n }\n }\n }\n }\n \n return {\n colors: sortColors(globalBestPosition.map(i => colors[i])),\n time: performance.now() - start\n };\n} ","import { rgb2lab, deltaE, sortColors, mulberry32 } from '../utils/colorUtils.js';\n\nexport function antColonyOptimization(colors, selectCount, settings = {}) {\n console.log('Starting Ant Colony Optimization...');\n const start = performance.now();\n \n const labColors = colors.map(rgb2lab);\n const numAnts = settings.numAnts ?? 20;\n const maxIterations = settings.acoIterations ?? 100;\n const evaporationRate = settings.evaporationRate ?? 0.1;\n const alpha = settings.pheromoneImportance ?? 1; // pheromone importance\n const beta = settings.heuristicImportance ?? 2; // heuristic importance\n \n // Use seeded PRNG if settings.seed is provided\n const prng = typeof settings.seed === 'number' ? mulberry32(settings.seed) : Math.random;\n \n // Initialize pheromone trails\n const pheromones = Array(colors.length).fill(1);\n \n // Calculate heuristic information (distances between colors)\n const distances = Array(colors.length).fill().map(() => Array(colors.length));\n for (let i = 0; i < colors.length; i++) {\n for (let j = i + 1; j < colors.length; j++) {\n const distance = deltaE(labColors[i], labColors[j]);\n distances[i][j] = distance;\n distances[j][i] = distance;\n }\n }\n \n let bestSolution = null;\n let bestFitness = -Infinity;\n \n // Main ACO loop\n for (let iteration = 0; iteration < maxIterations; iteration++) {\n // Solutions found by ants in this iteration\n const solutions = [];\n \n // Each ant constructs a solution\n for (let ant = 0; ant < numAnts; ant++) {\n const available = Array.from({length: colors.length}, (_, i) => i);\n const solution = [];\n \n // Randomly select first color\n const firstIndex = Math.floor(prng() * available.length);\n solution.push(available[firstIndex]);\n available.splice(firstIndex, 1);\n \n // Select remaining colors\n while (solution.length < selectCount) {\n // Calculate probabilities for each available color\n const probabilities = available.map(i => {\n const pheromone = Math.pow(pheromones[i], alpha);\n const minDist = Math.min(...solution.map(j => distances[i][j]));\n const heuristic = Math.pow(minDist, beta);\n return pheromone * heuristic;\n });\n \n // Select next color using roulette wheel selection\n const total = probabilities.reduce((a, b) => a + b, 0);\n let random = prng() * total;\n let selectedIndex = 0;\n \n while (random > 0 && selectedIndex < probabilities.length) {\n random -= probabilities[selectedIndex];\n if (random > 0) selectedIndex++;\n }\n \n solution.push(available[selectedIndex]);\n available.splice(selectedIndex, 1);\n }\n \n solutions.push(solution);\n }\n \n // Evaluate solutions and update best\n for (const solution of solutions) {\n const fitness = Math.min(...solution.map((i, idx) => \n solution.slice(idx + 1).map(j => \n deltaE(labColors[i], labColors[j])\n )\n ).flat());\n \n if (fitness > bestFitness) {\n bestFitness = fitness;\n bestSolution = solution;\n }\n }\n \n // Update pheromones\n for (let i = 0; i < pheromones.length; i++) {\n pheromones[i] *= (1 - evaporationRate);\n }\n \n // Add new pheromones from solutions\n for (const solution of solutions) {\n const deposit = 1 / solution.length;\n for (const i of solution) {\n pheromones[i] += deposit;\n }\n }\n }\n \n return {\n colors: sortColors(bestSolution.map(i => colors[i])),\n time: performance.now() - start\n };\n} ","import { rgb2lab, deltaE, sortColors } from '../utils/colorUtils.js';\n\nexport function tabuSearch(colors, selectCount, settings = {}) {\n console.log('Starting Tabu Search...');\n const start = performance.now();\n \n const labColors = colors.map(rgb2lab);\n const maxIterations = settings.tabuIterations ?? 1000;\n const tabuTenure = settings.tabuTenure ?? 5;\n \n // Helper function to calculate minimum distance between selected colors\n function calculateFitness(selection) {\n let minDist = Infinity;\n for (let i = 0; i < selection.length - 1; i++) {\n for (let j = i + 1; j < selection.length; j++) {\n const dist = deltaE(labColors[selection[i]], labColors[selection[j]]);\n minDist = Math.min(minDist, dist);\n }\n }\n return minDist;\n }\n \n // Initialize solution\n let current = Array.from({length: selectCount}, (_, i) => i);\n let best = [...current];\n let bestFitness = calculateFitness(best);\n \n // Tabu list implementation using a Map to store move expiration\n const tabuList = new Map();\n \n // Generate move key for tabu list\n function getMoveKey(oldColor, newColor) {\n return `${oldColor}-${newColor}`;\n }\n \n for (let iteration = 0; iteration < maxIterations; iteration++) {\n let bestNeighborSolution = null;\n let bestNeighborFitness = -Infinity;\n \n // Examine all possible moves\n for (let i = 0; i < selectCount; i++) {\n for (let j = 0; j < colors.length; j++) {\n if (!current.includes(j)) {\n const moveKey = getMoveKey(current[i], j);\n const neighbor = [...current];\n neighbor[i] = j;\n \n const fitness = calculateFitness(neighbor);\n \n // Accept if better than current best neighbor and not tabu\n // or if satisfies aspiration criterion (better than global best)\n if ((fitness > bestNeighborFitness && \n (!tabuList.has(moveKey) || tabuList.get(moveKey) <= iteration)) ||\n fitness > bestFitness) {\n bestNeighborSolution = neighbor;\n bestNeighborFitness = fitness;\n }\n }\n }\n }\n \n if (!bestNeighborSolution) break;\n \n // Update current solution\n current = bestNeighborSolution;\n \n // Update best solution if improved\n if (bestNeighborFitness > bestFitness) {\n best = [...current];\n bestFitness = bestNeighborFitness;\n }\n \n // Update tabu list\n for (let i = 0; i < selectCount; i++) {\n const moveKey = getMoveKey(current[i], best[i]);\n tabuList.set(moveKey, iteration + tabuTenure);\n }\n \n // Clean expired tabu moves\n for (const [move, expiration] of tabuList.entries()) {\n if (expiration <= iteration) {\n tabuList.delete(move);\n }\n }\n }\n \n return {\n colors: sortColors(best.map(i => colors[i])),\n time: performance.now() - start\n };\n} ","import { rgb2lab, deltaE, sortColors } from '../utils/colorUtils.js';\n\nexport function exactMaximum(colors, selectCount) {\n console.log('Starting Exact Maximum calculation...');\n const start = performance.now();\n \n const labColors = colors.map(rgb2lab);\n let bestSelection = null;\n let bestMaxDistance = -Infinity;\n \n // Helper function to calculate maximum distance between selected colors\n function calculateMaxDistance(selection) {\n let maxDist = -Infinity;\n for (let i = 0; i < selection.length - 1; i++) {\n for (let j = i + 1; j < selection.length; j++) {\n const dist = deltaE(labColors[selection[i]], labColors[selection[j]]);\n maxDist = Math.max(maxDist, dist);\n }\n }\n return maxDist;\n }\n \n // Generate all possible combinations\n function* combinations(arr, r) {\n if (r === 1) {\n for (let i = 0; i < arr.length; i++) {\n yield [arr[i]];\n }\n return;\n }\n \n for (let i = 0; i < arr.length - r + 1; i++) {\n const head = arr[i];\n const remaining = arr.slice(i + 1);\n for (const tail of combinations(remaining, r - 1)) {\n yield [head, ...tail];\n }\n }\n }\n \n // Try all combinations\n const indices = Array.from({length: colors.length}, (_, i) => i);\n for (const selection of combinations(indices, selectCount)) {\n const maxDistance = calculateMaxDistance(selection);\n if (maxDistance > bestMaxDistance) {\n bestMaxDistance = maxDistance;\n bestSelection = selection;\n }\n }\n \n return {\n colors: sortColors(bestSelection.map(i => colors[i])),\n time: performance.now() - start\n };\n} ","import { rgb2lab, deltaE, sortColors } from '../utils/colorUtils.js';\n\nexport function exactMinimum(colors, selectCount) {\n console.log('Starting Exact Minimum calculation...');\n const start = performance.now();\n \n const labColors = colors.map(rgb2lab);\n let bestSelection = null;\n let bestMinDistance = -Infinity;\n \n // Helper function to calculate minimum distance between selected colors\n function calculateMinDistance(selection) {\n let minDist = Infinity;\n for (let i = 0; i < selection.length - 1; i++) {\n for (let j = i + 1; j < selection.length; j++) {\n const dist = deltaE(labColors[selection[i]], labColors[selection[j]]);\n minDist = Math.min(minDist, dist);\n }\n }\n return minDist;\n }\n \n // Generate all possible combinations\n function* combinations(arr, r) {\n if (r === 1) {\n for (let i = 0; i < arr.length; i++) {\n yield [arr[i]];\n }\n return;\n }\n \n for (let i = 0; i < arr.length - r + 1; i++) {\n const head = arr[i];\n const remaining = arr.slice(i + 1);\n for (const tail of combinations(remaining, r - 1)) {\n yield [head, ...tail];\n }\n }\n }\n \n // Try all combinations\n const indices = Array.from({length: colors.length}, (_, i) => i);\n for (const selection of combinations(indices, selectCount)) {\n const minDistance = calculateMinDistance(selection);\n if (minDistance > bestMinDistance) {\n bestMinDistance = minDistance;\n bestSelection = selection;\n }\n }\n \n return {\n colors: sortColors(bestSelection.map(i => colors[i])),\n time: performance.now() - start\n };\n} ","import { sortColors, mulberry32 } from '../utils/colorUtils.js';\n\nexport function randomSelection(colors, selectCount, seed) {\n console.log('Starting Random Selection...');\n const start = performance.now();\n \n // Use seeded PRNG if seed is provided\n const prng = typeof seed === 'number' ? mulberry32(seed) : Math.random;\n \n // Randomly select indices without replacement\n const indices = Array.from({length: colors.length}, (_, i) => i);\n const selected = [];\n \n for (let i = 0; i < selectCount; i++) {\n const randomIndex = Math.floor(prng() * indices.length);\n selected.push(indices[randomIndex]);\n indices.splice(randomIndex, 1);\n }\n \n return {\n colors: sortColors(selected.map(i => colors[i])),\n time: performance.now() - start\n };\n} ","import { randomColor, mulberry32 } from './colorUtils.js';\nimport { maxSumDistancesGlobal, maxSumDistancesSequential } from '../algorithms/maxSumDistances.js';\nimport { greedySelection } from '../algorithms/greedy.js';\nimport { kmeansppSelection } from '../algorithms/kmeans.js';\nimport { simulatedAnnealing } from '../algorithms/simulatedAnnealing.js';\nimport { geneticAlgorithm } from '../algorithms/genetic.js';\nimport { particleSwarmOptimization } from '../algorithms/particleSwarm.js';\nimport { antColonyOptimization } from '../algorithms/antColony.js';\nimport { tabuSearch } from '../algorithms/tabu.js';\nimport { exactMaximum } from '../algorithms/exactMaximum.js';\nimport { exactMinimum } from '../algorithms/exactMinimum.js';\nimport { randomSelection } from '../algorithms/random.js';\n\nconst ALGORITHMS = {\n greedy: greedySelection,\n maxSumDistancesGlobal,\n maxSumDistancesSequential,\n kmeansppSelection,\n simulatedAnnealing,\n geneticAlgorithm,\n particleSwarmOptimization,\n antColonyOptimization,\n tabuSearch,\n exactMaximum,\n exactMinimum,\n randomSelection,\n};\n\n/**\n * Pick a set of maximally distinct colors using the specified algorithm.\n *\n * Recommended usage (named arguments):\n * pickDistinctColors({ count, algorithm, poolSize, colors, options, seed })\n *\n * @param {object|number} args - Either an options object or the count (legacy positional signature).\n * @param {number} args.count - Number of colors to select.\n * @param {string} [args.algorithm='greedy'] - Algorithm name (see docs for options).\n * @param {number} [args.poolSize] - Number of random colors to generate if no pool is provided (default: Math.max(count * 16, 128)).\n * @param {number[][]} [args.colors] - Optional array of RGB colors to select from.\n * @param {object} [args.options] - Optional algorithm-specific options.\n * @param {number} [args.seed=42] - Seed for deterministic random color generation.\n * @returns {Promise<{colors: number[][], time: number}>} Selected colors and execution time.\n */\nexport async function pickDistinctColors(args, algorithm, poolSize, colors, options, seed) {\n // Support both: pickDistinctColors({ ... }) and pickDistinctColors(count, ...)\n let count, _algorithm, _poolSize, _colors, _options, _seed;\n if (typeof args === 'object' && args !== null && !Array.isArray(args)) {\n count = args.count;\n _algorithm = args.algorithm ?? 'greedy';\n _poolSize = args.poolSize;\n _colors = args.colors;\n _options = args.options;\n _seed = args.seed ?? 42;\n } else {\n // Legacy positional signature\n count = args;\n _algorithm = algorithm ?? 'greedy';\n _poolSize = poolSize;\n _colors = colors;\n _options = options;\n _seed = seed ?? 42;\n }\n if (!ALGORITHMS[_algorithm]) {\n throw new Error(`Unknown algorithm: ${_algorithm}`);\n }\n let pool = _colors;\n if (!Array.isArray(pool) || pool.length === 0) {\n const size = _poolSize || Math.max(count * 16, 128);\n const prng = mulberry32(_seed);\n pool = Array.from({ length: size }, () => randomColor(prng));\n }\n if (_algorithm === 'maxSumDistancesGlobal') {\n return await maxSumDistancesGlobal(pool, count);\n }\n if (_algorithm === 'maxSumDistancesSequential') {\n return maxSumDistancesSequential(pool, count, _seed);\n }\n if (_algorithm === 'greedy') {\n return greedySelection(pool, count, _seed);\n }\n if (_algorithm === 'kmeansppSelection') {\n return kmeansppSelection(pool, count, _seed);\n }\n if (_algorithm === 'simulatedAnnealing') {\n const opts = { ...(_options || {}), seed: _seed };\n return simulatedAnnealing(pool, count, opts);\n }\n if (_algorithm === 'geneticAlgorithm') {\n const opts = { ...(_options || {}), seed: _seed };\n return geneticAlgorithm(pool, count, opts);\n }\n if (_algorithm === 'particleSwarmOptimization') {\n const opts = { ...(_options || {}), seed: _seed };\n return particleSwarmOptimization(pool, count, opts);\n }\n if (_algorithm === 'antColonyOptimization') {\n const opts = { ...(_options || {}), seed: _seed };\n return antColonyOptimization(pool, count, opts);\n }\n if (_algorithm === 'tabuSearch') {\n const opts = { ...(_options || {}), seed: _seed };\n return tabuSearch(pool, count, opts);\n }\n if (_algorithm === 'exactMaximum') {\n return exactMaximum(pool, count);\n }\n if (_algorithm === 'exactMinimum') {\n return exactMinimum(pool, count);\n }\n if (_algorithm === 'randomSelection') {\n return randomSelection(pool, count, _seed);\n }\n throw new Error(`Algorithm not implemented: ${_algorithm}`);\n} "],"names":["rgb2lab","rgb","r","g","b","Math","pow","x","y","z","deltaE","labA","labB","deltaL","deltaA","deltaB","sqrt","mulberry32","seed","t","imul","randomColor","prng","random","floor","sortColors","colors","labColors","map","indices","Array","from","length","_","i","sort","j","L1","a1","b1","L2","a2","b2","calculateMetrics","minDist","Infinity","maxDist","sumDist","count","dist","min","max","avg","sum","analyzeColorDistribution","stats","L","range","a","chunkSize","chunk","slice","lab","coverage","toFixed","error","console","findClosestPair","closestPair","distance","maxSumDistancesGlobal","selectCount","Promise","resolve","reject","workerCode","toString","blob","Blob","type","worker","Worker","URL","createObjectURL","onmessage","e","data","result","Error","terminate","onerror","postMessage","maxSumDistancesSequential","log","start","performance","now","selected","available","calculateTotalDistance","index","reduce","selectedIndex","firstIndex","push","splice","bestIndex","bestDistance","totalDistance","time","greedySelection","calculateMinDistance","bestMinDistance","minDistance","kmeansppSelection","minDistanceToCenters","point","centers","center","distances","includes","simulatedAnnealing","settings","maxIterations","initialTemp","coolingRate","minTemp","calculateFitness","selection","currentSolution","currentFitness","bestSolution","bestFitness","temperature","neighborSolution","swapIndex","availableIndices","filter","newIndex","neighborFitness","delta","exp","geneticAlgorithm","populationSize","generations","mutationRate","population","fill","generation","fitnesses","maxFitnessIndex","indexOf","newPopulation","tournament1","tournament2","parent1","parent2","crossoverPoint","child","Set","mutationIndex","particleSwarmOptimization","numParticles","psoIterations","w","inertiaWeight","c1","cognitiveWeight","c2","socialWeight","particles","position","velocity","bestPosition","globalBestPosition","globalBestFitness","iteration","particle","fitness","r1","r2","antColonyOptimization","numAnts","acoIterations","evaporationRate","alpha","pheromoneImportance","beta","heuristicImportance","pheromones","solutions","ant","solution","probabilities","pheromone","heuristic","total","idx","flat","deposit","tabuSearch","tabuIterations","tabuTenure","current","best","tabuList","Map","getMoveKey","oldColor","newColor","bestNeighborSolution","bestNeighborFitness","moveKey","neighbor","has","get","set","move","expiration","entries","delete","exactMaximum","bestSelection","bestMaxDistance","calculateMaxDistance","combinations","arr","head","remaining","tail","maxDistance","exactMinimum","randomSelection","randomIndex","ALGORITHMS","greedy","pickDistinctColors","args","algorithm","poolSize","options","_algorithm","_poolSize","_colors","_options","_seed","isArray","pool","size","opts"],"mappings":"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