You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, levelsStr = "5", saturationStr = "1.2", grainStr = "0.1") {
// 0. Parse parameters and set defaults/constraints
let levels = parseInt(levelsStr, 10);
if (isNaN(levels) || levels < 2) {
levels = 5; // Default to 5 levels if invalid or less than 2. Minimum 2 levels.
}
// For levels = 256, posterization effectively becomes a no-op, which is fine.
// Higher values of levels might lead to posterizationDivisor becoming very small;
// however, "levels" implies the number of output states, so > 256 isn't meaningful for 8-bit color.
let saturationFactor = parseFloat(saturationStr);
if (isNaN(saturationFactor)) {
saturationFactor = 1.2; // Default saturation boost
}
let grainAmount = parseFloat(grainStr);
if (isNaN(grainAmount) || grainAmount < 0) {
grainAmount = 0.1; // Default grain amount
}
grainAmount = Math.min(grainAmount, 1.0); // Cap grain amount at 1.0 for sensible range
// Helper: Clamp a value between min and max
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
// Helper: Convert RGB to HSL color space
// Input R, G, B are 0-255. Output H is [0, 1), S is [0, 1], L is [0, 1].
function rgbToHsl(r, g, b) {
r /= 255; g /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l];
}
// Helper: Convert HSL to RGB color space
// Input H is [0, 1), S is [0, 1], L is [0, 1]. Output R, G, B are 0-255.
function hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
// 1. Canvas Setup
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // Performance hint
const width = originalImg.naturalWidth || originalImg.width || 0;
const height = originalImg.naturalHeight || originalImg.height || 0;
if (width === 0 || height === 0) {
canvas.width = 0; // Ensure canvas is minimal if image is empty
canvas.height = 0;
return canvas; // Return empty canvas for empty image
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(originalImg, 0, 0, width, height);
// 2. Get Pixel Data
let imageData;
try {
imageData = ctx.getImageData(0, 0, width, height);
} catch (e) {
// Handle potential security errors if image is cross-origin and canvas becomes tainted
console.error("Error getting ImageData: ", e);
// Return a copy of the original image on a new canvas, or the original canvas
// For simplicity, just return the current canvas which has the image drawn but not processed
return canvas;
}
const data = imageData.data;
// 3. Iterate Pixels and Apply Effects
// Posterization divisor calculation:
// If levels = 1, (levels - 1) would be 0. This is prevented by `levels >= 2` check.
// If levels = 256, divisor is 255 / (256-1) = 1. This results in no change from posterization.
const posterizationDivisor = (levels > 1 && levels < 256) ? (255 / (levels - 1)) : 1;
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// a. Adjust Saturation
if (saturationFactor !== 1.0) {
let [h, s, l] = rgbToHsl(r, g, b);
s = clamp(s * saturationFactor, 0, 1); // Apply factor and clamp Saturation to [0, 1]
[r, g, b] = hslToRgb(h, s, l);
}
// b. Apply Posterization
if (posterizationDivisor !== 1) { // Only apply if effective
r = Math.round(Math.round(r / posterizationDivisor) * posterizationDivisor);
g = Math.round(Math.round(g / posterizationDivisor) * posterizationDivisor);
b = Math.round(Math.round(b / posterizationDivisor) * posterizationDivisor);
}
// Clamp intermediate RGB values (posterization can sometimes yield e.g. 255.0000001)
r = clamp(r, 0, 255);
g = clamp(g, 0, 255);
b = clamp(b, 0, 255);
// c. Add Grain
if (grainAmount > 0) {
const noise = (Math.random() - 0.5) * 255 * grainAmount;
r = clamp(r + noise, 0, 255);
g = clamp(g + noise, 0, 255);
b = clamp(b + noise, 0, 255);
}
// Ensure final values are integers for pixel data
data[i] = Math.round(r);
data[i + 1] = Math.round(g);
data[i + 2] = Math.round(b);
// Alpha channel (data[i+3]) remains unchanged
}
// 4. Put Pixel Data Back onto Canvas
ctx.putImageData(imageData, 0, 0);
// 5. Return the Processed Canvas
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 Street Art Filter Effect Tool allows users to apply a street art-inspired effect to images by adjusting properties such as color saturation, posterization levels, and graininess. This tool can enhance images by converting them into bold, stylized artwork reminiscent of street art, making it suitable for artists, graphic designers, or anyone looking to add an artistic touch to their photos. Use cases include creating graphics for social media, designing posters, or simply transforming personal photos into unique pieces of art.