Pull to refresh

Javascript: Отправка изображения на канвасе на сервер

Reading time4 min
Views9.4K
Здравствуйте хабровчане.

Решил я сделать отправку изображения с канваса на сервер.
А что из этого получилось смотрите под катом.

Итак, нам нужен браузер, поддерживающий холст.

Я решил сделать возможность мышкой чего-нибудь нарисовать на канвасе и отправить эту картинку на сервер.

Поехали


Холст


Сразу скажу, что код javascript-овый будет написан на javascript`е.
Ничего против библиотек не имею, сам их использую. Просто, это более общий случай. При желании можно будет переписать под библиотеки.

Создаём канвас/холст (нужное подчеркнуть):
<canvas id="canvas" ...></canvas>


Теперь надо сделать так, чтобы можно было рисовать на канвасе.
Для рисования нам нужно получить контекст отображения 2D, создать путь, переместиться в начальную точку,
вызвать метод LineTo() для проведения линии в конечную точку, вызвать метод stroke() для рисования контура, закрыть путь.

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.closePath();


Всё это можно видеть в моём коде, в обработчиках onmousedown, onmouseup и onmousemove.

Отправка на сервер

Но с холстом-то ладно, есть куча библиотек для работы с ним. Теперь о том, как отправлять картинки на сервер.

Нам нужно сделать ajax-запрос методом POST.
Только картиночку надо посылать не вот так:
«image=%01%02%03...», а как файл.

Нам нужно поставить Content-type не «application/x-www-form-urlencoded», а «multipart/form-data».
Это значит, что тело запроса будет состоять из других подзапросов, которые сами будут иметь свои собственные заголовки и тела.
Как выглядит тело такого запроса можно посмотреть в LiveHttpHeaders в Firefox-е, создав html-страничку с формой,
с атрибутом enctype=«multipart/form-data»:



Каждый такой подзапрос, соответствует определенному полю формы.
На рисунке у меня отправляется форма с текстовым полем с name=«id» и полем «file» для отправки файла.
Я загружаю файл «loading.gif»

Нам нужно придумать граничный разделитель (boundary), который будет разделять тела запросов.
На рисунке этим разделителем является строка "---------------------------12722593819037", этот разделитель должен разделять эти
подзапросы и не должен встречаться в них.
Перед каждым таким подзапросом должна быть строка "--" + boundary, а после всех — строка "--" + boundary + "--"

Об этом можно почитать здесь: rfc 1521
О form-data можно почитать в rfc 1867.

Замечу, что на 19.12.2010 в Хроме уже есть объект FormData для создания form-data, а в Файрфоксе будет только в 4 версии.

Для отправки содержимого канваса, будем использовать метод dataURL, который возвращает картинку в виде base64 строки.

Пример:
var canvas = document.getElementById('canvas');
var body = canvas.todataURL();
...
xmlhttprequest.open('POST', url);
...
xmlhttprequest.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundary);
...
var data = 
  /*--boundary*/
  "--" + boundary + "\n" + 
  
  /*заголовок*/
  "Content-Disposition: form-data; name=\"file\"; filename=\"filename\"\n" + 
  "Content-type: image/png\n\n" +
  
  /*тело*/
  body + 
  
  /*--boundary--*/
  "\n--" + boundary + "--\n"; 
...
xmlhttprequest.send(data);


Серверная часть

Для электронных писем можно написать заголовок «Content-Transfer-Encoding» со значением «base64» для указания того,
что прикрепленный файл зашифрован в base64. К сожалению у меня добавление такого заголовка ничего не дало.
Пришлось декодировать base64 на сервере самому.

Одно лишь маленькое «но», я скажу.
Когда читаешь файл кусочками и декодируешь из base64, нужно читать за один раз число байтов кратное 4.
Это связано с кодировкой base64. Она рассматривает каждые 3 байта, как набор из 4 6-битовых символов, а потом каждый
такой 6-битовый символ отображает как какой-нибудь обычный 8-битовый текстовый символ.
(если в конце не хватает двух или одного байта, то берутся нулевые байты, а в полученном тексте добавляется "=" или "==")

Соответственно, если за один раз прочитать количество символов не кратное 4, то мы не получим картинки (можете проверить, подправив мой код).
Собственно, вот код декодирования картинки на PHP:

$f  = fopen ($filename, 'r') or die('Cannot open file');#Читаем загруженный файл
$f2 = fopen ($path . '/' . $name, 'w') or die('Cannot open file');#Записываем новый фалй
$length = 64;//должно быть кратно 4!
$error = FALSE;
while ($content = fread($f, $length)) {
  $content = base64_decode($content, TRUE);
  
  if ($content === FALSE) {
    $error = TRUE;
    break;
  }  
  fwrite($f2, $content);
}
fclose($f);
fclose($f2);


Собственно всё это можно посмотреть на труъкодинг.рф/canvas/
Слева будет картинка, чего-нибудь на ней рисуете, нажимаете кнопку «Send», картинка отправляется на сервер,
а потом отображается справа.



Код здесь: труъкодинг.рф/canvas/canvas.zip,
и здесь: http://www.rapidshare.ru/1709200,
и здесь: http://narod.ru/disk/1762612001/canvas.zip.html

на яндексе иногда проскакивает сообщение «файл не найден», пробуйте несколько раз.

Пример здесь: труъкодинг.рф/canvas/

Ссылки:
RFC MIME
form-data
Canvas

Удачи!

upd: Здесь несколько прикольных изображений, залитых пользователями: http://труъкодинг.рф/cpg/thumbnails.php?album=1
upd: Подобное оказывается реализовано здесь: http://www.nihilogic.dk/labs/canvas2image/
Tags:
Hubs:
+20
Comments22

Articles

Change theme settings