You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, angle = 90, centerX = 0.5, centerY = 0.5, radius = 0.5) {
const width = originalImg.width;
const height = originalImg.height;
const resultCanvas = document.createElement('canvas');
resultCanvas.width = width;
resultCanvas.height = height;
const resultCtx = resultCanvas.getContext('2d');
if (width === 0 || height === 0) {
return resultCanvas; // Return empty canvas if image is 0x0
}
// Parse parameters, providing defaults if parsing fails or invalid values are given
let fAngle = parseFloat(angle);
if (isNaN(fAngle)) fAngle = 90;
let fCenterX = parseFloat(centerX);
if (isNaN(fCenterX)) fCenterX = 0.5;
let fCenterY = parseFloat(centerY);
if (isNaN(fCenterY)) fCenterY = 0.5;
let fRadius = parseFloat(radius);
if (isNaN(fRadius) || fRadius < 0) fRadius = 0.5; // Radius cannot be negative
const radAngle = fAngle * Math.PI / 180;
const iRadius = fRadius * Math.min(width, height); // Effective radius in pixels
// If no actual twirl will happen (zero angle or zero radius), draw original image and return
if (radAngle === 0 || iRadius <= 0) {
resultCtx.drawImage(originalImg, 0, 0);
return resultCanvas;
}
// Draw original image to a temporary canvas to get 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);
let imageData;
try {
imageData = tempCtx.getImageData(0, 0, width, height);
} catch (e) {
// This can happen due to tainted canvas if image is cross-origin
console.error("Error getting image data for twirl filter: ", e);
resultCtx.drawImage(originalImg, 0, 0); // Draw original image as fallback
// Optionally, draw an error message on the canvas
resultCtx.font = "14px Arial";
resultCtx.fillStyle = "red";
resultCtx.textAlign = "center";
resultCtx.fillText("Error: Could not process cross-origin image.", width / 2, height / 2);
return resultCanvas;
}
const pixels = imageData.data;
const newImageData = resultCtx.createImageData(width, height);
const newPixels = newImageData.data;
const actualCenterX = fCenterX * width;
const actualCenterY = fCenterY * height;
// Bilinear interpolation helper function
// Gets the color of a floating-point coordinate (x_src, y_src)
const getPixelBilinear = (x_src, y_src) => {
const x1 = Math.floor(x_src);
const y1 = Math.floor(y_src);
const fx = x_src - x1; // Fractional part of x
const fy = y_src - y1; // Fractional part of y
const invFx = 1 - fx;
const invFy = 1 - fy;
const resultColor = [0, 0, 0, 0]; // R, G, B, A
for (let ch = 0; ch < 4; ++ch) { // Iterate over R, G, B, A channels
// Helper to get a component value from original pixel data, clamping coordinates
const getClampedComponent = (currX, currY) => {
const sX = Math.max(0, Math.min(width - 1, currX));
const sY = Math.max(0, Math.min(height - 1, currY));
return pixels[(sY * width + sX) * 4 + ch];
};
const C00 = getClampedComponent(x1, y1); // Color component at (x1, y1)
const C10 = getClampedComponent(x1 + 1, y1); // Color component at (x1+1, y1)
const C01 = getClampedComponent(x1, y1 + 1); // Color component at (x1, y1+1)
const C11 = getClampedComponent(x1 + 1, y1 + 1); // Color component at (x1+1, y1+1)
// Interpolate along x-axis for y1 and y2
const R_x_y1 = C00 * invFx + C10 * fx;
const R_x_y2 = C01 * invFx + C11 * fx;
// Interpolate along y-axis
resultColor[ch] = R_x_y1 * invFy + R_x_y2 * fy;
}
return resultColor;
};
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const dx = x - actualCenterX; // Distance from center x
const dy = y - actualCenterY; // Distance from center y
const distance = Math.sqrt(dx * dx + dy * dy);
let srcX = x;
let srcY = y;
if (distance < iRadius) { // Only twirl pixels within the specified radius
// Standard twirl: rotation angle increases with distance from center, up to radAngle at iRadius
const rotation = radAngle * (distance / iRadius);
// To find the source pixel (srcX, srcY) for the destination pixel (x,y),
// we effectively rotate (dx, dy) by -rotation.
// src_offset_X = dx * cos(-rotation) - dy * sin(-rotation)
// = dx * cos(rotation) + dy * sin(rotation)
// src_offset_Y = dx * sin(-rotation) + dy * cos(-rotation)
// = -dx * sin(rotation) + dy * cos(rotation)
const cosRot = Math.cos(rotation);
const sinRot = Math.sin(rotation);
const rotatedOffsetX = dx * cosRot + dy * sinRot;
const rotatedOffsetY = -dx * sinRot + dy * cosRot;
srcX = actualCenterX + rotatedOffsetX;
srcY = actualCenterY + rotatedOffsetY;
}
const [r_val, g_val, b_val, a_val] = getPixelBilinear(srcX, srcY);
const destIdx = (y * width + x) * 4;
newPixels[destIdx] = r_val;
newPixels[destIdx + 1] = g_val;
newPixels[destIdx + 2] = b_val;
newPixels[destIdx + 3] = a_val;
}
}
resultCtx.putImageData(newImageData, 0, 0);
return resultCanvas;
}
Apply Changes