company_banner

Как создать процедуральный арт менее чем за 100 строк кода

https://medium.freecodecamp.org/how-to-create-generative-art-in-less-than-100-lines-of-code-d37f379859f
  • Перевод


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, он использует очередь и предыдущие значения элементов для обеспечения симметрии. При первом появлении значений их цвета помещаются в очередь, а зеркальные квадраты удаляют цвета.


Полный процесс создания

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

Заключение


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

Pixonic

271,00

Международная компания по разработке мобильных игр

Поделиться публикацией
Комментарии 21
    +7
    Где-то у меня валяется код на Actionscript 3, рендерящий относительно пристойно выглядящий лес, размером строк в двести и 12 килобайт исполняемого кода (SWF). Правда, вместо попиксельной рисовки там честное (почти честное) 3D для каждого дерева в лесу. Ну и работает со скоростью роста настоящего леса. Ссылки попротухали за шесть лет, но перевыложить могу, в принципе.
      +4
      Напишите публикацию, уверен, многим будет интересно (мне — точно).
        +5
        Готово: habr.com/post/429256 правда, самого кода там не так много, больше алгоритмов в виде текста.
          +1
          И все равно получилось отлично)
            0
            Спасибо, очень интересно.
          +2
          +1 к просьбе о публикации :)
          +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('Даже по цифрам не попадаете... Попробуйте ещё раз')

            +1
            Кажется это можно использовать как новый метод стеганографии.Нужно хеш-стеганографию подружить с генеративным искусством ;))
              +10

              А расшифровку этого метода назовём дегенеративным искусством.

                0
                Рабочий день кончился ;)))
              +2
              И если теперь подключить к вязальной машине, можно сделать интересный свитер. Но скучно, наверное.
                0
                Демосцена вас укуси.

                Данный код занимает ~2kb, то есть одну сорок восьмую часть этого: en.wikipedia.org/wiki/.kkrieger =)

                  +2
                  Сравнивать размер исходников и заоптимизированного под компактность скомпилированного бинарника — довольно неблагодарное занятие. Ну и вообще, где энтрилевельный любительский код на питоне, а где демосцена.
                    0
                    Благодарное. Хорошая иллюстрация — есть к чему стремиться. В конце концов в компо ограничение на объём, и не важно на чём написано.

                    А по ключевому слову «демосцена» можно найти достаточно материалов по процедурной графике в том числе.

                    Полезно знать общественное цифровое наследие и достояние.
                  0
                  А возможностей питона хватит чтобы визуализировать содержимое подключенного тома или папки вот в таком виде:

                  Скриншот k4dirstat



                  У меня есть желание создать свой похожий алгоритм, визуализирующий содержимое директорий и дисков, но я не знаю как подступиться к задаче. Т.к. знания о программировании ограничены знаниями BASIC, изученного больше десятилетия назад.
                    0
                    А можете пояснить что под спойлером? Не понятно…
                      0
                      «чтобы визуализировать содержимое подключенного тома или папки...»)

                      «Скриншот k4dirstat»
                        0
                        Эти квадратные и прямоугольные ячейки отображают файлы. Чем больше размер файла, тем больше площадь ячейки. Но мне не нравится эта визуализация, хочу свою запилить и не знаю как начать.
                        +1
                        Хватит. Советую для начала почитать туториал на английском в документации Python, если с английским проблем нет (даже если есть — лучше попробовать). Советую так же узнать что такое pip для python и как им пользоваться.

                        Вижу k4dirstat написан на Qt. Есть PyQt, с помощью которого можно зафигачить такой же интерфейс и привязать его к проге на питоне. Если не боитесь чужого кода (и вообще это отличная мысль) можно даже попробовать портировать интерфейс самого k4dirstat. Qt у нас кросплатформенный.

                        Можно и на tkinter'е написать интерфейс в принципе, если хотите. Он вовсе включен в стандартную библиотеку питона.
                          0
                          Сама программа хорошая и удобная. Меня не устраивает часть, которая визуализирует размер файлов.

                          Я как программер нулевой, моя компетенция GUI и визуальные «рюшечки». Просто пока я изучу работу с ОС, ФС, файлами и т.п. то состарюсь до 70 лет :)

                          А если я алгоритмически опишу свою версию хотя бы в общих чертах, то разраб k4dirstat вероятно возьмет мой алгоритм.

                            +1
                            Да, дерзай. Вообще питон довольно простой язык и я сомневаюсь, что на какое-нибудь вообще решение этой проблемы будет потрачено настолько много времени. Полагаю там надо будет использовать встроенный модуль os для действий с… ОС.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое