You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, gradientDirection = "top-to-bottom", density = 0.5, transitionStart = 0, transitionEnd = 50, filterColor = "black") {
// Parameter sanitization: Convert to number and clamp/validate
let numDensity = parseFloat(density);
if (isNaN(numDensity)) numDensity = 0.5; // Default if parsing fails
numDensity = Math.max(0, Math.min(1, numDensity));
let numTransitionStart = parseFloat(transitionStart);
if (isNaN(numTransitionStart)) numTransitionStart = 0;
numTransitionStart = Math.max(0, Math.min(100, numTransitionStart));
let numTransitionEnd = parseFloat(transitionEnd);
if (isNaN(numTransitionEnd)) numTransitionEnd = 50;
numTransitionEnd = Math.max(0, Math.min(100, numTransitionEnd));
if (numTransitionStart > numTransitionEnd) {
// If start is after end, swap them to maintain logical order
[numTransitionStart, numTransitionEnd] = [numTransitionEnd, numTransitionStart];
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Ensure the image is loaded before trying to use its dimensions or draw it
// Check if originalImg.src is truthy (not empty string, null, or undefined) AND image is not complete
if (originalImg.src && !originalImg.complete) {
try {
await new Promise((resolve, reject) => {
originalImg.onload = resolve;
originalImg.onerror = (err) => {
// err might be an Event object or string depending on browser/context
let message = "Image failed to load.";
if (typeof err === 'string') message += ` Error: ${err}`;
else if (err && err.message) message += ` Error: ${err.message}`;
else if (originalImg.src) message += ` SRC: ${originalImg.src}`;
reject(new Error(message));
};
// Double check if src is truly set, as an empty string might pass the initial check
if (!originalImg.src) { // This condition might be redundant if originalImg.src check above is strict
reject(new Error("Image has no src attribute."));
}
});
} catch (error) {
console.error("Error loading image in processImage:", error);
// Fallback: return an error-indicating canvas
canvas.width = 200; canvas.height = 100; // Default size
ctx.fillStyle = "lightgray"; ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red"; ctx.font = "12px Arial"; ctx.textAlign = "center";
ctx.fillText("Error loading image.", canvas.width / 2, canvas.height / 2 - 10);
ctx.fillText(error.message.substring(0,30) + (error.message.length > 30 ? "..." : ""), canvas.width/2, canvas.height/2 + 10);
return canvas;
}
} else if (!originalImg.src && !(originalImg.naturalWidth || originalImg.width) && !(originalImg.height || originalImg.naturalHeight)) {
// Handles case of `new Image()` passed without `src` set and no dimensions
console.warn("processImage: Input image has no src and no dimensions. Returning small empty canvas.");
canvas.width = 1; canvas.height = 1;
return canvas;
}
const w = originalImg.naturalWidth || originalImg.width;
const h = originalImg.naturalHeight || originalImg.height;
if (w === 0 || h === 0) {
console.warn("processImage: Image dimensions are zero. Returning small empty canvas.");
canvas.width = 1; canvas.height = 1;
return canvas;
}
canvas.width = w;
canvas.height = h;
ctx.drawImage(originalImg, 0, 0, w, h);
// Parse filterColor to r, g, b components
let r_color = 0, g_color = 0, b_color = 0; // Default to black
const tempElem = document.createElement('div');
tempElem.style.display = 'none';
tempElem.style.color = filterColor;
document.body.appendChild(tempElem);
const computedColor = window.getComputedStyle(tempElem).color;
document.body.removeChild(tempElem);
const colorMatch = computedColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
if (colorMatch) {
r_color = parseInt(colorMatch[1]);
g_color = parseInt(colorMatch[2]);
b_color = parseInt(colorMatch[3]);
} else {
console.warn(`Could not parse filterColor "${filterColor}". Defaulting to black.`);
}
const colorStartStr = `rgba(${r_color},${g_color},${b_color},${numDensity})`;
const colorEndStr = `rgba(${r_color},${g_color},${b_color},0)`;
const stop1Pos = numTransitionStart / 100;
const stop2Pos = numTransitionEnd / 100;
let grad;
let x0_grad, y0_grad, x1_grad, y1_grad;
switch (String(gradientDirection).toLowerCase()) {
case "bottom-to-top":
x0_grad = 0; y0_grad = h; x1_grad = 0; y1_grad = 0;
break;
case "left-to-right":
x0_grad = 0; y0_grad = 0; x1_grad = w; y1_grad = 0;
break;
case "right-to-left":
x0_grad = w; y0_grad = 0; x1_grad = 0; y1_grad = 0;
break;
case "top-to-bottom":
default:
x0_grad = 0; y0_grad = 0; x1_grad = 0; y1_grad = h;
if (String(gradientDirection).toLowerCase() !== "top-to-bottom") {
console.warn(`Invalid gradientDirection "${gradientDirection}". Defaulting to "top-to-bottom".`);
}
break;
}
grad = ctx.createLinearGradient(x0_grad, y0_grad, x1_grad, y1_grad);
if (stop1Pos === stop2Pos) { // Hard edge or solid fill
if (stop1Pos <= 0) { // Entirely transparent (transition starts and ends at the beginning)
grad.addColorStop(0, colorEndStr);
} else if (stop1Pos >= 1) { // Entirely dense (transition starts and ends at the end)
grad.addColorStop(1, colorStartStr);
} else { // Sharp transition somewhere in the middle (0 < stop1Pos < 1)
grad.addColorStop(stop1Pos, colorStartStr);
grad.addColorStop(Math.min(1.0, stop1Pos + 0.000001), colorEndStr);
}
} else { // Soft edge (stop1Pos < stop2Pos)
grad.addColorStop(stop1Pos, colorStartStr);
grad.addColorStop(stop2Pos, colorEndStr);
}
ctx.fillStyle = grad;
ctx.fillRect(0, 0, w, h);
return canvas;
}
Apply Changes