You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, strength = 0.5, lensRadiusFactor = 1.0, zoom = 1.0) {
const imgWidth = originalImg.width;
const imgHeight = originalImg.height;
const sourceCanvas = document.createElement('canvas');
sourceCanvas.width = imgWidth;
sourceCanvas.height = imgHeight;
// Use { willReadFrequently: true } for potential performance optimization if supported
const sourceCtx = sourceCanvas.getContext('2d', { willReadFrequently: true });
sourceCtx.drawImage(originalImg, 0, 0);
const sourceImageData = sourceCtx.getImageData(0, 0, imgWidth, imgHeight);
const sourceData = sourceImageData.data;
const destCanvas = document.createElement('canvas');
destCanvas.width = imgWidth;
destCanvas.height = imgHeight;
const destCtx = destCanvas.getContext('2d');
const destImageData = destCtx.createImageData(imgWidth, imgHeight);
const destData = destImageData.data;
const centerX = imgWidth / 2;
const centerY = imgHeight / 2;
const baseRadius = Math.min(centerX, centerY);
const lensRadiusPixels = baseRadius * lensRadiusFactor;
// Helper function to get raw pixel color with boundary clamping
function getPixelColor(data, W, H, u, v) {
u = Math.max(0, Math.min(W - 1, Math.floor(u)));
v = Math.max(0, Math.min(H - 1, Math.floor(v)));
const idx = (v * W + u) * 4;
return [data[idx], data[idx + 1], data[idx + 2], data[idx + 3]];
}
// Helper function for bilinear interpolation
function getPixelBilinear(data, W, H, u_float, v_float) {
const u_safe = Math.max(0, Math.min(W - 1.000000001, u_float)); // Ensure u_safe < W-1 for u0 calculation
const v_safe = Math.max(0, Math.min(H - 1.000000001, v_float)); // Ensure v_safe < H-1 for v0 calculation
const u0 = Math.floor(u_safe);
const v0 = Math.floor(v_safe);
// u1 and v1 will be clamped by getPixelColor if they exceed bounds
const u1 = u0 + 1;
const v1 = v0 + 1;
const frac_u = u_safe - u0;
const frac_v = v_safe - v0;
const one_minus_frac_u = 1.0 - frac_u;
const one_minus_frac_v = 1.0 - frac_v;
const c00 = getPixelColor(data, W, H, u0, v0);
const c10 = getPixelColor(data, W, H, u1, v0);
const c01 = getPixelColor(data, W, H, u0, v1);
const c11 = getPixelColor(data, W, H, u1, v1);
const R = c00[0] * one_minus_frac_u * one_minus_frac_v + c10[0] * frac_u * one_minus_frac_v +
c01[0] * one_minus_frac_u * frac_v + c11[0] * frac_u * frac_v;
const G = c00[1] * one_minus_frac_u * one_minus_frac_v + c10[1] * frac_u * one_minus_frac_v +
c01[1] * one_minus_frac_u * frac_v + c11[1] * frac_u * frac_v;
const B = c00[2] * one_minus_frac_u * one_minus_frac_v + c10[2] * frac_u * one_minus_frac_v +
c01[2] * one_minus_frac_u * frac_v + c11[2] * frac_u * frac_v;
const A = c00[3] * one_minus_frac_u * one_minus_frac_v + c10[3] * frac_u * one_minus_frac_v +
c01[3] * one_minus_frac_u * frac_v + c11[3] * frac_u * frac_v;
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;
const r_dest = Math.sqrt(dx * dx + dy * dy);
const dest_idx = (y * imgWidth + x) * 4;
if (lensRadiusPixels === 0 && r_dest > 0) { // Lens radius is zero, only center pixel is not transparent
destData[dest_idx] = 0; destData[dest_idx + 1] = 0; destData[dest_idx + 2] = 0; destData[dest_idx + 3] = 0;
continue;
}
if (r_dest > lensRadiusPixels && lensRadiusPixels > 0) { // Outside the fisheye circle
destData[dest_idx] = 0; destData[dest_idx + 1] = 0; destData[dest_idx + 2] = 0; destData[dest_idx + 3] = 0;
continue;
}
const common_theta = Math.atan2(dy, dx); // Angle of (dx, dy)
let r_src;
if (strength === 0.0) {
r_src = r_dest;
} else {
const K_angle = strength * (Math.PI / 2.0);
let phi;
if (r_dest === 0) { // Center pixel
phi = 0;
} else if (lensRadiusPixels === 0) { // r_dest is 0 here due to earlier check, but being defensive
phi = 0;
} else {
phi = (r_dest / lensRadiusPixels) * K_angle;
}
if (phi >= Math.PI / 2.0) {
phi = Math.PI / 2.0 - 1e-9; // Epsilon to avoid tan(PI/2)
}
if (K_angle < 1e-8) {
r_src = r_dest;
} else {
const f_eff = lensRadiusPixels / K_angle;
r_src = f_eff * Math.tan(phi);
}
}
if (zoom === 0) { // Avoid division by zero; treat as extreme zoom-in to center
r_src = 0;
} else {
r_src /= zoom;
}
const sx_float = centerX + r_src * Math.cos(common_theta);
const sy_float = centerY + r_src * Math.sin(common_theta);
const color = getPixelBilinear(sourceData, imgWidth, imgHeight, sx_float, sy_float);
destData[dest_idx] = color[0];
destData[dest_idx + 1] = color[1];
destData[dest_idx + 2] = color[2];
destData[dest_idx + 3] = color[3];
}
}
destCtx.putImageData(destImageData, 0, 0);
return destCanvas;
}
Apply Changes