You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, numColors = 5, quality = 0.2, quantization = 32) {
// 1. Input validation and parameter sanitization
numColors = Math.max(1, Math.floor(numColors));
quality = Math.max(0.01, Math.min(1, quality)); // Quality between 0.01 (1%) and 1 (100%)
quantization = Math.max(1, Math.floor(quantization)); // Quantization factor, 1 means exact colors (after scaling)
// Helper function to convert RGB components to a hex color string
function rgbComponentsToHex(r, g, b) {
const componentToHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? "0" + hex : hex;
};
return ("#" + componentToHex(r) + componentToHex(g) + componentToHex(b)).toUpperCase();
}
// Helper function to parse an "rgb(r,g,b)" string into an object {r, g, b}
function parseRgbString(rgbString) {
const match = rgbString.match(/^rgb\((\d+),(\d+),(\d+)\)$/);
if (!match) return null;
return {
r: parseInt(match[1], 10),
g: parseInt(match[2], 10),
b: parseInt(match[3], 10)
};
}
// 2. Create canvas and draw scaled image
const canvas = document.createElement('canvas');
// Use naturalWidth/Height for intrinsic image dimensions
const imgNaturalWidth = originalImg.naturalWidth;
const imgNaturalHeight = originalImg.naturalHeight;
if (imgNaturalWidth === 0 || imgNaturalHeight === 0) {
const errorDiv = document.createElement('div');
errorDiv.textContent = "Image has zero dimensions. It might not be loaded correctly or is an invalid image.";
errorDiv.style.color = 'red';
errorDiv.style.padding = '10px';
errorDiv.style.border = '1px solid red';
return errorDiv;
}
// Ensure scaled dimensions are at least 1px
const scaledWidth = Math.max(1, Math.floor(imgNaturalWidth * quality));
const scaledHeight = Math.max(1, Math.floor(imgNaturalHeight * quality));
canvas.width = scaledWidth;
canvas.height = scaledHeight;
const ctx = canvas.getContext('2d');
if (!ctx) {
// This should theoretically not happen for a '2d' context in modern browsers
// unless canvas dimensions are excessively large or there are severe memory constraints.
const errorDiv = document.createElement('div');
errorDiv.textContent = "Could not get 2D rendering context for the canvas.";
errorDiv.style.color = 'red';
errorDiv.style.padding = '10px';
errorDiv.style.border = '1px solid red';
return errorDiv;
}
ctx.drawImage(originalImg, 0, 0, scaledWidth, scaledHeight);
// 3. Get image data
let imageDataArray;
try {
imageDataArray = ctx.getImageData(0, 0, scaledWidth, scaledHeight).data;
} catch (e) {
console.error("Error getting image data:", e);
const errorDiv = document.createElement('div');
let message = "Could not process image data. ";
if (e.name === "SecurityError") {
message += "This might be due to cross-origin restrictions (CORS). Ensure the image is served from the same domain or with appropriate CORS headers.";
} else {
message += `Details: ${e.message}`;
}
errorDiv.textContent = message;
errorDiv.style.color = 'red';
errorDiv.style.padding = '10px';
errorDiv.style.border = '1px solid red';
return errorDiv;
}
// 4. Count color frequencies
const colorCounts = {}; // Using an object as a hash map/dictionary
const alphaThreshold = 128; // Pixels with alpha value below this will be ignored
for (let i = 0; i < imageDataArray.length; i += 4) { // Iterate R,G,B,A quadruplets
const r_orig = imageDataArray[i];
const g_orig = imageDataArray[i + 1];
const b_orig = imageDataArray[i + 2];
const a = imageDataArray[i + 3];
if (a < alphaThreshold) {
continue; // Skip pixels that are significantly transparent
}
// Apply quantization to group similar colors.
// For each color component (R,G,B), divide by the quantization factor,
// round to the nearest whole number, then multiply by the quantization factor.
// Finally, clamp the value to the 0-255 range.
const r_quantized = Math.min(255, Math.round(r_orig / quantization) * quantization);
const g_quantized = Math.min(255, Math.round(g_orig / quantization) * quantization);
const b_quantized = Math.min(255, Math.round(b_orig / quantization) * quantization);
const colorKey = `rgb(${r_quantized},${g_quantized},${b_quantized})`;
colorCounts[colorKey] = (colorCounts[colorKey] || 0) + 1;
}
// 5. Sort colors by frequency in descending order
const sortedColors = Object.entries(colorCounts)
.sort((itemA, itemB) => itemB[1] - itemA[1]); // item[1] is the count
// 6. Get the top N most frequent colors
const dominantColorsData = sortedColors.slice(0, numColors);
// 7. Create and return the display element
const resultContainer = document.createElement('div');
resultContainer.style.display = 'flex';
resultContainer.style.flexDirection = 'row'; // Main axis for color swatches
resultContainer.style.flexWrap = 'wrap'; // Allow wrapping if many colors
resultContainer.style.gap = '10px'; // Spacing between swatches
resultContainer.style.padding = '10px';
resultContainer.style.fontFamily = 'Arial, Helvetica, sans-serif';
resultContainer.style.alignItems = 'flex-start'; // Align items at the start of the cross axis
if (dominantColorsData.length === 0) {
resultContainer.textContent = 'No dominant colors found. The image might be mostly transparent, or the applied settings (quality, quantization) resulted in no qualifying pixels.';
return resultContainer;
}
dominantColorsData.forEach(([colorRgbString, count]) => {
const swatchWrapper = document.createElement('div');
swatchWrapper.style.display = 'flex';
swatchWrapper.style.flexDirection = 'column'; // Stack color box and text vertically
swatchWrapper.style.alignItems = 'center'; // Center items horizontally
swatchWrapper.style.padding = '8px';
swatchWrapper.style.border = '1px solid #eee';
swatchWrapper.style.borderRadius = '4px';
swatchWrapper.style.boxShadow = '0 1px 3px rgba(0,0,0,0.1)';
swatchWrapper.setAttribute('title', `Approximate pixel count: ${count} (after scaling and quantization)`);
const swatchColorDiv = document.createElement('div');
swatchColorDiv.style.width = '60px';
swatchColorDiv.style.height = '60px';
swatchColorDiv.style.backgroundColor = colorRgbString;
swatchColorDiv.style.border = '1px solid #ccc'; // Subtle border for the color box
swatchColorDiv.style.borderRadius = '4px'; // Rounded corners for the color box
swatchColorDiv.style.marginBottom = '5px'; // Space between color box and text
const colorHexText = document.createElement('div');
const rgbParts = parseRgbString(colorRgbString);
let hexString = colorRgbString; // Fallback to RGB string if parsing fails
if (rgbParts) {
hexString = rgbComponentsToHex(rgbParts.r, rgbParts.g, rgbParts.b);
}
colorHexText.textContent = hexString;
colorHexText.style.fontSize = '12px';
colorHexText.style.color = '#333'; // Dark grey text for good readability
colorHexText.style.marginTop = 'auto'; // Push text to bottom if swatchWrapper has fixed height
colorHexText.style.textAlign = 'center';
swatchWrapper.appendChild(swatchColorDiv);
swatchWrapper.appendChild(colorHexText);
resultContainer.appendChild(swatchWrapper);
});
return resultContainer;
}
Apply Changes