You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, threshold = 128, blackColorStr = "0,0,0", whiteColorStr = "255,255,255") {
// Helper function to parse color strings like "r,g,b"
function parseColor(colorStr, defaultColor) {
// Ensure colorStr is a string before attempting to split
if (typeof colorStr !== 'string') {
return defaultColor;
}
const parts = colorStr.split(',').map(s => {
const num = parseInt(s.trim(), 10);
// Check if num is NaN, or outside the 0-255 range for color components
return (isNaN(num) || num < 0 || num > 255) ? NaN : num;
});
// Check if we have exactly 3 parts and all are valid numbers
if (parts.length === 3 && parts.every(p => !isNaN(p))) {
return parts; // Returns [r, g, b]
}
return defaultColor; // Return default if parsing failed or input was invalid
}
const blackColor = parseColor(blackColorStr, [0, 0, 0]);
const whiteColor = parseColor(whiteColorStr, [255, 255, 255]);
// Use naturalWidth/Height if available, otherwise fallback to width/height
// This ensures we get the original dimensions even if the image is styled differently on the page
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
if (width === 0 || height === 0) {
// Return an empty canvas if image dimensions are invalid
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = width;
emptyCanvas.height = height;
return emptyCanvas;
}
// Create a temporary canvas to draw the original image and get its pixel data
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(originalImg, 0, 0, width, height);
let imageData;
try {
imageData = tempCtx.getImageData(0, 0, width, height);
} catch (e) {
console.error("Error getting image data:", e);
// Create a placeholder canvas indicating an error
const errorCanvas = document.createElement('canvas');
errorCanvas.width = width;
errorCanvas.height = height;
const errorCtx = errorCanvas.getContext('2d');
if (errorCtx) {
errorCtx.fillStyle = 'rgba(200, 200, 200, 0.5)';
errorCtx.fillRect(0, 0, width, height);
errorCtx.fillStyle = 'red';
errorCtx.textAlign = 'center';
errorCtx.textBaseline = 'middle';
errorCtx.font = `${Math.min(width, height) / 10}px Arial`;
errorCtx.fillText("Error processing image", width / 2, height / 2);
}
return errorCanvas;
}
const pixels = imageData.data;
// Create a 2D array to store grayscale values. Using Float32Array for precision with error accumulation.
const grayscaleGrid = new Array(height);
for (let y = 0; y < height; y++) {
grayscaleGrid[y] = new Float32Array(width);
for (let x = 0; x < width; x++) {
const idx = (y * width + x) * 4;
const r = pixels[idx];
const g = pixels[idx + 1];
const b = pixels[idx + 2];
// Standard luminance calculation for grayscale
grayscaleGrid[y][x] = 0.299 * r + 0.587 * g + 0.114 * b;
}
}
// Create the output canvas and its ImageData object
const outCanvas = document.createElement('canvas');
outCanvas.width = width;
outCanvas.height = height;
const outCtx = outCanvas.getContext('2d');
const outputImageData = outCtx.createImageData(width, height);
const outputPixels = outputImageData.data;
// Apply Floyd-Steinberg dithering
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// Get the current grayscale value (which includes diffused errors from previous pixels)
const currentGrayValue = grayscaleGrid[y][x];
// Clamp this value to the 0-255 range for thresholding
const oldPixelClamped = Math.max(0, Math.min(255, currentGrayValue));
let newPixelGrayscaleEquivalent; // This will be 0 for black, 255 for white after quantization
const outputIdx = (y * width + x) * 4; // Index for the output pixel array
if (oldPixelClamped < threshold) {
outputPixels[outputIdx] = blackColor[0]; // Red channel
outputPixels[outputIdx + 1] = blackColor[1]; // Green channel
outputPixels[outputIdx + 2] = blackColor[2]; // Blue channel
outputPixels[outputIdx + 3] = 255; // Alpha channel (opaque)
newPixelGrayscaleEquivalent = 0; // Target grayscale value for black
} else {
outputPixels[outputIdx] = whiteColor[0]; // Red channel
outputPixels[outputIdx + 1] = whiteColor[1]; // Green channel
outputPixels[outputIdx + 2] = whiteColor[2]; // Blue channel
outputPixels[outputIdx + 3] = 255; // Alpha channel (opaque)
newPixelGrayscaleEquivalent = 255; // Target grayscale value for white
}
// Calculate the quantization error using the non-clamped currentGrayValue
// This ensures accurate error distribution.
const quantError = currentGrayValue - newPixelGrayscaleEquivalent;
// Distribute the error to neighboring pixels according to Floyd-Steinberg
// ( Affects pixels to the right, and in the next row )
if (x + 1 < width) {
grayscaleGrid[y][x + 1] += quantError * 7 / 16;
}
if (y + 1 < height) {
if (x - 1 >= 0) {
grayscaleGrid[y + 1][x - 1] += quantError * 3 / 16;
}
grayscaleGrid[y + 1][x] += quantError * 5 / 16;
if (x + 1 < width) {
grayscaleGrid[y + 1][x + 1] += quantError * 1 / 16;
}
}
}
}
// Put the processed pixel data onto the output canvas
outCtx.putImageData(outputImageData, 0, 0);
return outCanvas;
}
Free Image Tool Creator
Can't find the image tool you're looking for? Create one based on your own needs now!
The Image Stipple Filter Application is a tool that transforms images into stippled artwork by applying a dithering effect. Users can adjust the threshold levels and customize the colors used for stippling. This tool is particularly useful for artists and designers looking to give images a unique, graphic quality or for creating art that mimics traditional printing techniques. Potential applications include enhancing visual content for graphic design, producing engaging social media posts, or creating distinctive prints and illustrations.