You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, sepiaParam = "0.7", contrastParam = "1.3", noiseParam = "15", vignettePowerParam = "0.6", vignetteCoverageParam = "0.5") {
// Convert string parameters to their appropriate numeric types
const sepiaAmount = parseFloat(sepiaParam);
const contrastValue = parseFloat(contrastParam);
const noiseIntensity = parseInt(noiseParam, 10); // Explicitly use radix 10 for parseInt
const vignettePower = parseFloat(vignettePowerParam);
const vignetteCoverage = parseFloat(vignetteCoverageParam);
const canvas = document.createElement('canvas');
// Use { willReadFrequently: true } for potential performance optimization if supported
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
canvas.width = imgWidth;
canvas.height = imgHeight;
// Handle cases where the image might not be loaded or has no dimensions
if (imgWidth === 0 || imgHeight === 0) {
// Optionally, log a warning or draw an error message on the canvas
// console.warn("Image has zero width or height.");
return canvas; // Return empty (or minimally setup) canvas
}
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
// Get image data to manipulate pixels.
// This can throw a security error if the image is cross-origin and the canvas becomes tainted.
let imageData;
try {
imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
console.error("Could not getImageData from canvas. Likely a cross-origin image security issue.", e);
// Fallback: return the canvas with the original image drawn, and perhaps an error message.
// For a visual cue, you could draw an error message on the canvas itself.
ctx.font = "16px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.fillText("Error: Could not process image (cross-origin issue?).", imgWidth / 2, imgHeight / 2);
return canvas;
}
const pixels = imageData.data;
const centerX = imgWidth / 2.0;
const centerY = imgHeight / 2.0;
// Iterate over each pixel (RGBA components)
for (let i = 0; i < pixels.length; i += 4) {
let r = pixels[i];
let g = pixels[i+1];
let b = pixels[i+2];
// Alpha channel (pixels[i+3]) is generally not modified for this effect
// 1. Desaturation and Tinting for Steampunk look
if (sepiaAmount > 0) {
// Calculate luminance (brightness) of the pixel
const lum = 0.299 * r + 0.587 * g + 0.114 * b;
// Desaturate: sepiaAmount an Dcontrols blend between original color and its luminance
// A portion of sepiaAmount (e.g., 70%) determines the desaturation strength
const desaturationLevel = sepiaAmount * 0.7;
r = r * (1 - desaturationLevel) + lum * desaturationLevel;
g = g * (1 - desaturationLevel) + lum * desaturationLevel;
b = b * (1 - desaturationLevel) + lum * desaturationLevel;
// Apply a steampunk tint (bronze/copper/brass tones)
// The strength of this tint is also influenced by sepiaAmount
const tintStrengthFactor = sepiaAmount;
r = r + 30 * tintStrengthFactor; // Add red/orange component
g = g + 15 * tintStrengthFactor; // Add a smaller green/yellow component
b = b - 20 * tintStrengthFactor; // Reduce blue component to make it warmer/browner
}
// Clamp color values after sepia/tint to stay within [0, 255]
r = Math.min(255, Math.max(0, r));
g = Math.min(255, Math.max(0, g));
b = Math.min(255, Math.max(0, b));
// 2. Contrast Adjustment
if (contrastValue !== 1.0) {
// Standard contrast formula: NewColor = ((OldColor/255 - 0.5) * ContrastFactor + 0.5) * 255
// ContrastValue: 1.0 = no change, >1.0 = increase contrast, <1.0 = decrease.
r = ((r / 255.0 - 0.5) * contrastValue + 0.5) * 255.0;
g = ((g / 255.0 - 0.5) * contrastValue + 0.5) * 255.0;
b = ((b / 255.0 - 0.5) * contrastValue + 0.5) * 255.0;
}
// Clamp after contrast
r = Math.min(255, Math.max(0, r));
g = Math.min(255, Math.max(0, g));
b = Math.min(255, Math.max(0, b));
// 3. Noise (Grain)
if (noiseIntensity > 0) {
// Add random noise (positive or negative) to each channel
const noise = (Math.random() - 0.5) * noiseIntensity;
r += noise;
g += noise;
b += noise;
}
// Clamp after noise
r = Math.min(255, Math.max(0, r));
g = Math.min(255, Math.max(0, g));
b = Math.min(255, Math.max(0, b));
// 4. Vignette Effect
// Apply only if vignette parameters are set to create an effect and image dimensions are valid
if (vignettePower > 0 && vignetteCoverage > 0 && imgWidth > 0 && imgHeight > 0) {
const currentX = (i / 4) % imgWidth;
const currentY = Math.floor((i / 4) / imgWidth);
// Calculate normalized distance components from the center (-1 to 1 range)
// This creates an elliptical vignette shape that matches the image aspect ratio
const dx_norm = (currentX - centerX) / centerX;
const dy_norm = (currentY - centerY) / centerY;
// Calculate combined normalized distance from center (0 at center, ~1 at corners)
let dist = Math.sqrt(dx_norm * dx_norm + dy_norm * dy_norm);
// Further normalize dist to be reliably within 0-1 (1 at corners)
dist = Math.min(dist / Math.sqrt(2.0), 1.0);
let vignetteEffect = 1.0; // Default: no change
// brightRadius: normalized radius of the central fully bright area (0 to 1)
// vignetteCoverage = 0: brightRadius = 1 (vignette only at extreme edges/corners)
// vignetteCoverage = 1: brightRadius = 0 (vignette effect starts from the very center)
const brightRadius = 1.0 - vignetteCoverage;
if (dist > brightRadius) {
// Pixel is in the vignette falloff zone.
// Calculate falloff intensity (0 to 1) within the vignette band.
// The width of the vignette band (normalized) is effectively 'vignetteCoverage'.
let falloff = (dist - brightRadius) / (vignetteCoverage + 0.00001); // Add epsilon to avoid division by zero
falloff = Math.min(1.0, Math.max(0.0, falloff)); // Clamp falloff to 0-1 range
// vignettePower determines maximum darkness (0=no darkening, 1=edges can become black)
vignetteEffect = 1.0 - falloff * vignettePower;
}
// Apply vignette darkening
r *= vignetteEffect;
g *= vignetteEffect;
b *= vignetteEffect;
}
// Final assignment of possibly modified and clamped color values to pixel data
pixels[i] = r; // r is already clamped
pixels[i+1] = g; // g is already clamped
pixels[i+2] = b; // b is already clamped
}
// Write the modified pixel data back to the canvas
ctx.putImageData(imageData, 0, 0);
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 Steampunk Filter Effect Tool allows users to apply a steampunk aesthetic to their images, transforming them with a unique combination of sepia tones, contrast adjustments, noise effects, and vignette detailing. This tool is ideal for artists, designers, or anyone looking to enhance their photos with a vintage, retro-futuristic style. Whether for social media posts, graphic design projects, or personal artwork, this filter can help achieve a distinct and creative visual appeal.