You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg,
sharpnessRadiusRatioStr = "0.2",
maxBlurPixelsStr = "10",
swirlIntensityStr = "1.5",
swirlStartRatioStr = "0.25",
blurSamplesStr = "9") {
const sharpnessRadiusRatio = parseFloat(sharpnessRadiusRatioStr);
const maxBlurPixels = parseInt(maxBlurPixelsStr, 10);
const swirlIntensity = parseFloat(swirlIntensityStr);
const swirlStartRatio = parseFloat(swirlStartRatioStr);
const numBlurSamples = Math.max(1, parseInt(blurSamplesStr, 10)); // Ensure at least 1 sample
const outputCanvas = document.createElement('canvas');
const ctx = outputCanvas.getContext('2d');
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
outputCanvas.width = imgWidth;
outputCanvas.height = imgHeight;
// Draw the original image to a source canvas to get its ImageData
const sourceCanvas = document.createElement('canvas');
sourceCanvas.width = imgWidth;
sourceCanvas.height = imgHeight;
const sourceCtx = sourceCanvas.getContext('2d', { willReadFrequently: true }); // Optimization hint
sourceCtx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
let sourceImageData;
try {
sourceImageData = sourceCtx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
console.error("Error getting ImageData (possibly due to CORS):", e);
// Fallback: return the original image drawn on the canvas
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
return outputCanvas;
}
const outputImageData = ctx.createImageData(imgWidth, imgHeight);
const centerX = imgWidth / 2;
const centerY = imgHeight / 2;
const halfDiagonal = Math.sqrt(centerX * centerX + centerY * centerY);
if (imgWidth === 0 || imgHeight === 0) { // Handle zero-size images
return outputCanvas; // Return empty canvas
}
// If halfDiagonal is 0 (e.g. 1x1 image), some calculations might fail.
if (halfDiagonal < 1) {
outputImageData.data.set(sourceImageData.data);
ctx.putImageData(outputImageData, 0, 0);
return outputCanvas;
}
const sharpnessPixelRadius = sharpnessRadiusRatio * halfDiagonal;
const swirlStartPixelRadius = swirlStartRatio * halfDiagonal;
// Helper function to get pixel value from ImageData, with boundary clamping.
// ix, iy are integer coordinates.
function getPixelValue(imgData, ix, iy) {
const w = imgData.width;
const h = imgData.height;
const clampedX = Math.max(0, Math.min(ix, w - 1));
const clampedY = Math.max(0, Math.min(iy, h - 1));
const pixelIndex = (clampedY * w + clampedX) * 4;
return [
imgData.data[pixelIndex],
imgData.data[pixelIndex + 1],
imgData.data[pixelIndex + 2],
imgData.data[pixelIndex + 3]
];
}
// Helper function for bilinear interpolation. x, y are float coordinates.
function getPixelBilinear(imgData, x, y) {
const x1 = Math.floor(x);
const y1 = Math.floor(y);
const x2 = x1 + 1;
const y2 = y1 + 1;
const Q11 = getPixelValue(imgData, x1, y1);
const Q21 = getPixelValue(imgData, x2, y1);
const Q12 = getPixelValue(imgData, x1, y2);
const Q22 = getPixelValue(imgData, x2, y2);
const fx = x - x1;
const fy = y - y1;
const fx1 = 1 - fx;
const fy1 = 1 - fy;
const r = Q11[0] * fx1 * fy1 + Q21[0] * fx * fy1 + Q12[0] * fx1 * fy + Q22[0] * fx * fy;
const g = Q11[1] * fx1 * fy1 + Q21[1] * fx * fy1 + Q12[1] * fx1 * fy + Q22[1] * fx * fy;
const b = Q11[2] * fx1 * fy1 + Q21[2] * fx * fy1 + Q12[2] * fx1 * fy + Q22[2] * fx * fy;
const a = Q11[3] * fx1 * fy1 + Q21[3] * fx * fy1 + Q12[3] * fx1 * fy + Q22[3] * fx * fy;
return [r, g, b, a]; // Return raw float values
}
function setPixel(imgData, x, y, r, g, b, a) {
const i = (y * imgData.width + x) * 4;
imgData.data[i] = Math.round(r);
imgData.data[i+1] = Math.round(g);
imgData.data[i+2] = Math.round(b);
imgData.data[i+3] = Math.round(a);
}
const normSwirlDenominator = Math.max(1, halfDiagonal - swirlStartPixelRadius);
const normBlurDenominator = Math.max(1, halfDiagonal - sharpnessPixelRadius);
for (let y = 0; y < imgHeight; y++) {
for (let x = 0; x < imgWidth; x++) {
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
let currentSwirl = 0;
if (distance > swirlStartPixelRadius) {
const normalizedDistForSwirl = Math.max(0, (distance - swirlStartPixelRadius) / normSwirlDenominator);
currentSwirl = swirlIntensity * Math.PI * 0.5 * Math.pow(normalizedDistForSwirl, 2);
}
const swirledAngle = angle + currentSwirl;
let currentBlurRadius = 0;
if (distance > sharpnessPixelRadius) {
const normalizedDistForBlur = Math.max(0, (distance - sharpnessPixelRadius) / normBlurDenominator);
currentBlurRadius = maxBlurPixels * Math.pow(normalizedDistForBlur, 1.5);
}
currentBlurRadius = Math.min(currentBlurRadius, maxBlurPixels);
const srcCentralX = centerX + distance * Math.cos(swirledAngle);
const srcCentralY = centerY + distance * Math.sin(swirledAngle);
if (currentBlurRadius < 0.5 || numBlurSamples_actual <= 1) {
const [r, g, b, a] = getPixelBilinear(sourceImageData, srcCentralX, srcCentralY);
setPixel(outputImageData, x, y, r, g, b, a);
} else {
let r_sum = 0, g_sum = 0, b_sum = 0, a_sum = 0;
const [cr, cg, cb, ca] = getPixelBilinear(sourceImageData, srcCentralX, srcCentralY);
r_sum += cr; g_sum += cg; b_sum += cb; a_sum += ca;
// numBlurSamples includes the central sample.
// So, loop numBlurSamples - 1 times for additional random samples.
const numAdditionalSamples = numBlurSamples -1;
for (let i = 0; i < numAdditionalSamples; i++) {
const randAngle = Math.random() * 2 * Math.PI;
const randRadius = Math.sqrt(Math.random()) * currentBlurRadius;
const offsetX = randRadius * Math.cos(randAngle);
const offsetY = randRadius * Math.sin(randAngle);
const sampleX = srcCentralX + offsetX;
const sampleY = srcCentralY + offsetY;
const [pr, pg, pb, pa] = getPixelBilinear(sourceImageData, sampleX, sampleY);
r_sum += pr; g_sum += pg; b_sum += pb; a_sum += pa;
}
setPixel(outputImageData, x, y,
r_sum / numBlurSamples,
g_sum / numBlurSamples,
b_sum / numBlurSamples,
a_sum / numBlurSamples);
}
}
}
ctx.putImageData(outputImageData, 0, 0);
return outputCanvas;
}
Apply Changes