You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, brushSizeLevel = "20", densityStr = "3") {
const width = originalImg.width;
const height = originalImg.height;
// Create main canvas
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (width === 0 || height === 0) return canvas;
// Draw original image as the underlying base layer to fill any tiny gaps
ctx.drawImage(originalImg, 0, 0);
// Create offscreen canvas to sample image data
const srcCanvas = document.createElement('canvas');
srcCanvas.width = width;
srcCanvas.height = height;
const srcCtx = srcCanvas.getContext('2d');
srcCtx.drawImage(originalImg, 0, 0);
const imgData = srcCtx.getImageData(0, 0, width, height).data;
// Calculate responsive brush size
let sizeVal = parseInt(brushSizeLevel, 10);
if (isNaN(sizeVal) || sizeVal < 1) sizeVal = 20;
let density = parseFloat(densityStr);
if (isNaN(density) || density < 1) density = 3;
// Scale brush size with image size (e.g., size 20 = 1.5% of the diagonal size)
const diag = Math.sqrt(width * width + height * height);
const maxBrushSize = Math.floor(diag * (0.005 + (sizeVal / 100) * 0.05));
const actualBrushSize = Math.max(6, maxBrushSize);
// Generate unique "sponge stamps" once
const stamps = [];
const numStamps = 3;
for (let b = 0; b < numStamps; b++) {
const stamp = document.createElement('canvas');
stamp.width = actualBrushSize;
stamp.height = actualBrushSize;
const sCtx = stamp.getContext('2d');
// Draw organic base shape
sCtx.fillStyle = 'white';
sCtx.beginPath();
for (let a = 0; a < Math.PI * 2; a += 0.3) {
const r = actualBrushSize / 2.2 - Math.random() * (actualBrushSize / 6);
const x = actualBrushSize / 2 + r * Math.cos(a);
const y = actualBrushSize / 2 + r * Math.sin(a);
if (a === 0) sCtx.moveTo(x, y);
else sCtx.lineTo(x, y);
}
sCtx.closePath();
sCtx.fill();
// Cut out partial irregular holes to mimic sponge pores
sCtx.globalCompositeOperation = 'destination-out';
const numHoles = Math.floor(8 + Math.random() * 8);
for (let i = 0; i < numHoles; i++) {
sCtx.beginPath();
const hx = Math.random() * actualBrushSize;
const hy = Math.random() * actualBrushSize;
const hr = Math.random() * (actualBrushSize / 3) + 1;
sCtx.arc(hx, hy, hr, 0, Math.PI * 2);
// Removing opacity makes transparent gaps
sCtx.fillStyle = `rgba(0,0,0,${Math.random() * 0.6 + 0.4})`;
sCtx.fill();
}
stamps.push(stamp);
}
// Scratch canvas to hold the color tinted version of our current stamp
const tintCanvas = document.createElement('canvas');
tintCanvas.width = actualBrushSize;
tintCanvas.height = actualBrushSize;
const tCtx = tintCanvas.getContext('2d');
// Setup our step size according to brush size and desired coverage
const step = Math.max(2, actualBrushSize / density);
// Apply sponge filter by traversing the image
for (let y = 0; y < height + step; y += step) {
for (let x = 0; x < width + step; x += step) {
// Apply slight random jitter to the grid positions
const px = Math.floor(x + (Math.random() - 0.5) * step);
const py = Math.floor(y + (Math.random() - 0.5) * step);
if (px < 0 || px >= width || py < 0 || py >= height) continue;
// Get source pixel data
const idx = (py * width + px) * 4;
const aVal = imgData[idx + 3];
// Skip fully transparent/empty pixels
if (aVal < 10) continue;
const r = imgData[idx];
const g = imgData[idx + 1];
const b = imgData[idx + 2];
// Introduce minor color vibrancy/shift for artistic contrast
const rShift = Math.max(0, Math.min(255, r + (Math.random() - 0.5) * 25));
const gShift = Math.max(0, Math.min(255, g + (Math.random() - 0.5) * 25));
const bShift = Math.max(0, Math.min(255, b + (Math.random() - 0.5) * 25));
// Select a random sponge stamp
const stamp = stamps[Math.floor(Math.random() * stamps.length)];
// Tint the stamp canvas cleanly and quickly using standard composite mode
tCtx.globalCompositeOperation = 'source-over';
tCtx.clearRect(0, 0, actualBrushSize, actualBrushSize);
tCtx.drawImage(stamp, 0, 0);
tCtx.globalCompositeOperation = 'source-in';
tCtx.fillStyle = `rgb(${rShift},${gShift},${bShift})`;
tCtx.fillRect(0, 0, actualBrushSize, actualBrushSize);
// Stamp onto main canvas
ctx.save();
ctx.translate(px, py);
ctx.rotate(Math.random() * Math.PI * 2); // Random orientation
ctx.globalAlpha = 0.85; // Enables layered blending
ctx.drawImage(tintCanvas, -actualBrushSize / 2, -actualBrushSize / 2);
ctx.restore();
}
}
return canvas;
}
Apply Changes