dlemstra / magick-wasm

The WASM library for ImageMagick
Apache License 2.0
486 stars 34 forks source link

Node native modules #154

Open vincaslt opened 4 months ago

vincaslt commented 4 months ago

Is your feature request related to a problem? Please describe.

No response

Describe the solution you'd like

Would it be possible to package imagemagick as a node nativem module?

I'm trying to generate lots of images (video frames) and I'm not getting as good of a performance with WASM on node, as https://github.com/Automattic/node-canvas which I think uses compiled C code. It seems like imagick should be faster as it doesn't need to work with the abstraction of canvas.

Describe alternatives you've considered

No response

Additional context

No response

dlemstra commented 4 months ago

Not sure what you are trying to do? You can use this library with node.

vincaslt commented 4 months ago

I'm trying to generate a bunch of images sequentially. I'm writing them out to images, but might as well stream to ffmpeg or whatever else. From my test, the part that's taking the longest is the draw command. I'm not familiar with imagemagick at all, but I assume it rasterizes the image? I was wondering if there were ways to improve the performance over using canvas (Cairo or Skia based) by working directly with images in Nodejs.

import {
  DrawableFillColor,
  DrawableFontPointSize,
  DrawableStrokeColor,
  DrawableStrokeWidth,
  DrawableText,
  IMagickImage,
  Magick,
  MagickColors,
  MagickFormat,
  MagickImage,
  initializeImageMagick,
} from "@imagemagick/magick-wasm";
import { readFileSync } from "node:fs";
import { readFile, writeFile } from "node:fs/promises";

const totalFrames = 100;
const wasmLocation = "./src/magick.wasm";

async function main() {
  const wasmBytes = await readFileSync(wasmLocation);
  await initializeImageMagick(wasmBytes);

  const fontData = await readFile("./Calibri.ttf");
  Magick.addFont("Calibri", fontData);

  const promises: Promise<void>[] = [];

  console.log("Start rendering frames", totalFrames);
  const start = process.hrtime();
  const pointSize = new DrawableFontPointSize(64);
  const color = new DrawableFillColor(MagickColors.White);
  const stroke = new DrawableStrokeColor(MagickColors.Black);
  const strokeWidth = new DrawableStrokeWidth(2);

  for (let frameNum = 1; frameNum <= totalFrames; frameNum++) {
    const image = MagickImage.create(MagickColors.DarkRed, 1080, 1920);
    image.settings.font = "Calibri";
    const text = new DrawableText(100, 100, `Frame ${frameNum}`);
    image.draw(pointSize, color, stroke, strokeWidth, text);
    const promise = write(image, frameNum);
    promises.push(promise);
  }

  await Promise.all(promises);

  const elapsed = process.hrtime(start)[1] / 1000000; // divide by a million to get nano to milli
  console.log(
    `${process.hrtime(start)[0]}s ${Math.floor(
      elapsed
    )}ms - Finished writing files`
  );
}

function write(image: IMagickImage, frameNum: number) {
  return new Promise<void>((resolve) =>
    image.write(MagickFormat.Rgba, (data) => {
      const buffer = Buffer.from(data);
      console.log("Done frame", frameNum, buffer.byteLength);
      return writeFile(`out/${frameNum}.raw`, buffer).then(resolve);
    })
  );
}
dlemstra commented 4 months ago

I am a bit surprised that the draw method takes a long time. It also looks like you are not disposing the images which could result in memory issues when you have a lot of frames. It might help if you use label:Your text as the filename of the image instead and use the MagickReadSettings to set those settings.