You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, overlayImgUrl = "https://picsum.photos/seed/doubleexposure/800/600", blendMode = "overlay", overlayOpacity = 0.5) {
// Helper function to ensure an image is loaded
const _ensureImageLoaded = function(imageObject, imageDescription) {
return new Promise((resolve, reject) => {
if (!imageObject.src && !(imageObject instanceof HTMLImageElement && imageObject.currentSrc)) { // check if src or currentSrc (for img tags in DOM) is missing
reject(new Error(`${imageDescription} has no src attribute set.`));
return;
}
if (imageObject.complete && imageObject.naturalWidth !== 0) {
resolve();
return;
}
if (imageObject.complete && imageObject.naturalWidth === 0) {
// This can happen if src is invalid, image is broken, or for an img tag that had an error but complete is true
reject(new Error(`${imageDescription} is broken, not a valid image, or failed to load. URL: ${imageObject.src || imageObject.currentSrc}`));
return;
}
let loadListener, errorListener;
const cleanup = () => {
imageObject.removeEventListener('load', loadListener);
imageObject.removeEventListener('error', errorListener);
};
loadListener = () => {
cleanup();
if (imageObject.naturalWidth === 0) {
reject(new Error(`${imageDescription} loaded but has zero width/height (likely an error or empty image). URL: ${imageObject.src || imageObject.currentSrc}`));
} else {
resolve();
}
};
errorListener = (err) => {
cleanup();
reject(new Error(`${imageDescription} failed to load. URL: ${imageObject.src || imageObject.currentSrc}. Error: ${err}`));
};
imageObject.addEventListener('load', loadListener);
imageObject.addEventListener('error', errorListener);
});
};
// 1. Parameter validation
let numOpacity = Number(overlayOpacity);
if (isNaN(numOpacity)) {
console.warn(`Invalid overlayOpacity: "${overlayOpacity}". Using default 0.5.`);
overlayOpacity = 0.5;
} else if (numOpacity < 0 || numOpacity > 1) {
console.warn(`overlayOpacity ${numOpacity} out of range [0,1]. Clamping value.`);
overlayOpacity = Math.max(0, Math.min(1, numOpacity));
}
// Ensure overlayOpacity is the number after validation
overlayOpacity = Number(overlayOpacity);
blendMode = String(blendMode).toLowerCase();
overlayImgUrl = String(overlayImgUrl);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 2. Load originalImg
try {
await _ensureImageLoaded(originalImg, "Original image");
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
} catch (err) {
console.error(err.message);
// Fallback canvas for original image load error
canvas.width = originalImg.width || 300; // Use HTML attribute width or default
canvas.height = originalImg.height || 150; // Use HTML attribute height or default
ctx.fillStyle = 'lightgray';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = "16px Arial";
ctx.fillStyle = "black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Original image error.", canvas.width / 2, canvas.height / 2);
return canvas;
}
// 3. Load overlay image
if (!overlayImgUrl || overlayImgUrl.trim() === "") {
console.warn("Overlay image URL is empty. Proceeding without overlay.");
return canvas; // Return canvas with only original image
}
const overlayImage = new Image();
if (!overlayImgUrl.startsWith('data:')) {
overlayImage.crossOrigin = "Anonymous";
}
overlayImage.src = overlayImgUrl;
try {
await _ensureImageLoaded(overlayImage, "Overlay image");
} catch (err) {
console.warn(err.message + " Proceeding without overlay.");
return canvas; // Return canvas with only original image
}
if (overlayImage.naturalWidth === 0 || overlayImage.naturalHeight === 0) {
console.warn("Overlay image loaded but has zero dimensions. Proceeding without overlay.");
return canvas;
}
// 4. Apply blend and draw overlay
ctx.globalAlpha = overlayOpacity;
const validBlendModes = [
"source-over", "source-in", "source-out", "source-atop",
"destination-over", "destination-in", "destination-out", "destination-atop",
"lighter", "copy", "xor", "multiply", "screen", "overlay", "darken",
"lighten", "color-dodge", "color-burn", "hard-light", "soft-light",
"difference", "exclusion", "hue", "saturation", "color", "luminosity"
];
if (validBlendModes.includes(blendMode)) {
ctx.globalCompositeOperation = blendMode;
} else {
console.warn(`Invalid blendMode: "${blendMode}". Using default 'overlay'.`);
ctx.globalCompositeOperation = 'overlay';
}
// Calculate "cover" dimensions for overlay image
// This will make the overlay image cover the canvas, preserving aspect ratio, cropping if necessary.
const targetWidth = canvas.width;
const targetHeight = canvas.height;
const sourceWidth = overlayImage.naturalWidth;
const sourceHeight = overlayImage.naturalHeight;
const targetAspectRatio = targetWidth / targetHeight;
const sourceAspectRatio = sourceWidth / sourceHeight;
let sWidth, sHeight, sx, sy;
if (sourceAspectRatio > targetAspectRatio) { // Overlay is wider than canvas aspect ratio (needs horizontal crop)
sHeight = sourceHeight;
sWidth = sourceHeight * targetAspectRatio;
sx = (sourceWidth - sWidth) / 2;
sy = 0;
} else { // Overlay is taller or same aspect ratio (needs vertical crop or no crop)
sWidth = sourceWidth;
sHeight = sourceWidth / targetAspectRatio;
sx = 0;
sy = (sourceHeight - sHeight) / 2;
}
ctx.drawImage(overlayImage, sx, sy, sWidth, sHeight, 0, 0, targetWidth, targetHeight);
// Reset canvas context properties to defaults
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes