You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, textureUrl = "default_grain", textureOpacity = 0.4, textureBlendMode = 'overlay', filmContrast = 120, filmSaturation = 80, filmSepia = 15, vignetteStrength = 0.5, vignetteColor = 'black') {
// Helper function to create procedural_grain texture
// This helper is defined within processImage scope
function _createProceduralGrainTexture(width, height, grainAmplitude = 32) {
const grainCanvas = document.createElement('canvas');
grainCanvas.width = width;
grainCanvas.height = height;
const grainCtx = grainCanvas.getContext('2d');
const imageData = grainCtx.getImageData(width, height);
const data = imageData.data; // This is a Uint8ClampedArray
for (let i = 0; i < data.length; i += 4) {
// Generate monochrome noise, centered around 128 for 'overlay' or 'soft-light' blend modes
const noise = (Math.random() - 0.5) * grainAmplitude;
let gray = 128 + noise;
gray = Math.max(0, Math.min(255, gray)); // Clamp to 0-255 range
data[i] = gray; // Red
data[i+1] = gray; // Green
data[i+2] = gray; // Blue
data[i+3] = 255; // Alpha (grain texture is opaque, its blending is controlled globally)
}
grainCtx.putImageData(imageData, 0, 0);
return grainCanvas; // Return the canvas element with the grain
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
canvas.width = width;
canvas.height = height;
// 1. Apply film color adjustments (contrast, saturation, sepia) using CSS filters
let filterString = '';
if (filmContrast !== 100) filterString += `contrast(${filmContrast}%) `;
if (filmSaturation !== 100) filterString += `saturate(${filmSaturation}%) `;
if (filmSepia > 0) filterString += `sepia(${filmSepia}%) `;
// Potentially add: brightness(X%) if needed
if (filterString.trim() !== '') {
ctx.filter = filterString.trim();
}
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.filter = 'none'; // Reset filter for subsequent drawing operations
// 2. Overlay texture
const GHOST_TEXTURE_AMPLITUDE = 32; // Default amplitude for procedural grain.
if (textureUrl && typeof textureUrl === 'string' && textureOpacity > 0 && textureOpacity <=1) {
let textureSource = null; // Will hold HTMLImageElement or HTMLCanvasElement
if (textureUrl.toLowerCase() === "default_grain") {
textureSource = _createProceduralGrainTexture(width, height, GHOST_TEXTURE_AMPLITUDE);
} else if (textureUrl.trim() !== "") {
textureSource = await new Promise((resolve) => {
const img = new Image();
img.crossOrigin = "Anonymous";
img.onload = () => resolve(img);
img.onerror = () => {
console.warn(`Failed to load texture from ${textureUrl}. Skipping texture.`);
resolve(null);
};
img.src = textureUrl;
});
}
if (textureSource) {
ctx.globalAlpha = Math.max(0, Math.min(1, textureOpacity)); // Clamp opacity
ctx.globalCompositeOperation = textureBlendMode;
ctx.drawImage(textureSource, 0, 0, width, height); // Stretch texture to fit
// Reset global alpha and composite operation
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'source-over';
}
}
// 3. Apply Vignette effect
const C_VIGNETTE_STRENGTH = Math.max(0, Math.min(1, vignetteStrength)); // Clamp to [0,1]
if (C_VIGNETTE_STRENGTH > 0) {
const centerX = width / 2;
const centerY = height / 2;
const outerRadius = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
// innerRadiusFactor adjusts the falloff "shape". Higher values make the vignette more concentrated.
const innerRadiusFactor = 0.7;
const innerRadius = outerRadius * (1 - C_VIGNETTE_STRENGTH * innerRadiusFactor);
const gradient = ctx.createRadialGradient(centerX, centerY, Math.max(0, innerRadius), centerX, centerY, outerRadius);
// Parse vignetteColor to get its RGB components for the gradient
const tempCanvas = document.createElement('canvas');
tempCanvas.width = 1; tempCanvas.height = 1;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.fillStyle = vignetteColor; // Apply the user-provided color string
tempCtx.fillRect(0,0,1,1);
const [r, g, b] = tempCtx.getImageData(0,0,1,1).data;
gradient.addColorStop(0, `rgba(${r},${g},${b},0)`); // Center of gradient (transparent)
gradient.addColorStop(1, `rgba(${r},${g},${b},${C_VIGNETTE_STRENGTH})`); // Edge of gradient (vignetteColor with strength as alpha)
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
return canvas;
}
Apply Changes