You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, focusXPercent = 50, focusYPercent = 50, focusRadiusPercent = 15, blurAmountPx = 5, transitionRangePercent = 20) {
// Ensure parameters are numbers and have valid ranges
focusXPercent = Number(focusXPercent);
focusYPercent = Number(focusYPercent);
focusRadiusPercent = Math.max(0, Number(focusRadiusPercent));
blurAmountPx = Math.max(0, Number(blurAmountPx));
transitionRangePercent = Math.max(0, Number(transitionRangePercent));
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
const outputCanvas = document.createElement('canvas');
if (imgWidth === 0 || imgHeight === 0) {
console.warn("Input image has zero dimensions. Returning 1x1 blank canvas.");
outputCanvas.width = 1;
outputCanvas.height = 1;
// Optionally, fill with a color or indicate error state on the canvas
// const ctx = outputCanvas.getContext('2d');
// ctx.fillStyle = 'rgba(0,0,0,0.1)'; // A very faint gray for example
// ctx.fillRect(0,0,1,1);
return outputCanvas;
}
outputCanvas.width = imgWidth;
outputCanvas.height = imgHeight;
// Optimization hint for context, useful for frequent getImageData/putImageData
const outputCtx = outputCanvas.getContext('2d', { willReadFrequently: true });
// 1. Prepare blurred version of the image
const blurCanvas = document.createElement('canvas');
blurCanvas.width = imgWidth;
blurCanvas.height = imgHeight;
const blurCtx = blurCanvas.getContext('2d', { willReadFrequently: true });
if (blurAmountPx > 0) {
blurCtx.filter = `blur(${blurAmountPx}px)`;
blurCtx.drawImage(originalImg, 0, 0, imgWidth, imgHeight); // Blur is applied during this draw
blurCtx.filter = 'none'; // Reset filter to ensure getImageData gets the filtered content correctly and doesn't affect future draws
} else {
// If no blur, blurred version is the same as original
blurCtx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
}
// It's crucial that originalImg is loaded and not tainted (CORS) for getImageData to work.
const blurImageData = blurCtx.getImageData(0, 0, imgWidth, imgHeight);
// 2. Prepare sharp version of the image (for its ImageData)
// Could optimize by only creating this if needed (i.e., focus is not 0 everywhere)
// but for clarity, we'll always get it.
const sharpCanvas = document.createElement('canvas');
sharpCanvas.width = imgWidth;
sharpCanvas.height = imgHeight;
const sharpCtx = sharpCanvas.getContext('2d', { willReadFrequently: true });
sharpCtx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
const sharpImageData = sharpCtx.getImageData(0, 0, imgWidth, imgHeight);
// Create ImageData for the output
const outputImageData = outputCtx.createImageData(imgWidth, imgHeight);
// Calculate actual metric values from percentage parameters
const actualFocusX = imgWidth * focusXPercent / 100;
const actualFocusY = imgHeight * focusYPercent / 100;
const minDimension = Math.min(imgWidth, imgHeight); // Base radius on the smaller dimension for consistency
const actualFocusRadius = minDimension * focusRadiusPercent / 100;
const actualTransitionRange = minDimension * transitionRangePercent / 100;
// 4. Pixel Loop for blending sharp and blurred images
for (let y = 0; y < imgHeight; y++) {
for (let x = 0; x < imgWidth; x++) {
const distX = x - actualFocusX;
const distY = y - actualFocusY;
const distance = Math.sqrt(distX * distX + distY * distY);
let sharpnessFactor = 0.0; // 1.0 means fully sharp, 0.0 means fully blurred
if (distance <= actualFocusRadius) {
sharpnessFactor = 1.0;
} else if (actualTransitionRange <= 0) { // No transition zone (hard edge), and we are outside focusRadius
sharpnessFactor = 0.0;
} else if (distance >= actualFocusRadius + actualTransitionRange) { // Past transition zone
sharpnessFactor = 0.0;
} else { // Within transition zone
sharpnessFactor = 1.0 - (distance - actualFocusRadius) / actualTransitionRange;
}
// Clamp sharpnessFactor to [0, 1] just in case of floating point nuances
sharpnessFactor = Math.max(0, Math.min(1, sharpnessFactor));
const idx = (y * imgWidth + x) * 4; // Index for the current pixel's R channel
// Blend R, G, B, and Alpha channels
// outputPixel = sharpPixel * factor + blurPixel * (1 - factor)
if (sharpnessFactor === 1.0) { // Fully sharp
outputImageData.data[idx] = sharpImageData.data[idx];
outputImageData.data[idx + 1] = sharpImageData.data[idx + 1];
outputImageData.data[idx + 2] = sharpImageData.data[idx + 2];
outputImageData.data[idx + 3] = sharpImageData.data[idx + 3];
} else if (sharpnessFactor === 0.0) { // Fully blurred
outputImageData.data[idx] = blurImageData.data[idx];
outputImageData.data[idx + 1] = blurImageData.data[idx + 1];
outputImageData.data[idx + 2] = blurImageData.data[idx + 2];
outputImageData.data[idx + 3] = blurImageData.data[idx + 3];
} else { // In transition zone, blend
outputImageData.data[idx] = sharpImageData.data[idx] * sharpnessFactor + blurImageData.data[idx] * (1 - sharpnessFactor);
outputImageData.data[idx + 1] = sharpImageData.data[idx + 1] * sharpnessFactor + blurImageData.data[idx + 1] * (1 - sharpnessFactor);
outputImageData.data[idx + 2] = sharpImageData.data[idx + 2] * sharpnessFactor + blurImageData.data[idx + 2] * (1 - sharpnessFactor);
outputImageData.data[idx + 3] = sharpImageData.data[idx + 3] * sharpnessFactor + blurImageData.data[idx + 3] * (1 - sharpnessFactor);
}
}
}
// 5. Draw the final composited image data to the output canvas
outputCtx.putImageData(outputImageData, 0, 0);
return outputCanvas;
}
Apply Changes