Dynamically create OG:Image from Text with Canvas
Working with the <canvas> tag can be frustrating. There's no way to debug what's going on (that I know of) and you are often caught wondering "Why did that happen"? But at the same time it's an extremely useful tool when you need to dynamically create images.

The first thing you need to do when working with canvas is set up image resolution for retina displays.
const ogImage = document.getElementById('playerCard') as HTMLCanvasElement; const ctx = ogImage.getContext('2d'); ogImage.width = 2400; ogImage.height = 1260; ogImage.style.width = '1200px'; ogImage.style.height = '630px'; ctx.scale(2, 2);
After which we can draw to the canvas with the scaled down dimensions 1200x630. Let's add a background image to the canvas and be sure to set the composite operation to "destination-over" so that all subsequent draw operations occur on a layer above the background.
const bgImg = new Image(); bgImg.src = 'canvas-bg.jpg'; bgImg.onload = () => { ctx.drawImage(bgImg, 0, 0, 1200, 630); }; ctx.globalCompositeOperation = 'destination-over';
The first text layer we'll add will be the author name and profile image in the lower left. In order to have a radiused border on the image, we have to create a clipping path and clip the images center.
const playerImg = new Image(); playerImg.src = 'profile-picture.jpg'; playerImg.onload = () => { ctx.save(); ctx.beginPath(); ctx.arc(30, 600, 16, 0, Math.PI * 2, true); ctx.closePath(); ctx.clip(); ctx.drawImage(playerImg, 14, 605, 32, 32); ctx.restore(); }; drawText(ctx, '@markmakes', '18px Overpass Mono', 60, 315, 'name'); drawText(ctx, 'This is a dynamically created image in a canvas element.', '36px Overpass', 15, 20, 'title');
The drawText() function simply writes the text to the appropriate position. Notice how the title is going to be treated differently in the next step. Canvas does not natively support text wrapping so we have to utilize a neat line wrapping function written by Peter Hrynkow.
function drawText(context, txt, font, x, y, type) { context.save(); context.font = font; context.textBaseline = 'top'; context.fillStyle = '#ffffff'; if (type === 'title') { wrapText(context, txt, x, y, 900); } else { ctx.fillText(txt, x, y); } context.restore(); }
At this point all that is left to do is to load the base64 into a blob and upload to our CDN of choice and we're done! Next up I may try to create an animated GIF dynamically using jsGif.