You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, blurRadius = 5, lineColorStr = "255,255,255", backgroundColorStr = "0,51,102") {
// Helper to parse color string "r,g,b" into an object {r, g, b}
// Clamps values to 0-255. Handles invalid or missing parts.
function parseColor(colorStr) {
const parts = (typeof colorStr === 'string' ? colorStr : '').split(',').map(s => parseInt(s.trim(), 10));
const r = Number.isFinite(parts[0]) ? parts[0] : 0;
const g = Number.isFinite(parts[1]) ? parts[1] : 0;
const b = Number.isFinite(parts[2]) ? parts[2] : 0;
return {
r: Math.max(0, Math.min(255, r)),
g: Math.max(0, Math.min(255, g)),
b: Math.max(0, Math.min(255, b))
};
}
const lineColor = parseColor(lineColorStr);
const backgroundColor = parseColor(backgroundColorStr);
// Use naturalWidth/Height if available, otherwise width/height.
// For a JS Image object, width/height are usually the intrinsic dimensions after loading.
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
if (width === 0 || height === 0) {
// Return an empty canvas or handle error for zero-dimension image
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = width;
emptyCanvas.height = height;
console.warn("Original image has zero width or height.");
return emptyCanvas;
}
// 1. Create grayscale version of the image
// We'll store grayscale values (0-255) for each pixel in an array.
const canvas1 = document.createElement('canvas');
canvas1.width = width;
canvas1.height = height;
const ctx1 = canvas1.getContext('2d', { willReadFrequently: true }); // Optimization hint
ctx1.drawImage(originalImg, 0, 0, width, height);
const imageData1 = ctx1.getImageData(0, 0, width, height);
const data1 = imageData1.data;
const grayscaleValues = new Uint8ClampedArray(width * height);
for (let i = 0; i < data1.length; i += 4) {
const r = data1[i];
const g = data1[i+1];
const b = data1[i+2];
// Using standard luminance calculation, rounded to nearest integer
const gray = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
grayscaleValues[i / 4] = gray;
}
// 2. Create inverted grayscale image (to be stored in canvas2)
// This image will then be blurred.
const canvas2 = document.createElement('canvas');
canvas2.width = width;
canvas2.height = height;
const ctx2 = canvas2.getContext('2d', { willReadFrequently: true });
const imageData2 = ctx2.createImageData(width, height);
const data2 = imageData2.data;
for (let i = 0; i < grayscaleValues.length; i++) {
const invertedGray = 255 - grayscaleValues[i];
const pixelIndex = i * 4;
data2[pixelIndex] = invertedGray; // R
data2[pixelIndex + 1] = invertedGray; // G
data2[pixelIndex + 2] = invertedGray; // B
data2[pixelIndex + 3] = 255; // Alpha
}
ctx2.putImageData(imageData2, 0, 0);
// 3. Blur the inverted grayscale image (result in canvas3)
const canvas3 = document.createElement('canvas');
canvas3.width = width;
canvas3.height = height;
const ctx3 = canvas3.getContext('2d', { willReadFrequently: true });
// Apply blur using canvas filter. A blurRadius of 0 means no blur.
// The filter syntax requires a unit, e.g., 'px'.
if (typeof blurRadius === 'number' && blurRadius > 0) {
ctx3.filter = `blur(${blurRadius}px)`;
}
ctx3.drawImage(canvas2, 0, 0, width, height); // Draw canvas2 to canvas3, applying blur if filter is set
const imageData3 = ctx3.getImageData(0, 0, width, height);
const data3 = imageData3.data; // This is the blurred inverted grayscale image (RGBA)
// Store blurred inverted grayscale values (0-255) for each pixel
const blurredInvertedGrayscaleValues = new Uint8ClampedArray(width * height);
for (let i = 0; i < blurredInvertedGrayscaleValues.length; i++) {
// Image is grayscale, so R, G, and B channels are the same. Using R channel.
blurredInvertedGrayscaleValues[i] = data3[i * 4];
}
// 4. Color Dodge blend
// Base: original grayscale (from grayscaleValues)
// Blend: blurred inverted grayscale (from blurredInvertedGrayscaleValues)
// Result: sketch-like lines (stored in dodgeResultValues)
const dodgeResultValues = new Uint8ClampedArray(width * height);
for (let i = 0; i < grayscaleValues.length; i++) {
const baseVal = grayscaleValues[i];
const blendVal = blurredInvertedGrayscaleValues[i];
if (blendVal === 255) { // If blend layer is white, result is white
dodgeResultValues[i] = 255;
} else {
// Standard Color Dodge formula: Result = Base / (1 - Blend/255)
// which is equivalent to Base * 255 / (255 - Blend)
// Denominator (255 - blendVal) will be > 0 because blendVal < 255 here.
const denominator = 255 - blendVal;
dodgeResultValues[i] = Math.min(255, (baseVal * 255) / denominator);
}
}
// 5. Final colorization to blueprint style
// Apply line color and background color based on the intensity from dodgeResultValues
const outputCanvas = document.createElement('canvas');
outputCanvas.width = width;
outputCanvas.height = height;
const outputCtx = outputCanvas.getContext('2d');
const outputImageData = outputCtx.createImageData(width, height);
const outputData = outputImageData.data;
for (let i = 0; i < dodgeResultValues.length; i++) {
// intensity is normalized from 0 (maps to background) to 1 (maps to line color)
const intensity = dodgeResultValues[i] / 255.0;
const pixelIndex = i * 4;
// Linear interpolation between background color and line color
outputData[pixelIndex] = Math.round(intensity * lineColor.r + (1 - intensity) * backgroundColor.r); // R
outputData[pixelIndex + 1] = Math.round(intensity * lineColor.g + (1 - intensity) * backgroundColor.g); // G
outputData[pixelIndex + 2] = Math.round(intensity * lineColor.b + (1 - intensity) * backgroundColor.b); // B
outputData[pixelIndex + 3] = 255; // Alpha (fully opaque)
}
outputCtx.putImageData(outputImageData, 0, 0);
return outputCanvas;
}
Apply Changes