You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, wireColorStr = "200,200,200", bgColorStr = "30,30,30", edgeThreshold = 50, wireThickness = 1, invertColorsStr = "false") {
// Helper to parse "R,G,B" string into an object {r, g, b}
function parseColor(colorStr) {
const str = String(colorStr); // Ensure it's a string
const parts = str.split(',').map(s => parseInt(s.trim(), 10));
return {
r: (parts.length > 0 && !isNaN(parts[0])) ? Math.max(0, Math.min(255, parts[0])) : 0,
g: (parts.length > 1 && !isNaN(parts[1])) ? Math.max(0, Math.min(255, parts[1])) : 0,
b: (parts.length > 2 && !isNaN(parts[2])) ? Math.max(0, Math.min(255, parts[2])) : 0
};
}
// --- Parameter Sanity Checks and Parsing ---
const finalWireColorObj = parseColor(wireColorStr);
const finalBgColorObj = parseColor(bgColorStr);
const invert = String(invertColorsStr).toLowerCase() === "true";
const wColor = invert ? finalBgColorObj : finalWireColorObj;
const bColor = invert ? finalWireColorObj : finalBgColorObj;
let numWireThickness = (typeof wireThickness === 'string') ? parseInt(wireThickness, 10) : wireThickness;
if (isNaN(numWireThickness) || numWireThickness < 1) {
numWireThickness = 1; // Default to 1 if invalid or less than 1
}
const T_thickness = numWireThickness;
let numEdgeThreshold = (typeof edgeThreshold === 'string') ? parseInt(edgeThreshold, 10) : edgeThreshold;
if (isNaN(numEdgeThreshold)) {
numEdgeThreshold = 50; // Default if invalid
}
// Clamp threshold to a practical range for normalized gradients (0-255)
const clampedEdgeThreshold = Math.max(0, Math.min(255, numEdgeThreshold));
// --- Canvas Setup ---
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.naturalWidth || originalImg.width || 0;
const height = originalImg.naturalHeight || originalImg.height || 0;
if (width === 0 || height === 0) {
console.error("Image has zero dimensions. Returning a minimal canvas.");
canvas.width = 1;
canvas.height = 1;
// Optionally fill with a default color or leave blank
const tempCtx = canvas.getContext('2d');
if (tempCtx) {
tempCtx.fillStyle = `rgb(${bColor.r},${bColor.g},${bColor.b})`;
tempCtx.fillRect(0,0,1,1);
}
return canvas;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(originalImg, 0, 0, width, height);
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
// --- Image Processing ---
const grayscaleData = new Uint8ClampedArray(width * height);
const gradientData = new Float32Array(width * height); // Use Float32Array for precision
// 1. Grayscale Conversion
for (let i = 0, j = 0; i < data.length; i += 4, j++) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
grayscaleData[j] = 0.299 * r + 0.587 * g + 0.114 * b;
}
// 2. Sobel Edge Detection
const Gx_kernel = [
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
];
const Gy_kernel = [
[-1, -2, -1],
[ 0, 0, 0],
[ 1, 2, 1]
];
let maxGradient = 0;
for (let y = 1; y < height - 1; y++) { // Iterate skipping border pixels
for (let x = 1; x < width - 1; x++) {
let sumX = 0;
let sumY = 0;
for (let ky = -1; ky <= 1; ky++) {
for (let kx = -1; kx <= 1; kx++) {
const pixelIndex = (y + ky) * width + (x + kx);
const grayVal = grayscaleData[pixelIndex];
sumX += grayVal * Gx_kernel[ky + 1][kx + 1];
sumY += grayVal * Gy_kernel[ky + 1][kx + 1];
}
}
const magnitude = Math.sqrt(sumX * sumX + sumY * sumY);
gradientData[y * width + x] = magnitude;
if (magnitude > maxGradient) {
maxGradient = magnitude;
}
}
}
// Normalize gradient data to 0-255 range
const normalizedGradientData = new Uint8ClampedArray(width * height);
if (maxGradient > 0) { // Avoid division by zero for flat images
for (let i = 0; i < gradientData.length; i++) {
// Border pixels (not processed by Sobel loop) will have gradientData[i] = 0
// as Float32Array is zero-initialized.
normalizedGradientData[i] = (gradientData[i] / maxGradient) * 255;
}
}
// 3. Render Output Image
const outputImageData = ctx.createImageData(width, height);
const outputData = outputImageData.data;
// Initialize output with background color
for (let i = 0; i < outputData.length; i += 4) {
outputData[i] = bColor.r;
outputData[i + 1] = bColor.g;
outputData[i + 2] = bColor.b;
outputData[i + 3] = 255; // Alpha
}
// Calculate offsets for thickening the wire
// For a thickness T, this creates a T x T square centered at (x,y)
const iterStartOffset = -Math.floor(T_thickness / 2);
const iterEndOffset = Math.floor((T_thickness - 1) / 2);
// Draw wires based on thresholded gradients and thickness
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const gradValue = normalizedGradientData[y * width + x]; // Border pixels will have gradValue = 0
if (gradValue > clampedEdgeThreshold) {
// This pixel (x,y) is an edge center. Draw a block for thickness.
for (let dy = iterStartOffset; dy <= iterEndOffset; dy++) {
for (let dx = iterStartOffset; dx <= iterEndOffset; dx++) {
const currentY = y + dy;
const currentX = x + dx;
// Check bounds for the thickened pixel
if (currentY >= 0 && currentY < height && currentX >= 0 && currentX < width) {
const targetIdx = (currentY * width + currentX) * 4;
outputData[targetIdx] = wColor.r;
outputData[targetIdx + 1] = wColor.g;
outputData[targetIdx + 2] = wColor.b;
outputData[targetIdx + 3] = 255; // Alpha
}
}
}
}
}
}
ctx.putImageData(outputImageData, 0, 0);
return canvas;
}
Apply Changes