You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, pincushionAmount = 0.2) {
// pincushionAmount: Strength of the pincushion effect.
// 0 means no distortion.
// Positive values (e.g., 0.2 to 0.5) create a pincushion effect.
// Higher values create a stronger effect.
// Very high values might lead to extreme warping.
const width = originalImg.width;
const height = originalImg.height;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
// Note: { willReadFrequently: true } is a performance hint for contexts that are frequently read from (e.g. getImageData)
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (width === 0 || height === 0) {
// Handle zero-size image.
return canvas;
}
// If pincushionAmount is zero, no distortion is applied.
// Draw the original image and return early.
if (pincushionAmount === 0) {
ctx.drawImage(originalImg, 0, 0, width, height);
return canvas;
}
// Create a temporary canvas to draw the original image and get its pixel data.
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
tempCtx.drawImage(originalImg, 0, 0, width, height);
const sourceImageData = tempCtx.getImageData(0, 0, width, height);
const sourceData = sourceImageData.data; // Pixel data array (Uint8ClampedArray)
const destImageData = ctx.createImageData(width, height);
const destData = destImageData.data;
// Bilinear interpolation helper function
// Takes pixel data array, image dimensions, and floating point coordinates (x, y)
// Returns an object {r, g, b, a} representing the interpolated pixel color.
function getBilinearPixel(pixelArr, imgWidth, imgHeight, x, y) {
const x0 = Math.floor(x);
const y0 = Math.floor(y);
const x1 = x0 + 1;
const y1 = y0 + 1;
const fx = x - x0; // Fractional part of x
const fy = y - y0; // Fractional part of y
const invFx = 1 - fx;
const invFy = 1 - fy;
// Helper to get pixel component at integer coordinates, clamping to image bounds
const getPixelComponent = (ix, iy, component) => {
const cx = Math.max(0, Math.min(ix, imgWidth - 1));
const cy = Math.max(0, Math.min(iy, imgHeight - 1));
return pixelArr[(cy * imgWidth + cx) * 4 + component];
};
let r = 0, g = 0, b = 0, a = 0;
for (let i = 0; i < 4; i++) { // Iterate over R, G, B, A components
const p00 = getPixelComponent(x0, y0, i);
const p10 = getPixelComponent(x1, y0, i);
const p01 = getPixelComponent(x0, y1, i);
const p11 = getPixelComponent(x1, y1, i);
const val = p00 * invFx * invFy +
p10 * fx * invFy +
p01 * invFx * fy +
p11 * fx * fy;
if (i === 0) r = val;
else if (i === 1) g = val;
else if (i === 2) b = val;
else if (i === 3) a = val;
}
return { r, g, b, a };
}
const aspect = width / height;
// For pincushion distortion, the coefficient k (in formulas like r_src = r_dst * (1 + k * r_dst^2))
// should be negative. We take positive pincushionAmount as input for user-friendliness.
const k_coefficient = -pincushionAmount;
for (let dstY = 0; dstY < height; dstY++) {
for (let dstX = 0; dstX < width; dstX++) {
// Normalize destination pixel coordinates to range [-0.5, 0.5] (approximately)
// Using (pixel_coord + 0.5) / dimension maps pixel centers correctly.
const normDstX = ((dstX + 0.5) / width) - 0.5;
const normDstY = ((dstY + 0.5) / height) - 0.5;
// Correct for aspect ratio to make distortion radially symmetric
// Y-axis is reference; X-axis is scaled by aspect ratio.
const correctedNormDstX_for_r = normDstX * aspect;
const correctedNormDstY_for_r = normDstY;
// Calculate squared radius from center in aspect-corrected normalized space
const r2 = correctedNormDstX_for_r * correctedNormDstX_for_r +
correctedNormDstY_for_r * correctedNormDstY_for_r;
// Apply distortion formula: src = dst * (1 + k * r_dst^2)
const distortionFactor = 1.0 + k_coefficient * r2;
// Calculate source coordinates in aspect-corrected normalized space
const correctedNormSrcX = correctedNormDstX_for_r * distortionFactor;
const correctedNormSrcY = correctedNormDstY_for_r * distortionFactor;
// Revert aspect ratio correction to get normalized UV source coordinates [0,1]
const normSrcX = (correctedNormSrcX / aspect) + 0.5;
const normSrcY = correctedNormSrcY + 0.5;
// Convert normalized UV source coordinates back to pixel coordinates
// Subtract 0.5 to map from pixel grid centers.
const srcX = normSrcX * width - 0.5;
const srcY = normSrcY * height - 0.5;
const destIdx = (dstY * width + dstX) * 4;
// Sample color from source image using bilinear interpolation
// The getBilinearPixel function handles boundary clamping for srcX, srcY implicitly.
const { r, g, b, a } = getBilinearPixel(sourceData, width, height, srcX, srcY);
destData[destIdx] = r;
destData[destIdx + 1] = g;
destData[destIdx + 2] = b;
destData[destIdx + 3] = a;
}
}
ctx.putImageData(destImageData, 0, 0);
return canvas;
}
Apply Changes