You can edit the below JavaScript code to customize the image tool.
function processImage(originalImg, stoneColorStr = "160,140,120", embossOffset = 1, embossIntensity = 1.0, noiseAmount = 0.15, contrast = 30) {
// Ensure embossOffset is an integer and non-negative
embossOffset = Math.max(0, Math.floor(embossOffset));
// Clamp contrast to a safe range to avoid division by zero or extreme factors
contrast = Math.max(-254, Math.min(254, contrast));
const canvas = document.createElement('canvas');
// Add willReadFrequently hint for potential performance improvement
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Ensure the image is loaded, otherwise width/height might be 0
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
if (imgWidth === 0 || imgHeight === 0) {
console.error("Image not loaded or has zero dimensions. Returning a 1x1 canvas.");
canvas.width = 1;
canvas.height = 1;
// Optionally, fill with a default color or throw an error
// ctx.fillStyle = 'red';
// ctx.fillRect(0,0,1,1);
return canvas;
}
canvas.width = imgWidth;
canvas.height = imgHeight;
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data; // This is a Uint8ClampedArray
const width = canvas.width;
const height = canvas.height;
// Parse stoneColorStr into normalized (0-1) R,G,B components
// Gracefully handles potential malformed strings by defaulting to black if NaN.
const stoneColorParts = stoneColorStr.split(',');
const stoneR_norm = (parseInt(stoneColorParts[0]?.trim(), 10) || 0) / 255.0;
const stoneG_norm = (parseInt(stoneColorParts[1]?.trim(), 10) || 0) / 255.0;
const stoneB_norm = (parseInt(stoneColorParts[2]?.trim(), 10) || 0) / 255.0;
// Intermediate arrays for processing
const originalGrayData = new Uint8ClampedArray(width * height); // Stores original grayscale values (0-255)
const processedGray = new Float32Array(width * height); // Stores intermediate float values for precision
// 1. Convert to Grayscale and initialize processedGray
// This loop populates originalGrayData and initializes processedGray with these values.
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const i = (y * width + x) * 4; // Index for imageData.data
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Standard luminance calculation for grayscale
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
originalGrayData[y * width + x] = gray; // Store clamped 0-255 gray value
processedGray[y * width + x] = gray; // Initialize processedGray with this (float but effectively 0-255)
}
}
// 2. Emboss Effect
// This modifies processedGray. It operates on originalGrayData for source values.
// Pixels not processed by this loop (borders if embossOffset > 0) will retain their original gray values from initialization.
if (embossOffset > 0) {
for (let y = embossOffset; y < height; y++) { // Start from y = embossOffset to have a valid neighbor
for (let x = embossOffset; x < width; x++) { // Start from x = embossOffset
const pixelIdx = y * width + x;
const currentGray = originalGrayData[pixelIdx];
// Get gray value of the pixel at (x - offset, y - offset)
const neighborGray = originalGrayData[(y - embossOffset) * width + (x - embossOffset)];
const diff = currentGray - neighborGray;
// Base of 128 for neutral gray, difference scaled by intensity creates light/shadow
let embossedValue = 128 + diff * embossIntensity;
processedGray[pixelIdx] = embossedValue; // Store float value, will be clamped later
}
}
}
// 3. Apply Contrast, Noise, and then Color Tint to each pixel in processedGray
// The contrast factor is calculated once for efficiency.
const contrastFactor = (259 * (contrast + 255)) / (255 * (259 - contrast));
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const pixelIdx = y * width + x;
let value = processedGray[pixelIdx]; // Get the (potentially embossed) gray value
// 3a. Clamp 'value' if it came from an emboss operation, as it might be outside [0,255]
// This check ensures that only values modified by emboss (and thus potentially out of 0-255 range)
// are clamped here. Original gray values (borders or if embossOffset=0) are already 0-255.
if (embossOffset > 0 && y >= embossOffset && x >= embossOffset) {
value = Math.max(0, Math.min(255, value));
}
// 3b. Contrast adjustment
if (contrast !== 0) { // Apply contrast if it's not neutral zero
value = contrastFactor * (value - 128) + 128;
value = Math.max(0, Math.min(255, value)); // Clamp after contrast
}
// 3c. Add Noise
if (noiseAmount > 0) {
// Generate noise: range is roughly [-noiseAmount * 128, +noiseAmount * 128)
const randomNoise = (Math.random() - 0.5) * 2 * noiseAmount * 128;
value += randomNoise;
value = Math.max(0, Math.min(255, value)); // Clamp after adding noise
}
// At this point, 'value' is the final grayscale intensity (0-255) for the pixel.
// 4. Color Tinting: Apply the stone color by modulating the grayscale value.
const i = pixelIdx * 4; // Index for the final imageData.data array
data[i] = value * stoneR_norm; // Uint8ClampedArray handles flooring & clamping
data[i + 1] = value * stoneG_norm;
data[i + 2] = value * stoneB_norm;
data[i + 3] = 255; // Alpha channel remains fully opaque
}
}
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 Of Mayan Stone Tablet Creator allows users to transform regular images into stylized representations resembling ancient Mayan stone tablets. This tool effectively applies an embossing effect, enhances the image’s contrast, and adds a noise texture to simulate the appearance of stone. Users can customize the stone color and intensity of the emboss effect, making it useful for creating artistic designs, educational materials, or themed graphics that evoke historical artifacts. Ideal for graphic designers, artists, and anyone interested in unique image modifications.