Eis o programa desenvolvido no livro Generative Art: A Practical Guide Using Processing , do Listing 6.4:



Exercício Figura 6.4
Basicamente, o programa gera um array de 10 novas instâncias de círculos agrupados (2 a 2, círculos concêntricos: um grande com raio variável, colorido e sem borda e outro menor de raio 10, sem preenchimento, e com borda colorida, ambos com o mesmo centro (x,y)) que se movem aleatoriamente na tela. Quando se clica na tela ele gera mais 10 novos círculos e assim por diante. Compilando o programa em Processing, obtem-se o seguinte resultado para a Figura 6.4:

Implementação em Python:
Em Python 3.7, utilizou-se as bibliotecas Random, Time e Tkinter para desenvolver o algoritmo:
import random as rd
import tkinter as tk
import time as tm
O construtor da classe "Circle" gera os centros dos círculos com as coordenadas (x,y), raios e cores aleatórias por meio da função randint (biblioteca random) e desenha dois tipos de círculos com a função create_oval (biblioteca tkinter). O primeiro círculo possui preenchimento colorido, sem bordas e raio variável com centro (x,y). O segundo círculo é plotado conjuntamente com o primeiro sem preenchimento, bordas coloridas e mesmo centro (x,y).
class Circle:
def __init__(self,width,height,canvas,root):
self.width=width
self.height=height
self.x = rd.randint(0,self.width)
self.y = rd.randint(0,self.height)
self.radius = rd.randint(0,100) + 10
self.xmove = rd.randint(0,10)
self.ymove = rd.randint(0,10)
self.root = root
self.root.title('circle')
self.canvas = canvas
self.canvas.pack()
self.random_color= rd.randint(0,0x1000000)
self.color = '{:06x}'.format(self.random_color)
self.random_color2= rd.randint(0,0x1000000)
self.color2 = '{:06x}'.format(self.random_color2)
self.x0 = self.x - self.radius
self.y0 = self.y - self.radius
self.x1 = self.x + self.radius
self.y1 = self.y + self.radius
self.my_circle = self.canvas.create_oval(self.x0, self.y0, self.x1, self.y1,\
fill = ('#'+ self.color),
outline = "", tags="circle")
self.center_circle = self.canvas.create_oval(self.x-10, self.y-10, self.x+10, self.y+10,\
fill = "",
outline = ('#'+ self.color2), tags="centercircle")
Os métodos iter e next abaixo da classe "Circle" tornam o objeto iterável e retorna o próximo objeto da lista:
def __iter__(self):
# Retorna o objeto iterável (ele próprio: self)
return self
def __next__(self):
return self
O método updateMe da classe "Circle" move os objetos conjuntamente (dois círculos concêntricos) dentro da tela por meio da função move (biblioteca tkinter):
def updateMe(self):
self.canvas.move(self.my_circle, self.xmove, self.ymove)
self.canvas.move(self.center_circle, self.xmove, self.ymove)
self.position=self.canvas.coords(self.my_circle)
if self.position[2]>=self.width+self.radius or self.position[0]<=0:
self.xmove=-self.xmove
if self.position[3]>=self.height+self.radius or self.position[1]<=0:
self.ymove=-self.ymove
Para executar a figura 6.4 foi utilizado o script abaixo. Foi criada a classe "DrawandMove" que gera um Array de 10 objetos (2 círculos concêntricos de cada vez, totalizando 20 círculos ao todo). O método release cria mais 10 objetos ao clicar na tela e assim por diante.
class DrawandMove():
def __init__(self,root,width,height,canvas):
self.root=root
self.width=width
self.height=height
self.canvas=canvas
self.canvas.bind("<ButtonRelease-1>", self.release)
self.circleArr= []
def drawCircles(self):
for i in range(0,10):
self.thisCirc = object.__new__(Circle)
Circle.__init__(self.thisCirc,self.width,self.height,self.canvas,self.root)
self.circleArr.append(self.thisCirc)
def release(self,event):
a= event.x
b=event.y
self.drawCircles()
def draw(self):
for circle in self.circleArr:
circle.updateMe()
self.root.after(25,self.draw)
if __name__ == '__main__':
width,height=1000,600
root=tk.Tk()
root.wm_attributes('-alpha', 0.9)
canvas=tk.Canvas(root,width=1000,height=600,bg='white')
l=object.__new__(DrawandMove)
DrawandMove.__init__(l,root,width,height,canvas)
l.drawCircles()
l.draw()
root.mainloop()
Uma outra forma de executar a Figura 6.4 sem utilizar o recurso de criar a classe "DrawandMove", seria utilizando uma variável global para o Array dos círculos:
if __name__ == '__main__':
global circleArr
circleArr= []
def drawCircles(canvas,width,height,root):
for i in range(0,10):
thisCirc = object.__new__(Circle)
Circle.__init__(thisCirc,width,height,canvas,root)
circleArr.append(thisCirc)
def release(event):
a= event.x
b=event.y
drawCircles(canvas,width,height,root)
def draw():
for circle in circleArr:
circle.updateMe()
root.after(25,draw)
width=1000
height=600
root=tk.Tk()
root.wm_attributes('-alpha', 0.90)
canvas=tk.Canvas(root,width=1000,height=600,bg='white')
drawCircles(canvas,width,height,root)
draw()
canvas.bind("<ButtonRelease-1>", release)
root.update()
tm.sleep(0.05)
root.mainloop()
Segue a versão completa do programa, com o uso de duas classes:
import random as rd
import tkinter as tk
class Circle:
def __init__(self,width,height,canvas,root):
self.width=width
self.height=height
self.x = rd.randint(0,self.width)
self.y = rd.randint(0,self.height)
self.radius = rd.randint(0,100) + 10
self.xmove = rd.randint(0,10)
self.ymove = rd.randint(0,10)
self.root = root
self.root.title('circle')
self.canvas = canvas
self.canvas.pack()
self.random_color= rd.randint(0,0x1000000)
self.color = '{:06x}'.format(self.random_color)
self.random_color2= rd.randint(0,0x1000000)
self.color2 = '{:06x}'.format(self.random_color2)
self.x0 = self.x - self.radius
self.y0 = self.y - self.radius
self.x1 = self.x + self.radius
self.y1 = self.y + self.radius
self.my_circle = self.canvas.create_oval(self.x0, self.y0, self.x1, self.y1,\
fill = ('#'+ self.color),
outline = "", tags="circle")
self.center_circle = self.canvas.create_oval(self.x-10, self.y-10, self.x+10, self.y+10,\
fill = "",
outline = ('#'+ self.color2), tags="centercircle")
def __iter__(self):
# Retorna o objeto iterável (ele próprio: self)
return self
def __next__(self):
return self
def updateMe(self):
self.canvas.move(self.my_circle, self.xmove, self.ymove)
self.canvas.move(self.center_circle, self.xmove, self.ymove)
self.position=self.canvas.coords(self.my_circle)
if self.position[2]>=self.width+self.radius or self.position[0]<=0:
self.xmove=-self.xmove
if self.position[3]>=self.height+self.radius or self.position[1]<=0:
self.ymove=-self.ymove
class DrawandMove():
def __init__(self,root,width,height,canvas):
self.root=root
self.width=width
self.height=height
self.canvas=canvas
self.canvas.bind("<ButtonRelease-1>", self.release)
self.circleArr= []
def drawCircles(self):
for i in range(0,10):
self.thisCirc = object.__new__(Circle)
Circle.__init__(self.thisCirc,self.width,self.height,self.canvas,self.root)
self.circleArr.append(self.thisCirc)
def release(self,event):
a= event.x
b=event.y
self.drawCircles()
def draw(self):
for circle in self.circleArr:
circle.updateMe()
self.root.after(25,self.draw)
if __name__ == '__main__':
width,height=1000,600
root=tk.Tk()
root.wm_attributes('-alpha', 0.9)
canvas=tk.Canvas(root,width=1000,height=600,bg='white')
l=object.__new__(DrawandMove)
DrawandMove.__init__(l,root,width,height,canvas)
l.drawCircles()
l.draw()
root.mainloop()
Eis a Figura 6,4, gerada pelo Python:

Exercício Figura 6.5
Para a Figura 6.5, tem-se a a modificação do programa para que, se quaisquer dois círculos interceptarem-se, apagam-se os círculos maiores e mantém-se os círculos de raio 10. No caso dos círculos não se cruzarem, e ficarem livres, devem ficar mais opacos (mais claros). De forma, que, ao longo do tempo os círculos maiores e coloridos vão esmaecendo e só sobram os círculos de raio 10. Essa é a modificação, na linguagem Processing, proposta pelo livro no método updateMe da classe Circle:


A figura gerada no Processing é mostrada a seguir:

Implementação em Python:
O módulo Tkinter não possui uma entrada RGBA (RGB + alpha), com o valor para alpha que trata da opacidade (transparência) da figura. Para tanto, foi utilizado um artifício da biblioteca PIL para possibilitar habilitar o alpha e assim, conseguir adaptar o programa. Em Python, segue o programa:
import tkinter as tk
import random as rd
import math as mt
from PIL import Image, ImageTk,ImageDraw
class Circle:
def __init__(self,width,height,canvas,root,circleArr):
self.circleArr=circleArr
self.width=width
self.height=height
self.x = rd.randint(0,self.width)/2
self.y = rd.randint(0,self.height)/2
self.radius = rd.randint(10,100) + 10
self.xmove = rd.randint(0,10) - 5
self.ymove = rd.randint(0,10) - 5
self.alpha = rd.randint(0,255)
self.root = root
self.root.title('circle')
self.canvas = canvas
self.canvas.pack()
self.fillcol = (rd.randint(0,255), rd.randint(0,255), rd.randint(0,255))
self.fillcol2 = (rd.randint(0,255), rd.randint(0,255), rd.randint(0,255))
def __iter__(self):
# Retorna o objeto iterável (ele próprio: self)
return self
def __next__(self):
return self
def create_circle(self,a,b,c,d, **kwargs):
if 'alpha' in kwargs:
alpha = kwargs.pop('alpha')
fill = kwargs.pop('fill')
fill = fill + (alpha,)
outline = kwargs.pop('outline')
self.image = Image.new('RGBA', (self.width,self.height),color=0)
self.draw = ImageDraw.Draw(self.image)
self.draw.ellipse([a,b,c,d],fill,outline)
self.images.append(ImageTk.PhotoImage(self.image))
return self.canvas.create_image((a+c)/2,(b+d)/2, image=self.images[-1], anchor='nw')
def drawMe(self):
self.images = []
self.x0 = self.x - self.radius
self.y0 = self.y - self.radius
self.x1 = self.x + self.radius
self.y1 = self.y + self.radius
self.my_circle = self.create_circle(self.x0, self.y0, self.x1, self.y1,fill=self.fillcol,outline=None,alpha=self.alpha)
self.center_circle= self.create_circle(self.x-10, self.y-10, self.x+10, self.y+10,fill=(255,255,255),outline=self.fillcol2,alpha=0)
def updateMe(self):
self.x += self.xmove
self.y += self.ymove
self.canvas.move(self.my_circle, self.xmove, self.ymove)
self.canvas.move(self.center_circle, self.xmove, self.ymove)
self.position=self.canvas.bbox(self.my_circle)
if self.position[0]>=(0.5*self.width) or self.position[0]<=0:
self.xmove=-self.xmove
if self.position[1]>=(0.5*self.height) or self.position[1]<=0:
self.ymove=-self.ymove
touching = False
for i in range(0,len(self.circleArr)):
otherCirc = self.circleArr[i]
coord = self.canvas.bbox(self.my_circle)
coord1 = self.canvas.bbox(otherCirc.my_circle)
if(otherCirc != self):
dis= mt.sqrt((coord[0]-coord1[0])**2+(coord[1]-coord1[1])**2)
rule= dis - self.radius - otherCirc.radius
if (rule < 0):
touching = True
break
if (touching):
if (self.alpha > 0):
self.alpha =0
self.fill = self.fillcol + (self.alpha,)
self.outline =self.fillcol2
self.drawMe()
else:
if (self.alpha <= 255):
self.alpha -= 2
self.fill = self.fillcol + (self.alpha,)
self.outline =self.fillcol2
self.drawMe()
return
class DrawandMove():
def __init__(self,root,width,height,canvas):
self.root=root
self.width=width
self.height=height
self.canvas=canvas
self.canvas.bind("<ButtonRelease-1>", self.release)
self.circleArr= []
def drawCircles(self):
for i in range(0,10):
self.thisCirc = object.__new__(Circle)
Circle.__init__(self.thisCirc,self.width,self.height,self.canvas,self.root,self.circleArr)
self.thisCirc.drawMe()
self.circleArr.append(self.thisCirc)
def release(self,event):
a= event.x
b=event.y
self.drawCircles()
def draw(self):
for circle in self.circleArr:
circle.updateMe()
self.root.after(25,self.draw)
if __name__ == '__main__':
width,height=1000,600
root=tk.Tk()
canvas=tk.Canvas(root,width=1000,height=600,bg='white')
l=object.__new__(DrawandMove)
DrawandMove.__init__(l,root,width,height,canvas)
l.drawCircles()
l.draw()
root.mainloop()
Essa é a sequência de evolução da Figura 6.5, gerada no Python:

