You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, eyeRadiusRatio = 0.3, swirlMaxAngle = 0.5, outerFadeColorStr = "30,30,40", outerFadeStartRatio = 0.6) {
// Helper to parse "r,g,b" color string into an object {r, g, b}
function parseColor(colorStr) {
const parts = colorStr.split(',').map(s => parseInt(s.trim(), 10));
return {
r: Number.isFinite(parts[0]) ? parts[0] : 0,
g: Number.isFinite(parts[1]) ? parts[1] : 0,
b: Number.isFinite(parts[2]) ? parts[2] : 0
};
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Prefer natural dimensions of the image, fallback to width/height
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
canvas.width = width;
canvas.height = height;
// If image dimensions are invalid, return an empty (but correctly sized) canvas
if (width === 0 || height === 0) {
return canvas;
}
// Draw the original image to the canvas
try {
ctx.drawImage(originalImg, 0, 0, width, height);
} catch (e) {
// Handle error if the image cannot be drawn (e.g., invalid image source)
console.error("Error drawing image to canvas: " + e.message);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, width, height);
ctx.font = "16px Arial";
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Error: Could not draw the input image.", width / 2, height / 2);
return canvas;
}
let originalImageData;
try {
// Attempt to get pixel data; this can fail for cross-origin images without CORS
originalImageData = ctx.getImageData(0, 0, width, height);
} catch (e) {
console.error("Error getting ImageData (cross-origin issues?): " + e.message);
// Display an error message on the canvas regarding cross-origin restrictions
ctx.fillStyle = "black";
ctx.fillRect(0, 0, width, height);
ctx.font = "14px Arial"; // Slightly smaller font for potentially longer messages
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const lineHeight = 18;
ctx.fillText("Error: Cannot process image pixels.", width / 2, height / 2 - lineHeight);
ctx.fillText("Image might be from a different origin", width / 2, height / 2);
ctx.fillText("and lack appropriate CORS headers.", width / 2, height / 2 + lineHeight);
return canvas;
}
const processedImageData = ctx.createImageData(width, height);
const data = originalImageData.data; // Source pixel data
const processedData = processedImageData.data; // Target pixel data
const centerX = width / 2;
const centerY = height / 2;
// Reference radius for effects: radius of the largest circle inscribed in the image
const effectReferenceRadius = Math.min(centerX, centerY);
// Calculate actual radius of the clear "eye"
const actualEyeRadius = effectReferenceRadius * Math.max(0, eyeRadiusRatio);
// Ensure outerFadeStartRatio is at least eyeRadiusRatio
const validatedOuterFadeStartRatio = Math.max(outerFadeStartRatio, eyeRadiusRatio);
let actualOuterFadeStartRadius = effectReferenceRadius * validatedOuterFadeStartRatio;
// Further ensure that the pixel radius for starting fade is not less than the eye radius
actualOuterFadeStartRadius = Math.max(actualOuterFadeStartRadius, actualEyeRadius);
const parsedFadeColor = parseColor(outerFadeColorStr);
// Iterate over each pixel
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const dx = x - centerX; // Distance from center X
const dy = y - centerY; // Distance from center Y
const dist = Math.sqrt(dx * dx + dy * dy); // Pixel's distance from center
const currentPixelIndex = (y * width + x) * 4; // Index for R, G, B, A values
if (dist <= actualEyeRadius) {
// Inside the "eye": copy original pixel
processedData[currentPixelIndex] = data[currentPixelIndex];
processedData[currentPixelIndex + 1] = data[currentPixelIndex + 1];
processedData[currentPixelIndex + 2] = data[currentPixelIndex + 2];
processedData[currentPixelIndex + 3] = data[currentPixelIndex + 3];
} else {
// Outside the "eye": apply swirl and fade effects
const angle = Math.atan2(dy, dx); // Angle of the pixel relative to center
let swirlZoneProgress;
const swirlEffectRange = effectReferenceRadius - actualEyeRadius;
if (swirlEffectRange <= 0) { // Eye is at or beyond reference radius
swirlZoneProgress = (dist > actualEyeRadius) ? 1 : 0; // Max swirl if truly outside eye
} else {
swirlZoneProgress = (dist - actualEyeRadius) / swirlEffectRange;
}
swirlZoneProgress = Math.min(1, Math.max(0, swirlZoneProgress)); // Clamp progress to [0,1]
const angleOffset = swirlMaxAngle * swirlZoneProgress; // Calculate swirl angle offset
// Calculate source pixel coordinates after applying swirl
let sampleX = Math.round(centerX + dist * Math.cos(angle - angleOffset));
let sampleY = Math.round(centerY + dist * Math.sin(angle - angleOffset));
// Clamp sample coordinates to be within image bounds
sampleX = Math.max(0, Math.min(width - 1, sampleX));
sampleY = Math.max(0, Math.min(height - 1, sampleY));
const sourcePixelIndex = (sampleY * width + sampleX) * 4;
// Get color from swirled position
let r = data[sourcePixelIndex];
let g = data[sourcePixelIndex + 1];
let b = data[sourcePixelIndex + 2];
let a = data[sourcePixelIndex + 3];
// Apply fade effect if pixel is beyond the fade start radius
if (dist > actualOuterFadeStartRadius) {
let fadeZoneProgress;
const fadeEffectRange = effectReferenceRadius - actualOuterFadeStartRadius;
if (fadeEffectRange <= 0) { // Fade starts at or beyond reference radius
fadeZoneProgress = (dist > actualOuterFadeStartRadius) ? 1 : 0; // Max fade if truly outside start
} else {
fadeZoneProgress = (dist - actualOuterFadeStartRadius) / fadeEffectRange;
}
fadeZoneProgress = Math.min(1, Math.max(0, fadeZoneProgress)); // Clamp progress to [0,1]
// Interpolate towards fade color
r = Math.round(r * (1 - fadeZoneProgress) + parsedFadeColor.r * fadeZoneProgress);
g = Math.round(g * (1 - fadeZoneProgress) + parsedFadeColor.g * fadeZoneProgress);
b = Math.round(b * (1 - fadeZoneProgress) + parsedFadeColor.b * fadeZoneProgress);
// Optionally, fade alpha:
// a = Math.round(a * (1 - fadeZoneProgress) + 255 * fadeZoneProgress); // Fade to opaque
}
// Set the processed pixel data
processedData[currentPixelIndex] = r;
processedData[currentPixelIndex + 1] = g;
processedData[currentPixelIndex + 2] = b;
processedData[currentPixelIndex + 3] = a;
}
}
}
// Put the processed pixel data back onto the canvas
ctx.putImageData(processedImageData, 0, 0);
return canvas;
}
Apply Changes