You can edit the below JavaScript code to customize the image tool.
// Helper function to parse color strings (hex, name, rgb(), etc.) to an {r, g, b} object.
function _caveArt_parseColorToRgb(colorStr) {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
// Use willReadFrequently for potential performance hint in some browsers when using getImageData.
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (!ctx) { // Fallback if context cannot be created
console.warn("Cave art: Could not create 1x1 canvas context for parsing color.");
return { r: 0, g: 0, b: 0 };
}
ctx.fillStyle = colorStr.trim(); // Trim whitespace from color string
ctx.fillRect(0, 0, 1, 1); // Draw the color onto the 1x1 canvas
const data = ctx.getImageData(0, 0, 1, 1).data; // Read the pixel data
return { r: data[0], g: data[1], b: data[2] };
}
// Helper function to calculate the squared Euclidean distance between two RGB colors.
// Squared distance is used for efficiency as square root is not needed for comparison.
function _caveArt_colorDistanceSq(rgb1, rgb2) {
const dr = rgb1.r - rgb2.r;
const dg = rgb1.g - rgb2.g;
const db = rgb1.b - rgb2.b;
return dr * dr + dg * dg + db * db;
}
// Helper function to find the closest color in a given palette to a pixel's color.
function _caveArt_findClosestColor(pixelRgb, paletteRgbArray) {
if (!paletteRgbArray || paletteRgbArray.length === 0) {
// This case should ideally be handled by the caller by ensuring a valid palette.
// If it still happens, return black.
return { r: 0, g: 0, b: 0 };
}
let closestColor = paletteRgbArray[0];
let minDistanceSq = _caveArt_colorDistanceSq(pixelRgb, closestColor);
// Iterate through the palette to find the color with the minimum distance.
for (let i = 1; i < paletteRgbArray.length; i++) {
const distanceSq = _caveArt_colorDistanceSq(pixelRgb, paletteRgbArray[i]);
if (distanceSq < minDistanceSq) {
minDistanceSq = distanceSq;
closestColor = paletteRgbArray[i];
}
// Optimization: if an exact match is found (distance is 0), no need to check further.
if (minDistanceSq === 0) break;
}
return closestColor;
}
function processImage(originalImg,
paletteColorsStr = "sienna,firebrick,darkslategray,burlywood,saddlebrown,peru",
simplificationBlur = 3,
textureNoise = 0.05) {
// Use naturalWidth and naturalHeight for the true dimensions of the image.
const { naturalWidth: width, naturalHeight: height } = originalImg;
// Create the main canvas for the final output.
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
// Use willReadFrequently hint for contexts where getImageData/putImageData will be used often.
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (!ctx) {
console.error("Cave art: Could not get 2D context for the main canvas.");
// Return an empty (but valid) canvas if context creation fails.
return canvas;
}
// Parse the paletteColorsStr string into an array of RGB objects.
const colorNames = paletteColorsStr.split(',')
.map(name => name.trim()) // Trim whitespace from each color name/code.
.filter(name => name.length > 0); // Filter out any empty strings resulting from split.
let parsedPalette = colorNames.map(name => _caveArt_parseColorToRgb(name));
// If the parsed palette is empty (e.g., due to invalid input string), use a default fallback.
if (parsedPalette.length === 0) {
console.warn("Cave art: Palette was empty or invalid, using default fallback palette.");
parsedPalette.push(_caveArt_parseColorToRgb("sienna")); // A reddish-brown ochre color.
parsedPalette.push(_caveArt_parseColorToRgb("black")); // For dark outlines or shades.
}
let sourceForProcessing = originalImg; // This will hold the image data source (original or blurred).
// Step 1: Simplification using blur (if requested).
// This effect mimics the less detailed nature of ancient paintings.
if (simplificationBlur > 0 && typeof simplificationBlur === 'number') {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true });
if (tempCtx) {
// Apply the blur filter to the temporary canvas context.
tempCtx.filter = `blur(${simplificationBlur}px)`;
// Draw the original image onto the temporary canvas; the filter will be applied during this draw.
tempCtx.drawImage(originalImg, 0, 0, width, height);
// The temporary canvas, now containing the blurred image, becomes the source for further processing.
sourceForProcessing = tempCanvas;
} else {
console.warn("Cave art: Could not create temporary canvas context for blurring. Skipping blur.");
}
}
// Draw the source image (which may be the original or the blurred version from tempCanvas)
// onto the main canvas. This is done to get its pixel data.
ctx.drawImage(sourceForProcessing, 0, 0, width, height);
// Get the pixel data from the main canvas.
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data; // This is a Uint8ClampedArray: [R,G,B,A, R,G,B,A, ...]
// Step 2: Color Quantization.
// Iterate through each pixel and map its color to the closest color in the defined palette.
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i+1];
const b = data[i+2];
// The alpha channel (data[i+3]) is preserved from the source.
const closestColor = _caveArt_findClosestColor({ r, g, b }, parsedPalette);
data[i] = closestColor.r; // Set Red channel to palette color.
data[i+1] = closestColor.g; // Set Green channel to palette color.
data[i+2] = closestColor.b; // Set Blue channel to palette color.
}
// Step 3: Add Texture Noise (if requested).
// This simulates the roughness of a cave wall surface.
if (textureNoise > 0 && typeof textureNoise === 'number') {
// noiseFactor determines the maximum deviation from the base color.
const noiseFactor = 255 * textureNoise;
for (let i = 0; i < data.length; i += 4) {
// Generate random noise for each color channel.
// Noise ranges from -noiseFactor/2 to +noiseFactor/2.
const rNoise = (Math.random() - 0.5) * noiseFactor;
const gNoise = (Math.random() - 0.5) * noiseFactor;
const bNoise = (Math.random() - 0.5) * noiseFactor;
// Add noise to the color-quantized pixel values and clamp to [0, 255].
data[i] = Math.max(0, Math.min(255, data[i] + rNoise));
data[i+1] = Math.max(0, Math.min(255, data[i+1] + gNoise));
data[i+2] = Math.max(0, Math.min(255, data[i+2] + bNoise));
// Alpha channel (data[i+3]) remains untouched by noise.
}
}
// Put the modified pixel data back onto the main canvas.
ctx.putImageData(imageData, 0, 0);
return canvas; // Return the canvas element with the cave painting effect.
}
Free Image Tool Creator
Can't find the image tool you're looking for? Create one based on your own needs now!
The Image Cave Painting Filter Effect tool allows users to transform their images into a style reminiscent of ancient cave paintings. By implementing color quantization, simplification through blurring, and adding texture noise, the tool mimics the aesthetics of primitive art. This tool can be beneficial for artists, designers, and hobbyists looking to create unique visuals for projects, digital art, or social media content. It provides a creative way to reinterpret photographs and adds a historical, artistic touch to modern images.