samizdatco / skia-canvas

A GPU-accelerated 2D graphics environment for Node.js
MIT License
1.7k stars 66 forks source link

How to render an image with dynamic size #90

Closed ReiKohaku closed 2 years ago

ReiKohaku commented 2 years ago

I have to render an image with an unknown height (cause I have to measure text with different parameters), but there's an issue with it.

import { Canvas } from "skia-canvas";
const canvas = new Canvas(720, 150);
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#FFFFFF";
const height = 150 + Math.floor(750 * Math.random());
ctx.rect(0, 0, canvas.width, height);
// 1. Assign var directly: this will lost all data of context
canvas.height = height;
// 2. Get image, resize canvas and then put image: this will lost data out of range
const img = ctx.getImageData(0, 0, canvas.width, height);
canvas.height = height;
ctx.putImageData(img, 0, 0);

Action 2 run correctly on my PC (windows 11) in the past few days, but when I deploy my app on my server (Ubuntu 20.04) it went wrong...and then also on my PC >_<

Can skia-canvas do this? How? thx

Trugamr commented 2 years ago

Text can be measured before drawing which gives you the height that text would take after it's drawn.

This function takes array of lines which is returned by using measureText() which can be used to calculate height of the text.

function getTextHeight(lines: TextMetricsLine[]) {
  const last = lines[lines.length - 1]
  return last.y + last.height
}

Example

const canvas = new Canvas(600, 0)
const context = canvas.getContext('2d')

const text = 'what do you call a dog that can do magic tricks? a labracadabrador 🐶✨'

// Measure text size
context.font = `bold 48px poppins`
context.textWrap = true
context.textBaseline = 'top'
const metrics = context.measureText(text, canvas.width)
const height = getTextHeight(metrics.lines)

// Update canvas height
canvas.height = height

// Canvas background
context.fillStyle = '#fafafa'
context.fillRect(0, 0, canvas.width, canvas.height)

// Draw text
context.font = `bold 48px poppins`
context.textWrap = true
context.textBaseline = 'top'
context.fillStyle = '#0e0e0e'
context.fillText(text, 0, 0, canvas.width)

// Save as png
canvas.saveAs(path.resolve(__dirname, '..', 'generated.png'), {
  format: 'png',
})

Resulting Image

image