Save canvas element to file [duplicate] - javascript

Having an HTML Form that is submittet via POST (user clicking the
submit button).
Furthermore having an image that is read via Javascript of a canvas
object (getImageData()).
Question:
How to "inject" this image data into the HTML Form, so that it gets uploaded as Content-Type:multipart/form-data and can be processed via the existing PHP Frameworks Data Extraction Logic?
Example from a <input type="file" upload captured with CHrome in a POST request => it should look like this
------WebKitFormBoundaryBBAQ5B4Ax1NgxFmD
Content-Disposition: form-data; name="images"; filename="fooimage.png"
Content-Type: image/png
Problem:
I know how to uploed it in a seperate request (via ajax, seperate from the form). I know how to upload it as base64 Data an process it manually in the form.
But I do not know how to send the Image Data along the exiting Form so that it looks for the PHP Serverside Scripts exactly the same as an image that is send via <input type="file"...
Reason: Symphony (FileUpload Object) checks if a file is uploaded via the POST Form and fails if I manulally instanciate the object with the data.
So the best solution would be (in regards to a lot of other things, like testing, avoiding unnecessary logik), if the data would be passed the same as a regular form upload. How to do this?
Thanks!

You can use a FormData object to get the values of your form, and then append a blob version of your canvas into the FormData.
This blob will be seen as a file by the server.
Unfortunately, all browsers still don't support the native canvas.toBlob() method, and even worth, all implementations are not the same.
All major browsers now support the toBlob method, and you can find a polyfill on mdn for older browsers.
// the function to create and send our FormData
var send = function(form, url, canvas, filename, type, quality, callback) {
canvas.toBlob(function(blob){
var formData = form ? new FormData(form) : new FormData();
formData.append('file', blob, filename);
var xhr = new XMLHttpRequest();
xhr.onload = callback;
xhr.open('POST', url);
xhr.send(formData);
}, type, quality);
};
// How to use it //
var form = document.querySelector('form'), // the form to construct the FormData, can be null or undefined to send only the image
url = 'http://example.com/upload.php', // required, the url where we'll send it
canvas = document.querySelector('canvas'), // required, the canvas to send
filename = (new Date()).getTime() + '.jpg',// required, a filename
type = 'image/jpeg', // optional, the algorithm to encode the canvas. If omitted defaults to 'image/png'
quality = .5, // optional, if the type is set to jpeg, 0-1 parameter that sets the quality of the encoding
callback = function(e) {console.log(this.response);}; // optional, a callback once the xhr finished
send(form, url, canvas, filename, type, quality, callback);
PHP side would then be :
if ( isset( $_FILES["file"] ) ){
$dir = 'some/dir/';
$blob = file_get_contents($_FILES["file"]['tmp_name']);
file_put_contents($dir.$_FILES["file"]["name"], $blob);
}

Related

What is the difference between file upload using FileReader and FormData?

There are two ways I can upload files using Ajax (XHR2). First, I can read the file content as array buffer or binary string and then simply stream using XHR send method. For example, as shown here:
function uploadFile(img, file) {
const reader = new FileReader();
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", function(e) {
if (e.lengthComputable) {
const percentage = Math.round((e.loaded * 100) / e.total);
// Do something with percentage
}
});
xhr.upload.addEventListener("load", (e) => console.log('Do something more'));
xhr.open("POST", "some-url");
xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
reader.onload = function(evt) {
xhr.send(evt.target.result);
};
reader.readAsBinaryString(file);
}
Second, I can use FormData to upload my file as shown here:
var formData = new FormData();
// HTML file input, chosen by user
formData.append("userfile", fileInputElement.files[0]);
var request = new XMLHttpRequest();
request.open("POST", "some-url");
request.send(formData);
Are the two methods equivalent? Is there any advantage of using FileReader instead of FormData? Is one more performant than the other?
First, there is a third option you omitted which is to send the File directly through xhr.send(file) just like you did with the ArrayBuffer.
That being said, there doesn't exist any possible advantage to first reading the file in memory through FileReader.
When doing a file upload from a File on disk, the browser doesn't load the full file in memory but streams it through the request. This is how you can upload gigs of data even though it wouldn't fit in memory. This also is more friendly with the HDD since it allows for other processes to access it between each chunk instead of locking it.
When reading the File through a FileReader you are asking the browser to read the full file to memory, and then when you send it through XHR the data from memory is being used. You are thus limited by the memory available, bloating it for no good reasons, and even asking the CPU to work here while the data could have gone from the disk to the network card almost directly.
As to what's the difference between formdata.append(file); xhr.send(formdata); and xhr.send(file), basically only request headers. The former will wrap the request as a multipart/form-data enctype request, while the latter will send it as is.
So you'd handle both requests differently on the receiving end.

how to upload separately selected files in form POST request? [duplicate]

This question already has answers here:
Uploading multiple files using formData()
(18 answers)
Closed 5 years ago.
What I want
People click add image button, they select an image, image is added to gallery.
They can delete images by clicking cross sign and re click add image button to add more images.
This all works, I've a reference to all File elements.
However, I can't figure out how to send the files in post request of form.
Problem the problem is that You can't create FileList out array of file, or set array of files as input.files = arrOfFiles.
Input element itself doesn't let you add more files, or remove files... it simply replaces old file with new file(s).
which is not what i want therefore i'm keep reference to file objects in js, and letting user remove images or add more.
I know i can send individual file as XHR, but I want to send them through form that already exists.
I wanted to know a way to send files through form not js, but apparently that's not possible
That's exactly what the FormData API is for: create from scratch a form's data that you will be able to upload to your server as if it were created from a <form> object, except that you can control what goes there or not.
So to append a File or a Blob in a FormData, the code is
var fd = new FormData();
fd.append(field_name, blob, file_name);
To append multiple files, you can call again fd.append, but note that backend often need to have the field_name formatted in such a way they can know multiple values are expected here.
Usually this is done by adding [] after your fieldname.
var fd = new FormData();
fd.append('files[]', blob_1, file_name_1);
fd.append('files[]', blob_2, file_name_2);
And then you can send it through an AJAX request to your server, which won't make the difference between this request and a real one made froma single <input multiple name="files[]">.
Note that in case of File, file_name is optional and will default to the File's name if not set. However, it is needed for Blobs if you don't want a random name to be set.
var file_1 = new File(['foo'], 'file1.txt',{type:'text/plain'});
var file_2 = new File(['bar'], 'file2.txt', {type:'text/plain'});
var fd = new FormData();
fd.append('files[]', file_1);
fd.append('files[]', file_2);
console.log(...fd.entries());
// and to send it to your server
var xhr = new XMLHttpRequest();
xhr.open('POST', 'your_server_url');
xhr.send(fd);
If you are just going to upload images. Consider using base64 encoding. That is what I did. Assign base64 encoded URL to stringify JSON, or whatever you wish that is easier for you.
When the data is POST-ed, do the conversion there, to get back your files for saving them. Decoding base64 will be dependent on your backend language though of which you did not mention in your question.
In JavaScript, you can use this function to load up the image in preview state like you mentioned.
function previewImage (inputId, eleId) {
if (window.FileReader) {
var oPreviewImg = null, oFReader = new window.FileReader(),
rFilter = /^(?:image\/bmp|image\/cis\-cod|image\/gif|image\/ief|image\/jpeg|image\/jpeg|image\/jpeg|image\/pipeg|image\/png|image\/svg\+xml|image\/tiff|image\/x\-cmu\-raster|image\/x\-cmx|image\/x\-icon|image\/x\-portable\-anymap|image\/x\-portable\-bitmap|image\/x\-portable\-graymap|image\/x\-portable\-pixmap|image\/x\-rgb|image\/x\-xbitmap|image\/x\-xpixmap|image\/x\-xwindowdump)$/i;
oFReader.onload = function (oFREvent) {
if (!oPreviewImg) {
var newPreview = document.getElementById(eleId);
oPreviewImg = new Image();
oPreviewImg.style.width = (newPreview.offsetWidth).toString() + "px";
oPreviewImg.style.height = (newPreview.offsetHeight).toString() + "px";
if(newPreview.children.length > 0)
newPreview.replaceChild(oPreviewImg, newPreview.children[0]);
else
newPreview.appendChild(oPreviewImg);
}
oPreviewImg.src = oFREvent.target.result;
// Add Bootstrap's img-thumbnail class to the image frame
oPreviewImg.classList.add("img-thumbnail");
};
return function () {
var aFiles = document.getElementById(inputId).files;
if (aFiles.length === 0) { return; }
if (!rFilter.test(aFiles[0].type)) { alert("You must select a valid image file!"); return; }
oFReader.readAsDataURL(aFiles[0]);
}
}
if (navigator.appName === "Microsoft Internet Explorer") {
return function () {
document.getElementById(eleId).filters.item("DXImageTransform.Microsoft.AlphaImageLoader").src = document.getElementById(inputId).value;
}
}
};
Then you can extract the base64 URL from the src attribute of the img element.
You may come out with your own preferred method of "cancelling" your upload by maybe destroying the img element.

Include json data in a non-ajax post

I have a JavaScript Pivot table that displays data to the user. The user can select the columns/rows etc. I've added an export to Excel button, that performs an ajax post with the current data/view of the pivot table (json data). It posts the data to my server that converts that to an Excel file.
Creating the file all works well, but my problem is getting the file to the user. From what I've read I can't send a file to a user after an ajax post.
I'm happy to use a plain html post (in fact it is what I want since I can redirect the user to the file), but I don't know how to include the pivot table data as part of the post since it is not a form.
I know I can save the file locally and send a url back but this complicates things and I would like to avoid it.
Is it possible to do this without saving the file locally and sending a url where the file is located?
Browsers don't (yet) support JSON serialization of forms, so AFAIK it's not really possible to send JSON to backend using pure forms.
I have two solutions that will not require saving file on the server:
1) Simple solution would be to generate an invisible form with JavaScript, create hidden input of name json and populate it with JSON content to send to the server. On the server side, you would read the form data and parse JSON that is stored in the data. Then you just generate the file and send the file in response. The browser should trigger download dialog.
var form = document.createElement('form');
form.method = 'post';
form.action = 'url';
var input = document.createElement('input');
input.type = 'hidden';
textarea.name = 'json';
textarea.value = JSON.stringify(your_json);
form.appendChild(input);
document.body.appendChild(form);
form.submit();
form.parentNode.removeChild(form);
2) Second option uses ajax to send data to the server. The browsers need to support several APIs, though.
You do your JSON request as usual and the server should respond with header Content-Type: here-the-MIME-type-of-your-file and with the file contents in response body.
The code on client side should look like:
var json = JSON.stringify({here: ['your', 'json', 'to', 'send', 'to', 'server']});
var xhr = new XMLHttpRequest();
xhr.open('POST', '/your/api/url', true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.addEventListener('load', function () {
var blob = new Blob([this.response], { // reading response, not responseText
type: this.getResponseHeader('Content-Type')
});
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'file_name.txt'; // set proper extension
document.body.appendChild(a); // it needs to be added to the document in order to work
a.click();
a.parentNode.removeChild(a);
});
xhr.send(json);
Also, the created URL objects should be revoked at some point, otherwise they will exist until the page reloads. But I think they cannot be revoked before the user downloads the file. So it's one task still to do with the above code.

Can't bind a resized image (as a blob) to my form

I'm trying to create an image upload interface, but to do the resizing on the client side so as to not tax my server (original files are generally 4Mb each, and I need files that are ~300kb)
I have my <input type="file" name="image0" />, and select my file ("cat.gif").
I then resize it, following the recipe at Mozilla Hacks. I'm left with a blob which I try to put back into my form:
//should I be trying to set the FormData in the presently-blank callback function?
var blob = canvas.toBlob(function(){}, "image/png", 0.95);
var form = document.querySelector("form");
var fd = new FormData(form);
fd.set("image0", blob);
When I submit the form, it sends the original image file ("cat.gif") I inputed, not the modified one I made as a blob. Here's part of the POST payload:
Content-Disposition: form-data; name="image0"; filename="cat.gif"
How do I bind the blob to the form input?

Get img from DOM and upload it via ajax

How can I get an image from DOM and then upload it via ajax?
my_img_ele = document.getElementById("my_img");
This is the ajax call
var xmlHttp=new XMLHttpRequest();
xmlHttp.open("POST", "site", true);
var formData = new FormData();
formData.append(IMG_HERE);
xmlHttp.send(formData);
What I have been doing is just sending the url and then looking up the image server side. But I figure this will be quicker.
EDIT: Note that the user is not uploading an image via a form. It's an img element.
If you were asking "upload images via AJAX", using the FormData the answer can be simpler and easily searchable on the internet.
Kindly see if this link addresses your question.
To get an image from the DOM, you would use the same above. But instead of submitting the form, you would be reading the DOM img element as a dataURI.
Check the FIDDLE for the following code's HTML:
var originalImage = document.getElementById("image"),
canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
formdata = false,
dummy = document.getElementById('dummy'),
newImage = new Image();
if (window.FormData) {
formdata = new FormData();
}
newImage.onload = function(){
// width and height here should be the same as original image
// detect before-hand, using jQuery(el).width() /.height() on the DOM element
context.drawImage(newImage, 0, 0, 400, 300);
}
newImage.src = originalImage.src;
// traditional ajax and jQuery, no HTML5
if (formdata) {
var canvasToDURI = canvas.toDataURL();
formdata.append("image-data", canvasToDURI);
// yay!, jQuery
$.ajax({
url: "upload.php",
type: "POST",
data: formdata,
processData: false,
contentType: false,
success: function (res) {
// do some thing !
dummy.innerText = 'Image data sent !';
},
error: function(a, b, c) {
dummy.innerText = 'The following data was not sent due to : ' + b + ' -- '+ c + ' \n\n' + canvasToDURI;
}
});
}
If FormData is also not supported, you can create a form element and change the enctype of the form to "multipart/form-data" and send the data using JavaScript in an AJAX call, without submitting the form.
Your figure is obviously wrong. Here's the thing, an image element is nothing else than a DOM node, containing the URL of the image, the image itself get downloaded by the browser, so on a client machine it's only available in the browser memory. And even is somehow you gain acces to the browser memory (cache), consider that based on the clients connection it is highly probable that uploading it from there is way slower than dowloading it server side from the original source. The other solution is that you download the image based on the URL (img.src) via ajax get simply detour it with an other ajax in the first ones callback, but it's of course means that the image travels averagely two times more.

Categories