You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, frameWidthPercent = 0.15, frameBaseColor = '#B08D57', glyphColor = '#5D4037', glyphDensity = 0.4, edgeRoughness = 8) {
// Helper: Hex to RGB
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: 0, g: 0, b: 0 }; // Default to black if parsing fails
}
// Helper: RGB to Hex
function componentToHex(c) {
const ch = Math.max(0, Math.min(255, Math.round(c))); // Clamp and round
const hex = ch.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
// Helper: Random float between -range/2 and range/2
const R_offset = (range) => (Math.random() - 0.5) * range;
// 1. Setup canvas and calculate dimensions
const actualFrameWidth = Math.max(10, Math.max(originalImg.width, originalImg.height) * frameWidthPercent); // Min frame width 10px
const canvas = document.createElement('canvas');
canvas.width = originalImg.width + 2 * actualFrameWidth;
canvas.height = originalImg.height + 2 * actualFrameWidth;
const ctx = canvas.getContext('2d');
// 2. Fill frame background color
ctx.fillStyle = frameBaseColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 3. Add speckle texture
const baseRgb = hexToRgb(frameBaseColor);
const numSpeckles = Math.floor(canvas.width * canvas.height / 30); // Density of speckles
for (let i = 0; i < numSpeckles; i++) {
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
const offset = (Math.random() - 0.5) * 60; // RGB variation amount
const speckleR = baseRgb.r + offset;
const speckleG = baseRgb.g + offset;
const speckleB = baseRgb.b + offset;
ctx.fillStyle = rgbToHex(speckleR, speckleG, speckleB);
ctx.fillRect(x, y, Math.random() * 2 + 1, Math.random() * 2 + 1); // Speckle size
}
// 4. Draw original image with rough edges (if edgeRoughness > 0)
ctx.save();
const imgX = actualFrameWidth;
const imgY = actualFrameWidth;
const imgW = originalImg.width;
const imgH = originalImg.height;
if (edgeRoughness > 0 && actualFrameWidth > 0) {
const ER = Math.min(edgeRoughness, actualFrameWidth * 0.5); // Cap roughness
ctx.beginPath();
const pointsPerEdge = 5; // Number of intermediate points on each edge for roughness
let currentX = imgX + R_offset(ER);
let currentY = imgY + R_offset(ER);
ctx.moveTo(currentX, currentY);
// Top edge
for (let i = 1; i < pointsPerEdge; i++) {
ctx.lineTo(imgX + (imgW * i / pointsPerEdge) + R_offset(ER), imgY + R_offset(ER));
}
ctx.lineTo(imgX + imgW + R_offset(ER), imgY + R_offset(ER));
// Right edge
for (let i = 1; i < pointsPerEdge; i++) {
ctx.lineTo(imgX + imgW + R_offset(ER), imgY + (imgH * i / pointsPerEdge) + R_offset(ER));
}
ctx.lineTo(imgX + imgW + R_offset(ER), imgY + imgH + R_offset(ER));
// Bottom edge
for (let i = pointsPerEdge - 1; i >= 1; i--) {
ctx.lineTo(imgX + (imgW * i / pointsPerEdge) + R_offset(ER), imgY + imgH + R_offset(ER));
}
ctx.lineTo(imgX + R_offset(ER), imgY + imgH + R_offset(ER));
// Left edge
for (let i = pointsPerEdge - 1; i >= 1; i--) {
ctx.lineTo(imgX + R_offset(ER), imgY + (imgH * i / pointsPerEdge) + R_offset(ER));
}
ctx.closePath();
ctx.clip();
}
ctx.drawImage(originalImg, imgX, imgY, imgW, imgH);
ctx.restore();
// 5. Draw Petroglyphs
if (actualFrameWidth >= 5) { // Only draw glyphs if frame is somewhat thick
const numGlyphs = Math.floor(glyphDensity * 150); // Max 150 glyphs at density 1
const baseGlyphLineWidth = Math.max(1, Math.floor(actualFrameWidth * 0.05));
const minGlyphSize = actualFrameWidth * 0.15;
const maxGlyphSize = actualFrameWidth * 0.7;
ctx.strokeStyle = glyphColor;
ctx.fillStyle = glyphColor; // For dots or filled shapes
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
const glyphTypes = [
'line', 'arc', 'dots', 'spiral', 'wavy', 'stickfigure', 'sun'
];
for (let i = 0; i < numGlyphs; i++) {
const glyphSize = minGlyphSize + Math.random() * (maxGlyphSize - minGlyphSize);
const glyphLineWidth = Math.max(1, baseGlyphLineWidth + R_offset(baseGlyphLineWidth * 0.5));
ctx.lineWidth = glyphLineWidth;
let gx, gy;
const randBand = Math.random();
// Determine band and position (gx, gy are center of glyph)
if (randBand < 0.25) { // Top band
gx = glyphSize / 2 + Math.random() * (canvas.width - glyphSize);
gy = glyphSize / 2 + Math.random() * (actualFrameWidth - glyphSize);
} else if (randBand < 0.5) { // Bottom band
gx = glyphSize / 2 + Math.random() * (canvas.width - glyphSize);
gy = canvas.height - actualFrameWidth + glyphSize / 2 + Math.random() * (actualFrameWidth - glyphSize);
} else if (randBand < 0.75) { // Left band
gx = glyphSize / 2 + Math.random() * (actualFrameWidth - glyphSize);
gy = actualFrameWidth + glyphSize / 2 + Math.random() * (originalImg.height - glyphSize);
} else { // Right band
gx = canvas.width - actualFrameWidth + glyphSize / 2 + Math.random() * (actualFrameWidth - glyphSize);
gy = actualFrameWidth + glyphSize / 2 + Math.random() * (originalImg.height - glyphSize);
}
if (gy + glyphSize/2 > canvas.height || gy - glyphSize/2 < 0 ||
gx + glyphSize/2 > canvas.width || gx - glyphSize/2 < 0 ||
(gy - glyphSize/2 < actualFrameWidth && gy + glyphSize/2 > actualFrameWidth) || // Avoid if frame is too thin by chance
(gy - glyphSize/2 < canvas.height - actualFrameWidth && gy + glyphSize/2 > canvas.height - actualFrameWidth) ||
(gx - glyphSize/2 < actualFrameWidth && gx + glyphSize/2 > actualFrameWidth) ||
(gx - glyphSize/2 < canvas.width - actualFrameWidth && gx + glyphSize/2 > canvas.width - actualFrameWidth)
) {
if (actualFrameWidth < glyphSize) continue; // Skip if glyph is too big for the band
}
const glyphType = glyphTypes[Math.floor(Math.random() * glyphTypes.length)];
ctx.beginPath();
switch (glyphType) {
case 'line': {
const angle = Math.random() * 2 * Math.PI;
const length = glyphSize * (0.5 + Math.random() * 0.5);
ctx.moveTo(gx - Math.cos(angle) * length / 2, gy - Math.sin(angle) * length / 2);
ctx.lineTo(gx + Math.cos(angle) * length / 2, gy + Math.sin(angle) * length / 2);
break;
}
case 'arc': {
const radius = glyphSize / 2 * (0.7 + Math.random() * 0.3);
const startAngle = Math.random() * 2 * Math.PI;
const endAngle = startAngle + Math.PI * (0.5 + Math.random()); // Arc length
ctx.arc(gx, gy, radius, startAngle, endAngle);
break;
}
case 'dots': {
const numDots = 3 + Math.floor(Math.random() * 4);
for (let k = 0; k < numDots; k++) {
const dotX = gx + R_offset(glyphSize * 0.8);
const dotY = gy + R_offset(glyphSize * 0.8);
const dotRadius = glyphLineWidth * (0.5 + Math.random() * 0.5);
ctx.moveTo(dotX + dotRadius, dotY); // moveTo for arc fill
ctx.arc(dotX, dotY, dotRadius, 0, 2 * Math.PI);
}
ctx.fill(); // Use fill for dots
continue; // skip stroke for this type
}
case 'spiral': {
const maxRadius = glyphSize / 2;
const rotations = 1.5 + Math.random() * 2;
ctx.moveTo(gx, gy);
for (let k = 0; k <= 50; k++) {
const progress = k / 50;
const angle = progress * rotations * 2 * Math.PI;
const r = progress * maxRadius * (0.8 + Math.random() * 0.2);
const sx = gx + r * Math.cos(angle);
const sy = gy + r * Math.sin(angle);
ctx.lineTo(sx, sy);
}
break;
}
case 'wavy': {
const waves = 3 + Math.floor(Math.random() * 3);
const amplitude = glyphSize * 0.15;
const length = glyphSize;
const startX = gx - length / 2;
const startY = gy;
ctx.moveTo(startX, startY);
for (let k = 0; k <= waves * 2; k++) {
const t = k / (waves * 2);
const wx = startX + t * length;
const wy = startY + Math.sin(t * waves * Math.PI) * amplitude * (0.7 + Math.random()*0.6) + R_offset(amplitude*0.3);
ctx.lineTo(wx, wy);
}
break;
}
case 'stickfigure': { // Very abstract stick figure
const bodyH = glyphSize * 0.6;
const headR = glyphSize * 0.15;
const limbL = glyphSize * 0.3;
// Head
ctx.moveTo(gx + headR, gy - bodyH / 2 - headR);
ctx.arc(gx, gy - bodyH / 2 - headR, headR, 0, 2 * Math.PI);
// Body
ctx.moveTo(gx, gy - bodyH / 2);
ctx.lineTo(gx, gy + bodyH / 2);
// Arms
ctx.moveTo(gx - limbL, gy - bodyH * 0.1);
ctx.lineTo(gx, gy - bodyH * 0.25);
ctx.lineTo(gx + limbL, gy - bodyH * 0.1);
// Legs
ctx.moveTo(gx - limbL, gy + bodyH / 2 + limbL * 0.6);
ctx.lineTo(gx, gy + bodyH / 2);
ctx.lineTo(gx + limbL, gy + bodyH / 2 + limbL * 0.6);
break;
}
case 'sun': {
const coreRadius = glyphSize * 0.2;
const rayLength = glyphSize * 0.25;
const numRays = 6 + Math.floor(Math.random() * 5);
ctx.moveTo(gx + coreRadius, gy);
ctx.arc(gx, gy, coreRadius, 0, 2 * Math.PI);
for(let k=0; k < numRays; k++) {
const angle = (k / numRays) * 2 * Math.PI;
ctx.moveTo(gx + Math.cos(angle) * (coreRadius+glyphLineWidth/2), gy + Math.sin(angle) * (coreRadius+glyphLineWidth/2));
ctx.lineTo(gx + Math.cos(angle) * (coreRadius + rayLength), gy + Math.sin(angle) * (coreRadius + rayLength));
}
break;
}
}
ctx.stroke();
}
}
return canvas;
}
Apply Changes