31

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?

confile
  • 29,115
  • 44
  • 187
  • 340
  • 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
  • 1
    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? – confile 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

52

There are a couple of things you will have to do save the frame as 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 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";
            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>
Community
  • 1
  • 1
Shiva
  • 5,992
  • 4
  • 32
  • 56
  • 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? – confile Oct 04 '14 at 23:41
  • @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
  • 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