You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, columns = '3', rows = '3', edgeThreshold = '60', style = 'neon') {
// Parse parameters
const cols = Math.max(1, parseInt(columns, 10) || 3);
const rws = Math.max(1, parseInt(rows, 10) || 3);
const thresh = Math.max(0, parseInt(edgeThreshold, 10) || 60);
const w = originalImg.width;
const h = originalImg.height;
// 1. Setup temporary canvas to get raw pixel data
const tempCanvas = document.createElement('canvas');
tempCanvas.width = w;
tempCanvas.height = h;
const tCtx = tempCanvas.getContext('2d');
tCtx.drawImage(originalImg, 0, 0);
// Safety check for empty or zero-size images
if (w === 0 || h === 0) return tempCanvas;
const imgData = tCtx.getImageData(0, 0, w, h);
const data = imgData.data;
// 2. Perform Sobel Edge Detection
const edgePixels = new Uint8ClampedArray(w * h * 4);
// Pre-calculate grayscale values for performance
const gray = new Uint8Array(w * h);
for(let i = 0, j = 0; i < data.length; i += 4, j++) {
// Standard luminescence formula
gray[j] = data[i] * 0.299 + data[i+1] * 0.587 + data[i+2] * 0.114;
}
// Apply Sobel operators
for (let y = 1; y < h - 1; y++) {
for (let x = 1; x < w - 1; x++) {
const i = y * w + x;
// Get surrounding grayscale pixels
const g00 = gray[i - w - 1], g01 = gray[i - w], g02 = gray[i - w + 1];
const g10 = gray[i - 1], g12 = gray[i + 1];
const g20 = gray[i + w - 1], g21 = gray[i + w], g22 = gray[i + w + 1];
// Horizontal & Vertical gradients
const gx = -g00 + g02 - 2 * g10 + 2 * g12 - g20 + g22;
const gy = g00 + 2 * g01 + g02 - g20 - 2 * g21 - g22;
// Gradient magnitude
const mag = Math.sqrt(gx * gx + gy * gy);
// Apply threshold: edges are white, background is black
const val = mag > thresh ? 255 : 0;
const baseIdx = i * 4;
edgePixels[baseIdx] = val; // R
edgePixels[baseIdx+1] = val; // G
edgePixels[baseIdx+2] = val; // B
edgePixels[baseIdx+3] = 255; // A
}
}
// 3. Create a canvas containing the base edge map
const edgeCanvas = document.createElement('canvas');
edgeCanvas.width = w;
edgeCanvas.height = h;
const eCtx = edgeCanvas.getContext('2d');
eCtx.putImageData(new ImageData(edgePixels, w, h), 0, 0);
// 4. Setup the final collage canvas
const finalCanvas = document.createElement('canvas');
finalCanvas.width = w;
finalCanvas.height = h;
const fCtx = finalCanvas.getContext('2d');
// Fill background black
fCtx.fillStyle = '#000000';
fCtx.fillRect(0, 0, w, h);
const cellW = w / cols;
const cellH = h / rws;
const neonColors = ['#FF00FF', '#00FFFF', '#00FF00', '#FFFF00', '#FF3300', '#3300FF'];
// 5. Construct the Video Collage Grid
for (let r = 0; r < rws; r++) {
for (let c = 0; c < cols; c++) {
const dx = c * cellW;
const dy = r * cellH;
fCtx.save();
// Clip drawing to just this cell's boundaries bounds
fCtx.beginPath();
fCtx.rect(dx, dy, cellW, cellH);
fCtx.clip();
// Draw the edge map scaled down into the cell
fCtx.drawImage(edgeCanvas, dx, dy, cellW, cellH);
// Determine cell tint
let cellColor = '#FFFFFF';
if (style === 'neon') {
cellColor = neonColors[(r * cols + c) % neonColors.length];
} else if (style === 'random') {
cellColor = `hsl(${Math.floor(Math.random() * 360)}, 100%, 60%)`;
} else if (style === 'matrix') {
cellColor = '#00FF41';
}
// Apply the tint using multiply blending (colors the white edges, ignores black bg)
if (style !== 'grayscale') {
fCtx.globalCompositeOperation = 'multiply';
fCtx.fillStyle = cellColor;
fCtx.fillRect(dx, dy, cellW, cellH);
fCtx.globalCompositeOperation = 'source-over';
}
// Add Video Aesthetics / HUD logic
// CRT scanlines effect for this cell
fCtx.fillStyle = 'rgba(0, 0, 0, 0.25)';
for (let sy = dy; sy < dy + cellH; sy += 4) {
fCtx.fillRect(dx, sy, cellW, 2);
}
// Border
fCtx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
fCtx.lineWidth = 2;
fCtx.strokeRect(dx, dy, cellW, cellH);
// Text Setup
const textMargin = Math.min(cellW, cellH) * 0.05;
const fontSize = Math.max(10, cellH * 0.08);
fCtx.font = `bold ${fontSize}px "Courier New", Courier, monospace`;
// Recording Dot
const dotX = dx + textMargin + 5;
const dotY = dy + textMargin + 5;
fCtx.fillStyle = '#ff0000';
fCtx.beginPath();
fCtx.arc(dotX, dotY, fontSize * 0.4, 0, Math.PI * 2);
fCtx.fill();
// Camera Name
fCtx.fillStyle = 'rgba(255, 255, 255, 0.85)';
fCtx.fillText(`CAM 0${r * cols + c + 1}`, dx + textMargin + (fontSize * 1.2), dy + textMargin + (fontSize * 0.8));
// Timestamp simulation
const fakeTime = new Date();
fakeTime.setMinutes(fakeTime.getMinutes() - (r * cols + c) * 15);
const timeStr = fakeTime.toISOString().split('T')[1].substring(0,8);
fCtx.fillText(`REC ${timeStr}`, dx + textMargin, dy + cellH - textMargin);
fCtx.restore();
}
}
return finalCanvas;
}
Apply Changes