ApeironTsuka / node-webpmux

A mostly 1:1 re-implementation of webpmux as a Node module in pure Javascript. Only thing currently missing is a command-line version.
GNU Lesser General Public License v3.0
28 stars 8 forks source link

Weird behavior extracting Frames #18

Closed Katreque closed 1 year ago

Katreque commented 1 year ago

Hello!

I'm trying to extract the frames from a animation using this:

const frame = await Webp.Image.getEmptyImage();
await frame.initLib();

await frame.setImageData(
    await anim.getFrameData(index)
);

await frame.save(path);

I get the frames, but mostly all of them are buggy:

image

I tried to use the demux. The noisy frames were fixed but the ones with white squares were not. However, I still want to use the setImageData because of the options its offers.

Am I doing something wrong?

I'm using the 3.1.3 version and node 16.18.0. (Using node 18.12.1 and 19.0.0, the initLib doesn't work. Looks like a problem with node's builtin fetch)

ApeironTsuka commented 1 year ago

Judging purely on those thumbnails, the striped/noisy ones are likely from putting, for instance, 10x10 image data inside of a 20x20 image. setImageData takes your word for it on if the data provided matches the width/height provided. The thing with WebP animations is that every frame can have its own width/height and x/y offset within the overall animation size. As for the white squares... that's likely an artifact from an optimizing algorithm since the frames are generally (but not always) drawn on top of previous frame, and those squares are probably transparent. Saves some on the file size.

This library isn't really for extracting rendered frames, only raw ones in this context. You could use ImageMagick or node-canvas to construct rendered frames from them relatively easily. With node-canvas, something like below could work.

const { createImageData, createCanvas } = require('canvas'), { Image } = require('node-webpmux');
let canvas = createCanvas(anim.width, anim.height), ctx = canvas.getContext('2d');
let anim = new Image(), output;
/* ..... */
await Image.initLib();
await anim.load(path);
output = await Image.getEmptyImage();
canvas.fillStyle = `rgba(${anim.bgColor.join(',')})`;
for (let i = 0, { frameCount, frames } = anim; i < frameCount; i++) {
  let frame = frames[i], data = createImageData(await anim.getFrameData(i), frame.width, frame.height);
  // clear the frame back to anim.bgColor if needed
  if (frame.dispose) { ctx.clearRect(0, 0, anim.width, anim.height); ctx.fillRect(0, 0, anim.width, anim.height); }
  // blend with alpha or draw directly over
  if (frame.blend) { ctx.globalAlpha = 1.0; } else { ctx.globalAlpha = 0.0; }
  // draw the frame at the frame's offset
  ctx.putImageData(data, frame.x, frame.y);
  data = ctx.getImageData(0, 0, anim.width, anim.height);
  await output.setImageData(data, { width: anim.width; height: anim.height }); // set other options as desired
  await output.save(framePath);
}

This is only an example, though - I can't guarantee the above code works (I didn't test it) but it gets the idea across if you have a better/easier library for image drawing.

Katreque commented 1 year ago

Thanks for the insight. Helped me a lot!

I used Sharp to get the frames as it can return a buffer. I did what a needed to the buffers then I saved to a array after running generateFrame. Then I a recreate the animations from scratch using Webp.Image.save and the array of frames.

All good! Thanks again.

DanielZanchi commented 1 year ago

@ApeironTsuka I am trying to extract a frame from an animated webp this way:

const fs = require('fs');
const WebP = require('node-webpmux');

async function extractFrame() {
  const img = new WebP.Image();
  const filePath = 'anim12.webp';
  const frame1Path = 'frame1.webp';

  await img.load(filePath);
  await img.demux(frame1Path, { frame: 3 });
}

extractFrame().catch(console.error);

But it doesn't save anything in that folder. What am I doing wrong?

ApeironTsuka commented 1 year ago

@DanielZanchi The syntax to demux() is incorrect here for this usage, you'll want img.demux({ path: '.', frame: 3 }) and the output will be named 'anim12_3.webp'. demux() is mainly for extract frames into predictable names. You can, though I don't recommend it[1], directly use await img._demuxFrame(frame1Path, img.frames[3]) to have more direct control over what the filename is.

[1]. I may break it in the future, but I currently don't plan to. Functions starting with _ are considered private, rather than using the more modern private syntax in order to keep support with older Node versions that don't have support for private member functions.

DanielZanchi commented 1 year ago

@DanielZanchi The syntax to demux() is incorrect here for this usage, you'll want img.demux({ path: '.', frame: 3 }) and the output will be named 'anim12_3.webp'. demux() is mainly for extract frames into predictable names. You can, though I don't recommend it[1], directly use await img._demuxFrame(frame1Path, img.frames[3]) to have more direct control over what the filename is.

[1]. I may break it in the future, but I currently don't plan to. Functions starting with _ are considered private, rather than using the more modern private syntax in order to keep support with older Node versions that don't have support for private member functions.

Thanks, that works. In the readme the syntax is different.