You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, trackName = "Unknown Track", artistName = "Unknown Artist", primaryColor = "#1DB954") {
// Canvas setup based on a mobile screen aspect ratio
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const cw = 750;
const ch = 1334;
canvas.width = cw;
canvas.height = ch;
// Polyfill for roundRect if not supported
if (!CanvasRenderingContext2D.prototype.roundRect) {
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
this.moveTo(x + r, y);
this.arcTo(x + w, y, x + w, y + h, r);
this.arcTo(x + w, y + h, x, y + h, r);
this.arcTo(x, y + h, x, y, r);
this.arcTo(x, y, x + w, y, r);
this.closePath();
return this;
};
}
// Helper to draw image using object-fit: cover
function drawCover(context, img, dx, dy, dw, dh) {
const iw = img.width;
const ih = img.height;
const ir = iw / ih;
const dr = dw / dh;
let sx, sy, sw, sh;
if (ir > dr) {
sh = ih;
sw = ih * dr;
sy = 0;
sx = (iw - sw) / 2;
} else {
sw = iw;
sh = iw / dr;
sx = 0;
sy = (ih - sh) / 2;
}
context.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
}
// Helper for rendering text with ellipsis
function fillTextEllipsis(context, text, x, y, maxWidth) {
let width = context.measureText(text).width;
if (width <= maxWidth) {
context.fillText(text, x, y);
return;
}
let str = text;
while (context.measureText(str + '...').width > maxWidth && str.length > 0) {
str = str.slice(0, -1);
}
context.fillText(str + '...', x, y);
}
// 1. Draw blurred, dark background from original image
ctx.save();
ctx.filter = 'blur(45px)';
// draw larger to hide blurred edges
drawCover(ctx, originalImg, -100, -100, cw + 200, ch + 200);
ctx.restore();
// 2. Add gradient overlay for UI contrast
const gradient = ctx.createLinearGradient(0, 0, 0, ch);
gradient.addColorStop(0, 'rgba(0, 0, 0, 0.4)');
gradient.addColorStop(0.5, 'rgba(0, 0, 0, 0.6)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0.9)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, cw, ch);
// 3. Header "Identified Music Topic" (Simulating the search/identifier UI)
ctx.fillStyle = 'rgba(255, 255, 255, 0.15)';
ctx.beginPath();
ctx.roundRect((cw - 320) / 2, 50, 320, 46, 23);
ctx.fill();
ctx.fillStyle = 'white';
ctx.font = 'bold 18px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('🎵 Identified Search Topic', cw / 2, 73);
// 4. Draw Square Album Art Overlay
ctx.save();
const ax = 75, ay = 180, aw = 600, ah = 600;
// Add shadow
ctx.shadowColor = 'rgba(0, 0, 0, 0.6)';
ctx.shadowBlur = 40;
ctx.shadowOffsetY = 20;
// Create rounded clipping mask
ctx.beginPath();
ctx.roundRect(ax, ay, aw, ah, 30);
ctx.fill(); // fills shadow
ctx.shadowColor = 'transparent'; // turn off shadow for draw
ctx.clip();
// Draw the actual image within bounded clip
drawCover(ctx, originalImg, ax, ay, aw, ah);
ctx.restore();
// 5. Title & Artist details
ctx.textAlign = 'left';
ctx.textBaseline = 'alphabetic';
ctx.fillStyle = 'white';
ctx.font = 'bold 54px sans-serif';
fillTextEllipsis(ctx, trackName, 75, 910, 600);
ctx.fillStyle = '#b3b3b3';
ctx.font = '32px sans-serif';
fillTextEllipsis(ctx, artistName, 75, 965, 600);
// 6. Progress Bar
const py = 1060;
// Background track
ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
ctx.beginPath();
ctx.roundRect(75, py, 600, 6, 3);
ctx.fill();
// Filled progress
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.roundRect(75, py, 180, 6, 3);
ctx.fill();
// Progress Thumb
ctx.beginPath();
ctx.arc(75 + 180, py + 3, 9, 0, Math.PI * 2);
ctx.fill();
// Time Indicators
ctx.font = '22px sans-serif';
ctx.fillStyle = '#b3b3b3';
ctx.fillText('1:08', 75, py + 35);
ctx.textAlign = 'right';
ctx.fillText('-2:45', 675, py + 35);
// 7. Player Controls
const cy = 1200;
// Play/Pause Button (Main Action)
ctx.fillStyle = primaryColor;
ctx.beginPath();
ctx.arc(cw / 2, cy, 45, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000000'; // Triangle Icon
ctx.beginPath();
ctx.moveTo(cw / 2 - 8, cy - 16);
ctx.lineTo(cw / 2 + 16, cy);
ctx.lineTo(cw / 2 - 8, cy + 16);
ctx.fill();
// Previous Track Button
ctx.fillStyle = 'white';
const px = cw / 2 - 130;
ctx.beginPath();
ctx.moveTo(px + 15, cy - 15);
ctx.lineTo(px - 10, cy);
ctx.lineTo(px + 15, cy + 15);
ctx.fill();
ctx.fillRect(px - 14, cy - 14, 4, 28);
// Next Track Button
const nx = cw / 2 + 130;
ctx.beginPath();
ctx.moveTo(nx - 15, cy - 15);
ctx.lineTo(nx + 10, cy);
ctx.lineTo(nx - 15, cy + 15);
ctx.fill();
ctx.fillRect(nx + 10, cy - 14, 4, 28);
// Action Icons (Shuffle / Repeat)
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
// Shuffle Placeholder (Just an abstract icon placeholder using lines)
ctx.lineWidth = 3;
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.beginPath();
ctx.moveTo(90, cy - 8); ctx.lineTo(120, cy + 8);
ctx.moveTo(90, cy + 8); ctx.lineTo(120, cy - 8);
ctx.stroke();
// Repeat Placeholder
ctx.beginPath();
ctx.arc(cw - 105, cy, 10, Math.PI, 0);
ctx.arc(cw - 105, cy, 10, 0, Math.PI);
ctx.stroke();
return canvas;
}
Apply Changes