You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, mashupStyle = "checkerboard", mashupComplexity = 8, secondaryImageUrl = "", blendMode = "source-over") {
const width = originalImg.width;
const height = originalImg.height;
// Main canvas
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
// Load secondary image for the mashup or generate a unique variation of the original
let img2 = null;
if (typeof secondaryImageUrl === "string" && secondaryImageUrl.trim() !== "") {
img2 = new Image();
img2.crossOrigin = "Anonymous";
const loaded = await new Promise((resolve) => {
img2.onload = () => resolve(true);
img2.onerror = () => resolve(false);
img2.src = secondaryImageUrl.trim();
});
if (!loaded || !img2.width || !img2.height) {
img2 = null; // Fallback entirely if the image failed to load
}
}
if (!img2) {
// Fallback: Create a cool procedural crossover candidate
// A flipped, hue-rotated, and saturated version of the original image
const tCanv = document.createElement("canvas");
tCanv.width = width;
tCanv.height = height;
const tCanvCtx = tCanv.getContext("2d");
tCanvCtx.translate(width, 0);
tCanvCtx.scale(-1, 1); // Flip horizontally
tCanvCtx.filter = "hue-rotate(180deg) saturate(200%)";
tCanvCtx.drawImage(originalImg, 0, 0, width, height);
img2 = tCanv;
}
// Draw the base layer (original image)
ctx.drawImage(originalImg, 0, 0, width, height);
// Prepare temp layer for secondary image to ensure it strictly respects the canvas dimensions
const tempLayer = document.createElement("canvas");
tempLayer.width = width;
tempLayer.height = height;
const tempCtx = tempLayer.getContext("2d");
tempCtx.drawImage(img2, 0, 0, width, height);
// Filter valid blending composite modes
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"
];
const targetBlendMode = validBlendModes.includes(blendMode) ? blendMode : "source-over";
ctx.globalCompositeOperation = targetBlendMode;
// Parse options
const mStyle = (typeof mashupStyle === "string" ? mashupStyle : "checkerboard").toLowerCase();
let subs = parseInt(mashupComplexity);
if (isNaN(subs) || subs < 2) subs = 8;
if (subs > 100) subs = 100; // reasonable complexity cap
// Helper to draw clipped regions of the second image to create the mashup
const applyClip = (clipFn) => {
ctx.save();
ctx.beginPath();
clipFn();
ctx.clip();
ctx.drawImage(tempLayer, 0, 0, width, height);
ctx.restore();
};
// Apply selected mashup style
if (mStyle === "double-exposure" || mStyle === "blend") {
if (targetBlendMode === "source-over") {
ctx.globalCompositeOperation = "screen"; // default to screen for natural double-exposure
}
ctx.drawImage(tempLayer, 0, 0, width, height);
} else if (mStyle === "horizontal-stripes") {
applyClip(() => {
const cellH = height / subs;
for (let y = 0; y < subs; y++) {
if (y % 2 === 1) ctx.rect(0, y * cellH, width, cellH);
}
});
} else if (mStyle === "vertical-stripes") {
applyClip(() => {
const cellW = width / subs;
for (let x = 0; x < subs; x++) {
if (x % 2 === 1) ctx.rect(x * cellW, 0, cellW, height);
}
});
} else if (mStyle === "diagonal-split") {
applyClip(() => {
ctx.moveTo(0, height);
ctx.lineTo(width, 0);
ctx.lineTo(width, height);
ctx.closePath();
});
} else if (mStyle === "triangles") {
applyClip(() => {
const cellW = width / subs;
const cellH = height / subs;
for (let y = 0; y < subs; y++) {
for (let x = 0; x < subs; x++) {
if ((x + y) % 2 === 0) {
ctx.moveTo(x * cellW, y * cellH);
ctx.lineTo((x + 1) * cellW, y * cellH);
ctx.lineTo(x * cellW, (y + 1) * cellH);
} else {
ctx.moveTo((x + 1) * cellW, (y + 1) * cellH);
ctx.lineTo((x + 1) * cellW, y * cellH);
ctx.lineTo(x * cellW, (y + 1) * cellH);
}
ctx.closePath();
}
}
});
} else if (mStyle === "circle-center") {
applyClip(() => {
ctx.arc(width / 2, height / 2, Math.min(width, height) * 0.4, 0, Math.PI * 2);
});
} else if (mStyle === "glitch-slices") {
applyClip(() => {
// Apply randomized horizontal sliced intersections
for (let i = 0; i < subs * 2; i++) {
const y = (height / (subs * 2)) * i + (Math.random() * (height / subs) - (height / (subs * 2)));
const sliceH = Math.random() * (height / subs);
ctx.rect(0, y, width, sliceH);
}
});
} else {
// Fallback or "checkerboard" default
applyClip(() => {
const cellW = width / subs;
const cellH = height / subs;
for (let y = 0; y < subs; y++) {
for (let x = 0; x < subs; x++) {
// Check for odd intersected blocks to create pattern
if ((x + y) % 2 === 1) {
ctx.rect(x * cellW, y * cellH, cellW, cellH);
}
}
}
});
}
ctx.globalCompositeOperation = "source-over"; // Cleanup
return canvas;
}
Apply Changes