You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, inkColorStr = "black", paperColorStr = "white", ditherScale = 4, brightnessAdjust = 0, grainAmount = 0.1) {
// Helper function to parse CSS color strings (e.g., "red", "#FF0000", "rgb(255,0,0)")
// into an [R, G, B, A] array.
function _parseColor(colorStr) {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = 1;
tempCanvas.height = 1;
// Use { willReadFrequently: true } for potential performance improvement if available/needed
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
tempCtx.fillStyle = colorStr; // Assign color
tempCtx.fillRect(0, 0, 1, 1); // Draw a 1x1 pixel rectangle
// Get the pixel data; .data will be a Uint8ClampedArray [R, G, B, A]
const pixelData = tempCtx.getImageData(0, 0, 1, 1).data;
return [pixelData[0], pixelData[1], pixelData[2], pixelData[3]];
}
const parsedInkColor = _parseColor(inkColorStr);
const parsedPaperColor = _parseColor(paperColorStr);
// Bayer matrices definition
// M(2) is the base 2x2 matrix.
// M(4), M(8), etc., can be generated recursively or predefined.
const _bayerMatrices = {
2: [[0, 2], [3, 1]],
4: [
[0, 8, 2, 10],
[12, 4, 14, 6],
[3, 11, 1, 9],
[15, 7, 13, 5]
]
// 8x8 matrix will be generated if needed and not already present
};
// Helper function to generate the next power-of-2 Bayer matrix from the previous one.
// E.g., generates 4x4 from 2x2, or 8x8 from 4x4.
function _generateNextBayerMatrix(prevMatrix) {
const prevSize = prevMatrix.length;
const newSize = prevSize * 2;
const newMatrix = Array(newSize).fill(null).map(() => Array(newSize).fill(0));
for (let r = 0; r < prevSize; r++) {
for (let c = 0; c < prevSize; c++) {
const val = prevMatrix[r][c];
newMatrix[r][c] = 4 * val + 0;
newMatrix[r][c + prevSize] = 4 * val + 2;
newMatrix[r + prevSize][c] = 4 * val + 3;
newMatrix[r + prevSize][c + prevSize] = 4 * val + 1;
}
}
return newMatrix;
}
// Ensure 8x8 matrix is available if requested or as part of progression
if (!_bayerMatrices[8] && _bayerMatrices[4]) {
_bayerMatrices[8] = _generateNextBayerMatrix(_bayerMatrices[4]);
}
let currentBayerMatrix;
let actualDitherPatternSize; // This will hold the dimension (e.g., 2, 4, or 8) of the chosen matrix
// Validate ditherScale parameter and select the appropriate Bayer matrix
if (_bayerMatrices[ditherScale]) {
currentBayerMatrix = _bayerMatrices[ditherScale];
actualDitherPatternSize = ditherScale;
} else {
// Default to 4x4 if an unsupported/invalid scale is provided
console.warn(`Invalid ditherScale value: ${ditherScale}. Using default 4x4 matrix.`);
currentBayerMatrix = _bayerMatrices[4];
actualDitherPatternSize = 4;
}
// Setup main canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Use naturalWidth/Height if available (for <img> elements), otherwise width/height
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
canvas.width = imgWidth;
canvas.height = imgHeight;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
// Handle cases where image might not be loaded or has no dimensions
if (imgWidth === 0 || imgHeight === 0) {
console.warn("Image has zero width or height. Returning empty canvas.");
return canvas;
}
const imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
const data = imageData.data; // Pixel data: [R,G,B,A, R,G,B,A, ...]
// Process each pixel
for (let y = 0; y < imgHeight; y++) {
for (let x = 0; x < imgWidth; x++) {
const idx = (y * imgWidth + x) * 4; // Calculate index for the current pixel's R value
const r = data[idx];
const g = data[idx + 1];
const b = data[idx + 2];
// data[idx + 3] is the alpha, we'll overwrite it with ink/paper color's alpha
// 1. Convert to grayscale (luminance method)
let gray = 0.299 * r + 0.587 * g + 0.114 * b;
// 2. Apply brightness adjustment
gray += brightnessAdjust;
// 3. Apply grain/noise
if (grainAmount > 0) {
// Generate noise between -(grainAmount*50) and +(grainAmount*50)
const noise = (Math.random() - 0.5) * 2 * grainAmount * 50;
gray += noise;
}
// 4. Clamp gray value to 0-255 range
gray = Math.max(0, Math.min(255, gray));
// 5. Dithering using Bayer matrix
// Determine which element of the Bayer matrix to use based on pixel position
const bayerRow = y % actualDitherPatternSize;
const bayerCol = x % actualDitherPatternSize;
const bayerElement = currentBayerMatrix[bayerRow][bayerCol];
// Normalize the Bayer matrix element and scale it to create a threshold
// The (+0.5) helps in distributing thresholds more evenly
const ditherThreshold = ((bayerElement + 0.5) / (actualDitherPatternSize * actualDitherPatternSize)) * 255;
let targetColor;
// If pixel's gray value is greater than the dither threshold, use paper color (lighter)
// Otherwise, use ink color (darker)
if (gray > ditherThreshold) {
targetColor = parsedPaperColor;
} else {
targetColor = parsedInkColor;
}
// Set the new pixel color
data[idx] = targetColor[0]; // R
data[idx + 1] = targetColor[1]; // G
data[idx + 2] = targetColor[2]; // B
data[idx + 3] = targetColor[3]; // A (use alpha from parsed ink/paper color)
}
}
// Put the modified pixel data back onto the canvas
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 Mimeograph Filter Effect Application allows users to apply a vintage mimeograph-style filter to their images. This tool transforms standard images into stylized artwork through processes like grayscale conversion, brightness adjustments, grain addition, and dithering using Bayer matrices. It is suitable for artists, designers, and anyone looking to create unique effects for presentations, social media posts, or digital art projects by adding a nostalgic touch to their images.