You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, threshold = 50, lineColorStr = "0,0,0", backgroundColorStr = "255,255,255") {
const canvas = document.createElement('canvas');
let ctx;
// Attempt to get context with willReadFrequently, fallback if not supported
try {
ctx = canvas.getContext('2d', { willReadFrequently: true });
} catch (e) {
ctx = canvas.getContext('2d');
}
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
if (imgWidth === 0 || imgHeight === 0) {
console.error("Image has zero width or height. Cannot process.");
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 200;
errorCanvas.height = 100;
const errorCtx = errorCanvas.getContext('2d');
errorCtx.fillStyle = 'lightgray';
errorCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
errorCtx.fillStyle = 'red';
errorCtx.font = '12px Arial';
errorCtx.textAlign = 'center';
errorCtx.textBaseline = 'middle';
errorCtx.fillText('Invalid image dimensions', errorCanvas.width / 2, errorCanvas.height / 2);
return errorCanvas;
}
canvas.width = imgWidth;
canvas.height = imgHeight;
try {
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Error drawing original image onto canvas:", e);
const errorCanvas = document.createElement('canvas');
errorCanvas.width = canvas.width > 0 ? canvas.width : 300;
errorCanvas.height = canvas.height > 0 ? canvas.height : 150;
const errorCtx = errorCanvas.getContext('2d');
errorCtx.fillStyle = '#FFDDDD'; // Light red background
errorCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
errorCtx.fillStyle = 'black';
errorCtx.font = `bold ${Math.min(16, Math.floor(errorCanvas.width/20))}px Arial`;
errorCtx.textAlign = 'center';
errorCtx.textBaseline = 'middle';
errorCtx.fillText('Error: Could not draw input image.', errorCanvas.width / 2, errorCanvas.height / 2);
return errorCanvas;
}
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
// This error typically occurs due to CORS restrictions.
console.error("Error getting ImageData from canvas (likely a CORS issue):", e);
const errorCanvas = document.createElement('canvas');
errorCanvas.width = canvas.width > 0 ? canvas.width : 350; // Ensure minimum width for message
errorCanvas.height = canvas.height > 0 ? canvas.height : 200; // Ensure minimum height for message
const errorCtx = errorCanvas.getContext('2d');
errorCtx.fillStyle = '#EEEEEE'; // Light gray background
errorCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
errorCtx.fillStyle = 'red';
const fontSize = Math.max(12, Math.min(16, Math.floor(errorCanvas.width / 28))); // Responsive font size
errorCtx.font = `bold ${fontSize}px Arial`;
errorCtx.textAlign = 'center';
errorCtx.textBaseline = 'middle';
const messages = [
"Error processing image: Canvas tainted.",
"This often occurs with cross-origin images",
"without proper CORS headers.",
"Ensure the image is from the same domain or",
"is served with 'Access-Control-Allow-Origin'.",
];
const lineHeight = fontSize * 1.4;
const totalTextHeight = (messages.length -1) * lineHeight;
let startY = (errorCanvas.height - totalTextHeight) / 2;
messages.forEach((msg, index) => {
errorCtx.fillText(msg, errorCanvas.width / 2, startY + index * lineHeight);
});
return errorCanvas;
}
const data = imageData.data;
const width = canvas.width;
const height = canvas.height;
// Helper to parse color strings (e.g., "255,0,0") into [R, G, B] arrays
const parseColor = (colorStr, defaultColor) => {
const parts = colorStr.split(',').map(numStr => parseInt(numStr.trim(), 10));
if (parts.length === 3 && parts.every(num => !isNaN(num) && num >= 0 && num <= 255)) {
return parts;
}
console.warn(`Invalid color string format: "${colorStr}". Using default [${defaultColor.join(',')}].`);
return defaultColor;
};
const lineRgb = parseColor(lineColorStr, [0, 0, 0]); // Default to black lines
const backgroundRgb = parseColor(backgroundColorStr, [255, 255, 255]); // Default to white background
// 1. Convert to grayscale
const grayscaleData = new Uint8ClampedArray(width * height);
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Standard luminosity calculation, rounded
grayscaleData[i / 4] = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
}
// Helper to get grayscale pixel, handling boundaries with zero-padding
const getGrayPixel = (x, y) => {
if (x < 0 || x >= width || y < 0 || y >= height) {
return 0;
}
return grayscaleData[y * width + x];
};
// 2. Apply Sobel operator for edge detection
const sobelXKernel = [
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
];
const sobelYKernel = [
[-1, -2, -1],
[0, 0, 0],
[1, 2, 1]
];
const outputImageData = ctx.createImageData(width, height);
const outputData = outputImageData.data;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let gx = 0;
let gy = 0;
// Convolve with Sobel kernels
for (let ky = -1; ky <= 1; ky++) {
for (let kx = -1; kx <= 1; kx++) {
const pixelVal = getGrayPixel(x + kx, y + ky);
gx += pixelVal * sobelXKernel[ky + 1][kx + 1];
gy += pixelVal * sobelYKernel[ky + 1][kx + 1];
}
}
const magnitude = Math.sqrt(gx * gx + gy * gy);
const pixelIndex = (y * width + x) * 4;
// Apply threshold to determine line or background
if (magnitude > threshold) {
outputData[pixelIndex] = lineRgb[0];
outputData[pixelIndex + 1] = lineRgb[1];
outputData[pixelIndex + 2] = lineRgb[2];
} else {
outputData[pixelIndex] = backgroundRgb[0];
outputData[pixelIndex + 1] = backgroundRgb[1];
outputData[pixelIndex + 2] = backgroundRgb[2];
}
outputData[pixelIndex + 3] = 255; // Set alpha to fully opaque
}
}
// Create a new canvas and put the processed image data onto it
const outputCanvas = document.createElement('canvas');
outputCanvas.width = width;
outputCanvas.height = height;
const outputCtx = outputCanvas.getContext('2d');
outputCtx.putImageData(outputImageData, 0, 0);
return outputCanvas;
}
Apply Changes