48

i am using node.js to save canvas to image img in writeFile is extractedby using toDataURL on my canvas element. it doenot save file here is my code

var fs = IMPORTS.require('fs');
var path = IMPORTS.require('path');
path.exists('images/', function(exists){
    if (exists) {

        fs.writeFile('images/icon.png', img, function(err){
            if (err) 
                callback({
                    error: false,
                    reply: err
                });
            console.log('Resized and saved in');
            callback({
                error: false,
                reply: 'success.'
            });
        });
    }
    else {
        callback({
            error: true,
            reply: 'File did not exist.'
        });
    }
 });    
madiha.athar
  • 1,103
  • 3
  • 12
  • 25
  • Can you include your canvas code or the url you are testing so that we can run your code without having to write any boilerplate? (see http://sscce.org/) You may also want to elaborate on your description a bit if you want people to help. – timoxley May 03 '11 at 14:41

2 Answers2

59

Here is a literal example of how to save canvas data to a file in Nodejs. The variable img is a string generated by canvas.toDataURL(). I've assumed you already know how to POST that string from the browser to your Nodejs server.

HTML snippet that generates the sample image I used:

<canvas id="foo" width="20px" height="20px"></canvas>
<script>
var ctx = $('#foo')[0].getContext('2d');
for (var x = 0; x < 20; x += 10) {
    for (var y = 0; y < 20; y += 10) {
        if (x == y) { ctx.fillStyle = '#000000'; }
        else { ctx.fillStyle = '#8888aa'; }
        ctx.fillRect(x, y, 10, 10);
    }
}
console.log($('#foo')[0].toDataURL());
</script>

Nodejs snippet to decode the base64 data and save the image:

const fs = require("fs").promises;

(async () => {
  
  // string generated by canvas.toDataURL()
  const img = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0"
      + "NAAAAKElEQVQ4jWNgYGD4Twzu6FhFFGYYNXDUwGFpIAk2E4dHDRw1cDgaCAASFOffhEIO"
      + "3gAAAABJRU5ErkJggg==";
  
  // strip off the data: url prefix to get just the base64-encoded bytes
  const data = img.replace(/^data:image\/\w+;base64,/, "");
  
  const buf = Buffer.from(data, "base64");
  await fs.writeFile("image.png", buf);
})();

Output:

enter image description here

ggorlen
  • 44,755
  • 7
  • 76
  • 106
samplebias
  • 37,113
  • 6
  • 107
  • 103
  • 1
    Thanks! I didn't know i needed a `Buffer`, i was just trying to use `base64.decode()` with the base64 data, but generated a corrupt file. – Husky Jun 12 '11 at 15:05
  • The server code example works for me. Can someone explain me why it doesn't work with this data: http://pastebin.com/raw.php?i=3yhGiQWR –  Feb 21 '14 at 10:38
  • I got buffer undefined – Krishna Aug 19 '16 at 13:30
  • 2
    for me, the `toDataUrl()` string included plus signs which `body-parser` turned into whitespaces in `request.body`, so I had to replace them with plus signs again so that my png wasn't corrupt: `var data=img.slice(img.indexOf(',')+1).replace(/\s/g,'+');` – Michelle Norris Oct 28 '16 at 00:16
  • 4
    `new Buffer` has been deprecated [per nodejs](https://nodejs.org/en/docs/guides/buffer-constructor-deprecation/). In this case you should use `Buffer.from(data, 'base64');` now. – Chris Mar 11 '19 at 17:29
22

I believe that Canvas does not store it's data in Base64 internally. It should store data in some much more efficient binary format. So, obviously, transferring to base64 and back to binary PNG is terribly slow and memory consuming.

Quick googling to node canvas documentation gives:

To create a PNGStream simply call canvas.pngStream(), and the stream will start to emit data events, finally emitting end when finished. If an exception occurs the error event is emitted.

var fs = require('fs')
  , out = fs.createWriteStream(__dirname + '/text.png')
  , stream = canvas.pngStream();

stream.on('data', function(chunk){
  out.write(chunk);
});

stream.on('end', function(){
  console.log('saved png');
});

Currently only sync streaming is supported. If you want to do it async, you can try to use a worker or cluster (I personally never tried this).

Dan
  • 55,715
  • 40
  • 116
  • 154
  • The console.log('saved png') happens before the file is saved. Is this because only sync is supported? How could I get an event to fire _after_ the file has been saved? – hamncheez Feb 17 '17 at 22:59