Please bookmark this page to avoid losing your image tool!

Image To CAD-Style Blueprint Converter

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
/**
 * Converts an image to a CAD-style blueprint by performing edge detection.
 * The function renders the image with white lines on a gridded blue background,
 * simulating a technical drawing.
 *
 * @param {Image} originalImg The original image object to process.
 * @param {number} lowThreshold Lower threshold for edge detection hysteresis. Default is 20.
 * @param {number} highThreshold Higher threshold for edge detection hysteresis. Default is 50.
 * @param {number} gridSize The size of grid squares in pixels for the background. Default is 40.
 * @param {number} gridOpacity The opacity of the background grid lines (0 to 1). Default is 0.2.
 * @param {number} lineThicknessFactor A multiplier affecting the thickness of stronger lines. Default is 1.5.
 * @returns {HTMLCanvasElement} A canvas element displaying the blueprint image.
 */
async function processImage(originalImg, lowThreshold = 20, highThreshold = 50, gridSize = 40, gridOpacity = 0.2, lineThicknessFactor = 2.0) {
    // 1. --- Canvas and Background Setup ---
    const outputSize = 2048;
    const canvas = document.createElement('canvas');
    canvas.width = outputSize;
    canvas.height = outputSize;
    const ctx = canvas.getContext('2d');

    const bgColor = '#0A3D91';
    ctx.fillStyle = bgColor;
    ctx.fillRect(0, 0, outputSize, outputSize);

    // Draw a subtle grid on the background
    ctx.strokeStyle = `rgba(255, 255, 255, ${gridOpacity})`;
    ctx.lineWidth = 1;
    for (let i = gridSize; i < outputSize; i += gridSize) {
        ctx.beginPath();
        ctx.moveTo(i, 0);
        ctx.lineTo(i, outputSize);
        ctx.stroke();
        ctx.beginPath();
        ctx.moveTo(0, i);
        ctx.lineTo(outputSize, i);
        ctx.stroke();
    }

    // 2. --- Image Scaling and Positioning ---
    // Calculate dimensions to fit image within output size while maintaining aspect ratio
    const hRatio = outputSize / originalImg.width;
    const vRatio = outputSize / originalImg.height;
    const ratio = Math.min(hRatio, vRatio);
    const scaledWidth = Math.floor(originalImg.width * ratio);
    const scaledHeight = Math.floor(originalImg.height * ratio);
    const offsetX = Math.floor((outputSize - scaledWidth) / 2);
    const offsetY = Math.floor((outputSize - scaledHeight) / 2);

    // 3. --- Image Processing (Canny Edge Detection) ---
    // Draw the scaled image onto a temporary canvas for pixel processing
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = scaledWidth;
    tempCanvas.height = scaledHeight;
    const tempCtx = tempCanvas.getContext('2d');
    tempCtx.drawImage(originalImg, 0, 0, scaledWidth, scaledHeight);
    const imageData = tempCtx.getImageData(0, 0, scaledWidth, scaledHeight);
    const { data, width, height } = imageData;

    // Helper for applying a convolution kernel
    const applyKernel = (src, dst, w, h, kernel) => {
        const kernelSize = Math.sqrt(kernel.length);
        const half = Math.floor(kernelSize / 2);
        for (let y = 0; y < h; y++) {
            for (let x = 0; x < w; x++) {
                let sum = 0;
                let k_i = 0;
                for (let ky = -half; ky <= half; ky++) {
                    for (let kx = -half; kx <= half; kx++) {
                        const px = x + kx;
                        const py = y + ky;
                        if (px >= 0 && px < w && py >= 0 && py < h) {
                            sum += src[py * w + px] * kernel[k_i];
                        }
                        k_i++;
                    }
                }
                dst[y * w + x] = sum;
            }
        }
    };

    // 3.1: Grayscale Conversion
    const grayData = new Uint8ClampedArray(width * height);
    for (let i = 0; i < data.length; i += 4) {
        grayData[i / 4] = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
    }

    // 3.2: Gaussian Blur (to reduce noise)
    const blurKernel = [2, 4, 5, 4, 2, 4, 9, 12, 9, 4, 5, 12, 15, 12, 5, 4, 9, 12, 9, 4, 2, 4, 5, 4, 2].map(v => v / 159);
    const blurredData = new Uint8ClampedArray(width * height);
    applyKernel(grayData, blurredData, width, height, blurKernel);

    // 3.3: Sobel Operator (to find gradient intensity and direction)
    const sobelXKernel = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
    const sobelYKernel = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
    const Gx = new Float32Array(width * height);
    const Gy = new Float32Array(width * height);
    applyKernel(blurredData, Gx, width, height, sobelXKernel);
    applyKernel(blurredData, Gy, width, height, sobelYKernel);

    const gradientMagnitude = new Float32Array(width * height);
    const gradientDirection = new Float32Array(width * height);
    let maxMagnitude = 0;
    for (let i = 0; i < gradientMagnitude.length; i++) {
        const mag = Math.sqrt(Gx[i] ** 2 + Gy[i] ** 2);
        gradientMagnitude[i] = mag;
        if (mag > maxMagnitude) maxMagnitude = mag;
        gradientDirection[i] = Math.atan2(Gy[i], Gx[i]);
    }
    
    const normalizedMagnitude = new Float32Array(width * height);
    if (maxMagnitude > 0) {
        for (let i = 0; i < gradientMagnitude.length; i++) {
            normalizedMagnitude[i] = gradientMagnitude[i] / maxMagnitude;
        }
    }

    // 3.4: Non-Maximum Suppression (to thin edges)
    const nmsData = new Float32Array(width * height);
    for (let y = 1; y < height - 1; y++) {
        for (let x = 1; x < width - 1; x++) {
            const i = y * width + x;
            const angle = gradientDirection[i] * (180 / Math.PI);
            const mag = gradientMagnitude[i];
            let q = 255, r = 255;

            if ((0 <= angle && angle < 22.5) || (157.5 <= angle && angle <= 180) || (-22.5 <= angle && angle < 0) || (-180 <= angle && angle < -157.5)) {
                q = gradientMagnitude[i + 1]; r = gradientMagnitude[i - 1];
            } else if ((22.5 <= angle && angle < 67.5) || (-157.5 <= angle && angle < -112.5)) {
                q = gradientMagnitude[i - width + 1]; r = gradientMagnitude[i + width - 1];
            } else if ((67.5 <= angle && angle < 112.5) || (-112.5 <= angle && angle < -67.5)) {
                q = gradientMagnitude[i - width]; r = gradientMagnitude[i + width];
            } else if ((112.5 <= angle && angle < 157.5) || (-67.5 <= angle && angle < -22.5)) {
                q = gradientMagnitude[i - width - 1]; r = gradientMagnitude[i + width + 1];
            }

            if (mag >= q && mag >= r) nmsData[i] = mag;
        }
    }

    // 3.5: Double Thresholding and Hysteresis (to connect edges)
    const edges = new Uint8ClampedArray(width * height); // 0 no, 128 weak, 255 strong
    const finalEdges = new Uint8ClampedArray(width * height);

    for (let i = 0; i < nmsData.length; i++) {
        if (nmsData[i] > highThreshold) edges[i] = 255;
        else if (nmsData[i] > lowThreshold) edges[i] = 128;
    }
    
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            if (edges[y * width + x] === 255 && finalEdges[y * width + x] === 0) {
                // Use an iterative approach (DFS) to avoid stack overflow on complex images
                const stack = [[x, y]];
                while (stack.length > 0) {
                    const [cx, cy] = stack.pop();
                    const currentIdx = cy * width + cx;
                    if (cx >= 0 && cx < width && cy >= 0 && cy < height && finalEdges[currentIdx] === 0 && edges[currentIdx] > 0) {
                        finalEdges[currentIdx] = 255;
                        for (let dy = -1; dy <= 1; dy++) {
                           for (let dx = -1; dx <= 1; dx++) {
                               if (dx === 0 && dy === 0) continue;
                               stack.push([cx + dx, cy + dy]);
                           }
                        }
                    }
                }
            }
        }
    }

    // 4. --- Drawing the Final Result ---
    // Draw edges with variable thickness based on gradient magnitude
    ctx.fillStyle = '#FFFFFF';
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const i = y * width + x;
            if (finalEdges[i] === 255) {
                const diameter = 1 + (normalizedMagnitude[i] * lineThicknessFactor);
                ctx.beginPath();
                ctx.arc(offsetX + x, offsetY + y, diameter / 2, 0, Math.PI * 2);
                ctx.fill();
            }
        }
    }

    return canvas;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image To CAD-Style Blueprint Converter transforms standard images into CAD-style blueprints by applying edge detection techniques. This tool creates an image with white outlines on a blue grid background, resembling a technical drawing. It is ideal for architects, engineers, and designers who want to visualize their designs in a blueprint format, or for educators seeking to illustrate concepts in a clear and structured way. Users can customize parameters such as edge detection thresholds, grid size, and line thickness to suit their specific needs.

Leave a Reply

Your email address will not be published. Required fields are marked *