41

I have a three.js scene like the following:

            var scene = new THREE.Scene();
            var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);

            var renderer = new THREE.WebGLRenderer();
            renderer.setSize(window.innerWidth, window.innerHeight);
            document.body.appendChild(renderer.domElement);

            var geometry = new THREE.BoxGeometry(1,1,1);
            var material = new THREE.MeshBasicMaterial({color: 0x00ff00});
            var cube = new THREE.Mesh(geometry, material);
            scene.add(cube);

            camera.position.z = 5;

            var render = function () {
                requestAnimationFrame(render);

                cube.rotation.x += 0.1;
                cube.rotation.y += 0.1;

                renderer.render(scene, camera);
            };

            render();

Is it possible to make a 2D SnapShot or ScreenShot from a Scene and export it as a JPG Image?

Michael
  • 32,527
  • 49
  • 210
  • 370
  • couple of resources to help you with that: http://stackoverflow.com/questions/16431318/webgl-single-frame-screenshot-of-webgl and http://stackoverflow.com/questions/15558418/how-do-you-save-an-image-from-a-three-js-canvas – gaitat Oct 04 '14 at 14:26
  • 2
    initialize webgl context with preserveDrawingBuffer flag set to true and use `yourCanvas.toDataURL()`. – LJᛃ Oct 04 '14 at 16:40
  • @LJ_1102 Could you please post an example? – Michael Oct 04 '14 at 21:42
  • dupes include https://stackoverflow.com/questions/34847293/threejs-canvas-todataurl-is-blank, https://stackoverflow.com/questions/15558418/how-do-you-save-an-image-from-a-three-js-canvas, https://stackoverflow.com/questions/26431862/three-js-canvas-todataurl-sometimes-blank – gman Dec 19 '19 at 11:56

1 Answers1

70

There are a couple of things you will have to do to save the frame as a jpg image.

Firstly initialize the WebGL context like this

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

preserveDrawingBuffer flag will help you to get the base64 encoding of the current frame The code for that will be something like this

var strMime = "image/jpeg";
imgData = renderer.domElement.toDataURL(strMime);

Now secondly you might want to save the file using a .jpg extension, but not all browsers allow you to specify the file name. The best solution I found was in this SO thread.

So our script will check if the browser allows it will create a new anchor element and set its download and click it(which will save the file in a specified filename) else it will just download the file but the user will have to rename it with a .jpg extension to open it.

Codepen Link

var camera, scene, renderer;
var mesh;
var strDownloadMime = "image/octet-stream";

init();
animate();

function init() {

    var saveLink = document.createElement('div');
    saveLink.style.position = 'absolute';
    saveLink.style.top = '10px';
    saveLink.style.width = '100%';
    saveLink.style.background = '#FFFFFF';
    saveLink.style.textAlign = 'center';
    saveLink.innerHTML =
        '<a href="#" id="saveLink">Save Frame</a>';
    document.body.appendChild(saveLink);
    document.getElementById("saveLink").addEventListener('click', saveAsImage);
    renderer = new THREE.WebGLRenderer({
        preserveDrawingBuffer: true
    });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
    camera.position.z = 400;

    scene = new THREE.Scene();

    var geometry = new THREE.BoxGeometry(200, 200, 200);


    var material = new THREE.MeshBasicMaterial({
        color: 0x00ff00
    });

    mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);

    window.addEventListener('resize', onWindowResize, false);
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
    requestAnimationFrame(animate);

    mesh.rotation.x += 0.005;
    mesh.rotation.y += 0.01;

    renderer.render(scene, camera);
}

function saveAsImage() {
    var imgData, imgNode;

    try {
        var strMime = "image/jpeg";
        var strDownloadMime = "image/octet-stream";

        imgData = renderer.domElement.toDataURL(strMime);

        saveFile(imgData.replace(strMime, strDownloadMime), "test.jpg");

    } catch (e) {
        console.log(e);
        return;
    }

}

var saveFile = function (strData, filename) {
    var link = document.createElement('a');
    if (typeof link.download === 'string') {
        document.body.appendChild(link); //Firefox requires the link to be in the body
        link.download = filename;
        link.href = strData;
        link.click();
        document.body.removeChild(link); //remove the link when done
    } else {
        location.replace(uri);
    }
}
html, body {
    padding:0px;
    margin:0px;
}
canvas {
    width: 100%;
    height: 100%
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r69/three.min.js"></script>
<script src="http://threejs.org/examples/js/libs/stats.min.js"></script>

Edit Dec 2022

As Mohammad Tbeishat pointed out in a comment this is a more preformat API available now canvas.toBlob you can refer to at

https://r105.threejsfundamentals.org/threejs/lessons/threejs-tips.html

Harsh Patel
  • 6,334
  • 10
  • 40
  • 73
Shiva
  • 6,677
  • 4
  • 36
  • 61
  • If I get you right the files are always saved as .jpg even in browsers that does not support it? Do you know a way how I can optimize the exported image without reducing the quality in the browser? On desktop I take imageoptim https://imageoptim.com/ which works great on MAC. The exported image can be reduced by 35% in size without loosing quality. Do you have any idea how to do this in the browser? – Michael Oct 04 '14 at 23:41
  • 1
    @confile: If the browser supports then the file will be saved in the given filename like `example.jpg` but if the browser don't support it the file will be saved with some random auto generated filename like `ac2wz43` , without the `.jpg` extension so the user will have to manually rename it to something with a `.jpg` extension to open it.you can check the compatibility of download attribute here http://caniuse.com/#feat=download – Shiva Oct 04 '14 at 23:55
  • @confile: I am not sure about how can we optimize the image completely on the client side, but you can search for other javascript libraries that may be able to do that. – Shiva Oct 04 '14 at 23:57
  • @shiva: JS Fiddle Link is not working, i would request you to please correct it. I need to have a look at working example. Thank you. – kautuksahni Aug 11 '16 at 12:25
  • @kautuksahni: Thanks for letting me know, I have updated the link to codepen as JSFiddle removed the link, also added a snippet it in the post – Shiva Aug 11 '16 at 13:27
  • @Shiva can you please help me with Similar type of Question :-http://stackoverflow.com/q/38896297/6433590 – kautuksahni Aug 12 '16 at 06:17
  • 2
    @Shiva - Your answer is great, thanks a lot! Wish I could give a +5 instead of +1! – Matteo Feb 01 '17 at 02:00
  • 2
    You do not need to set `preserveDrawingBuffer: true`. You just need to render and capture in the same event. You can do that either by adding a flag to your render loop or by calling renderer.render just before calling `toDataURL` – gman May 22 '19 at 10:51
  • 1
    According to the documentation: "The old one canvas.toDataURL and the new better one canvas.toBlob" https://r105.threejsfundamentals.org/threejs/lessons/threejs-tips.html – Mohammad Tbeishat Nov 09 '22 at 10:43
  • thanks @MohammadTbeishat : Added the link to the answer. – Shiva Dec 09 '22 at 20:39