Как да създадете генеративно изкуство в по-малко от 100 реда код

Генеративното изкуство, както всяка тема за програмиране, може да бъде смущаващо, ако никога преди не сте го опитвали. Винаги съм се интересувал от него, защото обичам да намирам нови начини програмирането да бъде използвано креативно. Освен това, мисля, че всеки може да оцени концепцията за произведение на изкуството, която буквално се създава.

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

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

С програмирането е доста лесно да се измислят правила и ограничения. Това са всички условни изявления. Като казахте това, намирането на начини да накарате тези правила да създадат нещо интересно може да бъде сложно.

Играта на живота е известен набор от четири прости правила, които определят „раждането” и „смъртта” на всяка клетка в системата. Всяко от правилата играе роля в развитието на системата през всяко поколение. Въпреки че правилата са прости и лесни за разбиране, сложните модели бързо започват да се появяват и в крайна сметка формират очарователни резултати.

Правилата може да са отговорни за създаването на основата на нещо интересно, но дори нещо толкова вълнуващо като Играта на живота на Конуей е предсказуемо. Тъй като четирите правила са определящите фактори за всяко поколение, начинът да се получат непредвидими резултати е въвеждането на рандомизация в началното състояние на клетките. Започвайки с произволна матрица ще направи всяко изпълнение уникално, без да се налага промяна на правилата.

Най-добрите примери за генеративно изкуство са тези, които намират комбинация от предсказуемост и случайност, за да създадат нещо интересно, което също е статистически невъзпроизводимо .

Защо трябва да опитате?

Не всички странични проекти са създадени еднакво и генеративното изкуство може да не е нещо, за което сте склонни да отделите време. Ако решите обаче да работите по проект, можете да очаквате следните предимства:

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

Къде трябва да започнете?

Започването с генеративно изкуство е същият процес като всеки проект, най-важната стъпка е да излезете с идея или да намерите такава, върху която да надграждате. След като имате предвид една цел, можете да започнете да работите по технологията, необходима за нейното постигане.

Повечето от моите генеративни арт проекти са реализирани в Python. Това е доста лесен език за свикване и разполага с някои невероятни пакети за помощ при манипулиране на изображения, като Pillow.

За ваш късмет няма нужда да търсите много далеч за начална точка, защото отдолу съм предоставил малко код, с който да играете.

Спрайт генератор

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

Знаех, че искам да практикувам манипулация на изображения в Python, затова реших, че мога просто да се опитам да пресъздам тази концепция сам. Освен това мислех, че мога да го разширя, тъй като първоначалният проект беше толкова ограничен по размер на спрайтовете. Исках да мога да посоча не само размера, но и броя им и дори размера на изображението.

Ето поглед към два различни изхода от решението, с което се озовах:

Тези две изображения изобщо не си приличат, но и двете са резултатите от една и съща система. Да не говорим, поради сложността на изображението и случайността на генерирането на спрайт, има изключително голяма вероятност дори при едни и същи аргументи тези изображения да останат завинаги единствени. Обичам го.

Околната среда

Ако искате да започнете да играете с генератора на спрайтове, първо трябва да се извърши малка основна работа.

Настройването на подходяща среда с Python може да бъде сложно. Ако досега не сте работили с Python, вероятно ще трябва да изтеглите Python 2.7.10. Първоначално имах проблеми с настройката на средата, така че ако започнете да срещате проблеми, можете да направите това, което направих аз, и да разгледаме виртуални среди. Не на последно място, уверете се, че сте инсталирали и Възглавница.

След като настроите средата, можете да копирате кода ми във файл с разширение .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!