You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, saturation = 0.2, contrast = 50, brightness = -25, coolShift = 30) {
// Parameters:
// originalImg: JavaScript Image object (HTMLImageElement)
// saturation: number (0.0 to 1.0). 0.0 for grayscale, 1.0 for original color intensity. Default: 0.2 (low saturation for brooding effect)
// contrast: number (0 to 100). Strength of contrast. 0 means no change. Default: 50 (results in a 1.5x contrast factor)
// brightness: number (-100 to 100). Brightness adjustment. Negative values darken, positive values lighten. Default: -25 (darker)
// coolShift: number (0 to 50). Amount of cool blue/cyan tint. Increases blue, slightly reduces red. Default: 30
const canvas = document.createElement('canvas');
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
if (width === 0 || height === 0) {
console.error("Original image has zero width or height.");
// Return a tiny, empty canvas as a fallback.
canvas.width = 1;
canvas.height = 1;
const tinyCtx = canvas.getContext('2d');
if (tinyCtx) {
tinyCtx.clearRect(0,0,1,1); // Make it transparent
}
return canvas;
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (!ctx) {
console.error("Could not get 2D rendering context.");
// Fallback: return an empty canvas (it's already created and sized)
return canvas;
}
try {
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Error drawing image onto canvas:", e);
// If drawing fails, return canvas with an error message.
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#DDDDDD'; // Light gray background for message
ctx.fillRect(0,0,canvas.width, canvas.height);
ctx.fillStyle = 'black';
const baseFontSize = Math.min(canvas.width, canvas.height) / 15;
const fontSize = Math.max(12, Math.floor(baseFontSize));
ctx.font = `bold ${fontSize}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Error: Could not draw input image.', canvas.width / 2, canvas.height / 2);
return canvas;
}
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Error getting ImageData (possibly from a tainted canvas due to cross-origin image restrictions):", e);
// Image is drawn, but pixel manipulation is not possible.
// Display a message on top of the drawn image.
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'; // Semi-transparent overlay
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
const baseFontSize = Math.min(canvas.width, canvas.height) / 20;
const fontSize = Math.max(12, Math.floor(baseFontSize));
ctx.font = `bold ${fontSize}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const msgLine1 = "Filter not applied:";
const msgLine2 = "Image data inaccessible.";
const textLineHeight = fontSize * 1.2;
ctx.fillText(msgLine1, canvas.width / 2, canvas.height / 2 - textLineHeight / 2);
ctx.fillText(msgLine2, canvas.width / 2, canvas.height / 2 + textLineHeight / 2);
return canvas;
}
const data = imageData.data;
// Convert parameters to numbers, provides robustness if string representations of numbers are passed.
const numSaturation = Number(saturation);
const numContrast = Number(contrast);
const numBrightness = Number(brightness);
const numCoolShift = Number(coolShift);
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i+1];
let b = data[i+2];
// 1. Brightness adjustment
r += numBrightness;
g += numBrightness;
b += numBrightness;
// Clamp to 0-255
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// 2. Contrast adjustment
// `numContrast` (0 to 100) determines factor.
// 0 maps to 1.0 (no change), 50 maps to 1.5, 100 maps to 2.0.
// The midpoint for contrast is 128.
const contrastFactor = 1.0 + (numContrast / 100.0);
r = contrastFactor * (r - 128) + 128;
g = contrastFactor * (g - 128) + 128;
b = contrastFactor * (b - 128) + 128;
// Clamp to 0-255
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. Saturation adjustment
// `numSaturation` (0.0-1.0): 0.0 for grayscale, 1.0 for original color intensity.
// Standard luminosity coefficients for grayscale conversion.
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
// Lerp (linear interpolation) between grayscale and original color component
r = gray * (1.0 - numSaturation) + r * numSaturation;
g = gray * (1.0 - numSaturation) + g * numSaturation;
b = gray * (1.0 - numSaturation) + b * numSaturation;
// Clamping here is good practice, though if inputs r,g,b are 0-255 and numSaturation is 0-1,
// results should remain within range. Floating point inaccuracies might slightly exceed.
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. Cool Tint (Brooding filter)
// `numCoolShift` (0-50) determines intensity of the tint.
r -= numCoolShift * 0.5; // Reduce red component to make it cooler
// g -= numCoolShift * 0.1; // Optional: slightly reduce green if a more cyan tint is desired
b += numCoolShift; // Boost blue component
// Final clamp for pixel component values and round to nearest integer
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
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes