You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Finds and marks the outlines of an image, including internal structures, using the Sobel operator.
*
* @param {Image} originalImg The original javascript Image object.
* @param {number} threshold The sensitivity for edge detection. Higher values mean fewer, more prominent edges are detected. Range 0-255.
* @param {string} lineColor The color of the detected outlines (e.g., 'black', '#FF0000', 'rgb(0,255,0)').
* @param {number} lineWidth The width of the outline in pixels.
* @param {string} backgroundColor The background of the output image. Can be a color string, 'transparent', or 'original' to keep the original image.
* @returns {HTMLCanvasElement} A canvas element with the original image's outlines marked.
*/
function processImage(originalImg, threshold = 50, lineColor = 'black', lineWidth = 1, backgroundColor = 'white') {
/**
* A helper function to parse a CSS color string into an [R, G, B] array.
* @param {string} colorStr The color string to parse.
* @returns {number[]} An array containing the [R, G, B] values.
*/
function parseColor(colorStr) {
const el = document.createElement('div');
el.style.color = colorStr;
document.body.appendChild(el);
const computedColor = window.getComputedStyle(el).color;
document.body.removeChild(el);
const match = computedColor.match(/(\d+)/g);
if (match) {
return [parseInt(match[0], 10), parseInt(match[1], 10), parseInt(match[2], 10)];
}
return [0, 0, 0]; // Default to black on failure
}
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
// 1. Prepare a canvas with the original image to read pixel data
const srcCanvas = document.createElement('canvas');
srcCanvas.width = width;
srcCanvas.height = height;
const srcCtx = srcCanvas.getContext('2d', {
willReadFrequently: true
});
srcCtx.drawImage(originalImg, 0, 0);
const imageData = srcCtx.getImageData(0, 0, width, height);
const data = imageData.data;
// 2. Convert the image 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];
const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
grayscaleData[i / 4] = luminance;
}
// 3. Apply the Sobel operator to find edge gradients
const gradientData = new Float32Array(width * height);
const sobelX = [
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]
];
const sobelY = [
[-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 grayValue = grayscaleData[(y + j) * width + (x + i)];
pixelX += grayValue * sobelX[j + 1][i + 1];
pixelY += grayValue * sobelY[j + 1][i + 1];
}
}
const magnitude = Math.sqrt(pixelX * pixelX + pixelY * pixelY);
gradientData[y * width + x] = magnitude;
}
}
// 4. Create the output canvas and set the background
const outputCanvas = document.createElement('canvas');
outputCanvas.width = width;
outputCanvas.height = height;
const outputCtx = outputCanvas.getContext('2d');
if (backgroundColor === 'original') {
outputCtx.drawImage(originalImg, 0, 0);
} else if (backgroundColor !== 'transparent') {
outputCtx.fillStyle = backgroundColor;
outputCtx.fillRect(0, 0, width, height);
}
// 5. Draw the detected outlines onto a separate transparent layer
const linesCanvas = document.createElement('canvas');
linesCanvas.width = width;
linesCanvas.height = height;
const linesCtx = linesCanvas.getContext('2d');
const linesImageData = linesCtx.createImageData(width, height);
const linesData = linesImageData.data;
const [lineR, lineG, lineB] = parseColor(lineColor);
// Define the brush size for the line width
const halfLwFloor = Math.floor(lineWidth / 2);
const halfLwCeil = Math.ceil(lineWidth / 2);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (gradientData[y * width + x] > threshold) {
// Draw a square brush centered at the edge pixel
for (let dy = -halfLwFloor; dy < halfLwCeil; dy++) {
for (let dx = -halfLwFloor; dx < halfLwCeil; dx++) {
const newY = y + dy;
const newX = x + dx;
if (newY >= 0 && newY < height && newX >= 0 && newX < width) {
const index = (newY * width + newX) * 4;
linesData[index] = lineR;
linesData[index + 1] = lineG;
linesData[index + 2] = lineB;
linesData[index + 3] = 255; // Opaque
}
}
}
}
}
}
linesCtx.putImageData(linesImageData, 0, 0);
// 6. Composite the outline layer on top of the background
outputCtx.drawImage(linesCanvas, 0, 0);
return outputCanvas;
}
Apply Changes