将 HTML5画布转换为要上传的文件?

标准的 HTML 文件上载工作如下:

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


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


</form>

在我的示例中,我将一个图像加载到 html5画布中,并希望将其作为文件提交给服务器。 我可以做到:

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

这给我一个图像/png 作为 base64。

如何将 base64映像以与输入类型文件相同的方式发送到服务器?

问题是 base64文件的类型与该文件不同,该文件位于 input type = “ file”内部。

我是否可以将 base64转换为服务器的类型是相同的?

104412 次浏览

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.

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)

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.

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

This is what worked for me in the end.

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


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,
});
});
const canvas = document.querySelector("canvas");
canvas.toBlob(blob => {
const file = new File([blob], "image.png");
});