You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
colorTintStr = "130,110,90", // RGB string for tint, e.g., "R,G,B"
tintStrength = 0.7, // How much tint to apply over grayscale (0-1)
noiseAmount = 0.08, // Amount of noise (0-1, where 1 is strong noise)
blurRadius = 0, // Blur radius in pixels (0 for no blur)
vignetteIntensity = 0.6, // Strength of vignette (0-1), 0 for no vignette
contrastValue = 0.7, // Contrast adjustment (1 is no change, <1 less, >1 more)
brightnessOffset = -15 // Brightness offset (e.g., -255 to 255)
) {
// Ensure originalImg is usable
if (!originalImg || typeof originalImg.naturalWidth === 'undefined' || originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
console.error("Lost Civilization Artifact Creator: Invalid or unloaded image provided.");
// Create a small placeholder canvas indicating error as per problem description of returning an element.
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 200;
errorCanvas.height = 100;
const errorCtx = errorCanvas.getContext('2d');
if (errorCtx) { // Check if context was obtained
errorCtx.fillStyle = '#404040'; // Dark gray background
errorCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
errorCtx.fillStyle = 'white';
errorCtx.font = '12px Arial';
errorCtx.textAlign = 'center';
const messages = ["Error: Invalid Image", "Could not process."];
messages.forEach((msg, index) => {
errorCtx.fillText(msg, errorCanvas.width / 2, errorCanvas.height / 2 - (messages.length-1)*7 + index * 20 -5 );
});
}
return errorCanvas;
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const width = originalImg.naturalWidth;
const height = originalImg.naturalHeight;
canvas.width = width;
canvas.height = height;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, width, height);
// Parse colorTintStr
const defaultTintValues = [130, 110, 90]; // Default tint if parsing fails
let parsedTintR, parsedTintG, parsedTintB;
try {
const parts = colorTintStr.split(',');
if (parts.length === 3) {
parsedTintR = parseFloat(parts[0].trim());
parsedTintG = parseFloat(parts[1].trim());
parsedTintB = parseFloat(parts[2].trim());
} else {
// If not 3 parts, trigger use of defaults by setting to NaN
parsedTintR = NaN; parsedTintG = NaN; parsedTintB = NaN;
}
} catch (e) {
// Error during split or trim (e.g. colorTintStr is not a string)
parsedTintR = NaN; parsedTintG = NaN; parsedTintB = NaN;
}
const tintR = (!isNaN(parsedTintR) && parsedTintR >= 0 && parsedTintR <= 255) ? parsedTintR : defaultTintValues[0];
const tintG = (!isNaN(parsedTintG) && parsedTintG >= 0 && parsedTintG <= 255) ? parsedTintG : defaultTintValues[1];
const tintB = (!isNaN(parsedTintB) && parsedTintB >= 0 && parsedTintB <= 255) ? parsedTintB : defaultTintValues[2];
// Get image data for pixel manipulation
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
const centerX = width / 2;
const centerY = height / 2;
const maxDistSq = Math.max(Math.pow(centerX, 2) + Math.pow(centerY, 2), 1); // Avoid division by zero for 1x1 image
for (let i = 0; i < data.length; i += 4) {
// 1. Original RGB
const rOrig = data[i];
const gOrig = data[i+1];
const bOrig = data[i+2];
// 2. Grayscale
let gray = 0.299 * rOrig + 0.587 * gOrig + 0.114 * bOrig;
// 3. Vignette (applied to grayscale)
if (vignetteIntensity > 0) {
const x = (i / 4) % width;
const y = Math.floor((i / 4) / width);
const dx = x - centerX;
const dy = y - centerY;
const distSq = dx * dx + dy * dy;
const vignetteAmount = Math.min(distSq / maxDistSq, 1.0); // normalized 0 to 1
const vignetteFactor = 1.0 - vignetteAmount * vignetteIntensity;
gray *= vignetteFactor;
gray = Math.max(0, Math.min(255, gray)); // Clamp
}
let r = gray;
let g = gray;
let b = gray;
// 4. Color Tint
if (tintStrength > 0) {
r = r * (1 - tintStrength) + tintR * tintStrength;
g = g * (1 - tintStrength) + tintG * tintStrength;
b = b * (1 - tintStrength) + tintB * tintStrength;
}
// 5. Contrast
if (contrastValue !== 1.0) {
r = ( (r / 255 - 0.5) * contrastValue + 0.5 ) * 255;
g = ( (g / 255 - 0.5) * contrastValue + 0.5 ) * 255;
b = ( (b / 255 - 0.5) * contrastValue + 0.5 ) * 255;
}
// 6. Brightness
if (brightnessOffset !== 0) {
r += brightnessOffset;
g += brightnessOffset;
b += brightnessOffset;
}
// Clamp after color, contrast, brightness
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// 7. Noise
if (noiseAmount > 0) {
// Monochromatic noise
const noiseVal = (Math.random() - 0.5) * 2 * noiseAmount * 128; // Noise range up to +/- 128 at noiseAmount=1
r = Math.max(0, Math.min(255, r + noiseVal));
g = Math.max(0, Math.min(255, g + noiseVal));
b = Math.max(0, Math.min(255, b + noiseVal));
}
data[i] = r;
data[i+1] = g;
data[i+2] = b;
// Alpha (data[i+3]) remains unchanged
}
// Put the modified image data back to the canvas
ctx.putImageData(imageData, 0, 0);
// 8. Blur (applied to the entire processed canvas)
if (blurRadius > 0) {
// Create a temporary canvas to hold the current state of the main canvas
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d');
if (!tempCtx) return canvas; // If context fails, return canvas as is.
// Draw current main canvas content to temporary canvas
tempCtx.drawImage(canvas, 0, 0);
// Apply blur filter on the main canvas context
ctx.filter = `blur(${blurRadius}px)`;
// Clear the main canvas and draw the image from tempCanvas onto it.
// The filter on 'ctx' will process this drawing operation, blurring the image.
ctx.clearRect(0, 0, width, height);
ctx.drawImage(tempCanvas, 0, 0);
// Reset filter on the main canvas context
ctx.filter = 'none';
}
return canvas;
}
Apply Changes