320

I've been trying to re-implement an HTML5 image uploader like the one on the Mozilla Hacks site, but that works with WebKit browsers. Part of the task is to extract an image file from the canvas object and append it to a FormData object for upload.

The issue is that while canvas has the toDataURL function to return a representation of the image file, the FormData object only accepts File or Blob objects from the File API.

The Mozilla solution used the following Firefox-only function on canvas:

var file = canvas.mozGetAsFile("foo.png");

...which isn't available on WebKit browsers. The best solution I could think of is to find some way to convert a Data URI into a File object, which I thought might be part of the File API, but I can't for the life of me find something to do that.

Is it possible? If not, any alternatives?

Nakilon
  • 34,866
  • 14
  • 107
  • 142
Stoive
  • 11,232
  • 4
  • 24
  • 32

14 Answers14

523

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:

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 there, appending the data to a form such that it will be uploaded as a file is easy:

var dataURL = canvas.toDataURL('image/jpeg', 0.5);
var blob = dataURItoBlob(dataURL);
var fd = new FormData(document.forms[0]);
fd.append("canvasImage", blob);
Stoive
  • 11,232
  • 4
  • 24
  • 32
  • 37
    Why does this always happen... You try to solve a problem for hours upon hours with SO searches here and there. Then you post a question. Within an hour you get the answer from another question. Not that I'm complaining... http://stackoverflow.com/questions/9388412/data-uri-to-object-url-with-createobjecturl-in-chrome-ff – syaz Feb 22 '12 at 05:36
  • @stoive I am able to contruct Blob but can you please explain how do you construct the `POST` or `PUT` to S3 ? – Gaurav Shah Dec 22 '12 at 01:00
  • @stoive I can't understand why you build the Uint8Array object. Where do you use it? – Mimo Feb 18 '13 at 00:51
  • 1
    @mimo - It points to the underlying `ArrayBuffer`, which is then written to the `BlobBuilder` instance. – Stoive Feb 18 '13 at 00:58
  • 2
    @stoive In that case why it's not bb.append(ia)? – Mimo Feb 18 '13 at 04:13
  • Question.. once we have the blob appended to a form.. how do we actually present that as a file with parse? for example with parse, I would do something like var parseFile = new Parse.File(name, file)... – gregdevs Oct 17 '14 at 15:59
  • You missed a semicolon on `var mimeString`, jshint tests failed – Luigi De Rosa Oct 18 '14 at 15:02
  • Came here from a comment on my q [here]( http://stackoverflow.com/questions/32662175/can-i-convert-an-image-from-base64-to-binary-client-side?noredirect=1#comment53171000_32662175). Note I wanted to use the file, not append it to a form, so I found this method: `URL.createObjectURL().`, which return the filename as a string. – axlotl Sep 19 '15 at 00:29
  • 6
    Thanks! This solved my problem with a small correction `var file = new File( [blob], 'canvasImage.jpg', { type: 'image/jpeg' } ); fd.append("canvasImage", file); ` – Prasad19sara Jan 20 '16 at 08:57
  • This is great!! Please note to set a suitable value for the quality. The 0.5 of the example is not suitable for anybody (e.g. me :)). Also notice that for .jpg images you will have lost any exif data after manipulation in the canvas. – Bart Jan 28 '16 at 15:35
  • I am having a similar issue - trying to upload canvas picture to facebook. Why do you append it to the form? Is there no way to convert it to `multipart/form-data` without it? – ilyo Mar 07 '16 at 14:52
  • `unescape` is deprecated, should we use `decodeURIComponent` or `decodeURI`? – JoniJnm Apr 13 '16 at 15:45
  • I find it really impressive that you found this all by trying yourself! – 0xc0de Oct 02 '16 at 01:11
  • please telle me if i need to convert a selected file from input type file in dataurl to blob how can i process – Ir Calif Jul 28 '17 at 15:13
  • I am having a problem with this on Safari. I create image file from base 64 string like this but then when image is uploaded it seems like something is wrong – jbojcic Nov 27 '17 at 18:06
  • After I have `fd.append("canvasImage", blob);` how do I upload the file? – aleherzko Jan 11 '18 at 14:35
  • If the file is empty, the blob wil contain the string `'undefined'`; – Alex78191 Mar 15 '22 at 10:53
  • @syaz I've come to the conclusion sometimes you when you write or share your issues it sorts out everything in your mind. countless times ive come up with solutions after writing long texts asking for help. – Gabriel Dec 13 '22 at 14:45
151

BlobBuilder and ArrayBuffer are now deprecated, here is the top comment's code updated with Blob constructor:

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'});
}
vava720
  • 1,511
  • 1
  • 9
  • 2
  • 4
    Just an idea: `array=[]; array.length=binary.length;` ... `array[i]=bina`... etc. So the array is pre-allocated. It saves a push() having to extend the array each iteration, and we're processing possibly millions of items (=bytes) here, so it matters. – DDS Mar 14 '13 at 20:28
  • 2
    Also fails for me on Safari. @WilliamT. 's answer works for Firefox/Safari/Chrome, though. – ObscureRobot Apr 10 '13 at 22:29
  • "binary" is a slightly misleading name, as it is not an array of bits, but an array of bytes. – Niels Abildgaard Jun 16 '14 at 08:42
  • 2
    "type: 'image/jpeg'" - what if it is a png image OR if you do not know the image extension in advance? – Jasper Aug 18 '14 at 10:17
  • 1
    Create Uint8Array at first is better than create a Array and then convert to Uint8Array. – cuixiping May 26 '15 at 22:21
  • why don't you read mime type from dataURI? – Alex78191 Mar 15 '22 at 10:56
  • If you want to send the blob as a file to the server, you can use: `let blob = dataURItoBlob(dataURL); var file = new File([blob], "yourfile.jpg");` – Avatar Apr 17 '23 at 07:59
55

This one works in iOS and Safari.

You need to use Stoive's ArrayBuffer solution but you can't use BlobBuilder, as vava720 indicates, so here's the mashup of both.

function dataURItoBlob(dataURI) {
    var byteString = atob(dataURI.split(',')[1]);
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ab], { type: 'image/jpeg' });
}
William T.
  • 12,831
  • 4
  • 56
  • 53
  • 11
    Great! But you could still keep the mime string dynamic, like in Stoive's solution, I suppose? // separate out the mime component var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] – Per Quested Aronsson Sep 23 '13 at 11:09
  • What is with the fallback for iOS6 with webkit prefix? How do you handle this? – Michael Mar 20 '14 at 22:52
35

Firefox has canvas.toBlob() and canvas.mozGetAsFile() methods.

But other browsers do not.

We can get dataurl from canvas and then convert dataurl to blob object.

Here is my dataURLtoBlob() function. It's very short.

function dataURLtoBlob(dataurl) {
    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
    while(n--){
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {type:mime});
}

Use this function with FormData to handle your canvas or dataurl.

For example:

var dataurl = canvas.toDataURL('image/jpeg',0.8);
var blob = dataURLtoBlob(dataurl);
var fd = new FormData();
fd.append("myFile", blob, "thumb.jpg");

Also, you can create a HTMLCanvasElement.prototype.toBlob method for non gecko engine browser.

if(!HTMLCanvasElement.prototype.toBlob){
    HTMLCanvasElement.prototype.toBlob = function(callback, type, encoderOptions){
        var dataurl = this.toDataURL(type, encoderOptions);
        var bstr = atob(dataurl.split(',')[1]), n = bstr.length, u8arr = new Uint8Array(n);
        while(n--){
            u8arr[n] = bstr.charCodeAt(n);
        }
        var blob = new Blob([u8arr], {type: type});
        callback.call(this, blob);
    };
}

Now canvas.toBlob() works for all modern browsers not only Firefox. For example:

canvas.toBlob(
    function(blob){
        var fd = new FormData();
        fd.append("myFile", blob, "thumb.jpg");
        //continue do something...
    },
    'image/jpeg',
    0.8
);
naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
cuixiping
  • 24,167
  • 8
  • 82
  • 93
34

My preferred way is canvas.toBlob()

But anyhow here is yet another way to convert base64 to a blob using fetch.

const url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="

fetch(url)
.then(res => res.blob())
.then(blob => {
  const fd = new FormData()
  fd.append('image', blob, 'filename')
  
  console.log(blob)

  // Upload
  // fetch('upload', { method: 'POST', body: fd })
})
Endless
  • 34,080
  • 13
  • 108
  • 131
  • 1
    what is fetch and how is it relevant? – Ricardo Freitas Nov 28 '16 at 17:33
  • Fetch is a modern ajax method that you can use instead of `XMLHttpRequest` since data url is just a url, You can use ajax to fetch that resource and you got yourself an option to decide if you want it as blob, arraybuffer or text – Endless Nov 28 '16 at 17:49
  • 1
    @Endless 'fetch()' a local base64 string... a really clever hack! – Diego ZoracKy Mar 06 '17 at 21:18
  • 1
    Keep in mind that `blob:` and `data:` are not universally supported by all fetch implementations. We use this approach, since we _know_ we will only deal with mobile browsers (WebKit), but Edge for instance, do no support itt: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#Browser_compatibility – oligofren Apr 11 '19 at 06:39
  • [Related answer which adds `new File`](https://stackoverflow.com/a/65669404/6243352) – ggorlen May 19 '23 at 22:31
20

Thanks to @Stoive and @vava720 I combined the two in this way, avoiding to use the deprecated BlobBuilder and ArrayBuffer

function dataURItoBlob(dataURI) {
    'use strict'
    var byteString, 
        mimestring 

    if(dataURI.split(',')[0].indexOf('base64') !== -1 ) {
        byteString = atob(dataURI.split(',')[1])
    } else {
        byteString = decodeURI(dataURI.split(',')[1])
    }

    mimestring = dataURI.split(',')[0].split(':')[1].split(';')[0]

    var content = new Array();
    for (var i = 0; i < byteString.length; i++) {
        content[i] = byteString.charCodeAt(i)
    }

    return new Blob([new Uint8Array(content)], {type: mimestring});
}
Mimo
  • 6,015
  • 6
  • 36
  • 46
12

The evolving standard looks to be canvas.toBlob() not canvas.getAsFile() as Mozilla hazarded to guess.

I don't see any browser yet supporting it :(

Thanks for this great thread!

Also, anyone trying the accepted answer should be careful with BlobBuilder as I'm finding support to be limited (and namespaced):

    var bb;
    try {
        bb = new BlobBuilder();
    } catch(e) {
        try {
            bb = new WebKitBlobBuilder();
        } catch(e) {
            bb = new MozBlobBuilder();
        }
    }

Were you using another library's polyfill for BlobBuilder?

Chris Bosco
  • 1,080
  • 10
  • 16
  • I was using Chrome with no polyfills, and don't recall coming across namespacing. I eagerly anticipate `canvas.toBlob()` - it seems much more appropriate than `getAsFile`. – Stoive Jun 28 '11 at 00:45
  • 1
    `BlobBuilder` seem to be deprecated in favor of `Blob` – sandstrom Oct 10 '12 at 12:57
  • BlobBuilder is deprecated and this pattern is awful. Better would be : window.BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder); the nested try catches are really ugly and what happens if none of the builders are available? – Chris Hanson Sep 12 '13 at 18:05
  • How is this awful? If an exception is thrown and 1) BlobBuilder doesn't exist then nothing happens and the next block is executed. 2) If it does exist, but an exception is thrown then it is deprecated and shouldn't be used anyway, so it continues into the next try block. Ideally you would check if Blob is supported first, and even before this check for toBlob support – TaylorMac Feb 14 '14 at 19:33
6

Here is an ES6 version of Stoive's answer:

export class ImageDataConverter {
  constructor(dataURI) {
    this.dataURI = dataURI;
  }

  getByteString() {
    let byteString;
    if (this.dataURI.split(',')[0].indexOf('base64') >= 0) {
      byteString = atob(this.dataURI.split(',')[1]);
    } else {
      byteString = decodeURI(this.dataURI.split(',')[1]);
    }
    return byteString;
  }

  getMimeString() {
    return this.dataURI.split(',')[0].split(':')[1].split(';')[0];
  }

  convertToTypedArray() {
    let byteString = this.getByteString();
    let ia = new Uint8Array(byteString.length);
    for (let i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }
    return ia;
  }

  dataURItoBlob() {
    let mimeString = this.getMimeString();
    let intArray = this.convertToTypedArray();
    return new Blob([intArray], {type: mimeString});
  }
}

Usage:

const dataURL = canvas.toDataURL('image/jpeg', 0.5);
const blob = new ImageDataConverter(dataURL).dataURItoBlob();
let fd = new FormData(document.forms[0]);
fd.append("canvasImage", blob);
Community
  • 1
  • 1
vekerdyb
  • 1,213
  • 12
  • 26
6
var BlobBuilder = (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder);

can be used without the try catch.

Thankx to check_ca. Great work.

Nafis Ahmad
  • 2,689
  • 2
  • 26
  • 13
  • 1
    This will still throw an error if the browser supports the deprecated BlobBuilder. The browser will use the old method if it supports it, even if it supports the new method. This is not desired, see Chris Bosco's approach below – TaylorMac Feb 14 '14 at 19:30
5

The original answer by Stoive is easily fixable by changing the last line to accommodate Blob:

function dataURItoBlob (dataURI) {
    // convert base64 to raw binary data held in a string
    // doesn't handle URLEncoded DataURIs
    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 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);
    }

    // write the ArrayBuffer to a blob, and you're done
    return new Blob([ab],{type: mimeString});
}
topkara
  • 886
  • 9
  • 15
5

Thanks! @steovi for this solution.

I have added support to ES6 version and changed from unescape to dataURI(unescape is deprecated).

converterDataURItoBlob(dataURI) {
    let byteString;
    let mimeString;
    let ia;

    if (dataURI.split(',')[0].indexOf('base64') >= 0) {
      byteString = atob(dataURI.split(',')[1]);
    } else {
      byteString = encodeURI(dataURI.split(',')[1]);
    }
    // separate out the mime component
    mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to a typed array
    ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ia], {type:mimeString});
}
crabbly
  • 5,106
  • 1
  • 23
  • 46
wilfredonoyola
  • 485
  • 5
  • 12
  • For anyone getting the deprecation warning on `atob()` - It's deprecated on node.js, you can use `Buffer.from()` now. Not deprecated in-browser, to hide the warning just use `window.atob()` – Chris Hayes Aug 23 '22 at 14:38
1

make it simple :D

function dataURItoBlob(dataURI,mime) {
    // convert base64 to raw binary data held in a string
    // doesn't handle URLEncoded DataURIs

    var byteString = window.atob(dataURI);

    // separate out the mime component


    // write the bytes of the string to an ArrayBuffer
    //var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    // write the ArrayBuffer to a blob, and you're done
    var blob = new Blob([ia], { type: mime });

    return blob;
}
Sendy
  • 170
  • 5
-2

toDataURL gives you a string and you can put that string to a hidden input.

Cat Chen
  • 2,387
  • 17
  • 12
  • 1
    Could you please give an example? I don't wan't to upload a base64 string (which is what doing `` would do), I want to upload the file data (like what `` does, except you're not allowed to set the `value` property on these). – Stoive Feb 24 '11 at 03:11
  • This should be a comment rather than an answer. Please elaborate the answer with proper explanation. @Cat Chen – Lucky Mar 30 '15 at 08:31
-8

I had exactly the same problem as Ravinder Payal, and I've found the answer. Try this:

var dataURL = canvas.toDataURL("image/jpeg");

var name = "image.jpg";
var parseFile = new Parse.File(name, {base64: dataURL.substring(23)});
Spooky
  • 2,966
  • 8
  • 27
  • 41
feng
  • 27
  • 2
  • 3
    Are you really suggesting to use Parse.com ? You should mention that your answer require dependencies ! – Pierre Maoui Jul 07 '16 at 14:44
  • 3
    WTF ? Why any one will upload base64 code of image to PARSE server and then download?When we can directly upload base64 on our servers and main thing is that it takes same data to upload base64 string or image file. And if you just want to alow user to download the image , you can use this `window.open(canvas.toDataURL("image/jpeg"))` – Ravinder Payal Jul 19 '16 at 18:52