Unable to send blob payload in FormData - javascript

I'm playing around with the FormData API. Here is the fiddle I'm using - I'm trying to inject blobs into my form and submit via AJAX.
//ic0 and tc0 are canvases
//image0 and thumb0 are file inputs
function ajaxSubmit(){
var fd = new FormData(document.forms[0]);
ic0.toBlob(
function(blob){
fd.set("image0", blob, "image0.jpg");
}, "image/jpeg", 0.7);
tc0.toBlob(
function(blob){
fd.set("thumb0", blob, "thumb0.jpg");
}, "image/jpeg", 0.7);
var xhr = new XMLHttpRequest();
xhr.open("POST", "/ajax-upload/", true);
xhr.send(fd);
}
Browser behaviour is a little ... odd:
Chrome 50 (in Ubuntu)
Not able to do it, gives:
Failed blob:https%3A//fiddle.jshell.net/uuid4 to load resource: the server responded with 404
But I thought FormData.set() was supported now? It seems to work with non-blobs?
Firefox 46 (in Ubuntu)
Doesn't seem work if FormData() object is not initialized with a DOM object that already has the necessary file inputs (appended normal inputs are posted however). FormData.set() does not seem to work with file inputs and blobs (you'll note that thumb0 is null in the payload despite calling fd.set("thumb0", blob, "thumb0.jpg") on it. You can verify that it's set by doing console.log(fd.get("thumb0")) just after setting. You'll also note that the payload of image0 is incorrectly your original image, not the resized canvas image.
It's inconvenient not being able to customise your multipart FormData with javascript. I'm a javascript noob - am I just doing something completely wrong here, or are these browsers not correctly supporting the FormData API? How can I submit image0 and thumb0 correctly in my fiddle?
Edit: OK, going to bounty this. I know there are big jquery-dependent libraries like blueimp out there, and I think they base64 the data rather than transmitting as a file input (plus I want to avoid jquery). I only have to support the latest Chrome or Firefox for this project and I'm really hoping I can keep the code as clean as the FormData API seems to suggest I might be able to. Can anyone successfully grab an image off a canvas and insert it into the POST payload as a file? I'd like to be able to access it in request.FILES in django.

I think one thing you are missing here is the asynchronous nature of javascript. In your ajaxSubmit method -
function ajaxSubmit(){
var fd = new FormData(document.forms[0]);
ic0.toBlob(function(blob){
fd.set("image0", blob, "image0.jpg");
}, "image/jpeg", 0.7); // this call is asynchronous
tc0.toBlob(function(blob){
fd.set("thumb0", blob, "thumb0.jpg");
}, "image/jpeg", 0.7); // this call is also asynchronous
/*
hence the below code may get executed before
fd.set("image0", blob, "image0.jpg");
this statement. which it does.
that is why the canvas files are not submitted.
*/
var xhr = new XMLHttpRequest();
xhr.open("POST", "/ajax-upload/", true);
xhr.send(fd);
}
Try synchronising the code like this
function ajaxSubmit(){
var fd = new FormData(document.forms[0]);
ic0.toBlob(function(blob){
fd.set("image0", blob, "image0.jpg");
tc0.toBlob(function(blob){
fd.set("thumb0", blob, "thumb0.jpg");
console.log(blob);
console.log("Just blobbed thumb0");
var xhr = new XMLHttpRequest();
xhr.open("POST", "fileuploadbackend.jsp", true);
//xhr.setRequestHeader("X-CSRFToken", csrftoken);
xhr.send(fd);
}, "image/jpeg", 0.7);
}, "image/jpeg", 0.7);
}
also as jornare has suggested you should comment out the line
URL.revokeObjectURL(img.src);
which is causing error in chrome. Invoke it after the loading image is done.
also you have multiple elements with same id,
<input id="image0" name="image0" type="file" />
<label for="image0">image0</label>
<input id="image0" name="thumb0" type="file" />
this isn't creating problem in your code but the id's should be different.
UPDATE
Here is the working fiddle.
Note: change the request url to your corresponding url.
Follow these links to get more insight on asynchronous behaviour of javascript and how AJAX request callbacks are handled.
How does Asynchronous Javascript Execution happen
Developer Mozilla - Synchronous and asynchronous requests
How does javascript handle AJAX request in background.

Looking through your fiddle, you were cleaning up the dataURL before it was loaded, and therefore the thumbnails did not load at all. (The 404)
I made an updated fiddle https://jsfiddle.net/tLqch5x2/3/
function handleFiles(e){
var img = new Image();
img.onload = function(){
var width = 30;
var height = img.height * 30/img.width;
e.target.ic0.width = width ;
e.target.ic0.height = height ;
e.target.tc0.width = width/2 ;
e.target.tc0.height = height/2;
var ctx = e.target.ic0.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
ctx = e.target.tc0.getContext("2d");
ctx.drawImage(img, 0, 0, width/2, height/2);
URL.revokeObjectURL(img.src); //clean up memory here instead
};
img.src = URL.createObjectURL(e.target.files[0]);
//clean up to prevent memory leak
//URL.revokeObjectURL(img.src); //too soon
}
It still has a 404, but now from trying to post to a url that does not exist on jsfiddle.net. Please give it a try in your environment.
Note that there are also some other minor changes in both the html and the code. (improper naming the images, and no need to reference the existing form when creating the new form for submission)

Related

What would cause an HTML5 canvas toBlob to create an incomplete image?

I have the following javascript produce images from a canvas and upload them to a server.
var can = document.createElement('canvas');
can.width = 600;
can.height = 600;
var ctx = can.getContext('2d');
ctx.fillRect(0, 0, can.width, can.height);
ctx.fillText("Julia", can.width/2, can.height/2);
can.toBlob(uploadImage, "image/jpg", 0.9);
function uploadImage(jpeg) {
var data = new FormData();
data.append('image', jpeg, 'image.jpg');
...
}
Every so often, the result looks like the above, only partially drawn. Multiple canvases are processed and uploaded serially, only moving on in the completion of the ajax (in the ... part), so only one at a time.
Have you seen this happen? If so, where in this process should I debug further? Maybe a setting or something in the context object?
Edit
The upload is an ajax post with a promise resolved only on the success branch. It actually uses angular's $http service:
$http({method: 'POST', url: '...', data: data}).then(function(response) {
// callback that processes and uploads the next image
});
We were facing with similar except part with canvas. You can debug it simply.
Print given blob to page as img src to make sure that file was created properly. Print also blob size.
Open browser's developer tools -> Network -> Filter XHR and start uploading
Assign finished(status 200) ajax request and look at Request Headers -> Content-Length. If this number is equal to blob size on page, it was uploaded properly and find issue on server side. Else check ajax call.
Make sure that server has set max client request size appropriate to your usecase. For example nginx has default value 1M.
TIP: set Content-Type by blob.type in $http headers. Default value for POST is application/json.
try to rearrange your logic a little,, you are getting racing condition with uploading process and when the image is created, they have to be callback in callback sorta ..
// ...
ctx.fillText("Julia", can.width / 2, can.height / 2);
can.toBlob(function(blob) {
var newImg = document.createElement('img'),
url = URL.createObjectURL(blob);
newImg.onload = function() {
// no longer need to read the blob so it's revoked
URL.revokeObjectURL(url);
newImg.src = url;
// we have image out of canvas and now do the upload of newImg
// $http || $.ajax call goes here
};
});
of if you want to submit image data URL try something like,,
// ...
ctx.fillText("Julia", can.width / 2, can.height / 2);
can.toBlob(function(blob) {
var reader = new window.FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
base64data = reader.result;
// we have image in base64 and now do the upload of base64data
// $http || $.ajax call goes here
}
});
hth, k

Upload an image to server using chrome extensions

I am doing a chrome extension capable of getting from a webpage an image, and after I got it, I'm trying to upload it to an intranet server automatically without user iteration.
I am doing this right now.
This is on Content_script.js
...
x = $(frame1).contents().find("#image");
chrome.extension.sendRequest(x[0].src);
...
This is on background.js
chrome.extension.onRequest.addListener(function(links) {
chrome.downloads.download( { url: links ,
conflictAction: "overwrite",
filename: "get_image.jpg" },
function(DescargaId) {
var formData = new FormData();
formData.append("doc", Blob, "~/Downloads/get_image.jpg");
var request = new XMLHttpRequest();
request.open("POST", "http://192.168.0.30/app_get_pictures/upload_img.php");
request.setRequestHeader("Content-Type", "multipart/form-data");
request.send(formData);
} );
This on upload_img.php
...
$uploaddir = $_SERVER['DOCUMENT_ROOT'].'/app_get_pictures/images/';
$uploadfile = $uploaddir . basename($_FILES['doc']['name']);
move_uploaded_file($_FILES['doc']['tmp_name'], $uploadfile);
...
With this, I already download the image successfully to the local machine, but can't upload the image to the server.
It is possible to do this, or even if I can upload the image to the server directly without download it first to the local machine.
Note: I don't have any tag form on a popup page in the extension solution, and I don't have a popup page neither, because as I already said, I don't need any iteration from the user.
Thanks for your help!
Thanks to https://stackoverflow.com/users/934239/xan I resolved this problem using his advise, here is the resulting working code.
...
// With this I can download or get content image into var blob
var xhr = new XMLHttpRequest();
var kima = $(frame1).contents().find("#image");
xhr.open('GET',kima[0].src,true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
var blob = new Blob([this.response], {type: 'image/png'});
send_image(blob);
}
};
xhr.send();
....
// After the image is loaded into var blob, it can be send
// to the server side
function send_image(x){
var formData = new FormData();
formData.append("doc", x);
var request = new XMLHttpRequest();
request.open("POST", "http://192.168.0.30/app_get_image/upload_img.php");
request.send(formData);
}
All this code into the content_script of the chrome extension. Also the code of the background using API download isn't needed anymore.
Hope this could works for anybody else.
Thanks again.
Besides the fact that the callback of downloads.download does NOT indicate that the file is already downloaded (only that the download is queued)..
formData.append("doc", Blob, "~/Downloads/get_image.jpg");
What do you think this code does? Documentation, for reference.
The second parameter is supposed to hold the data of the file; the third parameter is just the file name for the purposes of naming anonymous data (e.g. in a Blob)
Instead, you pass the Blob object itself; not an instance of Blob with the data.
In fact, with this architecture, you won't be able to upload the file, since at no point does chrome.downloads API give you access to the file's contents, and you can't just access a file on a disk by filename (which is what I think you thought this code would do).
To actually access the data, you need to request it yourself with XHR (or Fetch API if you want to be "modern"). Then, you get the response object which you can request to be a Blob. Then, you can both upload the blob and invoke chrome.downloads together with createObjectURL to "download" it from your extension's memory.

Automatically download and then upload a file in a chrome extension [duplicate]

This question already has an answer here:
Google chrome rehost image extension
(1 answer)
Closed 9 years ago.
I'm writing a Chrome Extension that automates downloading a file from one location and then uploading it to another. I've figured out how to do the download part using the HTML5 Filesystem API. However, I don't know how to then upload the file.
The issue is that the upload has to be done through a form using a "file" input element. I'd like to just tell the form where the file is located (I can get the location of the downloaded file from the Filesystem API). But I can't figure out a way of doing that programmatically.
Any ideas? Let me know if I can clarify anything!
EDIT: Another option is chrome.downloads extension API
EDIT: The File API looks promising (http://dev.w3.org/2006/webapi/FileAPI/). I also found this helpful: https://developer.mozilla.org/en-US/docs/Web/Guide/Using_FormData_Objects
So, this is the way I finally got it working. It's done in a Chrome extension. I don't think it's possible in a normal browser script, since the canvas.toDataURL function is used and will throw a security exception if the downloaded file is cross-origin. Anyway, here's a simplified version of how I did it. Luckily the files I'm downloading and uploading are images, so I can use the Image() class.
// load the image
var img = new Image();
img.src = "url to image";
// loaded
img.onload = function() {
// convert the image into a data url
var dataUrl = getImageDataUrl(this);
var blob = dataUrlToBlob(dataUrl);
// FormData will encapsulate the form, and understands blobs
var formData = new FormData();
// "image_name.png" can be whatever you want; it's what the
// host will see as the filename for this image (the host server
// that receives the posted form). it doesn't have to match the
// original filename, and can be arbitrary or not provided at all.
formData.append("file", blob, "image_name.png");
var request = new XMLHttpRequest();
// upload
request.open("POST", "url to form");
request.send(formData);
};
// converts an image into a dataurl
function getImageDataUrl(img) {
// Create an empty canvas element
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// NOTE: this will throw a cross-origin security exception
// if not done in an extension and if the image is cross-origin
return canvas.toDataURL("image/png");
}
// converts a dataurl into a blob
function dataUrlToBlob(dataUrl) {
var binary = atob(dataUrl.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/png'});
}
I donĀ“t know if something like that exists, but you could dynamically create a form using Javascript, then updating the file input value and finally trigger the form's submit event.

FineUploader: Thumbnails on the fly clientside

I am working on a FineUploader implementation. Special request is to create thumbnails on the fly client-side and then upload those with the original image-upload.
I have an implementation that works on FF, but does not seem to work on iOs. It looks like so:
var uploader = new qq.FineUploaderBasic({
button: document.getElementById(buttonID),
request: {
endpoint: '/up/load/a/' + $('section#ajax-viewport').data('albumid')
},
callbacks: {
onSubmit: function(id, fileName) {
// getFile obtains the file being uploaded
file = this.getFile(id);
// create a thumbnail & upload it:
ThumbDown(file, id, 200, fileName);
},
}
})
This code calls a function:
function ThumbDown(file, id, dimension, fileName) {
var reader = new FileReader();
reader.onload = function(e) {
var img = document.createElement("img");
img.onload = function (ev) {
var thumbnailDimensions; // object holding width & height of thumbnail
var c=document.getElementById("canvas-for-thumbnails"); // must be a <canvas> element
var ctx=c.getContext("2d");
// set thumbnail dimensions of canvas:
thumbnailDimensions = calcThumbnailDimension (img.width, img.height, dimension )
c.width = thumbnailDimensions.width;
c.height = thumbnailDimensions.height;
var ctx = c.getContext("2d");
ctx.drawImage(img, 0, 0, c.width, c.height);
uploadThumbnail(c.toDataURL('image/jpeg'), //a base64 encoded representation of the image
id,
fileName); // we need filename to combine with mother-image on the server
};
img.src = e.target.result;
}
reader.readAsDataURL(file);
} // end function
Finally the Thumbnail is uploaded with a dumb ajax-call:
function uploadThumbnail (base64encodedString, id, fileName) {
$.post('/up/thumb',
{
img : base64encodedString,
id: id,
fileName: fileName
},
function(data) {});
}
My questions:
1) Currently I have two uploads: one for mother-image and another for thumbnail. I would like to combine this in one FineUploader call. However, I do not see a way to do this, due to the asynchronous nature of my thumbnail creation.
Am I missing something? Is this possible to reduce this to one FineUploader call?
2) This code uploads the thumbnails as a base64 encoded string. I would like to upload the thumbnail as an image (or as a blob ?). Perhaps by following this recipe of Jeremy Banks. Would that work with FineUploader?
3) Are there other options/methods of FineUploader that I have missed but I should be using?
Any help is, as always, greatly appreciated.
So, it is already trivial to upload the original image. Fine Uploader takes care of that for you. If I understand correctly, you want to also upload a scaled version of the image (which you have already generated). I suggest you take the image you have drawn onto the canvas and convert it to a Blob. Then, you can submit this Blob directly to Fine Uploader, where it will upload it for you.
For example, change the value of uploadThumbnail to this:
function uploadThumbnail(thumbnailDataUri, id, filename) {
var imageBlob = getImageBlob(thumbnailDataUri, "image/jpeg"),
blobData = {
name: filename,
blob: imageBlob
};
// This will instruct Fine Uploader to upload the scaled image
uploader.addBlobs(blobData);
}
function getImageBlob(dataUri, type) {
var binary = atob(dataUri.split(',')[1]),
array = [];
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: type});
}
Note: the getImageBlob function was adapted from this Stack Overflow answer. If this works for you, be sure to upvote the answer I've linked to.
Server-side note
A Blob is pretty much a File without a name property. Your server-side code will handle the upload of a Blob pretty much the same way as it does a File or form submit containing a <input type="file"> form field. The only noticeable difference to your server will be the filename parameter value in the Content-Disposition header of the multipart boundary containing the file. To put it another way, your server may think the image is named "blob" or perhaps some other generic name, due to the way most browsers generate multipart encoded requests that contain Blob objects. Fine Uploader should be able to get around that by explicitly specifying a file name for the browser to include in blob's Content-Disposition header, but this ability does not have wide browser support. Fine Uploader gets around this limitation, to some degree, by including a "qqfilename" parameter with the request containing the actual name of the Blob.
Future native support for thumbnail generation & scaling
The plan is to add native support for thumbnail previews to Fine Uploader. This is covered in feature requests #868 and #896. There are other related feature requests open, such as image rotation and validation related to images. These features and other image-related features will likely be added to Fine Uploader in the very near future. Be sure to comment on the existing feature requests or add additional requests if you'd like.
As of version 4.4 of FineUploader, as Ray Nicholus pointed out would eventually happen, this functionality has been baked into their framework.
Here is an example of setting the upload sizes when creating a FineUploader instance:
var uploader = new qq.FineUploader({
...
scaling: {
sizes: [
{name: "small", maxSize: 100},
{name: "medium", maxSize: 300}
]
}
});
See their page on uploading scaled images.

Is it possible to check how much size of an image is loaded using JavaScript

It's a perfectly dumb question. But I just wanted to clear my doubt. When a image is loading we can check loading state using onload or oncomplete events. But I just wanted to know how much portion of the images is loaded using JavaScript. Is it really possible?
My question is, Can we get image size from URL? and Can we get how much portion of image is loaded in some interval?
If you load the images via an XMLHttpRequest object you can bind to the progress event and check the function argument "event" properties "loaded" and "total". As always, support between various browsers might be a surprise.
From the linked MDN article:
var req = new XMLHttpRequest();
req.addEventListener("progress", onProgress, false);
req.open();
function onProgress(ev) {
if (evt.lengthComputable) {
var percentComplete = evt.loaded / evt.total;
// ...
} else {
// Unable to compute progress information since the total size is unknown.
}
}
[Update] Per commenter #Bergi's question if you want to add the image to the DOM once it has fully downloaded you could add a new image element with:
The "src" attribute equal to the URL you fetched via XHR (and hope that the browser cache will prevent redundant download).
Or set the "src" attribute to a data URI of the Base64 encoded image content and not worry about the browser cache.
var img = new Image(); // or document.createElement('img');
img.src = ... // URL from the above XHR request, or
img.src = 'data:image/png;base64,' + base64(xhrContent);
document.body.appendChild(img);
You can achieve this with the html5 file api for modern browsers which allows to read the filename, filesize, mimetype, and a reference to the file handle - check this example

Categories

Resources