I have to create an image uploader for a future project (No flash, IE10+, FF7+ etc.) that does image resizing/converting/cropping on the clientside and not on the server.
So I made a javascript interface where the user can 'upload' their files and get resized/cropped in the browser directly, without ever contacting the server. The performance is OK, not that good, but it works.
The endresult is an array of canvas elements. The user can edit/crop the images after they got resized, so I keep them as canvas instead of converting them to jpeg. (Which would worsen the initial performance)
Now this works fine, but I don't know what's the best way to actually upload the finished canvas elements to the server now. (Using a asp.net 4 generic handler on the server)
I have tried creating a json object from all elements containing the dataurl of each canvas.
The problem is, when I got 10-40 pictures, the browser starts freezing when creating the dataurls, especially for images that are larger than 2 megabyte.
//images = array of UploadImage
for (var i = 0; i < images.length; i++) {
var data = document.getElementById('cv_' + i).toDataURL('image/jpg');
images[i].data = data.substr(data.indexOf('base64') + 7);
}
Also converting them to a json object (I am using json2.js) usually crashes my browser. (FF7)
My object
var UploadImage = function (pFileName, pName, pDescription) {
this.FileName = pFileName;
this.Name = pName;
this.Description = pDescription;
this.data = null;
}
The upload routine
//images = array of UploadImage
for (var i = 0; i < images.length; i++) {
var data = document.getElementById('cv_' + i).toDataURL('image/jpg');
images[i].data = data.substr(data.indexOf('base64') + 7);
}
var xhr, provider;
xhr = jQuery.ajaxSettings.xhr();
if (xhr.upload) {
xhr.upload.addEventListener('progress', function (e) {
console.log(Math.round((e.loaded * 100) / e.total) + '% done');
}, false);
}
provider = function () {
return xhr;
};
var ddd = JSON.stringify(images); //usually crash here
$.ajax({
type: 'POST',
url: 'upload.ashx',
xhr: provider,
dataType: 'json',
success: function (data) {
alert('ajax success: data = ' + data);
},
error: function () {
alert('ajax error');
},
data: ddd
});
What would be the best way to send the canvas elements to the server?
Should I send them all at once or one by one?
Uploading files one by one is better. Requires less memory and as soon as one file ready to upload, the upload can be started instead of waiting while all files will be prepared.
Use FormData to send files. Allows to upload files in binary format instead of base64 encoded.
var formData = new FormData;
If Firefox use canvas.mozGetAsFile('image.jpg') instead of canvas.toDataUrl(). Allow to avoid unnecessary conversion from base64 to binary.
var file = canvas.mozGetAsFile('image.jpg');
formData.append(file);
In Chrome use BlobBuilder to convert base64 into blob (see dataURItoBlob function
accepted
After playing around with a few things, I managed to figure this out myself.
First of all, this will convert a dataURI to a Blob:
//added for quick reference
function dataURItoBlob(dataURI) {
// convert base64/URLEncoded data component to raw binary data held in a string
var byteString;
if (dataURI.split(',')[0].indexOf('base64') >= 0)
byteString = atob(dataURI.split(',')[1]);
else
byteString = unescape(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to a typed array
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], {type:mimeString});
}
From this question):
var blob = dataURItoBlob(canvas.toDataURL('image/jpg'));
formData.append(blob);
And then send the formData object. I'm not sure how to do it in jQuery, but with plain xhr object it like so:
var xhr = new XMLHttpRequest;
xhr.open('POST', 'upload.ashx', false);
xhr.send(formData);
On server you can get files from Files collection:
context.Request.Files[0].SaveAs(...);
Related
I'd like to build a simple HTML page that includes JavaScript to perform a form POST with image data that is embedded in the HTML vs a file off disk.
I've looked at this post which would work with regular form data but I'm stumped on the image data.
JavaScript post request like a form submit
** UPDATE ** Feb. 2014 **
New and improved version available as a jQuery plugin:
https://github.com/CoeJoder/jquery.image.blob
Usage:
$('img').imageBlob().ajax('/upload', {
complete: function(jqXHR, textStatus) { console.log(textStatus); }
});
Requirements
the canvas element (HTML 5)
FormData
XMLHttpRequest.send(:FormData)
Blob constructor
Uint8Array
atob(), escape()
Thus the browser requirements are:
Chrome: 20+
Firefox: 13+
Internet Explorer: 10+
Opera: 12.5+
Safari: 6+
Note: The images must be of the same-origin as your JavaScript, or else the browser security policy will prevent calls to canvas.toDataURL() (for more details, see this SO question: Why does canvas.toDataURL() throw a security exception?). A proxy server can be used to circumvent this limitation via response header injection, as described in the answers to that post.
Here is a jsfiddle of the below code. It should throw an error message, because it's not submitting to a real URL ('/some/url'). Use firebug or a similar tool to inspect the request data and verify that the image is serialized as form data (click "Run" after the page loads):
Example Markup
<img id="someImage" src="../img/logo.png"/>
The JavaScript
(function() {
// access the raw image data
var img = document.getElementById('someImage');
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
var dataUrl = canvas.toDataURL('image/png');
var blob = dataUriToBlob(dataUrl);
// submit as a multipart form, along with any other data
var form = new FormData();
var xhr = new XMLHttpRequest();
xhr.open('POST', '/some/url', true); // plug-in desired URL
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
alert('Success: ' + xhr.responseText);
} else {
alert('Error submitting image: ' + xhr.status);
}
}
};
form.append('param1', 'value1');
form.append('param2', 'value2');
form.append('theFile', blob);
xhr.send(form);
function dataUriToBlob(dataURI) {
// serialize the base64/URLEncoded data
var byteString;
if (dataURI.split(',')[0].indexOf('base64') >= 0) {
byteString = atob(dataURI.split(',')[1]);
}
else {
byteString = unescape(dataURI.split(',')[1]);
}
// parse the mime type
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
// construct a Blob of the image data
var array = [];
for(var i = 0; i < byteString.length; i++) {
array.push(byteString.charCodeAt(i));
}
return new Blob(
[new Uint8Array(array)],
{type: mimeString}
);
}
})();
References
SO: 'Convert DataURI to File and append to FormData
Assuming that you are talking about embedded image data like http://en.wikipedia.org/wiki/Data_URI_scheme#HTML
****If my assumption is incorrect, please ignore this answer.**
You can send it as JSON using XMLHttpRequest.
Here is sample code: (you may want to remove the header part ('data:image/png;base64,') before sending)
Image
<img id="myimg" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA
AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot">
Button
<input id="subbtn" type="button" value="sub" onclick="sendImg()"></input>
Script
function sendImg() {
var dt = document.getElementById("myimg").src;
var xhr = new XMLHttpRequest();
xhr.open("POST", '/Home/Index', true); //put your URL instead of '/Home/Index'
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) { //4 means request finished and response is ready
alert(xhr.responseText);
}
};
var contentType = "application/json";
xhr.setRequestHeader("Content-Type", contentType);
for (var header in this.headers) {
xhr.setRequestHeader(header, headers[header]);
}
// here's our data variable that we talked about earlier
var data = JSON.stringify({ src: dt });
// finally send the request as binary data
xhr.send(data);
}
EDIT
As #JoeCoder suggests, instead of json, you can also use a FormData object and send in Binary format. Check his answer for more details.
I have an html5 mobile web app (http://app.winetracker.co) and I'm working on feature that remembers user's state when they come back to the app in their browser (which always automatically refreshes in iOS safari). I'm storing the URL and form-field data via local storage. One of the form field data items is an file-input for images. I am successfully converting images to a base64 via canvas and storing it to localStorage.
function storeTheImage() {
var imgCanvas = document.getElementById('canvas-element'),
imgContext = imgCanvas.getContext("2d");
var img = document.getElementById('image-preview');
// Make sure canvas is as big as the picture BUT make it half size to the file size is small enough
imgCanvas.width = (img.width/4);
imgCanvas.height = (img.height/4);
// Draw image into canvas element
imgContext.drawImage(img, 0, 0, (img.width/4), (img.height/4));
// Get canvas contents as a data URL
var imgAsDataURL = imgCanvas.toDataURL("image/png");
// Save image into localStorage
try {
window.localStorage.setItem("imageStore", imgAsDataURL);
$('.localstorage-output').html( window.localStorage.getItem('imageStore') );
}
catch (e) {
console.log("Storage failed: " + e);
}
}
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#image-preview').attr('src', e.target.result);
storeTheImage();
}
reader.readAsDataURL(input.files[0]);
}
}
$('.file-input').on('change', function() {
readURL(this);
});
see this jsFiddle: https://jsfiddle.net/tonejac/ceLwh9qp/19/
How might I convert the localStore image dataURL string back to an image file so I can upload it to my server?
How might I convert the localStore image dataURL string back to an image file
See your fiddle, Javascript pane line 25.
// recompose image :
var imgRecomposed = document.createElement('img');
$('.image-recomposed').append(imgRecomposed);
imgRecomposed.src = window.localStorage.getItem('imageStore');
We create an image element and fill the src attibute with the data of the stored 'dataImage'.
In your Fiddle:
// Save image into localStorage
try {
window.localStorage.setItem("imageStore", imgAsDataURL);
$('.localstorage-output').html( window.localStorage.getItem('imageStore') );
}
catch (e) {
console.log("Storage failed: " + e);
}
Place any of the following in your try/catch:
$('#canvas-element').context.drawImage(imgAsDataURL);
$('#canvas-element').context.drawImage(imgAsDataURL, 0, 0);
$('#canvas-element').replace(imgAsDataURL);
This will place the stored image into the Canvas you have displayed.
It is already an 'image' - you can use it as the src for an element etc. Sending it to your server is depends on the environment you have - basically an Ajax POST or similar sending the base64 string?.
You will first have to convert this dataURL to a blob, then use a FormData object to send this blob as a file.
To convert the dataURL to a blob, I do use the function from this answer.
function upload(dataURI, url){
// convert our dataURI to blob
var blob = dataURItoBlob(dataURI);
// create a new FormData
var form = new FormData();
// append the blob as a file (accessible through e.g $_FILES['your_file'] in php and named "your_filename.extension")
form.append('your_file', blob, 'your_filename.'+blob.type.split('image/')[1]);
// create a new xhr
var xhr = new XMLHttpRequest();
xhr.open('POST', url);
// send our FormData
xhr.send(form);
}
// from https://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata/5100158#5100158
function dataURItoBlob(dataURI) {
// convert base64/URLEncoded data component to raw binary data held in a string
var byteString;
if (dataURI.split(',')[0].indexOf('base64') >= 0)
byteString = atob(dataURI.split(',')[1]);
else
byteString = unescape(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to a typed array
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], {type:mimeString});
}
var savedIntoLocalStorage = 'data:image/png;base64,...=';
// execute the request
upload(savedIntoLocalStorage, 'http://yourserver.com/upload.php');
So I have an interesting question. I have a form where a user draws an image on a canvas (think a signature pad). I then need to send the image to my C# Controller (I am using ASP.NET MVC 5). The code I have functions for shorter strings, but when I try to pass the PNG data, it is too long and I recieve a HTTP Error 414. The request URL is too long error. Here is my code:
Html:
<form id="mainForm" action="submitUserAnswer" method="post">
<input type="hidden" id="userOutput" name="output" value="" />
//...other form elements, signature box, etc.
</form>
Javascript:
function goToNextQuestion() {
var output = $('#signature').jSignature("getData");
$('#userOutput').val(output);
$('#mainForm').submit();
}
C#:
public ActionResult submitUserAnswer()
{
//use the userOutput for whatever
//submit string to the database, do trigger stuff, whatever
//go to next question
System.Collections.Specialized.NameValueCollection nvc = Request.Form;
string userOutput = nvc["output"];
ViewBag.Question = userOutput;
return RedirectToAction("redirectToIndex", new { input = userOutput });
}
public ActionResult redirectToIndex(String input)
{
ViewBag.Answer = input;
return View("Index");
}
My png data is very long, so the error makes sense. My question is how can I get the png data back to my controller?
Maybe you just need to increase allowed GET request URL length.
If that doesn't works I have an aspx WebForm that saves a signature, and I use a WebMethod
[ScriptMethod, WebMethod]
public static string saveSignature(string data)
{
/*..Code..*/
}
and I call it like this:
PageMethods.saveSignature(document.getElementById('canvas').toDataURL(), onSucess, onError);
also I have to increase the length of the JSON request and it works fine, with no problems with the lenght.
In MVC there isn't WebMethods, but JSON and AJAX requests do the job, just save the data in a session variable, and then use it when need it.
Hope it helps
You have error because your data is string (base64) and have max limit for send characters, better way is to create blob (png file) from base64 at client side, and send it to server. Edit. All listed code here, exists in stackoverflow posts.
function dataURItoBlob(dataURI) {
// convert base64 to raw binary data held in a string
// doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
var byteString = atob(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
// write the bytes of the string to an ArrayBuffer
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
var blob = null;
// TypeError old chrome and FF
window.BlobBuilder = window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;
if(window.BlobBuilder){
var bb = new BlobBuilder();
bb.append(ab);
blob = bb.getBlob(mimeString);
}else{
blob = new Blob([ab], {type : mimeString});
}
return blob;
}
function sendFileToServer(file, url, onFileSendComplete){
var formData = new FormData()
formData.append("file",file);
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onload = onFileSendComplete;
xhr.send(formData);
}
var base64 = $('#signature').jSignature("getData");
var blob = dataURItoBlob(base64);
var onComplete = function(){alert("file loaded to server");}
sendFileToServer(blob, "/server", onComplete)
So, i have a script for capture a video from webcam and after create a gif with :
var base64data;
var img = document.createElement('img');
var reader = new window.FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
base64data = reader.result;
img.src = base64data;
}
So, with this script, i take the blob object from webcam, i encode this blob into base64 and i put the data into my img.src.
After that, i need to send this data into PHP with jquery ajax.
The base64data is very big, like 2 000 000 characters, so the ajax request is very long (20-50sec).
I just send the data like that :
var gif = $('.generated-img').attr('src').replace('data:image/gif;base64,', '');
gif = gif.match(/.{1,500000}/g);
$.ajax({
type:"POST",
url: "/webcam/",
data: {image_gif:gif, crop_x:x, crop_y:y, crop_w:w, crop_h:h, gif:true},
success: function () {
parent.$.fancybox.close();
}
});
I need to break the data into several chunks of 500 000 characters each.
But it's soooo long.. How can i do for optimize that ? I just need to retrieve this data into my php script for do some stuff...
I don't see exactly why you need to split it up; the idea of using AJAX - where A stands for asynchronous - is precisely that you can do big slow things in the background.
But rather than using a regular expression to do the split, I would just break it up using substr, e.g. something like (not tested, so may be offset errors - e.g. you may need a + 1 or - 1 here and there):
var chunkSize = 500000;
var chunkId = 0;
var isLastChunk = false;
for(var i = 0; i < gif.length; i += chunkSize)
{
var chunk;
if(gif.length > i + chunkSize) {
chunk = gif.substr(0, i, chunkSize);
} else {
chunk = gif.substr(0, i);
isLastChunk = true;
}
$.ajax({
type:"POST",
url: "/webcam/",
data: {
chunk: chunk, chunkId: chunkId, isLastChunk: isLastChunk,
crop_x:x, crop_y:y, crop_w:w, crop_h:h, gif:true},
success: function () {
// Chunk handled succesfully
}
});
}
In /webcam/ you could check chunkId, and isLastChunk.
I'd like to build a simple HTML page that includes JavaScript to perform a form POST with image data that is embedded in the HTML vs a file off disk.
I've looked at this post which would work with regular form data but I'm stumped on the image data.
JavaScript post request like a form submit
** UPDATE ** Feb. 2014 **
New and improved version available as a jQuery plugin:
https://github.com/CoeJoder/jquery.image.blob
Usage:
$('img').imageBlob().ajax('/upload', {
complete: function(jqXHR, textStatus) { console.log(textStatus); }
});
Requirements
the canvas element (HTML 5)
FormData
XMLHttpRequest.send(:FormData)
Blob constructor
Uint8Array
atob(), escape()
Thus the browser requirements are:
Chrome: 20+
Firefox: 13+
Internet Explorer: 10+
Opera: 12.5+
Safari: 6+
Note: The images must be of the same-origin as your JavaScript, or else the browser security policy will prevent calls to canvas.toDataURL() (for more details, see this SO question: Why does canvas.toDataURL() throw a security exception?). A proxy server can be used to circumvent this limitation via response header injection, as described in the answers to that post.
Here is a jsfiddle of the below code. It should throw an error message, because it's not submitting to a real URL ('/some/url'). Use firebug or a similar tool to inspect the request data and verify that the image is serialized as form data (click "Run" after the page loads):
Example Markup
<img id="someImage" src="../img/logo.png"/>
The JavaScript
(function() {
// access the raw image data
var img = document.getElementById('someImage');
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
var dataUrl = canvas.toDataURL('image/png');
var blob = dataUriToBlob(dataUrl);
// submit as a multipart form, along with any other data
var form = new FormData();
var xhr = new XMLHttpRequest();
xhr.open('POST', '/some/url', true); // plug-in desired URL
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
alert('Success: ' + xhr.responseText);
} else {
alert('Error submitting image: ' + xhr.status);
}
}
};
form.append('param1', 'value1');
form.append('param2', 'value2');
form.append('theFile', blob);
xhr.send(form);
function dataUriToBlob(dataURI) {
// serialize the base64/URLEncoded data
var byteString;
if (dataURI.split(',')[0].indexOf('base64') >= 0) {
byteString = atob(dataURI.split(',')[1]);
}
else {
byteString = unescape(dataURI.split(',')[1]);
}
// parse the mime type
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
// construct a Blob of the image data
var array = [];
for(var i = 0; i < byteString.length; i++) {
array.push(byteString.charCodeAt(i));
}
return new Blob(
[new Uint8Array(array)],
{type: mimeString}
);
}
})();
References
SO: 'Convert DataURI to File and append to FormData
Assuming that you are talking about embedded image data like http://en.wikipedia.org/wiki/Data_URI_scheme#HTML
****If my assumption is incorrect, please ignore this answer.**
You can send it as JSON using XMLHttpRequest.
Here is sample code: (you may want to remove the header part ('data:image/png;base64,') before sending)
Image
<img id="myimg" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA
AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot">
Button
<input id="subbtn" type="button" value="sub" onclick="sendImg()"></input>
Script
function sendImg() {
var dt = document.getElementById("myimg").src;
var xhr = new XMLHttpRequest();
xhr.open("POST", '/Home/Index', true); //put your URL instead of '/Home/Index'
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) { //4 means request finished and response is ready
alert(xhr.responseText);
}
};
var contentType = "application/json";
xhr.setRequestHeader("Content-Type", contentType);
for (var header in this.headers) {
xhr.setRequestHeader(header, headers[header]);
}
// here's our data variable that we talked about earlier
var data = JSON.stringify({ src: dt });
// finally send the request as binary data
xhr.send(data);
}
EDIT
As #JoeCoder suggests, instead of json, you can also use a FormData object and send in Binary format. Check his answer for more details.