Convert Data URI to File then append to FormData

JavascriptHtmlWebkit

Javascript Problem Overview


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?

Thanks.

Javascript Solutions


Solution 1 - Javascript

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

Solution 2 - Javascript

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

Solution 3 - Javascript

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

Solution 4 - Javascript

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

Solution 5 - Javascript

My preferred way is canvas.toBlob()

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

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

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

  // Upload
  // fetch('upload', {method: 'POST', body: fd})
})

Solution 6 - Javascript

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

Solution 7 - Javascript

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?

Solution 8 - Javascript

var BlobBuilder = (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder);

can be used without the try catch.

Thankx to check_ca. Great work.

Solution 9 - Javascript

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

Solution 10 - Javascript

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

Solution 11 - Javascript

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

Solution 12 - Javascript

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

Solution 13 - Javascript

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

Solution 14 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionStoiveView Question on Stackoverflow
Solution 1 - JavascriptStoiveView Answer on Stackoverflow
Solution 2 - Javascriptvava720View Answer on Stackoverflow
Solution 3 - JavascriptWilliam T.View Answer on Stackoverflow
Solution 4 - JavascriptcuixipingView Answer on Stackoverflow
Solution 5 - JavascriptEndlessView Answer on Stackoverflow
Solution 6 - JavascriptMimoView Answer on Stackoverflow
Solution 7 - JavascriptChris BoscoView Answer on Stackoverflow
Solution 8 - JavascriptNafis AhmadView Answer on Stackoverflow
Solution 9 - JavascripttopkaraView Answer on Stackoverflow
Solution 10 - JavascriptvekerdybView Answer on Stackoverflow
Solution 11 - JavascriptwilfredonoyolaView Answer on Stackoverflow
Solution 12 - JavascriptSendyView Answer on Stackoverflow
Solution 13 - JavascriptCat ChenView Answer on Stackoverflow
Solution 14 - JavascriptfengView Answer on Stackoverflow