You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, fallbackTitle = "The Matrix", omdbApiKey = "") {
// Create and size the main canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0);
// Dynamically load Tesseract.js for OCR if not present
if (!window.Tesseract) {
try {
await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/tesseract.js@4/dist/tesseract.min.js';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
} catch (e) {
console.error("Failed to load Tesseract.js for text detection.");
}
}
let extractedText = "";
// Attempt to extract text from the image to use as the movie/show title
if (window.Tesseract) {
try {
// Scale down image on a temporary canvas to dramatically significantly speed up OCR
const ocrCanvas = document.createElement('canvas');
const MAX_DIM = 800;
let scale = 1;
if (originalImg.width > MAX_DIM || originalImg.height > MAX_DIM) {
scale = Math.min(MAX_DIM / originalImg.width, MAX_DIM / originalImg.height);
}
ocrCanvas.width = originalImg.width * scale;
ocrCanvas.height = originalImg.height * scale;
const ocrCtx = ocrCanvas.getContext('2d');
ocrCtx.drawImage(originalImg, 0, 0, ocrCanvas.width, ocrCanvas.height);
const result = await window.Tesseract.recognize(ocrCanvas, 'eng');
// Filter valid lines and pick the largest text (likely the title)
const validLines = result.data.lines.filter(l =>
l.text.trim().length > 2 && /[a-zA-Z]{3,}/.test(l.text)
);
if (validLines.length > 0) {
// Heuristic: Highest bounding box height is usually the main title logic
validLines.sort((a, b) => (b.bbox.y1 - b.bbox.y0) - (a.bbox.y1 - a.bbox.y0));
extractedText = validLines[0].text.trim().replace(/\n/g, ' ');
}
} catch (e) {
console.error("OCR image parsing failed:", e);
}
}
// Determine title to search
const queryTitle = extractedText || fallbackTitle;
let rating = "N/A";
let titleFound = queryTitle;
let yearFound = "";
let votes = "";
// Attempt OMDb API fetch if a key is provided
if (omdbApiKey) {
try {
const res = await fetch(`https://www.omdbapi.com/?apikey=${omdbApiKey}&t=${encodeURIComponent(queryTitle)}`);
const data = await res.json();
if (data.Response === "True") {
rating = data.imdbRating;
titleFound = data.Title;
yearFound = data.Year;
votes = data.imdbVotes ? data.imdbVotes + " votes" : "";
}
} catch (e) {
console.error("OMDb API fetch failed:", e);
}
}
// Fallback to free TVMaze API if no OMDB key provided or rating wasn't found
if (rating === "N/A" || !omdbApiKey) {
try {
const res = await fetch(`https://api.tvmaze.com/search/shows?q=${encodeURIComponent(queryTitle)}`);
const data = await res.json();
if (data && data.length > 0) {
const show = data[0].show;
rating = show.rating.average ? show.rating.average.toFixed(1) : "N/A";
titleFound = show.name;
yearFound = show.premiered ? show.premiered.substring(0, 4) : "";
votes = "TVMaze";
}
} catch (e) {
console.error("TVMaze API fetch failed:", e);
}
}
// Determine scale for the UI overlay to maintain responsive sizing relative to original image size
let uis = canvas.width / 800;
if (uis < 0.6) uis = 0.6;
if (uis > 2.5) uis = 2.5;
const padding = 20 * uis;
const boxHeight = 110;
const boxWidth = 320;
// Draw the overlay widget
ctx.save();
// Position overlay in the bottom-left corner
ctx.translate(padding, canvas.height - (boxHeight * uis) - padding);
ctx.scale(uis, uis);
// 1. Shadow & Background Plate
ctx.shadowColor = 'rgba(0, 0, 0, 0.6)';
ctx.shadowBlur = 10;
ctx.shadowOffsetY = 5;
ctx.fillStyle = 'rgba(18, 18, 18, 0.9)';
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
ctx.lineWidth = 1;
// Draw Rounded Rectangle for background
ctx.beginPath();
const r = 8;
ctx.moveTo(r, 0);
ctx.arcTo(boxWidth, 0, boxWidth, boxHeight, r);
ctx.arcTo(boxWidth, boxHeight, 0, boxHeight, r);
ctx.arcTo(0, boxHeight, 0, 0, r);
ctx.arcTo(0, 0, boxWidth, 0, r);
ctx.closePath();
ctx.fill();
// Turn off shadow for internal sub-elements
ctx.shadowColor = 'transparent';
ctx.stroke();
// 2. IMDb Branding Block
ctx.fillStyle = '#F5C518'; // IMDb yellow
ctx.beginPath();
ctx.moveTo(15 + 4, 15);
ctx.arcTo(15 + 63, 15, 15 + 63, 15 + 32, 4);
ctx.arcTo(15 + 63, 15 + 32, 15, 15 + 32, 4);
ctx.arcTo(15, 15 + 32, 15, 15, 4);
ctx.arcTo(15, 15, 15 + 63, 15, 4);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#000000';
ctx.font = '900 21px "Arial Black", Impact, sans-serif';
ctx.textBaseline = 'top';
ctx.fillText("IMDb", 18, 18);
// 3. Star Icon helper
function drawStar(ctx, cx, cy, spikes, outerRadius, innerRadius) {
let rot = (Math.PI / 2) * 3;
let step = Math.PI / spikes;
ctx.beginPath();
ctx.moveTo(cx, cy - outerRadius);
for (let i = 0; i < spikes; i++) {
ctx.lineTo(cx + Math.cos(rot) * outerRadius, cy + Math.sin(rot) * outerRadius);
rot += step;
ctx.lineTo(cx + Math.cos(rot) * innerRadius, cy + Math.sin(rot) * innerRadius);
rot += step;
}
ctx.lineTo(cx, cy - outerRadius);
ctx.closePath();
ctx.fill();
}
ctx.fillStyle = '#F5C518';
drawStar(ctx, 105, 31, 5, 14, 6);
// 4. Rating Display
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 28px Arial, sans-serif';
ctx.textBaseline = 'alphabetic';
ctx.fillText(rating !== "N/A" ? rating : "?", 125, 42);
const ratingWidth = ctx.measureText(rating !== "N/A" ? rating : "?").width;
ctx.fillStyle = '#AAAAAA';
ctx.font = 'bold 16px Arial, sans-serif';
ctx.fillText("/ 10", 125 + ratingWidth + 5, 41);
// 5. Source / Vote Count
if (votes) {
ctx.fillStyle = '#888888';
ctx.font = '12px Arial, sans-serif';
ctx.fillText(votes, 125, 60);
}
// 6. Title Context display
ctx.fillStyle = '#EEEEEE';
ctx.font = 'bold 16px Arial, sans-serif';
let displayTitle = titleFound + (yearFound ? ` (${yearFound})` : "");
if (displayTitle.length > 34) {
displayTitle = displayTitle.substring(0, 31) + "...";
}
ctx.fillText(displayTitle, 15, 87);
ctx.restore();
return canvas;
}
Apply Changes