94

The standard HTML file upload works as follows:

<g:form method="post" accept-charset="utf-8" enctype="multipart/form-data"  
     name="form" url="someurl">

    <input type="file" name="file" id="file" />

</form>

In my case I loaded an image into a html5 canvas and want to submit it as a file to the server. I can do:

var canvas; // some canvas with an image
var url = canvas.toDataURL();

This gives me a image/png as base64.

How can I send the base64 image to the server the same way it is done with the input type file?

The problem is that the base64 file is not of the same type as the file, which is inside the input type="file".

Can I convert the base64 that the types are the same for the server somehow?

Robert Caden
  • 19
  • 11
Michael
  • 32,527
  • 49
  • 210
  • 370
  • 3
    `canvas.toBlob()`. Read more at https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement – Ray Nicholus Sep 26 '13 at 15:36
  • 2
    @RayNicholus and How do I get the blob into a file input? – Michael Sep 26 '13 at 15:39
  • 1
    You don't. Send it via an ajax request. – Ray Nicholus Sep 26 '13 at 15:40
  • 2
    @RayNicholus so how do I get the blob to the server? – Michael Sep 26 '13 at 15:41
  • 1
    As I said, send it via an ajax request. – Ray Nicholus Sep 26 '13 at 15:43
  • @RayNicholus is the blob smaller than the base64 representation? – Michael Sep 26 '13 at 15:44
  • look into FormData() to turn a blob into a virtual populated file input. – dandavis Sep 26 '13 at 15:57
  • @dandavis I did not get your point. Can you please post an answer for that? – Michael Sep 26 '13 at 16:01
  • it's too simple for custom code, checkout the docs at https://developer.mozilla.org/en-US/docs/Web/Guide/Using_FormData_Objects?redirectlocale=en-US&redirectslug=Web%2FAPI%2FFormData%2FUsing_FormData_Objects – dandavis Sep 26 '13 at 16:02
  • 1
    @RayNicholus I always get that there is no toBlob() method! – Michael Sep 26 '13 at 16:50
  • @confile: AFAIK canvas.toBlob used to be FF specific. You can create your own blob by decoding your dataURL into an array and supplying that array to a new Blob(). And about file size: yes, blobs are smaller than dataURLs--maybe 1/3 smaller for a .5mb image. – markE Sep 26 '13 at 17:05
  • possible duplicate: http://stackoverflow.com/questions/15685698/getting-binary-base64-data-from-html5-canvas-readasbinarystring - after you get convert it to base64 simply use ajax to send it to the server (e.g. `$.ajax()` from jQuery) – Adam Zielinski Sep 26 '13 at 17:12
  • @AdamZieliński base64 is to big. For example a 1,9MB image will be 2,5MB in base64. How can I get the file size smaller? Maybe as blob? – Michael Sep 26 '13 at 17:35
  • Possible duplicate of [Convert Data URI to File then append to FormData](http://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata) – Dmitry Shvedov Jan 11 '17 at 19:43

8 Answers8

78

For security reasons, you can't set the value of a file-input element directly.

If you want to use a file-input element:

  1. Create an image from the canvas (as you've done).
  2. Display that image on a new page.
  3. Have the user right-click-save-as to their local drive.
  4. Then they can use your file-input element to upload that newly created file.

Alternatively, you can use Ajax to POST the canvas data:

You asked about blob:

var blobBin = atob(dataURL.split(',')[1]);
var array = [];
for(var i = 0; i < blobBin.length; i++) {
    array.push(blobBin.charCodeAt(i));
}
var file=new Blob([new Uint8Array(array)], {type: 'image/png'});


var formdata = new FormData();
formdata.append("myNewFileName", file);
$.ajax({
   url: "uploadFile.php",
   type: "POST",
   data: formdata,
   processData: false,
   contentType: false,
}).done(function(respond){
  alert(respond);
});

Note: blob is generally supported in the latest browsers.

markE
  • 102,905
  • 11
  • 164
  • 176
  • 2
    I want to send the image to the server! – Michael Sep 26 '13 at 15:43
  • 3
    Understood! If you want to use an html file input element, use the method in my answer. Alternatively, you can use Ajax to send your image to your server. Here is a stackoverflow post about using that alternative: http://stackoverflow.com/questions/13198131/how-to-save-a-html5-canvas-as-image-on-a-server – markE Sep 26 '13 at 15:46
  • 1
    it work in Chrome but not in firefox. The data is alway null on the server side. Any idea? – Michael Sep 27 '13 at 00:28
  • The problem in Firefox is that the script breaks. – Michael Sep 27 '13 at 01:17
  • FireFox breaks if the image is to large. (e.g., 7MB) – Michael Sep 27 '13 at 01:20
  • What do you mean firefox "breaks"? What (1) errors or (2) network results are you getting in the Firebug console? What .done or .fail messages are coming back to your ajax request from the server? Have you set an adequate upload_max_filesize and post_max_size (or your webserver equivalent) on your server? – markE Sep 27 '13 at 02:28
  • Won't this send the base64 encoded file, instead of the actual content of the file ? I mean everything you have in the array should be first decoded from base64 and afterwards put in the Blob and sent to the server. – helly0d Jul 07 '14 at 14:34
  • Using out of date libraries, -1 – John Miller Sep 29 '21 at 19:28
44

The canvas image needs to be converted to base64 and then from base64 in to binary. This is done using .toDataURL() and dataURItoBlob()

It was a pretty fiddly process which required piecing together several SO answers, various blog posts and tutorials.

I've created a tutorial you can follow which walks you through the process.

In response to Ateik's comment here's a fiddle which replicates the original post in case you're having trouble viewing the original link. You can also fork my project here.

There's a lot of code but the core of what I'm doing is take a canvas element:

<canvas id="flatten" width="800" height="600"></canvas>

Set it's context to 2D

var snap = document.getElementById('flatten');
var flatten = snap.getContext('2d');

Canvas => Base64 => Binary

function postCanvasToURL() {
  // Convert canvas image to Base64
  var img = snap.toDataURL();
  // Convert Base64 image to binary
  var file = dataURItoBlob(img);
}

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});
}

You could stop at base64 if that's all you need, in my case I needed to convert again to binary so that I could pass the data over to twitter (using OAuth) without use of a db. It turns out you can tweet binary which is pretty cool, twitter will convert it back in to an image.

Pixelomo
  • 6,373
  • 4
  • 47
  • 57
42

This is what worked for me in the end.

canvas.toBlob((blob) => {
  let file = new File([blob], "fileName.jpg", { type: "image/jpeg" })
}, 'image/jpeg');

Reinier68
  • 2,450
  • 1
  • 23
  • 47
16

Currently in spec (very little support as of april '17)

Canvas.toBlob();

https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob

EDIT :

The link provides a polyfill (which seems to be slower from the wording), which code is roughtly equivalent to the @pixelomo answer, but with the same api as the native toBlob method :

A low performance polyfill based on toDataURL :

if (!HTMLCanvasElement.prototype.toBlob) {
  Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
    value: function (callback, type, quality) {
      var canvas = this;
      setTimeout(function() {

    var binStr = atob( canvas.toDataURL(type, quality).split(',')[1] ),
        len = binStr.length,
        arr = new Uint8Array(len);

    for (var i = 0; i < len; i++ ) {
      arr[i] = binStr.charCodeAt(i);
    }

    callback( new Blob( [arr], {type: type || 'image/png'} ) );

      });
    }
  });
}

To be used this way :

canvas.toBlob(function(blob){...}, 'image/jpeg', 0.95); // JPEG at 95% quality

or

canvas.toBlob(function(blob){...}); // PNG
Thierry
  • 5,270
  • 33
  • 39
Danie A
  • 811
  • 8
  • 18
9

Another solution: send the data in var url in a hidden field, decode and save it on the server.

Example in Python Django:

if form.is_valid():
    url = form.cleaned_data['url']
    url_decoded = b64decode(url.encode())        
    content = ContentFile(url_decoded) 
    your_model.model_field.save('image.png', content)
Ardine
  • 339
  • 4
  • 3
8
const canvas = document.querySelector("canvas");
canvas.toBlob(blob => {
  const file = new File([blob], "image.png");
});
4

I used to do it quite simply

var formData = new FormData(),
    uploadedImageName = 'selfie.png';

canvas.toBlob(function (blob) {
    formData.append('user_picture', blob, uploadedImageName);
    $.ajax({
        data: formData,
        type: "POST",
        dataType: "JSON",
        url: '',
        processData: false,
        contentType: false,
    });
});
BoCyrill
  • 1,219
  • 16
  • 17
1

2023 answer using toBlob and TypeScript:

const dataBlob = await new Promise<Blob | null>(
  (resolve) => canvas.toBlob(
    (blob) => resolve(blob),
    "image/png",
  )
);
RaphaMex
  • 2,781
  • 1
  • 14
  • 30