You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, statsText = "YouTube Stats For Nerds Volume Normalize 100% / 100% (-35.0dB/-14.0dB) Opus (-31.9dB/-14.0dB) Ac3", overlayOpacity = "0.85") {
const canvas = document.createElement("canvas");
canvas.width = originalImg.width;
canvas.height = originalImg.height;
const ctx = canvas.getContext("2d");
// Draw original image
ctx.drawImage(originalImg, 0, 0);
// Parse the stats string for analytical data
const dbRegex = /(-?\d+(?:\.\d+)?)dB/gi;
let match;
const dbValues = [];
while ((match = dbRegex.exec(statsText)) !== null) {
dbValues.push({ value: parseFloat(match[1]), label: match[0] });
}
// Cap to a max of 10 values to prevent layout overflow
dbValues.splice(10);
const pctRegex = /(\d+)%/g;
const percentages = [];
while ((match = pctRegex.exec(statsText)) !== null) {
percentages.push(match[0]);
}
const codecRegex = /(opus|ac3|mp4a|avc1|vp9|av01|vorbis)/gi;
const codecs = [];
while ((match = codecRegex.exec(statsText)) !== null) {
codecs.push(match[0]);
}
// Calculate overlay dimensions (auto-sizing based on image, with min/max bounds)
const padding = 20;
if (originalImg.width < 150 || originalImg.height < 150) {
// Image too small for an overlay, just return it untouched
return canvas;
}
const boxW = Math.max(280, Math.min(originalImg.width - padding * 2, 600));
const boxH = Math.max(200, Math.min(originalImg.height - padding * 2, 450));
const startX = padding;
const startY = padding;
// Draw overlay background box
const opacity = parseFloat(overlayOpacity) || 0.85;
ctx.fillStyle = `rgba(15, 15, 15, ${opacity})`;
ctx.fillRect(startX, startY, boxW, boxH);
ctx.strokeStyle = "rgba(200, 200, 200, 0.3)";
ctx.lineWidth = 1;
ctx.strokeRect(startX, startY, boxW, boxH);
let currentY = startY + 15;
const textX = startX + 20;
const textMaxW = boxW - 40;
// Header
ctx.fillStyle = "#ffffff";
ctx.font = "bold 16px sans-serif";
ctx.textBaseline = "top";
ctx.fillText("YouTube Stats for Nerds - Audio Analysis", textX, currentY);
// Close button
ctx.font = "16px sans-serif";
ctx.fillStyle = "#aaaaaa";
ctx.fillText("✕", startX + boxW - 30, currentY);
currentY += 35;
// Wrap text function for potentially long raw text input
function drawWrappedText(text, x, y, maxW, lineHeight) {
const words = text.split(' ');
let line = '';
let localY = y;
for (let n = 0; n < words.length; n++) {
let testLine = line + words[n] + ' ';
let metrics = ctx.measureText(testLine);
if (metrics.width > maxW && n > 0) {
ctx.fillText(line, x, localY);
line = words[n] + ' ';
localY += lineHeight;
} else {
line = testLine;
}
}
ctx.fillText(line, x, localY);
return localY + lineHeight;
}
// Print Original Text Line
ctx.fillStyle = "#bbbbbb";
ctx.font = "13px monospace";
currentY = drawWrappedText(`Input: ${statsText}`, textX, currentY, textMaxW, 18);
currentY += 15;
// Analysis Summary Section
ctx.fillStyle = "#ffffff";
ctx.font = "bold 14px sans-serif";
ctx.fillText("Extracted Parameters:", textX, currentY);
currentY += 22;
ctx.font = "13px monospace";
ctx.fillStyle = "#55acee"; // YouTube-like link blue variant
ctx.fillText(`Normalizations: ${percentages.length > 0 ? percentages.join(', ') : 'N/A'}`, textX, currentY);
currentY += 18;
ctx.fillText(`Loudness (dB): ${dbValues.length > 0 ? dbValues.map(d => d.label).join(', ') : 'N/A'}`, textX, currentY);
currentY += 18;
ctx.fillText(`Codecs Found: ${codecs.length > 0 ? codecs.join(', ') : 'None'}`, textX, currentY);
currentY += 25;
// Visualize with a Bar Chart
if (dbValues.length > 0) {
const chartX = textX;
const chartY = currentY;
const chartW = boxW - 40;
const chartH = (startY + boxH - 20) - chartY;
if (chartH > 60) {
// Draw chart background
ctx.fillStyle = "rgba(0, 0, 0, 0.4)";
ctx.fillRect(chartX, chartY, chartW, chartH);
const p = 15; // chart internal margin
const innerY = chartY + p;
const innerH = chartH - p * 2;
let minDb = Math.min(...dbValues.map(d => d.value), -20);
let maxDb = Math.max(...dbValues.map(d => d.value), 0);
// Allow bounds padding
minDb -= 5;
maxDb += 5;
const range = maxDb - minDb;
const zeroY = innerY + innerH - ((0 - minDb) / range) * innerH;
// Render grid lines
ctx.strokeStyle = "rgba(255, 255, 255, 0.1)";
ctx.fillStyle = "rgba(255, 255, 255, 0.4)";
ctx.font = "10px sans-serif";
for(let db = Math.ceil(minDb/5)*5; db <= Math.floor(maxDb/5)*5; db += 5) {
const gy = innerY + innerH - ((db - minDb) / range) * innerH;
ctx.beginPath();
ctx.moveTo(chartX, gy);
ctx.lineTo(chartX + chartW, gy);
ctx.stroke();
if(db !== 0) {
ctx.fillText(`${db}`, chartX + 4, gy - 4);
}
}
// 0 dB Axis styling
ctx.beginPath();
ctx.moveTo(chartX, zeroY);
ctx.lineTo(chartX + chartW, zeroY);
ctx.strokeStyle = "rgba(255, 60, 60, 0.9)";
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.fillStyle = "rgba(255, 60, 60, 1)";
ctx.fillText("0 dB", chartX + 4, zeroY - 4);
// Map bars
const computedBarW = (chartW - 20) / Math.max(1, dbValues.length) - 10;
const barWidth = Math.max(10, Math.min(60, computedBarW));
const spacing = (chartW - 20 - (barWidth * dbValues.length)) / (dbValues.length + 1);
for (let i = 0; i < dbValues.length; i++) {
const item = dbValues[i];
const bx = chartX + 10 + spacing + i * (barWidth + spacing);
const by = innerY + innerH - ((item.value - minDb) / range) * innerH;
const bHeight = zeroY - by;
// Color mapping to visually separate parameters
ctx.fillStyle = i % 2 === 0 ? "rgba(75, 175, 230, 0.85)" : "rgba(180, 120, 240, 0.85)";
ctx.fillRect(bx, Math.min(zeroY, by), barWidth, Math.abs(bHeight));
ctx.strokeStyle = "rgba(255, 255, 255, 0.3)";
ctx.lineWidth = 1;
ctx.strokeRect(bx, Math.min(zeroY, by), barWidth, Math.abs(bHeight));
// Text label logic logic
ctx.fillStyle = "#ffffff";
ctx.font = "bold 11px monospace";
const labelW = ctx.measureText(item.label).width;
const textY = by > zeroY ? by + 14 : by - 6;
ctx.shadowColor = "rgba(0, 0, 0, 0.9)";
ctx.shadowBlur = 3;
ctx.fillText(item.label, bx + barWidth/2 - labelW/2, textY);
ctx.shadowBlur = 0; // reset
}
}
}
return canvas;
}
Apply Changes