You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, topicTitle = "Epic Narrative Climax", currentTime = "1:24", totalTime = "4:56", progress = "30", themeColor = "#FF0000") {
const canvas = document.createElement('canvas');
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 1. Draw base image
ctx.drawImage(originalImg, 0, 0);
// 2. Responsive UI Scaling
// Ensures that the UI components scale cleanly regardless of input image resolution
const scale = Math.max(0.3, Math.min(width, height) / 800);
// Base structural metrics
const padX = 25 * scale;
const barY = height - 30 * scale;
const barHeight = 6 * scale;
const barWidth = width - (padX * 2);
// Calculate progress (scrubber location)
let prog = parseFloat(progress);
if (isNaN(prog) || prog < 0) prog = 0;
if (prog > 100) prog = 100;
const activeWidth = barWidth * (prog / 100);
// 3. Draw Dark Gradient bottom shadow (better contrast for UI)
const shadowH = 200 * scale;
const grad = ctx.createLinearGradient(0, height - shadowH, 0, height);
grad.addColorStop(0, 'rgba(0, 0, 0, 0)');
grad.addColorStop(0.5, 'rgba(0, 0, 0, 0.6)');
grad.addColorStop(1, 'rgba(0, 0, 0, 0.95)');
ctx.fillStyle = grad;
ctx.fillRect(0, height - shadowH, width, shadowH);
// 4. Center Play Button
const cx = width / 2;
const cy = height / 2;
const radius = 55 * scale;
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fill();
ctx.lineWidth = 3 * scale;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
ctx.stroke();
const pSize = 18 * scale;
ctx.beginPath();
ctx.moveTo(cx - pSize * 0.7, cy - pSize);
ctx.lineTo(cx + pSize * 1.4, cy);
ctx.lineTo(cx - pSize * 0.7, cy + pSize);
ctx.closePath();
ctx.lineJoin = 'round';
ctx.fillStyle = 'white';
ctx.fill();
// 5. Basic Control Texts (Timestamp Display)
const controlY = height - 55 * scale;
const fontSize = Math.max(12, Math.round(18 * scale));
ctx.font = `bold ${fontSize}px "Segoe UI", Roboto, Helvetica, Arial, sans-serif`;
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText(`${currentTime} / ${totalTime}`, padX, controlY);
// 6. Search Topic Identifier Picker (Tooltip Miniature Thumbnail)
const maxTipW = 240 * scale;
const maxTipH = 135 * scale;
const aspect = width / height;
// Maintain image aspect ratio in tooltip
let tipW = maxTipW;
let tipH = tipW / aspect;
if (tipH > maxTipH) {
tipH = maxTipH;
tipW = tipH * aspect;
}
const tipGap = 20 * scale;
let tipBaseX = padX + activeWidth;
let tipRectX = tipBaseX - tipW / 2;
// Clamp Tooltip box to screen edges
if (tipRectX < padX) tipRectX = padX;
if (tipRectX + tipW > width - padX) tipRectX = width - padX - tipW;
const tipRectY = barY - tipGap - tipH;
// Draw the miniature hover thumbnail with a drop shadow
ctx.save();
ctx.shadowColor = 'rgba(0, 0, 0, 0.8)';
ctx.shadowBlur = 20 * scale;
ctx.shadowOffsetY = 5 * scale;
ctx.drawImage(originalImg, tipRectX, tipRectY, tipW, tipH);
ctx.restore(); // reset shadow
// Picker border highlighting the exact frame selection
ctx.lineWidth = 3 * scale;
ctx.strokeStyle = themeColor;
ctx.strokeRect(tipRectX, tipRectY, tipW, tipH);
// 7. Topic Title Overlay inside the Picker Thumbnail
const barLabelH = 32 * scale;
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
ctx.fillRect(tipRectX, tipRectY + tipH - barLabelH, tipW, barLabelH);
ctx.fillStyle = 'white';
ctx.font = `bold ${Math.max(10, Math.round(15 * scale))}px Arial, sans-serif`;
ctx.textAlign = 'center';
// Fill text, clipped to the miniature box width
ctx.fillText(topicTitle, tipRectX + tipW / 2, tipRectY + tipH - barLabelH / 2, tipW - 16 * scale);
// Tooltip scrub time floating above the thumbnail
ctx.fillStyle = 'rgba(0, 0, 0, 0.75)';
ctx.font = `bold ${Math.max(10, Math.round(14 * scale))}px Arial, sans-serif`;
const tWidth = ctx.measureText(currentTime).width + 24 * scale;
const scrubTimeY = tipRectY - 30 * scale;
const scrubTimeH = 22 * scale;
ctx.fillRect(tipRectX + tipW / 2 - tWidth / 2, scrubTimeY, tWidth, scrubTimeH);
ctx.fillStyle = 'white';
ctx.fillText(currentTime, tipRectX + tipW / 2, scrubTimeY + scrubTimeH / 2);
// 8. Progress Bar Lines
// Background Line
ctx.fillStyle = 'rgba(255, 255, 255, 0.25)';
ctx.fillRect(padX, barY - barHeight / 2, barWidth, barHeight);
// Simulated buffered line
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.fillRect(padX, barY - barHeight / 2, barWidth * (Math.min(100, prog + 15) / 100), barHeight);
// Active Theme Current Time Line
ctx.fillStyle = themeColor;
ctx.fillRect(padX, barY - barHeight / 2, activeWidth, barHeight);
// Chapter Identifiers on the Progress bar (evenly spread marks to signify distinct topics)
const numChapters = 6;
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
for(let i = 1; i < numChapters; i++) {
let chX = padX + (barWidth * (i / numChapters));
ctx.fillRect(chX - 1.5 * scale, barY - barHeight, 3 * scale, barHeight * 2);
}
// 9. Identifier Scrub Cursor/Thumb
ctx.beginPath();
ctx.arc(tipBaseX, barY, 9 * scale, 0, Math.PI * 2);
ctx.fillStyle = themeColor;
ctx.fill();
ctx.lineWidth = 2 * scale;
ctx.strokeStyle = 'white';
ctx.stroke();
// Context down-chevron mapping the tooltip to the active bar position
ctx.beginPath();
ctx.moveTo(tipBaseX - 6 * scale, barY - 14 * scale);
ctx.lineTo(tipBaseX + 6 * scale, barY - 14 * scale);
ctx.lineTo(tipBaseX, barY - 7 * scale);
ctx.closePath();
ctx.fillStyle = themeColor;
ctx.fill();
return canvas;
}
Apply Changes