Как создать генеративное искусство менее чем за 100 строк кода

Генеративное искусство, как и любая другая тема программирования, может напугать, если вы никогда не пробовали его раньше. Меня всегда это интересовало, потому что я люблю находить новые способы творческого использования программирования. Более того, я думаю, что каждый может оценить концепцию произведения искусства, которое буквально создает само себя.

Что такое генеративное искусство?

Генеративное искусство - это результат системы, которая принимает собственные решения о произведении, а не человека. Система может быть такой же простой, как отдельная программа на Python, если в ней есть правила и некоторые аспекты случайности.

В программировании довольно просто придумать правила и ограничения. Это все условные утверждения. При этом найти способы заставить эти правила создавать что-то интересное может быть непросто.

Игра Жизни - это знаменитый набор из четырех простых правил, которые определяют «рождение» и «смерть» каждой ячейки в системе. Каждое из правил играет роль в продвижении системы через каждое поколение. Хотя правила просты и понятны, быстро начинают проявляться сложные закономерности, которые в конечном итоге приводят к удивительным результатам.

Правила могут быть ответственны за создание основы для чего-то интересного, но даже такая захватывающая игра, как «Игра жизни» Конвея, предсказуема. Поскольку четыре правила являются определяющими факторами для каждого поколения, способ получения непредвиденных результатов - это введение рандомизации в начальном состоянии ячеек. Начиная со случайной матрицы, каждое выполнение будет уникальным без необходимости изменять правила.

Лучшие примеры генеративного искусства - это те, которые находят сочетание предсказуемости и случайности, чтобы создать что-то интересное, что также является статистически невоспроизводимым .

Зачем вам это пробовать?

Не все побочные проекты одинаковы, и генеративное искусство может быть не тем, на что вы склонны тратить время. Однако, если вы решите работать над проектом, вы можете рассчитывать на следующие преимущества:

  • Опыт - Генеративное искусство - это просто еще одна возможность отточить новые и старые навыки. Он может служить воротами для отработки таких концепций, как алгоритмы, структуры данных и даже новые языки.
  • Ощутимые результаты. В мире программирования мы редко видим что-то физическое в результате наших усилий, или, по крайней мере, я этого не вижу. Прямо сейчас у меня в гостиной есть несколько плакатов с репродукциями моего генеративного искусства, и мне нравится, что за это отвечает программирование.
  • Привлекательные проекты - У всех нас был опыт объяснения кому-то личного проекта, возможно, даже во время собеседования, без простого способа передать усилия и результаты проекта. Генеративное искусство говорит само за себя, и большинство людей будут впечатлены вашими творениями, даже если они не могут полностью понять методы.

С чего начать?

Начало работы с генеративным искусством - это тот же процесс, что и любой проект, самый важный шаг - придумать идею или найти ее, на которой можно развиваться. Когда у вас есть цель, вы можете начать работу над технологией, необходимой для ее достижения.

Большинство моих генеративных художественных проектов были выполнены на Python. Это довольно простой язык, к которому можно привыкнуть, и в нем есть несколько невероятных пакетов, помогающих с манипуляциями с изображениями, например Pillow.

К счастью для вас, нет необходимости далеко искать отправную точку, потому что я предоставил вам код ниже, чтобы вы могли поиграть.

Генератор спрайтов

Этот проект начался, когда я увидел сообщение, демонстрирующее генератор спрайтов, написанный на Javascript. Программа создавала спрайты размером 5x5 пикселей с некоторыми случайными вариантами цвета, и ее результат напоминал разноцветных космических захватчиков.

Я знал, что хочу попрактиковаться в манипулировании изображениями в Python, поэтому решил, что могу просто попытаться воссоздать эту концепцию самостоятельно. Вдобавок я подумал, что могу расширить его, так как исходный проект был очень ограничен размером спрайтов. Я хотел иметь возможность указывать не только размер, но и их количество, и даже размер изображения.

Вот два разных результата решения, к которому я пришел:

Эти два изображения совершенно не похожи друг на друга, но оба являются результатом одной и той же системы. Не говоря уже о том, что из-за сложности изображения и случайности генерации спрайтов существует чрезвычайно высокая вероятность того, что даже с теми же аргументами эти изображения навсегда останутся единственными в своем роде. Я люблю это.

Среда

Если вы хотите поиграться с генератором спрайтов, сначала нужно проделать небольшую фундаментальную работу.

Настройка правильной среды с помощью Python может быть сложной задачей. Если вы раньше не работали с Python, вам, вероятно, потребуется загрузить Python 2.7.10. Изначально у меня были проблемы с настройкой среды, поэтому, если вы начнете сталкиваться с проблемами, вы можете сделать то же, что и я, и изучить виртуальные среды. И последнее, но не менее важное: убедитесь, что у вас также установлена ​​Pillow.

После настройки среды вы можете скопировать мой код в файл с расширением .py и выполнить следующую команду:

python spritething.py [SPRITE_DIMENSIONS] [NUMBER] [IMAGE_SIZE]

Например, команда для создания первой матрицы спрайтов сверху будет следующей:

python spritething.py 7 30 1900

Код

import PIL, random, sysfrom 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 определяет границу для каждого квадрата внутри спрайта. Тот же процесс определения границы применяется здесь и представлен ниже, только вместо полного изображения мы используем заранее определенную границу для работы внутри. Эти окончательные координаты для каждого квадрата будут использоваться в следующей функции для фактического рисования спрайта.

To determine the color, a simple array of three random RGB tuples and three blacks are used to simulate a 50% chance of being drawn. The lambda functions near the top of the code are responsible for generating the RGB values.

The real trick of this function is creating symmetry. Each square is paired with an element value. In the image below you can see the element values increment as they reach the center and then decrement. Squares with matching element values are drawn with the same color.

As create_square receives its parameters from create_invader, it uses a queue and the element values from before to ensure symmetry. The first occurrence of the values have their colors pushed onto the queue and the mirrored squares pop the colors off.

I realize how difficult it is to read through and understand someone else’s solution for a problem, and the roughness of the code certainly does not help with its complexity, but hopefully you’ve got a pretty good idea for how it works. Ultimately it would be incredible if you are able to scrap my code altogether and figure out an entirely different solution.

Conclusion

Generative art takes time to fully appreciate, but it’s worth it. I love being able to combine programming with a more traditional visual, and I have definitely learned a lot in every one of my projects.

Overall there may be more useful projects to pursue and generative art may not be something you need experience with, but it’s a ton of fun and you never know how it might separate you from the crowd.

Thank you for reading!