You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, levels = 4, paletteStr = "") {
const canvas = document.createElement('canvas');
// Set willReadFrequently to true for potential performance improvements
// if getImageData/putImageData are called often, though for a single pass it might not be critical.
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Use naturalWidth/Height if available (intrinsic dimensions), otherwise fallback to width/height
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
if (imgWidth === 0 || imgHeight === 0) {
console.error("Image has zero dimensions. Cannot process. Ensure image is loaded.");
// Return an empty canvas (or handle error as appropriate)
canvas.width = 0;
canvas.height = 0;
return canvas;
}
canvas.width = imgWidth;
canvas.height = imgHeight;
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
let imageData;
try {
imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
// This can happen due to cross-origin restrictions if the image is from a different domain
// and CORS headers are not set.
console.error("Could not get image data. This may be due to cross-origin restrictions.", e);
// Fallback: return the canvas with the original image drawn, but no filter applied.
return canvas;
}
const data = imageData.data;
let usePalette = false;
let parsedPalette = [];
if (paletteStr && typeof paletteStr === 'string' && paletteStr.trim() !== "") {
const hexColors = paletteStr.split(',').map(c => c.trim());
parsedPalette = hexColors.map(hex => {
// Validate hex color format (e.g., #RRGGBB)
if (!/^#[0-9A-F]{6}$/i.test(hex)) {
console.warn(`Invalid hex color '${hex}' in palette. It should be in #RRGGBB format. Skipping.`);
return null;
}
// Convert hex to RGB
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return { r, g, b };
}).filter(c => c !== null); // Remove any nulls from invalid entries
if (parsedPalette.length > 0) {
usePalette = true;
} else {
console.warn("Palette string provided, but no valid colors were found. Falling back to 'levels'-based posterization.");
}
}
if (usePalette) {
// Posterize to the specified palette
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Alpha (data[i+3]) is preserved
let minDistanceSq = Infinity;
let closestColor = parsedPalette[0]; // Initialize with the first palette color
for (const palColor of parsedPalette) {
const dr = r - palColor.r;
const dg = g - palColor.g;
const db = b - palColor.b;
// Use squared Euclidean distance for efficiency (avoids Math.sqrt)
const distanceSq = dr * dr + dg * dg + db * db;
if (distanceSq < minDistanceSq) {
minDistanceSq = distanceSq;
closestColor = palColor;
}
// Optimization: if an exact match is found, no need to check further for this pixel
if (minDistanceSq === 0) {
break;
}
}
data[i] = closestColor.r;
data[i + 1] = closestColor.g;
data[i + 2] = closestColor.b;
}
} else {
// Posterize based on the number of levels
let numLevelsParam = Number(levels);
let actualLevels;
if (isNaN(numLevelsParam)) {
// The 'levels' parameter from the function signature has a default of 4.
const functionDefaultLevels = 4;
console.warn(`Invalid 'levels' parameter: '${levels}'. Using default of ${functionDefaultLevels}.`);
actualLevels = functionDefaultLevels;
} else if (numLevelsParam <= 0) {
console.warn(`'levels' parameter (${levels}) must be positive. Using a minimum of 2 levels.`);
actualLevels = 2;
} else {
// Ensure actualLevels is at least 2 (e.g., for black and white per channel)
// Math.round(0.x) = 0, Math.round(1.x) where x<5 = 1.
// So Math.max(2, round(positive_value)) ensures it's at least 2.
actualLevels = Math.max(2, Math.round(numLevelsParam));
}
// actualLevels is now guaranteed to be an integer >= 2.
// The factor determines the size of each color step.
// (actualLevels - 1) gives the number of intervals.
const factor = 255 / (actualLevels - 1);
for (let i = 0; i < data.length; i += 4) {
data[i] = Math.round(data[i] / factor) * factor;
data[i + 1] = Math.round(data[i + 1] / factor) * factor;
data[i + 2] = Math.round(data[i + 2] / factor) * factor;
// Alpha (data[i+3]) is preserved
// Clamp values to 0-255, though Math.round should keep them in range if input is 0-255.
// This is an extra safety, good practice.
data[i] = Math.min(255, Math.max(0, data[i]));
data[i+1] = Math.min(255, Math.max(0, data[i+1]));
data[i+2] = Math.min(255, Math.max(0, data[i+2]));
}
}
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 Screen Print Filter is a web-based tool that allows users to apply a screen printing effect to their images. This tool can alter images by reducing their color depth, either by using a customizable palette of colors or by specifying the number of color levels to posterize the image. Real-world use cases for this tool include creating stylistic prints for textiles, developing graphics for merchandise, enhancing illustrations for creative projects, or crafting social media visuals that stand out due to their unique color effects.