You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, cellSize = 20, groutColor = 'black', groutWidth = 1, cellColorVariation = 0) {
// Parameter Sanity Checks and Type Coercion
let cs = parseInt(String(cellSize), 10);
if (isNaN(cs) || cs < 2) { // Minimum cell size of 2x2 for meaningful mosaic
cs = 20;
}
cellSize = cs;
let gc = String(groutColor).trim();
if (!gc) {
gc = 'black'; // Default grout color if empty string
}
groutColor = gc;
let gw = parseFloat(String(groutWidth));
if (isNaN(gw) || gw < 0) {
gw = 1; // Default to 1 if invalid (0 is a valid choice for no grout)
}
groutWidth = gw;
let ccv = parseInt(String(cellColorVariation), 10);
if (isNaN(ccv) || ccv < 0) {
ccv = 0;
}
cellColorVariation = Math.min(ccv, 255); // Cap variation
const outputCanvas = document.createElement('canvas');
const outputCtx = outputCanvas.getContext('2d');
// Use naturalWidth/Height for intrinsic image dimensions
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
// It's assumed originalImg is loaded. If not, drawImage might not work as expected.
// Caller should ensure img.complete is true or use img.onload.
outputCanvas.width = imgWidth;
outputCanvas.height = imgHeight;
// Create a temporary canvas to hold the original image data for sampling.
// This prevents issues if originalImg is itself a canvas that might be modified,
// or if we were to sample from outputCanvas while simultaneously modifying it.
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = imgWidth;
tempCanvas.height = imgHeight;
tempCtx.drawImage(originalImg, 0, 0, tempCanvas.width, tempCanvas.height);
// --- Step 1: Fill cells with average/varied color ---
for (let y = 0; y < imgHeight; y += cellSize) {
for (let x = 0; x < imgWidth; x += cellSize) {
// Determine dimensions of the current cell (can be smaller at edges)
const currentCellW = Math.min(cellSize, imgWidth - x);
const currentCellH = Math.min(cellSize, imgHeight - y);
if (currentCellW <= 0 || currentCellH <= 0) {
continue;
}
// Get pixel data for the current cell from the temporary canvas
const imageData = tempCtx.getImageData(x, y, currentCellW, currentCellH);
const data = imageData.data;
let r_sum = 0, g_sum = 0, b_sum = 0, a_sum = 0;
let opaque_pixel_count = 0; // Count of pixels with alpha > 0 for color averaging
const total_pixel_count = currentCellW * currentCellH; // Total pixels in this block
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const a = data[i + 3];
// For RGB average, consider only pixels that are not fully transparent.
if (a > 0) {
r_sum += r;
g_sum += g;
b_sum += b;
opaque_pixel_count++;
}
a_sum += a; // Sum alpha for calculating average alpha of the cell
}
let avg_r, avg_g, avg_b, avg_a;
if (opaque_pixel_count > 0) {
avg_r = Math.round(r_sum / opaque_pixel_count);
avg_g = Math.round(g_sum / opaque_pixel_count);
avg_b = Math.round(b_sum / opaque_pixel_count);
} else { // Cell is entirely transparent (or all pixels had alpha 0)
avg_r = 0; avg_g = 0; avg_b = 0; // Color doesn't matter if alpha will be 0
}
if (total_pixel_count > 0) {
avg_a = Math.round(a_sum / total_pixel_count);
} else { // Should not be reached if currentCellW/H > 0
avg_a = 0;
}
// Apply cell color variation if specified
if (cellColorVariation > 0 && opaque_pixel_count > 0) { // Only vary color if it's not fully transparent
const vary = (colorComponent) => {
// Generate a random shift between -cellColorVariation and +cellColorVariation
const variationAmount = Math.floor((Math.random() - 0.5) * 2 * cellColorVariation);
return Math.max(0, Math.min(255, colorComponent + variationAmount));
};
avg_r = vary(avg_r);
avg_g = vary(avg_g);
avg_b = vary(avg_b);
}
// Fill the cell on the output canvas
outputCtx.fillStyle = `rgba(${avg_r}, ${avg_g}, ${avg_b}, ${avg_a / 255})`;
outputCtx.fillRect(x, y, currentCellW, currentCellH);
}
}
// --- Step 2: Draw grout lines ---
// Check if grout should be drawn (width > 0 and color is not fully transparent)
const isGroutColorTransparent = groutColor.toLowerCase() === 'transparent' ||
groutColor.match(/^rgba\([\d\s,.]+\s*,\s*(0|0\.0+)\)$/i) ||
groutColor.match(/^hsla\([\d\s%,.]+\s*,\s*(0|0\.0+)\)$/i);
if (groutWidth > 0 && !isGroutColorTransparent) {
outputCtx.strokeStyle = groutColor;
outputCtx.lineWidth = groutWidth;
// Canvas lines are drawn centered on the path a_sum.
// No special 0.5px offset for lines needed; modern canvas implementations handle this acceptably.
// For very thin lines (1px), specific pixel alignment might be desired, but for general
// mosaic grout (often >1px), this is less critical.
// Draw horizontal grout lines
for (let y_boundary = cellSize; y_boundary < imgHeight; y_boundary += cellSize) {
outputCtx.beginPath();
outputCtx.moveTo(0, y_boundary);
outputCtx.lineTo(imgWidth, y_boundary);
outputCtx.stroke();
}
// Draw vertical grout lines
for (let x_boundary = cellSize; x_boundary < imgWidth; x_boundary += cellSize) {
outputCtx.beginPath();
outputCtx.moveTo(x_boundary, 0);
outputCtx.lineTo(x_boundary, imgHeight);
outputCtx.stroke();
}
}
return outputCanvas;
}
Free Image Tool Creator
Can't find the image tool you're looking for? Create one based on your own needs now!
The Image Mosaic Glass Filter tool allows users to transform an image into a mosaic-style artwork. This tool segments the image into cells and applies a color averaging technique to create a pixelated effect, mimicking the appearance of stained glass or mosaic tiles. Users can customize parameters such as cell size, grout color, grout width, and color variation to achieve unique artistic results. This tool can be useful for graphic designers, artists, and hobbyists looking to create visually interesting versions of their images for projects, presentations, or social media.