Отправка изображений из элементов Canvas с помощью Ajax и PHP $ файлов

мне нужно иметь возможность отправлять изображение и некоторые поля формы из элемента 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. Это не вариант, поскольку я пытаюсь передать изображение в функцию media_handle_upload () Wordpress, которая требует индекс $_FILES, указывающий на читаемое изображение. Я также не могу декодировать, сохранять и изменять "tmp_name" соответственно, так как это нарушает проверки безопасности.

Итак, я нашел это: http://www.webtoolkit.info/javascript-base64.html и попытался сделать декодирование на стороне клиента:

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

но по какой-то причине я все еще не получаю читаемый файл, когда он попадает в PHP. Поэтому возникает вопрос: "почему?"или "что я делаю не так?"или "это вообще возможно?" :-)

любая помощь очень высоко ценится!

спасибо, Кейн!--4-->

2 ответов


основываясь на отличном ответе Натана, я смог finnagle его так, что он все еще проходит через jQuery.Аякс. Просто добавьте это в запрос 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, который переопределен, так что" Отправить "означает"sendAsBinary". Тогда jQuery поступает правильно.


к сожалению, это невозможно в JavaScript без некоторой промежуточной кодировки. Чтобы понять, почему, предположим, вы base64 декодировали и разместили данные, как вы описали в своем примере. Первые несколько строк в hex допустимого файла 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 в Википедии.

вы не можете предотвратить это с помощью строк JavaScript, поэтому вы не можете загружать эти двоичные данные через AJAX без использования base64.

EDIT: после некоторого дальнейшего копания это возможно с некоторыми современными браузерами. Если браузер поддерживает 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);
  };
}