![](https://habrastorage.org/storage3/ac4/16d/776/ac416d7764f22ca79f8124e67bf955e3.jpg)
Доброго времени, Хабр!
Я всегда очень любил котов, и любил их рисовать, особенно морды кошачьи. Чуть изменишь форму, линию — и совершенно другое выражение, другое настроение. У меня листы А4 были изрисованы под предел. И тут мне недавно стукнуло в голову — а что если сделать генератор морд котов? Чтобы нажал на кнопочку и тебе выкинется случайная морда кота. Как можно более случайная и интересная. Давайте же посмотрим, как же сделать такую штуку.
Прошу под кат, любители котов.
Всё будем делать на JS и Canvas'е, и я не привожу код инициализации, настройки и подобного. Этого в интернетах полно, а нам же интересно как рисовать котов, правда?
Приведу лишь несколько предопределенных функций, просто для дальнейшего удобства, вот они:
function add(func, scale){
Graphics.ctx.scale(scale.x, scale.y);
func(Graphics.ctx);
Graphics.ctx.scale(1, 1);
}
Меняем размер канваса, что-то рисуем, возвращаем назад.
function drawСircle(ctx, pos, radius, fillColor, strokeColor, lineWidth)
{
ctx.beginPath();
ctx.arc(center.x - pos.x, center.y + pos.y, radius, 0, 2*Math.PI, false);
ctx.fillStyle = fillColor;
ctx.fill();
ctx.lineWidth = lineWidth;
ctx.strokeStyle = strokeColor;
ctx.stroke();
}
А это рисование окружностей\кругов. Тоже ничего особенного, всё это можно прочитать миллионы раз и на хабре и где угодно, давайте скорее к котам!
Шаг 1 — Голова
Начнем мы с головы. Шо есмь голова? Окружность. Которую можно немного сжать, или расширить.
Ещё можно задать толщину обводки, это тоже сделаем. И есть маленькая деталь, смотрим в коде комментарии.
Код весь прокомментирован, думаю проблем с понимаем не возникнет.
//Генерим случайный радиус
var radius = Math.sRandom(60, 70);
//Изменяем размеры
var scaleCircle = {x:Math.sRandom(1,1.1),y:Math.sRandom(0.9,1.0)};
//Задаем цвета
var whiteColor = "#fff";
var blackColor = "#000";
//Рисуем
add(function(ctx){
// (Math.sRandom(0, 100) < 95 ? blackColor : whiteColor) - эта строчка, иногда, редко, делает контур белым, т.е. голова оказывается без контура. Уверяю, это получаеться здорово.
drawСircle(ctx, {x:0,y:0}, radius, whiteColor, (Math.sRandom(0, 100) < 95 ? blackColor : whiteColor), Math.sRandom(2,5));
}, scaleCircle);
Воть и голова появилась.
![](http://habrastorage.org/storage3/090/aef/705/090aef705e1597619e3ad99f41501e86.png)
Идем дальше!
Шаг 2 — Уши
Уши у котов — одно из самых выразительных элементов морды. Поэтому чем более уши будут разные, по разному повернуты, разной ширины, тем больше будет разнообразия выражений.
Делаем.
//Вектор, который будем поворачивать, в поисках точек ушей
var dir = {x:0, y:radius};
//Первый угол
var angleOne = Math.PI + Math.PI/Math.sRandom(2, 5);
//Поворачиваем вектор и получаем первую точку
var pointR1 = VectorRot(dir, angleOne);
//Делаем вторую точку, через поворт вектора на первый угол минус отклонение
var pointR2 = VectorRot(dir, angleOne - Math.PI/Math.sRandom(4, 7));
//Считаем верхнюю точку уха
var topPointR = {x:((pointR1.x + pointR2.x) / 2)+Math.sRandom(-10, 10), y:pointR2.y - 30 + Math.sRandom(0, 5)};
//Рисуем
add(function(ctx){
//Рисуем правое ухо
ctx.beginPath();
ctx.strokeStyle = strokeColor;
ctx.fillStyle = fillColor;
ctx.lineWidth = Math.sRandom(2, 4);
topPointR = {x:topPointR.x+Math.sRandom(-1,5), y:topPointR.y+Math.sRandom(-5,5)};
ctx.moveTo(center.x + pointR1.x,center.y + pointR1.y);
ctx.lineTo(center.x + topPointR.x,center.y + topPointR.y);
ctx.lineTo(center.x + pointR2.x,center.y +pointR2.y);
ctx.fill();
ctx.stroke();
//Рисуем левое ухо
ctx.beginPath();
ctx.strokeStyle = strokeColor;
ctx.fillStyle = fillColor;
ctx.lineWidth = Math.sRandom(2, 4);
var topPointL = VectorXInvert({x:topPointR.x+Math.sRandom(-5,5), y:topPointR.y+Math.sRandom(-5,5)});
var pointL1 = VectorXInvert(pointR1);
var pointL2 = VectorXInvert(pointR2);
ctx.moveTo(center.x + pointL1.x,center.y + pointL1.y);
ctx.lineTo(center.x + topPointL.x,center.y + topPointL.y);
ctx.lineTo(center.x + pointL2.x,center.y +pointL2.y);
ctx.fill();
ctx.stroke();
}, {x:1,y:1});
Иииии вот что у нас получилось:
![](http://habrastorage.org/storage3/72d/525/336/72d525336d3dca0870b028030343a66f.png)
Уже напоминает кота, правда?
Шаг 3 — Усы
Второй во выразительности, после ушей, элемент морды кота. Сделать его не сложно, но сделать так чтобы они выглядели более правдоподобно сложнее. Одним рандомом тут не обойдёшься, приходится крутить цикл, и по счетчику высчитывать точки, для более линейных результатов.
//Расчитываем точки начала усов
//путем выбора вектора, и поворота вектора на почти случайное значение
//Результат - точка начала уса
var pointsR = [];
//Выбираем число усов
var count = Math.floor(Math.sRandom(3, 5));
for (var i = 0; i < count; i++) {
//Делаем вектор, с длиной из радиуса круга / на случайное значение
var dir = {x:0, y:radius/Math.sRandom(1.6,1.9)};
//Выбираем угол, плюс некоторые действия, для красоты
var angleOne = Math.PI/(2 + ((i+1)/4));
//Пвоворачиваем вектор
var pointR1 = VectorRot(dir, angleOne);
//Выбираем y конечной точки, так чтобы первую половину count усы отлонялись в одну сторону
//а во второй половине в другую
var y = pointR1.y+(i < count / 2 ? -Math.sRandom(8, 25) : Math.sRandom(7, 15) );
//Записываем точки начала и конца в массив
pointsR.push({begin:pointR1, end:{x:pointR1.x - Math.sRandom(60, 100),y:y}});
}
//Случайное значение ширины линии
var lineWidth = Math.sRandom(0.5, 2);
//Рисуем
add(function(ctx){
//Правая сторона
for (var i = 0; i < pointsR.length; i++) {
ctx.beginPath();
ctx.strokeStyle = strokeColor;
ctx.fillStyle = fillColor;
ctx.lineWidth = lineWidth;
ctx.moveTo(center.x - pointsR[i].begin.x,center.y + pointsR[i].begin.y);
ctx.lineTo(center.x - pointsR[i].end.x,center.y + pointsR[i].end.y);
ctx.stroke();
}
//Левая сторона
for (var i = 0; i < pointsR.length; i++) {
ctx.beginPath();
ctx.strokeStyle = strokeColor;
ctx.fillStyle = fillColor;
ctx.lineWidth = lineWidth;
var pointLBegin = VectorXInvert(pointsR[i].begin);
var pointLEnd = VectorXInvert(pointsR[i].end);
ctx.moveTo(center.x - pointLBegin.x,center.y + pointLBegin.y);
ctx.lineTo(center.x - pointLEnd.x,center.y + pointLEnd.y);
ctx.stroke();
}
}, {x:1,y:1});
Функция VectorXInvert пусть вас не смущает, просто приходилось часто отражать по x различные элементы, поэтому сделана простая функция, которая делает x = -x;
Смотрим как прорисовывается наш кот:
![](http://habrastorage.org/storage3/0fb/4d2/373/0fb4d2373e1b961a66baa82a8510e1f2.png)
Шаг 4 — Рот
Это довольно сложно сделать реалистично, поэтому сделаем просто линию рта, схематично, но для нашего графического рисунка вполне пойдет.
Пишем код, рисующий линию рта. Сделано на кривых Безье.
//---Рот
//Рот строем с помощью кривых Безье. Задаем четыре точки + 2 точки для отражения
// P0
// P3 | iP3
// | | |
// P2--P1--iP2
var P0 = {x:center.x, y:center.y};
var P1 = {x:center.x, y:center.y + Math.sRandom(40, 65)};
var P2 = {x:center.x - Math.sRandom(29, 36),y: center.y + 40};
var P3 = {x:center.x - Math.sRandom(20, 40), y:center.y + Math.sRandom(23, 28)};
var iP2 = {x:center.x + Math.sRandom(29, 36),y: center.y + 40};
var iP3 = {x:center.x + Math.sRandom(20, 40), y:center.y + Math.sRandom(23, 28)};
//Рисуем
add(function(ctx){
ctx.beginPath();
ctx.strokeStyle = strokeColor;
ctx.fillStyle = fillColor ;
ctx.lineWidth = Math.sRandom(1,3);
ctx.moveTo(P0.x,P0.y );
ctx.bezierCurveTo(P1.x, P1.y, P2.x, P2.y, P3.x, P3.y);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = strokeColor;
ctx.fillStyle = fillColor ;
ctx.lineWidth = Math.sRandom(1,3);
ctx.moveTo(P0.x,P0.y );
ctx.bezierCurveTo(P1.x, P1.y, iP2.x, iP2.y, iP3.x, iP3.y);
ctx.stroke();
}, {x:1,y:1});
Глядим на то, что получилось. Терпение, ещё чуть-чуть осталось!
![](http://habrastorage.org/storage3/b6f/5dc/857/b6f5dc8577f1b893c9a6b830ac1826cb.png)
Шаг 5 — Нос
Нос есть нос. Сделаем его двух вариантов — маленький круг, и треугольничек. Круг будет появляться намного реже. Ну так, для интереса.
Разуметься примешиваем рандом, в том числе в цвета, чтобы получались разные носы — закрашенные, и просто контур. Нам ведь нужно много котов?
//Два типа носа - либо треугольник, либо круг
var chance = Math.sRandom(0, 100);
if(chance < 98)
{
//Коефициент размера
var scale = {x:Math.sRandom(0.9,1.3), y:Math.sRandom(0.9,1.3)};
//Правая точка
var pointR ={x:Math.sRandom(4,5)*scale.x,y:Math.sRandom(-5,-4)*scale.y};
//Левая точка
var pointL ={x:Math.sRandom(-4,-5)*scale.x,y:Math.sRandom(-5,-4)*scale.y};
//Нижняя точка
var bottomPoint ={x:0,y:Math.sRandom(5,6)*scale.y};
//Рисуем
add(function(ctx){
ctx.beginPath();
ctx.strokeStyle = strokeColor;
ctx.fillStyle = (Math.sRandom(0, 100) > 50 ? fillColor : strokeColor);
ctx.lineWidth = Math.sRandom(1,3);
ctx.moveTo(center.x + pointR.x,center.y + 5 + pointR.y);
ctx.lineTo(center.x + bottomPoint.x,center.y + 5 + bottomPoint.y);
ctx.lineTo(center.x + pointL.x,center.y + 5 + pointL.y);
ctx.closePath();
ctx.fill();
ctx.stroke();
}, {x:1,y:1});
}
else
{
//Рисуем кот
add(function(ctx){
drawСircle(ctx, {x:0,y:0}, Math.sRandom(7, 10), strokeColor, strokeColor, 1);
}, {x:1,y:1});
}
Наш кот постепенно превращается в кота. Это не может не радовать.
![](http://habrastorage.org/storage3/942/a4d/4a6/942a4d4a68b6e7bb756780fa92d15c84.png)
Шаг 6 — Глаза
Глаза штука сложная. Нет, нарисовать не сложно — просто две кривых Безье. Но надо ещё добавить закрывающееся глаза, котик же может спать, а может и один глаз приоткрыть. И это надо сделать. В этот раз просто кот, без кода. Ибо большой объем, не хочется утомлять читателя кучей текста. В конце будут приведены исходники, кто захочет поглядит. А мы просто посмотрим на то что получилось.
![](http://habrastorage.org/storage3/ea7/bd8/c6d/ea7bd8c6d683a866871c02a340f69bc4.png)
Кот уже есть, но добавим ещё некоторый декор.
Шаг 7 — Точки усов на щеках
Знаете, бывают такие. Вероятность появления естественно, не 100%. Вот код, потом результат.
Код простой — просто раскидываем точки на некотором расстояниие от носа.
if(Math.sRandom(0, 100) > 60)
{
add(function(ctx){
for (var i = 0; i < Math.sRandom(5, 7); i++) {
var P = {x:Math.sRandom(20, 40),y:Math.sRandom(0, 30)};
drawСircle(ctx, P, 1, strokeColor, strokeColor, 1);
}
for (var i = 0; i < Math.sRandom(5, 7); i++) {
var P = {x:-Math.sRandom(20, 40),y:Math.sRandom(0, 30)};
drawСircle(ctx, P, 1, strokeColor, strokeColor, 1);
}
}, {x:1,y:1});
}
Результат:
![](http://habrastorage.org/storage3/3a2/d3f/d13/3a2d3fd135791347861c6e3002fbdce0.png)
Шаг 8 — «Челка»
Просто иногда рисуем парочку линий сверху. На словах тяжело описать, смотрим код, смотрим что получается.
if(Math.sRandom(0, 100) > 75)
{
add(function(ctx){
for (var i = 0; i < Math.sRandom(3, 5); i++) {
//Берем вектор равный по длине радиусу
var radiusVector = {x:0,y:-radius};
//Поворачиваем вектор на некоторое отлонение
//Нужно чтобы волосы выходили ровно из окружности
radiusVector = VectorRot(radiusVector, Math.sRandom(-0.01, 0.01))
//Строим нижнии точки
var P0 = {x:Math.sRandom(-25, 25), y: radiusVector.y};
var P1 = {x:Math.sRandom(-25, 25), y: Math.sRandom(-50, -40)};
//Ширина волоса
var lineWidth = Math.sRandom(0.5, 1.5);
//Рисуем
drawLine(ctx, P0, P1, strokeColor, strokeColor, lineWidth);
}
}, {x:1,y:1});
}
Здорово, правда?
![](http://habrastorage.org/storage3/bcf/2f2/b18/bcf2f2b18c85dc665f478b9e0a4a011f.png)
Шаг 8 — Колокольчик или бантик
Ну а это уже просто для интереса, я думал чтобы ещё добавить, и решил добавить бантик и колокольчик. Не судите строго, я развлекаюсь :)
Как всегда код, потом красивые картинки с котиками.
//Декорации
//Либо бабочка либо колокольчик
var chanceBottom = Math.sRandom(0, 100);
if(chanceBottom > 50)
{
var chance = Math.sRandom(0, 100);
if(chance > 90)
{
//Бабочка - два треугольника + круг, с точками в случайном дипазаоне
var P = {x:0,y:radius};
var P0 = {x:Math.sRandom(20, 45), y:radius- Math.sRandom(13, 22)};
var P1 = {x:Math.sRandom(20, 45), y:radius+ Math.sRandom(13, 22)};
add(function(ctx){
var color = (Math.sRandom(0, 100) > 50 ? fillColor : strokeColor);
ctx.beginPath();
ctx.strokeStyle = strokeColor;
ctx.fillStyle = color;
ctx.lineWidth = Math.sRandom(2, 5);
ctx.moveTo(center.x + P.x,center.y + P.y);
ctx.lineTo(center.x + P0.x,center.y + P0.y);
ctx.lineTo(center.x + P1.x,center.y + P1.y);
ctx.closePath();
ctx.stroke();
ctx.fill();
ctx.beginPath();
ctx.strokeStyle = strokeColor;
ctx.fillStyle = color;
ctx.lineWidth = Math.sRandom(2, 5);
ctx.moveTo(center.x - P.x,center.y + P.y);
ctx.lineTo(center.x - P0.x,center.y + P0.y);
ctx.lineTo(center.x - P1.x,center.y + P1.y);
ctx.closePath();
ctx.stroke();
ctx.fill();
drawСircle(ctx, P, Math.sRandom(6, 12), (Math.sRandom(0, 100) > 50 ? fillColor : strokeColor), strokeColor, Math.sRandom(1, 3));
}, {x:1,y:1});
}
}
else
{
//Колокольчик
var chance = Math.sRandom(0, 100);
if(chance > 90)
{
//Колокольчик - треугольник, внизу с кругом. Строится по трём точкам, всё аналогично, как и выше
var P = {x:0,y:radius};
var P0 = {x:Math.sRandom(8, 15), y:radius+ Math.sRandom(23, 29)};
var P1 = {x:-P0.x, y:P0.y};
var P3 = {x:0,y:P0.y + Math.sRandom(0, 7)};
add(function(ctx){
drawСircle(ctx, P3, Math.sRandom(2, 6), (Math.sRandom(0, 100) > 50 ? fillColor : strokeColor), strokeColor, Math.sRandom(1, 3));
var color = (Math.sRandom(0, 100) > 50 ? fillColor : strokeColor);
ctx.beginPath();
ctx.strokeStyle = strokeColor;
ctx.fillStyle = color;
ctx.lineWidth = Math.sRandom(2, 5);
ctx.moveTo(center.x + P.x,center.y + P.y);
ctx.lineTo(center.x + P0.x,center.y + P0.y);
ctx.lineTo(center.x + P1.x,center.y + P1.y);
ctx.closePath();
ctx.stroke();
ctx.fill();
}, {x:1,y:1});
}
}
Как видите ничего сложного, просто геометрические фигуры.
Код был бы скучен, если бы не было котов, да будет кот с бантиком!
![](http://habrastorage.org/storage3/e5f/31f/f04/e5f31ff0460e498e0fdbf59dca3a8dc0.png)
Заключение
Ну вот и закончил я свое повествование о котах.
Привожу код на гитхабе: github.com/MagistrAVSH/random-cat
А вот результат всех трудов, можете пощелкать: magistravsh.github.io/random-cat
А теперь брысь от монитора, и погладь кота! :)