You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, edgeThreshold = 80, invertColorsStr = "false") {
const invert = invertColorsStr === "true";
const canvas = document.createElement('canvas');
// Use willReadFrequently hint for potential performance improvements
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Determine image dimensions, preferring natural dimensions if available
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
// Handle cases where image might not be loaded or is genuinely 0-size
if (width === 0 || height === 0) {
canvas.width = 0;
canvas.height = 0;
console.warn("processImage: Image has zero width or height. Returning an empty (0x0) canvas.");
return canvas; // Return an empty 0x0 canvas
}
canvas.width = width;
canvas.height = height;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, width, height);
let imageData;
try {
// Get pixel data from the canvas
imageData = ctx.getImageData(0, 0, width, height);
} catch (e) {
// This error can occur if the image is cross-origin and the canvas becomes tainted
console.error("processImage: Error getting image data. Likely a cross-origin issue.", e);
// Draw an error message on the canvas over the original image
ctx.fillStyle = "rgba(255, 0, 0, 0.7)"; // Semi-transparent red overlay
ctx.fillRect(0, 0, width, height);
ctx.font = "16px Arial";
ctx.fillStyle = "white";
ctx.textAlign = "center";
const message = "Error: Cannot process image.";
const message2 = "(Cross-origin restrictions?)";
// Basic centering of text
const textY = height / 2 - (height > 30 ? 8 : 0); // Adjust Y for two lines if space
ctx.fillText(message, width / 2, textY);
if (height > 30) { // Only show second line if there's enough vertical space
ctx.fillText(message2, width / 2, textY + 20);
}
return canvas; // Return the canvas with the error indication
}
const data = imageData.data; // Reference to the pixel data (an RGBA Uint8ClampedArray)
// Step 1: Grayscale Conversion
// Create a 1D array to store grayscale values for each pixel.
const grayscaleData = new Uint8ClampedArray(width * height);
for (let i = 0; i < data.length; i += 4) { // Iterate over RGBA components
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Using standard luminance formula for grayscale
const gray = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
grayscaleData[i / 4] = gray; // Store one gray value per pixel (index i/4)
}
// Step 2: Sobel Edge Detection
// This array will store the magnitude of gradients calculated by the Sobel operator.
// Using Float32Array for precision as magnitudes can be non-integers.
const sobelMagnitudes = new Float32Array(width * height);
// Sobel kernels for detecting horizontal (Gx) and vertical (Gy) edges
const Gx_kernel = [
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
];
const Gy_kernel = [
[-1, -2, -1],
[ 0, 0, 0],
[ 1, 2, 1]
];
// Apply Sobel operator. Iterate over each pixel, skipping a 1-pixel border
// because the Sobel operator uses a 3x3 neighborhood.
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
let Gx = 0;
let Gy = 0;
// Apply 3x3 kernel to the neighborhood of the current pixel
for (let ky = -1; ky <= 1; ky++) { // Kernel y-offset
for (let kx = -1; kx <= 1; kx++) { // Kernel x-offset
// Calculate index of the neighbor pixel in grayscaleData
const pixelIndex = (y + ky) * width + (x + kx);
const pixelVal = grayscaleData[pixelIndex];
Gx += pixelVal * Gx_kernel[ky + 1][kx + 1];
Gy += pixelVal * Gy_kernel[ky + 1][kx + 1];
}
}
// Calculate the gradient magnitude: sqrt(Gx^2 + Gy^2)
const magnitude = Math.sqrt(Gx * Gx + Gy * Gy);
sobelMagnitudes[y * width + x] = magnitude; // Store magnitude for the current pixel
}
}
// Step 3: Thresholding and Final Image Construction
// Determine line and background colors based on the 'invert' flag.
// Architectural plans are typically black lines on white, or white lines on a dark background (blueprint).
const lineColor = invert ? 255 : 0; // White (255) lines if inverted, else black (0)
const backgroundColor = invert ? 0 : 255; // Black (0) background if inverted, else white (255)
// Iterate over all pixels to set final image data based on Sobel magnitudes.
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = (y * width + x) * 4; // Index for the pixel in the RGBA data array
let value; // This will be the color value (0 or 255) for R, G, B channels
// Pixels at the border (where Sobel wasn't fully applied or is less reliable)
// are set to the background color.
if (x === 0 || x === width - 1 || y === 0 || y === height - 1) {
value = backgroundColor;
} else {
// For inner pixels, compare Sobel magnitude to the threshold
const magnitude = sobelMagnitudes[y * width + x];
// If magnitude is above threshold, it's an edge (line), otherwise background.
value = (magnitude > edgeThreshold) ? lineColor : backgroundColor;
}
// Set R, G, B channels to the determined value
data[i] = value; // Red
data[i + 1] = value; // Green
data[i + 2] = value; // Blue
data[i + 3] = 255; // Alpha channel (fully opaque)
}
}
// Write the modified pixel data back to the canvas
ctx.putImageData(imageData, 0, 0);
return canvas; // Return the processed canvas
}
Free Image Tool Creator
Can't find the image tool you're looking for? Create one based on your own needs now!
The Image Architectural Plan Filter Effect Tool allows users to apply a filter that enhances architectural plans by converting them into a high-contrast edge-detected version. This tool performs grayscale conversion and applies Sobel edge detection, emphasizing lines and structures present in the original images. Users can adjust parameters such as edge detection thresholds and whether to invert colors, making it suitable for creating crisp and clear representations of architectural designs. This tool is particularly useful for architects, engineers, and designers looking to present or analyze technical drawings and architectural sketches.