You can edit the below JavaScript code to customize the image tool.
Apply Changes
/**
* Creates a "weirdcore" image of a sci-fi universe as if captured from an old,
* distorted doorbell camera. This function applies a fisheye (barrel distortion)
* effect, desaturates the image, adds grain, a color tint, a vignette, and a
* circular bezel to simulate the look of a peephole camera.
*
* @param {HTMLImageElement} originalImg The original image to process.
* @param {number} distortion The strength of the fisheye effect (0 for none, 0.5 is a good start). Defaults to 0.5.
* @param {number} grain The amount of noise/grain to add (0-255). Defaults to 30.
* @param {number} saturation The color saturation level (0 is grayscale, 1 is original color). Defaults to 0.2.
* @param {string} tintColor The color of the tint overlay (e.g., '#00ff44'). Defaults to '#00ff44'.
* @param {number} tintStrength The opacity of the color tint (0 to 1). Defaults to 0.15.
* @param {number} vignette The darkness of the vignette at the edges (0 to 1). Defaults to 0.6.
* @returns {HTMLCanvasElement} A canvas element displaying the processed image.
*/
function processImage(originalImg, distortion = 0.5, grain = 30, saturation = 0.2, tintColor = '#00ff44', tintStrength = 0.15, vignette = 0.6) {
// 1. SETUP
// Use the smaller dimension to create a square canvas
const size = Math.min(originalImg.width, originalImg.height);
const canvas = document.createElement('canvas');
canvas.width = canvas.height = size;
const ctx = canvas.getContext('2d');
if (!ctx) {
console.error("Canvas context is not available.");
return document.createElement('div').innerText = "Could not process image.";
}
const centerX = size / 2;
const centerY = size / 2;
const radius = size / 2;
// 2. FISHEYE/BARREL DISTORTION PASS
// This pass remaps pixels from the source image to the destination canvas
// to create a stretched, rounded effect.
// Create a temporary canvas to get source image data without affecting the original
const tempCanvas = document.createElement('canvas');
tempCanvas.width = originalImg.width;
tempCanvas.height = originalImg.height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(originalImg, 0, 0);
const srcData = tempCtx.getImageData(0, 0, originalImg.width, originalImg.height);
const srcPixels = srcData.data;
const destData = ctx.createImageData(size, size);
const destPixels = destData.data;
const srcCenterX = originalImg.width / 2;
const srcCenterY = originalImg.height / 2;
const srcRadius = Math.min(srcCenterX, srcCenterY);
// Distortion strength (k) should be non-negative for barrel distortion
const k = Math.max(0, distortion);
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const dx = x - centerX;
const dy = y - centerY;
const dist = Math.sqrt(dx * dx + dy * dy);
const destIndex = (y * size + x) * 4;
if (dist < radius) {
const angle = Math.atan2(dy, dx);
// Normalize distance from center [0, 1]
const normalizedDist = dist / radius;
// Apply inverse barrel distortion formula to find the source pixel
const distortedNormDist = Math.pow(normalizedDist, 1.0 / (1.0 + k));
const srcDist = distortedNormDist * srcRadius;
// Calculate source coordinates
let srcX = Math.floor(srcCenterX + srcDist * Math.cos(angle));
let srcY = Math.floor(srcCenterY + srcDist * Math.sin(angle));
// Clamp coordinates to be within source bounds
srcX = Math.max(0, Math.min(originalImg.width - 1, srcX));
srcY = Math.max(0, Math.min(originalImg.height - 1, srcY));
const srcIndex = (srcY * originalImg.width + srcX) * 4;
// Copy RGBA values
destPixels[destIndex] = srcPixels[srcIndex];
destPixels[destIndex + 1] = srcPixels[srcIndex + 1];
destPixels[destIndex + 2] = srcPixels[srcIndex + 2];
destPixels[destIndex + 3] = 255;
} else {
// Pixels outside the circle are transparent
destPixels[destIndex + 3] = 0;
}
}
}
ctx.putImageData(destData, 0, 0);
// 3. COLOR GRADING PASS
// Use an intermediate canvas to apply CSS-like filters
const filterCanvas = document.createElement('canvas');
filterCanvas.width = filterCanvas.height = size;
const filterCtx = filterCanvas.getContext('2d');
// Desaturate, increase contrast, and slightly darken the image
filterCtx.filter = `saturate(${saturation}) contrast(1.2) brightness(0.9)`;
filterCtx.drawImage(canvas, 0, 0); // Filter is applied on draw
ctx.clearRect(0, 0, size, size); // Clear main canvas
ctx.drawImage(filterCanvas, 0, 0); // Draw filtered image back
// 4. COLOR TINT PASS
if (tintStrength > 0) {
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = tintColor;
ctx.globalAlpha = tintStrength;
ctx.fillRect(0, 0, size, size);
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'source-over';
}
// 5. GRAIN/NOISE PASS
if (grain > 0) {
const imageData = ctx.getImageData(0, 0, size, size);
const pixels = imageData.data;
for (let i = 0; i < pixels.length; i += 4) {
// Only apply grain to non-transparent pixels (inside the circle)
if (pixels[i + 3] > 0) {
const noise = (Math.random() - 0.5) * grain;
pixels[i] = Math.max(0, Math.min(255, pixels[i] + noise)); // R
pixels[i + 1] = Math.max(0, Math.min(255, pixels[i + 1] + noise)); // G
pixels[i + 2] = Math.max(0, Math.min(255, pixels[i + 2] + noise)); // B
}
}
ctx.putImageData(imageData, 0, 0);
}
// 6. VIGNETTE PASS
if (vignette > 0) {
const gradient = ctx.createRadialGradient(centerX, centerY, radius * 0.3, centerX, centerY, radius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${vignette})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, size, size);
}
// 7. DOORBELL BEZEL PASS
// Draw a thick, dark border to complete the doorbell camera look
ctx.lineWidth = size * 0.1;
const bezelGradient = ctx.createLinearGradient(0, 0, size, size);
bezelGradient.addColorStop(0, '#444');
bezelGradient.addColorStop(0.5, '#111');
bezelGradient.addColorStop(1, '#222');
ctx.strokeStyle = bezelGradient;
ctx.beginPath();
ctx.arc(centerX, centerY, radius - ctx.lineWidth / 2, 0, Math.PI * 2);
ctx.stroke();
return canvas;
}
Apply Changes