You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, saturation = 1.4, contrast = 1.2, posterizeLevels = 6, noiseAmount = 0.1, outlineThreshold = 0.4) {
// 1. Setup main canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
/**
* Helper function to reduce the number of colors in an image.
* @param {ImageData} imageData - The image data to process.
* @param {number} levels - The number of color levels per channel.
* @returns {ImageData} The posterized image data.
*/
const posterize = (imageData, levels) => {
if (levels <= 1) return imageData;
const data = imageData.data;
const step = 255 / (levels - 1);
for (let i = 0; i < data.length; i += 4) {
data[i] = Math.round(data[i] / step) * step; // Red
data[i + 1] = Math.round(data[i + 1] / step) * step; // Green
data[i + 2] = Math.round(data[i + 2] / step) * step; // Blue
}
return imageData;
};
// ---==== LAYER 1: COLOR BASE ====---
const colorCanvas = document.createElement('canvas');
colorCanvas.width = canvas.width;
colorCanvas.height = canvas.height;
const colorCtx = colorCanvas.getContext('2d', { willReadFrequently: true });
// Apply saturation and contrast for a vintage, vibrant look
colorCtx.filter = `saturate(${saturation}) contrast(${contrast})`;
colorCtx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Apply posterization to mimic a limited color palette
if (posterizeLevels > 0) {
let imageData = colorCtx.getImageData(0, 0, canvas.width, canvas.height);
imageData = posterize(imageData, posterizeLevels);
colorCtx.putImageData(imageData, 0, 0);
}
// ---==== LAYER 2: OUTLINES ====---
const outlineCanvas = document.createElement('canvas');
outlineCanvas.width = canvas.width;
outlineCanvas.height = canvas.height;
const outlineCtx = outlineCanvas.getContext('2d', { willReadFrequently: true });
// Create a high-contrast, greyscale version to extract lines
// These filter values are chosen to isolate dark lines effectively
outlineCtx.filter = 'grayscale(1) contrast(200%) brightness(80%)';
outlineCtx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Process the outline layer to make it stark black lines on a transparent background
const outlineImageData = outlineCtx.getImageData(0, 0, canvas.width, canvas.height);
const outlineData = outlineImageData.data;
const thresholdValue = 255 * outlineThreshold;
for (let i = 0; i < outlineData.length; i += 4) {
const brightness = outlineData[i]; // Since it's grayscale, R=G=B
if (brightness < thresholdValue) {
// This pixel is part of a dark line
outlineData[i] = 0; // R
outlineData[i + 1] = 0; // G
outlineData[i + 2] = 0; // B
outlineData[i + 3] = 255; // Alpha (fully opaque)
} else {
// This is a light area, so make it disappear
outlineData[i + 3] = 0; // Alpha (fully transparent)
}
}
outlineCtx.putImageData(outlineImageData, 0, 0);
// ---==== LAYER 3: FILM GRAIN / NOISE ====---
const noiseCanvas = document.createElement('canvas');
noiseCanvas.width = canvas.width;
noiseCanvas.height = canvas.height;
const noiseCtx = noiseCanvas.getContext('2d', { willReadFrequently: true });
const noiseImageData = noiseCtx.createImageData(canvas.width, canvas.height);
const noiseData = noiseImageData.data;
// Fill with random grayscale pixels
for (let i = 0; i < noiseData.length; i += 4) {
const rand = Math.floor(Math.random() * 255);
noiseData[i] = rand;
noiseData[i + 1] = rand;
noiseData[i + 2] = rand;
noiseData[i + 3] = 255;
}
noiseCtx.putImageData(noiseImageData, 0, 0);
// ---==== FINAL COMPOSITING on the main canvas ====---
// 1. Draw the posterized color base
ctx.drawImage(colorCanvas, 0, 0);
// 2. Blend the outlines on top. 'multiply' darkens the base layer with the dark lines.
ctx.globalCompositeOperation = 'multiply';
ctx.drawImage(outlineCanvas, 0, 0);
// 3. Blend the noise for a film grain effect. 'overlay' is great for this.
ctx.globalCompositeOperation = 'overlay';
ctx.globalAlpha = noiseAmount;
ctx.drawImage(noiseCanvas, 0, 0);
// Reset context state for good practice
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 1.0;
return canvas;
}
Apply Changes