You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, primaryColor = "#ff00ff", secondaryColor = "#002288", contrastFactor = 1.5, aberrationAmount = 3, vignetteAmount = 0.4, scanlinesOpacity = 0.1) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // willReadFrequently for performance with getImageData/putImageData
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
// Helper function to parse hex color string to an {r, g, b} object
function hexToRgb(hex) {
hex = hex.replace(/^#/, '');
if (hex.length === 3) {
hex = hex.split('').map(char => char + char).join('');
}
const num = parseInt(hex, 16);
if (isNaN(num)) { // Fallback for invalid hex
return { r: 255, g: 20, b: 147 }; // Default to a pink color
}
return { r: (num >> 16) & 255, g: (num >> 8) & 255, b: num & 255 };
}
const pColorRGB = hexToRgb(primaryColor); // Highlight color (e.g., magenta/pink)
const sColorRGB = hexToRgb(secondaryColor); // Shadow color (e.g., dark blue/purple)
// --- Step 1: Draw original image ---
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// --- Step 2: Get image data ---
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Error getting image data (possibly tainted canvas):", e);
// Return original image on a new canvas if processing fails
const errorCanvas = document.createElement('canvas');
errorCanvas.width = canvas.width;
errorCanvas.height = canvas.height;
const errorCtx = errorCanvas.getContext('2d');
errorCtx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
return errorCanvas;
}
let data = imageData.data;
// --- Step 3: Pixel manipulation (Contrast & Color Grading) ---
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// 3.1 Contrast
if (contrastFactor !== 1.0) {
r = (r / 255 - 0.5) * contrastFactor + 0.5;
g = (g / 255 - 0.5) * contrastFactor + 0.5;
b = (b / 255 - 0.5) * contrastFactor + 0.5;
r = Math.max(0, Math.min(1, r)) * 255;
g = Math.max(0, Math.min(1, g)) * 255;
b = Math.max(0, Math.min(1, b)) * 255;
}
// 3.2 Cyberpunk Color Grading
// Calculate luminance (0-1) using current r,g,b after contrast
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
// Mix current color with primary (highlights) and secondary (shadows) colors
const mixStrength = 0.65; // How much of the target colors to mix in
let finalR = r * (1 - mixStrength) + (sColorRGB.r * (1 - luminance) + pColorRGB.r * luminance) * mixStrength;
let finalG = g * (1 - mixStrength) + (sColorRGB.g * (1 - luminance) + pColorRGB.g * luminance) * mixStrength;
let finalB = b * (1 - mixStrength) + (sColorRGB.b * (1 - luminance) + pColorRGB.b * luminance) * mixStrength;
// Additional common cyberpunk adjustments: slightly reduce green, boost blue
finalG *= 0.9;
finalB = Math.min(255, finalB * 1.1);
data[i] = Math.max(0, Math.min(255, finalR));
data[i+1] = Math.max(0, Math.min(255, finalG));
data[i+2] = Math.max(0, Math.min(255, finalB));
}
// --- Step 4: Chromatic Aberration ---
if (aberrationAmount > 0) {
const shiftedData = new Uint8ClampedArray(data.length);
const offset = Math.round(aberrationAmount);
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
const i = (y * canvas.width + x) * 4;
const rX = Math.max(0, Math.min(canvas.width - 1, x - offset));
const bX = Math.max(0, Math.min(canvas.width - 1, x + offset));
const rIndex = (y * canvas.width + rX) * 4;
const gIndex = i;
const bIndex = (y * canvas.width + bX) * 4;
shiftedData[i] = data[rIndex]; // Red channel from left-shifted pixel
shiftedData[i + 1] = data[gIndex + 1]; // Green channel from current pixel
shiftedData[i + 2] = data[bIndex + 2]; // Blue channel from right-shifted pixel
shiftedData[i + 3] = data[i + 3]; // Alpha
}
}
// Update data with aberrated data
data.set(shiftedData);
}
// --- Step 5: Vignette ---
if (vignetteAmount > 0) {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// Use the largest dimension for a more circular vignette, or maxDist for elliptical
const radius = Math.max(centerX, centerY);
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
const i = (y * canvas.width + x) * 4;
const dx = x - centerX;
const dy = y - centerY;
const dist = Math.sqrt(dx * dx + dy * dy);
// Calculate vignette factor (0 to 1, where 1 is full intensity, 0 is no darkening)
// Smootherstep or power curve for falloff
const vignetteEffect = Math.pow(dist / radius, 2) * vignetteAmount;
const factor = Math.max(0, 1 - vignetteEffect);
data[i] *= factor;
data[i+1] *= factor;
data[i+2] *= factor;
}
}
}
// --- Put final pixel data back ---
ctx.putImageData(imageData, 0, 0);
// --- Step 6: Scanlines ---
if (scanlinesOpacity > 0 && scanlinesOpacity <= 1) {
ctx.fillStyle = `rgba(0, 0, 0, ${scanlinesOpacity})`;
for (let y = 0; y < canvas.height; y += 4) { // 2px lines with 2px gap
ctx.fillRect(0, y, canvas.width, 2);
}
}
return canvas;
}
Apply Changes