Generative art (генеративное или процедуральное искусство) может отпугнуть, если вы никогда с ним раньше не сталкивались. Если коротко, то это концепция искусства, которое буквально создает само себя и не требует хардкорных знаний программирования для первого раза. Поэтому я решил немного разбавить нашу ленту, погнали.
Что такое генеративное искусство?
Это результат системы, которая принимает свои собственные решения о предмете вместо человека. Система может быть такой же простой, как одна программа на Python, если у нее есть правила и момент случайности.
С программированием довольно просто придумать правила и ограничения. Для этого есть условные операторы. Но найти способы заставить эти правила создавать что-то интересное может быть не так просто.
Conway’s Game of Life
Игра «Жизнь» (Conway’s Game of Life) — это известный набор из четырех простых правил, определяющих «рождение» и «смерть» каждой клетки в системе. Каждое правило играет определенную роль в продвижении системы через каждое поколение. Хотя правила просты и легки для понимания, быстро появляются сложные шаблоны, которые в конечном итоге формируют захватывающие результаты.
Правила могут быть ответственны за создание основы чего-то интересного, но даже нечто такое же захватывающее, как Conway’s Game of Life, предсказуемо. Четыре правила — это определяющие факторы для каждого поколения. Поэтому, чтобы получить непредвиденные результаты, нужно ввести рандомизацию в начальном состоянии ячеек. Начиная со случайной матрицы, каждое выполнение будет уникальным без необходимости изменения правил.
Лучшие примеры генеративного искусства — те, которые находят сочетание предсказуемости и случайности для создания чего-то интересного, что статистически невозможно повторить.
Почему вы должны это попробовать?
Генеративное искусство не всегда будет тем, на что вы захотите тратить время. Но если вы решите поработать над ним, то можете рассчитывать на следующие преимущества:
- Опыт. Генеративное искусство — это еще одна возможность отточить новые и старые навыки. Оно может служить входом для отработки таких понятий, как алгоритмы, структуры данных и даже новых языков.
- Ощутимые результаты. В программировании мы редко видим физические результаты наших усилий. Ну или, по крайней мере, я этого не вижу. Прямо сейчас у меня в гостиной висит несколько постеров с принтами моего процедурального арта. И мне нравится, что это сделано кодом.
- Привлекательные проекты. У всех был опыт объяснения личного проекта кому-то, возможно, даже во время собеседования. Генеративный арт говорит сам за себя. Большинство людей оценят результаты, даже если они не смогут полностью понять методы.
С чего начать?
Начало работы с генеративным искусством — такой же процесс, как начало работы с любым другим проектом. Самый важный шаг — придумать идею или найти ее для дальнейшего развития. Как только у вас есть цель, то можно начинать работать над ее достижением.
Большинство моих творческих проектов выполнены на Python. Это довольно простой язык с множество полезных пакетов, которые помогают в обработке изображений. Например, Pillow.
К счастью, вам не нужно долго искать с чего начать — ниже я поделюсь своим кодом.
Генератор спрайтов
Этот проект начался, когда я увидел пост с генератором спрайтов, написанным на JavaScript. Программа создавала 5?5 пиксель-арт спрайты со случайными вариантами цветов, а ее результат напоминал разноцветных космических захватчиков.
Я хотел попрактиковаться в обработке изображений на Python, поэтому решил воссоздать эту концепцию самостоятельно. Кроме того, я подумал, что могу расширить ее, так как исходный проект был сильно ограничен в размере спрайтов. А я хочу указывать не только размер спрайтов, но и их количество и даже размер изображения.
Вот два разных результата моей программы:
7x7–30–1900
43x43–6–1900
Эти два изображения совсем не похожи друг на друга, но они оба являются результатами одной и той же системы. Не говоря уже о том, что из-за сложности и случайной генерации спрайтов существует высокая вероятность, что даже при одинаковых аргументах эти изображения навсегда останутся единственными в своем роде. Обожаю это.
Окружающая среда
Перед знакомством с генератором спрайтов стоит подготовить небольшой фундамент для работы.
Если вы раньше не работали с Python, то скачайте Python 2.7.10. Сначала у меня были проблемы с настройкой среды, если вы тоже с ними столкнетесь — посмотрите в виртуальные среды. И убедитесь, что Pillow тоже установлен.
После настройки среды можете скопировать мой код в файл с расширением .py и выполнить следующую команду:
python spritething.py [SPRITE_DIMENSIONS] [NUMBER] [IMAGE_SIZE]
Например, команда для создания первой матрицы спрайтов будет:
python spritething.py 7 30 1900
Код
import PIL, random, sys
from PIL import Image, ImageDraw
origDimension = 1500
r = lambda: random.randint(50,215)
rc = lambda: (r(), r(), r())
listSym = []
def create_square(border, draw, randColor, element, size):
if (element == int(size/2)):
draw.rectangle(border, randColor)
elif (len(listSym) == element+1):
draw.rectangle(border,listSym.pop())
else:
listSym.append(randColor)
draw.rectangle(border, randColor)
def create_invader(border, draw, size):
x0, y0, x1, y1 = border
squareSize = (x1-x0)/size
randColors = [rc(), rc(), rc(), (0,0,0), (0,0,0), (0,0,0)]
i = 1
for y in range(0, size):
I *= -1
element = 0
for x in range(0, size):
topLeftX = x*squareSize + x0
topLeftY = y*squareSize + y0
botRightX = topLeftX + squareSize
botRightY = topLeftY + squareSize
create_square((topLeftX, topLeftY, botRightX, botRightY), draw, random.choice(randColors), element, size)
if (element == int(size/2) or element == 0):
I *= -1;
element += I
def main(size, invaders, imgSize):
origDimension = imgSize
origImage = Image.new(‘RGB’, (origDimension, origDimension))
draw = ImageDraw.Draw(origImage)
invaderSize = origDimension/invaders
padding = invaderSize/size
for x in range(0, invaders):
for y in range(0, invaders):
topLeftX = x*invaderSize + padding/2
topLeftY = y*invaderSize + padding/2
botRightX = topLeftX + invaderSize - padding
botRightY = topLeftY + invaderSize - padding
create_invader((topLeftX, topLeftY, botRightX, botRightY), draw, size)
origImage.save(«Examples/Example-«+str(size)+»x»+str(size)+»-«+str(invaders)+»-«+str(imgSize)+».jpg»)
if __name__ == «__main__»:
main(int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3]))
Это решение еще далеко от совершенства, но оно показывает, что создание генеративного искусства не требует тонны кода. Поясню ключевые моменты.
Основная функция начинается с создания исходного изображения и определения размера спрайтов. Два цикла for отвечают за определение границы каждого спрайта, в основном разделяя размеры изображения на количество запрошенных спрайтов. Эти значения используются для определения координат для каждого из них.
Посмотрите на изображение ниже. Представьте, что каждый из четырех квадратов представляет собой спрайт с размером 1. Граница, которая передается следующей функции, относится к координатам верхнего левого и нижнего правого углов. Так кортеж в верхнем левом спрайте будет (0,0,1,1), а кортеж в верхнем правом будет (1,0,2,1). Они будут использоваться в качестве размеров и базовых координат для квадратов каждого спрайта.
Пример определения границ спрайта
Функция create_invader определяет границу для каждого квадрата внутри спрайта. Тот же процесс определения границы применяется здесь и представлен ниже, только вместо полного изображения мы используем предварительно определенную границу для работы внутри. Эти конечные координаты для каждого квадрата будут использоваться в следующей функции для рисования спрайта.
Пример разбивки спрайта 3?3
Для определения цвета используется простой массив из трех случайных RGB-кортежей и трех черных для имитации 50% вероятности быть нарисованным. Лямбда-функции в верхней части кода отвечают за генерацию значений RGB.
Настоящая хитрость этой функции — создание симметрии. Каждый квадрат сопряжен со значением элемента. На рисунке ниже показано, как значения элементов увеличиваются по мере достижения центра, а затем уменьшаются. Квадраты с совпадающими значениями элементов отображаются одним цветом.
Значения элементов и симметричные цвета для строки в спрайте 7?7
Поскольку create_square получает свои параметры от create_invader, он использует очередь и предыдущие значения элементов для обеспечения симметрии. При первом появлении значений их цвета помещаются в очередь, а зеркальные квадраты удаляют цвета.
Полный процесс создания
Я понимаю, как трудно читать чужое решение проблемы и кривой код, но надеюсь, что вы найдете этому применение. Будет круто, если вы совсем откажетесь от моего кода и найдете совершенно другое решение.
Заключение
Генеративное искусство требует времени, чтобы полностью раскрыть свой потенциал. В целом, вокруг могут быть более полезные проекты, чем генеративное искусство, на которое не всегда стоит тратить время. Но это очень весело и никогда не знаешь, где может пригодиться.
Комментарии (15)
EarlAthos
08.11.2018 17:56+1Под Python 3.6.6, может кому пригодится.
Сам кодimport PIL, random from PIL import Image, ImageDraw origDimension = 1500 r = lambda: random.randint(50,215) rc = lambda: (r(), r(), r()) listSym = [] def create_square(border, draw, randColor, element, size): if (element == int(size/2)): draw.rectangle(border, randColor) elif (len(listSym) == element+1): draw.rectangle(border,listSym.pop()) else: listSym.append(randColor) draw.rectangle(border, randColor) def create_invader(border, draw, size): x0, y0, x1, y1 = border squareSize = (x1-x0)/size randColors = [rc(), rc(), rc(), (0,0,0), (0,0,0), (0,0,0)] i = 1 for y in range(0, size): i *= -1 element = 0 for x in range(0, size): topLeftX = x*squareSize + x0 topLeftY = y*squareSize + y0 botRightX = topLeftX + squareSize botRightY = topLeftY + squareSize create_square((topLeftX, topLeftY, botRightX, botRightY), draw, random.choice(randColors), element, size) if (element == int(size/2) or element == 0): i *= -1 element += i def main(size, invaders, imgSize): origDimension = imgSize origImage = Image.new('RGB', (origDimension, origDimension)) draw = ImageDraw.Draw(origImage) invaderSize = origDimension/invaders padding = invaderSize/size for x in range(0, invaders): for y in range(0, invaders): topLeftX = x*invaderSize + padding/2 topLeftY = y*invaderSize + padding/2 botRightX = topLeftX + invaderSize - padding botRightY = topLeftY + invaderSize - padding create_invader((topLeftX, topLeftY, botRightX, botRightY), draw, size) origImage.save("Result" + str(size) + "x" + str(size) + "-" + str(invaders) + "-" + str(imgSize) + ".jpg") print('Файл успешно создан') while True: size = input('Введите размер спрайта, не кратно 2 a = (1,3,5,7,9,11...)\n') if size == 'exit' or size == 'e' or size == 'EXIT' or size == 'quit' or size == 'q' or size == 'Q' or size == 'E': break invaders = input('Введите кол-во спрайтов(1,2,3,4,5,6,7...)\n') if invaders == 'exit' or invaders == 'e' or invaders == 'EXIT' or invaders == 'quit' or invaders == 'q' or invaders == 'Q' or invaders == 'E': break imgSize = input('Введите разрешение картинки(50,100,150...)\n') if imgSize == 'exit' or imgSize == 'e' or imgSize == 'EXIT' or imgSize == 'quit' or imgSize == 'q' or imgSize == 'Q' or imgSize == 'E': break try: main(int(size), int(invaders), int(imgSize)) except IndexError: print('Фигово вводите... Попробуйте ещё раз') except ValueError: print('Даже по цифрам не попадаете... Попробуйте ещё раз')
PavelMSTU
08.11.2018 19:00+1Кажется это можно использовать как новый метод стеганографии.Нужно хеш-стеганографию подружить с генеративным искусством ;))
dcc0
08.11.2018 19:46+2И если теперь подключить к вязальной машине, можно сделать интересный свитер. Но скучно, наверное.
oisee
09.11.2018 00:46Демосцена вас укуси.
Данный код занимает ~2kb, то есть одну сорок восьмую часть этого: en.wikipedia.org/wiki/.kkrieger =)
synedra
09.11.2018 06:37+2Сравнивать размер исходников и заоптимизированного под компактность скомпилированного бинарника — довольно неблагодарное занятие. Ну и вообще, где энтрилевельный любительский код на питоне, а где демосцена.
oisee
09.11.2018 10:13Благодарное. Хорошая иллюстрация — есть к чему стремиться. В конце концов в компо ограничение на объём, и не важно на чём написано.
А по ключевому слову «демосцена» можно найти достаточно материалов по процедурной графике в том числе.
Полезно знать общественное цифровое наследие и достояние.
engine9
09.11.2018 12:22А возможностей питона хватит чтобы визуализировать содержимое подключенного тома или папки вот в таком виде:
Скриншот k4dirstat
vesper-bot
Где-то у меня валяется код на Actionscript 3, рендерящий относительно пристойно выглядящий лес, размером строк в двести и 12 килобайт исполняемого кода (SWF). Правда, вместо попиксельной рисовки там честное (почти честное) 3D для каждого дерева в лесу. Ну и работает со скоростью роста настоящего леса. Ссылки попротухали за шесть лет, но перевыложить могу, в принципе.
old_gamer
Напишите публикацию, уверен, многим будет интересно (мне — точно).
vesper-bot
Готово: habr.com/post/429256 правда, самого кода там не так много, больше алгоритмов в виде текста.
azazelis Автор
И все равно получилось отлично)
old_gamer
Спасибо, очень интересно.
VioletGiraffe
+1 к просьбе о публикации :)