You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(
originalImg,
titleText = "ALBUM TITLE",
artistText = "ARTIST NAME",
spineText = "MY ALBUM ● MY ARTIST",
spineBgColor = "#DDDDDD",
spineTextColor = "#000000",
frontTextColor = "#FFFFFF",
titleFontSize = 36, // px
artistTextSize = 22, // px
spineFontSize = 20, // px
fontFamily = "Impact, Arial, sans-serif",
frontTextShadow = 1, // 0 for no, 1 for yes
drawFoldLine = 1, // 0 for no, 1 for yes
foldLineColor = "rgba(0,0,0,0.5)"
) {
// Helper function for text truncation, defined within processImage scope
function _truncateText(ctx, text, maxWidth) {
let currentText = String(text); // Ensure it's a string
if (maxWidth <= 0) return ""; // Cannot fit anything if maxWidth is zero or negative
if (ctx.measureText(currentText).width <= maxWidth) {
return currentText;
}
const ellipsis = "...";
const ellipsisWidth = ctx.measureText(ellipsis).width;
// If ellipsis itself doesn't fit, return empty string or as much text as possible without ellipsis
if (ellipsisWidth > maxWidth) {
let minimalText = "";
for (let i = 0; i < currentText.length; i++) {
if (ctx.measureText(minimalText + currentText[i]).width <= maxWidth) {
minimalText += currentText[i];
} else {
break;
}
}
return minimalText;
}
// Iteratively shorten the text and check if it fits with ellipsis
while (currentText.length > 0) {
currentText = currentText.slice(0, -1);
if (ctx.measureText(currentText + ellipsis).width <= maxWidth) {
return currentText + ellipsis;
}
}
// This case should ideally be covered if ellipsis fits but no char + ellipsis fits
return ""; // Or return ellipsis if it fits by itself and currentText is empty
}
const DPI = 150; // Resolution for the template
const FRONT_WIDTH_IN = 4; // inches
const FRONT_HEIGHT_IN = 2.5; // inches
const SPINE_WIDTH_IN = 0.5; // inches
const FRONT_WIDTH = FRONT_WIDTH_IN * DPI; // pixels
const FRONT_HEIGHT = FRONT_HEIGHT_IN * DPI; // pixels
const SPINE_WIDTH = SPINE_WIDTH_IN * DPI; // pixels
const SPINE_HEIGHT = FRONT_HEIGHT; // pixels (same as front panel height)
const CANVAS_WIDTH = FRONT_WIDTH + SPINE_WIDTH;
const CANVAS_HEIGHT = FRONT_HEIGHT;
const canvas = document.createElement('canvas');
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
const ctx = canvas.getContext('2d');
// Default canvas background color (e.g., transparent or white)
// This helps if originalImg is transparent or doesn't fully cover due to error
ctx.fillStyle = '#FFFFFF'; // White background for the whole template area
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// 1. Draw front cover image
if (originalImg && typeof originalImg.width === 'number' && originalImg.width > 0 && typeof originalImg.height === 'number' && originalImg.height > 0) {
const imgAspect = originalImg.width / originalImg.height;
const frontPanelAspect = FRONT_WIDTH / FRONT_HEIGHT;
let sx = 0, sy = 0, sWidth = originalImg.width, sHeight = originalImg.height;
if (imgAspect > frontPanelAspect) { // Image wider than panel aspect: fit height, crop width
sWidth = originalImg.height * frontPanelAspect;
sx = (originalImg.width - sWidth) / 2;
} else if (imgAspect < frontPanelAspect) { // Image taller than panel aspect: fit width, crop height
sHeight = originalImg.width / frontPanelAspect;
sy = (originalImg.height - sHeight) / 2;
}
// else: aspect ratios match, use full image (sx, sy, sWidth, sHeight are already correct)
ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, 0, 0, FRONT_WIDTH, FRONT_HEIGHT);
} else {
// Fallback for missing/invalid image: draw a placeholder on the front panel
ctx.fillStyle = '#CCCCCC'; // Light gray placeholder
ctx.fillRect(0, 0, FRONT_WIDTH, FRONT_HEIGHT);
ctx.fillStyle = '#555555'; // Dark gray text
ctx.font = `bold 20px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText("Image Error", FRONT_WIDTH / 2, FRONT_HEIGHT / 2);
}
// 2. Draw Spine
ctx.fillStyle = spineBgColor;
ctx.fillRect(FRONT_WIDTH, 0, SPINE_WIDTH, SPINE_HEIGHT);
// Spine Text
if (String(spineText).trim() !== "" && SPINE_WIDTH > 0 && SPINE_HEIGHT > 0) {
let actualSpineFontSize = Math.max(1, spineFontSize); // Ensure positive font size
const spineTextVerticalPadding = Math.max(2, SPINE_WIDTH * 0.05); // Padding for character height within spine width
const maxCharHeightOnSpine = SPINE_WIDTH - (2 * spineTextVerticalPadding);
if (actualSpineFontSize > maxCharHeightOnSpine) {
actualSpineFontSize = maxCharHeightOnSpine;
}
if (actualSpineFontSize > 0) { // Only draw if calculated font size is usable
ctx.save();
ctx.fillStyle = spineTextColor;
ctx.font = `bold ${actualSpineFontSize}px ${fontFamily}`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.translate(FRONT_WIDTH + SPINE_WIDTH / 2, SPINE_HEIGHT / 2);
ctx.rotate(-Math.PI / 2); // Rotate -90 degrees (clockwise)
const spineTextHorizontalPadding = Math.max(5, SPINE_HEIGHT * 0.025); // Padding for text length along spine height
const maxSpineTextWidth = SPINE_HEIGHT - (2 * spineTextHorizontalPadding);
const truncatedSpineText = _truncateText(ctx, spineText, maxSpineTextWidth);
ctx.fillText(truncatedSpineText, 0, 0);
ctx.restore();
}
}
// 3. Draw Front Panel Text (Title and Artist)
const frontTextHorizontalPadding = Math.max(10, FRONT_WIDTH * 0.05); // Horizontal padding
const frontTextVerticalPadding = Math.max(10, FRONT_HEIGHT * 0.05); // Vertical padding
if (frontTextShadow === 1) {
ctx.shadowColor = 'rgba(0,0,0,0.7)';
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 3;
}
// Title Text
if (String(titleText).trim() !== "" && FRONT_WIDTH > 0 && FRONT_HEIGHT > 0) {
let actualTitleFontSize = Math.max(1, titleFontSize);
ctx.font = `bold ${actualTitleFontSize}px ${fontFamily}`;
ctx.fillStyle = frontTextColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
const maxTitleWidth = FRONT_WIDTH - (2 * frontTextHorizontalPadding);
const truncatedTitleText = _truncateText(ctx, titleText, maxTitleWidth);
ctx.fillText(truncatedTitleText, FRONT_WIDTH / 2, frontTextVerticalPadding);
}
// Artist Text
if (String(artistText).trim() !== "" && FRONT_WIDTH > 0 && FRONT_HEIGHT > 0) {
let actualArtistTextSize = Math.max(1, artistTextSize);
ctx.font = `normal ${actualArtistTextSize}px ${fontFamily}`;
ctx.fillStyle = frontTextColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
const maxArtistWidth = FRONT_WIDTH - (2 * frontTextHorizontalPadding);
const truncatedArtistText = _truncateText(ctx, artistText, maxArtistWidth);
ctx.fillText(truncatedArtistText, FRONT_WIDTH / 2, FRONT_HEIGHT - frontTextVerticalPadding);
}
if (frontTextShadow === 1) { // Reset shadow effects
ctx.shadowColor = 'transparent';
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 0;
}
// 4. Draw Fold Line (optional)
if (drawFoldLine === 1 && SPINE_WIDTH > 0) {
ctx.strokeStyle = foldLineColor;
ctx.lineWidth = 1;
ctx.setLineDash([4, 2]); // Example: 4px line, 2px gap
ctx.beginPath();
ctx.moveTo(FRONT_WIDTH, 0);
ctx.lineTo(FRONT_WIDTH, FRONT_HEIGHT);
ctx.stroke();
ctx.setLineDash([]); // Reset line dash to solid for subsequent drawings
}
return canvas;
}
Apply Changes