You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, numColors = 5, targetLanguage = 'ru', sortOrder = 'popularity') {
/**
* Finds the most dominant colors in an image, finds their common names,
* and provides translations for those names.
*
* @param {Image} originalImg - The source Image object.
* @param {number} numColors - The number of dominant colors to identify.
* @param {string} targetLanguage - The language for color names (e.g., 'en', 'es', 'fr', 'de', 'ru').
* @param {string} sortOrder - How to sort the results: 'popularity' or 'alphabetical'.
* @returns {HTMLElement} A div element containing the image and a legend of identified colors.
*/
// --- Helper Functions ---
const rgbToHex = (r, g, b) => '#' + [r, g, b].map(x => {
const hex = x.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}).join('');
const hexToRgb = (hex) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
};
const colorDistance = (rgb1, rgb2) => {
const rDiff = rgb1.r - rgb2.r;
const gDiff = rgb1.g - rgb2.g;
const bDiff = rgb1.b - rgb2.b;
return Math.sqrt(rDiff * rDiff + gDiff * gDiff + bDiff * bDiff);
};
const getNamedColorData = () => {
// A curated list of common colors with names and translations.
// In a real-world app, this might be fetched from an API.
return [
{ hex: '#000000', name: 'Black', translations: { en: 'Black', es: 'Negro', fr: 'Noir', de: 'Schwarz', ru: 'Черный' } },
{ hex: '#FFFFFF', name: 'White', translations: { en: 'White', es: 'Blanco', fr: 'Blanc', de: 'Weiß', ru: 'Белый' } },
{ hex: '#FF0000', name: 'Red', translations: { en: 'Red', es: 'Rojo', fr: 'Rouge', de: 'Rot', ru: 'Красный' } },
{ hex: '#00FF00', name: 'Lime', translations: { en: 'Lime', es: 'Lima', fr: 'Citron vert', de: 'Limette', ru: 'Лайм' } },
{ hex: '#0000FF', name: 'Blue', translations: { en: 'Blue', es: 'Azul', fr: 'Bleu', de: 'Blau', ru: 'Синий' } },
{ hex: '#FFFF00', name: 'Yellow', translations: { en: 'Yellow', es: 'Amarillo', fr: 'Jaune', de: 'Gelb', ru: 'Желтый' } },
{ hex: '#00FFFF', name: 'Cyan', translations: { en: 'Cyan', es: 'Cian', fr: 'Cyan', de: 'Cyan', ru: 'Голубой' } },
{ hex: '#FF00FF', name: 'Magenta', translations: { en: 'Magenta', es: 'Magenta', fr: 'Magenta', de: 'Magenta', ru: 'Пурпурный' } },
{ hex: '#808080', name: 'Gray', translations: { en: 'Gray', es: 'Gris', fr: 'Gris', de: 'Grau', ru: 'Серый' } },
{ hex: '#C0C0C0', name: 'Silver', translations: { en: 'Silver', es: 'Plata', fr: 'Argent', de: 'Silber', ru: 'Серебряный' } },
{ hex: '#800000', name: 'Maroon', translations: { en: 'Maroon', es: 'Marrón', fr: 'Marron', de: 'Kastanienbraun', ru: 'Бордовый' } },
{ hex: '#808000', name: 'Olive', translations: { en: 'Olive', es: 'Oliva', fr: 'Olive', de: 'Oliv', ru: 'Оливковый' } },
{ hex: '#008000', name: 'Green', translations: { en: 'Green', es: 'Verde', fr: 'Vert', de: 'Grün', ru: 'Зеленый' } },
{ hex: '#800080', name: 'Purple', translations: { en: 'Purple', es: 'Púrpura', fr: 'Violet', de: 'Lila', ru: 'Фиолетовый' } },
{ hex: '#008080', name: 'Teal', translations: { en: 'Teal', es: 'Cerceta', fr: 'Sarcelle', de: 'Blaugrün', ru: 'Бирюзовый' } },
{ hex: '#000080', name: 'Navy', translations: { en: 'Navy', es: 'Marino', fr: 'Marine', de: 'Marine', ru: 'Темно-синий' } },
{ hex: '#FFA500', name: 'Orange', translations: { en: 'Orange', es: 'Naranja', fr: 'Orange', de: 'Orange', ru: 'Оранжевый' } },
{ hex: '#FFC0CB', name: 'Pink', translations: { en: 'Pink', es: 'Rosa', fr: 'Rose', de: 'Rosa', ru: 'Розовый' } },
{ hex: '#A52A2A', name: 'Brown', translations: { en: 'Brown', es: 'Marrón', fr: 'Brun', de: 'Braun', ru: 'Коричневый' } },
{ hex: '#F5F5DC', name: 'Beige', translations: { en: 'Beige', es: 'Beige', fr: 'Beige', de: 'Beige', ru: 'Бежевый' } },
{ hex: '#FFD700', name: 'Gold', translations: { en: 'Gold', es: 'Oro', fr: 'Or', de: 'Gold', ru: 'Золотой' } },
{ hex: '#ADD8E6', name: 'Light Blue', translations: { en: 'Light Blue', es: 'Azul claro', fr: 'Bleu clair', de: 'Hellblau', ru: 'Светло-синий' } },
{ hex: '#87CEEB', name: 'Sky Blue', translations: { en: 'Sky Blue', es: 'Azul cielo', fr: 'Bleu ciel', de: 'Himmelblau', ru: 'Небесно-голубой' } },
{ hex: '#4682B4', name: 'Steel Blue', translations: { en: 'Steel Blue', es: 'Azul acero', fr: 'Bleu acier', de: 'Stahlblau', ru: 'Стальной синий' } },
{ hex: '#90EE90', name: 'Light Green', translations: { en: 'Light Green', es: 'Verde claro', fr: 'Vert clair', de: 'Hellgrün', ru: 'Светло-зеленый' } },
{ hex: '#D2B48C', name: 'Tan', translations: { en: 'Tan', es: 'Canela', fr: 'Tan', de: 'Hellbraun', ru: 'Желто-коричневый' } },
{ hex: '#E6E6FA', name: 'Lavender', translations: { en: 'Lavender', es: 'Lavanda', fr: 'Lavande', de: 'Lavendel', ru: 'Лавандовый' } },
{ hex: '#EE82EE', name: 'Violet', translations: { en: 'Violet', es: 'Violeta', fr: 'Violet', de: 'Violett', ru: 'Лиловый' } },
{ hex: '#4B0082', name: 'Indigo', translations: { en: 'Indigo', es: 'Índigo', fr: 'Indigo', de: 'Indigo', ru: 'Индиго' } },
];
};
// --- Step 1: Extract Pixels and Find Dominant Colors ---
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Resize image for faster processing
const maxDim = 200;
const scale = Math.min(maxDim / originalImg.width, maxDim / originalImg.height, 1);
canvas.width = originalImg.width * scale;
canvas.height = originalImg.height * scale;
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data;
const colorCounts = {};
// Sample pixels for performance: iterate every 5 pixels
const samplingRate = 5;
for (let i = 0; i < pixels.length; i += 4 * samplingRate) {
const r = pixels[i];
const g = pixels[i + 1];
const b = pixels[i + 2];
const a = pixels[i + 3];
if (a < 128) continue; // Skip transparent/semi-transparent pixels
// Quantize colors to group similar shades. This reduces the number of unique colors.
const quantizeFactor = 16;
const qR = Math.round(r / quantizeFactor) * quantizeFactor;
const qG = Math.round(g / quantizeFactor) * quantizeFactor;
const qB = Math.round(b / quantizeFactor) * quantizeFactor;
const hex = rgbToHex(qR, qG, qB);
colorCounts[hex] = (colorCounts[hex] || 0) + 1;
}
// Sort colors by frequency to find the most dominant ones
const dominantColors = Object.entries(colorCounts)
.sort(([, countA], [, countB]) => countB - countA)
.slice(0, numColors)
.map(([hex]) => hex);
// --- Step 2 & 3: Name and Translate Colors ---
const colorNameList = getNamedColorData();
const identifiedColors = [];
for (const hex of dominantColors) {
const rgb = hexToRgb(hex);
if (!rgb) continue;
let closestMatch = null;
let minDistance = Infinity;
// Find the nearest named color from our list
for (const namedColor of colorNameList) {
const namedRgb = hexToRgb(namedColor.hex);
if (!namedRgb) continue;
const distance = colorDistance(rgb, namedRgb);
if (distance < minDistance) {
minDistance = distance;
closestMatch = namedColor;
}
}
if (closestMatch) {
identifiedColors.push({
hex: hex,
name: closestMatch.name,
translation: closestMatch.translations[targetLanguage] || closestMatch.name,
});
}
}
// --- Step 4: Sort Results ---
if (sortOrder === 'alphabetical') {
identifiedColors.sort((a, b) => a.translation.localeCompare(b.translation));
}
// --- Step 5: Create Output Element ---
const container = document.createElement('div');
container.style.cssText = `
display: flex;
flex-direction: column;
align-items: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
gap: 16px;
padding: 10px;
max-width: 100%;
`;
// Display the original image
const imageCanvas = document.createElement('canvas');
imageCanvas.width = originalImg.width;
imageCanvas.height = originalImg.height;
imageCanvas.getContext('2d').drawImage(originalImg, 0, 0);
imageCanvas.style.cssText = `
max-width: 100%;
height: auto;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
`;
const legend = document.createElement('div');
legend.style.cssText = `
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
width: 100%;
max-width: 600px;
`;
for (const color of identifiedColors) {
const item = document.createElement('div');
item.style.cssText = `
display: flex;
align-items: center;
padding: 8px;
border: 1px solid #eee;
border-radius: 6px;
background-color: #f9f9f9;
`;
const swatch = document.createElement('div');
swatch.style.cssText = `
width: 24px;
height: 24px;
min-width: 24px;
background-color: ${color.hex};
border: 1px solid rgba(0,0,0,0.1);
margin-right: 10px;
border-radius: 4px;
`;
const textContainer = document.createElement('div');
const nameText = document.createElement('div');
nameText.textContent = color.translation;
nameText.style.fontWeight = 'bold';
nameText.style.fontSize = '14px';
const hexText = document.createElement('div');
hexText.textContent = color.hex.toUpperCase();
hexText.style.fontSize = '12px';
hexText.style.color = '#666';
textContainer.appendChild(nameText);
textContainer.appendChild(hexText);
item.appendChild(swatch);
item.appendChild(textContainer);
legend.appendChild(item);
}
container.appendChild(imageCanvas);
if (identifiedColors.length > 0) {
container.appendChild(legend);
} else {
const noColorsMsg = document.createElement('p');
noColorsMsg.textContent = 'Could not identify any dominant colors.';
container.appendChild(noColorsMsg);
}
return container;
}
Apply Changes