You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
numSplatterClusters = 50,
dotsPerClusterMin = 10,
dotsPerClusterMax = 50,
clusterSpreadRadius = 30,
dotRadiusMin = 1,
dotRadiusMax = 3,
dotOpacity = 0.8, // Range: 0.0 to 1.0
colorSource = "image", // Options: "image", "random", or a CSS color string (e.g., "red", "#FF0000")
colorVariation = 20 // Range: 0 to 255. Max R,G,B deviation for dots from their cluster's base color.
) {
// Ensure numeric parameters are valid numbers and within sensible ranges
numSplatterClusters = Math.max(0, Number(numSplatterClusters));
dotsPerClusterMin = Math.max(0, Number(dotsPerClusterMin));
// Ensure dotsPerClusterMax is not less than dotsPerClusterMin
dotsPerClusterMax = Math.max(dotsPerClusterMin, Number(dotsPerClusterMax));
clusterSpreadRadius = Math.max(0, Number(clusterSpreadRadius));
// Ensure dotRadiusMin is at least a small positive value for visibility
dotRadiusMin = Math.max(0.1, Number(dotRadiusMin));
// Ensure dotRadiusMax is not less than dotRadiusMin
dotRadiusMax = Math.max(dotRadiusMin, Number(dotRadiusMax));
dotOpacity = Math.max(0, Math.min(1, Number(dotOpacity)));
colorVariation = Math.max(0, Math.min(255, Number(colorVariation)));
const canvas = document.createElement('canvas');
// Robustness check for the input image
if (!originalImg || !(originalImg instanceof HTMLImageElement) || !originalImg.complete || originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
console.error("Image Splatter Paint: Invalid or unloaded image provided. Returning a placeholder canvas.");
// Fallback canvas size if image details are unavailable
canvas.width = (originalImg && originalImg.width > 0) ? originalImg.width : 300;
canvas.height = (originalImg && originalImg.height > 0) ? originalImg.height : 150;
const errorCtx = canvas.getContext('2d');
errorCtx.fillStyle = 'lightgray';
errorCtx.fillRect(0, 0, canvas.width, canvas.height);
errorCtx.fillStyle = 'black';
errorCtx.font = '16px Arial';
errorCtx.textAlign = 'center';
errorCtx.textBaseline = 'middle';
errorCtx.fillText('Invalid Image', canvas.width / 2, canvas.height / 2);
return canvas;
}
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
// Use { willReadFrequently: true } if image data sampling is frequent or on large images.
// For "image" colorSource, we read pixel data.
const ctxOptions = (colorSource === "image") ? { willReadFrequently: true } : {};
const ctx = canvas.getContext('2d', ctxOptions);
// Helper function to clamp a value between a min and max
function clamp(value, min, max) {
return Math.max(min, Math.min(value, max));
}
// Helper function to parse a CSS color string (e.g., "red", "#FF0000") into an {r, g, b} object.
function parseColor(colorString) {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = 1;
tempCanvas.height = 1;
const tempCtx = tempCanvas.getContext('2d'); // No need for willReadFrequently for 1x1
tempCtx.fillStyle = colorString;
tempCtx.fillRect(0, 0, 1, 1);
const data = tempCtx.getImageData(0, 0, 1, 1).data;
return { r: data[0], g: data[1], b: data[2] };
}
// Draw the original image onto the canvas to serve as the background
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
let originalImageData = null;
if (colorSource === "image") {
try {
originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
} catch (e) {
console.error("Image Splatter Paint: Could not get image data (e.g., canvas tainted). Defaulting colorSource to 'random'.", e);
colorSource = "random"; // Fallback if getImageData fails
}
}
let parsedFixedColor = null;
if (colorSource !== "image" && colorSource !== "random") {
try {
parsedFixedColor = parseColor(colorSource);
} catch (e) {
console.error(`Image Splatter Paint: Invalid colorSource string '${colorSource}'. Defaulting to 'random'.`, e);
colorSource = "random"; // Fallback for invalid color string
parsedFixedColor = null; // Ensure it's null if fallback occurs
}
}
for (let i = 0; i < numSplatterClusters; i++) {
// Determine the center of the current splatter cluster
const clusterCenterX = Math.random() * canvas.width;
const clusterCenterY = Math.random() * canvas.height;
let baseR, baseG, baseB; // Base color for the current cluster
if (colorSource === "image" && originalImageData) {
const x = clamp(Math.floor(clusterCenterX), 0, canvas.width - 1);
const y = clamp(Math.floor(clusterCenterY), 0, canvas.height - 1);
const pixelIndex = (y * canvas.width + x) * 4;
baseR = originalImageData[pixelIndex];
baseG = originalImageData[pixelIndex + 1];
baseB = originalImageData[pixelIndex + 2];
} else if (colorSource === "random" || (colorSource === "image" && !originalImageData) ) { // Fallback for "image" if data is null
baseR = Math.floor(Math.random() * 256);
baseG = Math.floor(Math.random() * 256);
baseB = Math.floor(Math.random() * 256);
} else { // Fixed color source
if(parsedFixedColor){
baseR = parsedFixedColor.r;
baseG = parsedFixedColor.g;
baseB = parsedFixedColor.b;
} else { // Should not be reached if fallbacks above are correct, but defensively:
baseR = 0; baseG = 0; baseB = 0; // Default to black
}
}
const numDotsInCluster = Math.floor(dotsPerClusterMin + Math.random() * (dotsPerClusterMax - dotsPerClusterMin + 1));
for (let j = 0; j < numDotsInCluster; j++) {
const angle = Math.random() * 2 * Math.PI;
// Using Math.random() for distance skews dots towards the center (density decreases with radius)
// For uniform area distribution, use: Math.sqrt(Math.random()) * clusterSpreadRadius
const distanceFromCenter = Math.random() * clusterSpreadRadius;
const dotX = clusterCenterX + distanceFromCenter * Math.cos(angle);
const dotY = clusterCenterY + distanceFromCenter * Math.sin(angle);
const dotActualRadius = dotRadiusMin + Math.random() * (dotRadiusMax - dotRadiusMin);
// Apply color variation to the base color of the cluster
const r = clamp(Math.floor(baseR + (Math.random() - 0.5) * 2 * colorVariation), 0, 255);
const g = clamp(Math.floor(baseG + (Math.random() - 0.5) * 2 * colorVariation), 0, 255);
const b = clamp(Math.floor(baseB + (Math.random() - 0.5) * 2 * colorVariation), 0, 255);
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${dotOpacity})`;
ctx.beginPath();
ctx.arc(dotX, dotY, dotActualRadius, 0, 2 * Math.PI);
ctx.fill();
}
}
return canvas;
}
Apply Changes