You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, focusPointY = 0.5, focusDepth = 0.1, blurRadius = 5, blurTransition = 0.15, saturation = 1.2, contrast = 1.1) {
const w = originalImg.naturalWidth || originalImg.width;
const h = originalImg.naturalHeight || originalImg.height;
// 1. Create output canvas
const outputCanvas = document.createElement('canvas');
outputCanvas.width = w;
outputCanvas.height = h;
const outputCtx = outputCanvas.getContext('2d');
if (!outputCtx) {
console.error("Could not get 2D context for output canvas.");
// Return an empty canvas or throw an error if context cannot be created
return outputCanvas;
}
// 2. Create sharp canvas (copy of original)
const sharpCanvas = document.createElement('canvas');
sharpCanvas.width = w;
sharpCanvas.height = h;
const sharpCtx = sharpCanvas.getContext('2d');
if (!sharpCtx) {
console.error("Could not get 2D context for sharp canvas.");
return outputCanvas;
}
sharpCtx.drawImage(originalImg, 0, 0, w, h);
// 3. Create blurred canvas
const blurCanvas = document.createElement('canvas');
blurCanvas.width = w;
blurCanvas.height = h;
const blurCtx = blurCanvas.getContext('2d');
if (!blurCtx) {
console.error("Could not get 2D context for blur canvas.");
return outputCanvas;
}
if (blurRadius > 0) {
blurCtx.filter = `blur(${blurRadius}px)`;
blurCtx.drawImage(originalImg, 0, 0, w, h);
blurCtx.filter = 'none'; // Reset filter for any subsequent operations on blurCtx
} else {
// If no blur, blurred canvas is same as sharp
blurCtx.drawImage(originalImg, 0, 0, w, h);
}
// 4. Create mask canvas
const maskCanvas = document.createElement('canvas');
maskCanvas.width = w;
maskCanvas.height = h;
const maskCtx = maskCanvas.getContext('2d');
if (!maskCtx) {
console.error("Could not get 2D context for mask canvas.");
return outputCanvas;
}
// Calculate normalized stop points for the mask gradient
// These are relative to image height (0.0 to 1.0)
let norm_y1_sharp = focusPointY - focusDepth / 2; // Top of fully sharp area
let norm_y2_sharp = focusPointY + focusDepth / 2; // Bottom of fully sharp area
let norm_y0_trans_start = norm_y1_sharp - blurTransition; // Top of transition (to sharp)
let norm_y3_trans_end = norm_y2_sharp + blurTransition; // Bottom of transition (to blur)
// Clamp initial calculations to [0, 1] range
norm_y0_trans_start = Math.max(0, Math.min(1, norm_y0_trans_start));
norm_y1_sharp = Math.max(0, Math.min(1, norm_y1_sharp));
norm_y2_sharp = Math.max(0, Math.min(1, norm_y2_sharp));
norm_y3_trans_end = Math.max(0, Math.min(1, norm_y3_trans_end));
// Ensure logical order of points (y0 <= y1 <= y2 <= y3)
// This handles cases like zero/negative focusDepth or blurTransition
if (norm_y1_sharp > norm_y2_sharp) {
const mid = (norm_y1_sharp + norm_y2_sharp) / 2;
norm_y1_sharp = mid;
norm_y2_sharp = mid;
}
if (norm_y0_trans_start > norm_y1_sharp) norm_y0_trans_start = norm_y1_sharp;
if (norm_y3_trans_end < norm_y2_sharp) norm_y3_trans_end = norm_y2_sharp;
// Build gradient stops for the mask (white = sharp, black = blurred)
const stops = [];
// Determine color at the very top (offset 0)
if (norm_y1_sharp <= 0) stops.push({ offset: 0, color: 'white' }); // Sharp area starts at/before top
else if (norm_y0_trans_start <= 0) stops.push({ offset: 0, color: 'black' }); // Transition starts at/before top (gradient from black)
else stops.push({ offset: 0, color: 'black' }); // Fully blurred at top
// Add intermediate transition points if they are strictly between 0 and 1
if (norm_y0_trans_start > 0 && norm_y0_trans_start < 1) stops.push({ offset: norm_y0_trans_start, color: 'black' });
if (norm_y1_sharp > 0 && norm_y1_sharp < 1) stops.push({ offset: norm_y1_sharp, color: 'white' });
if (norm_y2_sharp > 0 && norm_y2_sharp < 1) stops.push({ offset: norm_y2_sharp, color: 'white' });
if (norm_y3_trans_end > 0 && norm_y3_trans_end < 1) stops.push({ offset: norm_y3_trans_end, color: 'black' });
// Determine color at the very bottom (offset 1)
if (norm_y2_sharp >= 1) stops.push({ offset: 1, color: 'white' }); // Sharp area ends at/after bottom
else if (norm_y3_trans_end >= 1) stops.push({ offset: 1, color: 'black' }); // Transition ends at/after bottom (gradient to black)
else stops.push({ offset: 1, color: 'black' }); // Fully blurred at bottom
// Create unique sorted gradient stops. Map ensures last entry for a given offset wins.
const uniqueSortedStops = Array.from(
new Map(stops.map(s => [s.offset, s]))
).sort((a,b) => a[0] - b[0]) // Sort by offset (Map keys are a[0])
.map(entry => entry[1]); // Get the stop object itself (Map values are a[1])
const grad = maskCtx.createLinearGradient(0, 0, 0, h);
for (const stop of uniqueSortedStops) {
grad.addColorStop(stop.offset, stop.color);
}
maskCtx.fillStyle = grad;
maskCtx.fillRect(0, 0, w, h);
// 5. Composite the layers
// Start with the sharp image on the output canvas
outputCtx.drawImage(sharpCanvas, 0, 0, w, h);
// Mask the sharp image: keep sharp image where mask is white (alpha=1), transparent where mask is black (alpha=0)
// 'destination-in': The existing content (sharpImage) is kept where it and the new shape (mask) overlap.
// Mask's luminance is used as alpha.
outputCtx.globalCompositeOperation = 'destination-in';
outputCtx.drawImage(maskCanvas, 0, 0, w, h);
// Draw the blurred image *behind* the masked sharp image
// 'destination-over': The new shape (blurImage) is drawn behind the existing content.
outputCtx.globalCompositeOperation = 'destination-over';
outputCtx.drawImage(blurCanvas, 0, 0, w, h);
// Reset composite operation for any further drawing
outputCtx.globalCompositeOperation = 'source-over';
// 6. Apply final saturation and contrast adjustments
if (saturation !== 1 || contrast !== 1) {
if (typeof outputCtx.filter !== 'undefined') {
const tempFilterCanvas = document.createElement('canvas');
tempFilterCanvas.width = w;
tempFilterCanvas.height = h;
const tempFilterCtx = tempFilterCanvas.getContext('2d');
if (!tempFilterCtx) {
console.warn("Could not get 2D context for temporary filter canvas. Skipping saturation/contrast.");
} else {
tempFilterCtx.filter = `saturate(${saturation}) contrast(${contrast})`;
tempFilterCtx.drawImage(outputCanvas, 0, 0, w, h); // Draw from current output to temp, applying filter
// Copy filtered result back to outputCanvas
outputCtx.clearRect(0,0,w,h); // Clear original output content
outputCtx.drawImage(tempFilterCanvas, 0, 0, w,h); // Copy filtered image back
}
} else {
console.warn("Canvas filters (saturate, contrast) not supported by this browser.");
}
}
return outputCanvas;
}
Apply Changes