You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Converts an image to a blueprint architectural style.
* This is achieved by performing edge detection on the image and then coloring
* the detected edges and the background with classic blueprint colors.
* An optional grid can be overlaid to enhance the effect.
*
* @param {Image} originalImg The original javascript Image object.
* @param {number} [threshold=60] The sensitivity for edge detection (0-255). A lower value detects more (fainter) edges.
* @param {string} [lineColor='#FFFFFF'] The CSS hex color for the blueprint lines.
* @param {string} [backgroundColor='#214285'] The CSS hex color for the blueprint background.
* @param {number} [gridSize=50] The size of the grid cells in pixels. Set to 0 to disable the grid.
* @param {number} [gridOpacity=0.2] The opacity of the grid lines (0.0 to 1.0).
* @returns {HTMLCanvasElement} A canvas element with the blueprint-style image.
*/
function processImage(originalImg, threshold = 60, lineColor = '#FFFFFF', backgroundColor = '#214285', gridSize = 50, gridOpacity = 0.2) {
// 1. SETUP CANVAS
// Create a canvas with the same dimensions as the original image.
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
canvas.width = width;
canvas.height = height;
// Draw the image onto a temporary canvas to access its pixel data.
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
tempCtx.drawImage(originalImg, 0, 0);
const imageData = tempCtx.getImageData(0, 0, width, height);
const data = imageData.data;
// 2. IMAGE PROCESSING PIPELINE
// 2a. Convert image to grayscale. This simplifies edge detection.
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];
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
grayscaleData[i / 4] = gray;
}
// 2b. Apply a simple Gaussian blur (3x3 box blur approximation).
// This reduces noise and results in cleaner edges.
const blurredData = new Uint8ClampedArray(width * height);
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
let sum = 0;
for (let j = -1; j <= 1; j++) {
for (let i = -1; i <= 1; i++) {
sum += grayscaleData[(y + j) * width + (x + i)];
}
}
blurredData[y * width + x] = sum / 9;
}
}
// 2c. Perform Sobel edge detection.
// This algorithm detects areas of high contrast, which correspond to edges.
const sobelData = new Float32Array(width * height);
const Gx = [
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
];
const Gy = [
[-1, -2, -1],
[0, 0, 0],
[1, 2, 1]
];
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
let pixelX = 0;
let pixelY = 0;
for (let j = -1; j <= 1; j++) {
for (let i = -1; i <= 1; i++) {
const gray = blurredData[(y + j) * width + (x + i)];
pixelX += gray * Gx[j + 1][i + 1];
pixelY += gray * Gy[j + 1][i + 1];
}
}
const magnitude = Math.sqrt(pixelX * pixelX + pixelY * pixelY);
sobelData[y * width + x] = magnitude;
}
}
// 3. RENDER THE BLUEPRINT
// Helper function to parse hex colors into an {r, g, b} object.
const hexToRgb = (hex) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
};
const bgRgb = hexToRgb(backgroundColor) || { r: 33, g: 66, b: 133 };
const lineRgb = hexToRgb(lineColor) || { r: 255, g: 255, b: 255 };
// Create the final image data by coloring pixels based on the edge detection result.
const finalImageData = ctx.createImageData(width, height);
const finalData = finalImageData.data;
for (let i = 0; i < sobelData.length; i++) {
const isEdge = sobelData[i] > threshold;
const targetRgb = isEdge ? lineRgb : bgRgb;
finalData[i * 4] = targetRgb.r;
finalData[i * 4 + 1] = targetRgb.g;
finalData[i * 4 + 2] = targetRgb.b;
finalData[i * 4 + 3] = 255; // Alpha
}
// Put the processed pixel data onto the final canvas.
ctx.putImageData(finalImageData, 0, 0);
// 4. ADD OPTIONAL GRID
// If gridSize is specified, draw a grid over the image.
if (gridSize > 0) {
const finalGridOpacity = Math.max(0, Math.min(1, gridOpacity));
ctx.strokeStyle = `rgba(${lineRgb.r}, ${lineRgb.g}, ${lineRgb.b}, ${finalGridOpacity})`;
ctx.lineWidth = 0.5;
for (let x = gridSize; x < width; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.stroke();
}
for (let y = gridSize; y < height; y += gridSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
}
}
// 5. RETURN RESULT
return canvas;
}
Apply Changes