I am developing a small web-application with a threejs Renderer where I want to be able to store some screenshots to my local filesystem at a specific location. Rendering the images and creating the snapshots works, only the storage/download part gives me headache.
I create the snapshot this way:
let imageData = renderer.domElement.toDataURL('image/png');
console.log(imageData); // data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABFAAAANBC...
Now to download this, there are several methods: window.location.href = imageData;
does trigger a download (and several alert messages from the browser). But the downloaded image is stored as a file called download inside the Download-Folder. If added the File-Extension .png it can be opened. This is not a useful solution, several downloads in a row will hardly be assigned.
So another working method is to build a download-link and trigger its click-method:
let a_element = document.createElement('a');
a_element.setAttribute('href', imageData.replace('image/png', 'image/octet-stream');
a_element.setAttribute('download', 'screenshot.png');
a_element.style.display = 'none';
document.body.appendChild(a_element);
a_element.click();
document.body.removeChild(a_element);
This does download the screenshot with a custom-name to the Download folder, it's almost what I want, but i'd like to have them stored in a custom folder as well. That's where the Filesystem-API comes in. The drawback is, that every download has to be confirmed, but not having to move the files manually from the download folder would pay off for that.
const options = {
types: [
{
description: 'Images',
accept: {
'image/png': ['.png'],
},
},
],
suggestedName: 'Screenshot.png',
};
imgFileHandle = await window.showSaveFilePicker(options);
console.log("Save File chosen");
const writable = await imgFileHandle.createWritable();
await writable.write(imageData);
await writable.close();
This seems to work, but the file that is stored is not a valid image. It contains only Text (same as the output of the console). How should I treat the imageData to download it as an image ?
For the File System Access API, you're almost there, but instead of a data URL, you need to provide the data as a blob. To create a blob from a data URL, you can use the code below:
function dataURItoBlob(dataURI) {
const byteString = atob(dataURI.split(',')[1]);
const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
const blob = new Blob([ab], {type: mimeString});
return blob;
}
It may be easier to get the Blob
directly, via the toBlob()
method. This should work in Three.js as well, since renderer.domElement
is a CanvasElement
.