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

时间:2011-03-13 21:51:58

标签: php ajax canvas base64

我需要能够将客户端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时,我仍然没有得到可读文件。 所以问题是:“为什么?”或者“我做错了什么?”或者“这有可能吗?” : - )

非常感谢任何帮助!

谢谢, 凯恩

2 个答案:

答案 0 :(得分:14)

不幸的是,如果没有一些中间编码,这在JavaScript中是不可能的。为了理解原因,让我们假设您已经解码并发布了数据,就像您在示例中所描述的那样。有效PHP文件的十六进制的前几行可能如下所示:

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

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

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。为什么呢?

JavaScript字符串是UTF。这意味着任何ASCII二进制值(0-127)都用一个字节编码。但是,128-2047之间的任何内容都会两个字节。上传文件中的额外0xc2是此多字节编码的工件。如果您想知道为什么会发生这种情况,您可以阅读有关UTF encoding on Wikipedia的更多信息。

您无法阻止JavaScript字符串发生这种情况,因此您无法在不使用base64的情况下通过AJAX上传此二进制数据。

编辑:经过一些进一步的挖掘,这可以通过一些现代浏览器实现。如果浏览器支持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)的浏览器,您可以这样填充:

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

答案 1 :(得分:4)

在Nathan的优秀答案的基础上,我能够完成它,以便它仍然通过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做正确的事。