You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, mustacheCenterXRatio = 0.5, mustacheCenterYRatio = 0.6, mustacheWidthRatio = 0.3, mustacheHeightRatio = 0.1, strength = 0.5) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // willReadFrequently for performance with getImageData/putImageData
// Ensure originalImg has dimensions. If not, it might not be loaded.
// The function will produce a 0x0 canvas if img dimensions are 0.
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
if (canvas.width === 0 || canvas.height === 0) {
// Return an empty canvas if image has no dimensions
return canvas;
}
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
const imgWidth = canvas.width;
const imgHeight = canvas.height;
let imageData;
try {
imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
// This can happen due to tainted canvas (e.g. cross-origin image without CORS)
console.error("Error getting ImageData:", e);
// Return canvas with original image drawn if we can't process pixels
return canvas;
}
const dstImageData = ctx.createImageData(imgWidth, imgHeight);
const dstData = dstImageData.data;
const centerX = imgWidth * mustacheCenterXRatio;
const centerY = imgHeight * mustacheCenterYRatio;
// Radii are half of the total width/height of the effect area
const radiusX = imgWidth * mustacheWidthRatio / 2;
const radiusY = imgHeight * mustacheHeightRatio / 2;
// If radii are zero or negative, or strength is zero, no distortion occurs.
// Strength = 0 means scale_factor = r_norm^0 = 1 (for r_norm != 0), resulting in no change.
// r_norm = 0 (center) is handled separately.
if (radiusX <= 0 || radiusY <= 0) {
// No distortion area, return original image on canvas
ctx.putImageData(imageData, 0, 0);
return canvas;
}
// Bilinear interpolation helper function
function getPixelBilinear(srcImgData, x, y) {
const srcData = srcImgData.data;
const width = srcImgData.width;
const height = srcImgData.height;
// Clamp coordinates to be within the source image bounds
const s_x = Math.max(0, Math.min(x, width - 1));
const s_y = Math.max(0, Math.min(y, height - 1));
const x1 = Math.floor(s_x);
const y1 = Math.floor(s_y);
// Ensure x2, y2 are also within bounds
const x2 = Math.min(x1 + 1, width - 1);
const y2 = Math.min(y1 + 1, height - 1);
const fx = s_x - x1; // Fractional part for x
const fy = s_y - y1; // Fractional part for y
const fx1 = 1 - fx;
const fy1 = 1 - fy;
const idx11 = (y1 * width + x1) * 4;
const idx12 = (y2 * width + x1) * 4; // Pixel at (x1, y2)
const idx21 = (y1 * width + x2) * 4; // Pixel at (x2, y1)
const idx22 = (y2 * width + x2) * 4; // Pixel at (x2, y2)
const r = srcData[idx11] * fx1 * fy1 + srcData[idx21] * fx * fy1 + srcData[idx12] * fx1 * fy + srcData[idx22] * fx * fy;
const g = srcData[idx11+1] * fx1 * fy1 + srcData[idx21+1] * fx * fy1 + srcData[idx12+1] * fx1 * fy + srcData[idx22+1] * fx * fy;
const b = srcData[idx11+2] * fx1 * fy1 + srcData[idx21+2] * fx * fy1 + srcData[idx12+2] * fx1 * fy + srcData[idx22+2] * fx * fy;
const a = srcData[idx11+3] * fx1 * fy1 + srcData[idx21+3] * fx * fy1 + srcData[idx12+3] * fx1 * fy + srcData[idx22+3] * fx * fy;
return [r, g, b, a];
}
for (let y = 0; y < imgHeight; y++) {
for (let x = 0; x < imgWidth; x++) {
const dx = x - centerX;
const dy = y - centerY;
// Normalized coordinates relative to ellipse radii
// (dx / radiusX)^2 + (dy / radiusY)^2 = 1 is the ellipse equation
const nx = dx / radiusX;
const ny = dy / radiusY;
const r_squared_norm = nx * nx + ny * ny;
let sourceX = x;
let sourceY = y;
if (r_squared_norm < 1) { // Pixel is inside the distortion ellipse
const r_norm = Math.sqrt(r_squared_norm); // Normalized distance from center (0 to <1)
if (r_norm === 0 && strength !== 0) { // At the very center of the ellipse
// If strength is positive (bulge), center maps to center.
// If strength is negative (pinch), center also maps to center.
// (0^strength is 0 for strength>0, Math.pow(0,0)=1, Math.pow(0,negative)=Infinity)
// To ensure center pixel always maps to center for simplicity unless strength is exactly 0:
sourceX = centerX;
sourceY = centerY;
} else if (strength === 0) { // No distortion if strength is 0
const scale_factor = 1; // Correctly declare scale_factor
sourceX = centerX + dx * scale_factor; // Effectively x
sourceY = centerY + dy * scale_factor; // Effectively y
}
else { // r_norm > 0 and strength !== 0
// Apply distortion
// strength > 0 for bulge (samples closer to center, scale_factor < 1 for r_norm < 1)
// strength < 0 for pinch (samples further from center, scale_factor > 1 for r_norm < 1)
const scale_factor = Math.pow(r_norm, strength);
sourceX = centerX + dx * scale_factor;
sourceY = centerY + dy * scale_factor;
}
}
const pixelDestIndex = (y * imgWidth + x) * 4;
const [r, g, b, a] = getPixelBilinear(imageData, sourceX, sourceY);
dstData[pixelDestIndex] = r;
dstData[pixelDestIndex + 1] = g;
dstData[pixelDestIndex + 2] = b;
dstData[pixelDestIndex + 3] = a;
}
}
ctx.putImageData(dstImageData, 0, 0);
return canvas;
}
Apply Changes