You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, posterizationLevels = 5, edgeColor = "0,0,0", edgeThreshold = 10) {
const width = originalImg.width;
const height = originalImg.height;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // Added willReadFrequently for potential performance hint
if (width === 0 || height === 0) {
return canvas; // Return an empty canvas for zero-dimension images
}
// Draw the original image to the canvas to get its pixel data
ctx.drawImage(originalImg, 0, 0);
let imageData;
try {
imageData = ctx.getImageData(0, 0, width, height);
} catch (e) {
// This can happen due to tainted canvas if the image is cross-origin and CORS isn't set up.
console.error("Could not get image data, possibly due to cross-origin restrictions:", e);
// Fallback: return a canvas with the original image drawn, without the effect.
// Or throw error, or return an empty canvas. For this tool, returning original might be graceful.
// For simplicity, just return the canvas as is (with original image drawn).
return canvas;
}
const data = imageData.data;
// Parse edgeColor string "r,g,b" into an array [r, g, b]
let parsedEdgeRGB = edgeColor.split(',').map(c => parseInt(c.trim(), 10));
if (parsedEdgeRGB.length !== 3 || parsedEdgeRGB.some(isNaN)) {
console.warn("Invalid edgeColor format. Defaulting to black [0,0,0].");
parsedEdgeRGB = [0, 0, 0];
}
// Ensure RGB values are within 0-255 range
parsedEdgeRGB = parsedEdgeRGB.map(c => Math.max(0, Math.min(255, c)));
// Helper function for quantizing a single color channel
const quantizeChannel = (value, levels) => {
levels = Math.max(2, Math.floor(levels));
if (levels > 256) levels = 256; // Max 256 distinct levels (0-255), where 256 means effectively no change.
if (levels === 256) return value; // No change if 256 levels
// levels-1 segments. E.g., 2 levels -> 1 segment (0 or 255)
// 3 levels -> 2 segments (0, 127.5, 255)
const step = 255 / (levels - 1);
return Math.round(value / step) * step;
};
// 1. Quantize colors and store them (R, G, B channels only)
// Using separate arrays for R, G, B can be slightly more cache-friendly for this access pattern
// than a single interleaved array, but a single Uint8ClampedArray for quantized RGB is also fine.
const numPixels = width * height;
const quantizedR = new Uint8ClampedArray(numPixels);
const quantizedG = new Uint8ClampedArray(numPixels);
const quantizedB = new Uint8ClampedArray(numPixels);
for (let i = 0; i < data.length; i += 4) {
const pixelIndex = i / 4;
quantizedR[pixelIndex] = quantizeChannel(data[i], posterizationLevels);
quantizedG[pixelIndex] = quantizeChannel(data[i + 1], posterizationLevels);
quantizedB[pixelIndex] = quantizeChannel(data[i + 2], posterizationLevels);
// data[i+3] is alpha, will be used directly later from the original 'data' array
}
// 2. Create output image data, applying edge detection
const outputImageData = ctx.createImageData(width, height);
const outputData = outputImageData.data;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const pixelIdx = y * width + x; // Current pixel index for R,G,B arrays
const dataIdx = pixelIdx * 4; // Current pixel index for 4-channel image data arrays
const currentR_q = quantizedR[pixelIdx];
const currentG_q = quantizedG[pixelIdx];
const currentB_q = quantizedB[pixelIdx];
const currentA = data[dataIdx + 3]; // Original alpha for the current pixel
let isEdge = false;
// Check right neighbor
if (x < width - 1) {
const rightPixelIdx = pixelIdx + 1; // y * width + (x + 1)
const diff = Math.abs(currentR_q - quantizedR[rightPixelIdx]) +
Math.abs(currentG_q - quantizedG[rightPixelIdx]) +
Math.abs(currentB_q - quantizedB[rightPixelIdx]);
if (diff > edgeThreshold) {
isEdge = true;
}
}
// Check bottom neighbor
if (!isEdge && y < height - 1) { // Only check if not already determined as an edge
const bottomPixelIdx = pixelIdx + width; // (y + 1) * width + x
const diff = Math.abs(currentR_q - quantizedR[bottomPixelIdx]) +
Math.abs(currentG_q - quantizedG[bottomPixelIdx]) +
Math.abs(currentB_q - quantizedB[bottomPixelIdx]);
if (diff > edgeThreshold) {
isEdge = true;
}
}
if (isEdge) {
outputData[dataIdx] = parsedEdgeRGB[0];
outputData[dataIdx + 1] = parsedEdgeRGB[1];
outputData[dataIdx + 2] = parsedEdgeRGB[2];
outputData[dataIdx + 3] = currentA; // Preserve original alpha for the edge line
} else {
outputData[dataIdx] = currentR_q;
outputData[dataIdx + 1] = currentG_q;
outputData[dataIdx + 2] = currentB_q;
outputData[dataIdx + 3] = currentA; // Preserve original alpha
}
}
}
ctx.putImageData(outputImageData, 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 Japanese Woodblock Filter Effect Tool allows users to apply a unique artistic effect to their images, resembling traditional Japanese woodblock prints. This tool enables customization through adjustable parameters such as the number of posterization levels for color depth and edge color settings. Ideal for artists, designers, and anyone looking to transform their photos into stylized artwork, this tool can be used for creating visually striking images suitable for digital art projects, social media posts, and various decorative applications.