You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
tattooPatternUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGDSURBVHhe7Zo9TsQwDIX9JsYIPMAGxgoE3sIo3MAC2ABjzAGgqihgId5hcfxZkmxpNq1UtJ1e1Xf69f0kTbJ8+y7N8u13L8k3bXv+vDMPPMAABjCAAQxgAAN40wP277d902+P/jI9nzY83zYy7xP4wAMMYAADOAABPJfAmL99eNyPbP5RSLn7zC65gAEMYADDLwEv/wz8c5w7f7b1X4Fz53f84QUMYAADGMAABvCEB6wFLyKkAEsgLgL2B0y4gAEMYADDE7C0X4HzJzAvYHiBDyxhAQMYwAAuSoCF3wPMf7A0fuAEvkAb2wUuYMAABjCAgQxgAP8gMN8D3gTOBwyuC5zYewc+sIQFDGAAAzgsAS39PnP+R8r9c5zzL6v/Apc3MN/yC5x7A4NNsIYFDGAAAxg4gJtpXwC8r9GfNOkABjCAAQxgAAP4cwT8B2Z81HlgAQMYwAAEjpvAXP/T+w9kZpgLNkYAAAAASUVORK5CYII=", // Default simple pattern
opacity = 0.7,
blendMode = "multiply",
imageGrayscaleFactor = 0.3 // Default: slight desaturation for better tattoo visibility
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const originalWidth = originalImg.naturalWidth || originalImg.width;
const originalHeight = originalImg.naturalHeight || originalImg.height;
if (originalWidth === 0 || originalHeight === 0) {
console.error("Original image has zero width or height. Cannot process.");
// Return a minimal canvas to avoid further errors downstream
canvas.width = 1;
canvas.height = 1;
return canvas;
}
canvas.width = originalWidth;
canvas.height = originalHeight;
// 1. Draw original image onto the canvas
ctx.drawImage(originalImg, 0, 0, originalWidth, originalHeight);
// 2. Apply grayscale effect to the image on canvas if specified
// Clamp effectiveGrayscaleFactor between 0 and 1
const effectiveGrayscaleFactor = Math.max(0, Math.min(1, imageGrayscaleFactor));
if (effectiveGrayscaleFactor > 0) {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Standard luminance calculation for grayscale
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
// Interpolate between original color and grayscale based on the factor
data[i] = r * (1 - effectiveGrayscaleFactor) + gray * effectiveGrayscaleFactor;
data[i + 1] = g * (1 - effectiveGrayscaleFactor) + gray * effectiveGrayscaleFactor;
data[i + 2] = b * (1 - effectiveGrayscaleFactor) + gray * effectiveGrayscaleFactor;
// Alpha channel (data[i+3]) remains unchanged
}
ctx.putImageData(imageData, 0, 0);
}
// 3. Load and overlay the tattoo pattern image
if (tattooPatternUrl && typeof tattooPatternUrl === 'string' && tattooPatternUrl.trim() !== "") {
try {
const tattooImg = await new Promise((resolve, reject) => {
const img = new Image();
// For loading images from other domains (requires CORS headers on the server)
// For data URLs or same-origin images, this has no detrimental effect.
img.crossOrigin = "Anonymous";
img.onload = () => resolve(img);
img.onerror = () => {
// errEvent is an Event object, not an Error object directly.
// For network errors on img.src, details might be sparse due to security.
reject(new Error(`Failed to load tattoo pattern image from URL: ${tattooPatternUrl}`));
};
img.src = tattooPatternUrl;
});
if (tattooImg.naturalWidth === 0 || tattooImg.naturalHeight === 0) {
console.warn("Tattoo pattern image loaded but has zero dimensions. Skipping overlay.");
} else {
// Set blending properties for the tattoo overlay
// Clamp opacity between 0 and 1
const finalOpacity = Math.max(0, Math.min(1, opacity));
ctx.globalAlpha = finalOpacity;
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.toLowerCase())) {
ctx.globalCompositeOperation = blendMode.toLowerCase();
} else {
console.warn(`Invalid blend mode: '${blendMode}'. Falling back to 'multiply'.`);
ctx.globalCompositeOperation = 'multiply';
}
// Draw the tattoo pattern image, stretched to fit the canvas dimensions
ctx.drawImage(tattooImg, 0, 0, canvas.width, canvas.height);
// Reset blending properties to defaults for any subsequent drawing (good practice)
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'source-over';
}
} catch (error) {
// This catch block will handle errors from the Promise (e.g., network error, CORS issue)
console.error("Error processing tattoo pattern:", error.message);
// The canvas will still contain the (potentially grayscaled) original image.
}
} else {
if (tattooPatternUrl !== null && tattooPatternUrl !== undefined && (typeof tattooPatternUrl !== 'string' || tattooPatternUrl.trim() === "")) {
console.warn("Tattoo pattern URL is invalid or empty. Skipping tattoo overlay.");
}
// If tattooPatternUrl is null or undefined, it's also skipped by the initial if condition.
}
return canvas;
}
Apply Changes