Automattic / node-canvas

Node canvas is a Cairo backed Canvas implementation for NodeJS.
10.19k stars 1.17k forks source link

Missing sync canvas.toDataURL("image/jpeg") API #1146

Closed challakoushik closed 6 years ago

challakoushik commented 6 years ago

I am using jsdom and node-canvas to run frontend html canvas code within node js, one of my script files has the line canvas.toDataURL('image/jpeg',{quality:1},function(){})

the third parameter is because node-canvas demands 3 parameters over here at line 301 in canvas.js

else if ('image/jpeg' === type) {
    if (undefined === fn) {
      throw new Error('Missing required callback function for format "image/jpeg"');
    }

    var stream = this.jpegStream(opts);
    // note that jpegStream is synchronous
    var buffers = [];
    stream.on('data', function (chunk) {
      buffers.push(chunk);
    });
    stream.on('error', function (err) {
      fn(err);
    });
    stream.on('end', function() {
      var result = 'data:image/jpeg;base64,' + Buffer.concat(buffers).toString('base64');
      fn(null, result);
    });
  }

but jsdom passes only two arguments to this, and it might not be their fault since the official frontend canvas.toDataURL is supposed to take only 2 arguments. I need to generate pdfs using jpg images generated by canvas in node. Would appreciate any help

zbjornson commented 6 years ago

You're right, "image/jpeg" currently only has async support, and the async API is non-standard.

Until that's fixed, you could use canvas.jpegStream() to create your JPEG buffers (in the same way that's done in the code that you pasted).

challakoushik commented 6 years ago

@zbjornson , yes, but again the canvas on which I'm using toDataURL() is an htmlcanvas element, which goes through jsdom and then goes to node canvas, and jsdom does not have jpegStream()

so is there a way i can create a node-canvas object using a html canvas element. If so, that would solve the issue

zbjornson commented 6 years ago

Sorry -- I meant you could do that by accessing the node-canvas Canvas instance that jsdom wraps, which I think is just canvas._getCanvas().

They actually provide their own implementation of toBlob that supports image/jpeg that you might be able to use: https://github.com/jsdom/jsdom/blob/2e90c57c282eb06023fab09304e29622268b91cb/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js#L62-L86

challakoushik commented 6 years ago

@zbjornson the _getCanvas does not exist in my HTMLCanvasElement, this method belongs to the HTMLCanvasElementImpl class as in HTMLCanvasElementImpl.js. I am unable to find the HTMLCanvasElementImpl instance in my HTMLCanvasElement

zbjornson commented 6 years ago

My bad. Looks like you'd have to use the method described here to get to the canvas instance: https://github.com/jsdom/jsdom/issues/2067.

challakoushik commented 6 years ago

The toBlob method worked. The code below did the job for me

canvas.toBlob(function(result){
    var buffer = result[Object.getOwnPropertySymbols(result)[0]]._buffer;
    fs.writeFileSync('images/temp.jpg',buffer)
},'image/jpeg',1)

Thanks for helping me out!! but I had a minor concern in line 71 of https://github.com/jsdom/jsdom/blob/2e90c57c282eb06023fab09304e29622268b91cb/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js#L62-L86 isn't Math.min(0, Math.max(1, qualityArgument)) * 100 always supposed to return 0? haha! The image generated looks fine I'm not sure if its of max quality now because of that line.

PS: I submitted an issue in jsdom https://github.com/jsdom/jsdom/issues/2200

zbjornson commented 6 years ago

Glad that worked.

That does look wrong :) -- you could open an issue with jsdom for that.