You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg,
wallColor = "#D2B48C", // Color of the stone wall
imageEngraveColor = "#4A2E20", // Color of the main image carving's "bottom"
engraveDepth = 2, // Pixel offset for shadow/highlight, creating depth illusion
imageShadowTintFactor = -0.35, // How much to darken wallColor for shadow (-1 to 0)
imageHighlightTintFactor = 0.15, // How much to lighten wallColor for highlight (0 to 1)
bgGlyphColor = "#AF8F6D", // Color of small background hieroglyphs
bgGlyphDensity = 0.25, // Probability (0-1) of a cell having a background glyph
bgGlyphCellSize = 40, // Size of the grid cells for placing background glyphs
imageThreshold = 128 // Luminance threshold (0-255) to convert original image to a silhouette
) {
// Helper: Convert hex to RGB object {r, g, b}
function _hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : { r: 200, g: 200, b: 200 }; // Default to a light gray if parse fails
}
// Helper: Convert RGB components to Hex string
function _rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
}
// Helper: Shade a hex color by a percentage factor (-1 darkens, 1 lightens fully)
function _shadeColor(hexColor, percent) {
const rgb = _hexToRgb(hexColor);
let { r, g, b } = rgb;
// Adjust brightness by percent
// For darkening (percent < 0): r = r * (1 + percent)
// For lightening (percent > 0): r = r + (255 - r) * percent
if (percent < 0) {
r = Math.round(Math.max(0, r * (1 + percent)));
g = Math.round(Math.max(0, g * (1 + percent)));
b = Math.round(Math.max(0, b * (1 + percent)));
} else {
r = Math.round(Math.min(255, r + (255 - r) * percent));
g = Math.round(Math.min(255, g + (255 - g) * percent));
b = Math.round(Math.min(255, b + (255 - b) * percent));
}
return _rgbToHex(r, g, b);
}
// Helper: Draw simple hieroglyph-like shapes
function _drawSimpleGlyph(ctx, x, y, size, color) {
ctx.save();
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = Math.max(1, Math.round(size / 15));
const type = Math.floor(Math.random() * 4);
ctx.beginPath();
// Effective drawing area for glyph (centered in cell)
const S = size * 0.75;
const dX = x + (size - S) / 2;
const dY = y + (size - S) / 2;
if (type === 0) { // Eye of Horus (simplified)
ctx.ellipse(dX + S / 2, dY + S / 2, S / 2.2, S / 4.5, 0, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(dX + S / 2, dY + S / 2, S / 10, 0, 2 * Math.PI);
ctx.fill();
} else if (type === 1) { // Wavy Lines (water)
ctx.moveTo(dX, dY + S / 3);
ctx.bezierCurveTo(dX + S / 3, dY, dX + 2 * S / 3, dY + S / 1.5, dX + S, dY + S / 3);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(dX, dY + S * (2/3));
ctx.bezierCurveTo(dX + S / 3, dY + S / 2, dX + 2 * S / 3, dY + S, dX + S, dY + S * (2/3));
ctx.stroke();
} else if (type === 2) { // Ankh-like cross
ctx.moveTo(dX + S / 2, dY + S * 0.2);
ctx.lineTo(dX + S / 2, dY + S);
ctx.stroke(); // Vertical line segment by segment
ctx.beginPath();
ctx.moveTo(dX, dY + S * 0.4);
ctx.lineTo(dX + S, dY + S * 0.4);
ctx.stroke(); // Horizontal line
} else { // Scarab beetle (simplified oval with line)
ctx.ellipse(dX + S / 2, dY + S / 2, S / 2.1, S / 1.6, 0, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(dX + S / 2, dY + S * 0.1);
ctx.lineTo(dX + S / 2, dY + S * 0.9);
ctx.stroke();
}
ctx.restore();
}
const canvas = document.createElement('canvas');
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
const ctx = canvas.getContext('2d');
// 1. Draw Wall Base Color
ctx.fillStyle = wallColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 2. Draw Wall Noise/Texture (subtle)
const numNoisePatches = Math.floor((canvas.width * canvas.height) / 60);
const wallRgbBase = _hexToRgb(wallColor);
for (let i = 0; i < numNoisePatches; i++) {
const nx = Math.random() * canvas.width;
const ny = Math.random() * canvas.height;
const nSize = Math.random() * 3 + 1; // Small patches 1-4px
const lightnessOffset = (Math.random() - 0.5) * 0.1; // +/- 10% lightness fluctuation
const r = Math.max(0, Math.min(255, Math.round(wallRgbBase.r * (1 + lightnessOffset))));
const g = Math.max(0, Math.min(255, Math.round(wallRgbBase.g * (1 + lightnessOffset))));
const b = Math.max(0, Math.min(255, Math.round(wallRgbBase.b * (1 + lightnessOffset))));
ctx.fillStyle = `rgba(${r},${g},${b},0.35)`; // Semi-transparent
ctx.fillRect(nx, ny, nSize, nSize);
}
// 3. Draw Background Hieroglyphs Pattern
for (let y = 0; y < canvas.height - bgGlyphCellSize / 2; y += bgGlyphCellSize) {
for (let x = 0; x < canvas.width - bgGlyphCellSize / 2; x += bgGlyphCellSize) {
if (Math.random() < bgGlyphDensity) {
const jitterX = (Math.random() - 0.5) * (bgGlyphCellSize / 2.5);
const jitterY = (Math.random() - 0.5) * (bgGlyphCellSize / 2.5);
const glyphX = x + jitterX;
const glyphY = y + jitterY;
const glyphSize = bgGlyphCellSize * (0.55 + Math.random() * 0.35); // Size variation
_drawSimpleGlyph(ctx, glyphX, glyphY, glyphSize, bgGlyphColor);
}
}
}
// 4. Prepare Main Image Mask (silhouette from original image)
const maskCanvas = document.createElement('canvas');
maskCanvas.width = canvas.width;
maskCanvas.height = canvas.height;
const maskCtx = maskCanvas.getContext('2d', { willReadFrequently: true });
maskCtx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
const imgData = maskCtx.getImageData(0, 0, canvas.width, canvas.height);
const data = imgData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i], g = data[i+1], b = data[i+2], a = data[i+3];
const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
if (luminance < imageThreshold && a > 128) { // Darker and reasonably opaque parts form the carving
data[i] = 255; data[i+1] = 255; data[i+2] = 255; data[i+3] = 255; // Opaque white for mask
} else {
data[i+3] = 0; // Transparent
}
}
maskCtx.putImageData(imgData, 0, 0);
// 5. Draw Engraved Main Image using the Mask
const tempColoredMaskCanvas = document.createElement('canvas');
tempColoredMaskCanvas.width = canvas.width;
tempColoredMaskCanvas.height = canvas.height;
const tempCtx = tempColoredMaskCanvas.getContext('2d');
const shadowEngraveColor = _shadeColor(wallColor, imageShadowTintFactor);
const highlightEngraveColor = _shadeColor(wallColor, imageHighlightTintFactor);
const drawMaskWithColorAndOffset = (targetCtx, offsetX, offsetY, color) => {
tempCtx.clearRect(0, 0, tempColoredMaskCanvas.width, tempColoredMaskCanvas.height);
tempCtx.fillStyle = color;
tempCtx.fillRect(0, 0, tempColoredMaskCanvas.width, tempColoredMaskCanvas.height);
tempCtx.globalCompositeOperation = 'destination-in';
tempCtx.drawImage(maskCanvas, 0, 0);
tempCtx.globalCompositeOperation = 'source-over';
targetCtx.drawImage(tempColoredMaskCanvas, offsetX, offsetY);
};
const d = Math.max(1, Math.round(engraveDepth)); // Ensure depth is at least 1 if > 0
if (d > 0) {
// Draw highlight layer (lighter wallColor, offset towards "light source")
drawMaskWithColorAndOffset(ctx, -d, -d, highlightEngraveColor);
// Draw shadow layer (darker wallColor, offset away from "light source")
drawMaskWithColorAndOffset(ctx, d, d, shadowEngraveColor);
}
// Draw main engrave fill (the "bottom" of the carving, no offset)
drawMaskWithColorAndOffset(ctx, 0, 0, imageEngraveColor);
return canvas;
}
Apply Changes