You can edit the below JavaScript code to customize the image tool.
async function processImage(originalImg, embossStrength = 2, tintColorStr = "160,120,80", noiseAmount = 10) {
const canvas = document.createElement('canvas');
// Using { willReadFrequently: true } can optimize repeated getImageData/putImageData calls
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
// Handle cases where image might not be loaded or has no dimensions
if (width === 0 || height === 0) {
console.error("Image has zero width or height. Ensure the image is loaded and valid.");
// Return a minimal canvas to avoid breaking downstream processing if possible
canvas.width = 1;
canvas.height = 1;
// Optionally, draw a placeholder or clear it
ctx.clearRect(0,0,1,1);
return canvas;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(originalImg, 0, 0, width, height);
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
// Temporary array for grayscale values (1 byte per pixel)
// This stores the luminance of each pixel.
const grayData = new Uint8ClampedArray(width * height);
// 1. Grayscale Conversion
// Convert the image to grayscale first, as emboss and tint operations are typically
// more effective or standard on monochrome data.
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Standard luminance calculation (weights for human perception)
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
grayData[i / 4] = gray; // Store grayscale value for the current pixel
}
// Create a read-only copy of grayscale data for the emboss calculation.
// This is important because the emboss effect reads neighbor pixel values,
// so we need the original grayscale values, not ones modified in-place.
const GsCopy = new Uint8ClampedArray(grayData);
// 2. Emboss Effect
// This effect simulates depth by highlighting edges based on differences
// with neighboring pixels, creating a "carved" or "raised" look.
const embossOffset = 1; // Pixel distance for comparison (e.g., top-left neighbor)
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const currentIndex = y * width + x;
const currentGray = GsCopy[currentIndex];
// Determine coordinates of the pixel to compare with
let prevX = x - embossOffset;
let prevY = y - embossOffset;
// Handle image boundaries: clamp coordinates to be within image bounds
prevX = Math.max(0, Math.min(prevX, width - 1));
prevY = Math.max(0, Math.min(prevY, height - 1));
const prevIndex = prevY * width + prevX;
const prevGray = GsCopy[prevIndex];
// Calculate emboss value:
// Base of 128 (mid-gray) + difference scaled by strength.
// This formula creates a pseudo-3D lighting effect.
let embossValue = 128 + (currentGray - prevGray) * embossStrength;
embossValue = Math.max(0, Math.min(255, embossValue)); // Clamp to [0, 255]
// Write the monochrome embossed value to the main imageData
const dataIndex = currentIndex * 4;
data[dataIndex] = embossValue; // R
data[dataIndex + 1] = embossValue; // G
data[dataIndex + 2] = embossValue; // B
// Alpha channel (data[dataIndex + 3]) remains unchanged
}
}
// 3. Parse Tint Color String
// Default to a clay-like brownish color.
let targetR = 160, targetG = 120, targetB = 80;
if (tintColorStr) {
const parts = tintColorStr.split(',').map(s => parseInt(s.trim(), 10));
// Validate that we have 3 numbers between 0-255
if (parts.length === 3 && parts.every(p => !isNaN(p) && p >= 0 && p <= 255)) {
targetR = parts[0];
targetG = parts[1];
targetB = parts[2];
} else {
console.warn(`Invalid tintColorStr: "${tintColorStr}". Using default tint ${targetR},${targetG},${targetB}.`);
}
}
// 4. Apply Noise and Color Tint
// Iterate through the (now embossed monochrome) image data.
for (let i = 0; i < data.length; i += 4) {
let v = data[i]; // Current pixel's embossed gray value (R, G, B are same)
// Apply Noise: Adds a granular texture, like clay or stone.
if (noiseAmount > 0) {
// Generate bipolar random noise (-noiseAmount to +noiseAmount)
const noise = (Math.random() * 2 - 1) * noiseAmount;
v = Math.max(0, Math.min(255, v + noise)); // Add noise and clamp
}
// Apply Color Tint: Multiplies the grayscale value by the normalized target color.
// This colorizes the image with the chosen tint.
data[i] = Math.max(0, Math.min(255, v * (targetR / 255.0)));
data[i + 1] = Math.max(0, Math.min(255, v * (targetG / 255.0)));
data[i + 2] = Math.max(0, Math.min(255, v * (targetB / 255.0)));
// Alpha (data[i + 3]) remains unchanged
}
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 Cuneiform Inscription Photo Filter Effect is a digital tool that transforms images to emulate the appearance of ancient cuneiform inscriptions. This tool provides a unique aesthetic by applying a combination of embossing, tinting, and noise effects. Users can adjust parameters such as emboss strength, tint color, and noise amount to customize the effect to their liking. This filter can be used for artistic purposes, such as creating textures in graphic design, enhancing visual storytelling in digital art, or simply for playful modifications of photos to give them an ancient, textured look.