[Графический редактор на Canvas] Кисть для скетчей

    Продолжаем неделю canvas на хабре.
    Графические редакторы на флеше/сильверлайте в интернете не редкость. На canvas их намного меньше, но есть и довольно интересные. Вот пара примеров:В этом цикле статей я планирую рассказать о различных моментах, с которыми можно столкнуться при создании графического редактора на canvas. Тривиальные части затрагивать не буду, постараюсь описывать только самое интересное. В этой статье опишу примерный алгоритм создания кисти для скетчей

    Итак, с чего начнем? Начнем с создания простого файлика с canvas внутри. Я постараюсь писать как можно компактнее без лишних фреймворков и прочего, чтобы сам алгоритм был как можно прозрачнее. Вот чистый файл в котором мы будем создавать кисть для скетчей:
    <!DOCTYPE html>
    <html>
     <head>
      <title></title>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <style type="text/css">
        body {
          margin: 0;
        }
        #cnvs {
          border: #000000 1px solid;
        }
      </style>
     </head>
     <body>
     <canvas id="cnvs" width="800" height="500"></canvas>
     </body>
    </html>


    * This source code was highlighted with Source Code Highlighter.

    Канва намеренно расположена в левом верхнем углу для того, чтобы координаты курсора на экране совпадали с координатами пикселей на канве (погрешностью в 1 пиксель бордера пренебрегаем для чистоты кода, но знаем и помним о ней).
    Для создания механизма рисования нам нужны несколько обработчиков стандартных событий мышки (onmousedown, onmouseup,onmousemove). Создадим для каждого функцию.
    function mDown(e){
    };
    function mUp(e){
    };
    function mMove(e){
    };


    * This source code was highlighted with Source Code Highlighter.

    Еще нам нужны несколько переменных: в action мы будем хранить текущее состояние левой кнопки мыши (нажата или нет), в ctx — 2d контекст для рисования на канве, в points — массив для хранения точек, в pointer — указатель на один из элементов этого массива.
    Итак, в чем же заключается алгоритм? Понаблюдав как рисует скетчи мой приятель, я примерно представил себе механизм работы будущей кисти. Основные моменты которые нужно учитывать для создания такой кисти: скетчи рисуются штрихами, кисть должна быть слабоинтенсивной (мой приятель слабо давил на карандаш, но прорисовывал одно место несколько раз, поэтому линия получается мохнатой с различными хвостиками туда-сюда), на изгибах линия становится шире.
    Моя версия алгоритма такова:
    • вслед за мышкой рисуем обычную линию, как если бы это был карандаш
    • кроме того соединяем текущую точку с какой-нибудь давно нарисованной(это придаст толщины на изгибах)
    • все это должно быть с небольшим элементом случайности (мохнатость)
    • кисть будет частично прозрачной (слабая интенсивность)

    Первый и второй пункт для наглядности изображены на рисунке:

    Итак, в массиве points храним координаты последних 10 точек (я выбрал число 10, вы можете поэкспериментировать с этим). Интенсивность убавляем с помощью прозрачности (выставляем 0.1 — это означает, что наша кисть будет на 90% прозрачна). Вот код объявления переменных и получения доступа к канве:
    var action = "up";
    var ctx,points,pointer;
    function initcnvs(){
      ctx = document.getElementById('cnvs').getContext('2d');
      ctx.globalAlpha = 0.1;
      points = new Array(10);
    };


    * This source code was highlighted with Source Code Highlighter.

    Вызов функции initcnvs() вешаем на onload у body. Остальные функции на соответствующие события:
    <body onload="initcnvs()" onmousedown="mDown(event)" onmousemove="mMove(event)" onmouseup="mUp(event)">

    * This source code was highlighted with Source Code Highlighter.

    Наконец сам алгоритм. При нажатии кнопки меняем action на down, кидаем первую точку в наш массив и выставляем указатель на нее.
    function mDown(e){
      action = "down";
      points[0] = [e.pageX, e.pageY];
      pointer = 0;
    };


    * This source code was highlighted with Source Code Highlighter.

    При отпускании кнопки — очищаем массив и меняем action на up.
    function mUp(e){
      points = new Array(10);
      action = "up";
    };


    * This source code was highlighted with Source Code Highlighter.

    И наконец при движении (только если зажата мышка) мы определяем новую точку в массиве (если дошли до конца, то начинаем с начала). Далее рисуем обычный отрезок соединяющий предыдущую точку с новыми координатами курсора и, если массив уже достаточно наполнен, соединяем текущее положение курсора с самой старой точкой из массива, при этом добавляем рандомом погрешность в 5 пикселей. В конце сохраняем новые координаты в массив.
    function mMove(e){
      if (action == "down") {
        var nextpoint = pointer + 1;
        if (nextpoint > 9) nextpoint = 0;
        ctx.beginPath();
        ctx.moveTo(points[pointer][0],points[pointer][1]);
        ctx.lineTo(e.pageX, e.pageY);
        if (points[nextpoint]) {
          ctx.moveTo(points[nextpoint][0] + Math.round(Math.random()*10-5),points[nextpoint][1] + Math.round(Math.random()*10-5));
          ctx.lineTo(e.pageX, e.pageY);
        }
        ctx.stroke();
        pointer = nextpoint;
        points[pointer] = [e.pageX, e.pageY];
      }
    };


    * This source code was highlighted with Source Code Highlighter.

    Вот как выглядит эта кисточка в движении:

    Вы можете сами поэкспериментировать с параметрами, случайностью, интенсивностью и посмотреть на результаты. Вот исходный код всего примера, достаточно компактно вышло:
    <!DOCTYPE html>
    <html>
     <head>
      <title></title>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <style type="text/css">
        body {
          margin: 0;
        }
        #cnvs {
          border: #000000 1px solid;
        }
      </style>
      <script type="text/javascript">
        var action = "up";
        var ctx,points,pointer;
        function initcnvs(){
          ctx = document.getElementById('cnvs').getContext('2d');
          ctx.globalAlpha = 0.1;
          points = new Array(10);
        };
        function mDown(e){
          action = "down";
          points[0] = [e.pageX, e.pageY];
          pointer = 0;
        };
        function mUp(e){
          points = new Array(10);
          action = "up";
        };
        function mMove(e){
          if (action == "down") {
            var nextpoint = pointer + 1;
            if (nextpoint > 9) nextpoint = 0;
            ctx.beginPath();
            ctx.moveTo(points[pointer][0],points[pointer][1]);
            ctx.lineTo(e.pageX, e.pageY);
            if (points[nextpoint]) {
              ctx.moveTo(points[nextpoint][0] + Math.round(Math.random()*10-5),points[nextpoint][1] + Math.round(Math.random()*10-5));
              ctx.lineTo(e.pageX, e.pageY);
            }
            ctx.stroke();
            pointer = nextpoint;
            points[pointer] = [e.pageX, e.pageY];
          }
        };
      </script>
     </head>
     <body onload="initcnvs()" onmousedown="mDown(event)" onmousemove="mMove(event)" onmouseup="mUp(event)">
     <canvas id="cnvs" width="800" height="500"></canvas>
     </body>
    </html>


    * This source code was highlighted with Source Code Highlighter.


    Вот и все, надеюсь, статья оказалась полезной для вас. В дальнейшем планирую рассказать об умной штриховке, кнопке «отмена» для канвы, трюке с размытой кисточкой и многом другом. Кроме того, если вас что-то интересует по теме — отпишите в комментариях, расскажу и об этом тоже. Ошибки в тексте — в приват, заранее спасибо.

    P.S. Мои безумные умения с этой кисточкой:
    • +28
    • 7,5k
    • 4
    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 4

      +2
      Спасибо за украденное время )
        0
        Недавно устанавливал аналогичное Android-приложение sketcher (всего 40kb). Забавно все это.
          +1
          Потрясающе, а главное так просто :)
          Закинул себе в закладки
            0
            А я меня в школе написал себе графический редактор, в котором моим любимым инструментом был такой:
            jsfiddle.net/pz_godlin/eghacjrx/

            :)

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое