You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, distortionStrength = 1.0, dishRadiusFactor = 1.0, backgroundColor = "transparent") {
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
if (imgWidth === 0 || imgHeight === 0) {
// Return an empty canvas or a canvas with background if image is invalid
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = imgWidth; // Could be 0
emptyCanvas.height = imgHeight; // Could be 0
if (imgWidth > 0 && imgHeight > 0) { // Only fill if it has dimensions
const emptyCtx = emptyCanvas.getContext('2d');
emptyCtx.fillStyle = backgroundColor;
emptyCtx.fillRect(0, 0, imgWidth, imgHeight);
}
return emptyCanvas;
}
const canvas = document.createElement('canvas');
canvas.width = imgWidth;
canvas.height = imgHeight;
const ctx = canvas.getContext('2d');
// Draw original image to a temporary canvas to get its pixel data
const tempCanvas = document.createElement('canvas');
tempCanvas.width = imgWidth;
tempCanvas.height = imgHeight;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
const srcData = tempCtx.getImageData(0, 0, imgWidth, imgHeight);
// Fill the_output canvas with the background color initially
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, imgWidth, imgHeight);
// Get the ImageData of the output canvas (which now contains the background color)
const destData = ctx.getImageData(0, 0, imgWidth, imgHeight);
const centerX = imgWidth / 2;
const centerY = imgHeight / 2;
const effectRadius = Math.min(centerX, centerY) * dishRadiusFactor;
// Helper function to get a pixel from source ImageData using integer coordinates.
// Handles boundary conditions by returning transparent black for out-of-bounds access.
function getPixelSafe(imageData, ix, iy) {
const { width, height, data } = imageData;
if (ix < 0 || ix >= width || iy < 0 || iy >= height) {
return [0, 0, 0, 0]; // Transparent black for out-of-bounds
}
const idx = (iy * width + ix) * 4;
return [data[idx], data[idx + 1], data[idx + 2], data[idx + 3]];
}
// Helper function for bilinear interpolation
function getPixelBilinear(imageData, x, y) { // x, y are float source coordinates
const x0 = Math.floor(x);
const y0 = Math.floor(y);
const dx_frac = x - x0; // fractional part for x
const dy_frac = y - y0; // fractional part for y
const p00 = getPixelSafe(imageData, x0, y0); // Top-left grid point
const p10 = getPixelSafe(imageData, x0 + 1, y0); // Top-right grid point
const p01 = getPixelSafe(imageData, x0, y0 + 1); // Bottom-left grid point
const p11 = getPixelSafe(imageData, x0 + 1, y0 + 1); // Bottom-right grid point
const result = [0, 0, 0, 0]; // R, G, B, A
for (let c = 0; c < 4; c++) {
const v00 = p00[c];
const v10 = p10[c];
const v01 = p01[c];
const v11 = p11[c];
const interpTop = v00 * (1 - dx_frac) + v10 * dx_frac;
const interpBottom = v01 * (1 - dx_frac) + v11 * dx_frac;
const val = interpTop * (1 - dy_frac) + interpBottom * dy_frac;
result[c] = Math.round(val);
}
return result;
}
// distortionStrength = 0: no effect. Higher positive values: stronger bulge.
// Negative values are clamped to 0.
const actualDistortionStrength = Math.max(0, parseFloat(distortionStrength) || 0);
const exponent = 1.0 / (1.0 + actualDistortionStrength);
for (let y_coord = 0; y_coord < imgHeight; y_coord++) {
for (let x_coord = 0; x_coord < imgWidth; x_coord++) {
const destIdx = (y_coord * imgWidth + x_coord) * 4;
const dx_center = x_coord - centerX; // Distance from center x
const dy_center = y_coord - centerY; // Distance from center y
const distanceToCenter = Math.sqrt(dx_center * dx_center + dy_center * dy_center);
if (distanceToCenter <= effectRadius) {
if (effectRadius === 0) { // Prevent division by zero if effectRadius is 0
// Effectively means only the center pixel might be part of the "dish"
// If current pixel is center, map from source center. Otherwise, it's background.
if (distanceToCenter === 0) {
const srcIdx = (Math.floor(centerY) * imgWidth + Math.floor(centerX)) * 4;
destData.data[destIdx] = srcData.data[srcIdx];
destData.data[destIdx+1] = srcData.data[srcIdx+1];
destData.data[destIdx+2] = srcData.data[srcIdx+2];
destData.data[destIdx+3] = srcData.data[srcIdx+3];
}
// else: it's already background color in destData
continue;
}
const angle = Math.atan2(dy_center, dx_center);
// Normalized distance from center, in range [0, 1] for pixels within effectRadius
const normalizedDistance = distanceToCenter / effectRadius;
let sourceNormalizedDistance;
if (normalizedDistance === 0) { // Center pixel
sourceNormalizedDistance = 0;
} else {
sourceNormalizedDistance = Math.pow(normalizedDistance, exponent);
}
const sourceDistance = sourceNormalizedDistance * effectRadius;
const sourceX = centerX + sourceDistance * Math.cos(angle);
const sourceY = centerY + sourceDistance * Math.sin(angle);
const rgba = getPixelBilinear(srcData, sourceX, sourceY);
destData.data[destIdx + 0] = rgba[0];
destData.data[destIdx + 1] = rgba[1];
destData.data[destIdx + 2] = rgba[2];
destData.data[destIdx + 3] = rgba[3]; // Alpha channel from interpolated source
}
// Else (distanceToCenter > effectRadius):
// This pixel is outside the dish area.
// It has already been filled with backgroundColor by fillRect,
// and destData was initialized from that canvas state. So, no action needed here.
}
}
ctx.putImageData(destData, 0, 0);
return canvas;
}
Apply Changes