You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, sepiaIntensity = 0.8, stainOpacity = 0.6, splotchCount = 3, noiseAmount = 20) {
const canvas = document.createElement('canvas');
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
if (width === 0 || height === 0) {
// Handle cases where image might not be loaded or is invalid
console.error("Original image has zero width or height. Is it loaded properly?");
canvas.width = 1; // Return a 1x1 canvas to avoid errors downstream
canvas.height = 1;
return canvas;
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 1. Draw the original image
ctx.drawImage(originalImg, 0, 0, width, height);
// 2. Apply Sepia Tone (if intensity > 0)
if (sepiaIntensity > 0 && sepiaIntensity <= 1.0) {
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Standard sepia calculation
const tr = 0.393 * r + 0.769 * g + 0.189 * b;
const tg = 0.349 * r + 0.686 * g + 0.168 * b;
const tb = 0.272 * r + 0.534 * g + 0.131 * b;
// Blend original with sepia based on intensity
data[i] = (1 - sepiaIntensity) * r + sepiaIntensity * Math.min(255, tr);
data[i + 1] = (1 - sepiaIntensity) * g + sepiaIntensity * Math.min(255, tg);
data[i + 2] = (1 - sepiaIntensity) * b + sepiaIntensity * Math.min(255, tb);
}
ctx.putImageData(imageData, 0, 0);
}
// 3. Add Coffee Stain Splotches
if (splotchCount > 0 && stainOpacity > 0 && stainOpacity <= 1.0) {
// 'multiply' blending mode darkens the image where the stain is applied,
// simulating absorption of coffee into paper.
ctx.globalCompositeOperation = 'multiply';
const baseStainColorR = 101; // A typical coffee brown (RGB: 101, 67, 33)
const baseStainColorG = 67;
const baseStainColorB = 33;
for (let i = 0; i < splotchCount; i++) {
const maxDim = Math.max(width, height);
// Stain radius: 10% to 30% of the larger image dimension
const radius = (Math.random() * 0.2 + 0.1) * maxDim;
const x = Math.random() * width;
const y = Math.random() * height;
// Create a radial gradient for the stain.
// Coffee stains often have a darker ring and a lighter center.
const gradient = ctx.createRadialGradient(x, y, radius * 0.2, x, y, radius);
// Alpha values are scaled by stainOpacity.
// Center of the stain (more transparent)
gradient.addColorStop(0, `rgba(${baseStainColorR}, ${baseStainColorG}, ${baseStainColorB}, ${0.15 * stainOpacity})`);
// Main body/ring of the stain (darker, more opaque)
gradient.addColorStop(0.6 + Math.random() * 0.2, `rgba(${baseStainColorR}, ${baseStainColorG}, ${baseStainColorB}, ${0.4 * stainOpacity})`);
// Outer edge of the stain (fades out)
gradient.addColorStop(1, `rgba(${baseStainColorR}, ${baseStainColorG}, ${baseStainColorB}, ${0.05 * stainOpacity})`);
ctx.beginPath();
// Draw a slightly irregular circle for the stain shape using lineTo
const numJaggedPoints = 15 + Math.floor(Math.random() * 10); // 15-24 points
const angleStepJagged = (Math.PI * 2) / numJaggedPoints;
// How much radius can vary for jaggedness (e.g., up to 40% of base radius)
const radiusJaggedVariance = 0.4;
for (let k = 0; k < numJaggedPoints; k++) {
// Add random offset to angle for more irregularity
const currentAngle = angleStepJagged * k + (Math.random() - 0.5) * angleStepJagged * 0.4;
// Vary radius for each point
const currentRadius = radius * (1 - Math.random() * radiusJaggedVariance);
const pointX = x + Math.cos(currentAngle) * currentRadius;
const pointY = y + Math.sin(currentAngle) * currentRadius;
if (k === 0) {
ctx.moveTo(pointX, pointY);
} else {
ctx.lineTo(pointX, pointY);
}
}
ctx.closePath();
ctx.fillStyle = gradient;
ctx.fill();
}
// Reset composite operation to default for subsequent drawing operations (like noise)
ctx.globalCompositeOperation = 'source-over';
}
// 4. Add Noise
if (noiseAmount > 0) {
const imageData = ctx.getImageData(0, 0, width, height);
const pixels = imageData.data;
const noiseStrength = Math.max(0, Math.min(noiseAmount, 100)); // Clamp noise amount
for (let i = 0; i < pixels.length; i += 4) {
// Add random noise to R, G, B channels. Alpha (pixels[i+3]) is unchanged.
const randomFactor = (Math.random() - 0.5) * noiseStrength;
pixels[i] = Math.max(0, Math.min(255, pixels[i] + randomFactor));
pixels[i+1] = Math.max(0, Math.min(255, pixels[i+1] + randomFactor));
pixels[i+2] = Math.max(0, Math.min(255, pixels[i+2] + randomFactor));
}
ctx.putImageData(imageData, 0, 0);
}
return canvas;
}
Apply Changes