You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, blockSizeStr = "12", varianceThresholdStr = "80", mergeRadiusStr = "2") {
const canvas = document.createElement('canvas');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
const ctx = canvas.getContext('2d');
// Draw original image to canvas
ctx.drawImage(originalImg, 0, 0);
const bSize = parseInt(blockSizeStr, 10) || 12;
const thresh = parseFloat(varianceThresholdStr) || 80;
const mRad = parseInt(mergeRadiusStr, 10) || 2;
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imgData.data;
const cols = Math.ceil(canvas.width / bSize);
const rows = Math.ceil(canvas.height / bSize);
const blocks = new Uint8Array(cols * rows);
// Step 1: Detect active blocks based on color variance
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
let sum = 0;
let sqSum = 0;
let count = 0;
let maxGray = 0;
let minGray = 255;
for(let y = r * bSize; y < (r+1) * bSize && y < canvas.height; y++) {
for(let x = c * bSize; x < (c+1) * bSize && x < canvas.width; x++) {
let i = (y * canvas.width + x) * 4;
// Calculate grayscale luminosity
let gray = 0.299 * data[i] + 0.587 * data[i+1] + 0.114 * data[i+2];
sum += gray;
sqSum += gray * gray;
count++;
}
}
if (count > 0) {
let mean = sum / count;
let variance = (sqSum / count) - (mean * mean);
// Flag blocks with significant variance (edges, text, boundaries)
if (variance > thresh) {
blocks[r * cols + c] = 1;
}
}
}
}
// Step 2: Connected Component Labeling via DFS
const labels = new Int32Array(cols * rows);
let nextLabel = 1;
function dfs(startR, startC, label) {
const stack = [[startR, startC]];
labels[startR * cols + startC] = label;
while(stack.length > 0) {
const [currR, currC] = stack.pop();
// Search in a window based on mergeRadius to cluster sparse elements (e.g., text)
for (let dr = -mRad; dr <= mRad; dr++) {
for (let dc = -mRad; dc <= mRad; dc++) {
let nr = currR + dr;
let nc = currC + dc;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {
let nIdx = nr * cols + nc;
if (blocks[nIdx] === 1 && labels[nIdx] === 0) {
labels[nIdx] = label;
stack.push([nr, nc]);
}
}
}
}
}
}
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
let idx = r * cols + c;
if (blocks[idx] === 1 && labels[idx] === 0) {
dfs(r, c, nextLabel++);
}
}
}
// Step 3: Find bounding boxes for each labeled component
const bboxes = {};
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
let label = labels[r * cols + c];
if (label > 0) {
if (!bboxes[label]) {
bboxes[label] = { minR: r, maxR: r, minC: c, maxC: c, count: 0 };
}
bboxes[label].minR = Math.min(bboxes[label].minR, r);
bboxes[label].maxR = Math.max(bboxes[label].maxR, r);
bboxes[label].minC = Math.min(bboxes[label].minC, c);
bboxes[label].maxC = Math.max(bboxes[label].maxC, c);
bboxes[label].count++;
}
}
}
// Step 4: Analyze and render identified UI elements
ctx.lineWidth = 2;
const colors = ['#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8'];
let elCount = 0;
for (let label in bboxes) {
const box = bboxes[label];
let x = box.minC * bSize;
let y = box.minR * bSize;
let w = (box.maxC - box.minC + 1) * bSize;
let h = (box.maxR - box.minR + 1) * bSize;
// Add aesthetic padding around elements
let px = Math.max(0, x - bSize/2);
let py = Math.max(0, y - bSize/2);
let pw = Math.min(canvas.width - px, w + bSize);
let ph = Math.min(canvas.height - py, h + bSize);
// Skip bounding boxes that cover almost the entire page (like background images)
if (pw >= canvas.width * 0.95 && ph >= canvas.height * 0.95) {
continue;
}
// Infer UI element type based on dimensions & proportions
let type = "Container";
let ratio = pw / ph;
if (pw >= canvas.width * 0.8 && ph <= 120) {
type = "Navbar / Header";
} else if (pw >= canvas.width * 0.7 && ph >= canvas.height * 0.6) {
type = "Main Content Segment";
} else if (ratio > 10 && ph <= 30) {
type = "Divider / Line";
} else if (ratio > 3.5 && ph <= 60) {
type = "Text / Input";
} else if (ratio < 1.5 && ratio > 0.6) {
if (pw <= 65 && ph <= 65) type = "Icon / Avatar";
else if (pw >= 120 && ph >= 120) type = "Image";
else type = "Card";
} else if (ratio >= 1.5 && ratio <= 4) {
if (ph <= 55) type = "Button / Tab";
else type = "Container / Panel";
} else {
type = "UI Element";
}
const color = colors[elCount % colors.length];
// Draw main bounding box
ctx.strokeStyle = color;
ctx.strokeRect(px, py, pw, ph);
// Draw crosshair-style corners for a "scanner" / "identifier" visual theme
ctx.beginPath();
let cLen = Math.min(10, pw/4, ph/4);
ctx.moveTo(px, py + cLen); ctx.lineTo(px, py); ctx.lineTo(px + cLen, py);
ctx.moveTo(px + pw - cLen, py); ctx.lineTo(px + pw, py); ctx.lineTo(px + pw, py + cLen);
ctx.moveTo(px, py + ph - cLen); ctx.lineTo(px, py + ph); ctx.lineTo(px + cLen, py + ph);
ctx.moveTo(px + pw - cLen, py + ph); ctx.lineTo(px + pw, py + ph); ctx.lineTo(px + pw, py + ph - cLen);
ctx.stroke();
ctx.fillStyle = color;
ctx.globalAlpha = 0.15;
ctx.fillRect(px, py, pw, ph);
ctx.globalAlpha = 1.0;
// Calculate appropriate font size for label
let fontSize = Math.max(12, Math.floor(Math.min(pw, ph) * 0.15));
fontSize = Math.min(fontSize, 16);
ctx.font = `bold ${fontSize}px Arial, sans-serif`;
// Draw label background pill
let textMetrics = ctx.measureText(type);
let ty = py - fontSize - 6;
if (ty < 0) ty = py; // Flow downwards if hitting top boundary
ctx.fillStyle = color;
ctx.fillRect(px - 1, ty, textMetrics.width + 12, fontSize + 8);
// Draw label text
ctx.fillStyle = '#111111'; // High contrast text over pastel colors
ctx.fillText(type, px + 5, ty + fontSize + 2);
elCount++;
}
return canvas;
}
Apply Changes