You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, strength = 0.5, centerX = 0.5, centerY = 0.5) {
// Ensure parameters are numbers
strength = Number(strength);
centerX = Number(centerX);
centerY = Number(centerY);
// Ensure the image is loaded before proceeding
try {
if (!originalImg.complete || originalImg.naturalWidth === 0) {
// If the image isn't loaded (e.g., src was set but download isn't complete)
// or if it's an Image object with no src yet.
// Use decode() if available (modern browsers, more efficient)
if (typeof originalImg.decode === 'function') {
await originalImg.decode();
} else {
// Fallback to onload event for older browsers or other contexts
// Check if src is actually set. If not, onload will never fire.
if (!originalImg.src && !(originalImg instanceof HTMLImageElement && originalImg.currentSrc)) {
throw new Error("Image source is not specified.");
}
if (!originalImg.complete || originalImg.naturalWidth === 0) { // Double check state
await new Promise((resolve, reject) => {
originalImg.onload = resolve;
originalImg.onerror = () => reject(new Error("Image failed to load."));
// Note: If originalImg.src was set and it already failed, onerror might not fire again.
// And if it was already loaded but complete was false for a moment, onload may not fire.
// This path is a fallback for browsers not supporting .decode().
});
}
}
}
} catch (error) {
console.error("Image Barrel Distortion: Image could not be loaded or decoded.", error);
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 0;
errorCanvas.height = 0;
return errorCanvas; // Return an empty canvas on error
}
const canvas = document.createElement('canvas');
// Using { willReadFrequently: true } is a performance hint for the browser
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
if (width === 0 || height === 0) {
console.warn("Image Barrel Distortion: Input image has zero dimensions after load attempt.");
canvas.width = 0;
canvas.height = 0;
return canvas;
}
canvas.width = width;
canvas.height = height;
// Draw the original image onto the canvas to access its pixel data
ctx.drawImage(originalImg, 0, 0, width, height);
const srcImageData = ctx.getImageData(0, 0, width, height);
const srcData = srcImageData.data;
// Create a new ImageData object for the distorted image
const dstImageData = ctx.createImageData(width, height);
const dstData = dstImageData.data;
// Helper function for bilinear interpolation
// sx_raw, sy_raw are floating point coordinates of the source pixel
function getPixelBilinear(sx_raw, sy_raw) {
// If the source pixel is strictly outside the image bounds, return transparent black.
if (sx_raw < 0 || sx_raw >= width || sy_raw < 0 || sy_raw >= height) {
return [0, 0, 0, 0]; // R, G, B, A
}
// Integer part of the coordinates (top-left of the 2x2 sampling block)
const x0 = Math.floor(sx_raw);
const y0 = Math.floor(sy_raw);
// Fractional part of the coordinates (weights for interpolation)
const x_frac = sx_raw - x0;
const y_frac = sy_raw - y0;
const one_minus_x_frac = 1.0 - x_frac;
const one_minus_y_frac = 1.0 - y_frac;
// Coordinates of the 4 neighboring pixels, clamped to image boundaries
// x0,y0 is the top-left texel.
// x1,y0 is the top-right texel.
// x0,y1 is the bottom-left texel.
// x1,y1 is the bottom-right texel.
const x1 = Math.min(x0 + 1, width - 1); // Clamp to ensure x1 does not exceed width-1
const y1 = Math.min(y0 + 1, height - 1); // Clamp to ensure y1 does not exceed height-1
// Calculate flat array indices for the 4 pixels
const p00_idx_base = (y0 * width + x0) * 4;
const p10_idx_base = (y0 * width + x1) * 4;
const p01_idx_base = (y1 * width + x0) * 4;
const p11_idx_base = (y1 * width + x1) * 4;
let R_val = 0, G_val = 0, B_val = 0, A_val = 0;
// Interpolate R, G, B, A channels separately
for (let c = 0; c < 4; ++c) { // 0:R, 1:G, 2:B, 3:A
const val00 = srcData[p00_idx_base + c]; // Top-left
const val10 = srcData[p10_idx_base + c]; // Top-right
const val01 = srcData[p01_idx_base + c]; // Bottom-left
const val11 = srcData[p11_idx_base + c]; // Bottom-right
const interpVal = val00 * one_minus_x_frac * one_minus_y_frac + // weight for val00
val10 * x_frac * one_minus_y_frac + // weight for val10
val01 * one_minus_x_frac * y_frac + // weight for val01
val11 * x_frac * y_frac; // weight for val11
if (c === 0) R_val = interpVal;
else if (c === 1) G_val = interpVal;
else if (c === 2) B_val = interpVal;
else if (c === 3) A_val = interpVal;
}
return [R_val, G_val, B_val, A_val]; // Floats, will be clamped by ImageData
}
// Iterate over each pixel in the destination (distorted) image
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// Normalize destination pixel coordinates to [0, 1] range
const currentPixelNormX = x / width;
const currentPixelNormY = y / height;
// Translate normalized coordinates to be relative to the distortion center
const dx_centered = currentPixelNormX - centerX;
const dy_centered = currentPixelNormY - centerY;
// Calculate squared distance from the center (r_d^2 in the formula)
const r_sq = dx_centered * dx_centered + dy_centered * dy_centered;
// Barrel distortion formula: P_source_centered = P_dest_centered / (1 + K * r_d^2)
// 'strength' is our K. For barrel distortion, K > 0.
// Factor is always >= 1 for barrel distortion, so no division by zero.
const distortionFactor = 1.0 + strength * r_sq;
const srcNormX_centered = dx_centered / distortionFactor;
const srcNormY_centered = dy_centered / distortionFactor;
// Translate centered source coordinates back to absolute normalized [0,1] range
const srcNormX = srcNormX_centered + centerX;
const srcNormY = srcNormY_centered + centerY;
// Convert normalized source coordinates to actual pixel coordinates
const sx = srcNormX * width; // Source pixel x coordinate (float)
const sy = srcNormY * height; // Source pixel y coordinate (float)
// Get the interpolated pixel color from the source image
const [R, G, B, A] = getPixelBilinear(sx, sy);
// Set the pixel in the destination image data
const outIdx = (y * width + x) * 4;
dstData[outIdx + 0] = R;
dstData[outIdx + 1] = G;
dstData[outIdx + 2] = B;
dstData[outIdx + 3] = A;
}
}
// Put the distorted image data onto the canvas
ctx.putImageData(dstImageData, 0, 0);
return canvas;
}
Apply Changes