使用Ajax和PHP $ _FILES从Canvas元素发送图像

我需要能够发送图像和一些表单字段从客户端的canvas元素到PHP脚本,最后在$ _POST和$ _FILES。 当我这样发送:

<script type="text/javascript"> var img = canvas.toDataURL("image/png"); ... ajax.setRequestHeader('Content-Type', "multipart/form-data; boundary=" + boundary_str); var request_body = boundary + '\n' + 'Content-Disposition: form-data; name="formfield"' + '\n' + '\n' + formfield + '\n' + '\n' + boundary + '\n' + 'Content-Disposition: form-data; name="async-upload"; filename="' + "ajax_test64_2.png" + '"' + '\n' + 'Content-Type: image/png' + '\n' + '\n' + img + '\n' + boundary; ajax.send(request_body); </script> 

$ _POST和$ _FILES都回来填充,但$ _FILES中的图像数据仍然需要像这样的解码:

 $loc = $_FILES['async-upload']['tmp_name']; $file = fopen($loc, 'rb'); $contents = fread($file, filesize($loc)); fclose($file); $filteredData=substr($contents, strpos($contents, ",")+1); $unencodedData=base64_decode($filteredData); 

…为了保存它作为一个可读的PNG。 这不是一个选项,因为我试图将图像传递给Wordpress的media_handle_upload()函数,该函数需要$ _FILES指向可读图像的索引。 我也不能解码,保存和相应地改变'tmp_name',因为它违反安全检查。

所以,我发现这个: http : //www.webtoolkit.info/javascript-base64.html,并尝试在客户端进行解码:

 img_split = img.split(",",2)[1]; img_decoded = Base64.decode( img_split ); 

但由于某种原因,我仍然没有得到一个可读的文件,当它到达PHP。 所以问题是:“为什么?” 或者“我做错了什么?” 或者“这甚至可能吗?” 🙂

任何帮助非常感谢!

谢谢,凯恩

基于Nathan的出色答案,我能够find答案,以便它仍然通过jQuery.ajax。 只需将此添加到ajax请求:

  xhr: function () { var myXHR = new XMLHttpRequest(); if (myXHR.sendAsBinary == undefined) { myXHR.legacySend = myXHR.send; myXHR.sendAsBinary = function (string) { var bytes = Array.prototype.map.call(string, function (c) { return c.charCodeAt(0) & 0xff; }); this.legacySend(new Uint8Array(bytes).buffer); }; } myXHR.send = myXHR.sendAsBinary; return myXHR; }, 

基本上,你只是返回一个被覆盖的xhr对象,以便“send”的意思是“sendAsBinary”。 然后jQuery做正确的事情。

不幸的是,这在JavaScript没有一些中间编码的情况下是不可能的。 要理解为什么,让我们假设你base64解码并发布数据,就像你在例子中描述的那样。 在一个有效的PHP文件的hex头几行可能看起来像这样:

 0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR 0000010: 0000 0080 0000 0080 0806 0000 00c3 3e61 ..............>a 

如果您查看上传的PNG文件的相同范围的hex,它将如下所示:

 0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR 0000010: 0000 00c2 8000 0000 c280 0806 0000 00c3 ................ 

差异是微妙的。 比较第二行的第二列和第三列。 在有效的文件中,四个字节是0x00 0x80 0x00 0x00 。 在您上传的文件中,相同的四个字节是0x00 0xc2 0x80 0x00 。 为什么?

JavaScriptstring是UTF。 这意味着任何ASCII二进制值(0-127)都用一个字节编码。 但是,从128-2047任何东西都得到两个字节。 上传的文件中额外的0xc2是这个多字节编码的人工产物。 如果你想知道为什么发生这种情况,你可以阅读更多关于维基百科UTF编码 。

您无法防止JavaScriptstring发生这种情况,所以您无法通过AJAX上传这些二进制数据,而无需使用base64。

编辑:经过一些进一步的挖掘,这是可能的一些现代浏览器。 如果浏览器支持XMLHttpRequest.prototype.sendAsBinary (Firefox 3和4),则可以使用它来发送图像,如下所示:

 function postCanvasToURL(url, name, fn, canvas, type) { var data = canvas.toDataURL(type); data = data.replace('data:' + type + ';base64,', ''); var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); var boundary = 'ohaiimaboundary'; xhr.setRequestHeader( 'Content-Type', 'multipart/form-data; boundary=' + boundary); xhr.sendAsBinary([ '--' + boundary, 'Content-Disposition: form-data; name="' + name + '"; filename="' + fn + '"', 'Content-Type: ' + type, '', atob(data), '--' + boundary + '--' ].join('\r\n')); } 

对于没有sendAsBinary浏览器,但是有Uint8Array (Chrome和WebKit),你可以像这样Uint8Array它:

 if (XMLHttpRequest.prototype.sendAsBinary === undefined) { XMLHttpRequest.prototype.sendAsBinary = function(string) { var bytes = Array.prototype.map.call(string, function(c) { return c.charCodeAt(0) & 0xff; }); this.send(new Uint8Array(bytes).buffer); }; }