You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, zoomLevel = 4, lensRadius = 250, reticleColor = "rgba(50, 255, 100, 0.6)") {
const zoom = parseFloat(zoomLevel) || 4;
const radius = parseFloat(lensRadius) || 250;
// Create and configure the canvas
const canvas = document.createElement('canvas');
const width = radius * 2 + 120; // Extra padding for the border
const height = radius * 2 + 120;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// UI Setup
canvas.style.cursor = 'crosshair';
canvas.style.maxWidth = '100%';
canvas.style.height = 'auto';
canvas.style.display = 'block';
canvas.style.margin = '0 auto';
canvas.style.boxShadow = '0 15px 35px rgba(0,0,0,0.8)';
canvas.style.borderRadius = '15px';
canvas.style.touchAction = 'none'; // Prevent scrolling on touch
canvas.style.backgroundColor = '#000';
// Viewer states
let currentCX = originalImg.width / 2;
let currentCY = originalImg.height / 2;
let targetCX = currentCX;
let targetCY = currentCY;
let time = 0;
// Generate procedural "bacteria" and "floaters" to sell the microscopic effect
const floaters = [];
for(let i = 0; i < 30; i++) {
let angle = Math.random() * Math.PI * 2;
let rad = Math.random() * radius * 0.95;
floaters.push({
x: Math.cos(angle) * rad,
y: Math.sin(angle) * rad,
r: Math.random() * 8 + 2,
length: Math.random() * 15 + 5,
angle: Math.random() * Math.PI,
opacity: Math.random() * 0.4 + 0.05,
speed: Math.random() * 0.05 + 0.01,
offset: Math.random() * 100
});
}
// Input handlers mapping mouse/touch to original image coordinates
const updateTarget = (clientX, clientY) => {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / (rect.width || 1);
const scaleY = canvas.height / (rect.height || 1);
const x = (clientX - rect.left) * scaleX;
const y = (clientY - rect.top) * scaleY;
targetCX = (x / width) * originalImg.width;
targetCY = (y / height) * originalImg.height;
};
canvas.addEventListener('mousemove', (e) => updateTarget(e.clientX, e.clientY));
canvas.addEventListener('touchmove', (e) => {
if (e.touches.length > 0) updateTarget(e.touches[0].clientX, e.touches[0].clientY);
});
// Render loop
function render() {
time += 0.04;
// Smooth easing camera pan
currentCX += (targetCX - currentCX) * 0.05;
currentCY += (targetCY - currentCY) * 0.05;
// 1. Clear background
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, width, height);
// 2. Open circular mask for the lens
ctx.save();
ctx.beginPath();
ctx.arc(width/2, height/2, radius, 0, Math.PI*2);
ctx.clip();
// Dark background for transparent areas of the image
ctx.fillStyle = '#050505';
ctx.fill();
// 3. Draw the scaled and panned original image
ctx.save();
ctx.translate(width/2, height/2);
ctx.scale(zoom, zoom);
// Add a very subtle organic jiggle to the "sample"
let jx = Math.sin(time) * 1.2;
let jy = Math.cos(time * 0.7) * 1.2;
// We draw the image so that the focus point is positioned at (0, 0)
ctx.drawImage(originalImg, -currentCX + jx, -currentCY + jy);
ctx.restore();
// 4. Lighting / Vignette
const vignette = ctx.createRadialGradient(width/2, height/2, radius * 0.1, width/2, height/2, radius);
vignette.addColorStop(0, 'rgba(255, 255, 255, 0.02)');
vignette.addColorStop(0.5, 'rgba(10, 50, 40, 0.15)'); // Slight bioluminescent tint
vignette.addColorStop(0.9, 'rgba(0, 0, 0, 0.6)');
vignette.addColorStop(1, 'rgba(0, 0, 0, 1)');
ctx.fillStyle = vignette;
ctx.fill(); // fills inside the clipped circle
// 5. Draw drifting microscopic floaters (gives depth / lens dirt illusion)
for(let i = 0; i < floaters.length; i++) {
let f = floaters[i];
let dx = Math.sin(time * f.speed + f.offset) * 20;
let dy = Math.cos(time * f.speed * 0.8 + f.offset) * 20;
let wobble = Math.sin(time * f.speed) * 0.3;
ctx.save();
ctx.translate(width/2 + f.x + dx, height/2 + f.y + dy);
ctx.rotate(f.angle + wobble);
// Soft blurry shadow to simulate out-of-focus optics
ctx.shadowColor = `rgba(0, 0, 0, ${f.opacity})`;
ctx.shadowBlur = f.r * 2.5;
ctx.fillStyle = `rgba(0, 0, 0, ${f.opacity * 0.6})`;
ctx.beginPath();
ctx.ellipse(0, 0, f.length, f.r, 0, 0, Math.PI*2);
ctx.fill();
// Lighter center membrane
ctx.fillStyle = `rgba(255, 255, 255, ${f.opacity * 0.3})`;
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.ellipse(0, 0, f.length * 0.5, f.r * 0.5, 0, 0, Math.PI*2);
ctx.fill();
ctx.restore();
}
// 6. Scientific Measurement Reticle overlay
ctx.strokeStyle = reticleColor;
ctx.shadowColor = reticleColor;
ctx.shadowBlur = 4; // slight glow
ctx.lineWidth = 1;
const cross = 15;
// Central target crosses
ctx.beginPath();
ctx.moveTo(width/2 - cross, height/2);
ctx.lineTo(width/2 + cross, height/2);
ctx.moveTo(width/2, height/2 - cross);
ctx.lineTo(width/2, height/2 + cross);
ctx.stroke();
// Main axial lines extending outwards
ctx.beginPath();
ctx.moveTo(width/2 - radius, height/2);
ctx.lineTo(width/2 - cross*2, height/2);
ctx.moveTo(width/2 + cross*2, height/2);
ctx.lineTo(width/2 + radius, height/2);
ctx.moveTo(width/2, height/2 - radius);
ctx.lineTo(width/2, height/2 - cross*2);
ctx.moveTo(width/2, height/2 + cross*2);
ctx.lineTo(width/2, height/2 + radius);
ctx.stroke();
// Peripheral radial ticks
for(let i = 0; i < 360; i += 5) {
let angle = i * Math.PI / 180;
let tickLength = (i % 90 === 0) ? 25 : ((i % 10 === 0) ? 12 : 5);
ctx.beginPath();
ctx.moveTo(width/2 + Math.cos(angle) * (radius - tickLength), height/2 + Math.sin(angle) * (radius - tickLength));
ctx.lineTo(width/2 + Math.cos(angle) * radius, height/2 + Math.sin(angle) * radius);
ctx.stroke();
}
// Dashed concentric scale
ctx.setLineDash([4, 6]);
ctx.beginPath();
ctx.arc(width/2, height/2, radius * 0.5, 0, Math.PI*2);
ctx.stroke();
ctx.setLineDash([]);
ctx.shadowBlur = 0; // reset shadow
ctx.restore(); // Ends the circular lens clip
// 7. Outer eyepiece mask covering everything outside the circular lens
ctx.fillStyle = '#080808';
ctx.beginPath();
ctx.rect(0, 0, width, height);
ctx.arc(width/2, height/2, radius, 0, Math.PI*2, true);
ctx.fill();
// 8. Draw physical microscope lens rims
// Thick metal-looking bezel edge
const gradientRim = ctx.createLinearGradient(0, 0, width, height);
gradientRim.addColorStop(0, '#222');
gradientRim.addColorStop(0.5, '#050505');
gradientRim.addColorStop(1, '#333');
ctx.strokeStyle = gradientRim;
ctx.lineWidth = 18;
ctx.beginPath();
ctx.arc(width/2, height/2, radius + 9, 0, Math.PI*2);
ctx.stroke();
// Inner shiny glass rim
ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(width/2, height/2, radius, 0, Math.PI*2);
ctx.stroke();
// Request next frame
requestAnimationFrame(render);
}
// Kickstart recursive render
render();
return canvas;
}
Apply Changes