Программирование для начинающих. Моё знакомство с Processing
Доброго времени суток, уважаемые.
Цифровые электронные самоделки часто взаимодействуют с компьютером. Передают данные, либо управляются с него. В свете этого всегда был интерес к программированию.
Мой прошлый опыт в этой области связан с интерпретатором бейсика ZX Spectrum, Qbasic«ом в рамках хобби юности и Си-86 в рамках студенчества. Были попытки освоить ECMAScript в рамках интереса к языку VRML. Встал вопрос, что выбрать сейчас?
Прошу под кат.
Чтение статей о бурно развивающихся языках вызывало грустные мысли. Все эти Го, Дельфи, Перлы, PHP, Java. Про питонов, так я вообще до последнего времени был уверен, что это вид змей. В общем, обилие и разнообразие. В котором мне, как начинающему, трудно понять, кто есть кто, и для каких задач. Чем, например, отличается Java от JavaScript?
Мои бесплодные размышления продолжались бы и дальше. Но прочитал пост ресурса Изокод о их цикле статей «Полуостров Бинария». О языке Processing. Заинтересовался. В рамках интереса к Ардуино несколько раз встречал упоминания о нём в туториалах. Были и сомнения. Ведь для него требуется Ява машина. С этим творением корпорации Oracle сталкивался при установке одной из любимых игр сына Minecraft. И с ним были проблемы. Домашний комп старенький, на атлоне 1,3. Оперативной памяти пол гига (сейчас уже полтора). И крайнюю версию этой игры запустить невозможно. Ибо требуется крайняя версия Явы, которая на этом железе уже не работает.
Сомнения плана: вот не будет работать Ява, по разным причинам. От железа до санкций. И что потом делать? Или, почему мой браузер Файерфокс верещит об обнаружении опасности, и прозрачно намекает «Братан, в общем, я тебя предупредил…»
Стал читать статьи Изокода. Мне они очень понравились. Простота и магия изложения. Ассоциация юного программиста с островом. Эпилоги. Стал читать сыну, и он тоже загорелся. Считаю, стиль изложения хорошо компенсирует трудность восприятия новых для начинающего понятий интригой его сравнения с растущим островом. Подстёгивает его интерес, играет на самолюбии. А когда прочитанное воплощается на экране в код и результат, вообще поглощает его. Но, также, один из эпилогов заставил меня сильно задуматься о целесообразности изучения сыном: «Раньше, не значит вовремя». Потому я пока приостановил это направление наших совместных изысканий. Но не остановился сам.
Итак. Установил среду. Ява уже стояла. Запустил. Всё знакомо по Arduino IDE. Набрал один пример из статьи. Отрисовка кучи жёлтеньких кружочков с рандомными координатами. Хм… Синтаксис до боли знаком… Да это же Си! Запустил. Поразила скорость выполнения программы. И это на моём дохленьком Samsung NC-110, с гигабайтом ОЗУ и жалким подобием графической подсистемы. Любопытство русского мужика из анекдота про японскую бензопилу потянуло на эксперименты. Увеличил в 10 раз количество точек в цикле. Вжик, сказал мой скромный трудяга. Хм… Увеличил в 100 раз… Вввжик, повторил он. Увеличил в 1000 раз. Тут уже мой нетбук ощутимо просел по скорости исполнения кода. Но тем не менее я остался доволен. Стал грузить примеры. Те, что с графикой, конечно притормаживали. Попробовал скомпилировать один из примеров в ЕХЕшник. И тут выяснилось, что скомпилированный в EXE скетч не запускается. Выскакивало два окна.
В одном «C:\ProgramData\Oracle\Java\javapatch\javaw.exe Неизвестная ошибка».
Второе с заголовком «Processing error», с текстом «Error calling ShellExecuteEx ()».
Дальше были классические действия начинающего программиста. Фары протёр, колёса попинал. Обшарил сайт processing.org, попытался найти чат программистов. Но там оказались не программисты, а юные озаботики. Отчаявшись найти ответ, стал размышлять над извечным русским вопросом. И вот так бывает! Правильно сформулированный вопрос содержит половину ответа. И свою проблему я решил. Я ж искал не там! В Program Files. А требует не Program Files, а ProgramData! Папка оказалась с атрибутом хидден. Залез. Там нашлись и требуемые подпапки, но! В папке javapath лежат не сами файлы, а ярлыки на них. Не смог отредактировать свойства, в частности указать рабочую папку. Полез в папку Явы. C:\Program Files\Java\jre1.8.0_25\bin\ Там нашёл требуемую программу, и создал с неё новый ярлык в C:\ProgramData\Oracle\Java\javapatch\ Это не помогло. Тогда просто скопировал её, и другие, на которые были ярлыки, в эту папку. И всё заработало!
Читая с сыном первые главы, разобрались с отрисовкой кружочка, его закраской желаемым цветом, задействование мыши, координат курсора и кнопок. Сыну очень понравилось рисовать мышкой. Он и до того баловался в программах раскрасках, фотошопе. А тут. Рисование в написанной нами программе! Требовал от мамы посмотреть. И у меня родилась идея. А почему бы не попробовать написать графический редактор а-ля паинтбраш?
Первая версия уместилась в 30 строк кода. В ней уже был выбор цвета и диаметра кисти. Сыну понравилось ещё больше. Конечно, именно 30 строк были не случайностью, а погоней за тенденциями :) Потому код представлял из себя кашу.
На данный момент их уже почти 300. Но не потому, что программа стала супер навороченной. А потому, что стараюсь уйти от быдло-стиля написания. После, в этой мешанине даже самому невозможно разобраться. Что сделано? Сделаны панели выбора цвета и размера кисти. И к ним кнопочки вызова. Количество образцов цвета теперь 8. По 4 на левую и правую кнопку мышки. К каждому образцу своя кисточка. Пока лишь диаметр пятна. В планах масштабирование пятна по X и Y, расстояние между мазками. Левой кнопкой выбирается цвет для изменения, правой — активные для лев. прав. кнопок при рисовании. Не закончено сохранение изображения в формате BMP. Честные 16+ миллионов оттенка цвета. Не хватает числовой информации. Например, о размере кисти, о координатах курсора в поле рисования. Это тоже в планах. Ещё в планах пипетка для определения цвета, штамп для копирования, размытие — его сильно не хватает при рисовании. Различные формы пятна от кисточки, различные алгоритмы закрашивания. Основное — равномерный тон, без многократного наложения мазка в одной точке, даже при прозрачности цвета. Не хватает векторов для рисования прямых. А ещё безумно не хватает Undo. Сделана более чёткая логика работы. Теперь цвет не сбивается, если вылетаешь из поля рисования на панель выбора цвета. Как и не сбивается размер кисточки. Но и есть ошибки. Пока не исправленные. Но и не проявляющиеся явно. Функции в этом языке, как и в Си. Функции это подпрограммы. Очень удобно. Написал функцию, получающую параметры при вызове. Она выполнила предписанное, вернула результат. Причём предписанное предписано не жёстко, а может выбираться в зависимости от значений параметров при вызове. Или не вернула ничего, но изменила значения глобальных переменных. Или перерисовала что-то на экране. Гуру говорят, что глобальные переменные зло. Но мне пока без них никуда. Изначально моя программа была монолитной. И самой сложной частью была проверка условий, где курсор, что нажато. Когда это было 30 строк кода — всё было наглядно и понятно. Но с увеличением количества «фич» стало появляться всё больше и больше повторяющихся кусков кода. И я решил использовать функции. И тут всё сломалось. Стройная монолитная программа взорвалась на пару десятков отдельных кусков — функций. Очень хотелось поддерживать работоспособность программы постоянно. Пугала возможность допустить ошибку, и уже не разобраться, что, откуда, куда, зачем? Но постепенно всё нормализовалось. И даже более того. Оптимизировалось! Теперь одна и та же функция как изменяет цвет, так и параметры кисти. И, наверняка, она же будет применена для других панелей, если появятся. Другая универсальная функция перерисовывает положения ползунков на скроллбарах. Третья отображает 8 цветов, выбранный для изменения цвет, активные цвета для кнопок мыши. В процессе тестирования стали выявляться ограничения. Разрешение создаваемых изображений 72 пикселя на дюйм. И при малейшем увеличении это сразу видно. Но ведь редактор позиционировался а-ля ПаинтБраш, а не а-ля фотошоп. Стоит отметить, что я прочитал всего 3 главы книги «Бинария». И, считаю, что обладая столь ограниченными знаниями, результат всё же неплохой. Листинг программы будет прикреплён в конце статьи. Алгоритм ясен из комментов. Я пишу обычно по ночам. Когда мой ненаглядный главный бета — тестер спит. Не отвлекает внимание на свои интересы. А их немало. Вижу смысл сказать вот что:
1) Всё, буквально всё, надо комментировать! Не только, что какая функция делает, но буквально каждую строчку! Иначе следующей ночью смотришь на собственный код, как баран на новые ворота. Понимаешь, что хочешь, но не понимаешь, в каком месте, что нужно изменить, чтобы реализовать. Код работает. Но ты не понимаешь, как! Надо бекапить. Отладил что-то в программе. Программа работает. Надо сделать копию — бэкап. Чтобы, если всё пойдёт совсем плохо, иметь возможность вернуться. Это особенно важно при внесении больших изменений. Изменении логики работы, добавлении больших функций.
2) С желаниями надо быть последовательным. Идеи приходят табунами. Начинаешь их обдумывать — теряешь мысль, что и для чего делаешь сейчас.
3) Нужно планировать. Программа должна начинаться не с ядра, которое работает. А с задач, которые она будет решать. После эскиз интерфейса, после блок — схемы алгоритмов. Как основной программы — в общих чертах, так и подпрограмм — в деталях. Так проще разобраться, что, куда, откуда, зачем. Иначе — неизбежное переписывание до 60% кода! Когда уже реализованное кардинально изменяется под новую структуру программы, под придуманные универсальные функции.
4) Четвёртое, но на самом деле главное. Надо писать! Надо пробовать, ошибаться, разбираться, исправлять. Никогда не получится прочитать сотню книг по теме, а потом сесть, и написать программу «от и до». Но это, конечно, моё личное мнение. Мнение дилетанта. И это четвёртое не противоречит третьему. Даже имея чёткий план будущей программы, написать сразу и без ошибок не получится.
Что сказать ещё? Очень смешанные чувства. С одной стороны растущая уверенность в собственных силах, с другой осознание, что программа стремительно растёт. С одной — желание и дальше совершенствовать своё «дитя». С другой — осознание, что оно вряд ли когда либо превзойдёт возможностями монстра Фотошопа. А значит, важен не результат, а лишь процесс. Закрепление в голове изученного. Дабы позже быть способным объяснить сыну. Помочь в изучении языка. Осознание, что надо читать следующие главы «Бинарии». Что на данный момент написанное — мой предел. Но далеко не предел этого языка.
В заключении, мой рисунок, выполненный в программе. На скорую руку. Теперь он постоянная заставка на планшете сына. Здесь он приведен скрин-дампом, на планшете, конечно, панель инструментов обрезана фотошопом.
Пара рисунков главного бета — тестера, сделанных в процессе работы над программой.
А ещё скрин-дамп работы его программы. Рисующей девять пасхальных яичек. Каждый раз новыми оттенками цвета. Очень простой программы. Меньше десятка строк. Но назначение каждой команды и переменной в ней он сможет объяснить сам!
Листинг программы:
boolean ris = false; // лог. рисуем или нет
boolean sel_col = false; // лог. выбрана панель цветов или нет
boolean sel_kist = false; // лог. выбрана панель кистей или нет
int num_color = 0; // Изменяемый цвет. 8 цветов. Выбран первый
int color_active_left = 0; int color_active_right = 1; // выбранные для кнопок мыши цвета
int num_panel; // для какой панели вызывается функция изменения значений
// 4 массива по 8 элементов. RGB наборы 8 цветов
int [] colr = new int [8]; int [] colg = new int [8];
int [] colb = new int [8]; int [] proz = new int [8];
// 4 массива по 8 элементов. Размеры кистей, деформации по X и Y, расстояние между следами
int [] r = new int [8]; int [] scale_x = new int [8];
int [] scale_y = new int [8]; int [] distanse = new int [8];
// диаметр кисти, время задержки повторного рисования, выбр набора от лев и прав кнопки мыши
int zader = 1000000000; int nabor;
void ris_main_menu () // главное меню
{
fill ( 20, 20, 20); rect ( 0, 0, 65, 600); // поле панели инструметов
// кнопка выбора панели цветов
fill (20, 20, 20); stroke (200, 200, 200); rect (3, 5, 15, 15); noStroke ();
fill (255, 0, 0); rect ( 5, 7, 3, 11);
fill ( 0, 255, 0); rect ( 8, 7, 3, 11);
fill ( 0, 0, 255); rect (11, 7, 3, 11);
for (int a= 3; a < 14; a++)
{// градиентная полоска прозрачности в кнопке выбора панели цвета
int b = a * 17;
stroke (b, b, b); line (15, 20 - a, 16, 20 - a);
}
// кнопка выбора панели кисточек
fill (20, 20, 20); rect (22, 5, 15, 15); fill (200, 200, 200); ellipse (29, 12, 8, 8);
fill (20, 20, 20); rect (41, 5, 15, 15); fill (200, 200, 200); rect (44, 8, 9, 9);
// кнопка сохранения изображения на диске
fill (50, 50, 50); rect (47, 11, 3, 3); stroke (50, 50, 50); line (48, 9, 51, 9);
}
void disp_sel_col () //индикация выбранных цветов для прав и лев кнопок мышки 2 столбца по 4 цвета
{
cls_sel_col (); // очистка области выбранных цветов
noStroke (); fill (0, 0, 0); // нет обводки, чёрная заливка
for (int i = 65; i <= 119; i = i + 18) // рисуем в цикле 8 прямоугольников под цвета по 2 за итерацию
{
rect (5, i, 21, 16); rect (35, i, 21, 16);
}
for (int i = 0; i < 8; i+=2) // заливаем в цикле 4 нечётных прямоугольника цветами из массивов
{
if (num_color == i) // если цвет выбран для редактирования - обводим белым прямоугольником
{stroke (200, 200, 200); }
else {noStroke ();} // иначе - не обводим цвет
fill (colr [i], colg [i], colb [i], proz [i]); rect ( 5, 65 + i * 9, 21, 16);
// если цвет выбран активным для левой кнопки мыши - ставим рядом красную точечку
if (i == color_active_left) {noStroke (); fill (240, 0, 0); ellipse (30, 73 + i * 9, 5, 5);}
}
for (int i = 1; i < 8; i+=2) // заливаем в цикле 4 чётных прямоугольника цветами из массивов
{
if (num_color == i)
{stroke (200, 200, 200); }
else {noStroke ();}
fill (colr [i], colg [i], colb [i], proz [i]); rect (35, 56 + i * 9, 21, 16);
// если цвет выбран активным для правой кнопки мыши - ставим рядом красную точечку
if (i == color_active_right) {noStroke (); fill (240, 0, 0); ellipse (60, 64 + i * 9, 5, 5);}
}
// если выбрана панель цветов - обновляем положение ползунков вместе с выбранным цветом
if (sel_col == true)
{ris_polz (colr [num_color], colg [num_color], colb [num_color], proz [num_color]);}
// если выбрана палель кистей - обновляем положение ползунов вместе с выбранным цветом
if (sel_kist == true)
{ris_polz (r [num_color], scale_x [num_color], scale_y [num_color], distanse [num_color]);}
}
void ris_col_bar () // панель выбора цветов
{
cls_pan_instr ();
ris_polz (colr [num_color], colg [num_color], colb [num_color], proz [num_color]);
for (int i=255; i>=0; i--) // рисуем в цикле 4 скроллбара для цветов и прозрачности
{
stroke (i, 0, 0); line (5, 400-i, 10, 400-i);
stroke (0, i, 0); line (20, 400-i, 25, 400-i);
stroke (0, 0, i); line (35, 400-i, 40, 400-i);
stroke (i, i, i); line (50, 400-i, 55, 400-i);
}
}
void ris_sel_kist () // панель выбора кистей
{
cls_pan_instr ();
ris_polz (r [num_color], scale_x [num_color], scale_y [num_color], distanse [num_color]);
for (int i=255; i>=0; i--) // рисуем в цикле 4 скроллбара для параметров кисти
{
stroke (200, 200, 200);
line (5, 400-i, 10, 400-i);
line (20, 400-i, 25, 400-i);
line (35, 400-i, 40, 400-i);
line (50, 400-i, 55, 400-i);
}
disp_sel_col ();
}
void cls_pan_instr () // очистка панели инструментов
{noStroke (); fill (20, 20, 20); rect (0, 145, 65, 450);}
void cls_polz () // очистка областей ползунов
{
noStroke (); fill (20, 20, 20);
for (int i = 11; i < 57; i = i + 15)
{rect (i, 140, 9, 265);}
}
void cls_sel_col () // очистка области отображения 8 цветов
{
noStroke (); fill (20, 20, 20); rect (2, 63, 63, 75);
}
void ris_polz (int r, int g, int b, int p) // положение ползунов
{
cls_polz ();
noStroke (); fill (200, 200, 200);
triangle (12, 400 - r, 16, 396 - r, 16, 404 - r);
triangle (27, 400 - g, 31, 396 - g, 31, 404 - g);
triangle (42, 400 - b, 46, 396 - b, 46, 404 - b);
triangle (57, 400 - p, 61, 396 - p, 61, 404 - p);
}
void sel_num_col (int x, int y)
// выбор одного из восьми цветов. Получает X и Y
// левой - выбор для изменения цвета, правой - установка активным.
// Левый столбец - левая кнопка, правый столбец - правая кнопка
{
for (int i = 0; i < 7; i++)
{
if (x >= 5 && x <= 26 && y >= 65 + i * 9 && y <= 81 + i * 8)
{
if (mouseButton == LEFT) {num_color = i;}
else {
if (mouseButton == RIGHT) {color_active_left = i;}
}}
if (x >= 35 && x <= 56 && y >= 65 + i * 9 && y <= 81 + i * 8)
{
if (mouseButton == LEFT) {num_color = i + 1;}
else {
if (mouseButton == RIGHT) {color_active_right = i + 1;}
}}
}
disp_sel_col ();
}
void select_value (int x, int y, int num_panel) // изменение выбранного цвета на скроллбарах
{
// создаём массив на 4 элемента, считываем текущие значения для редактируемого цвета
// если выбрана панель цвета - цвета из массивов, если панель кистей - размер кисти,
// деформации по X и Y, расстояние между мазками
if (sel_col == true | sel_kist == true)
{
int [] a = new int [4];
if (num_panel == 0)
{
a [0] = colr [num_color]; a [1] = colg [num_color]; a [2] = colb [num_color]; a [3] = proz [num_color];
}
else
{
if (num_panel == 1)
{
a [0] = r [num_color]; a [1] = scale_x [num_color]; a [2] = scale_y [num_color]; a [3] = distanse [num_color];
}
}
// проверяем в цикле положение всех четырёх ползунков, запоминаем в массиве новые вычисленные значения
for (int i = 0; i < 4; i++)
{
if (x >= 5 + i * 15 && x <= 15 + i * 15 && y >= 145 && y <= 400)
{a [i] = 400 - y;}
}
// переписываем значения из временного массива в глобальные массивы
if (num_panel == 0)
{
colr [num_color] = a [0]; colg [num_color] = a [1]; colb [num_color] = a [2]; proz [num_color] = a [3];
}
else
{
if (num_panel == 1)
{
r [num_color] = a[0]; scale_x [num_color] = a[1]; scale_y [num_color] = a[2]; distanse [num_color] = a[3];
}
}
ris_polz (a [0], a [1], a [2], a [3]); // перерисовываем ползуны
disp_sel_col (); // отображаем выбранные цвета
}
}
void setup()
{// инициализируем нулём RGB всех цветов, непрозрачные
for (int i = 0; i < 8; i++)
{
colr [i] = (i + 5) * 10; colg [i] = (i + 5) * 12; colb [i] = (i + 5) * 16; proz [i] = 255;
}
size(1000, 550); // размер окна
background (0,0,0); // цвет фона
frameRate (20);
ris_main_menu (); // рисуем главное меню (три кнопки)
disp_sel_col (); // отображаем образцы цветов
}
void draw()
{
if (mousePressed && ris == false) // если какая нить кнопка мышки нажата
{// курсор на кнопке выбора панели цветов
if (mouseX >= 3 && mouseX <= 15 && mouseY >= 5 && mouseY <= 15)
// рисуем панель цветов, лог. панель изменения цвета отображена, панель выбора кисти скрыта
{ris_col_bar (); sel_col = true; sel_kist = false;}
else { // иначе курсор на кнопке выбора кисти
if (mouseX >= 22 && mouseX <= 37 && mouseY >= 5 && mouseY <= 15)
// рисуем панель кистей, лог. панель кистей отображена
// выбор цветов заблокирован - лог. панель выбора цвета скрыта
{ris_sel_kist (); sel_kist = true; sel_col = false;}
else { // иначе курсор в области выбранных цветов
if (mouseX >= 5 && mouseX <= 56 && mouseY >= 65 && mouseY <= 135)
{sel_num_col (mouseX, mouseY);}
// иначе курсор в области скроллбаров. В зависимости какая панель активна, вызывается либо
// функция изменения выбранного цвета, либо функция изменения кисти
else {
if (sel_col == true && mouseX >= 5 && mouseX <= 55 && mouseY >= 145 && mouseY <= 400)
{num_panel = 0;}
if (sel_kist == true && mouseX >= 5 && mouseX <= 55 && mouseY >= 145 && mouseY <= 400)
{num_panel = 1;}
select_value (mouseX, mouseY, num_panel);
// else { // курсор на кнопке сохранения изображения
// if (mouseX >= 70 && mouseX <= 120 && mouseY >= 25 && mouseY <= 75)
// {save ("KARTINKA.bmp");}
}}} // закрываем все "else"
} // закрываем "если нажата какая нить кнопка мыши"
// если нажата какая нить кнопка мыши И положение курсора по X за областью панели
// инструментов - ширина панели + половина диаметра кисточки
// здесь косяк!!! Ограничение выбирается для одного набора, и не меняется при смене цвета / кисти.
// В итоге кисть может затереть всю панель инструментов
if (mousePressed && mouseX >= 65 + r [nabor] / 2)
{
ris = true; // лог. рисуем. Выход на выбор цвета или размера кисти не меняется
// пока не отпустим кнопку мышки
if (mouseButton == LEFT) {nabor = color_active_left;} // если нажата левая - выбранный для левой
else {
if (mouseButton == RIGHT) {nabor = color_active_right;} // если нажата правая - выбранный для правой
}
// нет обводки следа кисти, выбор цвета из массива в зависимости от нажатой кнопки мыши
noStroke (); fill (colr [nabor], colg [nabor], colb [nabor], proz [nabor]);
ellipse (mouseX, mouseY, r [nabor], r [nabor]); // рисуем окружность
// здесь должна быть задержка повторного рисования. Пятно от кисти слишком быстро
// закрашивается до непрозрачного
}
else { // иначе если кнопка отпущена - все лог. переменные в "ложно"
if(!mousePressed){ ris = false;}}
}
Упомяну найденные мною книги по этому языку на русском:
«Учимся программировать вместе с Processing» — авторы: Casey Reas/Кейси Риз, Ben Fry/Бен Фрай — 6,89 Мб
«Processing 2: креативное программирование» — автор: Ян Вантомм — 15,8 Мб
Готов выложить их, но не знаю, куда лучше