You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, frameSize = 50, baseColorStr = "245,222,179", noiseIntensity = 20, stainOpacity = 0.08, edgeRoughness = 25, edgeDarkness = 0.5, imageShadowOpacity = 0.3) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Ensure parameters are numbers
frameSize = Number(frameSize);
noiseIntensity = Number(noiseIntensity);
stainOpacity = Number(stainOpacity);
edgeRoughness = Number(edgeRoughness);
edgeDarkness = Number(edgeDarkness);
imageShadowOpacity = Number(imageShadowOpacity);
// Parse baseColorStr
const colorParts = baseColorStr.split(',');
const baseR = parseInt(colorParts[0].trim(), 10);
const baseG = parseInt(colorParts[1].trim(), 10);
const baseB = parseInt(colorParts[2].trim(), 10);
canvas.width = originalImg.width + frameSize * 2;
canvas.height = originalImg.height + frameSize * 2;
ctx.save(); // Save initial clean state
// Helper function to define the irregular parchment shape
function defineParchmentShape(currentCtx, x, y, width, height, roughness, segmentLength = 30) {
currentCtx.beginPath();
const r = () => (Math.random() - 0.5) * roughness * 2; // Main perpendicular offset
const rs = () => (Math.random() - 0.5) * roughness * 0.5; // Smaller tangential offset
// Top-left corner
currentCtx.moveTo(x + r(), y + r());
// Top edge
for (let curX = x + segmentLength; curX < x + width; curX += segmentLength) {
currentCtx.lineTo(curX + rs(), y + r());
}
currentCtx.lineTo(x + width - r(), y + r()); // Top-right corner
// Right edge
for (let curY = y + segmentLength; curY < y + height; curY += segmentLength) {
currentCtx.lineTo(x + width - r(), curY + rs());
}
currentCtx.lineTo(x + width - r(), y + height - r()); // Bottom-right corner
// Bottom edge
for (let curX = x + width - segmentLength; curX > x; curX -= segmentLength) {
currentCtx.lineTo(curX + rs(), y + height - r());
}
currentCtx.lineTo(x + r(), y + height - r()); // Bottom-left corner
// Left edge
for (let curY = y + height - segmentLength; curY > y; curY -= segmentLength) {
currentCtx.lineTo(x + r(), curY + rs());
}
currentCtx.closePath();
}
// 1. Define and fill the parchment shape
defineParchmentShape(ctx, 0, 0, canvas.width, canvas.height, edgeRoughness);
ctx.fillStyle = `rgb(${baseR},${baseG},${baseB})`;
ctx.fill();
// 2. Clip to this shape for subsequent effects
ctx.clip();
// 3. Noise Texture (Clipped)
if (noiseIntensity > 0) {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
// Only apply noise to opaque pixels (i.e., inside the clipped parchment shape)
if (data[i + 3] > 0) {
const rand = (Math.random() - 0.5) * noiseIntensity;
data[i] = Math.max(0, Math.min(255, data[i] + rand));
data[i+1] = Math.max(0, Math.min(255, data[i+1] + rand));
data[i+2] = Math.max(0, Math.min(255, data[i+2] + rand));
}
}
ctx.putImageData(imageData, 0, 0);
}
// 4. Mottled Stains (Clipped)
if (stainOpacity > 0) {
const stainR = Math.max(0, baseR - 40); // Darker shade of base color
const stainG = Math.max(0, baseG - 40);
const stainB = Math.max(0, baseB - 40);
ctx.fillStyle = `rgba(${stainR}, ${stainG}, ${stainB}, ${stainOpacity})`;
const numStains = Math.floor((canvas.width * canvas.height) / 50000) + 5; // Scale stains with size
for (let i = 0; i < numStains; i++) {
const sx = Math.random() * canvas.width;
const sy = Math.random() * canvas.height;
const sRadiusX = (Math.random() * 0.2 + 0.15) * canvas.width; // Large, soft stains
const sRadiusY = (Math.random() * 0.2 + 0.15) * canvas.height;
ctx.beginPath();
ctx.ellipse(sx, sy, sRadiusX, sRadiusY, Math.random() * Math.PI * 2, 0, Math.PI * 2);
ctx.fill();
}
}
// 5. Edge Darkening/Vignette (Clipped)
if (edgeDarkness > 0) {
const gradientCenterX = canvas.width / 2;
const gradientCenterY = canvas.height / 2;
// Make inner radius proportional to the smallest dimension, ensuring some central brightness
const innerRadius = Math.min(canvas.width, canvas.height) * 0.15;
// Outer radius should reach the corners
const outerRadius = Math.sqrt(Math.pow(canvas.width / 2, 2) + Math.pow(canvas.height / 2, 2));
const gradient = ctx.createRadialGradient(gradientCenterX, gradientCenterY, innerRadius, gradientCenterX, gradientCenterY, outerRadius);
gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Transparent center
gradient.addColorStop(1, `rgba(0,0,0,${edgeDarkness})`); // Darker edges
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.restore(); // Remove clipping, parchment is fully rendered.
// 6. Draw the Original Image
const imgX = frameSize;
const imgY = frameSize;
const imgWidth = originalImg.width;
const imgHeight = originalImg.height;
if (imageShadowOpacity > 0) {
ctx.save();
ctx.shadowColor = `rgba(0, 0, 0, ${imageShadowOpacity})`;
ctx.shadowBlur = Math.max(5, frameSize * 0.25);
ctx.shadowOffsetX = Math.max(2, frameSize * 0.1);
ctx.shadowOffsetY = Math.max(2, frameSize * 0.1);
ctx.drawImage(originalImg, imgX, imgY, imgWidth, imgHeight);
ctx.restore();
} else {
ctx.drawImage(originalImg, imgX, imgY, imgWidth, imgHeight);
}
// Optional: Draw a very thin, dark line around the image to make it "sit" better
ctx.strokeStyle = 'rgba(0,0,0,0.15)';
ctx.lineWidth = 1;
ctx.strokeRect(imgX - 0.5, imgY - 0.5, imgWidth + 1, imgHeight + 1);
return canvas;
}
Apply Changes