You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, strength = 0.2, centerX = 0.5, centerY = 0.5) {
const width = originalImg.width;
const height = originalImg.height;
// If image is empty, return a blank canvas
if (width === 0 || height === 0) {
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = width;
emptyCanvas.height = height;
console.warn("Image Lens Distortion: Input image has zero width or height.");
return emptyCanvas;
}
// Create source canvas to get imageData
const srcCanvas = document.createElement('canvas');
srcCanvas.width = width;
srcCanvas.height = height;
const srcCtx = srcCanvas.getContext('2d', { willReadFrequently: true });
srcCtx.drawImage(originalImg, 0, 0);
const srcImageData = srcCtx.getImageData(0, 0, width, height);
// Create output canvas
const dstCanvas = document.createElement('canvas');
dstCanvas.width = width;
dstCanvas.height = height;
const dstCtx = dstCanvas.getContext('2d');
const dstImageData = dstCtx.createImageData(width, height);
const dstData = dstImageData.data;
const absCenterX = centerX * width;
const absCenterY = centerY * height;
// Define a sensible radius for normalization. Typically, distance from image center to a corner.
// This ensures that rNormSq is roughly 1.0 at corners if the lens center is the image center.
const normRadius = Math.sqrt(Math.pow(width / 2, 2) + Math.pow(height / 2, 2));
// K_DIVISOR is normRadius squared. If normRadius is 0, set to 1 to avoid DivByZero.
const K_DIVISOR = (normRadius === 0) ? 1.0 : normRadius * normRadius;
const invKDivisor = (K_DIVISOR === 0) ? 1.0 : 1.0 / K_DIVISOR; // Precompute for performance
// Helper function to get a single pixel's color array [R,G,B,A] from imageData
// Clamps coordinates to be within image bounds. x, y are expected to be integers.
function getPixel(imgDataObj, x, y, w, h) {
const clampedX = Math.max(0, Math.min(x, w - 1));
const clampedY = Math.max(0, Math.min(y, h - 1));
const i = (clampedY * w + clampedX) * 4;
return [
imgDataObj.data[i],
imgDataObj.data[i + 1],
imgDataObj.data[i + 2],
imgDataObj.data[i + 3],
];
}
// Helper function for bilinear interpolation. x, y are float coordinates.
function getPixelBilinear(imgDataObj, x, y, w, h) {
const x1 = Math.floor(x);
const y1 = Math.floor(y);
const fx = x - x1; // fractional part of x
const fy = y - y1; // fractional part of y
const invFx = 1 - fx;
const invFy = 1 - fy;
// Coordinates for the four surrounding pixels
const p11 = getPixel(imgDataObj, x1, y1, w, h); // top-left
const p21 = getPixel(imgDataObj, x1 + 1, y1, w, h); // top-right
const p12 = getPixel(imgDataObj, x1, y1 + 1, w, h); // bottom-left
const p22 = getPixel(imgDataObj, x1 + 1, y1 + 1, w, h); // bottom-right
// Interpolate R, G, B, A channels
const R = (p11[0] * invFx + p21[0] * fx) * invFy + (p12[0] * invFx + p22[0] * fx) * fy;
const G = (p11[1] * invFx + p21[1] * fx) * invFy + (p12[1] * invFx + p22[1] * fx) * fy;
const B = (p11[2] * invFx + p21[2] * fx) * invFy + (p12[2] * invFx + p22[2] * fx) * fy;
const A = (p11[3] * invFx + p21[3] * fx) * invFy + (p12[3] * invFx + p22[3] * fx) * fy;
return [R, G, B, A];
}
// If strength is effectively zero, copy image directly for performance
if (Math.abs(strength) < 1e-5) {
dstCtx.putImageData(srcImageData, 0, 0);
return dstCanvas;
}
for (let j = 0; j < height; j++) { // y-coordinate / row
for (let i = 0; i < width; i++) { // x-coordinate / col
const dx = i - absCenterX; // distance from distortion center X
const dy = j - absCenterY; // distance from distortion center Y
const distSq = dx * dx + dy * dy; // squared distance
const rNormSq = distSq * invKDivisor; // normalized squared radius from center
const distortionFactor = 1.0 + strength * rNormSq;
let R = 0, G = 0, B = 0, A = 0; // Default to transparent black for problematic cases
// If distortionFactor is near zero, it creates a singularity.
// Pixels at this radius would be mapped to/from infinity.
if (Math.abs(distortionFactor) < 1e-5) {
// Set to transparent black (already default) or another indicator color.
} else {
const srcX = absCenterX + dx / distortionFactor;
const srcY = absCenterY + dy / distortionFactor;
// Check if computed source coordinates are finite.
// If distortionFactor was extremely small (but not zero), srcX/Y could be Infinity.
if (isFinite(srcX) && isFinite(srcY)) {
// Perform bilinear interpolation to get source pixel color
[R, G, B, A] = getPixelBilinear(srcImageData, srcX, srcY, width, height);
}
// else: R,G,B,A remain transparent black if srcX/Y are Inf/NaN
}
const dstIndex = (j * width + i) * 4;
dstData[dstIndex] = R;
dstData[dstIndex + 1] = G;
dstData[dstIndex + 2] = B;
dstData[dstIndex + 3] = A;
}
}
dstCtx.putImageData(dstImageData, 0, 0);
return dstCanvas;
}
Apply Changes