eugeneware / gifencoder

Server side animated gif generation for node.js
Other
472 stars 49 forks source link

Writing to base64 #30

Closed andyrichardson closed 6 years ago

andyrichardson commented 6 years ago

Hi there, first off, thanks for creating the package.

I'm currently trying to create a gif from a canvas element and turn it into a base64 string (all within the browser). When parsing the output of the readstream, the resulting base64 string is invalid.

Reproduction

const encoder = new GifEncoder(200, 200);
const stream = encoder.createReadStream();

// Create base64
let base64 = 'data:image/gif;base64,';
stream.on('data', d => {
  base64 = base64.concat(d.toString('base64'));
});

// Print base64
stream.on('end', () => {
  console.log(base64);
});

// Init encoder
encoder.start();
encoder.setRepeat(0);
encoder.setDelay(500);
encoder.setQuality(10);

// Create canvas
const canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 200;

// Add text and frame
canvas.getContext('2d').strokeText('Hello World', 10, 50);
encoder.addFrame(canvas);

// Encode
encoder.finish();

Output

Expected output Valid base64 string

Actual output

click to expand data:image/gif;base64,R0lGODlhyADIAPcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAiEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQAMgAAACwAAAAAyADIAAAI/gAhCBxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzatzIsaPHjyBDihxJsqTJkyhTqlzJsqXLlzBjypxJs6bNmzhz6tzJs6fPn0CDCh1KtKjRo0iTKl3KtKnTp1CjSp1KtarVq1izat3KtavXr2DDih1LtqzZs2jTql3Ltq3bt3Djyp1Lt67du3jz6t3Lt6/fv4ADCx5MuLDhw4gTK17MuLHjx5AjS55MubLly5gza97MubPnz6BDix5NurTp06hTq17NurXr17Bjy55Nu7bt27hz697Nu7fv38CDCx9OvLjx48iTK1/OvLnz59CjS59Ovbr169iza9/Ovbv37+DDR4sfT768+fPo06tfz769+/fw48ufT7++/fv48+vfz7+///8ABijggAQWaOCBCCao4IIMNujggxBGKOGEFFZo4YUYZqhhWAEBAA==Ow==

Other notes

I believe there is nothing wrong with the base64 translation method itself as the initial sequence R0lGOD... matches that of an example base64 string.

andyrichardson commented 6 years ago

I seem to have worked out the problem.

For anyone else who has this issue, the was to use Blobs and then read them using the file reader (solution below).

//  Add data cache
const data = [];   
const encoder = new GifEncoder(200, 200);
const stream = encoder.createReadStream();

// Write data to cache
stream.on('data', d => {
  data.push(d);
});

// Create blob and read using FileReader
stream.on('end', () => {
  const blob = new Blob(data);
  const fr = new FileReader();
  fr.readAsDataURL(blob);
  fr.onloadend = () => console.log(fr.result);
});

encoder.start();
encoder.setRepeat(0);
encoder.setDelay(500);
encoder.setQuality(10);

const canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 200;

canvas.getContext('2d').strokeText('Hello World', 10, 50);
encoder.addFrame(canvas);

encoder.finish();
heikkipora commented 6 years ago

Good that you got it solved. The problem you had was caused by the fact that concatenating base64 encoded pieces does indeed produce invalid base64 (base64 digits represent 6 bits of data, so the encoder needs to add a bit of padding at the end of each block in case the source data length isn't exactly divisible by that unit size - and that will confuse the decoder).

You could also use toString('base64') as long as you call it only after all of the data has been received (in the 'end' event)

andyrichardson commented 6 years ago

@heikkipora thanks for the explaination.

I did actually try the approach of waiting until all data was collected, merging and then doing toString('base64') but that didn't seem to work. I assume it's somehow related to FileReader extracting / creating metadata in the base64 string - something that the toString function did not do.