Яндекс.Метрика

    Ни о чём

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

    Здравствуйте хабровчане.

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

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

    Поехали


    Холст


    Сразу скажу, что код 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/