Текст
                    1


Валерий Рубанцев Программирование для всех Компьютерная графика на Питоне 2
Бесплатное издание Все права защищены. Никакая часть этой книги не может быть воспроизведена в любой форме без письменного разрешения правообладателей. Автор книги не несёт ответственности за возможный вред от использования информации, составляющей содержание книги и приложений. Copyright 2019 Валерий Рубанцев Лилия Рубанцева 3
От автора Современные программы редко обходятся без качественной и быстрой графики и при этом стремятся к мультимедийности и мультиплатформенности, поэтому требуют много времени и сил на разработку. Любители программирования хотели бы иметь инструменты, которые существенно облегчают программирование мультимедийных приложений. В этой книге мы рассмотрим графическую библиотеку Processing.py, которая полностью удовлетворяет их желания. Она написана на языке Ява, но программы, использующие её, нужно писать на Питоне. Этот язык достаточно прост в изучении и применении, а программы на Питоне работают на всех устройствах, на которых установлены операционные системы Windows, MacOS и Linux, то есть практически везде и всюду. В книге подробно, обстоятельно и с многочисленными примерами рассматриваются графические возможности библиотеки Processing.py. В ней вы найдёте множество разнообразных программ с исчерпывающими комментариями. Большинство проектов, что естественно для такой книги, учебные, но несколько программ имеют самостоятельную ценность и могут использоваться в художественном творческом процессе. Основное назначение программы Processing – помочь непрофессиональным программистам – художникам, учёным, дизайнерам и учащимся – использовать современную графику в работе и учёбе. Если вы хотите познакомиться с первоисточником, то прочитайте эту книгу. → 4
Коротко о достоинствах библиотеки Processing.py: • очень простая. Например для вычерчивания отрезка прямой нужно вызвать функцию line(x1,y1,x2,y2). Понятно, что сделать это проще нельзя. Все остальные функции/команды столь же просты и понятны. • небольшая по размеру, поэтому быстро загружается. • по системе команд почти полностью совместима с программами, написанные в Процессинге. Таких программ великое множество, поэтому есть где и чему учиться и черпать идеи и вдохновение. • быстрая. Можно рисовать динамические картинки даже отдельными пикселями в работающей программе. • имеет функции для рисования всех геометрических примитивов: пикселей, точек, отрезков, треугольников, прямоугольников, эллипсов/кругов, дуг, многоугольников и растровых картинок. • к координатной плоскости очень просто применить трансформации – перенос, масштабирование, поворот, перекос. • для растровых изображений имеются многочисленные фильтры. • управлять работающей программой можно мышкой и клавиатурой. Обработка событий очень простая. Таким образом, вы получаете интерактивные программы без лишних усилий. • постоянно развивается и становится всё более мощной. У неё есть хорошие перспективы, а это очень важно при выборе надёжных инструментов для работы. • для разработки программ имеется бесплатная и удобная среда разработки. • хорошо документирована. Для каждой функции имеются примеры использования в программах. На официальном сайте программы Процессинг можно найти дополнительные примеры и руководства. 5
• дополнительные библиотеки ещё больше расширяют возможности основной библиотеки: controlP5 позволяет легко добавлять к программе элементы управления, minim облегчает работу со звуковыми и музыкальными файлами. • помимо собственно графических программ можно писать игры, симуляторы, компьютерные модели, готовить иллюстрации к научным работам. Цель книги: изучить основные графические возможности библиотеки Processing.py на практических и занимательных примерах. C этой книгой вы быстро изучите основы компьютерной графики, и сможете самостоятельно рисовать красивые узоры, писать игры и разрабатывать компьютерные модели по биологии, физике, химии. В ней вы найдёте исчерпывающий теоретический материал для самостоятельного и разностороннего творчества: • цвет в компьютерной графике, цветовые модели RGB и HSB • растровая и векторная графика • двумерные и трёхмерные примитивы: пиксели, точки, прямые, треугольники, прямоугольники, многоугольники, эллипсы, дуги, кривые • буферная графика • трансформации, матрица трансформаций • режимы наложения цветов • анимация • шейдеры • управление мышкой и клавишами Все основные графические функции проиллюстрированы многочисленными проектами. Книга адресуется: • • Школьникам с 10-летнего возраста. Родителям для совместных занятий по программированию с детьми. 6
• Учителям информатики. • Всем начинающим программистам любого возраста, не имеющим предварительного опыта в программировании. Валерий Рубанцев 7
Условные обозначения, принятые в книге: Дополнение или замечание Требование или указание Исходный код: # ОКРАШИВАЕМ КАНВУ def mouseMoved(): # цвет пропорционален координате Y курсора: h = map(mouseY, 0, height, 0, 360) background(h, 100, 100) fill(0) text(u"Для изменения цвета фона двигайте мышку вверх-вниз", 20, 40) text("Hue = " + str(h), 20, 80) Задание для самостоятельного решения Заголовок проекта: Проект Исходные коды всех проектов находятся в папке _Projects 8
Оглавление Компьютерная графика на Пит оне................................ 2 От автора ............................................................... 4 Оглавление ............................................................. 9 Среда разработки...................................................... 15 Устанавливаем Процессинг.................................................................................. 16 Среда разработки Процессинг ............................................................................. 18 Русификация программы .............................................................................................. 20 Выбираем режим программирования ....................................................................... 22 Учебные программы................................................................................................ 25 Окно и мышка ......................................................... 28 Проект CreateWindow ............................................................................................. 28 Проект FullScreen .................................................................................................... 36 Серая мышка .............................................................................................................. 37 Проект Mouse ............................................................................................................ 37 Проект GrayColor ...................................................................................................... 42 Проект GrayColor2 ................................................................................................... 44 Проект Функция draw .............................................................................................. 45 Проект Функция draw 2 .......................................................................................... 47 Проект Консольная арифметика ......................................................................... 50 Проект Отладка программ .................................................................................... 54 Структура программ ................................................................................................. 56 Да будет цвет! ......................................................... 58 Цветовая модель RGB ............................................................................................. 60 Проект Цветные окна.............................................................................................. 60 Функция fill ................................................................................................................. 70 Проект ColorMode .................................................................................................... 70 Проект HSBColor ...................................................................................................... 73 9
Инструмент Color Selector..................................................................................... 75 Проект Цветовые диапазоны ............................................................................... 77 Проект Цветная таблица ........................................................................................ 78 Цветовые функции .................................................................................................. 87 Проект Светоформа................................................................................................. 89 Проект Картинный фон .......................................................................................... 93 Элемент управления ползунок (Slider) ............................. 96 Проект RGB-смеситель .......................................................................................... 96 Проект HSBSlider .................................................................................................. 100 Проект HSBSlider3 ................................................................................................ 102 Пиксели и точки.................................................... 104 Проект Пуантилизм, или Ставим точки ......................................................... 106 Проект Пуантилизм 2 .......................................................................................... 108 Проект Пуантилизм 3 ...........................................................................................110 Проект Случайные точки ......................................................................................112 Проект Большие точки ..........................................................................................115 Проект Мигающие точки .....................................................................................119 Проект Пульсирующие точки ........................................................................... 123 Проект Пипетка ..................................................................................................... 125 Проект Пипетка 2 .................................................................................................. 128 Занимательные игры с пикселями .................................................................. 132 Проект Синусоидные полоски .......................................................................... 132 Проект Синусоидные полоски с клавишами ................................................ 138 Проект Анимированные синусоидные полоски .......................................... 142 Проект Двойная волна ......................................................................................... 146 Проект Двойная волна HSB .................................................................................151 Проект Лунки .......................................................................................................... 154 Проект Лунки HSB ................................................................................................. 158 Проект Радиальные волны ..................................................................................161 Проект Радиальные волны с клавишами ....................................................... 164 Проект Анимированные радиальные волны ................................................ 167 10
Проект Проект Проект Проект Проект Проект Радиальные волны HSB ........................................................................ 170 Анимированные радиальные волны HSB ....................................... 172 Ромбы.......................................................................................................... 176 Синусоиды Винни-Пуха ....................................................................... 178 Туманность................................................................................................ 184 Заливаем! .................................................................................................. 193 Прямые линии ...................................................... 201 Проект Случайные линии .................................................................................. 202 Проект Функция strokeJoin ................................................................................ 205 Проект Функция strokeJoin 2 ............................................................................ 208 Проект Цветные линии ........................................................................................ 210 Проект Градиентная заливка .............................................................................. 212 Проект Случайный градиент .............................................................................. 216 Проект Интерактивный градиент ...................................................................... 219 Проект Интерактивный градиент 2 .................................................................. 221 Проект Горизонтальный градиент .................................................................. 223 Проект Горизонтальный градиент 2 ............................................................... 225 Проект Синусоидный градиент ........................................................................ 227 Проект Синусоидный градиент 2 .................................................................... 233 Проект Прямоугольный градиент ................................................................... 235 Треугольники ........................................................ 237 Проект Случайные треугольники .................................................................... 238 Проект Треугольный треугольник ................................................................... 241 Проект Треугольный треугольник 2 .............................................................. 243 Прямоугольники и квадраты ...................................... 245 Проект Проект Проект Проект Проект Прямоугольные режимы..................................................................... 249 Случайные прямоугольники .............................................................. 250 Случайные квадраты ............................................................................. 252 Считаем квадратики .............................................................................. 256 Пиксели .................................................................................................... 258 11
Проект Цветные прямоугольники ................................................................... 264 Проект Шахматная доска .................................................................................... 266 Проект Стакан......................................................................................................... 275 Эллипсы и круги ................................................... 278 Проект Многоточие .............................................................................................. 280 Проект Мишень...................................................................................................... 285 Проект Случайные эллипсы ............................................................................. 288 Проект Живые картинки ..................................................................................... 290 Проект Цветные круги......................................................................................... 296 Проект Радиальный градиент ........................................................................... 303 Проект Радиальные волны 2 ............................................................................. 305 Проект Одинокий пульсар .................................................................................. 310 Проект Жёлтая подводная лодка ...................................................................... 312 Геометрические примитивы ....................................... 317 Дуги ............................................................................................................................ 317 Проект Гнём дуги .................................................................................................. 318 Проект Анимированные сектора ....................................................................... 321 Четырёхугольники ................................................................................................ 325 Проект Бойкие четырёхугольники.................................................................. 325 Параметрические кривые .......................................... 329 Проект Параметрические кривые ................................................................... 329 Проект Параметрические кривые 2................................................................ 350 Проект Параметрические кривые 3................................................................ 362 Растровые изображения............................................. 366 Картинные функции ............................................................................................. 366 Проект Цветные волны ....................................................................................... 369 Проект Цветные волны 2 ................................................................................... 372 Проект Функция resize ........................................................................................ 376 Проект Функция copy ............................................................................................ 377 12
Проект Функция save ............................................................................................ 379 Проект Лупа............................................................................................................ 380 Проект Летающие мячи ...................................................................................... 386 Функция blend .......................................................................................................... 391 Проект Режимы наложения ............................................................................... 396 Проект Режимы наложения 2 ............................................................................ 399 Проект Цветные чернила .................................................................................... 401 Проект Фильтруем снимки ................................................................................. 407 Проект Фильтруем снимки 2 .............................................................................. 414 Проект Фильтруем снимки 2А ........................................................................... 418 Проект Картинные фильтры .............................................................................. 419 Проект Правила маскарада ............................................................................... 428 Проект Буферная графика ................................................................................... 431 Трансформации ..................................................... 434 Проект Проект Проект Проект Проект Проект Перенос .................................................................................................... 434 Масштабирование ................................................................................... 441 Масштабирование 2 ............................................................................... 445 Перекос ..................................................................................................... 447 Поворот .................................................................................................... 450 Трансформаторная анимация ............................................................ 452 Многоугольники ..................................................... 455 Проект Проект Проект Проект Проект Проект Проект Многоугольники..................................................................................... 455 Многоугольники 2 ................................................................................. 460 Точки ......................................................................................................... 463 Закругляемся! .......................................................................................... 466 Контур ......................................................................................................... 471 Контуры ..................................................................................................... 475 Контуры 2 ................................................................................................. 477 13
Шейдеры ............................................................. 480 Проект Проект Проект Проект Проект Проект Проект Проект Проект Проект Проект Проект Проект Проект Проект Проект Проект Шейдер Корсуна ..................................................................................... 482 Шейдеры своими руками .................................................................... 488 Шейдеры своими руками 2 ................................................................. 490 Шейдеры своими руками 3 ................................................................. 492 Шейдеры своими руками 4 ................................................................. 493 Шейдеры своими руками 5 ................................................................. 495 Шейдерные квадратики ........................................................................ 497 Шейдерный градиент............................................................................ 500 Шейдерный градиент 2 ........................................................................ 502 Шейдерный градиент 3 ........................................................................ 504 Шейдерный градиент 4 ........................................................................ 505 Шейдерный градиент 5 ........................................................................ 506 Шейдерный градиент 6 ........................................................................ 507 Анимированный шейдерный градиент ........................................... 508 Шейдерная чёрная дыра ........................................................................ 510 Шейдерная туманность .......................................................................... 512 Шейдерный ландшафт .......................................................................... 514 Литература ........................................................ 516 Cерия Cерия Серия Cерия Cерия Cерия Программирование для детей .............................................................. 516 Программирование на Питоне ............................................................. 518 Программирование на языке C# 5.0: Начальный уровень ......... 523 Учись программировать с Котлином.................................................. 525 Учись программировать с Процессингом ........................................ 530 Программирование на ЯваСкрипте ..................................................... 532 14
Среда разработки Современная компьютерная графика очень сложна для начинающих. В Питоне имеется для этого специальный модуль tkinter, но с ним нужно написать почти десяток строк кода, чтобы начертить на экране отрезок: А вот и результат нашей плодотворной работы: Начертить точку нам не удастся, поэтому придётся рисовать квадрат со стороной в 1 пиксель. 15
Для научной графики предназначен модуль matplоtlib, но чертить простые линии с его помощью тоже не очень удобно. Для изучения компьютерной графики наиболее подходит среда разработки Процессинг (Processing). Её можно использовать и для вычислений, но это, прежде всего, великолепный инструмент для создания графических программ. Нам нужно написать единственную строку, чтобы поставить точку в заданном месте окна приложения или провести отрезок: point(30, 20) line(0, 20, 200, 200) Но сначала вам нужно скачать и установить Processing на свой компьютер. Устанавливаем Процессинг На главной странице официального сайта программы Процессинг processing.org щёлкните по строчке Download Processing: 16
На странице загрузки щёлкните по нужному файлу: И здесь, и дальше я подразумеваю, что на вашем компьютере установлена 64-битная операционная система Windows. Однако Processing может работать и на 32-разрядной Windows, и на Mac OS X, и на Linux. Вы, естественно, должны скачать и установить ту версию программы, которая подходит для вашего компьютера. Когда архивный файл processing-3.5.3-windows64.zip будет полностью скачан, распакуйте его. Через пару минут в папке processing-3.5.3 вы найдёте выполняемый файл программы Процессинг: Вы можете открыть папку и запустить файл processing.exe, щёлкнув мышкой по его значку, но лучше для этого создать ярлык программы на Рабочем столе. Надпись на нём измените так, как показано на рисунке: 17
Среда разработки Процессинг После запуска программы Процессинг сначала на экране появится её фирменная заставка: Среда разработки, или Интегрированная среда разработки (ИСР, по-английски Integrated Development Environment — IDE) включает Редактор кода, отладчик и компилятор, почему так и называется. Но программа Процессинг – это не только полноценная среда разработки, но и язык программирования – диалект языка Ява. 18
А через несколько секунд Процессинг предстанет перед вами в полной боевой готовности: Интерфейс программы очень лаконичный, поэтому вы можете сразу приступить к работе в ИСР, без долгого и нудного изучения многочисленных элементов управления, которыми изобилуют современные программы. 19
Русификация программы Начинающим программистам легче ориентироваться в интерфейсе, если надписи выполнены на русском языке. Чтобы сменить язык интерфейса, выполните команду Главного меню File → Preferences. → Откройте список Language, выберите русский и нажмите кнопку ОК: 20
Закройте ИСР, нажав красную кнопку в правом верхнем углу Главного окна. → Или выполните команду Главного меню File → Quit (клавиатурное сокращение Ctrl + Q). → Cнова запустите Процессинг. Перезагрузку ИСР необходимо сделать, чтобы изменения вступили в силу. Теперь почти все надписи будут на русском языке: 21
Точно так же вы можете выбрать другой язык интерфейса или вернуться к исходному, английскому. Выбираем режим программирования По умолчанию действует режим Java, но программы можно писать также на Питоне и ЯваСкрипте. Чтобы добавить новый режим, раскройте список в правом верхнем углу Главного окна и нажмите строчку Добавить режим… (Add Mode…). → У меня это режим уже установлен, поэтому у вас список будет короче. В диалоговом окне Contribution Manager, на вкладке Modes выделите строчку Python Mode for Processing 3 и нажмите кнопку Install (см. рис. на след. стр.). Через некоторое время новый режим загрузится и установится, а в списке появится строчка Python. Щёлкните по ней: 22
В окне Редактора кода откроется закладка с названием файла по умолчанию (слово sketch_, текущая дата и буква латинского алфавита): 23
Слово sketch можно перевести как набросок, что и отражено в названии одного из пунктов Главного меню. Программы на Процессинге называются скетчами, эскизами, набросками, потому что часто используются для быстрой разработки приложений, которые затем легко перевести на другие языки программирования. Но на Процессинге вполне можно писать и полноценные программы. 24
Учебные программы Прежде чем писать собственные программы, совсем неплохо познакомиться с возможностями Процессинга на примерах. Выполните команду меню Фвйл → Примеры… (File → Examples… ) (клавиатурное сокращение Ctrl+Shift+O): Появится новое окно со списком примеров. Вы можете открыть любую папку и добраться до любого файла. Например, я выбрал файл Image/ex05_Pointillism: 25
Все примеры разложены по отдельным папкам, согласно их сложности и назначению. Чтобы раскрыть папку, щёлкните по её названию или по кнопке с крестиком слева от названия. Плюс сменится минусом, который означает, что папка открыта, и вы можете видеть её содержимое. Кликнув по открытой папке, вы свернёте (закроете) её. Файлы программ обозначены слева чёрным кружком. Чтобы загрузить пример в Редактор кода, достаточно дважды щёлкнуть по нему мышкой. Откроется новый экземпляр программы Процессинг, и в нём появится исходный код проекта: 26
Название файла на Питоне будет напечатано в заголовке окна и на вкладке с открытым документом. Запустите программу, нажав кнопку Запустить (Run) . Окно программа откроется, и в нём будут нарисованы круги разного цвета, диаметр которых определяется удалением курсора от левого края окна: чем оно больше, тем крупнее получаются «точки»: Поскольку каждый проект создаёт новый экземпляр ИСР, то по окончании работы окна программы Процессинг нужно закрыть. В меню Файл → Недавние записываются названия последних открытых проектов. Выберите из списка нужный и щёлкните по его названию, чтобы загрузить его в Редактор кода: 27
Текущий проект там появится только в новой ИСР. В контекстном меню кнопки с перевёрнутым треугольником вы найдёте названия всех открытых в Редакторе кода файлов: Окно и мышка Графические приложения выполняются в окнах. Всю свободную область окна (поверхность рисования, канву) или любую её часть мы можем использовать для вывода графики и текста. Для этого нужно создать окно. Проект CreateWindow Исходный код программы находится в папках Function_size , CreateWindow и Function_background. Запустите Processing. Если необходимо, перейдите в режим Python. 28
В Редакторе кода откроется новый набросок: Создайте на диске папку для всех проектов на Питоне. Все новые проекты сразу сохраняйте в этой папке! Для этого выполните команду Главного меню Файл → Сохранить как… (File → Save As…) (клавиатурное сокращение Ctrl+Shift+S). → 29
Откроется диалоговое окно Сохранить папку как… (Save sketch folder as…). Перейдите в папку проектов, наберите в текстовом поле Имя файла название проекта и нажмите кнопку Сохранить: Я назвал проект CreateWindow. Вы можете назвать его по-другому, но не используйте русские буквы и пробелы. В папке с проектами будет создана папка CreateWindow. Найдите её на диске и откройте. В ней находится файл CreateWindow.pyde. Исходный набросок переименован. В программе Процессинг имя главного файла программы (а у нас это файл с расширением pydе) должно совпадать с именем папки (проекта). Совсем неплохо, когда имя файла говорит о его назначении. Второй файл в папке проекта – sketch.proprties – нужен при разработке программ в Процессинге: 30
Обратите внимание, что заголовок Главного окна и название файла на закладке в Редакторе кода также изменились: Мы «написали» программу, которую можно запускать! Нажмите кнопку Запустить (Run) (клавиатурное сокращение Ctrl+R). Откроется окно с размерами по умолчанию. → Канва создаётся автоматически при запуске программы, но её размеры 100 х 100 пикселей слишком малы для вывода информации. Для работы программы необходимы две функции – setup и draw. Первая функция выполняется однократно при запуске программы, вторая выполняется многократно – при каждой смене картинки на экране. В статических приложениях она не нужна. Для создания окна нужно вызвать функцию size: size(w: int, h: int, [renderer]) 31
w – ширина клиентской области окна (канвы) в пикселях h – высота канвы в пикселях renderer – визуализатор. Он имеет значение P2D или P3D. В нашем случае его можно не указывать. Записываем в функции setup вызов функции size: def setup(): size(600, 400) Числа 600 и 400 говорят о том, что мы хотим создать канву размером 600 х 400 пикселей. Запустите программу. При этом она автоматически сохраняется на диске. На экране появится окно заданных размеров: 32
Цвет фона окна по умолчанию – серый. Функция background (в переводе - задний план, фон) окрашивает канву в заданный цвет. Она может иметь разное число параметров. Самый простой вариант этой функции имеет 2 параметра: background(gray: float, [аlpha: float]) gray – задаёт оттенок серого цвета. Если этот параметр равен 0, то цвет будет чёрным, если 255 – белым. Промежуточные значения дают серый цвет. аlpha – необязательный параметр – прозрачность цвета. Изменяется в диапазоне 0 (полная прозрачность)..255 (полная непрозрачность). Функция clear очищает канву, делая все пиксели прозрачными: clear() Канва становится чёрной: 33
Передадим функции background какое-нибудь значение между 0 и 255. Например, 100: def setup(): size(600, 400) #clear() background(100) Канва окрасилась в тёмно-серый цвет, и её хорошо видно на экране: Все программы имеют встроенные переменные width и height, в которых хранятся размеры канвы. Чтобы напечатать их значения, нам нужна функция print: 34
print(contents) contents – это число, строка, логическое значение, объект или массив. Добавьте в функцию setup 2 строки: def setup(): size(600, 400) #clear() background(100) print(u"Ширина канвы равна " + str(width)) print(u"Высота канвы равна " + str(height)) Так как Питон 2 не дружит с русскими буквами, то перед строкой нужно писать латинскую букву u, чтобы буквы в кавычках трактовались как символы Юникода. Тогда русский текст будет напечатан верно. Запустите программу. Функция print напечатает информацию не на канве, а в Консольном окне: И ещё 2 системные переменные – displayWidth и displayHeight - хранят ширину и высоту экрана монитора: print(u"Ширина экрана равна " + str(displayWidth)) print(u"Высота экрана равна " + str(displayHeight)) 35
Проект FullScreen Исходный код программы находится в папке FullScreen. Для перехода в полноэкранный режим нужно вызвать функцию fullScreen с аргументом, равным True: fullScreen([val: bool]) Если аргумент равен False, то полноэкранный режим выключается: def setup(): size(600, 400) #clear() background(100) print(u"Ширина канвы равна " + str(width)) print(u"Высота канвы равна " + str(height)) print(u"Ширина экрана равна " + str(displayWidth)) print(u"Высота экрана равна " + str(displayHeight)) fullScreen(True) Как показывают числа в консоли, канва заняла весь экран: Закрыть окно можно только из Панели задач. → Или - того хуже – из Диспетчера задач. 36
Не запускайте программу в полноэкранном режиме, если вы не предусмотрели выход их неё. Серая мышка Чтобы изменять параметры работающей программы, нужны элементы управления, которые часто можно заменить простой мышкой. Проект Mouse Исходный код программы находится в папке Mouse. Текущие координаты мышки относительно начала координат клиентской области окна (канвы) хранятся в двух системных переменных: mouseX mouseY Их значения изменяются при перемещении мышки по экрану. Начните новый проект Mouse. Функция setup только создаёт окно и окрашивает фон: # Mouse - Серая мышка def setup(): size(600, 400) 37
background(100) frameRate(10) А за перемещениями мышки мы будем следить в функции draw: def draw(): print("X = " + str(mouseX)) print("Y = " + str(mouseY)) Запустите программу и водите мышку по экрану: А в консоли читайте текущие координаты мышки относительно левого верхнего угла канвы. → 38
Системные переменные pmouseX и pmouseY хранят координаты курсора мышки относительно начала координат канвы на предыдущей итерации: pmouseX pmouseY Значение системной переменной mouseIsPressed равно True, если кнопка мышки нажата, и False в противном случае: mouseIsPressed Функция mouseMoved вызывается при всех перемещениях мышки без нажатой кнопки: mouseMoved() def mouseMoved(): print "mouseMoved" Функция mousePressed вызывается при каждом нажатии кнопки мышки: mousePressed() def mousePressed(): print "mousePressed" Системная переменная mouseButton хранит значение последней нажатой кнопки: 39
mouseButton LEFT – левая кнопка RIGHT – правая кнопка CENTER – средняя кнопка Функция mouseDragged вызывается при всех перемещениях мышки c нажатой кнопкой: mouseDragged() def mouseDragged(): print "mouseDragged" Функция mouseReleased вызывается, когда пользователь отпускает нажатую кнопку мышки: mouseReleased() def mouseReleased(): print "mouseReleased" Функция mouseClicked вызывается, когда пользователь нажимает, а затем отпускает кнопку мышки (кликает, щёлкает): mouseClicked() 40
def mouseClicked(): print "mouseClicked" Функция mouseWheel вызывается, когда пользователь крутит колёсико мышки: mouseWheel(event) event.count – значение, которое зависит от скорости и направления поворота колёсика: def mouseWheel(): print "mouseWheel" Все «мышиные» функции требуют, чтобы в программе была функция draw. Функция noCursor делает курсор невидимым: noCursor() Функция cursor делает курсор видимым: cursor(type,[x: int],[y: int]) x, y – координаты горячей точки курсора (необязательные аргументы) type – вид курсора: ARROW, CROSS, HAND, MOVE, TEXT, WAIT В качестве курсора можно использовать картинку размером 16 х 16 или 32 х 32 пикселя. В этом случае значение параметра type – это картинка типа PImage. 41
Проект GrayColor Исходный код программы находится в папке GrayColor. Вы уже знакомы с самой простой перегрузкой функции background, которая окрашивает фон в оттенки серого цвета: background(gray: float,[аlpha: float]) Параметр gray изменяет значения от 0 (чёрный цвет) до 255 (белый цвет). Промежуточные значения дают оттенки серого. Необязательный параметр аlpha изменяется в диапазоне 0..255 и определяет прозрачность цвета. В этом проекте мы будем изменять цвет канвы при перемещении мышки по ней. В функции setup создаём окно: def setup(): # создаём окно: size(600, 320) # размер шрифта: textSize(20) Так как цвет изменяется, то нам необходима функция draw, в которой мы вызываем функцию colorCanvas для изменения цвета фона: def draw(): colorCanvas() 42
В функции colorCanvas мы сообщаем пользователю, что он должен делать, и закрашиваем фон цветом clr: # ОКРАШИВАЕМ КАНВУ def colorCanvas(): # цвет пропорционален координате Y курсора: clr = map(mouseY, 0, height, 0, 255) background(clr) fill(255,0,0) text(u"Для изменения цвета фона двигайте мышку вверх-вниз", 20, 40) y-координата мышки изменяется в диапазоне 0..height. При этом значение параметра gray изменяется в диапазоне 0..255. Для приведения значения из одного диапазона в другой удобно пользоваться функцией map: map(value,start1,stop1,start2,stop2) → float value – конвертируемое значение из первого диапазона start1 – нижняя граница первого диапазона stop1 - верхняя граница первого диапазона start2 – нижняя граница второго диапазона stop2 - верхняя граница второго диапазона Все параметры имеют тип float. Функция возвращает соответствующее значение из второго диапазона. Запускаем программу. Когда мышка находится у верхней границы канвы, цвет фона чёрный. При движении мышки вниз цвет становится всё более светлым: 43
Проект GrayColor2 Исходный код программы находится в папке GrayColor2. Начните новый проект GrayColor2. При перемещении мышки вызывается функция mouseMoved, поэтому весь код из функции colorCanvas мы можем перенести в неё: def setup(): # создаём окно: size(600, 320) # размер шрифта: textSize(20) 44
def draw(): pass # ОКРАШИВАЕМ КАНВУ def mouseMoved(): # цвет пропорционален координате Y курсора: clr = map(mouseY, 0, height, 0, 255) background(clr) fill(255,0,0) text(u"Для изменения цвета фона двигайте мышку вверх-вниз", 20, 40) fill(255,255,0) text("gray = " + str(clr), 20, 70) Сюда же мы добавили строчки для вывода текущего значения параметра gray: Проект Функция draw Исходный код программы находится в папках Function_draw и Background_draw. 45
Почти во всех программах мы будем пользоваться встроенными функциями setup и draw. Функция setup вызывается автоматически и однократно после запуска программы. А функция draw вызывается сразу после того, как функция setup закончит свою работу. Но функция draw выполняется многократно – то тех пор, пока приложение не будет закрыто. Это может сделать пользователь программы, нажав кнопку с крестиком в заголовке окна, или программист, нажав кнопку Stop, или функция exit в самой программе: exit() По умолчанию функция draw срабатывает 60 раз в секунду, что определяется системной переменной frameRate, которая заменяет таймер в обычных приложениях: frameRate(fps: float) Здесь: fps – это число обновлений окна за 1 секунду. По умолчанию частота равна 60, то есть окно перерисовывается 60 раз в секунду. Но если у вашего компьютера медленный процессор, частота может оказаться меньше заданной. Частота обновления экрана обозначается буквами FPS (Frame Per Second, кадров в секунду). Текущее значение FPS хранится в системной переменной frameRate. Системная переменная frameRate типа float хранит текущее значение частоты. Первоначально это значение равно 10.0, а затем обновляется с каждой новой прорисовкой окна (кадром - frame) в функции draw. Поскольку частота несколько изменяется во времени, то вычисляется среднее значение по нескольким последним обновлениям. 46
Системная переменная frameCount подсчитывает общее число обновлений окна за время работы программы. В функции setup она обнуляется, а затем к ней добавляется единица каждый новый кадр. Добавьте к новому проекту функцию draw, которая будет печатать актуальное значение частоты перерисовки экрана: # СОЗДАЁМ ОКНО def settings(): size(300,200) # ОБНОВЛЯЕМ СЦЕНУ def draw(): print("FPS = " + str(frameRate)) Поскольку программа выполняется в многозадачной среде, то показания немного изменяются со временем. → Проект Функция draw 2 Исходный код программы находится в папках Function_draw2 и Background_draw2. В приложениях с анимацией это приводит к тому, что объекты перемещаются неравномерно, но в большинстве программ эти колебания скорости почти незаметны. Чтобы понять, как работает функция draw, добавьте в неё три строки для рисования окружностей в Графическом окне: # СОЗДАЁМ ОКНО def settings(): size(480, 320) 47
# ОБНОВЛЯЕМ СЦЕНУ def draw(): print("FPS = " + str(frameRate)) x = random(width) y = random(height) ellipse(x, y, 10, 10) После запуска программы вы увидите, что в Консольном окне печатаются строки, а в Графическом окне появляются всё новые и новые окружности: 48
Это значит, что при каждом вызове функции draw все её операторы выполняются последовательно – от первого до последнего. Но здесь важно отметить, что только после выполнения последнего оператора картинка на экране обновляется. Чтобы убедиться в этом, нарисуйте ещё и окружности вдвое большего размера: # ОБНОВЛЯЕМ СЦЕНУ def draw(): print("FPS = " + str(frameRate)) x = random(width) y = random(height) ellipse(x, y, 10, 10) noLoop() x = random(width) y = random(height) ellipse(x,y,20,20) Обратите внимание на функцию noLoop: noLoop() Она прекращает вызовы функции draw. Несмотря на это в окне появятся две окружности. → 49
Функцию noLoop можно вызывать в любом месте программы, но если это сделать в функции setup, то функция draw будет выполнена 1 раз, после чего её действие прекратится. Чтобы возобновить вызовы функции draw, нужно выполнить функцию loop() Проект Консольная арифметика Исходный код программы находится в папке ArithmExpressions. Среда разработки Процессинг – это не только текстовый редактор, но и мощный калькулятор и отладчик исходного кода. В консоли можно вычислять любые арифметические выражения. Проверим! Два плюс два, дважды два и два в квадрате – все они равняются четырём. Это, как поётся в песенке, всем известно в целом мире. А так оно и есть: print 2 + 2 print 2 * 2 print 2 ** 2 Вот ещё одна задача – на сообразительность. Сколько будет: два прибавить два, умножить на два? – Конечно, не восемь, как многие второпях отвечают. А сколько? - Смотрим ответ: 50
print 2 + 2 * 2 Компьютер не проведёшь! Решим пример со скобками: print 2 * (2 + 3) Знак умножения – звёздочка *. Его нужно ставить всегда. Скобки в арифметических выражениях используются только круглые. Знак деления – дробная черта: print 12 / 5 Если оба числа целые, то результат – целое число. Если хотя бы одно число вещественное, то и результат – вещественное число: print 12. / 5 Результат целочисленного деления, которое обозначается двумя дробными чертами, - целая часть частного: 51
print 12. // 5 При вычислениях можно использовать переменные: a = 24 b = 6 print a / b Процессинг знает число пи, поэтому мы легко вычислим длину окружности радиуса r: print PI r = 10 print 2 * PI * r Легко заметить, что число пи очень неточное. Лучше пользоваться числом пи из библиотеки math, но её необходимо импортировать в проект: import math print math.pi 52
Вычисляем площадь круга: print math.pi * r ** 2 Класс math имеет функции для вычисления синусов и косинусов: a = math.pi / 3 print math.sin(a) ** 2 + math.cos(a) ** 2 Решим пример на вычисление сложного тригонометрического выражения: Понятно, что с помощью тождественных преобразований выражение можно упростить и тем самым обойтись без вычислений. Но нам нужен пример именно для вычислений, поэтому сделаем вид, что мы не знаем формулу для синуса двойного угла. Нам гораздо понятнее градусы, чем радианы, но тригонометрические функции Питона понимают как раз только радианы. В Процессинге есть функция radians для перевода градусов в радианы. А дальше просто и аккуратно переводим условие примера на питоний язык: a = 36 * math.sin(radians(102)) * math.cos(radians(102)) b = a / math.sin(radians(204)) print b 53
Ответ мы получили правильный. Точно так же вы можете вычислять любые арифметические и алгебраические выражения: # сложение и вычитание: print(12 - 3 + 7 - 4 - 4 + 2) # умножение и деление: print(12 // 3 * 7 // 4 * 4 // 2) print(12 / 3 * 7 / 4 * 4 / 2) # все действия: print(23 +16 * 3 - 72 // 9) # скобки: print(((23 + 16) * 3 - 72) // 9) Проект Отладка программ Исходный код программы находится в папке Debug. Гораздо чаще консоль используется для отладки программ. Функция print печатает значение переменной или выражения. Например, чтобы узнать цвет в проекте GrayColor2, мы могли бы просто вызвать эту функцию: # ОКРАШИВАЕМ КАНВУ def mouseMoved(): # цвет пропорционален координате Y курсора: clr = map(mouseY, 0, height, 0, 255) print(clr) 54
background(clr) fill(255,0,0) text(u"Для изменения цвета фона двигайте мышку вверх-вниз", 20, 40) fill(255,255,0) text("gray = " + str(clr), 20, 70) Текущие значения цвета печатаются в консоли: Намеренно сделаем ошибку в программе (делать ошибки просто и приятно) – вместо clr напечатаем clt: print(clt) Переменной с таким именем в исходном коде нет, поэтому при запуске программы мы получим сообщение об ошибке: 55
Структура программ Все действия, которые выполняются однократно, принято помещать в специальную функцию setup. Назначение функции setup – установка параметров программы: задание размеров окна приложения, инициализация переменных, закрашивание фона окна, загрузка с диска различных файлов: текстовых, графических, звуковых, музыкальных, шрифтовых. Двумя словами это можно назвать настройкой программы. Все операторы в функции setup выполняются однократно и последовательно. Затем непрерывно вызывается функция draw, операторы которой также выполняются последовательно, после чего картинка на экране обновляется. При следующем вызове функции draw все операторы выполняются с начала. Иногда для загрузки файлов в программу и для создания окна приложения используют встроенную функцию settings, которая выполняется однократно сразу после запуска программы и раньше функции setup. Функции setup, settings и draw имеют особенности: • В каждой программе может быть только по одной функции setup, settings и draw. • Они не имеют параметров. • Они не возвращают значения. • Функции setup, settings вызываются автоматически и однократно при запуске программы, а функция draw – пока работает программа. В общем виде структуру динамической программы можно представить так: 56
57
Да будет цвет! Поскольку все видимые графические объекты окрашены в какой-то цвет, то нужно хорошенько изучить, какие цвета и как используются в компьютерной графике. Каждый пиксель на экране можно окрасить в 16 777 216 разных цветов. Наверное, вас удивляет такое непонятное, «некруглое» число. Давайте выясним, откуда оно взялось. Все цвета на экране получаются перемешиванием трёх основных цветов – красного, зелёного и синего. Каждый из них может изменять яркость от 0 (цвет отсутствует) до 255 (наибольшая яркость). Итого для каждой составляющей цвета мы получаем 256 оттенков. Они могут смешиваться всеми возможными способами. Чтобы посчитать все оттенки цвета на экране нужно найти произведение: 256 х 256 х 256. Вычисления можно проводить прямо в программе Процессинг: С оттенками мы разобрались, но всё равно не очень понятно, почему основные цвета имеют 256 оттенков, а не 100, например. Это легко объясняется устройством компьютера, который использует для хранения информации ячейки памяти. Каждая ячейка памяти может хранить одно из двух чисел - 0 или 1 (конечно, в памяти хранятся не числа, но нам удобнее считать, что это так). Такое количество информации называется в информатике битом. Но обрабатывать данные в каждом бите отдельно очень неудобно, поэтому их объ- 58
единяют в байты. Каждый байт содержит 8 битов. Так как каждый бит может хранить 2 числа, то мы легко найдём, что в 1 байте можно хранить 2 в восьмой степени разных чисел. Опять считаем в программе Процессинг: Итак, для задания любого цвета на экране нужно всего 3 байта памяти. Но современные процессоры обрабатывают данные порциями по 32 или 64 бита (4 или 8 байтов), поэтому к трём байтам добавляют ещё 1, который отвечает за прозрачность цвета. Это значит, что каждый цвет может быть полностью или частично прозрачным. Это очень удобно, когда мы накладываем одно изображение на другое. Тогда нижнее изображение «просвечивает» через верхнее. Остался самый «сложный» вопрос: почему в компьютерах используют биты, а не десятичные числа? – Всё дело в надёжности. Биты могут находиться только в двух состояниях – включен (мы обозначили это состояние 1) – выключен (обозначили 0). Эти 2 состояния очень просто отличить друг от друга. При 10 состояниях вероятность ошибок сильно возрастает. Именно поэтому в компьютерах используют двоичную систему счисления, а не десятичную. Конечно, двоичные числа значительно длиннее десятичных, поэтому для их хранения требуется много ячеек памяти. В Питоне имеется встроенная функция bin, которая возвращает двоичное представление заданного десятичного числа. → Префикс 0b говорит о том, что это число двоичное, иначе его легко спутать с десятичным. А следующие 8 единиц – это и есть десятичное число 255 в двоичной системе. Как вы видите, вместо 3 цифр для записи этого числа потребовалось 8. 59
Но такова плата за надёжность работы компьютеров. К тому же двоичная арифметика значительно проще десятичной, поэтому арифметические действия компьютер выполняет очень быстро. Цветовая модель RGB Вы уже знаете, что цвет пикселя на экране получается смешиванием трёх составляющих (компонентов) цветов – красного, зелёного и синего. Названия цветов Красный-Зелёный-Синий переводятся на английский язык как Red-Green-Blue. По первым буквам этих слов и назван способ создания цветов на экране. Он называется цветовой моделью RGB. Цветовая модель определяет способ конструирования цвета из отдельных компонентов. Цветовая модель называется также цветовым пространством. Все цвета получаются добавлением компонентов (первичных цветов) различной интенсивности к чёрному цвету. Все цвета, которые различает глаз человека, можно создать из этих трёх цветов, изменяя их интенсивность. При добавлении синего и красного цветов (полной интенсивности) мы получаем пурпурный, зелёного и красного – жёлтый, зелёного и синего – циановый. При смешивании всех цветов получается белый цвет. При отсутствии основных цветов получается чёрный. Проект Цветные окна Исходный код программы находится в папке Function_background. 60
Как вы уже знаете, при запуске программы, в которой нет ни единой строчки кода, Процессинг создаёт пустое Графическое окно: Его размеры равны 144 х 164 пикселей, а размеры клиентской части - 128 х 128 пикселей. Размеры окна приложения можно изменять с помощью функции size. Мышиный цвет фона не очень приятен для глаз, поэтому давайте перекрасим его в более радостный цвет. Для этого Процессинг располагает функцией background (фон). В самом простом случае эта функция закрашивает фон в оттенки серого цвета: background(gray: float) Параметр gray изменяет свои значения от 0 (чёрный цвет) до 255 (белый цвет). Промежуточные значения дадут оттенки серого: gray=0 gray=255 61
gray=64 gray=128 Если значения параметра gray не укладываются в диапазон 0..255, то они будут давать не оттенки серого, а другие цвета, потому что выполнится функция: background(rgb: int) Здесь параметр rgb позволяет задавать любые цвета. Однако при десятичной записи цвета очень трудно представить, как он будет выглядеть на экране. Гораздо удобнее пользоваться 16-ричными числами. Например, для первой формы функции background диапазон значений изменяется так: 0..255 – десятичная запись 0..0xFF – 16-ричная запись Для второй: 0..16646655– десятичная запись 0..0xFFFFFF – 16-ричная запись 62
Если для первой формы функции background мы не получаем никакого преимущества при использовании 16-ричных чисел, то для второй 16-ричные числа очень облегчают выбор нужного цвета. Первые две 16-ричные цифры определяют красную (red) составляющую цвета, вторые две – зелёную (green) и последние две – синюю (blue): 0xFFFFFF r g b Значение каждой составляющей изменяется точно так же, как и для параметра gray - от 0 до 0xFF. Например, вызов функции background с аргументом 0xFF0000 background(0xFF0000) окрасит окно в красный цвет. → С аргументом 0x00FF00 – в зелёный: 63
С аргументом 0x0100FF – в синий. → C аргументом 0xFF00FF – в лиловый и так далее: Для синего цвета аргумент должен быть равен 0x0000FF, а не 0x0100FF, но тогда срабатывает первая версия функции background, и вы получите совсем не то, что ожидали. Вот поэтому и приходится пускаться на хитрость. Впрочем, эти два цвета на глаз всё равно отличить невозможно. При вызове функции background с аргументом rgb можно вместо префикса 0x использовать префикс #, но тогда число нужно заключить в кавычки: background('#FF00FF') 64
Чтобы не гадать с 16-ричными числами, выберите в инструменте Color Selector цвет и нажмите кнопку Copy: Теперь установите текстовый курсор внутри кавычек и нажмите клавиши Ctrl + V. 16-ричное значение выбранного цвета будет вставлено по месту назначения: background('#19C147') Вместо клавиш вы можете выполнить команду Paste контекстного меню Редактора кода: Чтобы вызвать контекстное меню, нажмите правую кнопку мышки. 65
Запустите программу – окно окрасится в выбранный цвет: В функцию background можно передать 3 или 4 десятичных числа в диапазоне 0..255: background(r: float, g: float, b: float) background(r: float, g: float, b: float, alpha: float) Здесь: • r, g, b – это составляющие цвета • аlpha – составляющая прозрачности Во многих случаях этими функциями пользоваться гораздо удобнее, чем предыдущими. Так вызовы background(255,255,0) background(0,255,255) дают очаровательные жёлтый и циановый цвета: 66
И даже синий цвет вызывается правильно: background(0,0,255) Функция color возвращает численное представление заданного цвета: color(gray: int) color(r: float, g: float, b: float) color(r: float, g: float, b: float, alpha: float) 67
Перепишем примеры серого, жёлтого и цианового цвета так: # функция color # серый: clr = color(125) background(clr) # жёлтый: clr = color(0xFF, 0xFF, 0x00); background(clr) # циановый: clr = color(0, 255, 255) background(clr) Вызов функции color с одним параметром даёт тот же результат, что и непосредственное присвоение аргумента переменной clr, а вот остальные функции color используются в программах очень часто. Класс java.awt.Color может пригодиться вам при выборе цвета, так как имеет несколько полей с понятными именами. Подключаем его к нашему проекту: from java.awt import Color И окрашиваем окно в зелёный цвет: # Java-цвета: clr = Color.GREEN background(clr.getRGB()) Полный список названий цветов из класса Color: 68
BLACK, BLUE, CYAN, DARK_GRAY, GREEN, LIGHT_GRAY, MAGENTA, ORANGE, PINK, RED, WHITE, YELLOW Или: black, blue, cyan, darkGray, green, lightGray, magenta, orange, pink, red, white, yellow Как видите, их можно писать и как константы (БОЛЬШИМИ буквами), и как переменные (маленькими буквами): clr = Color.pink background(clr.getRGB()) Вы можете добавить к проекту и свои константы, с помощью которых создадите цветовую схему приложения: # цветовые "константы": RED = color(0xFF0000) GREEN = color(0x00FF00) BLUE = color(0x0000FF) background(GREEN) 69
Функция fill Функция fill определяет цвет заливки геометрических фигур: fill(gray: float) fill(gray: float, fill(rgb: int) fill(rgb: int, a: fill(r: float, g: fill(r: float, g: a: float) float) float, b: float) float, b: float, a: float) Здесь: • • • • gray – серый цвет, значения по умолчанию 0..255 rgb – хроматический цвет, значения по умолчанию больше 255 r, g, b – это составляющие цвета а – составляющая прозрачности alpha Функции noFill отключает заливку: noFill() Проект ColorMode Исходный код программы находится в папке ColorMode. По умолчанию установлен режим RGB, но можно переключиться на режим HSB, вызвав функцию colorMode с соответствующим аргументом: 70
colorMode(mode: int,[max1: float], [max2: float],[max3: float],[maxA: float]) • mode – цветовая модель. Параметр принимает значения RGB или HSB. • [max1] – максимальное значение составляющих red или hue в зависмости от выбранной цветовой модели. Если остальные параметры не заданы, то они получат такое же значение. • [max2] – максимальное значение составляющих green или saturation в зависмости от выбранной цветовой модели. • [max3] – максимальное значение составляющих blue или brightness/lighntess в зависмости от выбранной цветовой модели. • [maxA] – максимальное значение прозрачности. Минимальное значение параметров всегда равно нулю. По умолчанию установлен режим: colorMode(RGB, 255) Для режима HSB обычно выбирают такие параметры: colorMode(HSB, 360, 100, 100) Если представить цвета в режиме HSB в виде круга, то цвет удобно задавать как угол от 0 до 360 градусов. → Легко видеть, что при нулевом значении первого параметра получится красный цвет. Проверим теорию на практике: 71
# создаём окно: size(480, 320) # уствнавливаем цветовой режим: colorMode(HSB, 360, 100, 100) # красный цвет: background(0, 100, 100) Так оно и есть: Для зелёного цвета значение первого параметра нужно увеличить: # зелёный цвет: background(112.5, 100, 100) 72
Проект HSBColor Исходный код программы находится в папке HSBColor. Задавать цвета в градусах ничуть не удобнее, чем числами в режиме RGB, но главное преимущество цветовой модели HSB заключается в том, что при изменении параметра hue (оттенок) цвета изменяются плавно, чего в цветовом режиме RGB получить невозможно. Начните новый проект и в функции setup задайте цветовой режим HSB: def setup(): # создаём окно: size(800, 240) 73
# размер шрифта: textSize(24) # цветовой режим: colorMode(HSB, 360, 100, 100) background(0, 100, 100) При перемещении мышки вызывается функция mouseMoved, в которой мы изменяем значение параметра hue пропорционально удалению курсора от верхнего края канвы: def draw(): pass # ОКРАШИВАЕМ КАНВУ def mouseMoved(): # цвет пропорционален координате Y курсора: h = map(mouseY, 0, height, 0, 360) background(h, 100, 100) fill(0) text(u"Для изменения цвета фона двигайте мышку вверх-вниз", 20, 40) text("Hue = " + str(h), 20, 80) Теперь вы можете исследовать цвета во всём диапазоне изменения значений параметра hue: 74
Выполните команду меню Инструменты → Выбрать цвет… (Tools → Color Selector…). → В открывшемся диалоговом окне сразу видны значения составляющих цвета RGB и HSB, а также 16-ричное значение цвета. Скопируйте значения в свой проект – и готово! Инструмент Color Selector Мы уже несколько раз пользовались очень полезным инструментом Color Selector, которым удобно пользоваться при выборе цвета. Чтобы открыть его, выполните команду меню Инструменты – Выбрать цвет.. (Tools → Color Selector…). → В диалоговом окне выберите нужный цвет и запомните значения составляющих или просто нажмите кнопку Copy, чтобы скопировать 16-ричное значение цвета (оно начинается с символа #) в буфер обмена, а затем вставить в программу. 75
Чтобы выбрать цвет, сначала установите ползунок на вертикальной цветной шкале в нужное положение: Затем мышкой выберите цвет на цветном поле: 76
Проект Цветовые диапазоны Исходный код программы находится в папке Color_ranges. В функции colorMode стандартный диапазон RGB 0..255 можно преобразовать в 0..100 или 0..1.0, смотря по обстоятельствам. Иногда такие «необычные» диапазоны бывают удобнее. Например, тот же лиловый цвет мы можем получить так: # создаём окно: size(480, 320) # цветовой режим: colorMode(RGB, 100) # лиловый цвет: background(100, 0, 100) или даже так: # цветовой режим: colorMode(RGB, 1) # лиловый цвет: background(1, 0, 1) Такие «фокусы» можно проделывать и в режиме HSB. 77
Проект Цветная таблица Исходный код программы находится в папках Colors, Colours и Colors_test. Всегда полезно иметь под рукой палитру с проверенными красками. Для удобства пользования названия всех цветов и их 16-ричные значения цветов сведены в таблицу: Red Colors IndianRed LightCoral Salmon DarkSalmon LightSalmon Crimson Red FireBrick DarkRed "#CD5C5C" "#F08080" "#FA8072" "#E9967A" "#FFA07A" "#DC143C" "#FF0000" "#B22222" "#8B0000" Pink Colors Pink "#FFC0CB" LightPink "#FFB6C1" HotPink "#FF69B4" DeepPink "#FF1493" MediumVioletRed "#C71585" PaleVioletRed "#DB7093" Orange-Yellow Colors LightSalmon "#FFA07A" Coral "#FF7F50" Tomato "#FF6347" OrangeRed "#FF4500" DarkOrange "#FF8C00" Orange "#FFA500" Gold "#FFD700" Yellow "#FFFF00" LightYellow "#FFFFE0" 78
LemonChiffon LightGoldenrodYellow PapayaWhip Moccasin PeachPuff PaleGoldenrod Khaki DarkKhaki Purple Colors Lavender Thistle Plum Violet Orchid Fuchsia Magenta MediumOrchid MediumPurple BlueViolet DarkViolet DarkOrchid DarkMagenta Purple Indigo SlateBlue DarkSlateBlue MediumSlateBlue Green Colors GreenYellow Chartreuse LawnGreen Lime LimeGreen PaleGreen LightGreen MediumSpringGreen SpringGreen MediumSeaGreen SeaGreen ForestGreen "#FFFACD" "#FAFAD2" "#FFEFD5" "#FFE4B5" "#FFDAB9" "#EEE8AA" "#F0E68C" "#BDB76B" "#E6E6FA" "#D8BFD8" "#DDA0DD" "#EE82EE" "#DA70D6" "#FF00FF" "#FF00FF" "#BA55D3" "#9370DB" "#8A2BE2" "#9400D3" "#9932CC" "#8B008B" "#800080" "#4B0082" "#6A5ACD" "#483D8B" "#7B68EE" "#ADFF2F" "#7FFF00" "#7CFC00" "#00FF00" "#32CD32" "#98FB98" "#90EE90" "#00FA9A" "#00FF7F" "#3CB371" "#2E8B57" "#228B22" 79
Green DarkGreen YellowGreen OliveDrab Olive DarkOliveGreen MediumAquamarine DarkSeaGreen LightSeaGreen DarkCyan Teal Blue Colors Aqua Cyan LightCyan PaleTurquoise Aquamarine Turquoise MediumTurquoise DarkTurquoise CadetBlue SteelBlue LightSteelBlue PowderBlue LightBlue SkyBlue LightSkyBlue DeepSkyBlue DodgerBlue CornflowerBlue MediumSlateBlue RoyalBlue Blue MediumBlue DarkBlue Navy MidnightBlue Brown Colors Cornsilk BlanchedAlmond "#008000" "#006400" "#9ACD32" "#6B8E23" "#808000" "#556B2F" "#66CDAA" "#8FBC8F" "#20B2AA" "#008B8B" "#008080" "#00FFFF" "#00FFFF" "#E0FFFF" "#AFEEEE" "#7FFFD4" "#40E0D0" "#48D1CC" "#00CED1" "#5F9EA0" "#4682B4" "#B0C4DE" "#B0E0E6" "#ADD8E6" "#87CEEB" "#87CEFA" "#00BFFF" "#1E90FF" "#6495ED" "#7B68EE" "#4169E1" "#0000FF" "#0000CD" "#00008B" "#000080" "#191970" "#FFF8DC" "#FFEBCD" 80
Bisque NavajoWhite Wheat BurlyWood Tan RosyBrown SandyBrown Goldenrod DarkGoldenrod Peru Chocolate SaddleBrown Sienna Brown Maroon "#FFE4C4" "#FFDEAD" "#F5DEB3" "#DEB887" "#D2B48C" "#BC8F8F" "#F4A460" "#DAA520" "#B8860B" "#CD853F" "#D2691E" "#8B4513" "#A0522D" "#A52A2A" "#800000" White Colors White Snow Honeydew MintCream Azure AliceBlue GhostWhite WhiteSmoke Seashell Beige OldLace FloralWhite Ivory AntiqueWhite Linen LavenderBlush MistyRose "#FFFFFF" "#FFFAFA" "#F0FFF0" "#F5FFFA" "#F0FFFF" "#F0F8FF" "#F8F8FF" "#F5F5F5" "#FFF5EE" "#F5F5DC" "#FDF5E6" "#FFFAF0" "#FFFFF0" "#FAEBD7" "#FAF0E6" "#FFF0F5" "#FFE4E1" Gray Colors Gainsboro LightGray Silver DarkGray Gray DimGray "#DCDCDC" "#D3D3D3" "#C0C0C0" "#A9A9A9" "#808080" "#696969" 81
LightSlateGray SlateGray DarkSlateGray Black "#778899" "#708090" "#2F4F4F" "#000000" Все цвета разбиты по категориям, так что выбрать нужный цвет вам будет проще. Очень наглядная таблица цветов представлена на сайте msdn.microsoft.com: 82
Запишем все цвета как константы в файл colors.py: # Red Colors IndianRed ='#CD5C5C' LightCoral ='#F08080' Salmon ='#FA8072' DarkSalmon ='#E9967A' LightSalmon ='#FFA07A' Crimson ='#DC143C' Red ='#FF0000' FireBrick ='#B22222' DarkRed ='#8B0000' # Pink Colors Pink ='#FFC0CB' LightPink ='#FFB6C1' HotPink ='#FF69B4' DeepPink ='#FF1493' MediumVioletRed ='#C71585' PaleVioletRed ='#DB7093' # Orange-Yellow Colors LightSalmon ='#FFA07A' Coral ='#FF7F50' Tomato ='#FF6347' OrangeRed ='#FF4500' DarkOrange ='#FF8C00' Orange ='#FFA500' Gold ='#FFD700' Yellow ='#FFFF00' LightYellow ='#FFFFE0' LemonChiffon ='#FFFACD' LightGoldenrodYellow ='#FAFAD2' PapayaWhip ='#FFEFD5' Moccasin ='#FFE4B5' PeachPuff ='#FFDAB9' PaleGoldenrod ='#EEE8AA' Khaki ='#F0E68C' DarkKhaki ='#BDB76B' # Purple Colors 83
Lavender ='#E6E6FA' Thistle ='#D8BFD8' Plum ='#DDA0DD' Violet ='#EE82EE' Orchid ='#DA70D6' Fuchsia ='#FF00FF' Magenta ='#FF00FF' MediumOrchid ='#BA55D3' MediumPurple ='#9370DB' BlueViolet ='#8A2BE2' DarkViolet ='#9400D3' DarkOrchid ='#9932CC' DarkMagenta ='#8B008B' Purple ='#800080' Indigo ='#4B0082' SlateBlue ='#6A5ACD' DarkSlateBlue ='#483D8B' MediumSlateBlue ='#7B68EE' # Green Colors GreenYellow ='#ADFF2F' Chartreuse ='#7FFF00' LawnGreen ='#7CFC00' Lime ='#00FF00' LimeGreen ='#32CD32' PaleGreen ='#98FB98' LightGreen ='#90EE90' MediumSpringGreen ='#00FA9A' SpringGreen ='#00FF7F' MediumSeaGreen ='#3CB371' SeaGreen ='#2E8B57' ForestGreen ='#228B22' Green ='#008000' DarkGreen ='#006400' YellowGreen ='#9ACD32' OliveDrab ='#6B8E23' Olive ='#808000' DarkOliveGreen ='#556B2F' MediumAquamarine ='#66CDAA' DarkSeaGreen ='#8FBC8F' LightSeaGreen ='#20B2AA' DarkCyan ='#008B8B' Teal ='#008080' 84
# Blue Colors Aqua ='#00FFFF' Cyan ='#00FFFF' LightCyan ='#E0FFFF' PaleTurquoise ='#AFEEEE' Aquamarine ='#7FFFD4' Turquoise ='#40E0D0' MediumTurquoise ='#48D1CC' DarkTurquoise ='#00CED1' CadetBlue ='#5F9EA0' SteelBlue ='#4682B4' LightSteelBlue ='#B0C4DE' PowderBlue ='#B0E0E6' LightBlue ='#ADD8E6' SkyBlue ='#87CEEB' LightSkyBlue ='#87CEFA' DeepSkyBlue ='#00BFFF' DodgerBlue ='#1E90FF' CornflowerBlue ='#6495ED' MediumSlateBlue ='#7B68EE' RoyalBlue ='#4169E1' Blue ='#0000FF' MediumBlue ='#0000CD' DarkBlue ='#00008B' Navy ='#000080' MidnightBlue ='#191970' # Brown Colors Cornsilk ='#FFF8DC' BlanchedAlmond ='#FFEBCD' Bisque ='#FFE4C4' NavajoWhite ='#FFDEAD' Wheat ='#F5DEB3' BurlyWood ='#DEB887' Tan ='#D2B48C' RosyBrown ='#BC8F8F' SandyBrown ='#F4A460' Goldenrod ='#DAA520' DarkGoldenrod ='#B8860B' Peru ='#CD853F' Chocolate ='#D2691E' SaddleBrown ='#8B4513' 85
Sienna ='#A0522D' Brown ='#A52A2A' Maroon ='#800000' # White Colors White ='#FFFFFF' Snow ='#FFFAFA' Honeydew ='#F0FFF0' MintCream ='#F5FFFA' Azure ='#F0FFFF' AliceBlue ='#F0F8FF' GhostWhite ='#F8F8FF' WhiteSmoke ='#F5F5F5' Seashell ='#FFF5EE' Beige ='#F5F5DC' OldLace ='#FDF5E6' FloralWhite ='#FFFAF0' Ivory ='#FFFFF0' AntiqueWhite ='#FAEBD7' Linen ='#FAF0E6' LavenderBlush ='#FFF0F5' MistyRose ='#FFE4E1' # Gray Colors Gainsboro ='#DCDCDC' LightGray ='#D3D3D3' Silver ='#C0C0C0' DarkGray ='#A9A9A9' Gray ='#808080' DimGray ='#696969' LightSlateGray ='#778899' SlateGray ='#708090' DarkSlateGray ='#2F4F4F' Black ='#000000' Скопируйте его в папку с проектом, а затем импортируйте в главный файл программы: from colors import * 86
Теперь вы можете задавать цвет по имени: size(320, 320) background(Orchid) Это гораздо удобнее, чем запоминать 16-ричные значения. Запустите программу – фон окна окрасится в прекрасный орхидейный цвет: Цветовые функции В графических программах нужно уметь извлекать цветовые составляющие цвета. Для этого имеются такие функции. Функция red возвращает красную составляющую цвета или пиксельного массива: red(color: int) → float 87
Функция green возвращает зелёную составляющую цвета или пиксельного массива: green(color: int) → float Функция blue возвращает синюю составляющую цвета или пиксельного массива: blue(color: int) → float Аналогично действуют функции hue, saturation для цветовых моделей HSB и HSL и функция brightness для HSB: hue(color: int) → float saturation(color: int) → float brightness(color: int) → float Функция lightness возвращает светлоту цвета для цветовой модели HSL: lightness(color: int) → float Функция alpha возвращает прозрачность заданного цвета: alpha(color: int) → float Функция lerpColor возвращает цвет, «средний» между заданными цветами c1 и c2: 88
lerpColor(c1: int, c2: int, amt: float) → int • с1 – первый цвет • с2 – второй цвет • amt – число в диапазоне 0.0..1.0, которое показывает долю каждого цвета в смеси. • Если amt = 0, то смесь состоит только из первого цвета. • Если amt = 1, то смесь состоит только из второго цвета. • Если amt = 0.5, то смесь состоит из равных частей первого и второго цвета. Проект Светоформа Исходный код программы находится в папке Svetoforma. Мы уже научились окрашивать канву в разные цвета, а теперь давайте превратим её в светофор - чтобы она переливалось всеми цветами радуги. В функции settings установите размеры окна, а в функции setup - частоту его обновления в функции draw: # СВЕТОФОРМА # СОЗДАЁМ ОКНО def settings(): size(400, 300) def setup(): frameRate(3) 89
Функция frameRate заменяет таймер в обычных приложениях: frameRate([fps: float]) • fps – это число обновлений канвы за 1 секунду. По умолчанию частота равна 60, то есть канва перерисовывается 60 раз в секунду. Но если у компьютера медленный процессор, частота может оказаться меньше заданной. Если при вызове функции frameRate не задать аргумент, то она вернёт текущее значение частоты. Первоначально это значение равно 0, а затем обновляется с каждой новой прорисовкой канвы (кадром - frame) в функции draw. Поскольку частота несколько изменяется во времени, то вычисляется среднее значение по нескольким последним обновлениям. Системная переменная frameCount подсчитывает общее число обновлений канвы за время работы программы. В функции setup она обнуляется, а затем добавляет единицу за каждый новый кадр. Чтобы образовать новый случайный цвет, мы с помощью функции random получаем случайные числа в диапазоне 0..255, которые присваиваем переменным r, g, b – цветовым составляющим цвета. Сам цвет мы собираем в функции color, присваиваем значение цвета переменной clr, а затем окрашиваем фон: # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(256), random(256), random(256)) return clr Функция random возвращает случайное значение: random([min: float],[max: float]) → float 90
• Если ни один аргумент не задан, то функция возвращает вещественное число из интервала 0.0..1.0 (не включая 1.0). • Если задан 1 аргумент, то функция возвращает вещественное число из интервала 0.0..число (не включая его). • Если заданы 2 аргумента, то функция возвращает вещественное число из интервала min..max (не включая его). Если функции random передать массив choices, то она вернёт случайный элемент массива: random(choices) Функция random при каждом запуске программы генерирует новую последовательность случайных чисел. При отладке нужно каждый раз получать одну и ту же последовательность. Функция randomSeed инициализирует генератор псевдослучайных чисел (ГПСЧ) числом seed: randomSeed(seed: int) И он генерирует одну и ту же последовательность, которая определяется значением параметра seed. В функции draw мы присваиваем значение цвета переменной clr, а затем окрашиваем фон: def draw(): clr = random_color() background(clr) После старта программы окно с каждым новым кадром изменяет свой цвет произвольным образом: 91
В книге, конечно, световые метаморфозы передать невозможно, но мигающее окно смотрится забавно. Добавим интерактивности нашей программе. Окно перестанет мигать, если нажать на кнопку мышки. А если потом нажать на любую клавишу, то мигание возобновится. Функция mousePressed() и системная переменная mousePressed позволяют узнать, нажата ли какая-либо кнопка мышки, и обработать это событие. Если переменная mousePressed равна True, значит, кнопка мышки находится в нажатом состоянии. 92
Функция keyPressed() и системная переменная keyPressed позволяют узнать, нажата ли какая-либо клавиша. Если переменная keyPressed равна True, значит, клавиша находится в нажатом состоянии. Добавляем эти функции к программе. Чтобы остановить мигание, мы вызываем функцию noLoop: # НАЖИМАЕМ КНОПКУ МЫШКИ def mousePressed(): noLoop() А чтобы возобновить его – функцию loop: # НАЖИМАЕМ КЛАВИШУ def keyPressed(): loop() Проект Картинный фон Исходный код программы находится в папке Background_image. 93
В качестве фона окна можно использовать и картинки из файла. При этом важно помнить, что их размеры должны совпадать, иначе вы получите сообщение об ошибке. Таким образом, вы должны либо сразу создать картинку нужного размера, либо подогнать её к окну после загрузки, либо установить размеры окна по размерам картинки. Картинка должна быть полностью непрозрачной и на неё не действует функция tint. Лучшее решение такое: подготовить картинку нужных размеров. Эти же размеры указать в функции size. Загрузить картинку и сделать её фоном. В ситуации, если размеры картинки всё-таки не совпадают с размерами окна, то её следует подогнать методом resize: image.resize(width: int, height: int) Пропорции картинки при этом могут быть нарушены! Когда размеры картинки известны заранее, их можно указать в функции settings: # СОЗДАЁМ ОКНО def settings(): size(800,533) def setup(): #img = loadImage("tulip.jpg") img = loadImage(u"тюльпаны.jpg") background(img) 94
Пишите названия файлов латинскими буквами или добавляйте букву u перед строкой с кириллицей. При запуске программы вы увидите картинку: # Картинку для фона нужно скопировать в папку data в папке проекта: 95
Элемент управления ползунок (Slider) Очень часто нужно изменять какие-либо параметры в работающей программе. Иногда для этого хватает мышки и клавиатуры, но в большинстве случаев удобнее пользоваться специализированными элементами управления. Например, ползунком (slider), который может плавно изменять какое-либо значение. Проект RGB-смеситель Исходный код программы находится в папке RGB_mixer. Давайте научимся самостоятельно задавать RGB-составляющие цвета в работающей программе. Это можно сделать разными способами, но самый приятный состоит в том, чтобы установить 3 ползунка для изменения значений этих составляющих. Однако в Процессинге нет собственных элементов управления, и нам необходимо, в первую очередь, установить нужную библиотеку. А это совсем нетрудно, если компьютер подключён к Интернету. Выполните команду меню Набросок → Импортировать библиотеку… → Добавить библиотеку (Sketch → Import Library → Add Library): В диалоговом окне Contribution Manager на вкладке Libraries найдите библиотеку ControlP5 и 96
нажмите кнопку Install. Когда библиотека загрузится и установится, слева от названия библиотеки появится зелёный кружок с галочкой: Закройте диалоговое окно. Теперь вы можете пользоваться библиотекой в своих программах. Если вы работаете на разных компьютерах, то устанавливайте библиотеку на каждом! 97
Импортируем библиотеку controlP5 в наш проект: add_library('controlP5') Вот теперь можно объявить переменную cp5 для хранения ссылки на библиотеку: global cp5 В функции settings устанавливаем размеры окна: # СОЗДАЁМ ОКНО def settings(): size(660, 480) А в функции setup создаём ползунки с нужными свойствами: # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 380 # длина: w = 610 cp5.addSlider("rd")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(0, 255)\ .value = 128 cp5.addSlider("gr")\ .setPosition(x, y+30)\ .setSize(w, 20)\ 98
.setRange(0, 255)\ .setValue(128) cp5.addSlider("bl")\ .setPosition(x, y+60)\ .setSize(w, 20)\ .setRange(0, 255)\ .setValue(128) Название переменной нужно указать в двойных кавычках в операторе вызова функции addSlider. Всё! Тяжёлая работа позади! Осталось самое приятное в этом проекте – написать функцию draw, в которой мы снимаем показания с ползунков и по текущим значениям составляющих цвета окрашиваем окно: # ОБНОВЛЯЕМ СЦЕНУ def draw(): rd = cp5.getValue("rd") gr = cp5.getValue("gr") bl = cp5.getValue("bl") background(rd, gr, bl) Запускайте программу, перемещайте движки и следите, как работает наше цветное приложение. → 99
Проект HSBSlider Исходный код программы находится в папке HSBSlider. Одним ползунком можно изменять цвет в режиме HSB. Сделать это очень просто: add_library('controlP5') global cp5 # СОЗДАЁМ ОКНО def settings(): size(660, 480) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 480 - 40 # длина: w = 610 cp5.addSlider("h")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(0, 100)\ .value = 128 # цветовой режим: colorMode(HSB, 100) # ОБНОВЛЯЕМ СЦЕНУ def draw(): h = cp5.getValue("h") 100
background(color(h, 100, 100)) В функции draw вызываем метод getValue, который возвращает текущее значение на ползунке. Мы задали цветовой режим (HSB, 100), поэтому все цветовые компоненты изменяются в диапазоне 0..100, как и на ползунке. Полученное значение параметра hue отправляем в функцию color, а затем в функцию background. Запускаем программу, перемещаем движок – и канва окрашивается в заданный цвет: 101
Проект HSBSlider3 Исходный код программы находится в папке HSBSlider3. Вы уже хорошо выучили, как действует цветовая составляющая hue в цветовой модели HSB. Пришёл черёд исследовать и остальные две составляющие. А для этого нам понадобятся 3 ползунка. За основу мы возьмём предыдущий проект. В функции setup создаём все ползунки. Дело это несложное, но нужно правильно выбрать их положение на канве: def setup(): global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 380 # длина: w = 610 cp5.addSlider("h")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(0, 360)\ .value = 0 cp5.addSlider("s")\ .setPosition(x, y+30)\ .setSize(w, 20)\ .setRange(0, 100)\ .setValue(100) cp5.addSlider("v")\ .setPosition(x, y+60)\ .setSize(w, 20)\ .setRange(0, 100)\ .setValue(100) 102
# цветовой режим: colorMode(HSB, 360, 100, 100 Пределы изменения значений на ползунках выбираем в соответствии с параметрами цветовой модели, заданными в функции colorMode. В функции draw каждую итерацию снимаем показания с ползунков и перекрашиваем канву: # ОБНОВЛЯЕМ СЦЕНУ def draw(): h = cp5.getValue("h") s = cp5.getValue("s") v = cp5.getValue("v") background(color(h, s, v)) Теперь вы замечательно выучите цветовую модель HSB вдоль и поперёк. → 103
Пиксели и точки Самым простым геометрическим объектом является точка. Она, как известно из геометрии, размеров не имеет, поэтому настоящую точку увидеть нельзя, а вот компьютерную - можно, хотя размером она примерно в четверть миллиметра. Называется такая точка пикселем. Пиксель можно окрасить в миллионы цветов, а из множества пикселей мы сумеем собрать на экране любую картину. Для рисования точек-пикселей использовать функцию point: point(x: float, y: float) Здесь: x и y – координаты точки на плоскости: 104
Вся программа для рисования точки состоит из одной строки: point(30, 20) То есть мы хотим поставить точку с координатами: x = 30, y = 20. Программа готова, и мы запускаем её. Для этого нажимаем кнопку Запустить (Run) . На экране появляется окно приложения с малюсенькой чёрной точкой: Чтобы вы могли лучше разглядеть её, я увеличил изображение в 3 раза: 105
Фон окна – светло-серый, а более тёмным серым цветом выделена поверхность рисования (канва). Начало координат находится в её левом верхнем углу. Ось Х направлена вправо, а ось Y- вниз. Чтобы нарисовать нашу точку, программа отсчитывает 30 пикселей от левой границы канвы и затем 20 пикселей от верхней границы канвы. Пиксель с этими координатами она закрашивает чёрным цветом. Так вы можете поставить сколько угодно точек на канве. Функция set set(x: int, y: int, clr: int) закрашивает пиксель клиентской области окна в цвет clr. Цвет можно задать любым способом из тех, что мы уже рассмотрели. Функция set закрашивает именно отдельный пиксель, а не рисует точки, как функция point, а размеры пикселя изменить нельзя. Cерая канва и чёрные точки слишком скучны, поэтому давайте научимся окрашивать их в разные цвета! Проект Пуантилизм, или Ставим точки Исходный код программы находится в папке Pointilism. Мостовая пусть качнётся, как очнётся! Пусть начнётся, что ещё не началось! Вы рисуйте, вы рисуйте, 106
вам зачтётся... Что гадать нам: удалось - не удалось? Булат Окуджава. Живописцы Давайте напишем простенькую программу, которая будет самостоятельно выводить на канву точки случайно выбранного цвета. Эстетического удовольствия вы не получите никакого, но зато хорошенько познакомитесь с функцией set. Программа окрашивает точки канвы в случайные цвета, поэтому картинка получается совершенно хаотическая: В функции setup мы создаём окно и устанавливаем небольшую частоту обновления экрана: def setup(): # создаём окно: size(480, 320) frameRate(1) 107
В функции draw программа бесконечно перебирает все точки канвы и окрашивает их с помощью функции set: # РИСУЕМ ПИКСЕЛИ def draw(): # Окрашиваем пиксели канвы в разные цвета: for y in range(height): for x in range(width): # выбираем случайный цвет для пикселя: clr = getRandomColor() # и окрашиваем его: set(x, y, clr) Случайный цвет формирует функция getRandomColor: # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def getRandomColor(): # случайный цвет: r = random(256) g = random(256) b = random(256) return color(r, g, b) Проект Пуантилизм 2 Исходный код программы находится в папках Pointilism2 и Pixel_2. Добавьте к проекту несколько строчек кода, чтобы получить интересный визуальный эффект: надпись Пиксели печатается над цветными точками: 108
# СОЗДАЁМ ОКНО def settings(): size(320, 240) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): font = loadFont("Arial-Black-80.vlw") textFont(font, 56) fill(255,0,0, 160) frameRate(10) # ОБНОВЛЯЕМ СЦЕНУ def draw(): for y in range(height): for x in range(width): # выбираем случайный цвет для пикселя: clr = getRandomColor() # и окрашиваем его: set(x, y, clr) text(u"ПИКСЕЛИ", 5, 130) def getRandomColor(): r = random(256) g = random(256) b = random(256) clr = color(r, g, b) return clr Задавая цвет текста, мы позаботились о его частичной прозрачности: fill(255,0,0, 160) Поэтому через буквы видны и фоновые пиксели. 109
Проект Пуантилизм 3 Исходный код программы находится в папке Pointilism3. Функция set вполне годится для создания статических или медленно изменяющихся картинок на экране. Для создания динамических картинок пользуются другим способом. Сначала нужно вызвать функцию loadPixels: loadPixels() Она загружает все пиксели канвы в массив: pixels[] Если ширина канвы width пикселей, а высота height пикселей, то всего в массиве окажется width х height пикселей. def setup(): # создаём окно: size(640, 480) background(222) frameRate(3) Обновляем пиксели в массиве: # РИСУЕМ ПИКСЕЛИ def draw(): 110
loadPixels() # Окрашиваем пиксели канвы в разные цвета: for i in range(len(pixels)): # выбираем случайный цвет для пикселя: r = random(256) g = random(256) b = random(256) # и окрашиваем его: pixels[i] = color(r,g,b) updatePixels() Обратите внимание, что после изменения пикселей нужно вызвать функцию updatePixels для обновления канвы: updatePixels() Несмотря на то, что мы увеличили размеры канвы и частоту обновления, новая программа работает быстрее. → Итак, если вы изменяете большое число пикселей канвы, то пользуйтесь массивом пикселей. Если нужно изменить лишь некоторые пиксели, то вполне достаточно функции set. 111
Проект Случайные точки Исходный код программы находится в папке Random_points. Как вы уже знаете, поставить точку в заданном месте окна приложения можно с помощью функции point, которой нужно передать координаты (x, y) точки. Чтобы точки напоминали звёзды на ночном небе, окрасим фон окна в чёрный цвет: size(320, 240) background(0) Так как мы хотим расставить точки случайным образом, то воспользуемся функции randint из модуля random: from random import randint Координаты точек выбираем случайно, но так, чтобы они не выходили за пределы окна приложения: for _ in range(1000): # случайные координаты: x = randint(0, width) y = randint(0, height) point(x,y) Если вы запустите программу, то ни одной точки на чёрном небе не увидите: 112
И немудрено! По умолчанию все точки имеют чёрный цвет, поэтому их и не видно. Исправляем оплошность: for _ in range(1000): # цвет очередного пикселя: stroke(255) # случайные координаты: x = randint(0, width) y = randint(0, height) point(x,y) Теперь все звёзды хорошо видны на экране. → Здесь мы впервые встретились с функцией stroke. Она может получать любое значение цвета всеми возможными способами. При этом цвет может быть и полупрозрачным: 113
stroke(gray: float) stroke(gray: float, stroke(rgb: int) stroke(rgb: int, a: stroke(r: float, g: stroke(r: float, g: a: float) float) float, b: float) float, b: float, a: float) Здесь: • • • • gray – серый цвет, значения по умолчанию 0..255 rgb – хроматический цвет, значения по умолчанию больше 255 r, g, b – это составляющие цвета а – составляющая прозрачности alpha Также функции stroke можно передать переменную с заданным значением цвета. В своей программе мы вызвали функцию stroke с аргументом 255, который соответствует белому цвету. Но нам, конечно, интереснее получить на небе разноцветные звёзды. И это легко сделать с помощью всё той же «случайной» функции randint. Так как составляющие цвета имеют значения в диапазоне 0..255, то мы можем передать в функцию color случайные значения цветных составляющих, а она вернёт нам случайный цвет: for _ in range(1000): # цвет очередного пикселя: clr = color(randint(0,255), randint(0, 255), randint(0, 255)) stroke(clr) #stroke(255) # случайные координаты: x = randint(0, width) y = randint(0, height) point(x,y) 114
Небольшое изменение в программе – и мы видим на небе цветные звёзды: Проект Большие точки Исходный код программы находится в папке Color_points. Но звёзды бывают не только разного цвета, но и разного размера. Processing имеет собственную функцию random, которая возвращает случайное вещественное число в заданном диапазоне. Этой функцией пользоваться удобнее, чем функцией randint, так как не нужно дополнительно импортировать её. random(max: float) random(min: float, max: float) Если передать функции random одно число, то оно вернёт случайное число в диапазоне 0..max. А если передать два числа, то в диапазоне min..max. 115
Случайное число всегда меньше max. Будьте внимательны! Но точки не обязательно должны иметь диаметр в 1 пиксель. Достаточно вызвать функцию strokeWeight, и точки превратятся в элегантные кружочки. Функция strokeWeight устанавливает размер точек, толщину линий и контура фигур: strokeWeight(weight: float) weight – это диаметр точек или толщина линий в пикселях. После выполнения функции strokeWeight все точки будут иметь заданный радиус. Но мы хотим получить точки разного размера, поэтому для каждой точки устанавливаем свой, случайный размер: size(600, 480) background(0) for _ in range(1000): # цвет очередного кружка: clr = color(random(0,255), random(0, 255), random(0, 255)) stroke(clr) # радиус точки: r = random(1, 31) strokeWeight(r * 2) # случайные координаты: x = random(r, width - r) y = random(r, height - r) point(x,y) 116
Теперь наша картинка напоминает небо в центре Галактики, а не в Солнечной системе, но получилось красиво: Обратите внимание, какие ровненькие края получились у звёздочек. Это потому, что по умолчанию включён режим сглаживания. Но если вы хотите получить грубые края, то отключите этот режим, вызвав функцию noSmooth без параметров: noSmooth() Теперь края точек-кружочков стали рваными и неаккуратными: 117
Этот режим используется редко. Например, когда вам нужно нарисовать пиксельную картинку, в которой все пиксели должны сохранить свою форму. Вернуть режим сглаживания можно, вызвав функцию smooth без параметров: smooth() 118
Проект Мигающие точки Исходный код программы находится в папке Color_points2. Вы уже умеете ставить случайные точки, а сейчас мы аккуратно расставим их на экране стройными рядами и колоннами. Все точки будут одного размера: size(600, 480) background(0) # диаметр точки: d = 20 strokeWeight(d) Во вложенных циклах мы размещаем точки слева направо и сверху вниз: for row in range(height // for col in range(width # координаты: x = d * col + d // y = d * row + d // d): // d): 2 2 Цвет точки выбираем случайно: # цвет очередного кружка: clr = color(random(0,255), random(0, 255), random(0, 255)) stroke(clr) point(x,y) 119
Для задания случайного цвета можно использовать функцию getRandomColor: for row in range(height // d): for col in range(width // d): # координаты: x = d * col + d // 2 y = d * row + d // 2 # цвет очередного кружка: clr = getRandomColor() stroke(clr) point(x,y) 120
Мы рисуем все точки всего 1 раз, поэтому весь код можно поместить в функцию setup: def setup(): size(600, 480) background(0) # диаметр точки: d = 20 strokeWeight(d) for row in range(height // d): for col in range(width // d): # координаты: x = d * col + d // 2 y = d * row + d // 2 # цвет очередного кружка: clr = random_color() stroke(clr) point(x,y) # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(0,255), random(0, 255), random(0, 255)) return clr Давайте добавим к нашему проекту функцию draw. В неё нужно перенести весь код, который отвечает за рисование точек на экране: # диаметр точки: d = 20 def setup(): size(600, 480) strokeWeight(d) frameRate(3) def draw(): background(0) 121
for row in range(height // d): for col in range(width // d): # координаты: x = d * col + d // 2 y = d * row + d // 2 # цвет очередного кружка: clr = random_color() stroke(clr) point(x,y) # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(0,255), random(0, 255), random(0, 255)) return clr Обратите внимание, что определение переменной d мы вынесли за пределы функций, чтобы она была доступна в любом месте программы, а функцию background перенесли в функцию draw, чтобы канва очищалась в начале каждого кадра. Точки теперь перерисовываются примерно 60 раз в секунду, но на снимке с экрана это показать невозможно. Для нашей программы мелькающие точки не нужны, поэтому мы уменьшим частоту обновления экрана с помощью функции frameRate до 3-х кадров в секунду. # диаметр точки: d = 20 def setup(): size(600, 480) background(0) strokeWeight(d) frameRate(3) def draw(): 122
for row in range(height // d): for col in range(width // d): # координаты: x = d * col + d // 2 y = d * row + d // 2 # цвет очередного кружка: clr = random_color() stroke(clr) point(x,y) Проект Пульсирующие точки Исходный код программы находится в папке Pulsar. Давайте усовершенствуем нашу «точечную» программу так, чтобы точки не только изменяли цвет, но и пульсировали, то есть то расширялись, то сжимались. Текущий диаметр точки мы сохраним в переменной d, а максимальный – в переменной d_max. Также нам понадобится переменная dd для хранения величины изменения диаметра точки. Если она положительная, то точки расширяются, а если отрицательная – сжимаются: # диаметр точки: d = 10 # макс. диаметр точки: d_max = 40 # в-на изменения диаметра: dd = 3 def setup(): size(600, 480) 123
frameRate(16) def draw(): background(0) global d, dd strokeWeight(d) for row in range(height // d_max): for col in range(width // d_max): # координаты: x = d_max * col + d_max // 2 y = d_max * row + d_max // 2 # цвет очередного кружка: clr = random_color() stroke(clr) point(x,y) В конце каждого цикла мы изменяем текущий диаметр точек на величину dd: d = d + dd И тут же проверяем их размеры. Если текущий диаметр больше максимального, то точки в следующих циклах сжимаются: if d > d_max: dd = -3 Если же они уже хорошенько сжались, то в следующих циклах расширяются: elif d < 2: dd = 3 Циклы сжатия и расширения точек продолжаются до тех пор, пока вы не закроете программу. 124
На снимке с экрана этот процесс показать невозможно, поэтому посмотрите, как выглядят точки в промежуточном состоянии: Проект Пипетка Исходный код программы находится в папке Pipetka. 125
Вы можете представлять канву в виде двумерного массива пикселей, каждый из которых характеризуется координатами (индексами массива) и цветом. Чтобы узнать, какой цвет имеет тот или иной пиксель канвы, достаточно обратиться к функции get: get(x: int, y: int) → int Пипеткой в графических редакторах называют инструмент, который помогает узнать цвет пикселя под курсором. В этом режиме работы он может превратиться в пипетку: Пипетка в Фотошопе Сначала мы окрашиваем все пиксели канвы в разные цвета, а затем перемещаем мышку по канве и в Консольном окне читаем координаты курсора и цвет пикселя под ним: # ПИПЕТКА # СОЗДАЁМ ОКНО def settings(): size(320, 240) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ 126
def setup(): paintCanvas() def paintCanvas(): for y in range(height): for x in range(width): # выбираем случайный цвет для пикселя: clr = getRandomColor() # и окрашиваем его: set(x, y, clr) # ОБНОВЛЯЕМ СЦЕНУ def draw(): x = mouseX y = mouseY clr = get(x, y) s = u"Цвет пикселя (" + str(x) + "," + \ str(y) + ") = " + str(clr) print(s) def getRandomColor(): r = random(256) g = random(256) b = random(256) clr = color(r, g, b) return clr Координаты курсора (точнее – его «горячей точки», которая обычно находится в верхнем левом углу) можно легко узнать по значению системных переменных mouseX и mouseY: 127
Проект Пипетка 2 Исходный код программы находится в папке Pipetka_2. И если к координатам курсора претензий нет, то значения цвета печатаются совершенно непонятно. Поэтому попробуем разложить их на цветные составляющие. Следующие функции возвращают значения соответствующих цветовых составляющих: red(clr : int) → float green(clr : int) → float blue(clr : int) → float alpha(clr : int) → float brightness(clr : int) → float hue(clr : int) → float saturation(clr : int) → float Дополните код программы: # ОБНОВЛЯЕМ СЦЕНУ def draw(): x = mouseX y = mouseY clr = get(x, y) colorMode(RGB, 255) a = alpha(clr) r = red(clr) g = green(clr) b = blue(clr) colorMode(HSB, 360, 100, 100) br = brightness(clr) 128
h = hue(clr) sat = saturation(clr) s = u" Цвет пикселя (" + str(y) + "):" + \ " alpha= " + str(int(a)) " red= " + str(int(r)) + " green= " + str(int(g)) " blue= " + str(int(b)) str(x) + "," + \ + \ \ + \ s2 = " hue= " + str(int(h)) + \ " brightness= " + str(int(br)) + \ " saturation= " + str(int(sat)) print(s) print(s2) print("") Теперь информация о цвете пикселя стала гораздо нагляднее! К сожалению, пиксели такие маленькие, что трудно определить их цвет на глаз, поэтому добавьте в программу небольшое «окошко», которое закрасьте цветом текущего пикселя: left = 270 top = 10 fill(clr) rect(left, top, 32, 32) 129
Полная программа: # ПИПЕТКА 2 # СОЗДАЁМ ОКНО def settings(): size(320, 240) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): paintCanvas() # Окрашиваем пиксели канвы в разные цвета def paintCanvas(): for y in range(height): for x in range(width): # выбираем случайный цвет для пикселя: clr = getRandomColor() # и окрашиваем его: set(x, y, clr) # ОБНОВЛЯЕМ СЦЕНУ def draw(): x = mouseX y = mouseY clr = get(x, y) colorMode(RGB, 255) a = alpha(clr) r = red(clr) g = green(clr) b = blue(clr) colorMode(HSB, 360, 100, 100) br = brightness(clr) h = hue(clr) sat = saturation(clr) s = u" Цвет пикселя (" + str(x) + "," + \ 130
str(y) + "):" + \ " alpha= " + str(int(a)) + \ " red= " + str(int(r)) + \ " green= " + str(int(g)) + \ " blue= " + str(int(b)) s2 = " hue= " + str(int(h)) + \ " brightness= " + str(int(br)) + \ " saturation= " + str(int(sat)) print(s) print(s2) print("") left = 270 top = 10 fill(clr) rect(left, top, 32, 32) def getRandomColor(): r = random(256) g = random(256) b = random(256) clr = color(r, g, b) return clr 131
Занимательные игры с пикселями Случайные точки создают хаотичный «узор», поэтому теперь мы будем окрашивать пиксели по строгим математическим формулам. Они могут быть и довольно простыми, но узоры при этом давать расчудесные! Проект Синусоидные полоски Исходный код программы находится в папке Sine1D. В этом проекте рисунок будут создавать горизонтальные строки одинаково окрашенных пикселей (проще говоря, линии, которые мы рисуем отдельными точками). Цвет пикселей изменяется по высоте клиентской области окна сверху вниз по совсем простой формуле: fc = 255*(1 + sin(y/wl))/2 (1) Поскольку в формуле присутствует синус угла, то и проект называется Синусоидные полоски. Частота горизонтальных волн зависит от длины волны wl, так что вы легко получите разные картинки, изменяя значение этого параметра. Осталось узнать о назначении множителя 255 в формуле (1). Дело в том, что выражение в скобках изменяется в диапазоне 0..2, а делённое на два – в диапазоне 0..1. Цветные составляющие пикселя должны иметь значение от 0 до 255, откуда и вытекает необходимость введения в формулу этого множителя. Можно избавиться от коэффициента 255 в формуле (1): colorMode(RGB, 1) fc = (1 + sin(y/wl))/2 132
Но тогда все цвета задавайте в новом диапазоне! Для изменения длины волны установите ползунок и в функции setup настройте его: # СИНУСОИДНЫЕ ПОЛОСКИ add_library('controlP5') # цвет фона: bg = 160 # СОЗДАЁМ ОКНО def settings(): size(660, 480) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём ползунок: global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 440 # длина: w = 620 cp5.addSlider("wl")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) Саму картинку рисуем, как обычно, в функции draw: # ОБНОВЛЯЕМ СЦЕНУ def draw(): 133
background(bg) # длина волны: global cp5 wl = cp5.getValue("wl") # рисуем волны: for y in range(height - 60): # цвет очередной полоски: fc = 255 * (1 + sin(y / wl)) / 2.0 #fc = (1 + sin(y/wl)) / 2.0 # серые полоски: #clr = color(fc) # красные полоски: #clr = color(fc, 0, 0) # синие полоски: clr = color(0, 0, fc) for x in range(width): # окрашиваем пиксель: set(x, y, clr) Здесь важно ограничить область рисования, чтобы показания движка были хорошо видны. А картинка у нас получилась славная, но - чёрнобелая. → Впрочем, мы без труда окрасим волны в нужный цвет, слегка изменив значения параметров в функции color: 134
# красные полоски: clr = color(fc, 0, 0) # синие полоски: clr = color(0, 0, fc) Цветные волны ещё лучше! 135
Полная программа: # СИНУСОИДНЫЕ ПОЛОСКИ add_library('controlP5') # цвет фона: bg = 160 # СОЗДАЁМ ОКНО def settings(): size(660, 480) 136
# ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём ползунок: global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 440 # длина: w = 620 cp5.addSlider("wl")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(bg) # длина волны: global cp5 wl = cp5.getValue("wl") # рисуем волны: for y in range(height - 60): # цвет очередной полоски: fc = 255 * (1 + sin(y / wl)) / 2.0 #fc = (1 + sin(y/wl)) / 2.0 # серые полоски: #clr = color(fc) # красные полоски: #clr = color(fc, 0, 0) # синие полоски: clr = color(0, 0, fc) for x in range(width): # окрашиваем пиксель: set(x, y, clr) 137
Проект Синусоидные полоски с клавишами Исходный код программы находится в папке Sine1D2. Изменяя код, мы легко получим нужный цвет волн, а пользователь программы такой возможности лишён. Однако у него всегда под рукой клавиатура, вот пусть он и нажимает клавиши 1, 2, 3, а программа самостоятельно перекрасит волны. Не каждый пользователь догадается об этом, поэтому снабдим программу строкой-подсказкой: # информация для пользователя: info = u"1 - серый цвет 2 - красный 3 - синий" Номер цвета обозначим соответствующим числом. Сначала это будет 1, а цвет волн – серый: # номер цвета: nColor = 1 Размер шрифта по умолчанию слишком мелкий, и мы его увеличим: # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # размер шрифта: textSize(18) Когда пользователь нажимает клавишу, автоматически вызывается встроенная функция keyPressed. По значению символьной переменной key мы узнаём нажатую клавишу и изменяем значение переменной nColor: 138
# НАЖИМАЕМ КЛАВИШУ def keyPressed(): global nColor if key == '1': nColor = 1 elif key == '2': nColor = 2 elif key == '3': nColor = 3 else: nColor = 1 В функции draw по значению переменной nColor устанавливаем цвет волн в функции color: # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(bg) # длина волны: global cp5, nColor wl = cp5.getValue("wl") # рисуем волны: for y in range(height - 60): # цвет очередной полоски: fc = 255 * (1 + sin(y / wl)) / 2.0 if nColor == 1: # серые полоски: clr = color(fc) elif nColor == 2: # красные полоски: clr = color(fc, 0, 0) else: # синие полоски: clr = color(0, 0, fc) for x in range(width): # окрашиваем пиксель: set(x, y, clr) 139
# печатаем сообщение: fill(0) text(info, 10, 20) Запускайте программу и лёгким движением руки изменяйте цвет волн: Программа полностью: # СИНУСОИДНЫЕ ПОЛОСКИ 2 add_library('controlP5') 140
# цвет фона: bg = 160 # информация для пользователя: info = u"1 - серый цвет 2 - красный 3 - синий" # номер цвета: nColor = 1 # СОЗДАЁМ ОКНО def settings(): size(660, 480) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # размер шрифта: textSize(18) # создаём ползунок: global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 440 # длина: w = 620 cp5.addSlider("wl")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) # НАЖИМАЕМ КЛАВИШУ def keyPressed(): global nColor if key == '1': nColor = 1 elif key == '2': nColor = 2 elif key == '3': nColor = 3 else: 141
nColor = 1 # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(bg) # длина волны: global cp5, nColor wl = cp5.getValue("wl") # рисуем волны: for y in range(height - 60): # цвет очередной полоски: fc = 255 * (1 + sin(y / wl)) / 2.0 if nColor == 1: # серые полоски: clr = color(fc) elif nColor == 2: # красные полоски: clr = color(fc, 0, 0) else: # синие полоски: clr = color(0, 0, fc) for x in range(width): # окрашиваем пиксель: set(x, y, clr) # печатаем сообщение: fill(0) text(info, 10, 20) Проект Анимированные синусоидные полоски Исходный код программы находится в папке Sine1D3. 142
Неподвижные волны очень скучны, даже цветные. Но их легко привести в движение, то есть анимировать. Объявите переменную t – условное время: # время: t = 0.0 Чтобы оживить волны, добавьте параметр t в функцию, задающую цвет волн: # цвет очередной полоски: fc = 255 * (1 + sin(y / wl + t)) / 2.0 В конце функции draw изменяйте значение времени: # изменяем время: #t += 0.16 t -= 0.16 Чем больше абсолютная величина изменения времени, тем быстрее перемещаются волны. Если приращение положительное, то волны бегут вверх, если отрицательное – вниз. К сожалению, в книге анимацию не покажешь… Полная программа: # Анимированные синусоидные полоски add_library('controlP5') # цвет фона: bg = 160 143
# информация для пользователя: info = u"1 - серый цвет 2 - красный 3 - синий" # номер цвета: nColor = 1 # время: t = 0.0 # СОЗДАЁМ ОКНО def settings(): size(660, 480) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # размер шрифта: textSize(18) # создаём ползунок: global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 440 # длина: w = 620 cp5.addSlider("wl")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) # НАЖИМАЕМ КЛАВИШУ def keyPressed(): global nColor if key == '1': nColor = 1 elif key == '2': nColor = 2 elif key == '3': 144
nColor = 3 else: nColor = 1 # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(bg) # длина волны: global cp5, nColor, t wl = cp5.getValue("wl") # рисуем волны: for y in range(height - 60): # цвет очередной полоски: fc = 255 * (1 + sin(y / wl + t)) / 2.0 if nColor == 1: # серые полоски: clr = color(fc) elif nColor == 2: # красные полоски: clr = color(fc, 0, 0) else: # синие полоски: clr = color(0, 0, fc) for x in range(width): # окрашиваем пиксель: set(x, y, clr) # печатаем сообщение: fill(0) text(info, 10, 20) # изменяем время: #t += 0.16 t -= 0.16 145
Проект Двойная волна Исходный код программы находится в папке Sine2D. А теперь давайте пустим две волны – вертикальную и горизонтальную. Добавьте в формулу для вычисления цвета пикселя ещё один синус: fc = 255*(1+sin(x/wX)*sin(y/wY))/2 Длину горизонтальной волны мы поместим в переменную wX, а длину вертикальной – в переменную wY. А для изменения значения этих переменных мы установим 2 ползунка. Остальная часть программы мало отличается от предыдущей: # ДВОЙНАЯ ВОЛНА add_library('controlP5') # цвет фона: bg = 160 # СОЗДАЁМ ОКНО def settings(): size(660, 530) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём ползунки: global cp5 cp5 = ControlP5(this) 146
# x y # w координаты: = 20 = 460 длина: = 620 cp5.addSlider("wX")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) cp5.addSlider("wY")\ .setPosition(x, y + 30)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(bg) # длина волны: global cp5 wX = cp5.getValue("wX") wY = cp5.getValue("wY") # рисуем волны: for y in range(height - 60 - 50): for x in range(width): # цвет очередного пикселя: fc = 255 * (1 + sin(x / wX) * sin(y / wY)) / 2.0 clr = color(0, fc, 100) # окрашиваем пиксель: set(x, y, clr) Получилась интересная сетчатая структура: 147
Поработайте ползунками, чтобы получить ещё более интересные картинки! 148
149
150
Проект Двойная волна HSB Исходный код программы находится в папке Sine2DHSB. 151
Картинка станет ещё интереснее, если волны превратить в кружочки. Для этого перейдите на цветовую модель HSB: # ДВОЙНАЯ ВОЛНА HSB add_library('controlP5') # цвет фона: bg = 160 # СОЗДАЁМ ОКНО def settings(): size(660, 530) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): colorMode(HSB, 360, 100, 100) # создаём ползунки: global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = height - 80 # длина: w = 620 cp5.addSlider("wX")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 100)\ .setValue(10) cp5.addSlider("wY")\ .setPosition(x, y + 30)\ .setSize(w, 20)\ .setRange(1, 100)\ .setValue(10) # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(bg) 152
# длина волны: global cp5 wX = cp5.getValue("wX") wY = cp5.getValue("wY") # рисуем волны: for y in range(height - 60 - 50): for x in range(width): # цвет очередного пикселя: fc = 255 * (1 + sin(x / wX) * sin(y / wY)) / 2.0 clr = color(fc, 100, 100, 100) # окрашиваем пиксель: set(x, y, clr) 153
Проект Лунки Исходный код программы находится в папке Lunki. В этом проекте мы так изменим формулу для вычисления цвета пикселей так, чтобы в ней участвовала абсолютная величина синусов: fc = 255 * (1 + abs(sin(x / wX)) * abs(sin(y / wY))) / 2.0 Это приведёт к тому, что все лунки получат одинаковый цвет, в отличие от проекта Двойная волна, где лунки окрашивались в разные цвета: # ЛУНКИ add_library('controlP5') # цвет фона: bg = 160 # СОЗДАЁМ ОКНО def settings(): size(660, 530) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём ползунки: global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 460 # длина: 154
w = 620 cp5.addSlider("wX")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) cp5.addSlider("wY")\ .setPosition(x, y + 30)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(bg) # длина волны: global cp5 wX = cp5.getValue("wX") wY = cp5.getValue("wY") # рисуем волны: for y in range(height - 60 - 50): for x in range(width): # цвет очередного пикселя: fc = 255 * (1 + abs(sin(x / wX)) * abs(sin(y / wY))) / 2.0 clr = color(fc, 0, fc) # окрашиваем пиксель: set(x, y, clr) А картинки получились интересные: 155
156
157
Проект Лунки HSB Исходный код программы находится в папке LunkiHSB. 158
Перекрашиваем лунки в цветовом режиме HSB: # ЛУНКИ HSB add_library('controlP5') # цвет фона: bg = 160 # СОЗДАЁМ ОКНО def settings(): size(660, 530) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): colorMode(HSB, 360, 100, 100) # создаём ползунки: global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 460 # длина: w = 620 cp5.addSlider("wX")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 100)\ .setValue(10) cp5.addSlider("wY")\ .setPosition(x, y + 30)\ .setSize(w, 20)\ .setRange(1, 100)\ .setValue(10) # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(bg) # длина волны: 159
global cp5 wX = cp5.getValue("wX") wY = cp5.getValue("wY") # рисуем волны: for y in range(height - 60 - 50): for x in range(width): # цвет очередного пикселя: fc = 360 * (1 + abs(sin(x / wX)) * abs(sin(y / wY))) / 2.0 clr = color(fc, 100, 100) # окрашиваем пиксель: set(x, y, clr) 160
Проект Радиальные волны Исходный код программы находится в папке PolarSine. Бросая в воду камешки, смотри на круги, ими образуемые; иначе такое бросание будет пустою забавою. Козьма Прутков Если вы бросали камни в воду, то, конечно, обратили внимание на то, что волны, которые разбегаются от места падения камня, имеют вовсе не форму прямых линий или лунок - они совершенно круглые. Чтобы загнуть волны в дугу, нам придётся перейти от прямоугольных координат к полярным, что мы и сделаем с помощью формулы: rad = sqrt((x - CX) * (x - CX) + (y - CY) * (y - CY)) / wl Сама же формула для цвета пикселя останется без изменений: fc = 255*(1 + sin(rad))/2 clr = color(0, 0, fc) Исходный код программы очень похож на наши прежние проекты: 161
# РАДИАЛЬНЫЕ ВОЛНЫ add_library('controlP5') # цвет фона: bg = 160 # СОЗДАЁМ ОКНО def settings(): size(660, 530) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём ползунок: global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 490 # длина: w = 620 cp5.addSlider("wl")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(bg) # координаты центра волн: CX = width / 2.0 CY = height / 2.0 # длина волны: global cp5 wl = cp5.getValue("wl") # рисуем волны: for y in range(height - 60): for x in range(width): 162
# цвет очередного пикселя: rad = sqrt((x - CX) * (x - CX) + (y - CY) * (y - CY)) / wl fc = 255 * (1 + sin(rad)) / 2.0 clr = color(0, 0, fc) # окрашиваем пиксель: set(x, y, clr) А волны получились – как настоящие! 163
Проект Радиальные волны с клавишами Исходный код программы находится в папке PolarSine2. Воспользуемся тем же приёмом выбора цвета, что и в проекте Синусоидные полоски с клавишами: # РАДИАЛЬНЫЕ ВОЛНЫ С КЛАВИШАМИ add_library('controlP5') # цвет фона: bg = 160 # информация для пользователя: info = u"1 - серый цвет 2 - красный 3 - синий" # номер цвета: nColor = 1 # СОЗДАЁМ ОКНО def settings(): size(660, 530) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # размер шрифта: textSize(18) # создаём ползунок: global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 490 # длина: w = 620 164
cp5.addSlider("wl")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) # НАЖИМАЕМ КЛАВИШУ def keyPressed(): global nColor if key == '1': nColor = 1 elif key == '2': nColor = 2 elif key == '3': nColor = 3 else: nColor = 1 # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(bg) # координаты центра волн: CX = width / 2.0 CY = height / 2.0 # длина волны: global cp5 wl = cp5.getValue("wl") # рисуем волны: for y in range(height - 60): for x in range(width): # цвет очередного пикселя: rad = sqrt((x - CX) * (x - CX) + (y - CY) * (y - CY)) / wl fc = 255 * (1 + sin(rad)) / 2.0 if nColor == 1: # серые полоски: clr = color(fc) elif nColor == 2: # красные полоски: clr = color(fc, 0, 0) else: # синие полоски: 165
clr = color(0, 0, fc) # окрашиваем пиксель: set(x, y, clr) # печатаем сообщение: fill(255) text(info, 10, 20) 166
Проект Анимированные радиальные волны Исходный код программы находится в папке PolarSine3. Как и в проекте Анимированные синусоидные полоски, заставим волны двигаться и плескаться! Сделать это совсем просто: # АНИМИРОВАННЫЕ РАДИАЛЬНЫЕ ВОЛНЫ С КЛАВИШАМИ add_library('controlP5') # цвет фона: bg = 160 # информация для пользователя: info = u"1 - серый цвет 2 - красный 3 - синий" # номер цвета: nColor = 1 # время: t = 0.0 # СОЗДАЁМ ОКНО def settings(): size(660, 530) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # размер шрифта: textSize(18) # создаём ползунок: global cp5 cp5 = ControlP5(this) # координаты: x = 20 167
y = 490 # длина: w = 620 cp5.addSlider("wl")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) # НАЖИМАЕМ КЛАВИШУ def keyPressed(): global nColor if key == '1': nColor = 1 elif key == '2': nColor = 2 elif key == '3': nColor = 3 else: nColor = 1 # ОБНОВЛЯЕМ СЦЕНУ def draw(): global cp5, t background(bg) # координаты центра волн: CX = width / 2.0 CY = height / 2.0 # длина волны: wl = cp5.getValue("wl") # рисуем волны: for y in range(height - 60): for x in range(width): # цвет очередного пикселя: rad = sqrt((x - CX) * (x - CX) + (y - CY) * (y - CY)) / wl fc = 255 * (1 + sin(rad + t)) / 2.0 if nColor == 1: # серые полоски: clr = color(fc) elif nColor == 2: # красные полоски: clr = color(fc, 0, 0) 168
else: # синие полоски: clr = color(0, 0, fc) # окрашиваем пиксель: set(x, y, clr) # печатаем сообщение: fill(255) text(info, 10, 20) t += 0.9 169
Проект Радиальные волны HSB Исходный код программы находится в папке PolarSine4. Измените способ закрашивания пикселей: # РАДИАЛЬНЫЕ ВОЛНЫ HSB add_library('controlP5') # цвет фона: bg = 160 # СОЗДАЁМ ОКНО def settings(): size(660, 530) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): colorMode(HSB, 360, 100, 100) # создаём ползунок: global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 490 # длина: w = 620 cp5.addSlider("wl")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(bg) 170
# координаты центра волн: CX = width / 2.0 CY = height / 2.0 # длина волны: global cp5 wl = cp5.getValue("wl") # рисуем волны: for y in range(height - 60): for x in range(width): # цвет очередного пикселя: rad = sqrt((x - CX) * (x - CX) + (y - CY) * (y - CY)) / wl fc = 360 * (1 + sin(rad)) / 2.0 clr = color(fc, 100, 100) # окрашиваем пиксель: set(x, y, clr) 171
Проект Анимированные радиальные волны HSB Исходный код программы находится в папке PolarSine5. Опять анимируем волны: # АНИМИРОВАННЫЕ РАДИАЛЬНЫЕ ВОЛНЫ HSB add_library('controlP5') # цвет фона: bg = 160 # время: time = 0.0 # СОЗДАЁМ ОКНО def settings(): size(660, 530) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): colorMode(HSB, 360, 100, 100) # создаём ползунок: global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 490 # длина: w = 620 cp5.addSlider("wl")\ 172
.setPosition(x, y)\ .setSize(w, 20)\ .setRange(1, 20)\ .setValue(10) # ОБНОВЛЯЕМ СЦЕНУ def draw(): global cp5, time background(bg) # координаты центра волн: CX = width / 2.0 CY = height / 2.0 # длина волны: wl = cp5.getValue("wl") # рисуем волны: for y in range(height - 60): for x in range(width): # цвет очередного пикселя: rad = sqrt((x - CX) * (x - CX) + (y - CY) * (y - CY)) / wl fc = 360 * (1 + sin(rad + time)) / 2.0 clr = color(fc, 100, 100) # окрашиваем пиксель: set(x, y, clr) time += 0.9 При больших значениях длин волн вы увидите картинки, подобные той, что и в предыдущем проекте. А вот при маленьких значениях возникает интересный муар: 173
174
175
Проект Ромбы Исходный код программы находится в папке Rhombes2. Применив для пикселестроения более хитроумную формулу, мы получим великолепный ромбический узор, от которого глаза трудно оторвать: # ПИКСЕЛЬНЫЕ РОМБЫ add_library('controlP5') # цвет фона: bg = 160 # СОЗДАЁМ ОКНО def settings(): size(660, 530) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём ползунок: global cp5 cp5 = ControlP5(this) # координаты: x = 20 y = 490 # длина: w = 620 cp5.addSlider("wl")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(10, 200)\ .setValue(60) def draw(): # длина волны: 176
global cp5 wl = cp5.getValue("wl") # рисуем ромбы: for y in range(height - 60): for x in range(width): # цвет очередного пикселя: fc = ((255*2*(abs(x % wl -wl/2) + abs(y % wl -wl/2) )/wl ) % 256) clr = color(fc, 0, fc) #clr = color(0, fc, fc) # окрашиваем его: set(x, y, clr) 177
Проект Синусоиды Винни-Пуха Исходный код программы находится в папке SineThePooh. По никому неведомой причине неизвестный автор этих синусоид посвятил их нашему любимому Винни, прославившемуся своим пыхтеньем. В этом примере 9 параметров, входящих в формулу для вычисления цвета пикселя, выбираются случайно в функции setup: a = [0.0] * 10 for i in range(10): a[i] = random(2.0) + 1 Это значит, что при каждом новом запуске программы вы будете получать новую картинку. Вот где простор для поиска новых узоров! С другой стороны, случайный поиск не очень эффективен. Может быть, стоит попробовать подбирать коэффициенты вручную? Формула для вычисления цвета пикселей просто ужасная, но компьютер мы ею не напугаем: 178
# рисуем: for y in range(height - 60 - 50): for x in range(width): # цвет очередного пикселя: fc = 256.0 * (1.0 + (a[4] * sin(a[0] * sin(a[6] * x / wX) + a[1] * cos(a[7] * y / wY)) + a[5] * cos(a[2] * cos(a[8] * x / wX) + a[3] * sin(a[9] * y / wY)))) / 2.0 % 256 # жёлтая: clr = color(fc,fc,0) # синяя: #clr = color(0, 0, fc) # красная: #clr = color(fc,0,0) # окрашиваем пиксель: set(x, y, clr) Вы можете каждый цикл рисовать новый узор, если в начале функции draw напишете строчки: for i in range(10): a[i] = random(2.0) + 1 Однако учитывайте, что программа на Питоне работает не очень быстро. Иногда получаются красивые картинки: 179
180
181
Программа полностью: # СИНУСОИДЫ ВИННИ-ПУХА add_library('controlP5') 182
global cp5 # цвет фона: bg = 160 a = [0.0] * 10 # СОЗДАЁМ ОКНО def settings(): size(660, 530) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём ползунки: global cp5 cp5 = ControlP5(this) # координаты: x = 60 y = 440 # длина: w = 580 cp5.addSlider("wX")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(10, 100)\ .setValue(20) cp5.addSlider("wY")\ .setPosition(x, y + 30)\ .setSize(w, 20)\ .setRange(10, 100)\ .setValue(20) for i in range(10): a[i] = random(2.0) + 1 frameRate(5) # ОБНОВЛЯЕМ СЦЕНУ def draw(): 183
background(bg) wX = cp5.getValue("wX") wY = cp5.getValue("wY") # рисуем: for y in range(height - 60 - 50): for x in range(width): # цвет очередного пикселя: fc = 256.0 * (1.0 + (a[4] * sin(a[0] * sin(a[6] * x / wX) + a[1] * cos(a[7] * y / wY)) + a[5] * cos(a[2] * cos(a[8] * x / wX) + a[3] * sin(a[9] * y / wY)))) / 2.0 % 256 # жёлтая: clr = color(fc,fc,0) # синяя: #clr = color(0, 0, fc) # красная: #clr = color(fc,0,0) # окрашиваем пиксель: set(x, y, clr) Проект Туманность Исходный код программы находится в папке Nebula. Аналогично, выбирая случайные параметры для формулы в этом примере, вы получите причудливые линии, похожие на туманности: # ТУМАННОСТЬ # список коэффициентов: a = [0.0] * 8 184
# СОЗДАЁМ ОКНО def settings(): #size(400, 400) size(600, 600) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): #randomSeed(4) #randomSeed(8) for i in range(1, 6+1): a[i] = PI * (1 - 2 * random(1000) / 1000.0) background(0) # ОБНОВЛЯЕМ СЦЕНУ def draw(): CX = width / 2 CY = height / 2 for i in range(100): # рисуем: x = sin(a[1] * a[6]) - cos(a[2] * a[5]) y = sin(a[3] * a[5]) - cos(a[4] * a[6]) # цвет очередного пикселя: #clr = color(128 * (x + y), 128 * (x + y), 255, 33) clr = color(random(128 * (x + y)), random(128 * (x + y)), random(255), 33) # окрашиваем пиксель: set(floor(CX + x * 130), floor(CY + y * 130), clr) a[5] = x a[6] = y Здесь вы можете по-разному окрашивать пиксели, чтобы получить интересные картинки! 185
186
187
188
189
190
191
192
Проект Заливаем! Исходный код программы находится в папке FloodFill. В компьютерной графике очень часто бывает необходимо закрасить замкнутую фигуру со сложной границей: В некоторых системах программирования на этот случай имеется встроенная функция Flood или FloodFill (англ. название, которое можно перевести как наводнение). У нас такой функции нет, поэтому мы её напишем сами. Но, прежде всего, мы должны нарисовать на экране изрядную загогулину, чтобы посмотреть, как наша функция заливки справится со своей задачей. 193
Поскольку нам нужна высокохудожественная кривая, то мы сразу же откажемся от вычерчивания красивых и гладких, но неоригинальных геометрических примитивов, а нарисуем всё от руки. Известно, что все программисты рисуют как курица лапой, а это как раз то, что нам и нужно! Конечно, ещё лучше было бы рисовать пальцем, но не у всех имеется сенсорный экран, поэтому нашим художественным инструментом будет банальная мышка, что ничуть не уменьшит наш художественный потенциал. Так как закрашивать каждую точку мышиного пути несколько затруднительно, то мы будем соединять отрезками прямых последнюю запомненную точку пути и актуальную: def mouseDragged(): line(pmouseX, pmouseY, mouseX, mouseY) Функция mouseDragged вызывается при перемещении мышки с нажатой кнопкой. А чтобы она своевременно вызывалась, нам необходимо присутствие в программе функции draw, которая ничего не делает: def draw(): pass В функциях settings и setup мы создаём окно заданных размеров, окрашиваем фон в белый цвет и задаём свойства линии: # Заливаем! # размеры окна: GWWIDTH = 480 GWHEIGHT = 320 def settings(): 194
size(GWWIDTH, GWHEIGHT) def setup(): stroke(0) strokeWeight(1) background(255) Теперь вы можете рисовать на экране всякие кривые линии: В этой версии программы можно нарисовать единственную кривую. Этого вполне достаточно для наблюдения за процессом заливки. Чтобы закрасить замкнутую область, кликните внутри неё мышкой: def mouseClicked(): fill(mouseX, mouseY, getRandomColor()) def getRandomColor(): r = random(256) g = random(256) 195
b = random(256) clr = color(r, g, b) return clr Тогда функция fill получит координаты мышки и случайный цвет для заливки выбранной области. Процедуру заливки обычно выполняют рекурсивно, но вариант со стеком и быстрее, и понятнее. Роль стека в Питоне успешно выполняет список: # ЗАЛИВАЕМ ОБЛАСТЬ def fill(x, y, newColor): # стек для хранения координат точек: stack =[] Сразу запоминаем в стеке координаты мышки: # запоминаем координаты первой точки: stack.append(x) stack.append(y) Также нам нужен цвет под курсором мышки: # старый цвет: oldColor = get(x, y) Все пиксели такого цвета внутри области мы закрасим новым цветом newColor. Пока в стеке есть запомненные координаты пикселей, мы извлекаем их и сравниваем цвет очередного пикселя с новым цветом. Если они не совпадают, то перекрашиваем пиксель в новый цвет: 196
while (len(stack) > 0): y = stack.pop() x = stack.pop() if (get(x, y) != newColor): set(x, y, newColor) Чтобы лучше рассмотреть действие алгоритма закрашивания, я ввёл небольшую задержку: if random(100) > 90: delay(1) Вы можете её убрать или, наоборот, удлинить. Дальше мы смотрим из текущего пикселя на все 4 стороны и сравниваем цвет соседних пикселей со старым цветом. Если они совпадают, то запоминаем координаты таких пикселей в стеке, откуда они потом будут извлечены и перекрашены. Не забываем проверять координаты пикселей, чтобы не покинуть пределы окна приложения: if (x > 0 and get(x - 1, y) == oldColor): stack.append(x - 1) stack.append(y) if (x < GWWIDTH - 1 and get(x + 1, y) == oldColor): stack.append(x + 1) stack.append(y) if (y > 0 and get(x, y - 1) == oldColor): stack.append(x) stack.append(y - 1) if (y < GWHEIGHT - 1 and get(x, y + 1) == oldColor): stack.append(x) stack.append(y + 1) 197
Запускаем программу, рисуем загогулину, а потом щёлкаем внутри неё мышкой – фигура начинает закрашиваться: Через некоторое время все пиксели замкнутой области аккуратно закрашены: 198
Щёлкаем по фону, который также заливается случайным цветом: Повторными щелчками можно перекрасить и фигуру, и фон: 199
По умолчанию линии на экране получаются сглаженными, поэтому не все пиксели внутри области имеют белый цвет. Такие пиксели не закрашиваются, и вы можете видеть на границах линии серые пиксели. Их можно удалить, если задать режим рисования noSmooth: def setup(): stroke(0) strokeWeight(1) background(255) noSmooth() Теперь линии получаются «шершавыми», но заливка выглядит более аккуратно: 200
Прямые линии Через две точки можно провести прямую, поэтому теперь будем рисовать линии. Для вычерчивания линий Процессинг имеет функцию line: line(x1: float, y1: float, x2: float, y2: float) Точка с координатами (x1, y1) задаёт начало прямой, а точка (x2, y2) – её конец, то есть будет правильнее сказать, что эта функция вычерчивают отрезок прямой: 201
Толщина линии по умолчанию равняется одному пикселю, но её можно увеличить с помощью функции strokeWeight, а её цвет установить с помощью функции stroke. Для того чтобы все линии на экране были нарисованы аккуратно, нужно включить режим сглаживания функцией smooth. Проект Случайные линии Исходный код программы находится в папке Random_lines. Программа будет совершенно бесхитростной, поскольку рисовать прямые линии очень просто. Нам потребуется всего одна переменная – для хранения толщины линий: # ПРОГРАММА ДЛЯ ВЫЧЕРЧИВАНИЯ # СЛУЧАЙНЫХ ЦВЕТНЫХ ЛИНИЙ # толщина линий: penWidth = 2. #20. def setup(): size(480, 320) background(0) # толщина линий: strokeWeight(penWidth) #smooth() #noSmooth() #strokeCap(SQUARE) #strokeCap(PROJECT) frameRate(10) 202
Нам нужны координаты двух точек, которые мы затем и соединим прямой линией, вызвав функцию line с соответствующими параметрами: def draw(): # задаём случайные координаты начала и конца линии --> # координаты первой точки: x1 = random(width - penWidth / 2) y1 = random(height - penWidth / 2) # координаты второй точки: x2 = random(width - penWidth / 2) y2 = random(height - penWidth / 2) # выбираем случайный цвет линии: clr = random_color() stroke(clr) # проводим линию: line(x1, y1, x2, y2) Запускаем программу – и на чёрном фоне появляются всё новые и новые линии: 203
Увеличьте толщину линий до 20. Теперь линии напоминают прямоугольники с закруглёнными углами: penWidth = 20. Если толщина линий большая, то они больше напоминают прямоугольники с закруглёнными углами. За форму концов линий отвечает функция strokeCap: strokeCap(cap: int) Аргументами функций могут быть такие значения: • SQUARE – прямоугольные концы 204
• PROJECT – как режим SQUARE, но концы линий удлиняются на половину толщины линии • ROUND – скруглённые концы, режим по умолчанию Добавьте в функцию setup строчку strokeCap(SQUARE) и запустите приложение. Концы линий стали «обрубленными»: Проект Функция strokeJoin Исходный код программы находится в папке Function_strokeJoin. 205
Ломаные линии – это несколько последовательно соединённых между собой отрезков. Если линии толстые, то места их соединений могут выглядеть по-разному. За стиль соединения линий отвечает функция strokeJoin: strokeJoin(join: int) Параметр join может принимать такие значения: MITER (режим по умолчанию), BEVEL, ROUND В режиме по умолчанию strokeCap(ROUND), то все стили действуют одинаково: Режим strokeCap(SQUARE) лучше вообще не использовать для толстых линий. А в режиме strokeCap(PROJECT) все стили соединяют толстые линии не очень аккуратно: 206
# ПРОГРАММА ДЛЯ ВЫЧЕРЧИВАНИЯ # СЛУЧАЙНЫХ ЛОМАНЫХ ЛИНИЙ # толщина линий: penWidth = 20. def setup(): size(480, 320) global x1, y1 x1 = y1 = 0 background(255) # толщина линий: strokeWeight(penWidth) #strokeCap(SQUARE) #strokeCap(PROJECT) strokeCap(ROUND) strokeJoin(MITER) #strokeJoin(BEVEL) #strokeJoin(ROUND) frameRate(10) def draw(): 207
global x1, y1 # координаты второй точки: x2 = random(width - penWidth / 2) y2 = random(height - penWidth / 2) # выбираем случайный цвет линии: clr = random_color() stroke(clr); stroke(0, 100) # проводим линию: line(x1, y1, x2, y2) # запоминаем координаты конца линии: x1 = x2 y1 = y2 # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(32,255), random(32, 255), random(32, 255)) return clr Проект Функция strokeJoin 2 Исходный код программы находится в папке Function_strokeJoin2. Итак, при соединении отдельных линий функция strokeJoin не действует, поэтому лучше вычерчивать ломаные линии иначе: # ПРОГРАММА ДЛЯ ВЫЧЕРЧИВАНИЯ # СЛУЧАЙНЫХ ЛОМАНЫХ ЛИНИЙ # толщина линий: penWidth = 10. def setup(): 208
size(480, 320) global x1, y1 x1 = y1 = 0 background(255) # толщина линий: strokeWeight(penWidth) noFill() #strokeCap(SQUARE) strokeCap(PROJECT) strokeJoin(MITER) #strokeJoin(BEVEL) #strokeJoin(ROUND) noFill() stroke('#FF0000') create_line() def create_line(): beginShape() for _ in range(21): # координаты точки: x = random(width - penWidth*4) + penWidth y = random(height - penWidth*4) + penWidth vertex(x, y) endShape() В этом случае соединения линий получаются аккуратными во всех режимах: Что же касается вычерчивания фигур по точкам, то этот способ мы изучим дальше. 209
Проект Цветные линии Исходный код программы находится в папке ColorLines. Беспорядочно разбросанным по экрану отрезкам линий глаз не радуется. Однако есть много способов с помощью одних только прямых линий нарисовать узорную картину: # ПРОГРАММА "ЦВЕТНЫЕ ЛИНИИ" # толщина линий: penWidth = 2. def setup(): size(640, 480) background(0) # толщина линий: strokeWeight(penWidth) draw_lines() def draw_lines(): # отношение высоты окна к ширине: ratio = float(height) / width print(ratio) #print(width) a = 120. x = 0. # Чертим цветные линии while (x <= width): stroke(255, 0, 0, a) line(0, x * ratio, width - x, 0) stroke(255, 255, 0, a) line(0, (width - x) * ratio, width - x, width * ratio) stroke(0, 0, 255, a) line(width - x, 0 * ratio, width, (width - x) * ratio) stroke(0, 255, 0, a) line(width - x, width * ratio, width, x * ratio) 210
x += 10 Чтобы каждый из четырёх наборов линий имел свой цвет, перед рисованием каждой прямой мы задаём её цвет с помощью функции stroke. Как изменяются координаты начала и конца линий, хорошо видно на рисунке – они скользят по границам клиентской области окна с заданным шагом 10: x += 10 Изменяя этот параметр, а также цвет линий, вы сможете получить и другие узоры. 211
Проект Градиентная заливка Исходный код программы находится в папке Gradient_fill. Если чертить горизонтальные линии вплотную друг к другу, то можно получить градиентную заливку, в которой один цвет плавно переходит в другой. Этот приём часто применяется в заставках, которые появляются на экране при установке программ, а также в компьютерной графике для раскрашивания кнопок, баннеров и других объектов. Хорошим примером натуральной «градиентной заливки» может служить безоблачное полуденное или закатное небо! Всего наша программа сможет выполнить 6 различных градиентных заливок, которые пользователь сможет выбирать по своему желанию, нажимая кнопки 1..6: def draw(): pass # НАЖИМАЕМ КНОПКУ def keyPressed(): if key == '1': CB() if key == '2': CG() if key == '3': MB() if key == '4': MR() 212
if key == '5': YG() if key == '6': YR() Функция draw, как вы видите, ничего не делает, но без неё программа не сможет обрабатывать события клавиатуры. При нажатии цифровых клавиш вызываются функции для рисования градиента. Сразу после запуска программы вызывается функция CB: # ПРОГРАММА ДЛЯ ГРАДИЕНТНОЙ # ЗАЛИВКИ ПРЯМОУГОЛЬНИКА # толщина линий: penWidth = 1. def setup(): size(480, 320) background(0) # толщина линий: strokeWeight(penWidth) CB() Она рисует градиентный переход от цианового (сине-зелёного) цвета к синему: # переход от циана к синему - Cyan To Blue def CB(): b = 255 r = 0 for i in range(height): # вычисляем интенсивность зелёной составляющей цвета: g = 255.0 * (1. - float(i) / height) # задаём цвет линии: clr = color(r, g, b) 213
stroke(clr) # проводим горизонтальную линию: line(0, i, width, i) Переход от циана к синему При переходе от циана к синему цвету синяя составляющая текущего цвета всегда равна 255 (максимальное значение), красная – нулю (отсутствует вообще), а зелёная составляющая постепенно уменьшает своё значение при перемещении линий сверху вниз от 255 до 0. Именно благодаря этому изменению и возникает плавный переход цвета по вертикали. Так как по горизонтали цвет остается без изменений, то достаточно провести горизонтальную линию текущего цвета. Для создания градиентного перехода от лилового цвета к красному мы поступаем аналогично. От предыдущего градиента этот градиент отличается только составляющими цвета: # переход от лилового к красному - Magenta To Red def MR(): r = 255 214
g = 0 for i in range(height): # вычисляем интенсивность синей составляющей цвета: b = 255. * (1. - float(i) / height); # задаём цвет линии: clr = color(r, g, b) stroke(clr) # проводим горизонтальную линию: line(0, i, width, i) Переход от лилового цвета к красному Остальные градиенты отличаются от рассмотренных нами только изменением цветовых составляющих. Как видите, создать прямоугольную градиентную заливку совсем несложно, а результат получается впечатляющий! Запускайте программу, жмите на кнопки и наслаждайтесь цветовыми переходами! 215
Проект Случайный градиент Исходный код программы находится в папке Random_gradient. Очень легко получить случайный градиент, если для верхней и нижней границы канвы выбирать случайный цвет. Программа упрощается, но нужно внести в неё несколько добавлений и исправлений. На экране мы покажем подсказку: def printMessage() { fill(0) noStroke(); text(u"Нажмите ПРОБЕЛ", 10, 30) Можно нажимать любую клавишу, но нам удобнее пользоваться клавишей ПРОБЕЛ, потому что она всегда под рукой. Нажали ПРОБЕЛ – вызвали функцию createRandomGradient: # НАЖИМАЕМ КЛАВИШУ ПРОБЕЛ def keyPressed(): if (key == ' '): createRandomGradient() printMessage() А создать случайный градиент проще простого: # СОЗДАЁМ СЛУЧАЙНЫЙ ГРАДИЕНТ 216
def createRandomGradient(): # случайный цвет вверху: clrTop = random_color() # случайный цвет внизу: clrBottom = random_color() for y in range(height): amt = map(y, 0, height, 1, 0) clr = lerpColor(clrTop,clrBottom,amt) stroke(clr) line(0, y, width, y) Здесь важно правильно выбрать промежуточные цвета. Для этого мы пользуемся функцией lerpColor. Не всегда случайные градиенты получаются красивыми, но, нажимая клавишу ПРОБЕЛ, вы дождётесь и совсем неплохих картинок: 217
Программа полностью: # ПРОСТЫЕ ВЕРТИКАЛЬНЫЕ ГРАДИЕНТЫ def setup(): # создаём окно: size(640, 480) # размер текста: textSize(24) # первый градиент: createRandomGradient() # печатаем сообщение: printMessage() def draw(): pass # НАЖИМАЕМ КЛАВИШУ ПРОБЕЛ def keyPressed(): if (key == ' '): createRandomGradient() printMessage() # /СОЗДАЁМ СЛУЧАЙНЫЙ ГРАДИЕНТ def createRandomGradient(): # случайный цвет вверху: clrTop = random_color() # случайный цвет внизу: clrBottom = random_color() for y in range(height): amt = map(y, 0, height, 1, 0) clr = lerpColor(clrTop,clrBottom,amt) stroke(clr) line(0, y, width, y) def printMessage(): fill(0) 218
noStroke(); text(u"Нажмите ПРОБЕЛ", 10, 30) # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(256), random(256), random(256)) return clr Проект Интерактивный градиент Исходный код программы находится в папке Interactiv_gradient. Давайте избавимся от случайности в предыдущем проекте. В функции setup перейдём в режим HSB: def setup(): # создаём окно: size(640, 480) # размер текста: textSize(32) # цветовой режим: colorMode(HSB, 360, 100,100) В функции draw обновляем градиент на экране: def draw(): createGradient() 219
А сам градиент создаём в функции createGradient по текущим координатам мышки mouseX и mouseY: # СОЗДАЁМ ГРАДИЕНТ def createGradient(): # цвета пропорциональны координатам курсора: clrTop = color(map(mouseY, 0, height, 0, 360), 100, 100) clrBottom = color(map(mouseX, 0, width, 0, 360), 100, 100) for y in range(height): amt = 1. * y / height clr = lerpColor(clrTop,clrBottom,amt) stroke(clr) line(0, y, width, y) fill(0) noStroke() text(u"Для изменения цвета фона двигайте мышку вверх-вниз", 20, 40) Теперь любой пользователь может создавать собственные градиенты: 220
Градиент при этом может получиться многоцветным: Проект Интерактивный градиент 2 Исходный код программы находится в папке Interactiv_gradient2. Вернёмся к цветовой модели RGB. Здесь градиенты получаются «правильными»: 221
Но цвета при перемещении мышки изменяются слишком резко, потому что их очень много: NUM_COLORS = 255*255*255 def setup(): # создаём окно: size(740, 480) # размер текста: textSize(32) frameRate(5) def draw(): createGradient() 222
Дополнительно нам приходится выделять из чисел clrTop и clrBottom цветовые составляющие и создавать цвет: # СОЗДАЁМ ГРАДИЕНТ def createGradient(): # цвета пропорциональны координатам курсора: clrTop = int(map(mouseY, 0, height, 0, NUM_COLORS)) clrBottom = int(map(mouseX, 0, width, 0, NUM_COLORS)) rTop = (clrTop >> 16) & 255 gTop = (clrTop >> 8) & 255 bTop = (clrTop) & 255 rBottom = (clrBottom >> 16) & 255 gBottom = (clrBottom >> 8) & 255 bBottom = (clrBottom) & 255 for y in range(height): amt = map(y, 0, height, 0, 1) clr = lerpColor(color(rTop, gTop, bTop),color(rBottom, gBottom, bBottom ),amt) stroke(clr) line(0, y, width, y) fill(0) noStroke() text(u"Для изменения цвета фона двигайте мышку вверх-вниз", 20, 40) Проект Горизонтальный градиент Исходный код программы находится в папке Horizontal_gradient. Из вертикального градиента легко получить горизонтальный. Перепишем метод createGradient так, чтобы левая граница канвы имела цвет clrLeft, а правая – clrRight: 223
# СОЗДАЁМ ГРАДИЕНТ def createGradient(): # цвета пропорциональны координатам курсора: clrLeft = color(map(mouseY, 0, height, 0, 360), 100, 100) clrRight = color(map(mouseX, 0, width, 0, 360), 100, 100) for x in range(width): amt = float(x) / width clr = lerpColor(clrLeft,clrRight,amt) stroke(clr) line(x, 0, x, height) fill(0) noStroke() text(u"Для изменения цвета фона двигайте мышку", 20, 40) Если вы не запутались в координатах, то теперь сможете в интерактивном режиме создавать и горизонтальные градиенты: 224
Проект Горизонтальный градиент 2 Исходный код программы находится в папке Horizontal_gradient2. Не самый лёгкий, но самый верный способ создания градиентов заключается в конвертировании цвета из модели HSB в RGB. Тогда все (яркие и насыщенные) цвета уложатся в короткий диапазон. Например, 0..1. Для этого нам понадобится функция HSVtoRGBColor: # ПЕРЕВОДИМ HSB/HSV в RGB # h, s, v = 0..1 # r,g,b = 0..255 def HSVtoRGBColor(h, v, s): r = g = b = i = f = p = q = t = 0 if (s == None and v == None): s = h.s, v = h.v, h = h.h i = floor(h * 6) f = h * 6 - i p = v * (1 - s) q = v * (1 - f * s) t = v * (1 - (1 - f) * s); m = int(i) % 6 if m == 0: r = v g = t b = p elif m == 1: r = q g = v b = p elif m == 2: r = p g = v b = t elif m == 3: 225
r = p g = q b = v elif m == 4: r = t g = p b = v elif m == 5: r = v g = p b = q return color(r * 255, g * 255, b * 255) Весь остальной код прост и понятен, а градиенты теперь получаются на загляденье: def setup(): # создаём окно: size(740, 480) # размер текста: textSize(32) def draw(): createGradient() # СОЗДАЁМ ГРАДИЕНТ def createGradient(): # цвета пропорциональны координатам курсора: clrLeft = map(mouseY, 0, height, 0, 1) clrRight = map(mouseX, 0, width, 0, 1) clrLeftRGB = HSVtoRGBColor(clrLeft, 1, 1) clrRightRGB = HSVtoRGBColor(clrRight, 1, 1) for x in range(width): amt = float(x) / width clr = lerpColor(clrLeftRGB, clrRightRGB, amt) 226
stroke(clr) line(x, 0, x, height) fill(0) noStroke() text(u"Для изменения цвета фона двигайте мышку", 20, 40) Проект Синусоидный градиент Исходный код программы находится в папке Sine_gradient. 227
В этом проекте градиент будут создавать горизонтальные линии, цвет которых изменяется по высоте канвы сверху вниз по формуле: iclr = 255 * (1 + sin(y / wl)) / 2 (1) Синусоидные полоски мы уже рисовали пикселями, но с горизонтальными линиями градиент получить проще. Частота горизонтальных волн зависит от длины волны wl, так что вы легко получите разные картинки, изменяя значение этого параметра. В функции setup создаём окно заданных размеров: # СИНУСОИДНЫЙ ГРАДИЕНТ # длина волны синусоиды: wl = 10. # номер цвета волн: num_color = 1 def setup(): # создаём окно: size(740, 480) # размер текста: textSize(24) # рисуем волны: drawSinus() # печатаем сообщение: printMessage() Затем рисуем волны, а сверху печатаем надпись. Так как длину синусоиды мы будем изменять динамически, то её текущее значение сохраняем в переменной wl. А номер текущего цвета хранится в переменной num_color. 228
Сначала номер цвета равен 1, поэтому полосы получатся серыми. Чтобы изменить цвет полосок, нужно нажать клавишу 1, 2 или 3, о чём сообщает подсказка на экране: # НАЖИМАЕМ КЛАВИШУ 1..3 def keyPressed(): global num_color num_color = int(key) drawSinus() printMessage() Длину волн и номер цвета учитываем в функции drawSinus: # РИСУЕМ ВОЛНЫ def drawSinus(): clr = 0 # рисуем волны: for y in range(height): # цвет очередной линии: iclr = 255 * (1 + sin(y / wl)) / 2 # серные полоски: if (num_color == 1): clr = color(iclr, iclr, iclr) # красные полоски: elif (num_color == 2): clr = color(iclr, 0, 0) else: # синие полоски: clr = color(0, 0, iclr) stroke(clr) line(0, y, width, y) Вот такая у нас получилась волнистая картинка: 229
Нажимаем цифровые клавиши – и волны становятся цветными! 230
Но длину волны так просто не изменить. Для этого нужно двигать мышку вверхвниз, с нажатой кнопкой. В функции mouseDragged находим расстояние по вертикали dy, на которое переместилась мышка: # ПЕРЕМЕЩАЕМ МЫШКУ def mouseDragged(): global wl dy = mouseY – pmouseY Если оно отрицательное (мышка побежала вверх), то длина волны увеличивается, а если положительное – уменьшается: 231
if (dy < 0): wl += 1 else: wl -= 1 if (wl < 1): wl = 1. drawSinus() printMessage() #print(u"Длина волны = " + str(wl)) Мышкой (или даже пальцем на сенсорном экране) вы можете пускать волны любой длины: 232
Проект Синусоидный градиент 2 Исходный код программы находится в папке Sine_gradient2. Неподвижные волны очень скучны, даже цветные. Но их легко привести в движение, то есть анимировать. Добавим в функцию для вычисления цвета полоски параметр t – условное время: 233
# РИСУЕМ ВОЛНЫ def drawSinus(): clr = 0 # рисуем волны: for y in range(height): # цвет очередной линии: iclr = 255 * (1 + sin(y / wl + t)) / 2 . . . Теперь волны двигаются вниз, если время отрицательное, или вверх – если положительное: # время: t = 0.0 def draw(): # рисуем волны: drawSinus() global t t -= 0.6 # печатаем сообщение: printMessage() 234
Проект Прямоугольный градиент Исходный код программы находится в папке Rect_gradient. Казалось бы, прямоугольный градиент нужно рисовать прямоугольниками, но отрезками прямых получается быстрее. В функции setup создаём окно: def setup(): # создаём окно: size(740, 480) # размер текста: textSize(32) А в функции draw вызываем функцию createGradient, которая и создаёт прямоугольный градиент: def draw(): createGradient() Функция createGradient получилась довольно сложной, потому что в ней нужно учитывать изменение цвета от границы канвы к середине и чертить 4 отрезка: # СОЗДАЁМ ГРАДИЕНТ def createGradient(): clrOuter = map(mouseY, 0, height, 0, 1) clrInner = map(mouseX, 0, width, 0, 1) clrOuterRGB = HSVtoRGBColor(clrOuter, 1, 1) clrInnerRGB = HSVtoRGBColor(clrInner, 1, 1) 235
prop = 0. + width / height strokeWeight(3) for y in range(height // 2): amt = float(y) / height / 1.5 clr = lerpColor(clrInnerRGB, clrOuterRGB, amt) stroke(clr) x = y * prop h = height - y - 1 w = width - x - 1 line(x, y, w, y) line(x, y, x, h) line(x,height-y, w, height-y) line(width-x, y, width-x, h) fill(0) noStroke() text(u"Для изменения цвета фона двигайте мышку", 20, 40) А градиент получился не лучшего качества: 236
Треугольники Из отрезков можно построить любые многоугольники, но для треугольников и прямоугольников имеются специальные функции. Проще всего задать размеры треугольника и его положение на плоскости координатами трёх его вершин, которые и требуются функции triangle, чтобы вычертить треугольник: triangle(x1: float, y1: float, x2: float, y2: float, x3: float, y3: float) Цвет контура треугольника задаётся функцией stroke, а толщина линий – функцией strokeWeight. Поскольку треугольник составлен из трёх линий, то стиль их соединения можно устанавливать функцией strokeJoin. Если нужно вычертить только линии контура треугольника, то с помощью функции noFill нужно отключить заливку внутренней части треугольника. В противном случае цвет заливки определяется функцией fill. Если контур у треугольника отсутствует (установлен режим noStroke), то будет нарисован треугольник выбранного цвета, но без границ. Если же ни один из режимов noStroke и noFill не включён, то треугольник будет иметь и контур, и заливку. Их цвета, естественно, могут и не совпадать. Подумайте, что произойдёт, если одновременно вызвать функции noStroke и noFill. 237
Проект Случайные треугольники Исходный код программы находится в папке Random_triangles. Треугольник – это замкнутая ломаная линия, внутренняя часть которой может быть закрашена выбранным цветом. Для рисования треугольников мы выбираем произвольные координаты трёх его вершин, а также цвета контура и заливки: # ПРОГРАММА ДЛЯ ВЫЧЕРЧИВАНИЯ # СЛУЧАЙНЫХ ТРЕУГОЛЬНИКОВ # толщина линий: penWidth = 2. 238
def setup(): size(480, 320) background(0) # толщина линий: strokeWeight(penWidth) strokeJoin(ROUND) noFill() #noStroke() frameRate(10) def draw(): # задаём случайные координаты вершин треугольника --> # координаты первой точки: x1 = random(width - penWidth / 2) y1 = random(height - penWidth / 2) # координаты второй точки: x2 = random(width - penWidth / 2) y2 = random(height - penWidth / 2) # координаты третьей точки: x3 = random(width - penWidth / 2) y3 = random(height - penWidth / 2) # выбираем случайный цвет линии: clr = random_color() stroke(clr) # выбираем случайный цвет заливки: clr = random_color() #fill(clr) # чертим треугольник: triangle(x1, y1, x2, y2, x3, y3) # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(32,255), random(32, 255), random(32, 255)) return clr 239
240
А теперь давайте построим из маленьких треугольников один БОЛЬШОЙ. Проект Треугольный треугольник Исходный код программы находится в папке Triangles. Толщину линий установим в 2 пикселя, а длину сторон маленьких треугольников – в 32 пикселя: # ПРОГРАММА ДЛЯ ВЫЧЕРЧИВАНИЯ # ТРЕУГОЛЬНИКА ИЗ ТРЕУГОЛЬНИКОВ # толщина линий: penWidth = 2. # длина стороны треугольника: len= 32. 241
В функции setup мы задаём параметры треугольников и вызываем функцию draw_triangles для их рисования: def setup(): size(480, 440) background(0) # толщина линий: strokeWeight(penWidth) strokeJoin(ROUND) noFill() # цвет контура: stroke('#FFFF00') draw_triangles() Маленькие треугольники обращены вершиной вниз и образуют столбики, высота которых уменьшается по мере удаления столбиков от центра. Первый столбик начинается треугольником с координатами левой вершины (x1, y1), а следующие столбики смещены влево и вправо на size/4*3*j пикселей. Кроме того, каждый следующий столбик начинается ниже предыдущего на j*dy*1.5 пикселей. Это процесс хорошо виден на рисунке. → 242
Для вычерчивания каждого маленького треугольника мы вызываем функцию draw_triangle: # Чертим 1 треугольник def draw_triangle(x, y, dy): x1 = x y1 = y; x2 = x1 + len y2 = y1 x3 = x1+ len / 2. y3 = y1+dy triangle(x1,y1,x2,y2,x3,y3) Проект Треугольный треугольник 2 Исходный код программы находится в папке Triangles2. Мы сделаем картинку веселее, если раскрасим треугольники в разные цвета! Треугольники, а за ними и окно УКРУПНИМ, цвет контура сделаем жёлтым, а фон светлым: # длина стороны треугольника: len= 64. def setup(): size(960, 880) background(245, 255, 250) # толщина линий: strokeWeight(penWidth) strokeJoin(ROUND) 243
# цвет контура: stroke(0) draw_triangles() В функции drawTriangle выбираем для каждого треугольника случайный цвет заливки: # Чертим 1 треугольник def draw_triangle(x, y, dy): clr = random_color() fill(clr) x1 y1 x2 y2 x3 y3 = = = = = = x y; x1 + len y1 x1 + len / 2. y1 + dy triangle(x1,y1,x2,y2,x3,y3) Вот такая праздничная получилась картинка. → 244
Прямоугольники и квадраты Прямоугольники отличаются от треугольников только числом вершин (по крайней мере в компьютерной графике это так). Для их рисования имеются 3 «прямоугольные» функции: rect(x: float, y: float, w: float, h: float) rect(x: float, y: float, w: float, h: float, r: float) rect(x: float, y: float, w: float, h: float, rtl: float, rtr: float, rbr: float, rbl: float) Вторая и третья функции рисуют прямоугольник со скруглёнными углами! Здесь: x, y – координаты верхнего левого угла прямоугольника w, h – его ширина и высота r – радиус скругления всех углов (не действует в режиме CORNERS) rtl, rtr, rbr, rbl – радиусы скругления верхнего левого, верхнего правого, нижнего правого и нижнего левого углов, соответственно Остальные свойства для прямоугольников выбираются точно так же, как и для треугольников, то есть вы можете рисовать и контурные, и закрашенные прямоугольники. Если ширина прямоугольника равна высоте, то получится квадрат. За режимы рисования прямоугольника отвечает функция: rectMode(mode: int) 245
По умолчанию mode = CORNER. В этом случае прямоугольник вычерчивается так, как мы рассмотрели выше. 246
Обычный прямоугольник и прямоугольники со скруглёнными углами В режиме CORNERS третий и четвёртый параметры (w,h) трактуются как координаты правого нижнего угла прямоугольника: Рисование прямоугольника в режиме CORNERS 247
В режиме CENTER прямоугольник рисуется от центра (то есть координаты левого верхнего угла становятся координатами центра прямоугольника): Рисование прямоугольника в режиме CENTER Режим RADIUS отличается от предыдущего только тем, что третий и четвёртый параметры используются как полуширина и полувысота прямоугольника (то есть его размеры увеличатся в 2 раза): Рисование прямоугольника в режиме RADIUS 248
Проект Прямоугольные режимы Исходный код программы находится в папке Rect_mode. Чтобы лучше разобраться во всех премудростях этих режимов, давайте напишем небольшую программу, которая покажет нам всё в натуральном виде: # РЕЖИМЫ РИСОВАНИЯ ПРЯМОУГОЛЬНИКА size(320,260) rectMode(CORNERS) fill(0xFF, 0xFF, 00, 20) rect(150, 120, 200, 200) rectMode(CORNER) fill(0xFF, 00, 00, 20) rect(150, 120, 140, 100); rectMode(CENTER) fill(00, 0xFF, 00, 20) rect(150, 120, 140, 100) rectMode(RADIUS) fill(00, 00, 0xFF, 20) rect(150, 120, 140, 100) Заливку прямоугольников я сделал полупрозрачной, чтобы они не закрывали друг друга. На рисунке хорошо видно, что прямоугольники одинаковых размеров нарисованы в режимах CORNER и CENTER, большой прямоугольник – в режиме RADIUS, а маленький – в режиме CORNERS. 249
Проект Случайные прямоугольники Исходный код программы находится в папке RandomRects. А теперь давайте нарисуем случайные прямоугольники со скруглёнными углами в режиме CORNERS, чтобы сохранить преемственность с «треугольной» программой: # ПРОГРАММА ДЛЯ ВЫЧЕРЧИВАНИЯ # СЛУЧАЙНЫХ ПРЯМОУГОЛЬНИКОВ # толщина линий: penWidth = 2. def setup(): size(480, 320) background(0) # толщина линий: strokeWeight(penWidth) strokeJoin(ROUND) rectMode(CORNERS) frameRate(10) def draw(): # задаём случайные координаты вершин прямоугольника --> # координаты первой вершины: x1 = random(width - penWidth / 2) y1 = random(height - penWidth / 2) # координаты второй вершины: x2 = random(width - penWidth / 2) y2 = random(height - penWidth / 2) # выбираем случайный цвет линии: 250
clr = random_color() stroke(clr) # выбираем случайный цвет заливки: clr = random_color() fill(clr, 60); # чертим прямоугольник: rect(x1, y1, x2, y2, 20) # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(32, 255), random(32, 255), random(32, 255)) return clr Закрашиваем прямоугольники полупрозрачным цветом, чтобы они не заслоняли друг друга: 251
Проект Случайные квадраты Исходный код программы находится в папке RandomQuadr. Всю канву мы разобьём на квадратные клетки – 25 по ширине и столько же по высоте. Длина стороны квадрата тоже 25 пикселей (случайное совпадение!): # ПРОГРАММА ДЛЯ ВЫЧЕРЧИВАНИЯ # СЛУЧАЙНЫХ КВАДРАТОВ # размер клеток в пикселях: QSIZE = 25 # число клеток по горизонтали: NCOL = 25 # число клеток по вертикали: NROW = 25 Между клетками оставляем зазор шириной в PEN_WIDTH пикселей: # толщина линий: PEN_WIDTH = 1 Окрашиваем фон окна в чёрный цвет, который определяет цвет линий – границ (зазоров) между клетками: # цвет фона: BACKCOLOR = 0 Сами клетки мы будем случайно окрашивать в один из двух цветов: 252
# цвета клеток: Blue ='#0000FF' Gold = '#FFD700' COLORS = [Blue, Gold] Размеры окна подгоняем под размеры поля: # размеры окна: WIDTH = (QSIZE + PEN_WIDTH) * NCOL + PEN_WIDTH HEIGHT = (QSIZE + PEN_WIDTH) * NROW + PEN_WIDTH На этом «глобальные» приготовления закончены, и мы переходим в функцию setup. Здесь мы создаём окно и вызываем функцию drawTable для рисования цветных квадратиков: def setup(): # создаём окно: size(WIDTH, HEIGHT) background(BACKCOLOR) drawTable() В функции drawTable самый первый квадратик рисуем в левом верхнем углу канвы: # РИСУЕМ ТАБЛИЦУ def drawTable(): noStroke() # координаты верхнего левого угла первой клетки: x1 = PEN_WIDTH y1 = PEN_WIDTH Цвет квадратика выбираем случайным образом из списка COLORS: 253
# чертим таблицу: for r in range(NROW): for c in range(NCOL): # цвет клетки: clr = int(random(2)) clr = COLORS[clr] # закрашиваем клетку: fill(clr) Рисуем квадратик в окне: rect(x1, y1, QSIZE, QSIZE) И переходим к следующей клетке по горизонтали: # координаты верхнего левого угла следующей клетки: x1 += QSIZE + PEN_WIDTH Когда очередная строка квадратиков закончится, опускаемся на 1 клетку ниже: # переходим на следующую горизонталь: x1 = PEN_WIDTH y1 += QSIZE + PEN_WIDTH Все квадратики нарисованы, и у нас получилась вот такая геометрическая абстрактная картинка: 254
Поскольку цвет квадратиков выбирается случайно, то каждый раз будет получаться новая картинка, веселя и радуя вас! 255
Проект Считаем квадратики Исходный код программы находится в папке CountSquares. Чтобы рисование квадратиков не стало пустою забавою, их можно пересчитать! Конечно, считать квадратики, водя пальцем по экрану, это совсем неинтересно, поэтому мы будем оценивать число квадратиков на глазок и по результатам оценки делать вывод о том, квадратиков какого цвета на картинке больше. Вот так просто у нас получится психологический тест на объём восприятия. За основу мы возьмём предыдущий проект и дополним функцию drawTable несколькими строчками: # РИСУЕМ ТАБЛИЦУ def drawTable(): # число клеток с индексом 0: n0 = 0 noStroke() # координаты верхнего левого угла первой клетки: x1 = PEN_WIDTH y1 = PEN_WIDTH # чертим таблицу: for r in range(NROW): for c in range(NCOL): # цвет клетки: clr = int(random(2)) n0 += 1 if clr == 0 else 0 clr = COLORS[clr] # закрашиваем клетку: fill(clr) rect(x1, y1, QSIZE, QSIZE) # координаты верхнего левого угла следующей клетки: x1 += QSIZE + PEN_WIDTH # переходим на следующую горизонталь: x1 = PEN_WIDTH y1 += QSIZE + PEN_WIDTH 256
vsego = NROW * NCOL # число клеток с индексом 1: n1 = vsego - n0 print(u"Синих = " + str(n0) + u" : Золотых = " + str(n1)) Запускаем программу – тест готов: 257
В консоли мы предусмотрительно напечатали число квадратиков каждого цвета. → В готовом задании такой подсказки быть не должно. Проект Пиксели Исходный код программы находится в папке Pixels. В одном из выпусков программы Удивительные люди Евгений Дубин из Омска демонстрировал свои способности в номере Стереоскопическое зрение: 258
Он должен был найти на двух картинках 3 квадратика-пикселя, имеющие разный цвет. Мы облегчим себе задачу: у нас пикселей будет гораздо меньше, и окрашены они будут только в 2 цвета. Здесь мы опять воспользуемся предыдущим проектом, и внесём в него необходимые изменения. Так как теперь у нас 2 картинки, то ширину канвы нужно удвоить и добавить ещё зазор между ними: def setup(): # создаём окно: size(WIDTH + 10 + WIDTH, HEIGHT) 259
Нам понадобятся 2 списка для хранения цветов клеток. В Питоне нет двумерных массивов, поэтому мы создаём список списков с помощью функции createArray, которой нужно передать размерности списков: # СОЗДАЁМ ДВУМЕРНЫЙ СПИСОК def createArray(cols, rows): # игровое поле: return [[None for row in range(rows)] for col in range(cols)] Идём дальше и заполняем обе таблицы одинаковыми квадратиками: # СОЗДАЁМ ТАБЛИЦЫ def createTable(): global tableLeft, tableRight, otvet tableLeft = createArray(NCOL, NROW) tableRight = createArray(NCOL, NROW) for r in range(NROW): for c in range(NCOL): # цвет клетки: clr = int(random(2)) tableLeft[c][r] = clr tableRight[c][r] = clr Но несколько квадратиков должны отличаться по цвету: # число отличных пикселей: OTL = 3 Мы можем случайно выбирать квадратики по номеру, но нужно их запоминать, чтобы не перекрасить один и тот же квадратик несколько раз. Для хранения номеров перекрашенных квадратиков заведём список. Сначала он пуст: # изменяем цвет OTL квадратиков: 260
lstRCell = [] Также нам понадобятся переменные для подсчёта перекрашенных квадратиков и для номера случайной клетки: # перекрасили квадратиков: n = 0 # номер случайной клетки: rcell = 0 В цикле while выбираем случайную клетку, которой нет в списке, и вносим её туда: while (n < OTL): while True: # выбираем случайную клетку: rcell = int(random(0, NCOL * NROW)) if rcell not in lstRCell: break lstRCell.append(rcell) n += 1 Все клетки выбраны. Для «красоты» ответа сортируем список: lstRCell = sorted(lstRCell) Ответ записываем в глобальную переменную otvet: otvet = u"(Строка, Колонка) --> " По номерам клеток в списке lstRCell определяем их индексы и перекрашиваем в другой цвет. Так как у нас всего два цвета, то сделать это нетрудно: 261
for i in range(len(lstRCell)): # по номерам клеток определяем их координаты: cell = lstRCell[i] r = int(cell / NCOL) c = cell - r * NCOL # цвет клетки: clr = tableRight[c][r] # новый цвет: clr = 1 - clr tableRight[c][r] = clr otvet += "(" + str(r + 1) + "," + str(c + 1) + ") " В функции drawTable одновременно рисуем квадратики в обеих таблицах на экране. Цвета квадратиков для левой таблицы берём из списка tableLeft, а для правой - из списка tableRight: # РИСУЕМ ТАБЛИЦЫ def drawTable(): global tableLeft, tableRight, otvet noStroke() # координаты верхнего левого угла первой клетки: x1 = PEN_WIDTH y1 = PEN_WIDTH # чертим таблицы: for r in range(NROW): for c in range(NCOL): # цвет клетки: clr = tableLeft[c][r] clr = COLORS[clr] # закрашиваем клетку: fill(clr) rect(x1, y1, QSIZE, QSIZE) # квадратик на второй таблице: clr = tableRight[c][r] clr = COLORS[clr] fill(clr) rect(x1 + 10 + WIDTH, y1, QSIZE, QSIZE) 262
# координаты верхнего левого угла следующей клетки: x1 += QSIZE + PEN_WIDTH # переходим на следующую горизонталь: x1 = PEN_WIDTH y1 += QSIZE + PEN_WIDTH Закончив рисование квадратиков, печатаем ответ в консоли: print(otvet) А когда вам это занятие покажется лёгким, то добавьте пикселей и цветов к нашим таблицам. Конечно, такими стереоскопическими способностями вы уже никого не удивите, поэтому постарайтесь придумать что-нибудь своё! Теперь вы можете воспитывать в себе стереоскопическое зрение на простых примерах: 263
Проект Цветные прямоугольники Исходный код программы находится в папке ColorRects. В компьютерной графике чаще всего используется цветовая модель RGB, в которой все цвета получаются смешиванием трёх цветовых составляющих – красной, зелёной и синей. Из них легко получить любой случайный цвет. Мы написали для этого специальную функцию random_color: # размеры окна: WIDTH = 600 HEIGHT = 300 # число прямоугольников: N_RECT = 60 def setup(): # создаём окно: size(WIDTH, HEIGHT) drawRandRect() #drawRect() def drawRandRect(): colorMode(RGB) stroke(0) strokeWeight(1) # прямоугольник --> # ширина: w = WIDTH // N_RECT for i in range(N_RECT): clr = random_color() fill(clr) rect(i * w, 0, w, HEIGHT) 264
Но когда нужно выполнить плавный переход от одного цвета у другому, как в радуге, удобнее пользоваться цветовой моделью HSV (или HSB). В функции drawRect рисуем полоску из цветных прямоугольников: # РИСУЕМ ПРЯМОУГОЛЬНИКИ def drawRect(): colorMode(HSB, 360, 100, 100) # контур: stroke(0) strokeWeight(1) # прямоугольник --> # ширина: w = WIDTH // N_RECT for i in range(N_RECT): clr = color(i * 360.0 / N_RECT, 100, 100) fill(clr) rect(i * w, 0, w, HEIGHT) Цвета заливки прямоугольников выбираем так, чтобы они полностью покрыли диапазон 0..360. 265
На рисунке хорошо видно, что цвета плавно переходят от красного к оранжевому, потом жёлтому – и так пока снова не вернутся к красному: Проект Шахматная доска Исходный код программы находится в папке Chess_board. Клетчатый рисунок всегда отлично смотрится и на экране, и в жизни, а между тем нарисовать его – сущие пустяки! Чтобы разнообразить шахматные доски, мы будем изменять размеры клеток - отдельно их ширину и высоту: # ШАХМАТНАЯ ДОСКА 266
def setup(): size(480, 480) size(1000, 1000) background(255) # размеры клеток: global cell_width, cell_height cell_width = cell_height = 20 # шрифт: font = createFont('Arial', 20) textFont(font) # цвета клеток: global color1, color2 color1 = random_color() color2 = random_color() # клетки без контуров: noStroke() # смещение и размеры шахматной доски: global offset_x, offset_y, w, h offset_x = offset_y = w = h = 0 Сразу после запуска программы функция draw вызовет функцию draw_grid: def draw(): draw_grid() Которая нарисует шахматную доску с размерами клеток по умолчанию – 20 х 20 пикселей и закрашенных в случайные цвета. → Так как цвета клеток выбираются случайно, то не всегда они будут радовать глаз. Но, нажав кнопку мышки, вы заставите функцию mousePressed изменить цвета клеток: 267
# ИЗМЕНЯЕМ ЦВЕТ КЛЕТОК def mousePressed(): global color1, color2 color1 = random_color() color2 = random_color() Но и здесь цвета выбираются случайно, поэтому вы можете нажимать кнопку мышки до тех пор, пока пара цветов вам не понравится. В функции draw_grid мы рисуем шахматную доску. Это просто, но мы должны учесть, что размеры картинки могут изменяться, поэтому перед рисованием новой доски мы должны стереть старую. Проще всего очистить весь фон целиком, но тогда он мерцает при перерисовке. Чтобы стереть только старую доску, мы должны запоминать её размеры в глобальных переменных: # РИСУЕМ ШАХМАТНУЮ ДОСКУ def draw_grid(): global offset_x, offset_y, w, h # стираем старую доску: fill(255) rect(offset_x, offset_y, w, h) Размеры шахматной доски изменяются потому, что не всегда по ширине и высоте окна укладывается целое число клеток. Если мы хотим, чтобы все клетки были в полный размер, то часть окна останется свободной. Мы должны нарисовать на ней шахматное поле так, чтобы слева и справа, а также сверху и снизу оставались одинаковые полоски, то есть мы всегда размещаем шахматное поле по центру окна. Так как сверху мы будем выводить информацию о размерах клеток, то полоска высотой в 30 пикселей должна остаться свободной от клеток: 268
# оставляем сверху место для текста: offset_y = 30 # число колонок: col = width // cell_width # число строк: row = (height - offset_y) // cell_height # ширина картинки: w = col * cell_width # высота картинки: h = row * cell_height # центрируем: offset_x = (width - w)/2 offset_y += (height - offset_y - h)/2 # рисуем клетки: for y in range(row): for x in range(col): if ((x + y) % 2 == 0): clr = color1 else: clr = color2 fill(clr) rect(offset_x + x*cell_width, offset_y + y*cell_height, cell_width, cell_height) Самый простой и красивый способ изменения размеров клеток заключается в отслеживании перемещения мышки по окну приложения. • • • • Если мышка перемещается вверх, то высота клеток увеличивается. Если мышка перемещается вниз, то высота клеток уменьшается. Если мышка перемещается вправо, то ширина клеток увеличивается. Если мышка перемещается влево, то ширина клеток уменьшается. Системные переменные pmouseX и pmouseY хранят координаты мышки в предыдущем кадре. Системные переменные mouseX и mouseY хранят координаты мышки в текущем кадре. 269
Зная это, мы легко найдём величину перемещения мышки по горизонтали и по вертикали - dx, dy. Если перемещения невелики, то мы их игнорируем, чтобы избежать «помех» при перемещении мышки. Обратите внимание, что размеры клеток изменяются только при нажатой клавише (удобнее всего удерживать клавишу ПРОБЕЛ). Это легко объясняется. Если программа будет реагировать на все перемещения мышки по полю, то большие клетки «вытянуть» очень трудно, поскольку придётся двигать мышку в противоположном направлении. Но тогда размеры клеток снова изменятся. Чтобы этого избежать, мышку придётся уводить с канвы, что не очень удобно. # ИЗМЕНЯЕМ РАЗМЕРЫ КЛЕТОК def mouseMoved(): if not keyPressed: return global cell_width, cell_height dx = mouseX - pmouseX dy = mouseY - pmouseY if dx > 2: dx = 1 elif dx < - 2: dx = -1 else: dx = 0 if dy > 2: dy = -1 elif dy < - 2: dy = 1 else: dy = 0 Мы нашли величину изменения текущих размеров клеток dx, dy, но необходимо ещё проконтролировать, чтобы они оставались в разумных пределах: new_cell_width = cell_width + dx 270
if 1 < new_cell_width <= width: cell_width += dx new_cell_height = cell_height + dy if 1 < new_cell_height <= height: cell_height += dy Осталось напечатать свежую информацию о размерах клеток. Для этого мы закрашиваем красным цветом полоску в верхней части экрана и чёрным шрифтом пишем текст: # печатаем размеры клеток: fill(255,0,0) rect(0,0,width, 30) fill(0) text(u"Ширина = " + str(cell_width),20,20) text(u"Высота = " + str(cell_height),180,20) Функция createFont имеет 3 вида: createFont(name: str, size: float) → PFont createFont(name: str, size: float, smooth) → PFont createFont(name: str, size: float, smooth: bool, charset: char[]) → PFont Здесь: name – имя шрифта. Например, 'Arial'. size – высота шрифта smooth – сглаживание шрифта (True) – режим по умолчанию charset = список символов Созданный шрифт font нужно передать в функцию textFont, чтобы он стал текущим при печати сообщений на экране: 271
textFont(font) Функция text печатает заданную строку str на экране. text(str: str, x: float, y: float) Здесь: str - строка x, y – это координаты верхнего левого угла прямоугольника, в который вписан текст. На этом все шахматно-шашечные приготовления успешно закончены, и вы можете приступить к рисованию. Немного сноровки – и получатся замечательные картинки! 272
Если позволяет монитор, то размеры шахматной доски можно значительно увеличить. Тогда картинка будет смотреться ещё лучше! 273
274
Проект Стакан Исходный код программы находится в папке Stakan. Вот стакан пустой - он предмет простой. Так, наверно, сказал бы наш любимый Винни об этой картинке. → Так как наш стакан – это просто белый прямоугольник, то нарисовать его совсем просто. А чтобы было не совсем просто, мы воспользуемся нашей библиотекой цветов, чтобы окрасить фон окна тёмно-синий цвет: from colors import * def setup(): size(320, 600) background(DarkBlue) # наполнение: global nap nap = 0.0 strokeWidth = 1. rectMode(RECT) # НАПОЛНЯЕМ СТАКАН def draw(): fill(255) rect(50, 20, width-50, height-20) Переменная nap отвечает за наполнение стакана. Сначала она равна нулю, что соответствует пустому стакану. 275
В функции draw соответственно наполненности стакана мы рисуем снизу голубой прямоугольник, призванный символизировать наполнение стакана живительной влагой: # высота наполнения: global nap h = (height - 40) * nap fill(DodgerBlue) rect(50, height - 20 - h, width-50, height-20) В каждом цикле мы подливаем в стакан немного воды: if nap < 1: nap += .01 Этот ирригационный процесс продолжается до тех пор, пока стакан не наполнится до края. А, вам ещё раз хочется посмотреть на простую компьютерную анимацию? – Тогда нажмите любую клавишу – вода из стакана исчезнет, и он снова начнёт наполняться водой: # ОПУСТОШАЕМ СТАКАН def keyPressed(): # наполнение: global nap nap = 0.0 Получилось наглядно и забавно: 276
Попробуйте зациклить этот процесс, то есть после наполнения стакана сливайте воду, а затем снова набирайте её. Это будет ещё забавнее! 277
Эллипсы и круги Функция для рисования эллипсов почти ничем не отличается от «прямоугольной». Единственное различие состоит в том, что у эллипса нет вершин, поэтому его положение на канве задаётся координатами описанного прямоугольника: ellipse(x: float, y: float, w: float, h: float) Как будут трактоваться эти параметры при рисовании, определяет функция ellipseMode: ellipseMode(mode: int) Параметр mode может принимать те же значения, что и при вычерчивании прямоугольников - CENTER, RADIUS, CORNER и CORNERS, но по умолчанию действует режим CENTER то есть первые два параметра - x и y - задают центр эллипса: Эллипс в режиме CENTER 278
Эллипс в режиме CORNER Эллипс в режиме CORNERS 279
Эллипс в режиме RADIUS Если ширина эллипса равна высоте, то получится круг. Проект Многоточие Исходный код программы находится в папке Polycircles. Сейчас мы напишем программу, которая умеет рисовать огромные точки, так что после её работы экран будет усыпан ими, как новогодний пол – конфетти: 280
Здесь цвет и место точек задаются случайным образом: # МНОГОТОЧИЕ # толщина линий: penWidth = 2. # радиус "точки": radius = 10. def setup(): size(640, 480) background(0) # толщина линий: strokeWeight(penWidth) 281
ellipseMode(CORNERS) def draw(): # задаём случайные координаты вершин # описанного прямоугольника --> # координаты первой вершины: x1 = random(width - radius) y1 = random(height - radius) # выбираем случайный цвет линии: clr = random_color() stroke(clr) noStroke() # выбираем случайный цвет заливки: clr = random_color() fill(clr) # чертим кружок: ellipse(x1, y1, x1+2*radius, y1+2*radius) # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(32, 255), random(32, 255), random(32, 255)) return clr Немного изменим функцию рисования: # выбираем случайный цвет линии: clr = random_color() stroke(clr) #noStroke() # выбираем случайный цвет заливки: clr = random_color() fill(clr) noFill() # чертим кружок: ellipse(x1, y1, x1+2*radius, y1+2*radius) И точки-кружочки превращаются в элегантные окружности: 282
Смотреть на точки одного и того же размера скучно, поэтому давайте усеем канву точками всевозможного калибра. Сделать это проще простого. Достаточно радиус кружков задавать случайным образом с помощью функции random: # случайный радиус: global radius radius = random(4, 10) # чертим кружок: ellipse(x1, y1, x1+2*radius, y1+2*radius) 283
Если обвести кружочки, то картинка получится и того веселее: 284
Проект Мишень Исходный код программы находится в папке Target. Простая программа для упражнения в круговерчении. Мы начертим мишень из разноцветных колец: 285
# МИШЕНЬ # толщина линий: penWidth = 2 # ширина колец: wc = 40 def setup(): # создаём окно: size(640, 640) background(0) drawTarget() # РИСУЕМ КРУГИ def drawTarget(): stroke(0) # толщина линий: strokeWeight(penWidth) # число кругов: nc = width // wc for i in range(nc, 0, -1): # выбираем случайный цвет заливки: clr = random_color() fill(clr) # чертим кружок: radius = wc * i ellipse(width / 2, height / 2, radius, radius) # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(32, 255), random(32, 255), random(32, 255)) return clr Минимум интеллектуальных затрат, а картинка получилась совсем неплохая: 286
287
Проект Случайные эллипсы Исходный код программы находится в папке RandomEllipses. Эллипсы красивее квадратиков, особенно если они большие и разноцветные: # ПРОГРАММА ДЛЯ ВЫЧЕРЧИВАНИЯ # СЛУЧАЙНЫХ ЭЛЛИПСОВ # размер клеток в пикселях: QSIZE = 75 # число клеток по горизонтали: NCOL = 12 # число клеток по вертикали: NROW = 8 # толщина линий: PEN_WIDTH = 2 # цвет фона: BACKCOLOR = '#124184' # размеры окна: WIDTH = (QSIZE + PEN_WIDTH) * NCOL + PEN_WIDTH HEIGHT = (QSIZE + PEN_WIDTH) * NROW + PEN_WIDTH def setup(): # создаём оуно: size(WIDTH, HEIGHT) background(BACKCOLOR) drawTable() Мы нарисуем целую табличку из эллипсов, причём некоторые из них сплющим по бокам: 288
# РИСУЕМ ТАБЛИЦУ def drawTable(): #noStroke(); strokeWeight(PEN_WIDTH) ellipseMode(CENTER) # координаты верхнего левого угла первой клетки: x1 = PEN_WIDTH y1 = PEN_WIDTH # чертим таблицу: for r in range(NROW): for c in range(NCOL): # цвет клетки: clr = random_color() # закрашиваем клетку: fill(clr) stroke(inverse_color(clr)) ellipse(x1+QSIZE/2, y1+QSIZE/2, random(QSIZE-20) + 20, QSIZE) # координаты верхнего левого угла следующей клетки: x1 += QSIZE + PEN_WIDTH # переходим на следующую горизонталь: x1 = PEN_WIDTH y1 += QSIZE + PEN_WIDTH Чтобы цвета контура и заливки были контрастными, мы вызываем функцию inverse_color: # ВОЗВРАЩАЕМ ИНВЕРТИРОВАННЫЙ ЦВЕТ def inverse_color(c): # составляющие цвета: r = 255 - red(c) g = 255 - green(c) b = 255 - blue(c) return color(r, g, b) В результате у нас получилась вполне художественная картина из эллипсов: 289
Проект Живые картинки Исходный код программы находится в папке Rectangular. 290
До сих пор мы занимались статическими картинками: нарисовал их – и всё! А ведь гораздо интереснее наблюдать на экране за непрерывными изменениями геометрических фигур. Сейчас мы напишем программу, которая будет рисовать прямоугольники или эллипсы, непрерывно обновляющие картинку на экране. В функции setup мы устанавливаем размеры окна и области рисования: # ПРОГРАММА ДЛЯ ВЫЧЕРЧИВАНИЯ # ДИНАМИЧЕСКИХ ПРЯМОУГОЛЬНИКОВ и ЭЛЛИПСОВ # толщина линий: penWidth = 1.5 def setup(): size(640, 480) background(0) # толщина линий: strokeWeight(penWidth) strokeJoin(ROUND) rectMode(CORNERS) ellipseMode(CORNERS) # размеры области рисования: global x0, y0 x0 = 0 y0 = 0 # максимальные координаты прямоугольника: global xmax, ymax xmax = width ymax = height # что рисуем? global figura figura = 'RECT' noFill() prepare() 291
Мы нарисуем прямоугольники и эллипсы, а переключаться между ними будем с помощью клавиш 1 и 2: # НАЖИМАЕМ КЛАВИШУ def keyPressed(): global figura if key == '1': figura = 'RECT' background(0) prepare() if key == '2': figura = 'ELL' background(0) prepare() После нажатия на эти клавиши мы попадём в функцию draw с соответствующим значением переменной figura. В функции draw мы вычерчиваем прямоугольники или эллипсы. Начинаем с самой большой фигуры, размеры которой равны области рисования, затем постепенно координаты вершин (описанного) прямоугольника всё больше и больше приближаются к центру и, наконец, наступает момент, когда левый верхний угол занимает место правее и/или ниже правого нижнего, после чего прямоугольник «выворачивается», но функция rect рисует его правильно: def draw(): global x0, y0 global xmax, ymax # текущие координаты прямоугольника: global x1, y1, x2, y2 global figura global i,j if((x1 <= xmax) and (y1 <= ymax)): if (figura == "RECT"): # рисуем прямоугольник: rect(x1, y1, x2, y2) 292
else: # рисуем эллипс: ellipse(x1, y1, x2, y2) # новые координаты вершин прямоугольника: x1 += i y1 += j x2 -= i y2 -= j elif (j <= 5): # стартовый прямоугольник: x1 = x0 y1 = y0 x2 = xmax y2 = ymax # выбираем случайный цвет контура: clr = random_color() stroke(clr) j += 1 elif(i >= 1): i -= 1 else: j=0 prepare() Переменные (x1, y1) и (x2, y2) задают координаты верхнего левого и правого нижнего углов прямоугольника, которые необходимы при вызове функций rect и ellipse в режиме CORNERS. Правило преобразования фигур очень простое, но образующиеся при работе программы динамические узоры весьма любопытны, и разглядывать их можно не одну минуту! 293
294
И последняя функция – prepare – подготавливает программу к новому циклу рисования фигур: def prepare(): global x0, y0 global xmax, ymax global x1, y1, x2, y2 global i,j i = 6 j = 0 # стартовый прямоугольник: x1 = x0 y1 = y0 295
x2 = xmax y2 = ymax # выбираем случайный цвет контура: clr = random_color() stroke(clr) Проект Цветные круги Исходный код программы находится в папке ColorCircles. Цвет каждого пикселя определяется RGB-составляющими, которые смешиваются друг с другом, чтобы создать самые разнообразные цвета. К сожалению, пиксели слишком малы, чтобы можно было разглядеть, как три разные составляющие при смешивании образуют тот или иной цвет. В новом проекте мы сделаем «пиксели» огромными. Изменять прозрачность цвета мы будем ползунком: # длина ползунка: LENGTH = WIDTH – 110 Роль цветных составляющих пикселя в нашем проекте исполнят цветные кружки, для которых мы написали класс Circle: # КЛАСС КРУГОВ class Circle: def __init__(self, x, y, radius, clr): # координаты центра кружка: self.x = x self.y = y 296
# его радиус: self.radius = radius # и цвет: self.clr = clr # рисуем кружок: def draw(self): noStroke() fill(self.clr) ellipse(self.x, self.y, \ self.radius * 2, self.radius * 2) # ПРОВЕРЯЕМ, НАХОДИТСЯ ЛИ ТОЧКА # (x,y) ВНУТРИ КРУГА def over(self, x, y): dx = self.x - x dy = self.y - y r = self.radius return dx * dx + dy * dy <= r * r Радиус кругов выбираем достаточно большим, чтобы их было хорошо видно на экране: # радиус кругов: RADIUS = 120 Для хранения кружков заведём список circles. В функции settings создаём окно: # размеры окна: WIDTH = 640 HEIGHT = 480 + 40 # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT) 297
А в функции setup - 3 круга с основными цветами и помещаем их в список: # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # толщина линий: strokeWeight(penWidth) ellipseMode(CENTER) global circles circles = [] circles.append(Circle(WIDTH / 2, HEIGHT / 2, RADIUS, color(255, 0, 0))) circles.append(Circle(WIDTH / 2 + 20, HEIGHT / 2 + 20, RADIUS, color(0, 255, 0))) circles.append(Circle(WIDTH / 2 + 40, HEIGHT / 2 + 40, RADIUS, сolor(0, 0, 255))) # создаём ползунок: global cp5 cp5 = ControlP5(this) cp5.addSlider("1")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(0, 255)\ .setValue(255) # размер текста: textSize(24) В функции draw все 3 цветных кружка появляются на экране: # РИСУЕМ КРУГИ def draw(): background(BACKCOLOR) # текущая прозрачность: global cp5 a = cp5.getValue("1") fill(255,0,0) text('A=' + str(a), WIDTH - 85, HEIGHT - 20) # рисуем кружочки: for c in circles: # обновляем цвет кружка 298
# с учётом прозрачности: c.clr = add_alpha(c.clr, a) c.draw() # ДОБАВЛЯЕМ К ЦВЕТУ ПРОЗРАЧНОСТЬ def add_alpha(c, a): return color(red(c), green(c), blue(c), a) Порядок рисования кружков определяется индексом кружка в списке circles: сначала красный, потом зелёный, и последним рисуется синий кружок. 299
Новые кружки полностью непрозрачные, поэтому их цвета не смешиваются. Уменьшаем непрозрачность цвета (или увеличиваем прозрачность), перемещая движок ползунка влево. Теперь цвета кружков смешиваются, образуя новые цвета: Уже интересно, но не очень, поэтому берём кружки мышкой и перемещаем их по канве. Так как одновременно перемещать можно только 1 кружок, то его индекс запоминаем в переменной cMoved: 300
# индекс перемещаемого кружка: cMoved = -1 Итак, мы нажали кнопку мышки и попали в функцию mousePressed. Здесь мы получаем координаты мышки и с помощью метода over, который имеется у всех кружков, ищем тот кружок, на котором нажата мышка: # НАЖИМАЕМ КНОПКУ МЫШКИ def mousePressed(): #if mouseY > HEIGHT - 40: # return global cMoved, dx, dy # ищем нажатый кружок: for i in range(len(circles)): c = circles[i] if (c.over(mouseX, mouseY)): cMoved = i; #print("Нажат кружок " + str(i)) # запоминаем расстояние: global dx, dy dx = c.x - mouseX dy = c.y - mouseY break Тут нужно отметить, что, если под курсором мышки находится несколько кружков, то активным становится первый из них, но он находится ниже остальных кружков, что не очень логично. Закомментируйте оператор break, и тогда активным станет последний, верхний кружок. Кружок мы взяли и весело перемещаем его по экрану: # ПЕРЕМЕЩАЕМ КРУЖОК def mouseDragged(): global cMoved, dx, dy if (cMoved == -1): return 301
# новые координаты кружка: circles[cMoved].x = mouseX + dx circles[cMoved].y = mouseY + dy Нажатый кружок останется активным до тех пор, пока вы не отпустите кнопку мышки: # ОТПУСКАЕМ КНОПКУ МЫШКИ def mouseReleased(): global cMoved 302
cMoved = - 1 Конечно, настоящего смешивания цветов у нас не получилось, но, в принципе, модель вполне правдоподобная. Проект Радиальный градиент Исходный код программы находится в папке CircGradient. Проще всего создать радиальный градиент, вычерчивая окружности, начиная от центра: def setup(): # создаём окно: size(680, 680) # размер текста: textSize(24) def draw(): createGradient() # СОЗДАЁМ ГРАДИЕНТ def createGradient(): # цвета пропорциональны координатам курсора: clrOuter = map(mouseY, 0, height, 0, 1) clrInner = map(mouseX, 0, width, 0, 1) print mouseY, mouseX, clrOuter, clrInner clrOuterRGB = HSVtoRGBColor(clrOuter, 1, 1) clrInnerRGB = HSVtoRGBColor(clrInner, 1, 1) noFill(); strokeWeight(3) ellipseMode(CENTER) # макс. радиус окружности: maxR = height / 1.4 303
for r in range(int(maxR)): amt = float(r) / maxR clr = lerpColor(clrInnerRGB, clrOuterRGB, amt) stroke(clr) ellipse(width / 2, height / 2, r * 2, r * 2) fill(0) noStroke() text(u"Для изменения цвета фона двигайте мышку", 20, 40) Радиальный градиент хорош собой: 304
Проект Радиальные волны 2 Исходный код программы находится в папке Radial. Мы рисовали радиальные волны пикселями, но вполне естественно рисовать их концентрическими окружностями, цвет которых изменяется по такой формуле: clr = 255 * (1 + sin(r / wl)) / 2 Длина волны хранится в глобальной переменной wl, а номер цвета – в переменной num_color: # РАДИАЛЬНЫЕ ВОЛНЫ 2 # длина волны синусоиды: wl = 20. # цвет волн: num_color = 1 Создаём окно большого размера: # СОЗДАЁМ ОКНО def settings(): size(680, 680) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # размер шрифта: textSize(24) 305
И рисуем волны в функции draw: # РИСУЕМ ВОЛНЫ def draw(): noFill() strokeWeight(3) ellipseMode(CENTER) # макс. радиус окружности: maxR = height / 1.4 for r in range(int(maxR)): # цвет окружности: clr = 255 * (1 + sin(r / wl)) / 2 if num_color == 1: stroke(clr, clr, clr) elif num_color == 2: stroke(clr, 0, 0) elif num_color == 3: stroke(0, clr, 0) elif num_color == 4: stroke(0, 0, clr) ellipse(width / 2, height / 2, r * 2, r * 2) fill(0) noStroke() text(u"Для изменения длины волны крутите колёсико мышки", 20, 40) text(u"Для изменения цвета волн нажимайте клавиши 1..4", 20, 66) Сразу после запуска программы волны имеют серый, невыразительный цвет: 306
Но их можно перекрасить в красный (цифровая клавиша 2), в зелёный (цифровая клавиша 3) или в синий цвет (цифровая клавиша 4), а также изменить длину волны колёсиком мышки: # КРУТИМ КОЛЁСИКО МЫШКИ def mouseWheel(event): 307
global wl if (event.count > 0): wl -= 1 else: wl+= 1 wl = constrain(wl, 1, 40) # НАЖИМАЕМ КЛАВИШУ def keyPressed(): global num_color if key == '1': num_color = 1 elif key == '2': num_color = 2 elif key == '3': num_color = 3 else: num_color = 4 308
309
Проект Одинокий пульсар Исходный код программы находится в папках Pulsar2 и Pulsar3. Пульсары – это удивительные космические объекты, излучение которых изменяется со временем, «пульсирует». Наша программа моделирует скорее не пульсары, а другие – не менее замечательные! – космические объекты – переменные звёзды, которые периодически изменяют свои размеры. Звезду мы заменим цветным кругом, который то уменьшается, то увеличивается в размерах. Когда круг уменьшится до размеров точки, он перекрасится в случайный цвет: def setup(): size(320, 320) # макс. диаметр кружка: global d_max d_max = width # цвет кружка: global clr clr = random_color() fill(clr) ellipseMode(CENTER) # координаты центра: global x, y x = width // 2 y = height // 2 # текущий диаметр точки: global d d = 10 # в-на изменения диаметра: global dd dd = 3 frameRate(50) 310
def draw(): background(0) global d, dd ellipse(x, y, d, d) d = d + dd if d > d_max: dd = -3 elif d < 2: dd = 3 clr = random_color() fill(clr) # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(256), random(256), random(256)) return clr Здесь нам удобнее использовать режим ellipseMode(CENTER), потому что у нас всего один круг, центр которого совпадает с центром канвы. Вся остальная часть программы понятна и без комментариев. А пульсирующая звезда получилась на славу! 311
Проект Жёлтая подводная лодка Исходный код программы находится в папке Podlodka. Самая известная из всех подводных лодок та, про которую спели битлы: Она была жёлтого цвета, и, как следует из песни, жить в ней было уютно и беззаботно. Но у битлов подводная лодка появилась сама собой, а у нас всё-таки забота есть: нам нужно её нарисовать. Но сначала мы по примеру стаканного проекта наполним наш океан водой. Но не жёлтой и не оранжевой, а обыкновенной. Когда всё будет готово, в самой пучине океана появится та самая йеллосабмарин, о которой так здорово поётся в песне. 312
У нас она будет, правда, не такая красивая, как на картинке, но вполне правдоподобная – эллиптическая. Жить мы будем не внутри лодки, а снаружи и управлять её возвратно-поступательными движениями будем клавишами со стрелками. Всё это несложно, но мы ходу движения подлодки должны приглядывать за ней, чтобы она ненароком не покинула вверенную нам акваторию. Если бы мы научили нашу героиню подводного труда обходить мины и стрелять торпедами, то получилась бы самая настоящая стрелялка. Но эта задача слишком сложна для нас, поэтому будем лелеять мечту, а пока займёмся текущими делами. В этом проекте мы опять воспользуемся библиотекой цветов: from colors import * Размеры лодки незыблемы и непоколебимы, поэтому мы сохраним их в константах: # размеры лодки: P_WIDTH = 80 P_HEIGHT = 20 Координаты лодки, напротив, вполне могут и должны изменяться: # координаты лодки: p_x = 300 p_y = 150 Подготовительные работы мы проводим в функции setup: 313
def setup(): size(640, 320) background(White) # наполнение: global nap nap = 0.0 rectMode(RECT) ellipseMode(RECT) smooth() В функции draw мы заполняем наш океан водой, после чего выпускаем подводную лодку: def draw(): create_ozean() if nap > 1: draw_podlodka() Процесс создания океана, несмотря на масштабы работ, даже проще, чем наполнение стакана: # СОЗДАЁМ ОКЕАН def create_ozean(): # высота наполнения: global nap h = height * nap fill(DodgerBlue) noStroke() rect(0, height - h, width, height) if nap < 1: nap += .01 И тут на сцене появляется во всей красе наша жёлтая подводная лодка: # РИСУЕМ ПОДВОДНУЮ ЛОДКУ 314
def draw_podlodka(): fill(Yellow) stroke(Blue) ellipse(p_x, p_y, P_WIDTH, P_HEIGHT) Её даже можно увидеть, если поглубже засунуть голову в воду: # РИСУЕМ ПОДВОДНУЮ ЛОДКУ def draw_podlodka(): fill(Yellow) stroke(Blue) ellipse(p_x, p_y, P_WIDTH, P_HEIGHT) С управлением мы легко управляемся в функции keyPressed: # УПРАВЛЯЕМ ЛОДКОЙ def keyPressed(): global p_x, p_y if key == CODED: if keyCode == UP: 315
p_y -= if keyCode p_y += if keyCode p_x -= if keyCode p_x += 2 == DOWN: 2 == LEFT: 2 == RIGHT: 2 # ограничиваем перемещения: if p_x < 0: p_x = 0 if p_x > width - P_WIDTH: p_x = width - P_WIDTH if p_y < 0: p_y = 0 if p_y > height - P_HEIGHT: p_y = height - P_HEIGHT Крутите штурвал, набирайте балласт, продувайте цистерны. Короче говоря, счастливого плавания! Радиальные волны 2 Переделайте наши остальные пиксельные проекты с радиальными волнами! 316
Геометрические примитивы Мы изучили функции для рисования геометрических фигур, без которых трудно представить себе графические программы: • • • • • точки – point прямые линии – line треугольники - triangle прямоугольники и квадраты – rect эллипсы и круги – ellipse А в этой главе вы познакомитесь ещё с двумя представителями плоских примитивов – дугой и произвольным четырёхугольником. Дуги Дуга вычерчивается с помощью функции arc: arc(x: float, y: float, w: float, h: float, start: float, stop: float) Здесь: Параметры x, y, w, h – задают эллипс, дугу которого мы вычерчиваем. Функция ellipseMode назначает режим вычерчивания эллипса. Параметры start, stop – углы в радианах, которые устанавливают начало и конец дуги. Углы отсчитываются по часовой стрелке, начиная от положительного направления оси Х. 317
Эллиптическая дуга Если стартовый угол меньше конечного, то дуга не будет вычерчена. Внутренняя часть дуги может быть закрашена точно так же, как и у замкнутых фигур. Проект Гнём дуги Исходный код программы находится в папке Arcs. Если с эллипсом всё просто и понятно, то для лучшего понимания его дуг, мы напишем отдельную программу. Чтобы можно было изменять начальный и конечный угол в работающей программе, мы установим два ползунка. Также нам понадобятся переменные w и h, которые задают размеры эллипса: # ПРОГРАММА ДЛЯ ВЫЧЕРЧИВАНИЯ # ДУГИ ЭЛЛИПСА 318
add_library('controlP5') # размеры окна: WIDTH = 680 HEIGHT = 550 # цвет фона: BACKCOLOR = color(240, 248, 255) # длина ползунка: LENGTH = WIDTH - 110 # ширина эллипса: w = 660 # высота эллипса: h = 420 В функции setup создаём оба ползунка и устанавливаем начальные значения углов дуги: def setup(): size(WIDTH, HEIGHT) # создаём ползунки: # создаём ползункик: global cp5 cp5 = ControlP5(this) cp5.addSlider("Beg")\ .setPosition(10, HEIGHT - 80)\ .setSize(LENGTH, 20)\ .setRange(0, 360)\ .setValue(45) cp5.addSlider("End")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(0, 360)\ .setValue(315) 319
В функции draw заливаем фон чёрным прозрачным цветом, что создаёт эффект затухания, когда углы дуги уменьшаются. Прямоугольную область в нижней части окна закрашиваем синим цветом, чтобы показания движков хорошо читались. Далее мы вычерчиваем контурный эллипс, чтобы лучше ориентироваться в углах дуги, и наконец, саму дугу – с жёлтой заливкой и утолщённым контуром: # РИСУЕМ ДУГУ def draw(): noStroke() fill(0, 10) rect(0, 0, width, height-100) fill(0,0,255) rect(0, height-100, width, 100) noFill() stroke(255, 5) strokeWeight(1.) ellipse(width/2., height/2.-50, w,h) # углы: global cp5 st = cp5.getValue("Beg") fin = cp5.getValue("End") strokeWeight(3.) stroke(255) fill(0xFF, 0xFF, 00) arc(width/2., height/2.-50, w,h, radians(st), radians(fin)) Запускаем программу, перемещаем движки и с интересом наблюдаем за происходящими событиями, которые наверняка помогут вам разобраться с углами и дугами. 320
Проект Анимированные сектора Исходный код программы находится в папке Sectors. А теперь давайте заставим несколько дуг рисоваться самостоятельно! 321
Для описания каждой дуги нам потребуется класс, который мы назовём Sector, поскольку закрашенные дуги больше похожи на сектора. Нам потребуются такие поля: # КЛАСС СЕКТОРА class Sector(): # координаты и размеры дуги: x = y = w = h = 0 # цвет: clr = 0 # начальный угол: startAngle = 0 # конечный угол: stopAngle = 0 # приращение конечного угла: dAngle = 0 Я остановил свой выбор на семи дугах, но вы легко можете изменить это число и в ту, и в другую сторону: # ПРОГРАММА ДЛЯ ВЫЧЕРЧИВАНИЯ ДУГ ЭЛЛИПСА # размеры окна: WIDTH = 680 HEIGHT = 550 COUNT = 7 arcs = [None] * COUNT Основное назначение функции setup – создание заданного числа экземпляров класса Sector и присвоение их полям нужных значений: def setup(): # создаём окно: size(WIDTH, HEIGHT) # размер текста: 322
textSize(24) # создаём дуги: for i in range(COUNT): arcs[i] = Sector() arcs[i].w = random(120, 250) arcs[i].h = random(120, 260) arcs[i].x = random(arcs[i].w / 2, width - arcs[i].w / 2) arcs[i].y = random(arcs[i].h / 2, height - arcs[i].h / 2) arcs[i].clr = random_color(5) arcs[i].startAngle = 0 arcs[i].stopAngle = random(360) arcs[i].dAngle = random(1, 3) В функции draw мы поочерёдно извлекаем значения полей всех дуг и чертим их на экране: # РИСУЕМ ДУГИ def draw(): # очищаем экран: noStroke() fill(228, 3) rect(0, 0, width, height) # чертим дуги: strokeWeight(1) stroke(90) for i in range(COUNT): # углы: beg = arcs[i].startAngle end = arcs[i].stopAngle x = arcs[i].x y = arcs[i].y w = arcs[i].w h = arcs[i].h clr = arcs[i].clr fill(clr) arc(x, y, w, h, radians(beg), radians(end)) arcs[i].stopAngle += arcs[i].dAngle arcs[i].stopAngle %= 360 323
Функцию random_color нужно изменить так, чтобы она возвращала прозрачный цвет: # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(a = 255): clr = color(random(0,255), random(0, 255), random(0, 255), a) return clr Статическая картинка тоже смотрится неплохо, но динамическая – гораздо интереснее! . 12.3. 324
Четырёхугольники Для вычерчивания произвольных четырёхугольников имеется специальная функция quad: quad(x1: float, y1: float, x2: float, y2: float, x3: float, y3: float, x4: float, y4: float) Здесь все параметры задают координаты вершин четырёхугольника – по часовой стрелке или против неё: Проект Бойкие четырёхугольники Исходный код программы находится в папке Quads. 325
Поскольку прямоугольники – частный вид четырёхугольников – мы уже подробно разобрали, то давайте напишем приложение, которое покажет нам, как преображается четырёхугольник при изменении координат его вершин. Мы заставим их двигаться в произвольном направлении и отражаться от границ окна при столкновении. Этот незамысловатый процесс заставит координаты вершин изменяться весьма причудливым образом, так что наблюдение за четырёхугольником наверняка доставит вам несколько приятных минут. Опишите новый класс Vertex. Он представляет одну вершину четырёхугольника: # КЛАСС ВЕРШИНЫ: class Vertex(): # координаты: x = y = 0 # цвет: clr = 0 # перемещение: dx = dy = 0 Для рисования нам потребуются 4 вершины, каждую из которых мы обведём цветным кружком, чтобы на экране их было хорошо видно: # ПРОГРАММА ДЛЯ ВЫЧЕРЧИВАНИЯ # ПРОИЗВОЛЬНЫХ ЧЕТЫРЁХУГОЛЬНИКОВ # размеры окна: WIDTH = 680 HEIGHT = 550 # радиус кружков: r = 5 # число кружков: COUNT = 4 # массив кружков: verts = [None] * 4 В функции setup случайным образом создаём вершины четырёхугольника: 326
def setup(): # создаём окно: size(WIDTH, HEIGHT) background(128) # создаём вершины: for i in range(COUNT): verts[i] = Vertex() verts[i].x = random(r, width - r) verts[i].y = random(r, height - r) verts[i].dx = random(4, 8) verts[i].dy = random(4, 8) verts[i].clr = random_color() frameRate(30) И рисуем их в функции draw: # РИСУЕМ ЧЕТЫРЁХУГОЛЬНИК def draw(): # очищаем канву: noStroke() fill(255, 20) rect(0, 0, width, height) # рисуем четырёхугольник: strokeWeight(1.5) stroke(60) fill(255, 255, 0, 60) quad(verts[0].x, verts[0].y, verts[1].x, verts[1].y, verts[2].x, verts[2].y, verts[3].x, verts[3].y) # рисуем вершины-кружки: for i in range(COUNT): x = verts[i].x y = verts[i].y clr = verts[i].clr fill(clr) ellipse(x, y, r * 2, r * 2) verts[i].x += verts[i].dx 327
if ((verts[i].x > width - r) or (verts[i].x < r)): verts[i].dx = - verts[i].dx verts[i].y += verts[i].dy if ((verts[i].y > height - r) or (verts[i].y < r)): verts[i].dy = - verts[i].dy Самое «сложное» здесь – проконтролировать отскок вершин от сторон окна. Мы делаем это грубовато, но для наших целей вполне удовлетворительно: 328
Параметрические кривые Кривая линия может быть задана параметрическими уравнениями. Для плоской кривой понадобятся 2 уравнения – для каждой из координат. Координаты – это функции от некоторой переменной t. Проект Параметрические кривые Исходный код программы находится в папке Parametric. В этом проекте мы рассмотрим параметрические кривые, которые задаются такими уравнениями: x = cos(A * t) + cos(B * t) / 2 + sin(C * t) / 3 y = sin(A * t) + sin(B * t) / 2 + cos(C * t) / 3 Параметр t изменяется в диапазоне 0..2π, а коэффициенты А, В и С – произвольные целые числа. Эти уравнения вы можете найти в книге Фрэнка Фарриса (Frank A. Farris) Creating Symmetry: The Artful Mathematics of Wallpaper Patterns. → В ней параметры равны: А=1 В=6 С = 14 329
Им соответствует кривая на рисунке: Но нам, конечно, хотелось бы посмотреть и на другие кривые – может быть, среди них найдутся ещё более интересные! Параметрические формулы содержат 3 параметра - a, b, c. 330
Их нужно изменять в интерактивном режиме. Как обычно, мы для этого используем ползунки. В функции settings создаём большое окно, а в функции setup - ползунки: add_library('controlP5') global cp5 # СОЗДАЁМ ОКНО def settings(): size(660, 600) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): global cp5 cp5 = ControlP5(this) # координаты: x = 10 y = 500 # длина: w = 630 cp5.addSlider("a")\ .setPosition(x, y)\ .setSize(w, 20)\ .setRange(-90, 170)\ .setValue(1)\ .setNumberOfTickMarks(261)\ .snapToTickMarks(True)\ .setColorTickMark(0) cp5.addSlider("b")\ .setPosition(x, y+30)\ .setSize(w, 20)\ .setRange(-50, 50)\ .setValue(6)\ .setNumberOfTickMarks(101)\ .snapToTickMarks(True)\ .setColorTickMark(0) 331
cp5.addSlider("c")\ .setPosition(x, y+60)\ .setSize(w, 20)\ .setRange(-50, 50)\ .setValue(14)\ .setNumberOfTickMarks(101)\ .snapToTickMarks(True)\ .setColorTickMark(0) А функции draw создаём кривую и печатаем текущие значения коэффициентов кривой: # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(0) createCurve() a = int(cp5.getValue("a")) b = int(cp5.getValue("b")) c = int(cp5.getValue("c")) fill(255) text("a = " + str(a) + " b = " + str(b) + \ " c = " + str(c), 10, height - 6) По этим коэффициентам в функции drawCurve чертим кривую. При расчёте по формулам значения координат получаются очень маленькими, поэтому их следует умножить на коэффициент k, значение которого нужно подобрать опытным путём: # СОЗДАЁМ КРИВУЮ def createCurve(): # задаём цвет линии - красный: clr = color(255, 0, 0) stroke(clr) # коэффициент увеличения: k = 130.0 332
Чтобы кривая получилась гладкой, нужно задать параметру t небольшое приращение step, которое зависит от значения параметров кривой: A = round(cp5.getValue("a")) B = round(cp5.getValue("b")) C = round(cp5.getValue("c")) # шаг: maxk = max(max(A, B), C) if maxk == 0: maxk = 0.01 step = min(1.0 / maxk / 8, 0.01) # толстая линия: strokeWeight(1.6) if (abs(maxk) > 40): strokeWeight(1.2) В цикле for вычисляем значения координат точек кривой и создаём вершины: noFill() # создаём кривую: t = 0.0 with beginShape(): while (t < PI * 2.1): x = cos(A * t) + cos(B * t) / 2.0 + sin(C * t) / 3.0 y = sin(A * t) + sin(B * t) / 2.0 + cos(C * t) / 3.0 vertex(x * k + width / 2.0, y * k + height / 2.0 - 50) t += step Чтобы соединить отдельные вершины кривой отрезками, нужно поместить цикл for между вызовами функций beginShape и endShape, которые можно заменить оператором with beginShape(). Функция beginShape начинает построение фигур: beginShape() beginShape(mode: int) 333
Параметр mode определяет вид создаваемой фигуры и может принимать следующие значения: • • • • • • • POINTS LINES TRIANGLES TRIANGLE_FAN TRIANGLE_STRIP QUADS QUAD_STRIP Первая функция beginShape строит по заданным точкам произвольный многоугольник. Функция endShape завершает построение фигуры: endShape() endShape(CLOSE) Вторая функция endShape замыкает фигуру, то есть соединяет отрезком прямой первую и последнюю точки (зависит от режима beginShape). Координаты вершин фигуры (x, y) задаются функцией vertex: vertex(x: float, y: float) Цвет и толщина линий (точек) устанавливаются функциями stroke и strokeWeight, а цвет заливки – функцией fill. Стиль соединения линий определяется функцией strokeJoin, а стиль концов линий (форма точек) – функцией strokeCap. Внутри блока beginShape – endShape функции strokeWeight, strokeCap и strokeJoin не действуют. То же самое касается и всех видов трансформаций. 334
Результат работы программы вы уже видели в начале проекта. Пришло время натворить кривых линий! Занятие это увлекательное и полезное. 335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
Проект Параметрические кривые 2 Исходный код программы находится в папке Parametric2. Мы сделаем параметрические кривые ещё красивее, раскрасив их в градиентные цвета! Описанный выше способ не позволяет окрашивать отрезки в разные цвета, поэтому нам придётся создать список вершин: # список вершин: VertexArray = None Заполнять список мы будем в функции createCurve, которую нужно вызвать в функции draw, чтобы кривая появилась на экране: # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(0) createCurve() drawCurve() a = int(cp5.getValue("a")) b = int(cp5.getValue("b")) c = int(cp5.getValue("c")) fill(255) text("a = " + str(a) + " b = " + str(b) + \ " c = " + str(c), 10, height - 6) И она появится, потому что функция draw затем вызывает функцию drawCurve. В функции createCurve координаты всех вершин кривой вносим в список VertexArray: 350
# СОЗДАЁМ КРИВУЮ def createCurve(): # задаём цвет линии - красный: clr = color(255, 0, 0) stroke(clr) # коэффициент увеличения: k = 160.0 A = round(cp5.getValue("a")) B = round(cp5.getValue("b")) C = round(cp5.getValue("c")) # шаг: maxk = max(max(A, B), C) if maxk == 0: maxk = 0.01 step = min(1.0 / maxk / 8, 0.01) global VertexArray VertexArray = [] # создаём вершины: t = 0.0 while (t < PI * 2.1): x = cos(A * t) + cos(B * t) / 2.0 + sin(C * t) / 3.0 y = sin(A * t) + sin(B * t) / 2.0 + cos(C * t) / 3.0 VertexArray.append(PVector(x * k + width / 2, y * k + height / 2 - 50)) t += step # толстая линия: #strokeWeight(1.6) #if (abs(maxk) > 40): # strokeWeight(1.2) strokeWeight(2) Чтобы сохранить координаты вершины, мы создаём для неё новый вектор типа PVector. Список вершин заполнен, и в функции drawCurve мы соединяем все соседние пары вершин отрезками прямых: 351
# РИСУЕМ КРИВУЮ def drawCurve(): noFill() # соединяем соседние пары точек: for i in range(len(VertexArray)-1): # начало отрезка: v1 = VertexArray[i] # конец отрезка: v2 = VertexArray[i+1] # цвет отрезка: #clr = HSVtoRGBColor(float(i)/len(VertexArray), 10., 10.) clr = color(float(i)/len(VertexArray),1.,1.) stroke(clr) line(v1.x, v1.y, v2.x, v2.y) Цвет каждого отрезка в режиме HSV вычисляем по его индексу, что очень удобно. «Книжная» кривая стала разноцветной. → Двигайте ползунки в разные стороны – и получите замечательные картинки: 352
353
354
355
356
357
358
При отрицательных значениях параметров кривой получаются угловатые кривые, которые не менее интересны: 359
360
361
Проект Параметрические кривые 3 Исходный код программы находится в папке Parametric3. Пойдём ещё дальше и анимируем построение кривой! Для счёта очередной пары вершин нам потребуется глобальная переменная iter: # число итераций: iter = 0 В функции setup, а также при каждом изменении параметров кривой вызываем функцию newCurve для заполнения списка вершин новыми координатами: add_library('controlP5') # коэффициенты: A,B,C = 1, 6, 14 a,b,c = A,B,C # СОЗДАЁМ ОКНО def settings(): size(660+100, 600+100) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): . . . newCurve() В функции newCurve мы заливаем канву чёрным цветом, создаём новую кривую, печатаем сообщение и обнуляем переменную iter: 362
# НОВАЯ КРИВАЯ def newCurve(): background(0) # создаём кривую: createCurve() global iter iter = 0 В функции drawCurve последовательно соединяем все пары соседних вершин: # РИСУЕМ КРИВУЮ def drawCurve(): global iter, VertexArray #print iter if (iter >= len(VertexArray) - 1): return noFill() # соединяем соседние пары точек: # начало отрезка: v1 = VertexArray[iter] # конец отрезка: v2 = VertexArray[iter+1] # цвет отрезка: clr = color(float(iter)/len(VertexArray),1.,1.) stroke(clr) line(v1.x, v1.y, v2.x, v2.y) iter += 1 Параметры кривой можно изменять на лету с помощью ползунков, как и раньше: 363
364
Смотреть графические мультики очень интересно! Завораживающее зрелище! 365
Растровые изображения PImage – это тип данных для хранения изображений. Чтобы использовать картинки в форматах .gif, .jpg и .png, нужно в функции settings или setup загрузить в переменную файл с диска с помощью функции loadImage: def setup(): img = loadImage(u"тюльпаны.jpg") При вызове функции loadImage нужно указать путь к файлу path: loadImage(path: string) loadImage(path: string, extension: string) В первом варианте название файла должно включать и расширение! Графические файлы должны храниться в папке с программой или во вложенной папке data. Так как на загрузку картинок может потребоваться значительное время, то их лучше загружать в функции settings. Картинные функции Картинку (или её часть) можно напечатать в любом месте канвы, воспользовавшись функцией image: image(img: PImage, x: float, y: float) image(img: PImage, x: float, y: float, w: float, h: float) 366
Здесь: x, y – координаты верхнего левого угла картинки на канве w, h – размеры картинки на канве sx, sy – координаты верхнего левого угла прямоугольной области картинки sWidth, sHeight– размеры прямоугольной области картинки При этом нужно учитывать режим печати, который устанавливается функцией imageMode: imageMode(mode: int) Параметр mode может принимать такие значения: • CORNER (режим по умолчанию) • CORNERS • CENTER Тогда x и y - это координаты левого верхнего угла картинки в режимах CORNER и CORNERS или центра картинки в режиме CENTER. Первая функция image печатает картинку без изменения размеров, вторая позволяет масштабировать картинку, задавая нужные значения параметрам w и h: • в режиме CORNER - это новая ширина и высота картинки на экране (оригинал не изменяется!) • в режиме CORNERS – это координаты правого нижнего угла картинки • в режиме CENTER - это новая ширина и высота картинки на экране Первую функцию image может заменить функция set: img.set(x: int, y: int, img: PImage) 367
Здесь: x, y – координаты верхнего левого угла картинки на канве img – картинка Первая функция get возвращает цвет пикселя в заданной позиции картинки: img.get(x: int, y: int) → int Вторая – её фрагмент: img.get(x: int, y: int, w: int, h: int) → PImage А третья возвращает всю картинку целиком: img.get() → PImage Другой способ создания картинок – применить функцию createImage: createImage(w: int, h: int) → PImage w и h – размеры картинки в пикселях Такая картинка существует только в памяти компьютера! 368
Проект Цветные волны Исходный код программы находится в папке ImgSinus. Давайте нарисуем на пустой картинке цветные волны – как в проекте Синусоидные полоски. Объявляем глобальную переменную img и в функции setup создаём пустую картинку по размерам окна: # Цветные волны # длина волны синусоиды: wl = 10. # номер цвета волн: num_color = 1 def setup(): # создаём окно: size(480, 480) # создаём картинку по размерам канвы: global img img = createImage(width, height, RGB) # размер текста: textSize(22) # рисуем волны: drawSinus() # печатаем сообщение: printMessage() В функции drawSinus рисуем на картинке волны заданного размера и печатаем готовую картинку на экране: 369
# РИСУЕМ ВОЛНЫ def drawSinus(): global num_color, img, wl img.loadPixels() # рисуем волны: for y in range(img.height): # цвет очередной полоски: fc = 255 * (1 + sin(y / wl)) / 2 if (num_color == 1): # синие полоски: clr = color(0, 0, fc) elif (num_color == 2): # жёлтые полоски: clr = color(fc, fc, 0) elif (num_color == 3): # красные полоски: clr = color(fc, 0, 0) else: # зелёные полоски: clr = color(0, fc, 0) for x in range(img.width): # окрашиваем пиксель: img.set(x, y, clr) img.updatePixels() image(img, 0, 0) Цвет полосок изменяем в функции keyPressed: # НАЖИМАЕМ КЛАВИШУ 1..4 def keyPressed(): global num_color num_color = int(key) def draw(): drawSinus() printMessage() 370
А длину волны – в функции mouseDragged: # ПЕРЕМЕЩАЕМ МЫШКУ def mouseDragged(): global wl dy = mouseY - pmouseY if (dy < 0): wl += 1 else: wl -= 1 if (wl < 1): wl = 1. print(u"Длина волны = " + str(wl)) # ПЕЧАТАЕМ СООБЩЕНИЕ def printMessage(): fill(0) noStroke() text(u"Нажимайте клавиши 1..4 для смены цвета", 10, 30) Теперь вы можете морщить волны по своему вкусу и настроению: 371
Проект Цветные волны 2 Исходный код программы находится в папке ImgSinus2. Давайте освоим масштабирование картинок при печати и вывод их в заданную позицию окна. Для этого слегка изменим код предыдущего проекта: # Цветные волны 2 # длина волны синусоиды: wl = 10. def setup(): # создаём окно: size(480, 480) # создаём картинку по размерам канвы: global img img = createImage(width, height, RGB) # размер текста: textSize(22) # рисуем волны: drawSinus() def draw(): drawSinus() # ПЕРЕМЕЩАЕМ МЫШКУ def mouseDragged(): global wl dy = mouseY - pmouseY if (dy < 0): wl += 1 else: wl -= 1 if (wl < 1): 372
wl = 1. print(u"Длина волны = " + str(wl)) # РИСУЕМ ВОЛНЫ def drawSinus(): global img for i in range(1, 4+1): img.loadPixels() # рисуем волны: for y in range(img.height): # цвет очередной полоски: fc = 255 * (1 + sin(y / wl)) / 2 if (i == 1): # синие полоски: clr = color(0, 0, fc) elif (i == 2): # жёлтые полоски: clr = color(fc, fc, 0) elif (i == 3): # красные полоски: clr = color(fc, 0, 0) else: # зелёные полоски: clr = color(0, fc, 0) for x in range(img.width): # окрашиваем пиксель: img.set(x, y, clr) img.updatePixels(); if (i == 1): # синие полоски: image(img, 0, 0) elif (i == 2): # жёлтые полоски: image(img, width / 2, 0) elif (i == 3): # красные полоски: image(img, 0, height / 2) else: # зелёные полоски: image(img, width / 2, height / 2) 373
Функция drawSinus печатает «уполовиненные» картинки, и мы получаем целый набор волн разного цвета: В функции drawSinus мы рисуем картинки в полный размер, а затем масштабируем их только в учебных целях! В реальном приложении следует сразу готовить картинку нужного размера. Размеры картинки хранятся в полях width и height, которые доступны и для записи, то есть, присваивая им нужные значения, мы изменяем физические размеры картинки (масштабируем её): 374
# РИСУЕМ ВОЛНЫ def drawSinus(): . . . img.width = 120 img.height = 120 if (i == 1): # синие полоски: image(img, 0, 0) elif (i == 2): # жёлтые полоски: image(img, width / 2, 0) elif (i == 3): # красные полоски: image(img, 0, height / 2) else: # зелёные полоски: image(img, width / 2, height / 2) 375
Проект Функция resize Исходный код программы находится в папке ImgResize. Функция resize служит для изменения размеров картинки: resize(width: int, height: int) width, height – новые размеры картинки в пикселях Для пропорционального изменения размеров картинки один из параметров должен быть равен 0. def settings(): global img img = loadImage(u"data/тюльпаны.jpg") def setup(): # создаём окно: size(480, 320) background(0) # пропорционально изменяем # размеры картинки: global img img.resize(img.width / 3, 0) # печатаем её на канве: image(img, 0, 0) 376
Проект Функция copy Исходный код программы находится в папке ImgCopy. Функция copy копирует всю картинку или её часть в другую картинку: img.copy() → PImage img.copy(sx: int, sy: dx: int, dy: img.copy(src: PImage, dx: int, dy: int, sw: int, dw: sx: int, int, dw: int, sh: int, int, dh: int) → PImage sy: int, sw: int, sh: int, int, dh: int) Здесь: sx, sy – координаты верхнего левого угла прямоугольной области исходной картинки sw, sh – размеры прямоугольной области исходной картинки dx, dy – координаты верхнего левого угла на целевой картинке dw, dh – размеры прямоугольной области на целевой картинке src – исходная картинка Если размеры исходной и целевой картинок не совпадают, то копируемое изображение соответствующим образом масштабируется: 377
Загружаем картинку в переменную img: def settings(): global img img = loadImage(u"data/тюльпаны.jpg") def setup(): # создаём окно: size(640, 480) background(0) Создаём вспомогательную картинку нужных размеров: # создаём вспомогательную картинку: tmp = createImage(500, 400, RGB) Вырезаем из картинки img прямоугольник 200 х 100 пикселей, левый верхний угол которого находится в точке (0, 0), и копируем его в картинку tmp, начиная с угла (10, 10). Размеры целевого прямоугольника увеличиваем до 500 х 400 пикселей: # копируем в неё часть картинки img: tmp.copy(img, 0,0, 200, 100, 10, 10, 500, 400) # печатаем копию на канве: image(tmp, 0, 0) После печати целевой картинки на канве видно, что фрагмент исходной картинки увеличился. → 378
Проект Функция save Исходный код программы находится в папке ImgSave. Понравившуюся картинку вы можете сохранить в файле, указав его имя в функции save: save(filename: string) filename – строка - название файла. Если расширение отсутствует, то картинка будет записана в формате tif. В противном случае – в указанном формате png, tga или jpg. Допишите в конец функции setup предыдущего проекта 2 строки: tmp.save("tulip.jpg") tmp.save("tulip") Если вы хотите записать кадры в методе draw, то воспользуйтесь функцией saveFrame: saveFrame() saveFrame(filename: string) filename – строка - название файла с расширением tif, tga, jpg или png. 379
Запись на диск - операция довольно медленная, поэтому скорость выполнения программы уменьшится. По умолчанию формат файлов .tif. Для ускорения работы используйте формат .jpg. А сейчас мы напишем программу, которая покажет, как использовать операцию копирования с одновременным масштабированием на практике. Проект Лупа Исходный код программы находится в папке Lupa. С лупой знаком всякий коллекционер марок, искатель сокровищ, детектив или следопыт. Этот оптический прибор имеется, правда в виртуальном воплощении, и во всех графических редакторах, где он используется для увеличения «мелкоскопического» изображения: Лупа в Фотошопе 380
Лупа в CorelDRAW Но эти инструменты увеличивают всё изображение целиком, а наше приложение сможет укрупнить любую часть картинки по нашему желанию. Лупы обычно имеют круглую форму, но нам удобнее сделать её прямоугольной. А чтобы изменять масштаб изображения, мы прибегнем к помощи ползунка: # ПИКСЕЛЬНАЯ ЛУПА add_library('controlP5') lupa = None flgDrag = False mx = my = 0 # КЛАСС ЛУПЫ class Lupa(): def __init__(self): self.x = 0 self.y = 0 self.w = 0 self.h = 0 self.rd = 0 self.rclr = 0 self.img = None def over(self, px, py): return (px >= self.x and px <= self.x + self.w and \ py >= self.y and py <= self.y +self. h) 381
Класс лупы имеет такие поля: x, y – координаты верхнего левого угла лупы w, h – её размеры rd, rclr – толщина и цвет рамки img – увеличенный фрагмент исходного изображения Метод over возвращает True, если заданная точка (px, py) находится внутри лупы. Этот метод, а также переменные flgDrag, mx и my нужны нам для перемещения лупы по исходной картинке. В функциях settings и setup мы создаём окно по размерам выбранной картинки, загружаем её в переменную img и впечатываем в фон окна. Затем создаём ползунок с надлежащими параметрами и присваиваем переменной flgDrag значение False, которое означает, что лупу мы пока не перемещаем: # СОЗДАЁМ ОКНО def settings(): global img img = loadImage("data/auto.jpg") size(800, 600) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём ползунок: global cp5 cp5 = ControlP5(this) cp5.addSlider("zoom")\ .setPosition(10, height - 40)\ .setSize(width - 40, 20)\ .setRange(1, 3)\ .setValue(1.6) # создаём лупу: global lupa lupa = Lupa() 382
lupa.x = 0 lupa.y = 0 lupa.w = 240 lupa.h = 160 lupa.img = createImage(lupa.w, lupa.h, RGB) lupa.rclr = color(255,255,0) lupa.rd = 2.0 flgDrag = False Закончив приготовления, переходим в функцию draw, где и вырисовываем всю эту красоту на экране: # ОБНОВЛЯЕМ СЦЕНУ def draw(): Сначала мы восстанавливаем фон – это необходимо, так как лупа и ползунок «портят» его: global img, lupa background(img) Затем считываем показания движка и узнаём увеличение лупы: global cp5 # увеличение: zoom = cp5.getValue("zoom") По этому значению, а также по координатам и размерам лупы копируем в её поле img часть исходного изображения, то есть фона. Это самая ответственная операция во всём приложении, поэтому разберите её досконально: # копируем фрагмент фона в картинку лупы: 383
lupa.img.copy(img, lupa.x, lupa.y, \ int(lupa.w / zoom), int(lupa.h / zoom), \ 0, 0, \ lupa.w, lupa.h) Скопированное изображение печатаем на экране с помощью функции image или set: # обновляем лупу на экране: image(lupa.img, lupa.x, lupa.y, lupa.w, lupa.h) #set(lupa.x, lupa.y, lupa.img) И обводим его рамкой, чтобы оно не потерялось на экране: # обводим её контуром: stroke(lupa.rclr) strokeWeight(lupa.rd) noFill() rect(lupa.x, lupa.y, lupa.w, lupa.h) Проверяем флаг flgDrag. Если он установлен, значит, пользователь двигает лупу по исходному изображению: if (flgDrag): global mx, my dx = mouseX - mx dy = mouseY - my if (lupa.x + dx + lupa.x + dx > lupa.x += dx if (lupa.y + dy + lupa.y + dy > lupa.y += dy mx = mouseX my = mouseY lupa.w < width and \ 0): lupa.h < height and \ 0): 384
Здесь важно проследить, чтобы лупа не покидала окно. Для перемещения лупы пользователь должен внутри неё нажать кнопку мышки. А мы об этом узнаём по результату проверки координат курсора (его горячей точки) в функции over: # НАЖИМАЕМ КНОПКУ МЫШКИ def mousePressed(): global lupa, flgDrag, mx, my if (lupa.over(mouseX, mouseY)): flgDrag = True mx = mouseX my = mouseY Если пользователь - парень не промах, то функция вернёт True, и функция-обработчик нажатия кнопки мышки mousePressed установит флажок flgDrag в True и запомнит текущие координаты мышки в переменных mx и my. Они помогут нам перемещать лупу вслед за курсором. Как только пользователь отпустит кнопку мышки, функция-обработчик этого события mouseReleased сбросит флажок flgDrag, и лупа остановится: # ОТПУСКАЕМ КНОПКУ МЫШКИ def mouseReleased(): global flgDrag flgDrag = False А теперь нам пора перейти к подробному изучению происходящих событий. Запускаем приложение, устанавливаем крупное увеличение и водим лупу мышкой - от её зоркого глаза никто и ничто не скроется: 385
Проект Летающие мячи Исходный код программы находится в папке Balls. А теперь давайте напишем программу, в которой картинки выводятся на экран в заданном месте. За основу мы возьмём проект Бойкие четырёхугольники, но заменим точки реалистичными картинками футбольных мячей: 386
Для удобства загрузки дадим файлам «числовые» имена и запишем их в список FILE_NAME: # размеры окна: WIDTH = 680 HEIGHT = 550 # число мячей: COUNT = 5 # Список мячей:: balls = [] FILE_NAME = ["0.png", "1.png", "2.png", "3.png", "4.png"] Класс Ball очень простой, поскольку он хранит только самую важную информацию о мяче: # КЛАСС МЯЧА: class Ball(): # координаты: x = 0 y = 0 # картинка: img = None # радиус мяча: r = 0 # перемещение: dx = 0 dy = 0 В функции settings создаём 5 мячей из картинок: 387
def settings(): # создаём мячи: for i in range(COUNT): b = Ball() b.img = loadImage("data/" + FILE_NAME[i]) balls.append(b) А в функции setup создаём поле подобающих размеров и задаём свойства мячей: def setup(): # создаём окно: size(WIDTH, HEIGHT) background(155); imageMode(CENTER); # задаём свойства мячей: for b in balls: r = b.img.width / 2 b.r = r b.x = random(r, width - r) b.y = random(r, height - r) b.dx = random(4, 8) b.dy = random(4, 8) Обратите внимание, что мы установили режим CENTER, чтобы поместить координаты x и y в центр картинки, что вполне естественно для круглого мяча. В функции draw мы немного улучшили алгоритм отскока мячей от границ окна: # РИСУЕМ МЯЧИ def draw(): noStroke() fill(100, 100) rect(0, 0, width, height) global balls for b in balls: 388
r = b.r; newX = b.x + b.dx if (newX + r > width): b.x = width - r b.dx = -b.dx if (random(100) > 80): background('#FF0000') elif (newX - r < 0): b.x = r b.dx = -b.dx if (random(100) > 80): background('#00FF00') else: b.x = newX newY = b.y + b.dy if (newY + r > height): b.y = height - r b.dy = -b.dy if (random(100) > 80): background('#0000FF') elif (newY - r < 0): b.y = r b.dy = -b.dy if (random(100) > 80): background('#FF00FF') else: b.y = newY # рисуем мяч на экране: image(b.img, b.x, b.y) Теперь при столкновении с границей мяч не только изменяет направление движения, но и возвращается в позицию соприкосновения со стенкой. Это не совсем точно отражает физические процессы, но всё-таки создаёт вполне реалистичную картину мира. Время от времени при столкновении мяча со стенкой всё окно окрашивается в тот или иной цвет, что поднимает настроение и веселит публику. 389
Если вас обескураживает беспрепятственное проникновение мячей друг сквозь друга, то считайте, что они находятся на разном удалении от вас. Либо добавьте проверку столкновения мячей не только с границами поля, но и с себе подобным геометрическими собратьями. 390
Функция blend Функция blend действует аналогично функции copy, но имеет дополнительный параметр mode: blend(sx: int, sy: dx: int, dy: blend(src: PImage, dx: int, dy: int, sw: int, dw: sx: int, int, dw: int, sh: int, dh: sy: int, int, dh: int, int, mode: int) sw: int, sh: int, int, mode: int) Режим наложения mode может принимать следующие значения: • BLEND – конечный цвет вычисляется по формуле: clr = src*factor + dest. Если исходный рисунок непрозрачный, то никаких изменений цвета не будет. • ADD - конечный цвет вычисляется по формуле: clr = min(src*factor + dest, 255). Эти два режима суммируют значения составляющих цвета всех пикселей исходного и целевого изображения (с учётом прозрачности исходного изображения) и присваивают их пикселям целевого изображения. Режим ADD при этом учитывает максимальное значение составляющей цвета: Режимы BLEND (слева) и ADD (справа) • SUBTRACT - конечный цвет вычисляется по формуле: clr = max(dest - src*factor, 0). Из значения составляющих цвета пикселей конечного изображения вычитаются значения составляющих цвета пикселей исходного изображения, поэтому конечное изображение становится более тёмным: 391
Режим SUBTRACT • DARKEST - конечный цвет вычисляется по формуле: clr = min(src*factor, dest). В этом режиме выбирается наиболее тёмный пиксель из соответствующих пикселей исходного и конечного изображения, поэтому общая картинка также становится более тёмной: Режим DARKEST • LIGHTEST - конечный цвет вычисляется по формуле: clr = max(src*factor, dest). А в этом режиме, наоборот, из двух пикселей выбирается более светлый: Режим LIGHTEST 392
• DIFFERENCE – значения составляющих цвета вычитаются: Режим DIFFERENCE • EXCLUSION – этот режим действует аналогично предыдущему, но в более мягкой форме: Режим EXCLUSION • MULTIPLY – умножает цвета; конечное изображение всегда темнее исходного: Режим MULTIPLY 393
• SCREEN – осветляет изображение: Режим SCREEN • OVERLAY – сочетает режимы MULTIPLY и SCREEN. Затемняет тёмные цвета и осветляет светлые: Режим OVERLAY • HARD_LIGHT – применяет режим SCREEN к цветам, значения которых больше 50% серого, и режим MULTIPLY к меньшим значениям серого: Режим HARD_LIGHT 394
• SOFT_LIGHT – сочетает режимы DARKEST и LIGHTEST. Действует подобно режиму OVERLAY, но более мягко: Режим SOFT_LIGHT • DODGE – повторяет режим Color Dodge в Фотошопе. Осветляет светлые участки изображения и увеличивает общий контраст: Режим DODGE • BURN – повторяет режим Color Burn в Фотошопе. Затемняет тёмные участки исходного изображения и увеличивает общий контраст: Режим BURN 395
В функции blend: src, dest – цвет первого и второго изображения, соответственно factor – значение составляющей цвета alpha исходного изображения В первой функции blend участвует только одно изображение, которое одновременно служит и исходным, и целевым. Для второй нужны 2 разных изображения – исходное и целевое. Чтобы лучше понять, как работают режимы наложения, мы напишем 2 программы – для каждой функции blend. Проект Режимы наложения Исходный код программы находится в папке Function_blend. За основу нового приложения мы возьмём проект Лупа, но на этот раз обойдёмся без ползунка, поскольку масштабирование картинки будет только сбивать нас с толку. В функции settings загружаем ту же картинку с автомобилями, что и раньше, но частично прозрачную, поскольку некоторые режимы учитывают прозрачность при вычислении конечного значения цвета пикселя. Это диктует нам выбор формата картинки .png, который поддерживает прозрачность (в отличие от формата .jpg): # СОЗДАЁМ ОКНО def settings(): global img img = loadImage("data/auto.png") size(800, 600) 396
В функции draw мы копируем часть исходного изображения (фона) под лупой в переменную lupa.img: # ОБНОВЛЯЕМ СЦЕНУ def draw(): global img, lupa # обновляем фоновую картинку: background(255) image(img,0,0) # копируем фрагмент фона в картинку лупы: lupa.img.copy(img, lupa.x, lupa.y, \ lupa.w, lupa.h, 0, 0, \ lupa.w, lupa.h) А затем накладываем его на исходное изображение с помощью функции blend, указав нужный режим наложения: lupa.img.blend(img, lupa.x, lupa.y, \ lupa.w, lupa.h, \ 0, 0, lupa.w, lupa.h, \ BURN) #LIGHTEST) #MULTIPLY) # BURN) Результирующую картинку lupa.img впечатываем в фон вместо исходного изображения: # обновляем лупу на экране: image(lupa.img, lupa.x, lupa.y, lupa.w, lupa.h) . . . Перемещая лупу по фоновой картинке, вы можете наблюдать действие режимов наложения: 397
Наложение изображений в режиме BURN 398
Проект Режимы наложения 2 Исходный код программы находится в папке Function_blend2. Для второго эксперимента нам нужна ещё одна картинка. Можно взять любое цветное изображения, например, с градиентной заливкой: И в функции settings загружаем обе картинки: # СОЗДАЁМ ОКНО def settings(): global img1, img2 img1 = loadImage("data/auto.png") img2 = loadImage("data/grad.png") size(800, 600) В функции draw проделываем те же операции, что и в первом случае, но при наложении используем вторую картинку img2: # ОБНОВЛЯЕМ СЦЕНУ def draw(): global img1, img2, lupa # обновляем фоновую картинку: background(255) image(img1,0,0) # копируем фрагмент фона в картинку лупы: lupa.img.copy(img1, lupa.x, lupa.y, \ lupa.w, lupa.h, 0, 0, \ lupa.w, lupa.h) 399
lupa.img.blend(img2, 0, 0, \ lupa.w, lupa.h, \ 0, 0, lupa.w, lupa.h, \ BURN) #LIGHTEST) #MULTIPLY) # BURN) # обновляем лупу на экране: image(lupa.img, lupa.x, lupa.y, lupa.w, lupa.h) Поскольку целевая картинка окрашена во все цвета радуги, то очень легко наблюдать, как они смешиваются с цветами фона: 400
Проект Цветные чернила Исходный код программы находится в папке Function_tint. Функция blend реально перекрашивает второе изображение, поэтому если оно вам необходимо и для других целей, то придётся завести ещё одну переменную для хранения этого изображения. Но в Процессинге имеется функция tint, которая окрашивает (тонирует) изображение только при его печати на канве (исходное изображение остаётся без изменений!): tint(gray: float) tint(gray: float, alpha: float) tint(rgb: int) tint(rgb: int, alpha: float) tint(v1: float, v2: float, v3: float) tint(v1: float, v2: float, v3: float: float, alpha: float) Здесь: gray – серый цвет; значения по умолчанию 0..255 rgb – хроматический цвет; значения по умолчанию больше 255 v1, v2, v3 –составляющие цвета r, g, b / hue, saturation, brightness, соответственно alpha – составляющая прозрачности Важно помнить, что функция tint действует на все изображения, которые печатаются на экране. В случае необходимости нужно вызвать функцию noTint, которая отменяет действие функции tint: noTint() 401
Функция tint позволяет легко управлять прозрачностью изображения, не затрагивая его цветов. Для этого первый параметр должен быть равен 255, а второй – значению прозрачности: tint(255, alpha: float) И хотя действие функции tint очень напоминает тонирование фотографий, но не всегда просто представить себе конечный результат такого тонирования, поэтому мы напишем небольшое приложение для изучения этой функции. Поскольку самая мощная функция tint имеет 4 параметра, то мы установим столько же ползунков для плавного изменения их значений: # ФУНКЦИЯ tint add_library('controlP5') # размеры окна: WIDTH = 800 HEIGHT = 600 # длина ползунка: LENGTH = WIDTH - 110 def settings(): global img img = loadImage("data/auto.jpg") # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): size(WIDTH, HEIGHT) background(img) # создаём ползунки: global cp5 402
cp5 = ControlP5(this) cp5.addSlider("A")\ .setPosition(10, HEIGHT - 160)\ .setSize(LENGTH, 20)\ .setRange(0, 255)\ .setValue(255) cp5.addSlider("R")\ .setPosition(10, HEIGHT - 120)\ .setSize(LENGTH, 20)\ .setRange(0, 255)\ .setValue(255) cp5.addSlider("G")\ .setPosition(10, HEIGHT - 80)\ .setSize(LENGTH, 20)\ .setRange(0, 255)\ .setValue(255) cp5.addSlider("B")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(0, 255)\ .setValue(255) # размер текста: textSize(20) В функции draw мы снимаем показания с ползунков и передаём их функции tint. При печати исходной картинки функцией image она тонируется установленным цветом и дополнительно приобретает заданную прозрачность. Именно поэтому мы сначала должны покрасить фон в чистый белый цвет, иначе он будет искажать цвета прозрачных изображений: # РИСУЕМ КРУГИ def draw(): background(255) 403
# цвет: global cp5, r,g,b,a a = int(cp5.getValue("A")) r = int(cp5.getValue("R")) g = int(cp5.getValue("G")) b = int(cp5.getValue("B")) noStroke() fill(255,255,0) text('A=' + str(a), WIDTH - 80, HEIGHT - 140) fill(255,0,0) text('R=' + str(r), WIDTH - 80, HEIGHT - 100) fill(0,255,0) text('G=' + str(g), WIDTH - 80, HEIGHT - 60) fill(0,0,255) text('B=' + str(b), WIDTH - 80, HEIGHT - 20) tint(r, g, b, a) image(img, 0, 0) noTint() 404
Изменяем прозрачность изображения 405
Тонируем изображение 406
Проект Фильтруем снимки Исходный код программы находится в папке Pointilize. Вам, без сомнения, не раз попадались на глаза необычные снимки, как бы составленные из цветных квадратиков или точек. Может быть, вы и сами делали такие картинки в Фотошопе, который имеет немало фильтров для придания фотографиям нового облика. Например, фильтр Pointilize, который может увеличивать пиксели исходного изображения в несколько раз: В итоге получается «художественное» изображение, похожее на картины, написанные в технике пуантилизма (цветными точками): 407
Фрагмент картины Ж. Сёра Мы не будем замахиваться на достижения искусства или даже всеми нами любимого Фотошопа, а напишем простенький, но свой фильтр, который превратит любую фотографию в «мозаику». Для экспериментов всё-таки лучше взять не любую фотографию, а красочную и без мелких деталей, которые неизбежно исчезнут при пикселизации. Если вам приходилось видеть картины, вышитые крестиком, то смело берите пример с них. 408
В начало программы добавим переменную r, которая будет отвечать за величину точек отфильтрованного изображения: # ПРОГРАММА ДЛЯ ПИКСЕЛИЗАЦИИ ИЗОБРАЖЕНИЯ # размеры окна: WIDTH = 800 HEIGHT = 533 # радиус точки: r = 10 Размеры окна нужно подогнать под выбранную вами фотографию. Поместите её в папку data и при запуске программы впечатайте в канву функцией background. В качестве примера я взял фотографию с красными тюльпанами, которая на экране смотрится совсем неплохо: def settings(): global img img = loadImage(u"data/тюльпаны.jpg") def setup(): # создаём окно: size(WIDTH, HEIGHT) background(img) ellipseMode(CORNER) rectMode(CORNER) # применяем фильтр: setFilter() 409
Мы напишем два фильтра, которые отличаются друг от друга только тем, что в первом точки - квадратные, а во втором - круглые. С точки зрения геометрии, разница небольшая, но в искусстве, как известно мелочей нет, поэтому пробуйте разные варианты, пока не добьётесь совершенства. Первый фильтр действует очень просто. Мы разбиваем всё изображение на квадратики со стороной l пикселей и с помощью двух циклов for сканируем изображение. Для каждого квадратика находим цвет пикселя в его центре, а затем всё изображение внутри квадратика заливаем этим цветом: # ПЕРВЫЙ ФИЛЬТР def setFilter(): l = 2 * r for j in range(height // l + 1 ): for i in range(width // l): # определяем цвет пикселя в центре точки: 410
xc = i * l + r yc = j * l + r clr = img.get(xc, yc) fill(clr) # чертим квадрат: x1 = xc - r y1 = yc - r rect(x1, y1, l, l) В результате изображение становится более грубым, как бы состоящим из огромных пикселей. Подобный эффект вы можете наблюдать при большом увеличении фотографий. Правда, в этом случае увеличиваются и размеры картинки, поэтому все пиксели исходного изображения остаются в целости и сохранности, наш же фильтр изменяет цвет большинства пикселей. 411
Вы можете убрать контур у квадратов или изменить его цвет. Для данной картинки лучшие результаты получаются с чёрным контуром, но для других картинок он может оказаться излишне контрастным. Если вместо квадратиков мы нарисуем кружки, то картинка станет более привлекательной: Функция get работает очень медленно, поэтому не уменьшайте размеры клеток! 412
Второй фильтр более «изощрённый». Сначала мы запоминаем цвет пикселя в центре квадрата, затем закрашиваем его чёрным цветом. Вы можете вообще не закрашивать квадрат, или закрашивать его другим цветом. Иногда таким простым способом удаётся получить хорошие результаты! А уже потом, «по-чёрному» рисуем кружок заданного цвета: # ВТОРОЙ ФИЛЬТР def setFilter2(): l = 2 * r for j in range(height // l + 1 ): for i in range(width // l): # определяем цвет пикселя в центре точки: xc = i * l + r yc = j * l + r clr = img.get(xc, yc) fill(clr) # чертим квадрат: x1 = xc - r y1 = yc - r #rect(x1, y1, l, l) fill(0) rect(x1, y1, l, l) fill(clr) noStroke() ellipse(x1, y1, l, l) Если каждый цветной кружок вышить крестиком (а лучше не полениться и вышить двойным крестиком), то получится прекрасная картина: 413
Проект Фильтруем снимки 2 Исходный код программы находится в папке Pointilize2. Попробуем ускорить процесс фильтрации, записывая цвета нужных нам пикселей в список colors: 414
# ПРОГРАММА ДЛЯ ПИКСЕЛИЗАЦИИ ИЗОБРАЖЕНИЯ 2 add_library('controlP5') # размеры окна: WIDTH = 800 HEIGHT = 533 # длина ползунка: LENGTH = WIDTH - 30 def settings(): global img img = loadImage(u"data/тюльпаны.jpg") Этот приём позволит нам изменять размеры кружков в работающей программе с помощью ползунка. Его мы создаём в функции setup: def setup(): # создаём окно: size(WIDTH, HEIGHT) background(img) ellipseMode(CORNER) rectMode(CORNER) # создаём ползунок: global cp5 cp5 = ControlP5(this) cp5.addSlider("R")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(4, 30)\ .setValue(5) Тут же начинает работать функция draw, в которой мы узнаём текущий радиус кружков r: def draw(): # радиус: global cp5, r 415
r = int(cp5.getValue("R")) # создаём таблицу цветов: createTable(); # применяем фильтр: setFilter() После чего отправляемся в функцию createTable для заполнения списка colors цветами пикселей, которые находятся в центре кругов: # СОЗДАЁМ ТАБЛИЦУ ЦВEТОВ def createTable(): l = 2 * r background(img) global colors colors = [] loadPixels() for j in range(int(height / l) + 1): for i in range(int(width / l) + 1): # определяем цвет пикселя в центре точки: xc = i * l + r yc = j * l + r id = yc * width + xc if id >= len(pixels): return clr = pixels[id] colors.append(clr) Здесь нужно обновить фоновую картинку, поскольку пиксели мы берём из неё. Теперь в функции setFilter мы спокойно извлекаем цвета нужных нам пикселей и закрашиваем кружки: # ФИЛЬТР def setFilter(): l = 2 * r id = 0; #stroke(0) 416
background(0) global colors for j in range(int(height / l) + 1): for i in range(int(width / l) +1): # определяем цвет пикселя в центре точки: xc = i * l + r; yc = j * l + r; clr = colors[id] id += 1 if id >= len(colors): return fill(clr) # чертим квадрат: x1 = xc - r y1 = yc - r #rect(x1, y1, l, l) ellipse(x1, y1, l, l) Скорость программы значительно выросла, и вы можете фильтровать картинки через дырочки разного диаметра: 417
Получилось здорово, но было бы ещё здоровее, если бы можно было не запоминать цвета пикселей в списке, а сразу вертеть кружочки. Увы, так не получится. Проект Фильтруем снимки 2А Исходный код программы находится в папке Pointilize2a. А вот и получится! В конце функции setup загружаем пиксели картинки в список. Так как картинка не изменяется, то достаточно это сделать всего 1 раз: def setup(): . . . # загружаем пиксели картинки # в список pixels: loadPixels() В функции draw вызываем обновлённую функцию setFilter: def draw(): # радиус: global cp5, r r = int(cp5.getValue("R")) # применяем фильтр: setFilter() А в функции setFilter цвет пикселя извлекаем из списка пикселей: 418
# ФИЛЬТР def setFilter(): l = 2 * r # очищаем канву: background(0) for j in range(int(height / l) + 1): for i in range(int(width / l) +1): # определяем цвет пикселя в центре точки: xc = i * l + r; yc = j * l + r; id = yc * width + xc if id >= len(pixels): return clr = pixels[id] fill(clr) # чертим квадрат: x1 = xc - r y1 = yc - r #rect(x1, y1, l, l) ellipse(x1, y1, l, l) Вот теперь наша программа действует быстро и аккуратно! Проект Картинные фильтры Исходный код программы находится в папке ImgFilter. Процессинг имеет и несколько собственных фильтров, которые мы и освоим на практике. Чтобы получить желаемый эффект, нужно вызвать метод filter и при необходимости задать значение параметра param: 419
img.filter(mode: int) img.filter(mode: int, param: float) Режимы mode: GRAY – конвертирует цветное изображение в оттенки серого: OPAQUE – превращает прозрачное изображение в полностью непрозрачное. INVERT – инвертирует изображение: 420
ERODE – сужает светлые области: 421
DILATE – расширяет светлые области: 422
THRESHOLD – конвертирует изображение в чёрно-белое, в зависимости от значения цвета пикселя. Параметр изменяется от нуля до единицы. Чем больше его значение, тем больше будет чёрных пикселей в конечном изображении: 423
Если параметр не указан, используется значение по умолчанию, равное 0.5. POSTERIZE – имитирует эффект плаката, уменьшая число цветов в изображении. Параметр изменяет значения от 2 до 255, но лучшие результаты дают небольшие значения: 424
BLUR (Guassian blur) - размывает изображение пропорционально значению параметра: 425
Если параметр не указан, используется радиус размытия 1 пиксель. Для изменения параметров некоторых фильтров нам понадобится ползунок: # ПРОГРАММА ДЛЯ ФИЛЬТРАЦИИ ИЗОБРАЖЕНИЯ add_library('controlP5') # размеры окна: WIDTH = 800 426
HEIGHT = 600 # длина ползунка: LENGTH = WIDTH - 30 def settings(): global img img = loadImage("data/auto.jpg") В функции setup создаём ползунок и картинку img2, в которую будем копировать исходную картинку, чтобы не испортить её при фильтровании: def setup(): # создаём окно: size(WIDTH, HEIGHT) # создаём картинку для копирования: global img, img2 img2 = createImage(img.width, img.height, RGB) # создаём ползунок: global cp5 cp5 = ControlP5(this) cp5.addSlider("A")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(0, 100)\ .setValue(50) Сами фильтры применяем в функции draw: def draw(): # радиус: global cp5, img, img2 a = cp5.getValue("A") img2.copy(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height) 427
# фильтры без параметров: #img2.filter(GRAY) #img2.filter(INVERT) #img2.filter(ERODE) #img2.filter(DILATE) # фильтры с параметрами: #img2.filter(THRESHOLD, a/100) #img2.filter(POSTERIZE, max(2, a / 10)) img2.filter(BLUR, a/10) image(img2, 0, 0) Раскомментируйте нужный вам фильтр и запустите программу. Если фильтр имеет параметр, то его можно изменять ползунком. Проект Правила маскарада Исходный код программы находится в папке Mask. В графике, как и в жизни, маску надевают для того, чтобы скрыть часть изображения. Правда, компьютерная маска – это просто полутоновая картинка, которую «накладывают» на исходное изображение. При этом чёрные области маски делают изображение полностью невидимым, через белые области маски изображение видно без изъяна, а серые участки маски придают ему частичную прозрачность – пропорционально «черноте» этих участков. Этот приём часто используют в графических программах, чтобы устранить непрозрачный фон изображения. В метод mask нужно передать изображение-маску: 428
img.mask(imgMask: PImage) Но прежде нужно загрузить картинки с диска: # размеры окна: WIDTH = 300 HEIGHT = 300 def settings(): global img1, img2 img1 = loadImage("data/grad.jpg") img2 = loadImage("data/star.gif") img2.filter(INVERT) def setup(): # создаём окно: size(WIDTH, HEIGHT) background(0) imageMode(CENTER) global img1, img2 img1.mask(img2) image(img1, 150, 150) def draw(): pass В качестве исходной я выбрал картинку с радиальным градиентом. → Маску можно нарисовать в любом графическом редакторе и сохранить её в формате gif. Не забывайте, что размеры основного изображения и маски должны совпадать: 429
Применим маску к картинке: Прозрачная область вырезает из картинки изображение звезды. 430
Проект Буферная графика Исходный код программы находится в папке Balls2. Изученные нами графические функции выполняют операции непосредственно на канве. Но довольно часто в графических приложениях необходимо создать кадр (сцену) в памяти компьютера, а уже потом целиком вывести его на экран. Для этого нужно внеэкранную поверхность, которую можно создать функцией createGraphics: createGraphics(width: int, height: int, [renderer]: string) → PGrapics Здесь: w – ширина графического буфера h – высота графического буфера renderer – визуализатор: P2D или P3D. По умолчанию используется режим P2D. Функция возвращает поверхность рисования заданных размеров. Все графические функции можно использовать и для внеэкранной поверхности. Давайте применим внеэкранную поверхность к нашему проекту Летающие мячи. В функции setup создаём внеэкранную поверхность по размерам окна (размеры буфера могут быть произвольными, но в данном случае они должны совпадать с размерами окна, потому что мячи могут оказаться в любой его точке): def setup(): . . . global pg 431
pg = createGraphics(WIDTH, HEIGHT) pg.imageMode(CENTER) pg.smooth() В функции draw мы задействовали те же самые графические функции, что и раньше, но оформили их как методы класса: # РИСУЕМ МЯЧИ def draw(): global pg pg.beginDraw() pg.noStroke() pg.fill(100, 100) pg.rect(0, 0, 680, 550) global balls for b in balls: r = b.r; newX = b.x + b.dx if (newX + r > width): b.x = width - r b.dx = -b.dx if (random(100) > 80): pg.background(255,0,0) elif (newX - r < 0): b.x = r b.dx = -b.dx if (random(100) > 80): pg.background(0,255,0) else: b.x = newX newY = b.y + b.dy if (newY + r > height): b.y = height - r b.dy = -b.dy if (random(100) > 80): pg.background(0,0,255) elif (newY - r < 0): b.y = r b.dy = -b.dy if (random(100) > 80): 432
pg.background(255,0,255) else: b.y = newY # рисуем мяч на экране: pg.image(b.img, b.x, b.y) pg.endDraw() И вот вся сцена готова, и мы можем вывести её на экран. Для этого вызываем функцию image: image(pg, 0, 0) fill(255) text(frameRate, 10, 20) Картина с летающими мячами ничем не отличается от исходного варианта: 433
Трансформации Первый, самый простой способ трансформации фигур (и растровых изображений) состоит в применении функций set и image. Обе функции позволяют выводить фигуру в любом месте окна приложения, а функция image умеет ещё и масштабировать изображения. Второй способ заключается в трансформации системы координат поверхности рисования. При этом фигуру (растровое изображение или геометрические примитивы) мы рисуем в исходном положении. Этот способ более универсальный и позволяет не только печатать фигуры в любом месте окна и масштабировать их, но также перекашивать и поворачивать. Проект Перенос Исходный код программы находится в папке Translate. Функция translate переносит начало координат из точки (0,0) в точку (tx,ty): translate(tx: float, ty: float,[tz: float]) 434
Необязательный параметр tz переносит фигуры ближе/дальше. При последовательных вызовах функции translate переносы суммируются. На рисунке видно, что картинка, верхний левый угол которой находится в начале координат, «переезжает» вместе с осями координат в точку (tx, ty) окна приложения, хотя её положение в системе координат не изменяется. Ещё лучше процесс переноса покажет вам новое приложение. Сначала мы должны приготовить мизансцену для наших трансформаций: 435
# ФУНКЦИЯ translate add_library('controlP5') # размеры окна: WIDTH = 498 HEIGHT = 498 # длина ползунка: LENGTH = WIDTH - 110 # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT) global img, img2 img = loadImage("data/Back.jpg") img2 = loadImage("data/transform.png") # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # печатаем фоновую картинку: background(img) # создаём ползунки: global cp5 cp5 = ControlP5(this) cp5.addSlider("X")\ .setPosition(10, HEIGHT - 80)\ .setSize(LENGTH, 20)\ .setRange(-200, 200)\ .setValue(0) cp5.addSlider("Y")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(-100, 300)\ .setValue(0) # размер текста: textSize(20) 436
Нам понадобятся 2 ползунка, чтобы перемещать систему координат в горизонтальном и вертикальном направлении. В качестве фона мы используем картинку с координатными осями и бледным Якубовичем центре системы координат: Другого Якубовича мы закрепим на трансформированной системе координат, в центре которой он всегда и будет оставаться, несмотря на все перипетии жизни. → За визуальную сторону превращений отвечает функция draw, для которой это привычная обязанность: 437
def draw(): imageMode(CORNER) Для начала мы впечатываем фоновую картинку в окно приложения: background(img) Затем снимаем значения с ползунков: # текущие координаты: x = int(cp5.getValue("X")) y = int(cp5.getValue("Y")) Переносим систему координат в указанную точку канвы: translate(x, y) И печатаем полупрозрачную картинку фона (иначе не будет видно исходное положение системы координат) в новом месте: tint(255, 120) image(img, 0, 0) noTint() А в её центре – румяного Якубовича: imageMode(CENTER) image(img2, width / 2 + 5, height / 2 + 15) 438
И наконец, восстанавливаем матрицу трансформаций, иначе элементы управления «уедут» вместе с Якубовичем: resetMatrix() noStroke() fill(0,0,255) text('X=' + str(x), WIDTH - 80, HEIGHT - 60) fill(255,0,0) text('Y=' + str(y), WIDTH - 80, HEIGHT - 20) Перемещая движки, вы можете наглядно наблюдать, как система координат изменяет своё положение относительно окна приложения, а вместе с ней и картинный Якубович, которого мы неизменно рисуем в центре системы координат: 439
Как видите, кроме собственно функции translate, нам понадобились и другие функции для контроля над матрицей трансформаций. Матрица трансформаций состоит из двух строк и трёх столбцов. В исходном состоянии все элементы, кроме двух, равны нулю (эту матрицу называют матрицей идентичности), а при перемещении системы координат значения в последнем столбце изменяются. → Верхнее значение показывает смещение начала координат по оси X, а нижнее – по оси Y. Функция resetMatrix resetMatrix() восстанавливает матрицу идентичности. Функция push: push() помещает на стек текущую систему координат, а функция pop: pop() извлекает её из стека и тем самым восстанавливает запомненную предыдущей функцией систему координат. Эти функции всегда работают в паре, поэтому, если вы сохранили данные на матричном стеке, то обязательно должны их снять со стека. Если функции вложенные, то соблюдается правило матрёшки. Каждая внутренняя пара функций push-pop должна находиться внутри внешней пары этих функций. 440
Проект Масштабирование Исходный код программы находится в папке Scale. Вторая известная вам трансформация – это масштабирование, то есть изменение размеров изображения на экране. Функция scale: scale(sxy: float) scale(sx: float, sy: float) scale(sx: float, sy: float, sz: float) масштабирует не само изображение, а координатную систему, а это, в свою очередь, приводит к пропорциональному масштабированию изображений. Рисунок показывает, что при масштабировании начало координат остаётся в точке (0,0): 441
Первая функции scale масштабирует координатную систему одинаково по всем осям, а вторая и третья позволяют масштабировать изображение по каждой оси отдельно. Параметры sxy, sx, sy и sz показывают, во сколько раз увеличится (коэффициенты больше 1) или уменьшится (коэффициенты меньше 1) исходное изображение (или объект) при масштабировании, которому соответствуют коэффициенты, равные 1. При последовательных вызовах функции scale коэффициенты масштабирования умножаются. Для наглядного изучения этого вида трансформаций мы напишем новое приложение. Оно отличается от предыдущего всего несколькими строками: 442
# ФУНКЦИЯ scale . . . # создаём ползунки: global cp5 cp5 = ControlP5(this) cp5.addSlider("X")\ .setPosition(10, HEIGHT - 80)\ .setSize(LENGTH, 20)\ .setRange(0, 200)\ .setValue(100) cp5.addSlider("Y")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(0, 200)\ .setValue(100) . . . def draw(): . . . push() scale(x, y) imageMode(CENTER) image(img2, width / 2 + 5, height / 2 + 15) pop() . . . Запускаем приложение и делаем с Якубовичем всё, что захотим. → 443
Коэффициент масштабирования по оси X занимает в матрице первое место в первой строке, а коэффициент масштабирования по оси Y – второе место во второй строке: 444
Проект Масштабирование 2 Исходный код программы находится в папке Scale2. Функция scale позволяет проделывать с картинками «трюки»: при отрицательных значениях параметров система координат «выворачивается наизнанку». Такой вид трансформаций называется зеркальным отражением. Таким образом, при отрицательных значениях коэффициента скалирования картинка дополнительно отражается по вертикали и/или по горизонтали! Чтобы перевернуть систему координат, нам придётся изменить диапазон значений на ползунках: # создаём ползунки: global cp5 cp5 = ControlP5(this) cp5.addSlider("X")\ .setPosition(10, HEIGHT - 80)\ .setSize(LENGTH, 20)\ .setRange(-200, 200)\ .setValue(100) cp5.addSlider("Y")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(-200, 200)\ .setValue(100) А также подправить код в функции draw: def draw(): imageMode(CORNER) background(img) # текущие координаты: 445
x = cp5.getValue("X") / 100 y = cp5.getValue("Y") / 100 push() translate(width/2, height/2) scale(x, y) image(img2, 10, 15) pop() . . . Мы должны перенести начало координат в центр окна, иначе при переворачивании Якубович исчезнет за пределами окна, и мы его не увидим. А вот теперь пора запустить приложение и отправить Якубовича в Зазеркалье: 446
Проект Перекос Исходный код программы находится в папке Shear. За перекос системы координат отвечают функции: shearX(angle: float) shearY(angle: float) Первая перекашивает систему координат вдоль оси X: Вторая – вдоль оси Y: 447
Параметр angle задаёт угол перекоса в радианах. Исходному изображению соответствует угол 0. Опять слегка подправим наше предыдущее приложение и сможем перекашивать Якубовича и вдоль, и поперёк! # ФУНКЦИЯ shear . . . def setup(): . . . # создаём ползунки: global cp5 cp5 = ControlP5(this) cp5.addSlider("X")\ .setPosition(10, HEIGHT - 80)\ .setSize(LENGTH, 20)\ .setRange(-90, 90)\ .setValue(0) 448
cp5.addSlider("Y")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(-90, 90)\ .setValue(0) . . . def draw(): . . . x = radians(cp5.getValue("X")) y = radians(cp5.getValue("Y")) push() translate(width / 2, height / 2) shearX(x) shearY(y) imageMode(CENTER) image(img2, 10, 15) pop() . . . 449
Проект Поворот Исходный код программы находится в папке Rotate. И вот мы добрались до поворота. И в этом случае поворачивается не картинка, а оси координат – вокруг начальной точки: Функция rotate: rotate(angle: float) поворачивает систему координат на угол angle, заданный в радианах. Положительные углы поворачивают её по часовой стрелке, отрицательные – против часовой стрелки. 450
За пару секунд мы переделаем предыдущее приложение так, чтобы оно поворачивало систему координат: # ФУНКЦИЯ rotate Функции setup и draw почти не подверглись изменениям: def setup(): . . . # создаём ползунок: global cp5 cp5 = ControlP5(this) cp5.addSlider("A")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(-90, 90)\ .setValue(0) def draw(): imageMode(CORNER) background(img) # угол поворота: a = cp5.getValue("A") push() translate(width / 2, height / 2) rotate(radians(a)) imageMode(CENTER) image(img2, 10, 15) pop() fill(255,0,0) text('A=' + str(a), WIDTH - 80, HEIGHT - 20) 451
При повороте матрица трансформаций умножается на матрицу поворота, что приводит к изменению четырёх элементов матрицы трансформаций: Проект Трансформаторная анимация Исходный код программы находится в папке MaskAnimation. 452
И напоследок давайте напишем программу с простейшей анимацией. Мы заставим вращаться звезду из проекта Правила маскарада: Сделать это совсем несложно: # размеры окна: WIDTH = 300 HEIGHT = 300 # угол поворота: angle = 0 def settings(): global img1, img2 img1 = loadImage("data/grad.jpg") img2 = loadImage("data/star.gif") img2.filter(INVERT) def setup(): # создаём окно: size(WIDTH, HEIGHT) background(0) 453
imageMode(CENTER) global img1, img2 img1.mask(img2) image(img1, 150, 150) def draw(): fill(0, 30) rect(0, 0, width, height) translate(width / 2, height / 2) global angle rotate(radians(angle)) angle += 3 image(img1, 0, 0) Чтобы звезда вращалась вокруг центра окна и не улетала за его пределы, мы переносим в функции draw начало координат в центр окна и поворачиваем звезду на угол angle, который увеличивается с каждой итерацией на 3 градуса. Обратите внимание, что мы не используем здесь функции push, pop и resetMatrix, поскольку функция draw автоматически восстанавливает матрицу трансформаций после каждого кадра. Почему же мы раньше делали это самостоятельно? – Дело в том, что в этой же функции прорисовываются и элементы управления. Если не восстановить матрицу трансформаций, то они будут нарисованы неверно. В этом проекте элементов управления нет, поэтому нам и не нужно выполнять дополнительные операции. 454
Многоугольники Много-многоугольники мы уже чертили, когда знакомились с параметрическими кривыми. Но можно таким же способом начертить и обычные многоугольники. Проект Многоугольники Исходный код программы находится в папке Poly. У многоугольника должно быть по крайней мере 3 вершины: 455
Число сторон равно числу вершин, если многоугольник замкнуть функцией endShape в режиме CLOSE: endShape(CLOSE) Эта же функция без параметров оставит многоугольник без последней стороны: endShape() Но при этом заливка многоугольника действует верно: 456
Чтобы изменять число вершин в работающей программе, создадим ползунок: add_library('controlP5') # размеры окна: WIDTH = 480 HEIGHT = 480 # длина ползунка: LENGTH = WIDTH - 110 # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # печатаем фоновую картинку: background(0) # создаём ползунок: global cp5 cp5 = ControlP5(this) cp5.addSlider("N")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(3, 40)\ .setValue(3) # размер текста: textSize(22) strokeJoin(ROUND) strokeCap(ROUND) frameRate(0.5) def draw(): # серый фон: background(160) # рисуем многоугольник: 457
drawPoly() Вершины многоугольника создаём в функции drawPoly, где и рисуем его: def draw(): # серый фон: background(160) # рисуем многоугольник: drawPoly() # РИСУЕМ МНОГОУГОЛЬНИК def drawPoly(): # число сторон: n = int(cp5.getValue("N")) # толщина линий: strokeWeight(2) # цвет линии: clr = color(255, 0, 0) stroke(clr) noFill() fill(255,255,0) beginShape() # создаём вершины: for i in range(n): x = random(width - 40) + 10 y = random(height - 40) + 10 vertex(x, y) endShape(CLOSE) #endShape() noStroke() fill(255,0,0) text('N=' + str(n), WIDTH - 80, HEIGHT - 20) Число вершин получаем от ползунка. С треугольниками всё просто – они практически всегда остаются привычными нам треугольниками. А вот четырёхугольники могут получиться разные. Выпуклые и невыпуклые: : 458
И даже с пересекающимися сторонами: 459
Чем больше сторон у случайного многоугольника, тем выше вероятность пересечения сторон. Но это забавное свойство многоугольников можно использовать в компьютерном творчестве. → Проект Многоугольники 2 Исходный код программы находится в папке Poly2. Специально для художественного творчества немного подправим предыдущую программу. Непременно нужно увеличить размеры окна – большие картинки, как правило, смотрятся гораздо лучше: 460
# размеры окна: WIDTH = 1000 HEIGHT = 1000 Число сторон у многоугольника также можно увеличить. Например, до сотни: # создаём ползунок: global cp5 cp5 = ControlP5(this) cp5.addSlider("N")\ .setPosition(10, HEIGHT - 30)\ .setSize(LENGTH, 20)\ .setRange(3, 100)\ .setValue(3) Линии для больших многоугольников следует утолстить: strokeWeight(3) Серый, мышиный фон заменим синим: # фон: background(0, 0, 205) И последний штрих – корректируем координаты вершин: x = random(10, width - 40) + 10 y = random(10, height - 40) + 10 Программа готова для художественного творчества: 461
462
Проект Точки Исходный код программы находится в папке Points. Если параметр в функции beginShape равен POINTS, то можно рисовать отдельными точками: add_library('controlP5') # размеры окна: WIDTH = 1000 HEIGHT = 1000 # длина ползунка: LENGTH = WIDTH - 110 # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # печатаем фоновую картинку: background(0) # создаём ползунок: global cp5 cp5 = ControlP5(this) cp5.addSlider("N")\ .setPosition(10, HEIGHT - 30)\ .setSize(LENGTH, 20)\ .setRange(10, 500)\ .setValue(10) # размер текста: textSize(22) 463
frameRate(1) def draw(): # серый фон: background(0, 0, 205) # рисуем многоугольник: drawPoints() # РИСУЕМ ТОЧКИ def drawPoints(): # число сторон: n = int(cp5.getValue("N")) # размер точек: strokeWeight(random(40) + 10) # цвет точек: clr = random_color() stroke(clr) beginShape(POINTS) # создаём вершины: for i in range(n): x = random(10, width - 40) + 10 y = random(10, height - 50) + 10 vertex(x, y) endShape() noStroke() fill(255,0,0) text('N=' + str(n), WIDTH - 80, HEIGHT - 20) # ВОЗВРАЩАЕМ СЛУЧАЙНЫЙ ЦВЕТ def random_color(): clr = color(random(0,255), random(0, 255), random(0, 255)) return clr Но все точки имеют один и тот же размер и цвет, поэтому картинка получается неинтересная: 464
Но вот если точки расположить регулярно или по каким-либо хитроумным законам, то вполне можно добиться и более художественных результатов. Все остальные режимы функции beginShape также дают малоинтересные фигуры. 465
Проект Закругляемся! Исходный код программы находится в папке Curve. Многоугольные фигуры, как им и полагается, всегда получаются несколько угловатыми. Чтобы их сгладить, можно некоторые вершины создавать скруглёнными. Для этого вместо функции vertex нужно вызвать одну из следующих функций: curveVertex(x, y) x, y – координаты вершины кривой bezierVertex(x1, y1, x2, y2, x, y) x1, y1 – координаты первой контрольной точки x2, y2 – координаты второй контрольной точки x, y – координаты вершины Все параметры имеют тип float. Обе эти функции работают только с функцией beginShape без параметров! 466
Чтобы построить сплайн Кэтмулл-Рома (Catmull-Rom spline) с помощью первой функции, необходимы, как минимум, 4 точки, причём кривая проводится между второй и третьей точкой: Пятая точка добавляет к кривой ещё одну точку, и так далее: 467
Вторая функция строит кривые Безье (Bezier curve). Для каждой вершины необходимо задать 3 точки. Мы используем в нашем проекте только вершины curveVertex. 468
Изменения по сравнению с проектом Многоугольники нужно сделать совсем небольшие. В функции setup учитываем, что для построения кривой необходимо взять не менее четырёх точек: # создаём ползунок: global cp5 cp5 = ControlP5(this) cp5.addSlider("N")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(4, 100)\ .setValue(4) Внесите изменения и в функцию draw: beginShape() # создаём вершины: for i in range(n): x = random(width - 40) + 10 y = random(height - 40) + 10 curveVertex(x, y) #endShape(CLOSE) endShape() Здесь функция beginShape не имеет параметров, а вершины кривой создаёт функция curveVertex. При большом числе вершин получается вполне современная абстрактная картина: 469
470
Проект Контур Исходный код программы находится в папке Contour. Из фигур, созданных в блоке beginShape – endShape, можно вырезать другую фигуру, координаты вершин которой задаются внутри блока функций: beginContour() . . . endContour() При этом направление обхода вершин контура должно быть противоположным по сравнению с вершинами внешней фигуры. Фоном в новом проекте мы назначим картинку с автомобилем: add_library('controlP5') # размеры окна: WIDTH = 800 HEIGHT = 600 # длина ползунка: LENGTH = WIDTH - 110 # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT) global img img = loadImage("data/auto.jpg") 471
# ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # печатаем фоновую картинку: background(img) # создаём ползунок: global cp5 cp5 = ControlP5(this) cp5.addSlider("DY")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(0, HEIGHT/2)\ .setValue(HEIGHT/2) # размер текста: textSize(20) В функции draw мы накроем её сверху полупрозрачным прямоугольником по размерам картинки: # Рисуем прямоугольник по размерам # картинки с контурным вырезом def draw(): image(img, 0, 0) # полупрозрачный цвет большого # прямоугольника: fill(255, 160) beginShape() vertex(0, 0) vertex(WIDTH, 0) vertex(WIDTH, HEIGHT) vertex(0, HEIGHT) endShape(CLOSE) fill(0) text('dy=' + str(int(dy)), WIDTH - 80, HEIGHT - 20) 472
На рисунке этот прямоугольник хорошо виден: Теперь вырежем в нём прямоугольную дырку, размеры которой можно изменять ползунком. Вначале дырка имеет нулевые размеры, то есть картинка останется без изменений: # пропорции картинки: prop = WIDTH / HEIGHT 473
# ширина рамки: dy = cp5.getValue("DY") dx = dy * prop # полупрозрачный цвет большого # прямоугольника: fill(255, 160) beginShape() vertex(0, 0) vertex(WIDTH, 0) vertex(WIDTH, HEIGHT) vertex(0, HEIGHT) beginContour() vertex(WIDTH - dx*1, dy) vertex(dx, dy) vertex(dx, HEIGHT - dy*1) vertex(WIDTH - dx*1, HEIGHT - dy*1) endContour() endShape(CLOSE) Но с помощью ползунка мы можем увеличить размеры внутреннего прямоугольника, и тогда через него картинка будет видна в полном цвете. → 474
Проект Контуры Исходный код программы находится в папке Contours. Более интересный эффект можно получить, если вырезать в большой фигуре несколько дырок. Для простоты сделаем все контуры квадратными: # размеры контуров: SIZE = 100 # число контуров: COL = int(WIDTH / SIZE) ROW = int(HEIGHT / SIZE) Диапазон изменения значений на ползунке нужно привести к размерам контуров: # создаём ползунок: global cp5 cp5 = ControlP5(this) cp5.addSlider("DY")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(0, SIZE)\ .setValue(SIZE/2) В функции draw вырезаем из большого прямоугольника квадратные дырки, размер которых регулируется ползунком: # Рисуем прямоугольники по размерам # картинки с контурным вырезом 475
def draw(): image(img, 0, 0) # ширина рамки: dy = cp5.getValue("DY") dx = dy # полупрозрачный цвет большого # прямоугольника: fill(255, 160) beginShape() vertex(0, 0) vertex(WIDTH, 0) vertex(WIDTH, HEIGHT) vertex(0, HEIGHT) for row in range(ROW): for col in range(COL): x = col * SIZE y = row * SIZE beginContour() vertex(x + SIZE - dx, y + dy) vertex(x + dx, y + dy) vertex(x + dx, y + SIZE- dy) vertex(x + SIZE- dx, y + SIZE- dy) endContour() endShape(CLOSE) fill(0) text('dy=' + str(int(dy)), WIDTH - 80, HEIGHT - 20) Теперь контурные дырки открывают и закрывают картинку гораздо эффектнее: 476
Проект Контуры 2 Исходный код программы находится в папке Contours2. Можно пойти другим путём и вырезать дырку в дырке: 477
global cp5 cp5 = ControlP5(this) cp5.addSlider("DY")\ .setPosition(10, HEIGHT - 40)\ .setSize(LENGTH, 20)\ .setRange(0, HEIGHT)\ .setValue(WIDTH/2) beginShape() vertex(0, 0) vertex(WIDTH, 0) vertex(WIDTH, HEIGHT) vertex(0, HEIGHT) beginContour() vertex(WIDTH - dx, dy) vertex(dx, dy) vertex(dx, HEIGHT - dy) vertex(WIDTH - dx, HEIGHT - dy) endContour() beginContour() vertex(WIDTH - dx/2, dy/2) vertex(dx/2, dy/2) vertex(dx/2, HEIGHT - dy/2) vertex(WIDTH - dx/2, HEIGHT - dy/2) endContour() endShape(CLOSE) Через дырку в дырке ничего не видно, потому что дырка в дырке превращается в область большого прямоугольника. Это можно сравнить с умножением: -1 x -1 = 1. Но внутри дырки в дырке снова можно сделать дырку, которая будет уже настоящей дыркой. И так до бесконечности. Ну, и само собой, дыркам можно придать более интересную форму… 478
479
Шейдеры Шейдер – это небольшая программа, написанная на языке GLSL (OpenGL Shading Language). Шейдеры служат для добавления различных визуальных эффектов к изображению. Функция loadShader загружает шейдер в программу: loadShader(fragFilename: str) → PShader loadShader(fragFilename: str, vertFilename: str) → PShader Первая функция загружает только фрагментный шейдер, вторая - фрагментный и вершинный. fragFilename, vertFilename – это имена фрагментного и вершинного шейдера соответственно. Файлы с шейдерами должны находиться непосредственно в папке с проектом либо во вложенной папке data. Если шейдер находится в другой папке, то необходимо указать полный путь к нему. Шейдеры не действуют в режиме по умолчанию, поэтому при создании окна нужно явно указать режим P2D или P3D: size(800, 600, P2D) Чтобы применить шейдер в программе, нужно вызвать функцию shader: shader(shader: PShader) shader(shader: PShader, kind: int) 480
Ей следует передать имя переменной, в которую загружен шейдер – первая перегрузка. И дополнительно – тип шейдера kind: POINTS, LINES или TRIANGLES – вторая перегрузка. Для изменения значений Uniform-переменных в шейдере используется метод set, имеющий множество перегрузок: .set(name, .set(name, .set(name, .set(name, .set(name, .set(name, .set(name, .set(name, .set(name, .set(name, x) x, y) x, y, z) x, y, z, w) vec) vec, ncoords) boolvec, ncoords) mat) mat, use3x3) tex) Здесь: name - строка - имя Uniform-переменной для изменения x - boolean, float или int – первый элемент переменной (или одиночная переменная) y - boolean, float или int – второй элемент переменной. Переменная должна быть объявлена в шейдере как array/vector: int[2], vec2 z - boolean, float или int – третий элемент переменной. Переменная должна быть объявлена в шейдере как array/vector: int[3], vec3 w - boolean, float или int – четвёртый элемент переменной. Переменная должна быть объявлена в шейдере как array/vector: int[4], vec4 vec - boolean[], float[], int[] или PVector – для изменения всех элементов переменной типа array/vector. Тип PVector используется, только если переменная имеет тип vec3 ncoords - int – число координат: max 4 mat - PMatrix3D илиr PMatrix2D – матрица значений 481
use3x3 - boolean – поддерживает матрицу 3 x 3 tex - PImage – изображение-текстура Проект Шейдер Корсуна Исходный код программы находится в папке Shader_Korsun. В Интернете вы без труда найдёте множество шейдеров для своих программ. В этом проекте вы познакомитесь с двумя шейдерами Виктора Корсуна с сайта glslsandbox.com. Для их работы нужна пустая текстура без картинки, потому что они сами заполняют текстуру цветными пикселями, которые рассчитываются по весьма замысловатым формулам. Для хранения картинки и шейдера нам понадобятся 2 переменные: # размеры окна: WIDTH = 800 HEIGHT = 533 global sh, img В функции settings создаём широкое окно в шейдер: # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT, P2D) 482
Не забудьте указать режим P2D или P3D! В функции setup создаём картинку по размерам окна и загружаем шейдер из файла: # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём пустую картинку по размерам окна: global img # создаём пустую картинку по размерам окна: img = PImage(WIDTH, HEIGHT, RGB) # загружаем шейдер: global sh sh = loadShader("sandbox.glsl") Шейдеры обычно имеют параметры, которые записываются в начале программы. У шейдера sandbox.glsl 2 параметра – разрешение и время: uniform vec2 resolution; uniform float time; void main(void) { float x = gl_FragCoord.x; float y = gl_FragCoord.y; float mov0 = x + y + cos(sin(time) * 2.) * 100.+ sin(x / 100.) * 1000.; float mov1 = y / resolution.y / 0.2 + time; float mov2 = x / resolution.x / 0.2; float c1 = abs(cos(mov1 + time) / 2.+ mov2 / 2.- mov1 - mov2 + time); float c2 = abs(sin(c1 + sin(mov0 / 1000.+ time) + tan(y / 40.+ time) + tan((x + y) / 100.) * 3.)); float c3 = abs(tan(c2 + cos(mov1 + mov2 + c2) - tan(mov2) - cos(x / 1000.))); gl_FragColor = vec4(c1, c2, c3, 1.0); } 483
Первый – resolution – это размеры картинки. В данном случае их передавать в шейдер не нужно – это сделает Процессинг без нашего участия. Для изменения картинки в цикле draw создаём переменную time: # время: time = 0.0 А тут как раз и начинается цикл draw. На каждой итерации он изменяет значение параметра time: # ОБНОВЛЯЕМ СЦЕНУ def draw(): global sh, img, time # изменяем параметр время: sh.set("time", time) Печатаем картинку с шейдером: # применяем шейдер: shader(sh) # печатаем картинку на экране: image(img, 0, 0) # увеличиваем время: time += 0.1 И вот такая у нас получилась картинка: На экране волны двигаются, но в книге движение не передашь. 484
К сожалению, на картинке много лишних линий, которые только портят её. Давайте упростим шейдер: #sh = loadShader("sandbox.glsl") sh = loadShader("sandbox1.glsl") uniform vec2 resolution; uniform float time; void main(void) { float x = gl_FragCoord.x; float y = gl_FragCoord.y; float mov0 = x + y + cos(sin(time) * 2.); float mov1 = y / resolution.y / 0.2 + time; float mov2 = x / resolution.x / 0.2; float c1 = abs(cos(mov1 + time) / 1.); float c2 = abs(sin(c1 + sin(mov0 / 10.+ time))); float c3 = abs(tan(c2 + cos(mov1 + mov2 + c2))); gl_FragColor = vec4(c1, c2, c3, 1.0); } Волны получились не столь изящными, как в оригинале, но зато без всяких посторонних шумов. → Продолжайте эксперименты, и у вас наверняка получатся новые красивые шейдеры. А мы переходим ко второму шейдеру Виктора Корсуна. Он имеет те же самые параметры, что и 485
первый, но менее «навороченный»: sh = loadShader("sandboxA.glsl") uniform vec2 resolution; uniform float time; void main(void) { float x = gl_FragCoord.x; float y = gl_FragCoord.y; float mov0 = x + y + cos(sin(time) * 2.) * 100.+ sin(x / 100.) * 1000.; float mov1 = y / resolution.y / 0.2 + time; float mov2 = x / resolution.x / 0.2; float c1 = abs(sin(mov1 + time) / 2.+ mov2 / 2.- mov1 - mov2 + time); float c2 = abs(sin(c1 + sin(mov0 / 1000.+ time) + sin(y / 40.+ time) + sin((x + y) / 100.) * 3.)); float c3 = abs(sin(c2 + cos(mov1 + mov2 + c2) + cos(mov2) + sin(x / 1000.))); gl_FragColor = vec4(c1, c2, c3, 1.0); } Здесь уже посторонних линий нет, так что плазма получилась на загляденье: 486
Но при желании вы и здесь можете поиграть параметрами: sh = loadShader("sandboxA1.glsl") uniform vec2 resolution; uniform float time; void main(void) { float x = gl_FragCoord.x; float y = gl_FragCoord.y; float mov0 = x + y + cos(sin(time) * 2.) * 100.+ sin(x / 100.) * 10000.; float mov1 = y / resolution.y / 0.5 + time; float mov2 = x / resolution.x / 0.5; float c1 = abs(sin(mov1 + time) / 2.+ mov2 / 2.- mov1 - mov2 + time); float c2 = abs(sin(c1 + sin(mov0 / 1000.+ time) + sin(y / 40.+ time) + sin((x + y) / 100.) * 3.)); float c3 = abs(sin(c2 + cos(mov1 + mov2 + c2) + cos(mov2) + sin(x / 10000.))); gl_FragColor = vec4(c1, c2, c3, 1.0); } 487
sh = loadShader("sandboxA2.glsl") Проект Шейдеры своими руками Исходный код программы находится в папке Shader_self. 488
Язык шейдеров довольно сложный и требует отдельного изучения, но самые простые шейдеры вы можете написать уже сейчас. Фрагментный (пиксельный) шейдер окрасит картинку в цвет, который задаётся цветовыми составляющими. Их значения изменяются от 0.0 до 1.0. Цветовые составляющие удобно хранить в вещественных переменных. Установив значения всех составляющих цвета, вы создаёте вектор vec4 и присваиваете его встроенной переменной gl_FragColor: void main(void) { float r = 1.0; float g = 0.0; float b = 0.0; float a = 1.0; gl_FragColor = vec4(r, g, b, a); } В этом шейдере мы задали красный непрозрачный цвет для всей текстуры. В функции setup загружаем шейдер и создаём картинку нужных размеров: # размеры окна: WIDTH = 480 HEIGHT = 320 # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT, P2D) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём пустую картинку по размерам окна: img = PImage(WIDTH, HEIGHT, RGB) # загружаем шейдер: sh = loadShader("color.glsl") 489
Затем применяем шейдер к картинке и печатаем её на экране # применяем шейдер: shader(sh) # печатаем картинку на экране: image(img, 0, 0) Запускайте программу – вся клиентская часть окна окрашена в красный цвет: Проект Шейдеры своими руками 2 Исходный код программы находится в папке Shader_self_2. Чтобы сделать шейдер более гибким, заменим переменные параметрами для всех составляющих цвета: 490
uniform uniform uniform uniform float float float float r; g; b; a; void main(void) { gl_FragColor = vec4(r, g, b, a); } Теперь вы можете в программе задавать значения этих параметров: # загружаем шейдер: sh = loadShader("color2.glsl") # задаём цвет: sh.set("r", 1.0) sh.set("g", 0.5) sh.set("b", 0.0) sh.set("a", 1.0) 491
Проект Шейдеры своими руками 3 Исходный код программы находится в папке Shader_self_3. Создадим мигающее окно. В функции setup задаём смену кадров 1 раз в секунду, а прозрачность устанавливаем в 1, потому что она не изменяется: # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём пустую картинку по размерам окна: global img img = PImage(WIDTH, HEIGHT, RGB) # загружаем шейдер: global sh sh = loadShader("color2.glsl") sh.set("a", 1.0) frameRate(1) В функции draw на каждой итерации задаём случайные значения цветовым составляющим: # ОБНОВЛЯЕМ СЦЕНУ def draw(): global sh # задаём случайные составляющие цвета: r = random(1) sh.set("r", r) g = random(1) sh.set("g", g) b = random(1) sh.set("b", b) # применяем шейдер: 492
shader(sh) # печатаем картинку на экране: global img image(img, 0, 0) Один раз в секунду экран окрашивается в новый цвет: Проект Шейдеры своими руками 4 Исходный код программы находится в папке Shader_self_4. Чтобы не мучить шейдер многочисленными изменениями параметров, перепишем его: 493
uniform vec3 color; void main(void) { gl_FragColor = vec4(color, 1.0); } Пользоваться этим шейдером удобнее: # размеры окна: WIDTH = 480 HEIGHT = 320 global sh, img # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT, P2D) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): #size(WIDTH, HEIGH, P2D) # создаём пустую картинку по размерам окна: global img img = PImage(WIDTH, HEIGHT, RGB) # загружаем шейдер: global sh sh = loadShader("color3.glsl") # задаём случайные составляющие цвета: r = random(1) g = random(1) b = random(1) sh.set("color", r, g, b) frameRate(1) # ОБНОВЛЯЕМ СЦЕНУ def draw(): 494
global sh # задаём случайные составляющие цвета: r = random(1) g = random(1) b = random(1) sh.set("color", r, g, b) # применяем шейдер: shader(sh) # печатаем картинку на экране: global img image(img, 0, 0) Проект Шейдеры своими руками 5 Исходный код программы находится в папке Shader_self_5. 495
Если вы хотите изменять и прозрачность, то пользуйтесь таким шейдером: uniform vec4 rgba; void main(void) { gl_FragColor = vec4(rgba); } В этом примере прозрачность равна 1 (полная непрозрачность), но вы можете передать в шейдер любое значение от 0 до 1: # размеры окна: WIDTH = 480 HEIGHT = 320 global sh, img # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT, P2D) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём пустую картинку по размерам окна: global img img = PImage(WIDTH, HEIGHT, RGB) # загружаем шейдер: global sh sh = loadShader("color4.glsl") # задаём случайные составляющие цвета: r = random(1) g = random(1) b = random(1) sh.set("rgba", r, g, b, 1) frameRate(1) # ОБНОВЛЯЕМ СЦЕНУ def draw(): 496
global sh # задаём случайные составляющие цвета: r = random(1) g = random(1) b = random(1) sh.set("rgba", r, g, b, 1) # применяем шейдер: shader(sh) # печатаем картинку на экране: global img image(img, 0, 0) Проект Шейдерные квадратики Исходный код программы находится в папке Shader_squres. 497
В этом проекте мы нарисуем целую сетку из цветных квадратиков: Нам для этого нужен единственный квадратик quadr: # квадратик: Q_SIZE = 40 global sh, quadr Чтобы регулировать расстояния между квадратиками, объявим переменную OFFSET: 498
# зазоры между квадратиками: OFFSET = 2 Задаём число клеток в сетке и вычисляем размеры окна: # размер сетки в клетках: COLS = 16 ROWS = 12 # размеры окна: WIDTH = (Q_SIZE + OFFSET) * COLS + OFFSET HEIGHT = (Q_SIZE + OFFSET) * ROWS + OFFSET В функции setup загружаем шейдер color3.glsl и устанавливаем частоту обновления экрана 1 раз в секунду: # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT, P2D) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): #size(WIDTH, HEIGH, P2D) # создаём пустую картинку по размерам окна: global quadr # создаём пустую картинку по размерам квадратика: quadr = PImage(Q_SIZE, Q_SIZE, RGB) # загружаем шейдер: global sh sh = loadShader("color3.glsl") frameRate(1) И начинаем функцию draw. Цветные составляющие для каждого квадратика выбираем случайно, а его место в сетке вычисляем по несложной формуле: 499
# ОБНОВЛЯЕМ СЦЕНУ def draw(): background(0) global sh # рисуем сетку из квадратиков: for row in range(ROWS): for col in range(COLS): # задаём случайные составляющие цвета: r = random(1) g = random(1) b = random(1) sh.set("color", r, g, b) # применяем шейдер: shader(sh) # устанавливаем квадратик в сетке: x = OFFSET + col * (Q_SIZE + OFFSET) y = OFFSET + row * (Q_SIZE + OFFSET) image(quadr, x, y) Мы нарисовали цветную сетку, которая обновляется 1 раз в секунду. Квадратики можно раскрашивать и шейдерами! Проект Шейдерный градиент Исходный код программы находится в папке Shader_gradient. Вы научились раскрашивать поверхности одним цветом. Следующий этап в освоении шейдеров – рисование градиентов. В шейдерах это можно сделать несколькими способами. Самый простой из них – перевести код из градиентного проекта на язык шейдеров. 500
Текущие координаты пикселя в заданном фрагменте (у нас это оконные координаты) мы получаем от переменной gl_FragCoord. Значения цветовых составляющих изменяются в диапазоне 0.0..1.0, а не 0..255, как это принято в компьютерной графике. Значения двух составляющих не изменяются, а значение третьей составляющей изменяется от 1 до 0, в результате чего мы и получаем градиентный переход: uniform vec2 resolution; void main(void) { // pixel coords: float x = gl_FragCoord.x; float y = gl_FragCoord.y; // blue: float b = 1; // red: float r = 0; // green: float g = y / resolution.y; // pixel color (rgb) --> (x,y): gl_FragColor = vec4(r, g, b, 1.0); } # размеры окна: WIDTH = 640 HEIGHT = 480 # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT, P2D) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # создаём пустую картинку по размерам окна: img = PImage(WIDTH, HEIGHT, RGB) # загружаем шейдер: sh = loadShader("grad.glsl") # применяем шейдер: shader(sh) # печатаем картинку на экране: 501
image(img, 0, 0) Сам градиент ничуть не пострадал при переводе: Проект Шейдерный градиент 2 Исходный код программы находится в папке Shader_gradient_2. 502
Если изменять и вторую составляющую цвета, то получится более сложный градиент: # загружаем шейдер: sh = loadShader("grad2.glsl") uniform vec2 resolution; void main(void) { // pixel coords: float x = gl_FragCoord.x; float y = gl_FragCoord.y; // blue: float b = x / resolution.x; // red: float r = 0; // green: float g = y / resolution.y; // pixel color (rgb) --> (x,y): gl_FragColor = vec4(r, g, b, 1.0); } 503
Проект Шейдерный градиент 3 Исходный код программы находится в папке Shader_gradient_3. Вы можете поэкспериментировать со случайными значениями составляющих цвета: # загружаем шейдер: sh = loadShader("grad3.glsl") uniform vec2 resolution; void main(void) { // pixel coords: float x = gl_FragCoord.x; float y = gl_FragCoord.y; // blue: float b = 0; // red: float r = 0; // green: float g = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453); // pixel color (rgb) --> (x,y): gl_FragColor = vec4(r, g, b, 1.0); } 504
Проект Шейдерный градиент 4 Исходный код программы находится в папке Shader_gradient_4. И с синусами: float g = fract(sin(gl_FragCoord.y)) # загружаем шейдер: sh = loadShader("grad4.glsl") 505
Проект Шейдерный градиент 5 Исходный код программы находится в папке Shader_gradient_5. float g = sin(gl_FragCoord.y); # загружаем шейдер: sh = loadShader("grad5.glsl") 506
Проект Шейдерный градиент 6 Исходный код программы находится в папке Shader_gradient_6. А вот и наши синусоидные полоски: float g = (sin(y * 0.035) + 1.0) / 2.0 # загружаем шейдер: sh = loadShader("grad6.glsl") 507
Проект Анимированный шейдерный градиент Исходный код программы находится в папке Shader_gradient_animir. Простенький шейдер из книги The Book of Shaders: uniform vec2 resolution; uniform float time; void main() { vec2 st = gl_FragCoord.xy / resolution.xy; vec3 color = vec3(st.x, abs(cos(st.y + time * sqrt(2))), abs(sin(time))); gl_FragColor = vec4(color, 1.0); } Я слегка обработал этот шейдер, что ничуть его не ухудшило: # размеры окна: WIDTH = 640 HEIGHT = 480 # время: time = 0.0 global sh # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT, P2D) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # загружаем шейдер: global sh sh = loadShader("bookofsh.glsl") sh.set("resolution", WIDTH, HEIGHT) # ОБНОВЛЯЕМ СЦЕНУ 508
def draw(): background(0) global sh, time # изменяем параметр время: sh.set("time", time) # применяем шейдер: shader(sh) rect(0, 0, WIDTH, HEIGHT) # увеличиваем время: time += 0.1 Цвета со временем изменяются, поэтому за их игрой наблюдать весьма любопытно: 509
Проект Шейдерная чёрная дыра Исходный код программы находится в папке Shader_black_hole. Интересные шейдеры можно найти повсюду! Например, к программе Процессинг приложены демонстрационные шейдеры. Мы рассмотрим только три из них. Программы одинаковы для всех шейдеров. Нужно только подправлять название шейдера: # размеры окна: WIDTH = 640 HEIGHT = 480 # время: time = 0.0 global sh # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT, P2D) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # загружаем шейдер: global sh sh = loadShader("monjori.glsl") sh.set("resolution", WIDTH, HEIGHT) # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(0) global sh, time # изменяем параметр время: sh.set("time", time) 510
# применяем шейдер: shader(sh) rect(0, 0, WIDTH, HEIGHT) # увеличиваем время: time += 0.1 Все шейдерные картинки изменяются со временем, поэтому оценить их в полной мере можно только в работающей программе. 511
Проект Шейдерная туманность Исходный код программы находится в папке Shader_nebula. А этот шейдер создаёт вполне реалистическую картину космического путешествия: # размеры окна: WIDTH = 640 HEIGHT = 480 # время: time = 0.0 global sh # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT, P2D) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # загружаем шейдер: global sh sh = loadShader("nebula.glsl") sh.set("resolution", WIDTH, HEIGHT) # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(0) global sh, time # изменяем параметр время: sh.set("time", time) # применяем шейдер: shader(sh) rect(0, 0, WIDTH, HEIGHT) 512
# увеличиваем время: time += 0.1 513
Проект Шейдерный ландшафт Исходный код программы находится в папке Shader_landscape. Самый сложный шейдер. Он умеет создавать горный ландшафт! И даже с тенями: # размеры окна: WIDTH = 640 HEIGHT = 480 # время: time = 0.0 global sh # СОЗДАЁМ ОКНО def settings(): size(WIDTH, HEIGHT, P2D) # ИНИЦИАЛИЗИРУЕМ ПРОГРАММУ def setup(): # загружаем шейдер: global sh sh = loadShader("landscape.glsl") sh.set("resolution", WIDTH, HEIGHT) # ОБНОВЛЯЕМ СЦЕНУ def draw(): background(0) global sh, time # изменяем параметр время: sh.set("time", time) # применяем шейдер: shader(sh) rect(0, 0, WIDTH, HEIGHT) 514
# увеличиваем время: time += 0.1 515
Литература Cерия Программирование для детей [Питон] Рубанцев Валерий Развивающее программирование Практикум по решению задач на языке Питон 3. Базовый уровень. - 500 с. В книге подробно рассматривается решение более 100 задач: математических, словесных, комбинаторных, вероятностных, игровых. Лучшие упражнения для отработки навыков программирования на языке Питон. [Си-шарп] Рубанцев Валерий Компьютер, наука и жизнь Занимательные математические задачи: От древности до современности. - 500 с. Около 150 проектов на языке Си-шарп, показывающих, как можно решать разнообразные занимательные математические задачи на компьютере. 516
[Геогебра] Рубанцев Валерий Высокие технологии в школе Занимательные задачи с Геогеброй. 160 с. Решение занимательных математических задач в среде Геогебра. Несколько десятков интересных задачек по всем школьным разделам алгебры. [С++] Рубанцев Валерий Развивающее программирование Решение задач на языке С++. - 200 с. Несколько десятков проектов на языке С++, показывающих, как можно решать задачи на компьютере. 517
Cерия Программирование на Питоне [Python 01] Рубанцев Валерий Программирование для всех Компьютерная графика на Питоне 530 с. В книге подробно описываются возможности графической библиотеки Processing для простой и эффективной разработки графических приложений на языке Питон. Все функции проиллюстрированы многочисленными примерами. 518
[Python 02] Рубанцев Валерий Программирование для всех Простые компьютерные игры на Питоне 220 с. В книге подробно описывается разработка 10 простых компьютерных игр. Среди них есть и очень известные игры – Игра Баше, Угадай число, Закраска – и не очень, и совсем новые – Пузыри, Блиц-Клик, Охота на Скалоеда и Скалоедов, две программы про Незнайку и великолепная головоломка Ножки вверх! Цель книги: научиться писать простые компьютерные игры на языке Питон с использованием графической библиотеки Processing. Для учащихся, учителей информатики, любителей программирования и начинающих программистов, имеющих небольшой опыт в программировании на Питоне. 519
[Python 03] Рубанцев Валерий Программирование для всех Компьютерные игры и головоломки на Питоне 310 с. Продолжаем программировать компьютерные игры и головоломки: Солитер, Прыгающие лягушки, Крестики-нолики, математическая Игра Ярбро, головоломки Eliminator и Местор Научимся использовать в программах анимацию и метод минимакса, разрабатывать графический интерфейс, писать "решалки" для головоломок, придумывать свои уровни и программы. Для учащихся, учителей информатики, любителей программирования и начинающих программистов, имеющих небольшой опыт в программировании на Питоне. 520
[Python 04] Рубанцев Валерий Программирование для всех Программирование игр для детей. На Питоне 240 с. Учимся программировать интересные и красочные компьютерные игры для детей! Самая известная из них – Фрудоку. Это упрощённый вариант судоку 6 х 6 клеточек с фруктами вместо цифр. Менее известны, но не менее увлекательны игры ABCD, Arukone, Bit-Shift, Grand Tour, Кубиковая считалка и Римская задача. Для учащихся, учителей информатики, любителей программирования и начинающих программистов, имеющих небольшой опыт в программировании на Питоне. 521
[Python 05] Рубанцев Валерий Программирование для всех Программирование детских игр на Питоне 230 с. Новые компьютерные игры и головоломки для детей! Фокус, Собиратель монет, Собиратель букв, Анаграммы, Коровы, Сикаку, Тетрамино, Тетраминки. Для учащихся, учителей информатики, любителей программирования и начинающих программистов, имеющих небольшой опыт в программировании на Питоне. 522
Серия Программирование на языке C# 5.0: Начальный уровень [CS10] Рубанцев Валерий Программирование на языке C# 5: Начальный уровень. – 620 с. Основы программирования на языке Си-шарп. [CS11] Рубанцев Валерий Программирование на языке C# 5: Практикум по решению задач начального уровня. – 420 с. Многочисленные проекты для укрепления навыков программирования на языке Си-шарп. 523
[CS12] Рубанцев Валерий Программирование на языке C# 5: Компьютерная графика. Начальный уровень. – 460 с. Основы компьютерной графики. [CS13] Рубанцев Валерий Программирование на языке C# 5: Тотальный тренинг по Си-шарпу. Начальный уровень. – 400 с. Разнообразные полезные и занимательные проекты на языке Си-шарп. 524
Cерия Учись программировать с Котлином [Kotlin01] Рубанцев Валерий Привет, Котлин! Программирование на языке Котлин для детей 350 с. Это самоучитель по программированию на языке Котлин для детей, начиная с 10-летнего возраста. Для книги отобрано ровно столько учебного материала, сколько его необходимо и достаточно, чтобы начать писать программы на языке Котлин самостоятельно. 525
[Kotlin02] Рубанцев Валерий Практикум по решение задач на языке Kotlin для детей 200 с. В этой книге собраны занимательные математические задачи, которые можно решать на языке Котлин. Решение всех задач подробно описано. В них используются все теоретические знания, которые читатели получили в первой книге. Решение задач – отличная тренировка при изучении нового языка программирования! 526
[Kotlin03] Рубанцев Валерий Практикум по решению задач на языке Kotlin для школьников 250 с. Решаем занимательные математические задачи всех времён и народов на языке Котлин в среде IntelliJ IDEA. При решении задач используются все базовые понятия языка Котлин. Совершенно необходимая книга для закрепления знаний и укрепления навыков программирования на языке Котлин. 527
[Kotlin05] Рубанцев Валерий Решение задач на языке Kotlin 370 с. Решение математических задач на языке Котлин в среде IntelliJ IDEA. При решении задач используются все базовые понятия языка Котлин. Совершенно необходимая книга для закрепления знаний и укрепления навыков программирования на языке Котлин. 528
[Kotlin06] Рубанцев Валерий Основы компьютерной графики на языке Котлин 500 с. Это самоучитель по компьютерной графике для начинающих. В книге подробно описаны возможности графической библиотеки core.js – основы языка Процессинг - для простой и эффективной разработки графических приложений на языке Котлин. Эта книга поможет вам быстро изучить основы компьютерной графики, и вы сможете самостоятельно рисовать красивые узоры, решать задачи, писать игры и разрабатывать компьютерные модели по биологии, физике, химии. В ней вы найдёте исчерпывающий теоретический материал для самостоятельного и разностороннего творчества. 529
Cерия Учись программировать с Процессингом [Processing 03] Рубанцев Валерий Учись программировать с Процессингом 540 с. Это самоучитель по программированию на языке Ява для начинающих. Для книги отобрано ровно столько учебного материала, сколько его необходимо и достаточно, чтобы писать программы школьного уровня. 530
[Processing 04] Рубанцев Валерий Учись программировать с Процессингом Основы компьютерной графики 490 с. Это самоучитель по компьютерной графике для начинающих. Эта книга поможет вам быстро изучить основы компьютерной графики, и вы сможете самостоятельно рисовать красивые узоры, решать задачи, писать игры и разрабатывать компьютерные модели по биологии, физике, химии. В ней вы найдёте исчерпывающий теоретический материал для самостоятельного и разностороннего творчества. 531
Cерия Программирование на ЯваСкрипте [JavaScript 01] Рубанцев Валерий Программирование на ЯваСкрипте Занимательная графика на ЯваСкрипте 430 с. В книге подробно описываются возможности графической библиотеки p5.js для простой и эффективной разработки графических приложений на языке ЯваСкрипт. Все функции проиллюстрированы многочисленными примерами. 532
[JavaScript 02] Рубанцев Валерий Тотальный тренинг по ЯваСкрипту Массивы и функциональное программирование 260 с. Книга о массивах, методах и функциональном программировании на ЯваСкрипте. Все методы проиллюстрированы демонстрационными проектами. На занимательных примерах показано, как решать практические задачи на ЯваСкрипте в функциональном стиле. Для учащихся, учителей информатики, любителей программирования и начинающих программистов, имеющих небольшой опыт в программировании на ЯваСкрипте. 533
[JavaScript 03] Рубанцев Валерий Тотальный тренинг по ЯваСкрипту Простые компьютерные игры 220 с. В книге подробно описывается разработка 10 простых компьютерных игр. Среди них есть и очень известные игры – Игра Баше, Угадай число, Закраска – и не очень, и совсем новые – Пузыри, Блиц-Клик, Охота на Скалоеда и Скалоедов, две программы про Незнайку и великолепная головоломка Ножки вверх! Цель книги: научиться писать простые компьютерные игры на языке ЯваСкрипт с использованием графической библиотеки p5.js. Для учащихся, учителей информатики, любителей программирования и начинающих программистов, имеющих небольшой опыт в программировании на ЯваСкрипте. 534
[JavaScript 04] Рубанцев Валерий Тотальный тренинг по ЯваСкрипту Простые компьютерные игры 310 с. Продолжаем программировать компьютерные игры и головоломки: Солитер, Прыгающие лягушки, Крестики-нолики, математическая Игра Ярбро, головоломки Eliminator и Местор Научимся использовать в программах анимацию и метод минимакса, разрабатывать графический интерфейс, писать "решалки" для головоломок, придумывать свои уровни и программы. Для учащихся, учителей информатики, любителей программирования и начинающих программистов, имеющих небольшой опыт в программировании на ЯваСкрипте. 535
[JavaScript 05] Рубанцев Валерий Программирование на ЯваСкрипте Программирование игр для детей 240 с. Учимся программировать интересные и красочные компьютерные игры для детей! Самая известная из них – Фрудоку. Это упрощённый вариант судоку 6 х 6 клеточек с фруктами вместо цифр. Менее известны, но не менее увлекательны игры ABCD, Arukone, Bit-Shift, Grand Tour, Кубиковая считалка и Римская задача. Для учащихся, учителей информатики, любителей программирования и начинающих программистов, имеющих небольшой опыт в программировании на ЯваСкрипте. 536
[JavaScript 06] Рубанцев Валерий Программирование на ЯваСкрипте Программирование детских игр 230 с. Новые компьютерные игры и головоломки для детей! Фокус, Собиратель монет, Собиратель букв, Анаграммы, Коровы, Сикаку, Тетрамино, Тетраминки. Для учащихся, учителей информатики, любителей программирования и начинающих программистов, имеющих небольшой опыт в программировании на ЯваСкрипте. 537
[JavaScript 07] Рубанцев Валерий Программирование на ЯваСкрипте Классические компьютерные игры 250 с. Совершенствуем и развиваем навыки программирования компьютерных игр на ЯваСкрипте. На этот раз на очень занимательных примерах: Тетрис, Змейка, Сапёр, Bubble Shooter! А также: мы разовьём плодотворные классические идеи и напишем клоны: игры Рекстрис, По грибы и вторую Змейку. Для учащихся, учителей информатики, любителей программирования и начинающих программистов, имеющих небольшой опыт в программировании на ЯваСкрипте. 538
[JavaScript 08] Рубанцев Валерий Программирование на ЯваСкрипте Компьютерные игры для начальной школы 220 с. Новые компьютерные игры и головоломки для детей! Перекинь мостик, Сотенный билет, Найди треугольник, Психологическая считалка, Фруктосчёт, Раскрась карту. Для учащихся, учителей информатики, любителей программирования и начинающих программистов, имеющих небольшой опыт в программировании на ЯваСкрипте. 539
[JavaScript 09] Рубанцев Валерий Программирование на ЯваСкрипте Новые компьютерные игры 290 с. В этой книжке только одна головоломная программа, а все остальные – это весёлые, заводные игры: • Головоломка Инь-Ян • Игра Катапульта • Игра Сквош • Игра Сквош на двоих • Игра Теннис • Игра Танчики • Игра Платформер Для учащихся, учителей информатики, любителей программирования и начинающих программистов, имеющих небольшой опыт в программировании на ЯваСкрипте. 540
541