You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, blurRadius = 1, threshold = 200) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Ensure originalImg dimensions are valid
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
if (width === 0 || height === 0) {
console.error("Image has zero width or height. Ensure the image is loaded and has valid dimensions.");
// Return an empty or minimal canvas to avoid further errors
canvas.width = 1; // Avoid 0x0 canvas if possible
canvas.height = 1;
if (ctx) { // ctx might be null if canvas creation failed in extreme environments
ctx.clearRect(0,0,1,1);
}
return canvas;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(originalImg, 0, 0, width, height);
let imageData;
try {
imageData = ctx.getImageData(0, 0, width, height);
} catch (e) {
console.error("Could not get image data. This might be due to cross-origin restrictions if the image is hosted on a different domain.", e);
// Return the canvas with the original image drawn, as processing failed.
// Or indicate error on canvas:
ctx.clearRect(0,0,width,height);
ctx.font = "16px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Error: Could not process image data.", width / 2, height / 2);
return canvas;
}
const data = imageData.data; // This is a Uint8ClampedArray [R,G,B,A, R,G,B,A, ...]
// 1. Create Grayscale version
// We store grayscale values in a separate array of appropriate size (width * height)
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 formula for grayscale conversion
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
grayscaleData[i / 4] = gray;
}
// 2. Create Inverted Grayscale version
const invertedGrayscaleData = new Uint8ClampedArray(width * height);
for (let i = 0; i < grayscaleData.length; i++) {
invertedGrayscaleData[i] = 255 - grayscaleData[i];
}
// 3. Blur the Inverted Grayscale version (Box Blur)
// A blurRadius of 0 will cause the Color Dodge to make the image mostly white.
// A small positive blurRadius (e.g., 1-3) is recommended for this effect.
const radius = Math.max(0, Math.floor(blurRadius));
const blurredInvertedGrayscaleData = new Uint8ClampedArray(width * height);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let sum = 0;
let count = 0;
// Iterate over the blur kernel
for (let ky = -radius; ky <= radius; ky++) {
for (let kx = -radius; kx <= radius; kx++) {
const currentY = y + ky;
const currentX = x + kx;
// Check if the neighboring pixel is within image bounds
if (currentX >= 0 && currentX < width && currentY >= 0 && currentY < height) {
const index = currentY * width + currentX;
sum += invertedGrayscaleData[index];
count++;
}
}
}
if (count > 0) {
blurredInvertedGrayscaleData[y * width + x] = sum / count;
} else {
// This case should ideally not be hit if image dimensions are > 0
// and radius is reasonable. Fallback to the non-blurred value.
blurredInvertedGrayscaleData[y * width + x] = invertedGrayscaleData[y * width + x];
}
}
}
// 4. Blend Grayscale (bottom layer) with Blurred Inverted Grayscale (top layer)
// using the Color Dodge blend mode. Then, apply a threshold to create a
// binary black and white "ink drawing" effect.
const finalData = imageData.data; // Modify imageData.data in place for efficiency
for (let i = 0; i < grayscaleData.length; i++) {
const bottomPixelVal = grayscaleData[i]; // Original grayscale
const topPixelVal = blurredInvertedGrayscaleData[i]; // Blurred inverted grayscale
let dodgedValue;
if (topPixelVal === 255) { // If blend layer (top) is white, result is white
dodgedValue = 255;
} else if (topPixelVal === 0) { // If blend layer is black, result is the source (bottom)
dodgedValue = bottomPixelVal; // This preserves detail in very dark inverted areas
} else {
// Color Dodge formula: Result = Bottom / (1 - Top/255)
// Scaled to 0-255 range: Result = (Bottom * 255) / (255 - Top)
dodgedValue = (bottomPixelVal * 255.0) / (255.0 - topPixelVal);
}
// Clamp the dodged value to the valid [0, 255] range
dodgedValue = Math.min(255, Math.max(0, dodgedValue));
// Apply threshold: pixels with intensity below threshold become black (0),
// others (at or above threshold) become white (255).
const outputValue = dodgedValue < threshold ? 0 : 255;
const dataIndex = i * 4; // Each pixel has 4 components (R,G,B,A)
finalData[dataIndex] = outputValue; // Red channel
finalData[dataIndex + 1] = outputValue; // Green channel
finalData[dataIndex + 2] = outputValue; // Blue channel
finalData[dataIndex + 3] = 255; // Alpha channel (fully opaque for ink on paper)
}
// Put the modified image data back onto the canvas
ctx.putImageData(imageData, 0, 0);
return 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 Ink Drawing Filter transforms images into a stylized ink drawing effect. It converts input images into grayscale, applies an inversion and blurring effect, and then merges the results to create a high-contrast black and white representation. This tool can be used for artistic purposes, such as converting photos into illustrations or enhancing designs for print media. It is especially useful for artists, designers, or anyone looking to add a unique visual flair to images.