You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, themeColor = "#00ff00", scannerSpeed = 2) {
// Parse parameters
if (typeof scannerSpeed === 'string') scannerSpeed = parseFloat(scannerSpeed) || 2;
if (typeof themeColor !== 'string') themeColor = "#00ff00";
// Main container
const container = document.createElement('div');
container.style.position = 'relative';
container.style.fontFamily = 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif';
container.style.display = 'inline-block';
container.style.maxWidth = '100%';
container.style.borderRadius = '8px';
container.style.overflow = 'hidden';
container.style.boxShadow = '0 10px 25px rgba(0,0,0,0.3)';
container.style.background = '#111';
// Image canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
ctx.drawImage(originalImg, 0, 0);
canvas.style.display = 'block';
canvas.style.maxWidth = '100%';
canvas.style.height = 'auto';
container.appendChild(canvas);
// Scanner Overlay
const overlay = document.createElement('div');
overlay.style.position = 'absolute';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.background = 'rgba(0, 0, 0, 0.3)';
overlay.style.display = 'flex';
overlay.style.flexDirection = 'column';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
overlay.style.color = '#fff';
overlay.style.zIndex = '10';
overlay.style.transition = 'all 0.5s ease';
// Scan Line
const scanLine = document.createElement('div');
scanLine.style.position = 'absolute';
scanLine.style.top = '0';
scanLine.style.left = '0';
scanLine.style.width = '100%';
scanLine.style.height = '3px';
scanLine.style.backgroundColor = themeColor;
scanLine.style.boxShadow = `0 0 15px ${themeColor}, 0 0 30px ${themeColor}`;
scanLine.style.zIndex = '11';
overlay.appendChild(scanLine);
// Status Text Badge
const statusText = document.createElement('div');
statusText.innerText = 'Initializing Scanner...';
statusText.style.fontSize = 'clamp(14px, 3vw, 20px)';
statusText.style.fontWeight = 'bold';
statusText.style.color = '#fff';
statusText.style.textShadow = '0 2px 4px rgba(0,0,0,0.8)';
statusText.style.background = 'rgba(0,0,0,0.6)';
statusText.style.padding = '10px 20px';
statusText.style.borderRadius = '30px';
statusText.style.backdropFilter = 'blur(4px)';
statusText.style.border = `1px solid ${themeColor}`;
statusText.style.boxShadow = `0 0 10px rgba(0,0,0,0.5)`;
overlay.appendChild(statusText);
container.appendChild(overlay);
// Scanner Animation Loop
let pos = 0;
let dir = 1;
let isScanning = true;
let lastTime = 0;
const animate = (time) => {
if (!isScanning) return;
if (!lastTime) lastTime = time;
let delta = time - lastTime;
if (delta > 100) delta = 16; // limit jump on inactive tabs
lastTime = time;
pos += dir * scannerSpeed * (delta / 16);
if (pos >= 100) {
pos = 100;
dir = -1;
} else if (pos <= 0) {
pos = 0;
dir = 1;
}
scanLine.style.top = `${pos}%`;
requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
// Process API requests asynchronously
(async () => {
try {
// Compress large images to ensure fast API upload
let maxSize = 800; // max dimension for trace.moe
let uploadCanvas = canvas;
if (canvas.width > maxSize || canvas.height > maxSize) {
statusText.innerText = 'Optimizing Image...';
uploadCanvas = document.createElement('canvas');
let ratio = Math.min(maxSize / canvas.width, maxSize / canvas.height);
uploadCanvas.width = canvas.width * ratio;
uploadCanvas.height = canvas.height * ratio;
uploadCanvas.getContext('2d').drawImage(canvas, 0, 0, uploadCanvas.width, uploadCanvas.height);
}
statusText.innerText = 'Scanning visual database...';
const blob = await new Promise(resolve => uploadCanvas.toBlob(resolve, 'image/jpeg', 0.8));
const formData = new FormData();
formData.append('image', blob);
// Fetch match from trace.moe (Public Anime Screenshot Database)
const response = await fetch('https://api.trace.moe/search', {
method: 'POST',
body: formData
});
const data = await response.json();
isScanning = false;
scanLine.style.display = 'none';
if (data && data.result && data.result.length > 0) {
const bestMatch = data.result[0];
const similarity = (bestMatch.similarity * 100).toFixed(1);
if (bestMatch.similarity < 0.75) {
showError(`No confident match found. (Highest Likelihood: ${similarity}%)`);
return;
}
statusText.innerText = 'Match found! Fetching metadata...';
// Fetch rich metadata using Anilist GraphQL API
const query = `
query ($id: Int) {
Media (id: $id, type: ANIME) {
title {
romaji
english
native
}
coverImage {
large
}
format
}
}
`;
const variables = { id: bestMatch.anilist };
let title = bestMatch.filename;
let coverUrl = '';
let format = '';
try {
const aniRes = await fetch('https://graphql.anilist.co', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ query, variables })
});
const aniData = await aniRes.json();
if (aniData && aniData.data && aniData.data.Media) {
const media = aniData.data.Media;
const titles = media.title;
title = titles.english || titles.romaji || titles.native;
coverUrl = media.coverImage.large;
format = media.format || 'Unknown';
}
} catch (e) {
console.error('Anilist API error', e);
}
// Prepare results UI
overlay.style.background = 'rgba(0, 0, 0, 0.85)';
overlay.style.backdropFilter = 'blur(8px)';
const resultDiv = document.createElement('div');
resultDiv.style.textAlign = 'center';
resultDiv.style.width = '90%';
resultDiv.style.maxWidth = '450px';
resultDiv.style.background = 'rgba(255,255,255,0.08)';
resultDiv.style.padding = '20px';
resultDiv.style.borderRadius = '12px';
resultDiv.style.boxShadow = '0 8px 32px rgba(0,0,0,0.5)';
resultDiv.style.border = '1px solid rgba(255,255,255,0.1)';
resultDiv.style.animation = 'fadeIn 0.4s ease';
resultDiv.style.maxHeight = '90%';
resultDiv.style.overflowY = 'auto';
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '×';
closeBtn.style.position = 'absolute';
closeBtn.style.top = '10px';
closeBtn.style.right = '15px';
closeBtn.style.background = 'transparent';
closeBtn.style.border = 'none';
closeBtn.style.color = '#fff';
closeBtn.style.fontSize = '32px';
closeBtn.style.cursor = 'pointer';
closeBtn.style.textShadow = '0 2px 5px rgba(0,0,0,0.5)';
closeBtn.onclick = () => { overlay.style.display = 'none'; };
resultDiv.innerHTML = `
<style>
@keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
</style>
<h2 style="margin: 0 0 15px 0; color: ${themeColor}; font-size: 22px; text-transform: uppercase; letter-spacing: 1px;">Target Identified</h2>
${coverUrl ? `<img src="${coverUrl}" style="max-height: 140px; border-radius: 8px; margin-bottom: 15px; box-shadow: 0 4px 10px rgba(0,0,0,0.6);" />` : ''}
<div style="font-size: 20px; margin-bottom: 15px; font-weight: bold; color: #fff; line-height: 1.3;">${escapeHtml(title)}</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; text-align: left; background: rgba(0,0,0,0.4); padding: 15px; border-radius: 8px;">
<div style="font-size: 13px; color: #bbb;">Match Confidence</div>
<div style="font-size: 14px; font-weight: bold; color: ${similarity > 90 ? '#00ff00' : '#ffa500'}; text-align: right;">${similarity}%</div>
<div style="font-size: 13px; color: #bbb;">Format</div>
<div style="font-size: 14px; font-weight: bold; color: #fff; text-align: right;">${escapeHtml(format)}</div>
<div style="font-size: 13px; color: #bbb;">Episode Section</div>
<div style="font-size: 14px; font-weight: bold; color: #fff; text-align: right;">${bestMatch.episode || 'Movie/OVA'}</div>
<div style="font-size: 13px; color: #bbb;">Scene Timestamp</div>
<div style="font-size: 14px; font-weight: bold; color: #fff; text-align: right;">${formatTime(bestMatch.from)}</div>
</div>
`;
overlay.innerHTML = '';
overlay.appendChild(closeBtn);
overlay.appendChild(resultDiv);
// Option to display the matching scene video clip
if (bestMatch.video) {
const videoContainer = document.createElement('div');
videoContainer.style.marginTop = '15px';
videoContainer.style.width = '100%';
videoContainer.style.borderRadius = '8px';
videoContainer.style.overflow = 'hidden';
videoContainer.style.background = '#000';
const video = document.createElement('video');
video.src = bestMatch.video;
video.controls = true;
video.autoplay = true;
video.muted = true;
video.loop = true;
video.style.width = '100%';
video.style.display = 'block';
videoContainer.appendChild(video);
resultDiv.appendChild(videoContainer);
}
} else {
showError('No references found in the visual database.');
}
} catch (error) {
isScanning = false;
scanLine.style.display = 'none';
showError('Network error or Scanner API unavailable.');
console.error(error);
}
})();
// Helper to display error/warnings cleanly
function showError(msg) {
overlay.innerHTML = '';
const errorDiv = document.createElement('div');
errorDiv.style.background = 'rgba(220, 50, 50, 0.9)';
errorDiv.style.padding = '20px';
errorDiv.style.borderRadius = '8px';
errorDiv.style.fontWeight = 'bold';
errorDiv.style.textAlign = 'center';
errorDiv.style.boxShadow = '0 6px 20px rgba(0,0,0,0.6)';
errorDiv.style.maxWidth = '300px';
const text = document.createElement('div');
text.innerText = msg;
text.style.fontSize = '16px';
const subtext = document.createElement('div');
subtext.innerText = 'Note: This open-source scanner uses trace.moe which specifically focuses on indexed animated movies & television shows.';
subtext.style.fontSize = '12px';
subtext.style.marginTop = '12px';
subtext.style.fontWeight = 'normal';
subtext.style.color = '#ffd';
subtext.style.lineHeight = '1.4';
const btn = document.createElement('button');
btn.innerText = 'Dismiss';
btn.style.marginTop = '15px';
btn.style.padding = '8px 20px';
btn.style.border = 'none';
btn.style.borderRadius = '20px';
btn.style.cursor = 'pointer';
btn.style.background = '#fff';
btn.style.color = '#333';
btn.style.fontWeight = 'bold';
btn.style.transition = 'background 0.2s';
btn.onmouseover = () => btn.style.background = '#ddd';
btn.onmouseout = () => btn.style.background = '#fff';
btn.onclick = () => { overlay.style.display = 'none'; };
errorDiv.appendChild(text);
errorDiv.appendChild(subtext);
errorDiv.appendChild(btn);
overlay.appendChild(errorDiv);
overlay.style.background = 'rgba(0,0,0,0.6)';
}
// Helper functions
function escapeHtml(unsafe) {
return (unsafe || "").toString()
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function formatTime(seconds) {
if (!seconds) return '00:00';
const m = Math.floor(seconds / 60);
const s = Math.floor(seconds % 60);
return `${m}:${s < 10 ? '0' : ''}${s}`;
}
return container;
}
Apply Changes