You can edit the below JavaScript code to customize the image tool.
async function processImage(originalImg,
liftBlacksParam = 20,
reduceWhitesParam = 20,
saturationParam = 0.7,
tintColorStr = "40,30,25", // Default: A subtle warm, slightly desaturated brownish tone
tintAlphaParam = 0.1) {
// Clamp and validate numeric parameters
const liftBlacks = Math.max(0, Math.min(255, Number(liftBlacksParam)));
const reduceWhites = Math.max(0, Math.min(255, Number(reduceWhitesParam)));
const saturation = Math.max(0, Math.min(1, Number(saturationParam)));
const tintAlpha = Math.max(0, Math.min(1, Number(tintAlphaParam)));
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Ensure the image is loaded if it's an HTMLImageElement
// The problem states originalImg is an "Image object", usually meaning HTMLImageElement.
if (originalImg instanceof HTMLImageElement && !originalImg.complete) {
try {
await new Promise((resolve, reject) => {
originalImg.onload = resolve;
originalImg.onerror = (err) => reject(new Error("Image failed to load for processing: " + (err.message || err)));
// Check if src is set, otherwise onload might never fire
if (!originalImg.src) {
reject(new Error("Image source is not set."));
}
});
} catch (e) {
console.error("Error loading image:", e);
// Return an empty (or error-indicating) canvas or re-throw
ctx.canvas.width = 100; // Example error canvas
ctx.canvas.height = 30;
ctx.font = "12px Arial";
ctx.fillText("Error loading image", 5, 20);
return canvas;
}
}
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
if (canvas.width === 0 || canvas.height === 0) {
console.warn("Original image has zero width or height. Returning an empty canvas.");
// Potentially draw a message on the canvas if preferred
return canvas;
}
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Could not get ImageData (e.g., tainted canvas):", e);
// Potentially return the original drawn canvas without filter or an error canvas
ctx.canvas.width = 200; // Example error canvas
ctx.canvas.height = 30;
ctx.font = "12px Arial";
ctx.fillText("Error: Could not process image", 5, 20);
return canvas; // Return canvas with original image / error
}
const data = imageData.data;
// Parse tint color string
let parsedTintR, parsedTintG, parsedTintB;
// These are the components of the default tintColorStr "40,30,25"
const defaultTintValues = { r: 40, g: 30, b: 25 };
const tintParts = tintColorStr.split(',').map(s => parseFloat(s.trim()));
if (tintParts.length === 3 && tintParts.every(n => !isNaN(n) && isFinite(n))) {
parsedTintR = Math.max(0, Math.min(255, tintParts[0]));
parsedTintG = Math.max(0, Math.min(255, tintParts[1]));
parsedTintB = Math.max(0, Math.min(255, tintParts[2]));
} else {
const defaultTintStrForComparison = `${defaultTintValues.r},${defaultTintValues.g},${defaultTintValues.b}`;
if (tintColorStr !== defaultTintStrForComparison) {
console.warn(`Invalid tintColorStr: "${tintColorStr}". Using default tint color ("${defaultTintStrForComparison}"). Format should be 'R,G,B'.`);
}
// Fallback to hardcoded default tint values
parsedTintR = defaultTintValues.r;
parsedTintG = defaultTintValues.g;
parsedTintB = defaultTintValues.b;
}
// Calculate new black and white points for contrast adjustment
const newBlack = liftBlacks;
let newWhite = 255 - reduceWhites;
if (newWhite < newBlack) {
// This ensures that the mapped range is not inverted (e.g. black becoming white).
// If newWhite becomes equal to newBlack, contrastRange will be 0,
// leading to a flat color (newBlack,newBlack,newBlack) before tinting.
newWhite = newBlack;
}
const contrastRange = newWhite - newBlack;
// Pre-calculate factors for performance optimization within the loop
const sFactor = 1 - saturation; // Used for desaturation
const taFactor = 1 - tintAlpha; // Used for tint blending
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i+1];
let b = data[i+2];
// 1. Saturation adjustment
if (saturation < 1.0) { // saturation = 1.0 means original saturation, no change needed
const lum = 0.299 * r + 0.587 * g + 0.114 * b; // Standard luminance calculation
r = r * saturation + lum * sFactor;
g = g * saturation + lum * sFactor;
b = b * saturation + lum * sFactor;
}
// 2. Contrast adjustment (Lifting blacks, crushing whites)
// This linearly maps the current pixel component's value (assumed to be in [0, 255] after saturation)
// to a new range [newBlack, newWhite].
// If contrastRange is 0 (i.e., newBlack == newWhite), all components will map to newBlack.
r = newBlack + (r / 255) * contrastRange;
g = newBlack + (g / 255) * contrastRange;
b = newBlack + (b / 255) * contrastRange;
// 3. Tinting
if (tintAlpha > 0) { // tintAlpha = 0 means no tint, no change needed
r = r * taFactor + parsedTintR * tintAlpha;
g = g * taFactor + parsedTintG * tintAlpha;
b = b * taFactor + parsedTintB * tintAlpha;
}
// 4. Clamping results to [0, 255] and rounding
// Pixel data must be integers.
data[i] = Math.max(0, Math.min(255, Math.round(r)));
data[i+1] = Math.max(0, Math.min(255, Math.round(g)));
data[i+2] = Math.max(0, Math.min(255, Math.round(b)));
// Alpha channel (data[i+3]) is preserved untouched
}
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 Matte Filter Application allows users to enhance their images by applying a matte filter with adjustable parameters. Users can modify the contrast by lifting blacks and reducing whites, adjust saturation levels, and add a custom tint color to achieve the desired aesthetic effect. This tool is suitable for photographers and graphic designers who want to create a vintage or softened look in their images, as well as for social media enthusiasts looking to enhance their visual content with unique color treatments.