You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, grainAmount = 15, vignetteStrength = 0.4, sepiaValue = 0.0, contrastValue = 1.05, saturationValue = 0.9) {
// Ensure parameter inputs are correctly typed as numbers
grainAmount = Number(grainAmount);
vignetteStrength = Number(vignetteStrength); // Expected range 0.0 (none) to 1.0 (strong)
sepiaValue = Number(sepiaValue); // Expected range 0.0 (none) to 1.0 (full)
contrastValue = Number(contrastValue); // Expected around 1.0 (e.g., 1.05 for 105% contrast)
saturationValue = Number(saturationValue); // Expected around 1.0 (e.g., 0.9 for 90% saturation)
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Use naturalWidth/Height for original dimensions, fallback to width/height
// This assumes originalImg is a loaded HTMLImageElement or similar
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
// Ensure canvas has dimensions, otherwise drawing might fail or be 0x0
if (imgWidth === 0 || imgHeight === 0) {
console.error("Image dimensions are zero. Ensure the image is loaded and has valid dimensions.");
// Return a minimal canvas as a fallback to prevent further errors
canvas.width = 1;
canvas.height = 1;
return canvas;
}
canvas.width = imgWidth;
canvas.height = imgHeight;
// 1. Build and apply CSS-like filters first using ctx.filter
// These filters are applied when ctx.drawImage is called.
let filterArray = [];
if (saturationValue !== 1.0) {
filterArray.push(`saturate(${saturationValue})`);
}
if (contrastValue !== 1.0) {
filterArray.push(`contrast(${contrastValue})`);
}
if (sepiaValue > 0 && sepiaValue <= 1) {
filterArray.push(`sepia(${sepiaValue})`);
}
// Optional: A general brightness adjustment can also be part of a film look
// e.g., filterArray.push(`brightness(0.98)`); // Slight darkening
if (filterArray.length > 0) {
ctx.filter = filterArray.join(' ');
}
// 2. Draw the original image onto the canvas. Filters (if any) are applied at this stage.
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
// Reset ctx.filter to 'none' so it doesn't affect subsequent pixel manipulation or vignette drawing
ctx.filter = 'none';
// 3. Perform pixel-level adjustments: custom color tint (if sepia not used) and film grain
// This step is necessary for effects not easily achievable with simple CSS filters.
const needsPixelManipulation = grainAmount > 0 || (sepiaValue === 0.0); // Custom tint applies if sepia is off
if (needsPixelManipulation) {
const imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
const data = imageData.data; // This is a Uint8ClampedArray: [R,G,B,A, R,G,B,A, ...]
for (let i = 0; i < data.length; i += 4) {
// Get current pixel's RGBA values (already affected by canvas filters)
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// Apply a custom film color tint if sepia filter was not used
if (sepiaValue === 0.0) {
// This creates a subtle warm tint, common in some film aesthetics.
r = r * 1.015; // Slightly enhance red tones
// g = g; // Green channel can be left as is for this tint
b = b * 0.97; // Slightly reduce blue tones (contributes to warmth)
}
// Add film grain
if (grainAmount > 0) {
// Generate a random value for grain, centered around 0
const grainIntensity = (Math.random() - 0.5) * grainAmount;
r += grainIntensity;
g += grainIntensity;
b += grainIntensity;
}
// Clamp color values to the valid 0-255 range and assign back to image data
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));
// Alpha channel (data[i+3]) is typically preserved
}
ctx.putImageData(imageData, 0, 0); // Write the modified pixel data back to the canvas
}
// 4. Apply vignetting as a final overlay effect
// Vignetting darkens the corners of the image.
if (vignetteStrength > 0 && vignetteStrength <= 1) {
// Ensure standard drawing mode for the overlay
ctx.globalCompositeOperation = 'source-over';
const centerX = imgWidth / 2;
const centerY = imgHeight / 2;
// Calculate radii for the vignette gradient
const minDimension = Math.min(imgWidth, imgHeight);
// Inner radius: defines the central area that remains relatively clear of the vignette
const innerRadius = minDimension * 0.25; // Adjust for softer/harder center (0.2-0.5 typical)
// Outer radius: where the vignette effect is at its maximum strength (should cover corners)
const outerRadius = Math.sqrt(centerX * centerX + centerY * centerY);
// Create a radial gradient for the vignette effect
const gradient = ctx.createRadialGradient(
centerX, centerY, innerRadius, // Inner circle (start of gradient)
centerX, centerY, outerRadius // Outer circle (end of gradient)
);
// Define gradient color stops: fades from fully transparent black to semi-transparent black
gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Center of vignette is transparent
gradient.addColorStop(1, `rgba(0,0,0,${vignetteStrength})`); // Edges are dark based on strength
ctx.fillStyle = gradient; // Set the fill style to the created gradient
ctx.fillRect(0, 0, imgWidth, imgHeight); // Apply the gradient over the entire canvas
}
return canvas;
}
Free Image Tool Creator
Can't find the image tool you're looking for? Create one based on your own needs now!
The Image Medium Format Film Filter Effect tool allows users to apply a vintage film look to their images by adjusting various parameters such as grain, vignette, sepia tone, contrast, and saturation. This can enhance photographs to give them a classic, artistic appearance reminiscent of medium format film photography. Ideal for photographers, graphic designers, and social media enthusiasts, this tool is useful for transforming digital images into visually appealing artworks, perfect for printing, sharing online, or personal projects.