You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, movieTitle = "UNTITLED MASTERPIECE", genre = "Action • Drama • Thriller") {
// Dynamically load the Montserrat font to give it a modern TV UI / App look
const fontName = 'Montserrat';
if (!document.getElementById('tubi-meme-font')) {
const link = document.createElement('link');
link.id = 'tubi-meme-font';
link.rel = 'stylesheet';
link.href = `https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;800;900&display=swap`;
document.head.appendChild(link);
}
// Wait for the fonts to be ready
await document.fonts.ready;
try {
await document.fonts.load('900 64px Montserrat');
await document.fonts.load('600 24px Montserrat');
} catch (e) {
// Fallback silently if explicitly loading throws
}
// Standard 16:9 720p HD canvas size for a TV/Monitor Search App Interface
const canvas = document.createElement('canvas');
const width = 1280;
const height = 720;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Draw the uploaded image using "object-fit: cover" logic to fill the background
const scale = Math.max(width / originalImg.width, height / originalImg.height);
const imgW = originalImg.width * scale;
const imgH = originalImg.height * scale;
const imgX = (width - imgW) / 2;
const imgY = (height - imgH) / 2;
ctx.drawImage(originalImg, imgX, imgY, imgW, imgH);
// Create a dark gradient overlay pushing focus to the left (Sidebar) and bottom
const sideGrad = ctx.createLinearGradient(0, 0, width * 0.7, 0);
sideGrad.addColorStop(0, '#121214');
sideGrad.addColorStop(0.45, 'rgba(18, 18, 20, 0.95)');
sideGrad.addColorStop(1, 'rgba(18, 18, 20, 0)');
ctx.fillStyle = sideGrad;
ctx.fillRect(0, 0, width, height);
const bottomGrad = ctx.createLinearGradient(0, height * 0.4, 0, height);
bottomGrad.addColorStop(0, 'rgba(18, 18, 20, 0)');
bottomGrad.addColorStop(1, 'rgba(18, 18, 20, 0.85)');
ctx.fillStyle = bottomGrad;
ctx.fillRect(0, 0, width, height);
// ==========================================
// LEFT SIDE: TUBI SEARCH INTERFACE MOCKUP
// ==========================================
const primaryOrange = '#F55422'; // Tubi brand color
// Tubi Logo
ctx.fillStyle = primaryOrange;
ctx.font = '800 48px Arial, sans-serif'; // Arial bold looks close enough to Tubi's logomark style
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.letterSpacing = '-2px'; // emulate tighter logo kerning
ctx.fillText('tubi', 80, 50);
ctx.letterSpacing = '0px';
// Search Page Header
ctx.fillStyle = '#ffffff';
ctx.font = '900 32px Montserrat, sans-serif';
ctx.fillText('Search', 80, 130);
// Search Input Field
ctx.fillStyle = '#27272a'; // dark zinc
ctx.beginPath();
ctx.roundRect(80, 180, 350, 60, 8);
ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = '#3f3f46';
ctx.stroke();
// Search Text ("September 2026")
ctx.fillStyle = '#ffffff';
ctx.font = '600 24px Montserrat, sans-serif';
ctx.textBaseline = 'middle';
ctx.fillText('September 2026 ', 100, 210);
// Fake blinking cursor
ctx.fillStyle = primaryOrange;
ctx.fillRect(320, 195, 3, 30);
// On-Screen TV Keyboard Grid Mockup
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const startX = 80;
const startY = 270;
const btnSize = 50;
const gap = 10;
const cols = 6;
for (let i = 0; i < letters.length; i++) {
const col = i % cols;
const row = Math.floor(i / cols);
const bx = startX + col * (btnSize + gap);
const by = startY + row * (btnSize + gap);
// Highlight the '6' button to mock typing '2026'
const isHighlight = letters[i] === '6';
ctx.fillStyle = isHighlight ? primaryOrange : 'rgba(255, 255, 255, 0.05)';
ctx.beginPath();
ctx.roundRect(bx, by, btnSize, btnSize, 6);
ctx.fill();
if (!isHighlight) {
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
ctx.stroke();
}
ctx.fillStyle = isHighlight ? '#ffffff' : '#d4d4d8';
ctx.font = '600 20px Montserrat, sans-serif';
ctx.textAlign = 'center';
ctx.fillText(letters[i], bx + btnSize / 2, by + btnSize / 2 + 2); // +2 optical align
}
// Action Keyboards (Space, Clear, Del)
const actionY = startY + 6 * (btnSize + gap);
const drawBtn = (x, y, w, text) => {
ctx.fillStyle = 'rgba(255, 255, 255, 0.08)';
ctx.beginPath();
ctx.roundRect(x, y, w, btnSize, 6);
ctx.fill();
ctx.fillStyle = '#a1a1aa';
ctx.font = '600 16px Montserrat, sans-serif';
ctx.textAlign = 'center';
ctx.fillText(text, x + w / 2, y + btnSize / 2 + 1);
};
drawBtn(startX, actionY, 170, 'Space');
drawBtn(startX + 180, actionY, 80, 'Clear');
drawBtn(startX + 180 + 90, actionY, 80, 'Del');
// ==========================================
// RIGHT SIDE: MOVIE PREVIEW RESULTS
// ==========================================
const rightX = 520;
// "COMING SOON" Badge
const badgeY = 360;
const badgeW = 160;
const badgeH = 34;
ctx.fillStyle = primaryOrange;
ctx.beginPath();
ctx.roundRect(rightX, badgeY, badgeW, badgeH, 4);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = '800 14px Montserrat, sans-serif';
ctx.textAlign = 'center';
ctx.fillText('COMING SOON', rightX + badgeW / 2, badgeY + badgeH / 2 + 1);
// Movie Title Text
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillStyle = '#ffffff';
ctx.font = '900 68px Montserrat, sans-serif';
// Emulate simple wrapping/limiting for massive titles
const displayTitle = movieTitle.length > 25 ? movieTitle.substring(0, 22) + "..." : movieTitle;
ctx.fillText(displayTitle.toUpperCase(), rightX - 4, 410);
// Metadata line (Year | Platform | Genre)
ctx.fillStyle = '#a1a1aa'; // cool gray
ctx.font = '600 20px Montserrat, sans-serif';
ctx.fillText(`SEP 2026 • Tubi Original • ${genre}`, rightX, 490);
// Descriptive Summary Body
ctx.fillStyle = '#d4d4d8';
ctx.font = '400 20px Montserrat, sans-serif';
const descriptionText = "Get ready for the most highly anticipated streaming event of the year. This groundbreaking cinematic experience is arriving exclusively on Tubi in September 2026. Set your reminders now.";
// Word wrapping logic
const maxWidth = 700;
const lineHeight = 30;
const words = descriptionText.split(' ');
let line = '';
let textY = 540;
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n] + ' ';
const metrics = ctx.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
ctx.fillText(line, rightX, textY);
line = words[n] + ' ';
textY += lineHeight;
} else {
line = testLine;
}
}
ctx.fillText(line, rightX, textY);
// Preview Action Buttons ("Add to My List")
const btnGrpY = textY + 50;
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.roundRect(rightX, btnGrpY, 200, 48, 24);
ctx.fill();
ctx.fillStyle = '#121214';
ctx.font = '800 16px Montserrat, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('+ Add to My List', rightX + 100, btnGrpY + 24);
return canvas;
}
Apply Changes