You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, swirlStrength = 0.3, caramelIntensity = 0.5, swirlCenterX = 0.5, swirlCenterY = 0.5) {
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
// Basic check if image has dimensions. If not, it's not loaded or invalid.
if (width === 0 || height === 0) {
console.error("Image has zero dimensions or is not loaded properly.");
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 100;
errorCanvas.height = 100; // Small canvas for error message
const errorCtx = errorCanvas.getContext('2d');
errorCtx.fillStyle = "lightgray";
errorCtx.fillRect(0, 0, 100, 100);
errorCtx.fillStyle = "red";
errorCtx.textAlign = "center";
errorCtx.textBaseline = "middle";
errorCtx.font = "12px Arial";
errorCtx.fillText("Invalid Image", 50, 50);
return errorCanvas;
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
// Add { willReadFrequently: true } for potential performance optimization if supported
const ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.drawImage(originalImg, 0, 0, width, height);
let sourceImageData;
try {
sourceImageData = ctx.getImageData(0, 0, width, height);
} catch (e) {
// Handle cases like cross-origin image data access restrictions
console.error("Error getting image data (cross-origin issue?):", e);
// Return the canvas with the original image drawn and an error message
ctx.fillStyle = "rgba(255, 0, 0, 0.6)"; // Semi-transparent red overlay for message
ctx.fillRect(0, 0, width, Math.max(40, height / 8)); // Red bar at top
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const fontSize = Math.min(20, height / 10, width / 15);
ctx.font = `bold ${fontSize}px Arial`;
ctx.fillText("Error: Cannot process pixels", width / 2, Math.max(20, height / 16));
return canvas;
}
const outputImageData = ctx.createImageData(width, height);
const sourceDataArray = sourceImageData.data;
const outputDataArray = outputImageData.data;
const actualCenterX = swirlCenterX * width;
const actualCenterY = swirlCenterY * height;
// referenceRadius helps scale the swirl; using min of width/height ensures it's relative to image size
const referenceRadius = Math.max(1, Math.min(width, height) / 2);
// Helper function to get pixel data with boundary clamp from a data array
function getColorAt(x, y, dataArr, imgW, imgH) {
const clampedX = Math.max(0, Math.min(imgW - 1, Math.floor(x)));
const clampedY = Math.max(0, Math.min(imgH - 1, Math.floor(y)));
const idx = (clampedY * imgW + clampedX) * 4;
return {
r: dataArr[idx],
g: dataArr[idx+1],
b: dataArr[idx+2],
a: dataArr[idx+3]
};
}
// Helper function for bilinear interpolation using a data array
function getInterpolatedColorAt(x, y, dataArr, imgW, imgH) {
const x_floor = Math.floor(x);
const y_floor = Math.floor(y);
const u = x - x_floor; // Fractional part for x
const v = y - y_floor; // Fractional part for y
const p00 = getColorAt(x_floor, y_floor, dataArr, imgW, imgH);
const p10 = getColorAt(x_floor + 1, y_floor, dataArr, imgW, imgH);
const p01 = getColorAt(x_floor, y_floor + 1, dataArr, imgW, imgH);
const p11 = getColorAt(x_floor + 1, y_floor + 1, dataArr, imgW, imgH);
// Interpolate R, G, B, A channels
const r = p00.r * (1 - u) * (1 - v) + p10.r * u * (1 - v) + p01.r * (1 - u) * v + p11.r * u * v;
const g = p00.g * (1 - u) * (1 - v) + p10.g * u * (1 - v) + p01.g * (1 - u) * v + p11.g * u * v;
const b = p00.b * (1 - u) * (1 - v) + p10.b * u * (1 - v) + p01.b * (1 - u) * v + p11.b * u * v;
const a = p00.a * (1 - u) * (1 - v) + p10.a * u * (1 - v) + p01.a * (1 - u) * v + p11.a * u * v;
return { r, g, b, a };
}
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const dx = x - actualCenterX;
const dy = y - actualCenterY;
const distance = Math.sqrt(dx*dx + dy*dy);
const originalAngle = Math.atan2(dy, dx); // angle in radians
let swirledAngle = originalAngle;
// Only apply swirl if strength is non-zero and distance is >0 (to avoid issues at center)
if (swirlStrength !== 0 && distance > 0) {
// swirlStrength defines radians of twist at referenceRadius.
// Angle offset is proportional to (distance / referenceRadius).
swirledAngle = originalAngle + swirlStrength * (distance / referenceRadius);
}
const sourceX = actualCenterX + distance * Math.cos(swirledAngle);
const sourceY = actualCenterY + distance * Math.sin(swirledAngle);
const { r: r_swirled, g: g_swirled, b: b_swirled, a: a_swirled } =
getInterpolatedColorAt(sourceX, sourceY, sourceDataArray, width, height);
let r_final = r_swirled;
let g_final = g_swirled;
let b_final = b_swirled;
// Apply Caramel Color Effect if intensity is > 0
if (caramelIntensity > 0) {
// Increase red and green, decrease blue for a warm, caramel tone.
// The caramelIntensity (0 to 1) scales the effect.
r_final += 60 * caramelIntensity;
g_final += 30 * caramelIntensity;
b_final -= 20 * caramelIntensity;
}
const dest_idx = (y * width + x) * 4;
outputDataArray[dest_idx] = Math.max(0, Math.min(255, r_final));
outputDataArray[dest_idx + 1] = Math.max(0, Math.min(255, g_final));
outputDataArray[dest_idx + 2] = Math.max(0, Math.min(255, b_final));
outputDataArray[dest_idx + 3] = Math.max(0, Math.min(255, a_swirled)); // Preserve alpha
}
}
ctx.putImageData(outputImageData, 0, 0); // Draw the processed image data back to the
return canvas; // canvas and return the canvas.
}
Apply Changes