You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, desaturationAmount = 0.3, contrastAdjustment = -25, liftBlacks = 20, vignetteIntensity = 0.4, vignetteSoftness = 0.6) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Ensure originalImg dimensions are valid
const naturalWidth = originalImg.naturalWidth || originalImg.width;
const naturalHeight = originalImg.naturalHeight || originalImg.height;
if (naturalWidth === 0 || naturalHeight === 0) {
// Return an empty canvas or handle error for zero-dimension images
canvas.width = 0;
canvas.height = 0;
console.warn("Image Matte Portrait Filter: Input image has zero dimensions.");
return canvas;
}
canvas.width = naturalWidth;
canvas.height = naturalHeight;
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const width = canvas.width;
const height = canvas.height;
const centerX = width / 2;
const centerY = height / 2;
// maxDist for vignette normalization (distance from center to a corner)
const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);
// Helper function for smoothstep interpolation (used in vignette)
function smoothstep(edge0, edge1, value) {
// Clamp value to edge0-edge1 range before interpolation
const x = Math.max(0, Math.min(1, (value - edge0) / (edge1 - edge0)));
// Hermite interpolation
return x * x * (3 - 2 * x);
}
// Ensure parameters are numbers and provide fallback if not (though defaults should handle this)
desaturationAmount = Number(desaturationAmount) || 0;
contrastAdjustment = Number(contrastAdjustment) || 0;
liftBlacks = Number(liftBlacks) || 0;
vignetteIntensity = Number(vignetteIntensity) || 0;
vignetteSoftness = Number(vignetteSoftness) || 0;
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// 1. Desaturation
if (desaturationAmount > 0) {
const gray = 0.299 * r + 0.587 * g + 0.114 * b; // Luminosity method
// Lerp original color towards gray
r = r + (gray - r) * desaturationAmount;
g = g + (gray - g) * desaturationAmount;
b = b + (gray - b) * desaturationAmount;
}
// 2. Contrast Adjustment
if (contrastAdjustment !== 0) {
// Clamp contrastAdjustment to prevent extreme values or division by zero.
// The formula supports -255 to 255, but practically, very high values are extreme.
const clampedContrast = Math.max(-254, Math.min(254, contrastAdjustment));
const factor = (259 * (clampedContrast + 255)) / (255 * (259 - clampedContrast));
r = factor * (r - 128) + 128;
g = factor * (g - 128) + 128;
b = factor * (b - 128) + 128;
}
// Clamp values after contrast adjustment
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// 3. Lift Blacks (core to "matte" look)
// This raises the minimum brightness, making shadows less deep.
if (liftBlacks > 0) {
r += liftBlacks;
g += liftBlacks;
b += liftBlacks;
}
// Clamp values again before vignette, so vignette applies to the colors already adjusted
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// 4. Vignette
// vignetteIntensity: 0 (no vignette) to 1 (full effect at edges)
// vignetteSoftness: 0 (sharpest transition at edge) to 1 (softest, transition starts nearer center)
if (vignetteIntensity > 0 && maxDist > 0) { // maxDist check for 1x1 pixel images or similar
const currentX = (i / 4) % width;
const currentY = Math.floor((i / 4) / width);
const dx = currentX - centerX;
const dy = currentY - centerY;
const dist = Math.sqrt(dx * dx + dy * dy);
const normalizedDist = dist / maxDist; // 0 at center, 1 at corners
// Calculate where the vignette fade begins (edge0) and ends (edge1 for smoothstep)
// vignetteSoftness = 0: edge0 = 1.0 (fade only at the very edge, sharp)
// vignetteSoftness = 1: edge0 = 0.25 (fade starts 25% from center, very soft over large area)
// Default softness = 0.6: edge0 = 1.0 - (0.6 * 0.75) = 1.0 - 0.45 = 0.55 (fade from 55% radius to edge)
const vignetteEdge0 = Math.max(0, 1.0 - (vignetteSoftness * 0.75));
const vignetteEdge1 = 1.0; // Fade always reaches full intensity at the very edge/corners
let fadeAmount = 0;
if (vignetteEdge0 < vignetteEdge1) { // Normal case for smoothstep
fadeAmount = smoothstep(vignetteEdge0, vignetteEdge1, normalizedDist);
} else if (normalizedDist >= vignetteEdge0) { // Handles case where edge0=edge1 (e.g. softness makes edge0=1)
fadeAmount = 1; // Full effect if at or beyond the sharp edge
}
const reductionFactor = 1.0 - fadeAmount * vignetteIntensity;
r *= reductionFactor;
g *= reductionFactor;
b *= reductionFactor;
}
// Final clamp and assignment to image data (ensure integer values)
data[i] = Math.round(Math.max(0, Math.min(255, r)));
data[i + 1] = Math.round(Math.max(0, Math.min(255, g)));
data[i + 2] = Math.round(Math.max(0, Math.min(255, b)));
// Alpha channel (data[i+3]) is preserved
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes