Upload file (HTML5 canvas image → file) in javascript - javascript

Attempting to upload an image as a file per these instructions: https://github.com/graphcool-examples/react-graphql/blob/master/files-with-apollo/src/components/CreatePage.js#L48-L65
The above-referenced instructions work on mobile and desktop/laptop:
handleDrop(files) {
let data = new FormData()
data.append('data', person.avatar)
fetch('https://api.graph.cool/file/v1/___PROJECTID___', {
method: 'POST',
body: data
})
[...]
}
However, if instead of directly uploading an image, I want to crop it first. So I:
I save the file (using React) in the local state first,
Crop it,
Then upload it.
However, this process only seems to work on a desktop/laptop, but not on mobile. For mobile, an empty image is produced, with the error InvalidStateError (DOM Exception 11): The object is in an invalid state.
I wonder if it has to do with file storage limitations for mobile. Could a potential solution perhaps be to use FileReader?
Here’s the code that works on a desktop/laptop, but not on mobile:
handleDrop(files) {
// First save file to local state
this.setState({ file: file[0] })
}
// Image is then cropped, upon which handleCrop() is called
handleCrop() {
// This returns a HTMLCanvasElement, it can be made into a data URL or a blob, drawn on another canvas, or added to the DOM.
const image = this.refs.avatar.getImageScaledToCanvas().toDataURL()
// Custom DataURLtoBlob() function
const blob = DataURLtoBlob(image)
let file = new File([blob], 'avatar.png', {
lastModified: new Date(),
type: "image/png"
})
let data = new FormData()
data.append('data', file)
fetch('https://api.graph.cool/file/v1/___PROJECTID___', {
method: 'POST',
body: data
})
[...]
}

Found the solution. Don’t convert the blob back into a File. Instead, just upload the blob 😳
handleDrop(files) {
// First save file to local state
this.setState({ file: file[0] })
}
// Image is then cropped, upon which handleCrop() is called
handleCrop() {
// This returns a HTMLCanvasElement, it can be made into a data URL or a blob, drawn on another canvas, or added to the DOM.
const image = this.refs.avatar.getImageScaledToCanvas().toDataURL()
// Custom DataURLtoBlob() function
const blob = DataURLtoBlob(image)
let data = new FormData()
data.append('data', blob)
fetch('https://api.graph.cool/file/v1/___PROJECTID___', {
method: 'POST',
body: data
})
[...]
}

Related

Store canvas screenshot to local filesystem with Filesystem API

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.

play a blob video file

imagine i have a video file and i want to build a blob URL from that file then play it in a html page, so far i tried this but i could not make it work ...
var files = URL.createObjectURL(new Blob([someVideoFile], {type: "video/mp4"}));
document.getElementById(videoId).setAttribute("src", files);//video tag id
document.getElementById(videoPlayer).load();//this is source tag id
document.getElementById(videoPlayer).play();//this is source tag id
it gives me a blob URL but wont play the video... am i doing something wrong? i am pretty new to electron so excuse me if my code is not good enough
i saw the similar questions mentioned in comments but they dont work for me as they dont work for others in those pages....
I know this is an old question, but it still deserves a working answer.
In order to play a video in the renderer context, you're on the right track: you can use a blob url and assign it as the video source. Except, a local filepath is not a valid url, which is why your current code doesn't work.
Unfortunately, in electron, currently there are only 3 ways to generate a blob from a file in the renderer context:
Have the user drag it into the window, and use the drag-and-drop API
Have the user select it via a file input: <input type="file">
Read the entire file with the 'fs' module, and generate a Blob from it
The third option (the only one without user input) can be done as long as nodeIntegration is enabled or if it is done in a non-sandboxed preloader. For accomplishing this via streaming vs. loading the entire file at once, the following module can be used:
// fileblob.js
const fs = require('fs');
// convert system file into blob
function fileToBlob(path, {bufferSize=64*1024, mimeType='aplication/octet-stream'}={}) {
return new Promise((resolve,reject) => {
// create incoming stream from file
const stream = fs.createReadStream(path, {highWaterMark:bufferSize});
// initialize empty blob
var blob = new Blob([], {type:mimeType});
stream.on('data', buffer => {
// append each chunk to blob by building new blob concatenating new chunk
blob = new Blob([blob, buffer], {type:mimeType});
});
stream.on('close', () => {
// resolve with resulting blob
resolve(blob);
});
});
}
// convert blob into system file
function blobToFile(blob,path, {bufferSize=64*1024}={}) {
return new Promise((resolve,reject) => {
// create outgoing stream to file
const stream = fs.createWriteStream(path);
stream.on('ready', async () => {
// iterate chunks at a time
for(let i=0; i<blob.size; i+=bufferSize) {
// read chunk
let slice = await blob.slice(i, i+bufferSize).arrayBuffer();
// write chunk
if(!stream.write(new Uint8Array(slice))) {
// wait for next drain event
await new Promise(resolve => stream.once('drain', resolve));
}
}
// close file and resolve
stream.on('close', () => resolve());
stream.close();
});
});
}
module.exports = {
fileToBlob,
blobToFile,
};
Then, in a preloader or the main context with nodeIntegration enabled, something like the following would load the file into a blob and use it for the video player:
const {fileToBlob} = require('./fileblob');
fileToBlob("E:/nodeJs/test/app/downloads/clips/test.mp4", {mimeType:"video/mp4"}).then(blob => {
var url = URL.createObjectURL(blob);
document.getElementById(videoId).setAttribute("src", url);
document.getElementById(videoPlayer).load();
document.getElementById(videoPlayer).play();
});
Again, unfortunately this is slow for large files. We're still waiting for a better solution from electron:
https://github.com/electron/electron/issues/749
https://github.com/electron/electron/issues/35629
Try
video.src = window.URL.createObjectURL(vid);
For more details please refer to this answer

image url to file() object using js

For a registration module in my vue app I let users upload images in a form. I upload these images to my storage and save the download url of this photo in the registration. When editing a registration I need to get the photo's out of the storage which is simple since I got the url. But I need it to be a file() object. I have found ways to turn it into a blob but it needs to be a file. How can I do this?
It can be done by requesting a blob and generating a File object. It is necessary to specify the MIME type of the blob.
const urlToObject= async()=> {
const response = await fetch(image);
// here image is url/location of image
const blob = await response.blob();
const file = new File([blob], 'image.jpg', {type: blob.type});
console.log(file);
}
The ES6 way, with Promise:
const blobUrlToFile = (blobUrl:string): Promise<File> => new Promise((resolve) => {
fetch(blobUrl).then((res) => {
res.blob().then((blob) => {
// please change the file.extension with something more meaningful
// or create a utility function to parse from URL
const file = new File([blob], 'file.extension', {type: blob.type})
resolve(file)
})
})
})
Since you are letting the user upload a file, you already have the file as a File object.
But if you wanna convert it to a blob for making some edits and convert it back to a File object, you can use the File() constructor for converting a blob to a File.
const file = new File([blob], "imagename.png");
Also, notice that the File() constructor takes an array of blobs as argument and not a single blob.

JS: How to create a file of type image, from base64

I'm trying to convert png file, produced by https://html2canvas.hertzen.com/ to a blob file, that I'd send to API.
Code below produces such output that API does not throw 400, however the file is somehow corrupted.
Is there something wrong with the way I construct the blob?
const data = new FormData();
const [, binary] = image.toDataURL().split(',');
const blobFile = new Blob([window.atob(binary)], { type: 'image/png' });
data.append('attachments[]', blobFile, 'screenshot.png');
Alright, turns out canvas is already well equipped for translating itself to blob. All you have to do is use canvas.toBlob(cb) and you are ready to go.

Passing a Cordova camera.getPicture File to AWS S3 (JS SDK)

I am trying to pass an image created by Cordova's camera plugin to Amazon Web Service's S3.
In the past, I have used the HTML File API to create my S3 params, and been able to pass the file object. I can't directly link to how you do this, but there is an example on this page under the section 'example uses the HTML5 File API to upload a file on disk to S3'.
But this file has not been inserted by an input element, so I can't access anything like files[0] - the file is returned by Cordova either as a base64 or an available file location. So I am trying to figure out how I would replicate that action using Cordova information.
The solution I have worked off of is found here which results in the code below:
function onSuccess(imageData) {
window.resolveLocalFileSystemURL(imageData, function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(evt) {
var theBody = btoa(evt.target._result);
var 3_params = {
Bucket: 'the-Bucket',
Key: 'the-Key',
ContentType: file.type,
Body: theBody
}
...other S3 SDK code...
};
reader.readAsDataURL(file);
}
}
}
This process works, but:
I have to take _result (which is a base64) through btoa which means...
It is pretty slow.
It results in larger file sizes than I should need
It also requires that I use getObject instead of getSignedURL
When I get each object, I then have to put it through atob which means...
If I have several objects to get things go VERY slow
What I would like to do is send the actual file object instead of messing with this base64. Alternatively, I would take a better/smarter/faster way to handle the base64 to S3 process.
I was able to use the 'JavaScript Canvas to Blob' polyfill to create a blob from my Base64, and then send the returned Blob on to S3. Because it is now a blob instead of a coded string, I can use getSignedURL in the s3 APK to reference the image.
The only change from above is:
var theBody = btoa(evt.target._result);
Becomes
var theBody = window.dataURLtoBlob(evt.target._result);
https://github.com/blueimp/JavaScript-Canvas-to-Blob
For those who find this, there is a better way since the new features introduced in XMLHttpRequest and now we can use readers and Blob as a source.
The initial code now could be written using FileReader.readAsArrayBuffer method and passing Blob object as the body source:
function onSuccess(imageData) {
window.resolveLocalFileSystemURL(imageData, function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(evt) {
var blob = new Blob([new Uint8Array(this.result)], { type: file.type });
var 3_params = {
Bucket: 'the-Bucket',
Key: 'the-Key',
ContentType: file.type,
Body: blob
}
...other S3 SDK code...
};
reader.readAsArrayBuffer(file);
}
}
}
More info can be found at Cordova Blog - Transition off of cordova-plugin-file-transfer

Categories

Resources