You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
shadowColor = "rgb(40, 10, 70)", // Dark Purple-Blue
highlightColor = "rgb(255, 80, 150)", // Vibrant Pink
contrastValue = 1.4,
brightnessValue = 5,
scanlineOpacity = 0.15,
scanlineThickness = 1,
scanlineGap = 2
) {
// Helper function to parse color strings (e.g., "rgb(r,g,b)", "#RRGGBB", "colorname")
// to an {r, g, b} object.
function parseColor(colorStr) {
// Create a temporary 1x1 canvas to draw the color and get its RGB components.
const tempCanvas = document.createElement('canvas');
tempCanvas.width = 1;
tempCanvas.height = 1;
const tempCtx = tempCanvas.getContext('2d');
if (!tempCtx) { // Fallback for safety, though highly unlikely in browsers
return { r: 0, g: 0, b: 0 };
}
tempCtx.fillStyle = colorStr;
tempCtx.fillRect(0, 0, 1, 1);
const pixelData = tempCtx.getImageData(0, 0, 1, 1).data;
return { r: pixelData[0], g: pixelData[1], b: pixelData[2] };
}
// Clamp utility function
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
const canvas = document.createElement('canvas');
// Use naturalWidth/Height if available, otherwise fallback to width/height.
// Provide default dimensions if image info is missing.
const imgWidth = originalImg.naturalWidth || originalImg.width || 300;
const imgHeight = originalImg.naturalHeight || originalImg.height || 150;
canvas.width = imgWidth;
canvas.height = imgHeight;
const ctx = canvas.getContext('2d');
if (!ctx) {
console.error("Failed to get 2D context for the main canvas.");
// Return the canvas element, even if unusable, as per preference.
// The caller might handle this or it might appear as a broken element.
return canvas;
}
// Draw the original image onto the canvas
try {
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Error drawing image: ", e);
ctx.fillStyle = "rgba(255, 0, 0, 0.7)";
ctx.fillRect(0,0, canvas.width, canvas.height);
ctx.font = "16px Arial";
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.fillText("Error: Could not draw image.", canvas.width / 2, canvas.height / 2);
return canvas;
}
// Get image data for pixel manipulation
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Could not getImageData (e.g., CORS issue if image is cross-origin): ", e);
// Draw an error message on the canvas
ctx.fillStyle = "rgba(0,0,0,0.7)"; // Semi-transparent overlay
ctx.fillRect(0,0, canvas.width, canvas.height);
ctx.font = "16px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.fillText("Error: Cannot process image pixels (CORS issue?).", canvas.width / 2, canvas.height / 2);
return canvas;
}
const data = imageData.data;
const sColorRGB = parseColor(shadowColor);
const hColorRGB = parseColor(highlightColor);
// Pixel manipulation loop
for (let i = 0; i < data.length; i += 4) {
let rOrig = data[i];
let gOrig = data[i + 1];
let bOrig = data[i + 2];
// 1. Apply Contrast
// The midpoint 127.5 is used for contrast calculation.
let r = (rOrig - 127.5) * contrastValue + 127.5;
let g = (gOrig - 127.5) * contrastValue + 127.5;
let b = (bOrig - 127.5) * contrastValue + 127.5;
// 2. Apply Brightness
r += brightnessValue;
g += brightnessValue;
b += brightnessValue;
// Clamp values after contrast and brightness adjustments
r = clamp(r, 0, 255);
g = clamp(g, 0, 255);
b = clamp(b, 0, 255);
// 3. Duotone mapping based on luminance of the adjusted color
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
const lumNorm = gray / 255; // Normalized luminance (0-1)
// Interpolate between shadow and highlight colors based on luminance
data[i] = clamp(sColorRGB.r * (1 - lumNorm) + hColorRGB.r * lumNorm, 0, 255);
data[i+1] = clamp(sColorRGB.g * (1 - lumNorm) + hColorRGB.g * lumNorm, 0, 255);
data[i+2] = clamp(sColorRGB.b * (1 - lumNorm) + hColorRGB.b * lumNorm, 0, 255);
// Alpha channel (data[i+3]) remains unchanged
}
// Put the modified image data back onto the canvas
ctx.putImageData(imageData, 0, 0);
// 4. Draw Scanlines
const st = Math.max(0, scanlineThickness); // Ensure thickness is not negative
const so = clamp(scanlineOpacity, 0, 1); // Ensure opacity is between 0 and 1
if (st > 0 && so > 0) {
ctx.fillStyle = `rgba(0, 0, 0, ${so})`;
const sg = Math.max(0, scanlineGap); // Ensure gap is not negative
const step = st + sg;
if (step > 0) { // Ensure we make progress in the loop
for (let y = 0; y < canvas.height; y += step) {
ctx.fillRect(0, y, canvas.width, st);
}
}
}
return canvas;
}
Apply Changes