You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, colorCount = 5, edgeThreshold = 40) {
/**
* Finds the most dominant colors in an image's pixel data using quantization.
* @param {Uint8ClampedArray} pixels - The pixel data from ImageData.
* @param {number} count - The number of dominant colors to return.
* @returns {Array<Object>} An array of dominant color objects { rgb, hex, count }.
*/
const getDominantColors = (pixels, count) => {
const colorMap = new Map();
// Quantization factor: higher value means fewer distinct colors and faster processing.
const quant = 32;
for (let i = 0; i < pixels.length; i += 4) {
// Ignore transparent or semi-transparent pixels
if (pixels[i + 3] < 128) {
continue;
}
const r = Math.round(pixels[i] / quant) * quant;
const g = Math.round(pixels[i + 1] / quant) * quant;
const b = Math.round(pixels[i + 2] / quant) * quant;
const key = `${r},${g},${b}`;
colorMap.set(key, (colorMap.get(key) || 0) + 1);
}
const sortedColors = Array.from(colorMap.entries()).sort((a, b) => b[1] - a[1]);
return sortedColors.slice(0, count).map(entry => {
const [r, g, b] = entry[0].split(',').map(Number);
// Helper to convert RGB component to a two-digit hex string
const toHex = c => ('0' + c.toString(16)).slice(-2);
const hex = `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
return { rgb: `rgb(${r},${g},${b})`, hex: hex, count: entry[1] };
});
};
/**
* Calculates the brightness histogram for the image pixel data.
* @param {Uint8ClampedArray} pixels - The pixel data from ImageData.
* @returns {Array<number>} An array of 256 numbers representing the histogram.
*/
const getBrightnessHistogram = (pixels) => {
const histogram = new Array(256).fill(0);
for (let i = 0; i < pixels.length; i += 4) {
if (pixels[i + 3] < 128) continue;
const r = pixels[i];
const g = pixels[i + 1];
const b = pixels[i + 2];
// Using the luminosity formula for perceived brightness
const brightness = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
histogram[brightness]++;
}
return histogram;
};
/**
* Creates a simple edge-detected image from source image data.
* @param {ImageData} sourceImageData - The source image data to process.
* @param {number} threshold - The sensitivity for edge detection.
* @returns {ImageData} A new ImageData object containing the black and white edges.
*/
const getEdgeData = (sourceImageData, threshold) => {
const { width, height, data: sourceData } = sourceImageData;
const edgeImageData = new ImageData(width, height);
const edgeData = edgeImageData.data;
const toGray = (r, g, b) => 0.299 * r + 0.587 * g + 0.114 * b;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = (y * width + x) * 4;
// Set image borders to white
if (x === 0 || x === width - 1 || y === 0 || y === height - 1) {
edgeData[i] = edgeData[i + 1] = edgeData[i + 2] = 255;
edgeData[i + 3] = 255;
continue;
}
const currentGray = toGray(sourceData[i], sourceData[i + 1], sourceData[i + 2]);
const rightGray = toGray(sourceData[i + 4], sourceData[i + 5], sourceData[i + 6]);
const bottomGray = toGray(sourceData[i + (width * 4)], sourceData[i + (width * 4) + 1], sourceData[i + (width * 4) + 2]);
// Calculate magnitude using a simple difference method
const magnitude = Math.abs(currentGray - rightGray) + Math.abs(currentGray - bottomGray);
const color = magnitude > threshold ? 0 : 255; // Black for edges, white for non-edges
edgeData[i] = edgeData[i + 1] = edgeData[i + 2] = color;
edgeData[i + 3] = 255;
}
}
return edgeImageData;
};
// 1. Setup Canvas
const w = originalImg.width;
const h = originalImg.height;
const analysisPaneWidth = Math.max(300, w * 0.5);
const canvas = document.createElement('canvas');
canvas.width = w + analysisPaneWidth;
canvas.height = h;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// 2. Draw background and original image
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(originalImg, 0, 0, w, h);
ctx.fillStyle = '#f8f9fa';
ctx.fillRect(w, 0, analysisPaneWidth, h);
ctx.strokeStyle = '#dee2e6';
ctx.lineWidth = 1;
ctx.strokeRect(w, 0, analysisPaneWidth, h);
// 3. Create a downscaled temporary canvas for faster analysis
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
const scale = Math.min(200 / w, 200 / h, 1);
tempCanvas.width = w * scale;
tempCanvas.height = h * scale;
tempCtx.drawImage(originalImg, 0, 0, tempCanvas.width, tempCanvas.height);
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
const pixels = imageData.data;
// --- RENDER ANALYSIS PANE ---
let currentY = 15;
const leftMargin = w + 20;
const contentWidth = analysisPaneWidth - 40;
// A. Title
ctx.fillStyle = '#212529';
ctx.font = 'bold 18px "Segoe UI", Arial, sans-serif';
ctx.fillText('Image Analysis Report', leftMargin, currentY + 18);
currentY += 45;
// B. Dominant Colors
if (currentY + colorCount * 33 < h - 50) {
ctx.font = 'bold 14px "Segoe UI", Arial, sans-serif';
ctx.fillText('Dominant Colors', leftMargin, currentY);
currentY += 25;
const dominantColors = getDominantColors(pixels, colorCount);
dominantColors.forEach(color => {
ctx.fillStyle = color.hex;
ctx.fillRect(leftMargin, currentY, 25, 25);
ctx.font = '13px "Courier New", monospace';
ctx.fillStyle = '#343a40';
ctx.textBaseline = 'middle';
ctx.fillText(color.hex, leftMargin + 35, currentY + 12.5);
ctx.textBaseline = 'alphabetic';
currentY += 33;
});
currentY += 10;
}
// C. Brightness Distribution
if (currentY < h - 110) {
ctx.fillStyle = '#212529';
ctx.font = 'bold 14px "Segoe UI", Arial, sans-serif';
ctx.fillText('Brightness Distribution', leftMargin, currentY);
currentY += 25;
const histogram = getBrightnessHistogram(pixels);
const histHeight = 60;
const histWidth = contentWidth;
const maxHistValue = Math.max(...histogram);
ctx.fillStyle = '#e9ecef';
ctx.fillRect(leftMargin, currentY, histWidth, histHeight);
ctx.strokeStyle = '#adb5bd';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(leftMargin, currentY + histHeight);
for(let i = 0; i < 256; i++) {
const barHeight = (histogram[i] / maxHistValue) * histHeight;
ctx.lineTo(leftMargin + (i / 255) * histWidth, currentY + histHeight - barHeight);
}
ctx.stroke();
ctx.font = '11px "Segoe UI", Arial, sans-serif';
ctx.fillStyle = '#6c757d';
ctx.fillText('Dark', leftMargin, currentY + histHeight + 15);
ctx.textAlign = 'right';
ctx.fillText('Bright', leftMargin + histWidth, currentY + histHeight + 15);
ctx.textAlign = 'left';
currentY += histHeight + 30;
}
// D. Structural Outline
if (currentY < h - 100) {
ctx.fillStyle = '#212529';
ctx.font = 'bold 14px "Segoe UI", Arial, sans-serif';
ctx.fillText('Structural Outline', leftMargin, currentY);
currentY += 25;
const edgeImageData = getEdgeData(imageData, edgeThreshold);
const edgeImageBitmap = await createImageBitmap(edgeImageData);
const previewHeight = contentWidth * (edgeImageBitmap.height / edgeImageBitmap.width);
if (currentY + previewHeight < h - 10) {
ctx.drawImage(edgeImageBitmap, leftMargin, currentY, contentWidth, previewHeight);
}
}
return canvas;
}
Apply Changes