You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, sepiaStrength = 0.9, stainOverlayColor = "rgba(140, 105, 75, 0.25)", noiseIntensity = 10, vignetteColor = "rgb(60, 40, 20)", vignetteStrength = 0.6, vignetteStart = 0.3) {
// Helper function to parse CSS color strings (rgb, rgba, hex, named colors etc.)
// Returns an object {r, g, b, a} with 'a' in [0,1] range.
function _parseColor(colorStr) {
const canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
// Using '2d' context. Adding { willReadFrequently: true } can be a hint for optimization
// if this function were called extremely often on the same context, but here it's for one-off parsing.
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Ensure a clean state for the pixel by filling with transparent black.
// This helps if colorStr is invalid, as fillStyle might be ignored,
// and getImageData would read whatever was on the canvas pixel before.
ctx.fillStyle = 'rgba(0,0,0,0)';
ctx.fillRect(0,0,1,1);
ctx.fillStyle = colorStr; // Apply the user-provided color string
ctx.fillRect(0, 0, 1, 1); // Fill the pixel with this color
const data = ctx.getImageData(0, 0, 1, 1).data;
// data array contains R, G, B, A components as integers from 0 to 255.
// Alpha is converted to 0-1 range.
return { r: data[0], g: data[1], b: data[2], a: data[3] / 255 };
}
const outputCanvas = document.createElement('canvas');
const ctx = outputCanvas.getContext('2d');
// Use naturalWidth/Height for the true dimensions of the loaded image.
outputCanvas.width = originalImg.naturalWidth || originalImg.width;
outputCanvas.height = originalImg.naturalHeight || originalImg.height;
// 1. Draw the original image onto the output canvas
ctx.drawImage(originalImg, 0, 0, outputCanvas.width, outputCanvas.height);
// 2. Apply Sepia and/or Noise via pixel manipulation
if (sepiaStrength > 0 || noiseIntensity > 0) {
const imageData = ctx.getImageData(0, 0, outputCanvas.width, outputCanvas.height);
const data = imageData.data;
const len = data.length;
for (let i = 0; i < len; i += 4) {
let r = data[i];
let g = data[i+1];
let b = data[i+2];
// Store original pixel color for sepia blending
const origR = r;
const origG = g;
const origB = b;
if (sepiaStrength > 0) {
// Standard sepia matrix calculation
const tr = 0.393 * origR + 0.769 * origG + 0.189 * origB;
const tg = 0.349 * origR + 0.686 * origG + 0.168 * origB;
const tb = 0.272 * origR + 0.534 * origG + 0.131 * origB;
// Blend original with sepia based on sepiaStrength
r = (tr * sepiaStrength) + (origR * (1 - sepiaStrength));
g = (tg * sepiaStrength) + (origG * (1 - sepiaStrength));
b = (tb * sepiaStrength) + (origB * (1 - sepiaStrength));
}
if (noiseIntensity > 0) {
// Add random noise to each channel. Noise is in [-noiseIntensity, +noiseIntensity]
const noise = (Math.random() * 2 - 1) * noiseIntensity;
r += noise;
g += noise;
b += noise;
}
// Clamp values to [0, 255] range
data[i] = Math.max(0, Math.min(255, r));
data[i+1] = Math.max(0, Math.min(255, g));
data[i+2] = Math.max(0, Math.min(255, b));
}
ctx.putImageData(imageData, 0, 0);
}
// 3. Apply Stain Overlay Color
const parsedStainColor = _parseColor(stainOverlayColor);
// Apply only if the stain color has some transparency (alpha > 0)
if (parsedStainColor.a > 0) {
// 'multiply' blend mode tends to work well for staining effects
ctx.globalCompositeOperation = 'multiply';
// Use the original stainOverlayColor string for fillStyle, as it might be more precise
// or a specific format (e.g. HSL) that the canvas can handle directly.
ctx.fillStyle = stainOverlayColor;
ctx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
ctx.globalCompositeOperation = 'source-over'; // Reset to default blend mode
}
// 4. Apply Vignette Effect
if (vignetteStrength > 0 && vignetteStart <= 1) { // vignetteStart > 1 would mean no vignette
const centerX = outputCanvas.width / 2;
const centerY = outputCanvas.height / 2;
// Calculate the maximum radius to a corner of the canvas
const maxRadius = Math.sqrt(centerX * centerX + centerY * centerY);
if (maxRadius <= 0) return outputCanvas; // Avoid issues with tiny/zero-size canvas
// vignetteStart defines where the clear part of the vignette ends (as a ratio of maxRadius)
const gradInnerRadius = maxRadius * Math.min(vignetteStart, 1.0); // Clamp start to be within maxRadius
const gradOuterRadius = maxRadius;
const parsedVignetteBaseColor = _parseColor(vignetteColor);
const vignetteR = parsedVignetteBaseColor.r;
const vignetteG = parsedVignetteBaseColor.g;
const vignetteB = parsedVignetteBaseColor.b;
const gradient = ctx.createRadialGradient(centerX, centerY, gradInnerRadius, centerX, centerY, gradOuterRadius);
// Gradient: transparent center, fades to vignetteColor with vignetteStrength alpha at edges
gradient.addColorStop(0, `rgba(${vignetteR}, ${vignetteG}, ${vignetteB}, 0)`);
gradient.addColorStop(1, `rgba(${vignetteR}, ${vignetteG}, ${vignetteB}, ${vignetteStrength})`);
// 'multiply' blend mode for darkening the edges
ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
ctx.globalCompositeOperation = 'source-over'; // Reset to default blend mode
}
return outputCanvas;
}
Free Image Tool Creator
Can't find the image tool you're looking for? Create one based on your own needs now!
The Image Tea Stain Filter Effect Tool allows users to apply a vintage-like tea stain effect to their images. This tool enhances images with a sepia tone, adds noise for texture, overlays a stain color, and applies a vignette effect to darken the edges, creating a warm and aged appearance. It is perfect for artists, photographers, and social media enthusiasts looking to give their visuals a unique, antique look or to create a nostalgic atmosphere in their photos.