You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, filterType = "pixelate", param1 = 10, param2 = 0.1, param3 = 0.4, param4 = 0.15, param5 = -0.1, param6 = 90, param7 = 0.8, param8 = 0.5, param9 = 0.75) {
// Parameter mapping based on filterType:
// param1: For "pixelate" -> blockSize (integer, e.g., 10)
// param2: For "googlyEyes" -> eyeScale (number, e.g., 0.1 relative to image width)
// param3: For "googlyEyes" -> pupilScale (number, e.g., 0.4 relative to sclera radius)
// param4: For "googlyEyes" -> eyeDistFactor (number, e.g., 0.15, each eye's distance from image center X, relative to image width)
// param5: For "googlyEyes" -> eyeYOffsetFactor (number, e.g., -0.1, vertical offset from image center Y, relative to image height)
// param6: For "swirl" -> swirlAngle (number, degrees, e.g., 90)
// param7: For "swirl" -> swirlRadiusFactor (number, e.g., 0.8, active radius as a factor of min(width,height)/2)
// param8: For "bulge" / "pinch"-> bulgePinchFactor (number, e.g., 0.5 for bulge; 1.0 for no effect; >1.0 for pinch)
// param9: For "bulge" / "pinch"-> bulgePinchRadiusFactor (number, e.g., 0.75, active radius as a factor of min(width,height)/2)
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // Optimize for frequent getImageData/putImageData
const w = originalImg.width;
const h = originalImg.height;
if (w === 0 || h === 0) {
console.error("Image has zero width or height. Ensure it's loaded and dimensions are valid.");
canvas.width = 1; // Return a minimal canvas to indicate an issue
canvas.height = 1;
ctx.fillStyle = "red"; // Red pixel
ctx.fillRect(0,0,1,1);
return canvas;
}
canvas.width = w;
canvas.height = h;
ctx.drawImage(originalImg, 0, 0, w, h); // Draw the original image first
// Helper function for bilinear interpolation.
// Samples a color from 'pixelDataArray' (which is imageData.data) at floating point (x,y) coordinates.
function getPixelBilinear(pixelDataArray, imgWidth, imgHeight, x, y) {
const x_floor = Math.floor(x);
const y_floor = Math.floor(y);
// Clamp coordinates to be within image bounds for pixel sampling
const x1 = Math.max(0, Math.min(imgWidth - 1, x_floor)); // Top-left X
const y1 = Math.max(0, Math.min(imgHeight - 1, y_floor)); // Top-left Y
const x2 = Math.max(0, Math.min(imgWidth - 1, x_floor + 1));// Top-right X / Bottom-right X
const y2 = Math.max(0, Math.min(imgHeight - 1, y_floor + 1));// Bottom-left Y / Bottom-right Y
const idxTL = (y1 * imgWidth + x1) * 4; // Index for Top-Left pixel
const idxTR = (y1 * imgWidth + x2) * 4; // Index for Top-Right pixel
const idxBL = (y2 * imgWidth + x1) * 4; // Index for Bottom-Left pixel
const idxBR = (y2 * imgWidth + x2) * 4; // Index for Bottom-Right pixel
const fx = x - x_floor; // Fractional part of x
const fy = y - y_floor; // Fractional part of y
const fx1 = 1 - fx; // 1 - fractional part of x
const fy1 = 1 - fy; // 1 - fractional part of y
// Calculate weights for each of the 4 neighboring pixels
const wTL = fx1 * fy1;
const wTR = fx * fy1;
const wBL = fx1 * fy;
const wBR = fx * fy;
// Interpolate R, G, B, A channels
const r = pixelDataArray[idxTL] * wTL + pixelDataArray[idxTR] * wTR + pixelDataArray[idxBL] * wBL + pixelDataArray[idxBR] * wBR;
const g = pixelDataArray[idxTL+1] * wTL + pixelDataArray[idxTR+1] * wTR + pixelDataArray[idxBL+1] * wBL + pixelDataArray[idxBR+1] * wBR;
const b = pixelDataArray[idxTL+2] * wTL + pixelDataArray[idxTR+2] * wTR + pixelDataArray[idxBL+2] * wBL + pixelDataArray[idxBR+2] * wBR;
const a = pixelDataArray[idxTL+3] * wTL + pixelDataArray[idxTR+3] * wTR + pixelDataArray[idxBL+3] * wBL + pixelDataArray[idxBR+3] * wBR;
return [r, g, b, a];
}
switch (filterType) {
case "pixelate": {
const blockSize = Math.max(1, Math.floor(param1));
// For pixelation, we sample the original image (already on canvas)
const originalImageData = ctx.getImageData(0, 0, w, h);
const origData = originalImageData.data;
for (let yBlock = 0; yBlock < h; yBlock += blockSize) {
for (let xBlock = 0; xBlock < w; xBlock += blockSize) {
let sumR = 0, sumG = 0, sumB = 0, sumA = 0;
let count = 0;
// Calculate average color for the current block
for (let yPx = yBlock; yPx < Math.min(yBlock + blockSize, h); yPx++) {
for (let xPx = xBlock; xPx < Math.min(xBlock + blockSize, w); xPx++) {
const idx = (yPx * w + xPx) * 4;
sumR += origData[idx];
sumG += origData[idx + 1];
sumB += origData[idx + 2];
sumA += origData[idx + 3];
count++;
}
}
if (count > 0) {
ctx.fillStyle = `rgba(${sumR/count}, ${sumG/count}, ${sumB/count}, ${sumA/count/255})`;
// Fill the block on the canvas. Handle edge blocks carefully.
ctx.fillRect(xBlock, yBlock, Math.min(blockSize, w - xBlock), Math.min(blockSize, h - yBlock));
}
}
}
break;
}
case "googlyEyes": {
const eyeScale = param2;
const pupilScale = param3;
const eyeDistFactor = param4; // Distance for *each* eye from center X
const eyeYOffsetFactor = param5;
const centerX = w / 2;
const centerY = h / 2;
const scleraRadius = Math.max(1, (w * eyeScale) / 2);
const pupilRadius = Math.max(1, scleraRadius * pupilScale);
// Calculate positions for left and right eyes
const finalEyeLX = centerX - w * eyeDistFactor;
const finalEyeRX = centerX + w * eyeDistFactor;
const finalEyeY = centerY + h * eyeYOffsetFactor;
const maxPupilOffset = Math.max(0, scleraRadius - pupilRadius); // Max distance pupil can move within sclera
function drawEye(eyeCenterX, eyeCenterY, scleraR, pupilR) {
// Random offset for pupil to make it "googly"
const pupilOffsetX = (Math.random() * 2 - 1) * maxPupilOffset * 0.7; // 0.7 limits offset slightly
const pupilOffsetY = (Math.random() * 2 - 1) * maxPupilOffset * 0.7;
// Draw sclera (white part)
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(eyeCenterX, eyeCenterY, scleraR, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = 'black'; // Outline for sclera
ctx.lineWidth = Math.max(1, scleraR * 0.05); // Line width relative to sclera size
ctx.stroke();
// Draw pupil (black part)
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(eyeCenterX + pupilOffsetX, eyeCenterY + pupilOffsetY, pupilR, 0, Math.PI * 2);
ctx.fill();
}
// Draw eyes on top of the existing image on canvas
drawEye(finalEyeLX, finalEyeY, scleraRadius, pupilRadius);
drawEye(finalEyeRX, finalEyeY, scleraRadius, pupilRadius);
break;
}
case "swirl":
case "bulge": { // Swirl and Bulge/Pinch filters involve per-pixel remapping
const srcImageData = ctx.getImageData(0, 0, w, h); // Get current canvas pixels to sample from
const srcData = srcImageData.data;
const destImageData = ctx.createImageData(w, h); // Create a new buffer for destination pixels
const destData = destImageData.data;
const centerX = w / 2;
const centerY = h / 2;
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const dx = x - centerX; // Distance from center X
const dy = y - centerY; // Distance from center Y
const distance = Math.sqrt(dx * dx + dy * dy); // Overall distance from center
let srcX = x; // Source X to sample from (defaults to current X)
let srcY = y; // Source Y to sample from (defaults to current Y)
if (filterType === "swirl") {
const swirlAngleDeg = param6;
const swirlRadiusFactor = param7;
const swirlAngleRad = swirlAngleDeg * Math.PI / 180; // Convert degrees to radians
const effectRadius = (Math.min(w, h) / 2) * swirlRadiusFactor;
if (distance < effectRadius && effectRadius > 0) { // Apply only within effect radius
const angle = Math.atan2(dy, dx); // Original angle of pixel relative to center
// Swirl strength decreases linearly from center to edge of effectRadius
const swirlStrength = 1 - (distance / effectRadius);
const newAngle = angle + swirlAngleRad * swirlStrength; // New angle after swirl
srcX = centerX + distance * Math.cos(newAngle);
srcY = centerY + distance * Math.sin(newAngle);
}
} else { // "bulge" (or "pinch")
const bulgePinchFactor = param8; // <1.0 for bulge, 1.0 for no effect, >1.0 for pinch
const bulgePinchRadiusFactor = param9;
const effectRadius = (Math.min(w, h) / 2) * bulgePinchRadiusFactor;
if (distance < effectRadius && effectRadius > 0 && bulgePinchFactor > 0.001) { // Factor must be positive
const normDist = distance / effectRadius; // Normalized distance [0,1)
let transformedNormDist;
if (Math.abs(bulgePinchFactor - 1.0) < 0.001) { // Effectively no change if factor is 1.0
transformedNormDist = normDist;
} else {
// Exponent adjusts sampling radius:
// bulgePinchFactor 0.5 (bulge) => exponent 2 => samples from smaller radius (magnifies center)
// bulgePinchFactor 2.0 (pinch) => exponent 0.5 => samples from larger radius (minifies center)
const exponent = 1.0 / bulgePinchFactor;
transformedNormDist = Math.pow(normDist, exponent);
}
const newDist = transformedNormDist * effectRadius;
const angle = Math.atan2(dy, dx);
if (distance === 0) { // Center pixel remains center
srcX = centerX;
srcY = centerY;
} else {
srcX = centerX + newDist * Math.cos(angle);
srcY = centerY + newDist * Math.sin(angle);
}
}
}
// Sample color from source coordinates (srcX, srcY) using bilinear interpolation
const C = getPixelBilinear(srcData, w, h, srcX, srcY);
const destIdx = (y * w + x) * 4; // Index in destination buffer
destData[destIdx] = C[0]; // R
destData[destIdx + 1] = C[1]; // G
destData[destIdx + 2] = C[2]; // B
destData[destIdx + 3] = C[3]; // A
}
}
ctx.putImageData(destImageData, 0, 0); // Apply the modified pixels to the canvas
break;
}
default:
console.warn(`Unknown filterType: "${filterType}". No filter applied.`);
// Original image is already on the canvas as per the initial drawImage.
break;
}
return canvas;
}
Apply Changes