52

How do you save an image from a Three.js canvas?

I'm attempting to use Canvas2Image but it doesn't like to play with Threejs. Since the canvas isn't defined until it has a div to attach the canvas object to.

http://ajaxian.com/archives/canvas2image-save-out-your-canvas-data-to-images

rjd
  • 1,656
  • 2
  • 16
  • 19

3 Answers3

80

Since the toDataURL is a method of canvas html element, that will work for 3d context too. But you have to take care of couple of things.

  1. Make sure when the 3D context is initialized you set preserveDrawingBuffer flag to true, like so:

    var context = canvas.getContext("experimental-webgl", {preserveDrawingBuffer: true});
    
  2. Then user canvas.toDataURL() to get the image

In threejs you would have to do the following when the renderer is instantiated:

new THREE.WebGLRenderer({
    preserveDrawingBuffer: true 
});

Also, keep in mind this can have performance implications. (Read: https://github.com/mrdoob/three.js/pull/421#issuecomment-1792008)

This is only for webgl renderer, in case of threejs canvasRenderer though, you can simply do renderer.domElement.toDataURL(); directly, no initialization parameter needed.

My webgl experiment: http://jsfiddle.net/TxcTr/3/ press 'p' to screenshot.

Props to gaitat, I just followed the link in his comment to get to this answer.

Flavien Volken
  • 14,820
  • 9
  • 78
  • 105
29

I read the conversation posted by Dinesh (https://github.com/mrdoob/three.js/pull/421#issuecomment-1792008) and came up with a solution that won't slow down your application.

    function render() { 
        requestAnimationFrame(render);
        renderer.render(scene, camera);
        if(getImageData == true){
            imgData = renderer.domElement.toDataURL();
            getImageData = false;
        }
    } 

With this you can leave the preserveDrawingBuffer-Flag at false and still get the image from THREE.js. Simply set getImageData to true and call render() and you are good to go.

getImageData = true;
render();
console.debug(imgData);

Hope this helps people like me who need the high fps :)

CapsE
  • 291
  • 3
  • 3
  • 3
    Basically it turns out that with threejs you need 2 calls one after another: renderer.render(scene, camera); renderer.domElement.toDataURL(); In many cases the simplest approach may be just to put them in some seperate function outside of the normal rendering loop. – dadasign Apr 29 '15 at 09:42
  • 2
    Use these settings to prevent having too much memory usage `.toDataURL( 'image/jpeg', 1.0 );` otherwise the imgData contains a raw image. It saved me from 12MB to 400Kb of space – Thomas Webber Sep 12 '19 at 14:32
  • I'm trying this version, but unfortunately I'm getting a black image. Does the render function in requestAnimationFrame call the function it is located in, or is this a render function provided by three (so renderer.render)? – DSz Nov 11 '20 at 12:06
  • @DSz it's been 6 years but if I remember correctly the render function is constantly calling itself to redraw (like games do for example) so animated objects or camera movement would show up on screen. This loop will check if it needs to genereate a "screenshot" everytime. Calling render() one additional time shouldn't have any impact on this loop. But again it has been 6 years. Chances are high you're using a newer version of three.js where things might be different. – CapsE Nov 13 '20 at 09:21
  • Thanks for coming back and answering. I'll try exploring more how to solve it! – DSz Nov 13 '20 at 16:58
0

use canvas to create the url, and then the same can be downloaded

function createImage(saveAsFileName) {

    var canvas = document.getElementById("canvas");

    var url = canvas.toDataURL();

    var link = document.createElement('a');

    link.setAttribute('href', url);
    link.setAttribute('target', '_blank');
    link.setAttribute('download', saveAsFileName);

    link.click();
}
Pranavka
  • 404
  • 1
  • 4
  • 11