17 May 2011

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

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. Мои безумные умения с этой кисточкой:
Tags:javascriptcanvasграфические редакторыалгоритмкистискетчи
Hubs: Canvas
+28
7.9k 56
Comments 4
Popular right now