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)


  1. vesper-bot
    08.11.2018 14:22
    +6

    Где-то у меня валяется код на Actionscript 3, рендерящий относительно пристойно выглядящий лес, размером строк в двести и 12 килобайт исполняемого кода (SWF). Правда, вместо попиксельной рисовки там честное (почти честное) 3D для каждого дерева в лесу. Ну и работает со скоростью роста настоящего леса. Ссылки попротухали за шесть лет, но перевыложить могу, в принципе.


    1. old_gamer
      08.11.2018 16:33
      +4

      Напишите публикацию, уверен, многим будет интересно (мне — точно).


      1. vesper-bot
        09.11.2018 12:57
        +4

        Готово: habr.com/post/429256 правда, самого кода там не так много, больше алгоритмов в виде текста.


        1. azazelis Автор
          09.11.2018 12:58
          +1

          И все равно получилось отлично)


        1. old_gamer
          09.11.2018 15:35

          Спасибо, очень интересно.


    1. VioletGiraffe
      08.11.2018 19:12
      +2

      +1 к просьбе о публикации :)


  1. 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('Даже по цифрам не попадаете... Попробуйте ещё раз')


  1. PavelMSTU
    08.11.2018 19:00
    +1

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


    1. mikhailian
      08.11.2018 19:08
      +10

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


      1. PavelMSTU
        08.11.2018 19:09

        Рабочий день кончился ;)))


  1. dcc0
    08.11.2018 19:46
    +2

    И если теперь подключить к вязальной машине, можно сделать интересный свитер. Но скучно, наверное.


  1. oisee
    09.11.2018 00:46

    Демосцена вас укуси.

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


    1. synedra
      09.11.2018 06:37
      +2

      Сравнивать размер исходников и заоптимизированного под компактность скомпилированного бинарника — довольно неблагодарное занятие. Ну и вообще, где энтрилевельный любительский код на питоне, а где демосцена.


      1. oisee
        09.11.2018 10:13

        Благодарное. Хорошая иллюстрация — есть к чему стремиться. В конце концов в компо ограничение на объём, и не важно на чём написано.

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

        Полезно знать общественное цифровое наследие и достояние.


  1. engine9
    09.11.2018 12:22

    А возможностей питона хватит чтобы визуализировать содержимое подключенного тома или папки вот в таком виде:

    Скриншот k4dirstat