100行未満のコードでジェネレーティブアートを作成する方法

ジェネレーティブアートは、他のプログラミングトピックと同様に、これまで試したことがない場合は威圧的になる可能性があります。プログラミングを創造的に活用できる新しい方法を見つけるのが大好きなので、私はいつもそれに興味を持っていました。さらに、文字通り自分自身を創り出すアートワークのコンセプトは誰でも理解できると思います。

ジェネレーティブアートとは何ですか?

ジェネレーティブアートは、人間ではなく、作品について独自の決定を下すシステムの成果です。システムには、ルールランダム性の側面があれば、単一のPythonプログラムと同じくらい単純にすることができます

プログラミングでは、ルールと制約を考え出すのは非常に簡単です。条件文は以上です。そうは言っても、これらのルールで何か面白いものを作成する方法を見つけるのは難しい場合があります。

Game of Lifeは、システム内の各セルの「誕生」と「死」を決定する4つの単純なルールの有名なセットです。各ルールは、各世代を通じてシステムを前進させる上で役割を果たします。ルールはシンプルで理解しやすいものですが、複雑なパターンがすぐに現れ始め、最終的には魅力的な結果を形成します。

ルールは何か面白いものの基盤を作る責任があるかもしれませんが、コンウェイのライフゲームのようにエキサイティングなものでさえ予測可能です。4つのルールが各世代の決定要因であるため、予測できない結果を生成する方法は、セルの開始状態でランダム化を導入することです。ランダム行列から始めると、ルールを変更することなく、各実行が一意になります。

ジェネレーティブアートの最良の例は、統計的に再現不可能な興味深いものを作成するために、予測可能性とランダム性の組み合わせを見つけるものです。

なぜあなたはそれを試すべきですか?

すべてのサイドプロジェクトが同じように作成されているわけではなく、ジェネレーティブアートはあなたが時間を費やしたいと思うものではないかもしれません。ただし、プロジェクトに取り組むことにした場合は、次のようなメリットが期待できます。

  • 経験—ジェネレーティブアートは、新旧のスキルを磨くもう1つの機会です。これは、アルゴリズム、データ構造、さらには新しい言語などの概念を実践するためのゲートウェイとして機能します。
  • に見える結果—プログラミングの世界では、私たちの努力から物理的なものが生まれることはめったに見られません。少なくとも私はそうは思いません。現在、リビングルームにジェネレーティブアートのプリントを表示するポスターがいくつかありますが、プログラミングがその原因であることが大好きです。
  • 魅力的なプロジェクト—私たちは皆、プロジェクトの努力と結果を伝える簡単な方法がなくても、おそらく面接中にさえ、誰かに個人的なプロジェクトを説明した経験があります。ジェネレーティブアートはそれ自体を物語っています。方法を完全に理解できなくても、ほとんどの人はあなたの作品に感銘を受けるでしょう。

どこから始めればいいですか?

ジェネレーティブアートを始めることは他のプロジェクトと同じプロセスです。最も重要なステップは、アイデアを思いつくか、それを基に構築するものを見つけることです。目標を念頭に置いたら、それを達成するために必要なテクノロジーに取り組み始めることができます。

私のジェネレーティブアートプロジェクトのほとんどはPythonで達成されています。慣れるのはかなり簡単な言語であり、Pillowなどの画像操作に役立ついくつかの素晴らしいパッケージがあります。

幸いなことに、開始点をそれほど遠くまで検索する必要はありません。これは、以下にいくつかのコードを提供して、遊んでもらうためです。

スプライトジェネレーター

このプロジェクトは、Javascriptで書かれたスプライトジェネレーターを披露する投稿を見たときに始まりました。プログラムは、いくつかのランダムな色のオプションを使用して5x5ピクセルのアートスプライトを作成し、その出力はマルチカラーのスペースインベーダーに似ていました。

Pythonで画像操作を練習したいと思っていたので、自分でこの概念を再現してみることができると思いました。さらに、元のプロジェクトではスプライトのサイズが非常に限られていたので、それを拡張できると思いました。サイズだけでなく、その数や画像のサイズも指定できるようにしたかったのです。

これは、私が最終的に得たソリューションからの2つの異なる出力を示しています。

これらの2つの画像はまったく似ていませんが、どちらも同じシステムの結果です。言うまでもなく、画像の複雑さとスプライト生成のランダム性のために、同じ引数を使用しても、これらの画像は永遠に類のないものになる可能性が非常に高くなります。大好きです。

環境

スプライトジェネレーターをいじり始めたい場合は、最初に行う必要のある基本的な作業が少しあります。

Pythonで適切な環境を設定するのは難しい場合があります。これまでPythonを使用したことがない場合は、Python2.7.10をダウンロードする必要があります。最初は環境のセットアップに問題があったので、問題が発生した場合は、私が行ったことを実行して仮想環境を調べることができます。最後になりましたが、Pillowもインストールされていることを確認してください。

Once you have the environment set up, you can copy my code into a file with extension .py and execute with the following command:

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

For example, the command to create the first matrix of sprites from above would be:

python spritething.py 7 30 1900

The code

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]))

This solution is a long way from perfect, but it shows that creating generative art doesn’t take a ton of code. I’ll try my best to explain the key pieces.

The main function starts by creating the initial image and determining the size of the sprites. The two for loops are responsible for defining a border for each sprite, basically dividing the dimensions of the image by the number of sprites requested. These values are used to determine the coordinates for each one.

Let’s ignore padding and take a look at the image below. Imagine that each of the four squares represents a sprite with a size of 1. The border that is being passed to the next function refers to the top left and bottom right coordinates. So the tuple for the top left sprite would be (0,0,1,1) whereas the tuple for the top right would be (1,0,2,1). These will be used as the dimensions and base coordinates for the squares of each sprite.

The function create_invader determines the border for each square within the sprite. The same process for determining the border is applied here and represented below, only instead of the full image we’re using a pre-determined border to work inside. These final coordinates for each square will be used in the next function to actually draw the sprite.

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!