You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
textTop = "ATOMIC",
textBottom = "BLONDE",
contrastFactor = 1.8,
shiftPercent = 0.015,
neonColor1 = "#ff0066",
neonColor2 = "#00ffff",
shadowColor = "#050014"
) {
// Helper to flexibly parse any valid CSS color to an RGB array
const colorToRgb = (color) => {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = 1;
tempCanvas.height = 1;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
tempCtx.fillStyle = color;
tempCtx.fillRect(0, 0, 1, 1);
const d = tempCtx.getImageData(0, 0, 1, 1).data;
return [d[0], d[1], d[2]];
};
const c1 = colorToRgb(neonColor1);
const c2 = colorToRgb(neonColor2);
const shadow = colorToRgb(shadowColor);
const contrast = parseFloat(contrastFactor);
const shift = parseFloat(shiftPercent);
const canvas = document.createElement('canvas');
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImg, 0, 0);
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
const srcData = new Uint8ClampedArray(data); // Create a clone for offset reading
const offset = Math.max(1, Math.floor(width * shift));
// 1. Process Background: High Contrast, Aberration, and Gradient Duotone
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// Apply Chromatic Aberration by offsetting the RED and BLUE channels
let rX = Math.max(0, x - offset);
let bX = Math.min(width - 1, x + offset);
let rIdx = (y * width + rX) * 4;
let gIdx = (y * width + x) * 4;
let bIdx = (y * width + bX) * 4;
let r = srcData[rIdx];
let g = srcData[gIdx + 1];
let b = srcData[bIdx + 2];
// Grayscale luminance
let lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
// Apply Contrast filter
lum = ((lum / 255 - 0.5) * contrast + 0.5) * 255;
lum = Math.max(0, Math.min(255, lum));
// Diagonal gradient for dual lighting effect
let blend = (x / width + (height - y) / height) / 2;
// Interpolate mid-tone color based on position
let midR = c1[0] * (1 - blend) + c2[0] * blend;
let midG = c1[1] * (1 - blend) + c2[1] * blend;
let midB = c1[2] * (1 - blend) + c2[2] * blend;
let outR, outG, outB;
// Map luminance to: shadowColor -> midToneColor -> White
if (lum < 128) {
let t = lum / 128;
outR = shadow[0] * (1 - t) + midR * t;
outG = shadow[1] * (1 - t) + midG * t;
outB = shadow[2] * (1 - t) + midB * t;
} else {
let t = (lum - 128) / 127;
outR = midR * (1 - t) + 255 * t;
outG = midG * (1 - t) + 255 * t;
outB = midB * (1 - t) + 255 * t;
}
let outIdx = (y * width + x) * 4;
data[outIdx] = outR;
data[outIdx + 1] = outG;
data[outIdx + 2] = outB;
data[outIdx + 3] = 255; // Alpha
}
}
ctx.putImageData(imgData, 0, 0);
// 2. Process Atomic Blonde Style Text with stencil slices
if ((textTop && textTop.trim() !== "") || (textBottom && textBottom.trim() !== "")) {
// Use an offscreen canvas to allow for destructive slicing logic (stencil visual)
const txtCanvas = document.createElement('canvas');
txtCanvas.width = width;
txtCanvas.height = height;
const txtCtx = txtCanvas.getContext('2d');
const fontSize = Math.max(20, Math.floor(width / 6));
txtCtx.font = `italic 900 ${fontSize}px Impact, "Arial Black", sans-serif`;
txtCtx.textAlign = 'center';
txtCtx.textBaseline = 'middle';
const drawSlicedLogoText = (text, y) => {
const txtShift = Math.max(2, Math.floor(width * 0.006));
// Cyan shift (bottom right)
txtCtx.fillStyle = `rgba(${c2[0]}, ${c2[1]}, ${c2[2]}, 0.9)`;
txtCtx.fillText(text, width / 2 + txtShift, y + txtShift);
// Pink shift (top left)
txtCtx.fillStyle = `rgba(${c1[0]}, ${c1[1]}, ${c1[2]}, 0.9)`;
txtCtx.fillText(text, width / 2 - txtShift, y - txtShift);
// White core
txtCtx.fillStyle = '#ffffff';
txtCtx.fillText(text, width / 2, y);
// Stencil clear slicing (slices through the text block)
txtCtx.globalCompositeOperation = 'destination-out';
const sliceHeight = Math.max(1, Math.floor(fontSize * 0.04));
// Random-ish tactical cuts usually seen in this aesthetic
txtCtx.fillRect(0, y - fontSize * 0.15, width, sliceHeight);
txtCtx.fillRect(0, y + fontSize * 0.25, width, sliceHeight);
// Reset composite mode
txtCtx.globalCompositeOperation = 'source-over';
};
// Draw top and bottom words
if (textTop && textBottom) {
drawSlicedLogoText(textTop, height / 2 - fontSize * 0.55);
drawSlicedLogoText(textBottom, height / 2 + fontSize * 0.55);
} else if (textTop) {
drawSlicedLogoText(textTop, height / 2);
} else if (textBottom) {
drawSlicedLogoText(textBottom, height / 2);
}
// Draw the composed text overlay onto the main image
ctx.drawImage(txtCanvas, 0, 0);
}
return canvas;
}
Apply Changes