Best way to save to jpeg snapshots of scene

good day,
whats the best and recommended way to include in a scene a button for people to download a jpeg or png of the current camera view of the scene?

thank you :slight_smile:

Hi

Check this out… Simple an working. You can create html button and attach this function onClick

3 Likes

Or you can use Babylon GUI button of course :smiley:

1 Like

Low-level answer, if needed:

canvas element has .toDataURL() function, which returns base64-encoded image of the canvas contents. By default, it returns png image, but you can pass optional parameter, e.g. 'image/jpeg' for different format.

If you want to obtain canvas snapshot, even if the scene is not declared with preserveDrawingBuffer: true, as the documentation suggests, you can do so by calling toDataURL() immediately after scene.render() in your render loop. So if you want to react on button click, raise a flag, check the flag after scene.render(), obtain snapshot, clear the flag.

And a StackOverflow thread how to trigger a download with the image data.

3 Likes

Here is small example with GUI and HTML button with the same function - Babylon.js SCREENSHOT MAKER Prototype

2 Likes

this is a great community, thank you @nogalo @labris @atmin for great suggestions, and yes this works, excellent:

Tools.CreateScreenshotUsingRenderTarget(
scene.getEngine(),
scene.activeCamera,
{ width: 1920, height: 1080 },
undefined,
‘image/png’,
8,
false,
‘screenshot.png’
);

thank you again :slight_smile:

@nogalo @labris @atmin there is an issue, so the command below works perfect for me on my laptop, perfect, downloads awesome snapshots. But I just tried it on an android xiaomi mi a2 phone, and it downloads the image but empty on the mobile phone, any idea why it would work well on laptop but not on mobile?

Tools.CreateScreenshotUsingRenderTarget(
scene.getEngine(),
scene.activeCamera,
{ width: 2500 },
// { width: 1920, height: 1080 },
undefined,
‘image/png’,
8,
false,
‘screenshot.png’
);

@nogalo @labris @atmin so I found the reason, i need to set this in the engine:

{ preserveDrawingBuffer: true, stencil: true },

and then it also works well on mobile.

But the problem is that performance seems to suffer when using
{ preserveDrawingBuffer: true, stencil: true },

isnt there an alternative to take screenshots without having to decrease performance by activating the { preserveDrawingBuffer: true, stencil: true } options?

1 Like

You can try to call CreateScreenShotUsingRenderTarget in a engine.onEndFrameObservable observable and see if it helps.

Another alternative, my suggestion as (untested) application code:

let snapshotRequested = false;
document.getElementById('requestSnapshotButton').addEventListener('click', () => {
  snapshotRequested = true;
});
engine.runRenderLoop(() => {
  scene.render();
  if (snapshotRequested) {
    snapshotRequested = false;
    const imageData = document.getElementById('babylonCanvas').toDataURL();
    const a = document.createElement('a');
    a.setAttribute('download', 'snapshot.png');
    a.setAttribute('href', imageData.replace('image/png', 'application/octet-stream'));
    a.click();
  }
});

I tested the approach by setting a breakpoint on scene.render(), stepping over it, executing copy(document.querySelector('canvas').toDataURL()) on dev console and pasting the image data in a browser address bar.
If I execute the above copy not immediately after scene.render() while JS event loop is stopped, I get empty image.

1 Like

@nogalo @labris @atmin @Evgeni_Popov thank you all for your great help, good news, the suggestion from Evgeni seems to work well, I did this:

on save button click:
const saveScene = (scene) =>{
saveNow=1;
};

and separately I declared:

scene.getEngine().onEndFrameObservable.add(function () {
if (saveNow===1){
saveNow=0;
Tools.CreateScreenshotUsingRenderTarget(
scene.getEngine(),
scene.activeCamera,
{ width: 1200 },
undefined,
‘image/png’,
8,
false,
‘test.png’
);
}
});

and yes, this is working both in mobile android phone and in laptop! great,
I hope this strategy is stable enough that it will work in all phones and laptops…

2 Likes

Here is the small example of recursive scene screenshots, just for fun - https://playground.babylonjs.com/#WEICS8#3 :slight_smile:

3 Likes

@nogalo @labris @atmin @Evgeni_Popov
reporting back, I increased resolution to 2500px and now in mobile phone i got an empty save again, so mmm not yet working well enough, maybe if i increase resolution it takes too long to capture the frame and then it fails

That should not be noticeably longer whatever the resolution you choose. However, you should make sure your mobile does support the size you pass in. Maybe the max texture size is 2048px?

1 Like

Seems that may depend on device and its limitations.

1 Like

@Evgeni_Popov @labris good points, im gonna test below 2048 and see what happens :wink:

Mobile devices have some limitations. From my experience best resolution to use in 3D apps for mobile phones (especially on iOS) is max 1k. You can have few 2k here and there, depending on the number of textures in the app. But making sure that you have no textures over 1k pretty much secures that your app will work.

Now, is this a reason for your issue? Without some deep debugging, I don’t know.

2 Likes

@Evgeni_Popov @labris @nogalo happy to report that i changed resolution to 1920pixels and now the mobile phone generated twice the saving well,
so maybe its what you say, limitations of the mobile phone, i guess i can detect if i am in mobile, and set a resolution for mobiles and a different one for laptops

I think you should take as a maximum texture size the minimum of engine.getCaps().maxRenderTextureSize and engine.getCaps().maxTextureSize.

@Evgeni_Popov great suggestion, just tried it on my laptop and both numbers give: 16384,
will see what happens in mobile with those numbers