You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, effect = 'pinch', strength = 0.5, radius = 0.5) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
ctx.drawImage(originalImg, 0, 0, width, height);
const sourceData = ctx.getImageData(0, 0, width, height);
const destData = ctx.createImageData(width, height);
const centerX = width / 2;
const centerY = height / 2;
/**
* Performs bilinear interpolation to find the color of a non-integer coordinate.
* @param {ImageData} imgData The source image data.
* @param {number} x The x-coordinate.
* @param {number} y The y-coordinate.
* @returns {Uint8ClampedArray} An array containing [r, g, b, a] for the interpolated pixel.
*/
function getBilinearPixel(imgData, x, y) {
const x1 = Math.floor(x);
const y1 = Math.floor(y);
// Check if the coordinates are out of bounds
if (x1 < 0 || x1 >= width - 1 || y1 < 0 || y1 >= height - 1) {
// Return a default color (e.g., transparent black) for out-of-bounds pixels
const safeX = Math.max(0, Math.min(width - 1, Math.floor(x)));
const safeY = Math.max(0, Math.min(height - 1, Math.floor(y)));
const i = (safeY * width + safeX) * 4;
return sourceData.data.slice(i, i+4);
}
const x2 = x1 + 1;
const y2 = y1 + 1;
const i1 = (y1 * width + x1) * 4;
const i2 = (y1 * width + x2) * 4;
const i3 = (y2 * width + x1) * 4;
const i4 = (y2 * width + x2) * 4;
const xFrac = x - x1;
const yFrac = y - y1;
const xFracInv = 1 - xFrac;
const yFracInv = 1 - yFrac;
const out = new Uint8ClampedArray(4);
for (let i = 0; i < 4; i++) { // R, G, B, A
const c1 = imgData.data[i1 + i];
const c2 = imgData.data[i2 + i];
const c3 = imgData.data[i3 + i];
const c4 = imgData.data[i4 + i];
const top = c1 * xFracInv + c2 * xFrac;
const bottom = c3 * xFracInv + c4 * xFrac;
out[i] = top * yFracInv + bottom * yFrac;
}
return out;
}
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let sourceX = x;
let sourceY = y;
const dx = x - centerX;
const dy = y - centerY;
const dist = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
switch (effect) {
case 'pinch':
case 'bulge': {
const actualRadius = radius * Math.min(width, height);
if (dist < actualRadius) {
// For bulge, amount is positive; for pinch, it's negative.
// strength is assumed to be [0, 1].
const amount = (effect === 'pinch' ? -strength : strength);
// Using an inverse function to map destination pixels to source pixels
const power = 1.0 - amount;
if (power !== 0) { // Avoid division by zero
const sourceRadius = actualRadius * Math.pow(dist / actualRadius, 1.0 / power);
sourceX = centerX + sourceRadius * Math.cos(angle);
sourceY = centerY + sourceRadius * Math.sin(angle);
}
}
break;
}
case 'swirl': {
const actualRadius = radius * Math.min(width, height);
if (dist < actualRadius) {
const factor = 1.0 - (dist / actualRadius);
// Strength parameter interpreted as number of full rotations
const maxAngle = strength * 2 * Math.PI;
// The swirl angle decreases with distance from the center
const swirlAngle = maxAngle * factor * factor; // Use factor^2 for smoother falloff
// Inverse mapping: find the source angle for the destination pixel
const sourceAngle = angle - swirlAngle;
sourceX = centerX + dist * Math.cos(sourceAngle);
sourceY = centerY + dist * Math.sin(sourceAngle);
}
break;
}
case 'fisheye': {
// Fisheye is a strong bulge effect over the whole image
const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);
// strength is assumed to be [0, 1]
const amount = strength;
const power = 1.0 - amount;
if (power > 0.001) { // Avoid power being zero or negative for stability
const sourceRadius = maxDist * Math.pow(dist / maxDist, 1.0 / power);
sourceX = centerX + sourceRadius * Math.cos(angle);
sourceY = centerY + sourceRadius * Math.sin(angle);
}
// If strength is too high, we default to the original pixel
break;
}
}
const color = getBilinearPixel(sourceData, sourceX, sourceY);
const index = (y * width + x) * 4;
destData.data.set(color, index);
}
}
ctx.putImageData(destData, 0, 0);
return canvas;
}
Apply Changes