Hi, I want to save the webgl canvas to image by using toDataURL or gl.readPixels and have some questions:
deck.redraw() to trigger the render and read from framebuffer. <DeckGL> in React and, according to the code the this.deck is not exposed from <DeckGL>, how could I achieve this without passing customized deck instance?ref.current.deck.redraw(true)You probably want to use a ref, e.g.:
<DeckGL
ref={ref => {
this.deck = ref;
}}
Then you can access deck.gl in the class through this.deck
@kylebarron Thank you, that makes sense. ref works here.
Hi, I'm glad I saw this issue. I'm facing the same issue here, wanna save deck.gl scatterLayer result to an image. But I am using plain JS, can you elaborate more on how to do this? Thank you!
My code looks like this:
<div id="deck-gl-canvas"></div>
$scope.scatterLayer = new deck.DeckGL({
container: 'deck-gl-canvas',
initialViewState: {
latitude: $scope.map.getCenter().lat,
longitude: $scope.map.getCenter().lng,
zoom: $scope.map.getZoom() - 1,
bearing: 0,
pitch: 0
},
controller: true,
layers: []
});
const deckGLScatterplot = new deck.ScatterplotLayer({
id: 'deck-gl-scatter',
data: $scope.points,
radiusMinPixels: Math.round($scope.pointRadius),
getPosition: d => [d[1], d[0]], // returns longitude, latitude, [altitude]
getRadius: d => $scope.pointRadius, // returns radius in meters
getFillColor: d => [0, 0, 255] // returns R, G, B, [A] in 0-255 range
});
$scope.scatterLayer.setProps(
{
layers: [deckGLScatterplot],
viewState:
{
latitude: $scope.map.getCenter().lat,
longitude: $scope.map.getCenter().lng,
zoom: $scope.map.getZoom() - 1,
bearing: 0,
pitch: 0
}
});
The general idea:
let screenshotCb = null;
const myDeckInstance = new deck.Deck({
...
glOptions: {
preserveDrawingBuffer: true // https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext
},
onAfterRender: () => {
if (screenshotCb) {
screenshotCb(myDeckInstance.canvas.toDataURL());
screenshotCb = null;
}
}
});
If you use the onAfterRender callback, you shouldn't need preserveDrawingBuffer (which has some performance implications), since it will be in the same requestAnimationFrame loop.
Hi @Pessimistress and @tsherif , thank you for your suggestions! Actually I figured out myself with using the html2canvas tool, and it's quite simple, just call the DeckGL.redraw(true) before capturing the canvas content. The code snippet is pasted here in case anyone in the future may also need this:
function screenshot() {
// force redraw of deckgl
$scope.scatterLayer.redraw(true);
// get the container div of deck.gl
let div = document.getElementById("deck-gl-canvas");
// use html2canvas tool to capture the content of the canvas inside the div and download it as an png file
html2canvas(div).then(canvas => {
document.body.appendChild(canvas);
let a = document.createElement('a');
// toDataURL defaults to png, so we need to request a jpeg, then convert for file download.
a.href = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
a.download = 'screenshot.png';
a.click();
});
}
Best,
Qiushi
Great! It doesn't look like you need that html2canvas tool though. Deck.gl maintains a reference to its canvas. I believe the rest of your code will work the same way, i.e.
let canvas = $scope.scatterLayer.canvas;
document.body.appendChild(canvas);
let a = document.createElement('a');
// toDataURL defaults to png, so we need to request a jpeg, then convert for file download.
a.href = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
a.download = 'screenshot.png';
a.click();
Anyone else getting a blank image when using the above ^ ? Thoughts on how to fix?
NM, forgot to redraw() before saving.
Most helpful comment
Great! It doesn't look like you need that
html2canvastool though. Deck.gl maintains a reference to its canvas. I believe the rest of your code will work the same way, i.e.