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');
Related
Im looking to resize an image before it is uploaded to a server, at the moment i am using ng2-imageupload like this:
<input id="media" class="inputfile" type="file" name="media" image-upload
(imageSelected)="selected($event)"
[resizeOptions]="resizeOptions" (change)="onChange($event)">
export class WpMediaFormComponent {
file: File;
resizeOptions: ResizeOptions = {
resizeMaxHeight: 768,
resizeMaxWidth: 438
};
selected(imageResult: ImageResult) {
console.log(imageResult);
this.dataBlob = this.dataURItoBlob(imageResult.resized.dataURL);
let blob = this.dataURItoBlob(imageResult.resized.dataURL);
}
This then returns an object, like this:
dataURL:"data:image/jpeg;base64, DATA URI HERE"
type:"image/jpeg;"
I can then convert this object to a blob using this 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 = decodeURI(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});
}
Before doing this I was uploading the image to the server using this code:
onChange(event: EventTarget) {
let eventObj: MSInputMethodContext = <MSInputMethodContext> event;
let target: HTMLInputElement = <HTMLInputElement> eventObj.target;
let files: FileList = target.files;
this.file = files[0];
console.log(this.file);
//this.update.emit(this.file);
}
Does anyone have idea how I can feed the blob returned from dataURItoBlob method into the file upload onChange event?
Im a little lost here.
So I figured it out with the help of #Brother Woodrow, and this thread:
How to convert Blob to File in JavaScript
Here is my updated code, not the only thing I had to change was the selected method:
selected(imageResult: ImageResult) {
// create a blob
let blob: Blob = this.dataURItoBlob(imageResult.resized.dataURL);
// get the filename
let fileName: string = imageResult.file.name;
// create a file
this.file = new File([blob], fileName);
console.log(this.file);
// event emitter send to container then to http post
this.update.emit(this.file);
}
I can now upload 3MB and they are pushed to the server around 150kB in seconds which is great for the user especially as this app will mostly be used by mobile devices.
You'll need to convert the Data URI to a Blob, then send that back to your server. This might be helpful: Convert Data URI to File then append to FormData
Once you have the blob, it should be easy enough to use FormData and the Angular HTTP class to upload it to your server for further processing.
let formData = new FormData();
formData.append(blob);
this.http.post('your/api/url', fd).subscribe((response) => console.log(reponse);
I am not able to find a proper way to read an image from a URL and upload it using JavaScript/Ajax.
Suppose there is a URL: https://pbs.twimg.com/profile_images/1580483969/parisjs_transparent.png.
I should be able to upload it now, but I guess to upload first I need to download it, so what will be the procedure?
If the remote server allows CORS requests, it possible to download a remote image and read its contents. If the remote server does not allow CORS, the image can be shown (using a standard <img src=".."> tag), but the content cannot be read - the canvas gets tainted.
Download image from remote URL using JavaScript when CORS is enabled
function getImageFormUrl(url, callback) {
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.onload = function (a) {
var canvas = document.createElement("canvas");
canvas.width = this.width;
canvas.height = this.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0);
var dataURI = canvas.toDataURL("image/jpg");
// 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 callback(new Blob([ia], { type: mimeString }));
}
img.src = url;
}
getImageFormUrl('https://upload.wikimedia.org/wikipedia/commons/thumb/b/b4/The_Sun_by_the_Atmospheric_Imaging_Assembly_of_NASA%27s_Solar_Dynamics_Observatory_-_20100819.jpg/628px-The_Sun_by_the_Atmospheric_Imaging_Assembly_of_NASA%27s_Solar_Dynamics_Observatory_-_20100819.jpg', function (blobImage) {
var formData = new FormData();
formData.append('key1', 'value1');
formData.append('key2', 'value2');
formData.append('key2', 'value3');
formData.append('imageToUpload', blobImage);
console.log(blobImage);
//use formData in "data" property in $.ajax
//$.ajax({
//blabla: blabla,
//data: formData,
//blabla: blabla
//}})
});
Let server download/upload your image. What you need to do is to create a text input field for image URL. Than via AJAX you send to backend the URL only and server gets its contents.
#DheerajAgrawal
see this example:
http://jsfiddle.net/WQXXT/3004/
you should customize that, where it says:
alert('start upload script here');
At the moment I get a byte array of a JPEG from my signalr connection and load it into a image and on the image.onload event I draw the image to my canvas.
so:
desktopImage.onload = function () {
myCanvasContext.drawImage(desktopImage, 0, 0);
}
chat.client.broadcastMessage = function (jpeg) {
desktopImage.src = 'data:image/jpeg;base64,' + jpeg;
}
Which does work well.
I was wondering whether i could 'quicken' this process by drawing the image directly onto the canvas without 1st loading it to an image 1st.
Is it possible?
If you receive a byte array as you state, assuming ArrayBuffer, you could wrap it as a Blob object and create an object URL for it instead. This will save significant overhead from encoding and decoding Base-64:
Example:
var desktopImage = new Image();
var url;
desktopImage.onload = function () {
myCanvasContext.drawImage(desktopImage, 0, 0);
(URL || webkitURL).revokeObjectURL(url); // release memory
}
// assuming jpeg = ArrayBuffer:
chat.client.broadcastMessage = function (jpeg) {
var blob = new Blob([jpeg], {type: "image/jpeg"});
url = (URL || webkitURL).createObjectURL(blob);
desktopImage.src = url;
}
How to append blob to input of type file?
<!-- Input of type file -->
<input type="file" name="uploadedFile" id="uploadedFile" accept="image/*"><br>
// I am getting image from webcam and converting it to a blob
function takepicture() {
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(video, 0, 1, width, height);
var data = canvas.toDataURL('image/png');
var dataURL = canvas.toDataURL();
var blob = dataURItoBlob(dataURL);
photo.setAttribute('src', data);
}
function dataURItoBlob(dataURI) {
var binary = atob(dataURI.split(',')[1]);
var array = [];
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
}
// How can I append this var blob to "uploadedFile". I want to add this on form submit
It is possible to set value of <input type="file">.
To do this you create File object from blob and new DataTransfer object:
let file = new File([data], "img.jpg",{type:"image/jpeg", lastModified:new Date().getTime()});
let container = new DataTransfer();
Then you add file to container thus populating its 'files' property, which can be assigned to 'files' property of file input:
container.items.add(file);
fileInputElement.files = container.files;
Here is a fiddle with output, showing that file is correctly placed into input.
The file is also passed correctly to server on form submit. This works at least on Chrome 88.
If you need to pass multiple files to input you can just call container.items.add multiple times. So you can add files to input by keeping track of them separately and overwriting its 'files' property as long as this input contains only generated files (meaning not selected by user). This can be useful for image preprocessing, generating complex files from several simple ones (e.g. pdf from several images), etc.
API references:
File object
DataTransfer object
I had a similar problem with a fairly complex form in an angular app, so instead of the form I just sent the blob individually using XMLHttpRequest(). This particular "blob" was created in a WebAudioAPI context, creating an audio track in the user's browser.
var xhr = new XMLHttpRequest();
xhr.open('POST', 'someURLForTheUpload', true); //my url had the ID of the item that the blob corresponded to
xhr.responseType = 'Blob';
xhr.setRequestHeader("x-csrf-token",csrf); //if you are doing CSRF stuff
xhr.onload = function(e) { /*irrelevant code*/ };
xhr.send(blob);
You can't change the file input but you can use a hidden input to pass data. ex.:
var hidden_elem = document.getElementById("hidden");
hidden_elem.value = blob;
I had take too much time to find how to do "url, blob to input -> preview"
so I had do a example you can check here
https://stackoverflow.com/a/70485949/6443916
https://vulieumang.github.io/vuhocjs/file2input-input2file/
image bonus
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(...);