You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, strength = 50, waistLevel = 0.5, waistHeight = 0.4) {
/**
* Helper function to get pixel data from a specific coordinate using bilinear interpolation.
* This creates a smoother result than nearest-neighbor sampling.
* @param {Uint8ClampedArray} pixels - The source image pixel data array.
* @param {number} width - The width of the source image.
* @param {number} height - The height of the source image.
* @param {number} x - The x-coordinate to sample from (can be a float).
* @param {number} y - The y-coordinate to sample from (can be a float).
* @returns {{r: number, g: number, b: number, a: number}} The interpolated pixel color.
*/
function getPixelBilinear(pixels, width, height, x, y) {
const x_floor = Math.floor(x);
const y_floor = Math.floor(y);
const x_ceil = Math.min(width - 1, x_floor + 1);
const y_ceil = Math.min(height - 1, y_floor + 1);
// If the coordinates are out of bounds, return the nearest edge pixel
if (x_floor < 0 || x_floor >= width || y_floor < 0 || y_floor >= height) {
const clampedX = Math.max(0, Math.min(width - 1, x_floor));
const clampedY = Math.max(0, Math.min(height - 1, y_floor));
const clampedIndex = (clampedY * width + clampedX) * 4;
return {
r: pixels[clampedIndex],
g: pixels[clampedIndex + 1],
b: pixels[clampedIndex + 2],
a: pixels[clampedIndex + 3]
};
}
const fx = x - x_floor;
const fy = y - y_floor;
const fx1 = 1 - fx;
const fy1 = 1 - fy;
const i_tl = (y_floor * width + x_floor) * 4;
const i_tr = (y_floor * width + x_ceil) * 4;
const i_bl = (y_ceil * width + x_floor) * 4;
const i_br = (y_ceil * width + x_ceil) * 4;
const w_tl = fx1 * fy1;
const w_tr = fx * fy1;
const w_bl = fx1 * fy;
const w_br = fx * fy;
const r = pixels[i_tl] * w_tl + pixels[i_tr] * w_tr + pixels[i_bl] * w_bl + pixels[i_br] * w_br;
const g = pixels[i_tl + 1] * w_tl + pixels[i_tr + 1] * w_tr + pixels[i_bl + 1] * w_bl + pixels[i_br + 1] * w_br;
const b = pixels[i_tl + 2] * w_tl + pixels[i_tr + 2] * w_tr + pixels[i_bl + 2] * w_bl + pixels[i_br + 2] * w_br;
const a = pixels[i_tl + 3] * w_tl + pixels[i_tr + 3] * w_tr + pixels[i_bl + 3] * w_bl + pixels[i_br + 3] * w_br;
return { r, g, b, a };
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
canvas.width = width;
canvas.height = height;
ctx.drawImage(originalImg, 0, 0, width, height);
const sourceData = ctx.getImageData(0, 0, width, height);
const targetData = ctx.createImageData(width, height);
const sourcePixels = sourceData.data;
const targetPixels = targetData.data;
// Define the pinch effect parameters
const centerX = width / 2;
const centerY = height * Math.max(0, Math.min(1, waistLevel));
const effectRadiusY = (height * Math.max(0, Math.min(1, waistHeight))) / 2;
// Map strength (0-100) to a safer range (0-0.8) to prevent extreme distortion
// where the scaling factor could become zero or negative.
const pinchAmount = Math.min(strength, 80) / 100.0;
if (effectRadiusY <= 0 || pinchAmount <= 0) {
return canvas; // No effect to apply, return the original drawn canvas
}
// Iterate over each pixel in the target canvas
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let sourceX = x;
const sourceY = y; // No vertical distortion
const dy = y - centerY;
const distY = Math.abs(dy);
// Apply effect only if the pixel is within the vertical waist area
if (distY < effectRadiusY) {
// Calculate a smooth falloff factor (0-1) based on vertical distance from the center
// A parabolic curve (1 - t^2) provides a smooth ease-in/ease-out effect.
const verticalT = distY / effectRadiusY;
const verticalFactor = 1.0 - verticalT * verticalT;
// Calculate the horizontal scaling factor. A value < 1 means we are pinching.
const scale = 1.0 - pinchAmount * verticalFactor;
// Using reverse mapping to find the corresponding source pixel for each target pixel.
// This prevents holes or artifacts in the output image.
// We calculate `sourceX` from `x` by "un-scaling" its distance from the center.
sourceX = centerX + (x - centerX) / scale;
}
// Get the color from the source image using interpolation for a high-quality result
const color = getPixelBilinear(sourcePixels, width, height, sourceX, sourceY);
// Set the pixel in our target image data
const targetIndex = (y * width + x) * 4;
targetPixels[targetIndex] = color.r;
targetPixels[targetIndex + 1] = color.g;
targetPixels[targetIndex + 2] = color.b;
targetPixels[targetIndex + 3] = color.a;
}
}
// Draw the modified pixel data back to the canvas
ctx.putImageData(targetData, 0, 0);
return canvas;
}
Apply Changes