2017-08-09 16:46:59 -04:00
|
|
|
from PyQt5 import QtGui, QtCore, QtWidgets
|
2017-08-10 00:46:31 -04:00
|
|
|
from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter
|
2017-08-09 16:46:59 -04:00
|
|
|
import os
|
|
|
|
import math
|
|
|
|
|
|
|
|
from component import Component
|
2017-08-10 17:27:59 -04:00
|
|
|
from toolkit import alphabetizeDict
|
2017-08-10 00:46:31 -04:00
|
|
|
from toolkit.frame import BlankFrame, scale
|
2017-08-09 16:46:59 -04:00
|
|
|
|
|
|
|
|
|
|
|
class Component(Component):
|
|
|
|
name = 'Conway\'s Game of Life'
|
2017-08-10 17:27:59 -04:00
|
|
|
version = '1.0.0'
|
2017-08-09 16:46:59 -04:00
|
|
|
|
|
|
|
def widget(self, *args):
|
|
|
|
super().widget(*args)
|
|
|
|
self.scale = 32
|
|
|
|
self.updateGridSize()
|
|
|
|
self.startingGrid = {}
|
2017-08-10 00:46:31 -04:00
|
|
|
self.page.pushButton_pickImage.clicked.connect(self.pickImage)
|
2017-08-09 16:46:59 -04:00
|
|
|
self.trackWidgets({
|
|
|
|
'tickRate': self.page.spinBox_tickRate,
|
|
|
|
'scale': self.page.spinBox_scale,
|
|
|
|
'color': self.page.lineEdit_color,
|
|
|
|
'shapeType': self.page.comboBox_shapeType,
|
|
|
|
'shadow': self.page.checkBox_shadow,
|
2017-08-10 00:46:31 -04:00
|
|
|
'customImg': self.page.checkBox_customImg,
|
2017-08-10 21:57:06 -04:00
|
|
|
'showGrid': self.page.checkBox_showGrid,
|
2017-08-10 00:46:31 -04:00
|
|
|
'image': self.page.lineEdit_image,
|
2017-08-09 16:46:59 -04:00
|
|
|
}, colorWidgets={
|
|
|
|
'color': self.page.pushButton_color,
|
|
|
|
})
|
2017-08-10 21:57:06 -04:00
|
|
|
self.shiftButtons = (
|
|
|
|
self.page.toolButton_up,
|
|
|
|
self.page.toolButton_down,
|
|
|
|
self.page.toolButton_left,
|
|
|
|
self.page.toolButton_right,
|
|
|
|
)
|
|
|
|
def shiftFunc(i):
|
|
|
|
def shift():
|
|
|
|
self.shiftGrid(i)
|
|
|
|
return shift
|
|
|
|
shiftFuncs = [shiftFunc(i) for i in range(len(self.shiftButtons))]
|
|
|
|
for i, widget in enumerate(self.shiftButtons):
|
|
|
|
widget.clicked.connect(shiftFuncs[i])
|
2017-08-09 16:46:59 -04:00
|
|
|
self.page.spinBox_scale.setValue(self.scale)
|
2017-08-10 00:46:31 -04:00
|
|
|
self.page.spinBox_scale.valueChanged.connect(self.updateGridSize)
|
|
|
|
|
|
|
|
def pickImage(self):
|
|
|
|
imgDir = self.settings.value("componentDir", os.path.expanduser("~"))
|
|
|
|
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
|
|
|
|
self.page, "Choose Image", imgDir,
|
|
|
|
"Image Files (%s)" % " ".join(self.core.imageFormats))
|
|
|
|
if filename:
|
|
|
|
self.settings.setValue("componentDir", os.path.dirname(filename))
|
|
|
|
self.page.lineEdit_image.setText(filename)
|
|
|
|
self.update()
|
2017-08-09 16:46:59 -04:00
|
|
|
|
2017-08-10 21:57:06 -04:00
|
|
|
def shiftGrid(self, d):
|
|
|
|
def newGrid(Xchange, Ychange):
|
|
|
|
return {
|
|
|
|
(x + Xchange, y + Ychange): True
|
|
|
|
for x, y in self.startingGrid
|
|
|
|
}
|
|
|
|
|
|
|
|
if d == 0:
|
|
|
|
newGrid = newGrid(0, -1)
|
|
|
|
elif d == 1:
|
|
|
|
newGrid = newGrid(0, 1)
|
|
|
|
elif d == 2:
|
|
|
|
newGrid = newGrid(-1, 0)
|
|
|
|
elif d == 3:
|
|
|
|
newGrid = newGrid(1, 0)
|
|
|
|
self.startingGrid = newGrid
|
|
|
|
self.sendUpdateSignal()
|
|
|
|
|
2017-08-09 16:46:59 -04:00
|
|
|
def update(self):
|
|
|
|
self.updateGridSize()
|
2017-08-10 00:46:31 -04:00
|
|
|
if self.page.checkBox_customImg.isChecked():
|
|
|
|
self.page.label_color.setVisible(False)
|
|
|
|
self.page.lineEdit_color.setVisible(False)
|
|
|
|
self.page.pushButton_color.setVisible(False)
|
|
|
|
self.page.label_shape.setVisible(False)
|
|
|
|
self.page.comboBox_shapeType.setVisible(False)
|
|
|
|
self.page.label_image.setVisible(True)
|
|
|
|
self.page.lineEdit_image.setVisible(True)
|
|
|
|
self.page.pushButton_pickImage.setVisible(True)
|
|
|
|
else:
|
|
|
|
self.page.label_color.setVisible(True)
|
|
|
|
self.page.lineEdit_color.setVisible(True)
|
|
|
|
self.page.pushButton_color.setVisible(True)
|
|
|
|
self.page.label_shape.setVisible(True)
|
|
|
|
self.page.comboBox_shapeType.setVisible(True)
|
|
|
|
self.page.label_image.setVisible(False)
|
|
|
|
self.page.lineEdit_image.setVisible(False)
|
|
|
|
self.page.pushButton_pickImage.setVisible(False)
|
2017-08-10 21:57:06 -04:00
|
|
|
enabled = (len(self.startingGrid) > 0)
|
|
|
|
for widget in self.shiftButtons:
|
|
|
|
widget.setEnabled(enabled)
|
2017-08-09 16:46:59 -04:00
|
|
|
super().update()
|
|
|
|
|
|
|
|
def previewClickEvent(self, pos, size, button):
|
|
|
|
pos = (
|
|
|
|
math.ceil((pos[0] / size[0]) * self.gridWidth) - 1,
|
|
|
|
math.ceil((pos[1] / size[1]) * self.gridHeight) - 1
|
|
|
|
)
|
|
|
|
if button == 1:
|
|
|
|
self.startingGrid[pos] = True
|
|
|
|
elif button == 2 and pos in self.startingGrid:
|
|
|
|
self.startingGrid.pop(pos)
|
|
|
|
|
|
|
|
def updateGridSize(self):
|
|
|
|
w, h = self.core.resolutions[-1].split('x')
|
|
|
|
self.gridWidth = int(int(w) / self.scale)
|
|
|
|
self.gridHeight = int(int(h) / self.scale)
|
|
|
|
self.pxWidth = math.ceil(self.width / self.gridWidth)
|
|
|
|
self.pxHeight = math.ceil(self.height / self.gridHeight)
|
|
|
|
|
|
|
|
def previewRender(self):
|
|
|
|
return self.drawGrid(self.startingGrid)
|
|
|
|
|
|
|
|
def preFrameRender(self, *args, **kwargs):
|
|
|
|
super().preFrameRender(*args, **kwargs)
|
|
|
|
self.progressBarSetText.emit("Computing evolution...")
|
|
|
|
self.tickGrids = {0: self.startingGrid}
|
|
|
|
tick = 0
|
|
|
|
for frameNo in range(
|
|
|
|
self.tickRate, len(self.completeAudioArray), self.sampleSize
|
|
|
|
):
|
2017-08-10 00:46:31 -04:00
|
|
|
if self.parent.canceled:
|
|
|
|
break
|
2017-08-09 16:46:59 -04:00
|
|
|
if frameNo % self.tickRate == 0:
|
|
|
|
tick += 1
|
|
|
|
self.tickGrids[tick] = self.gridForTick(tick)
|
|
|
|
|
|
|
|
# update progress bar
|
|
|
|
progress = int(100*(frameNo/len(self.completeAudioArray)))
|
|
|
|
if progress >= 100:
|
|
|
|
progress = 100
|
|
|
|
pStr = "Computing evolution: "+str(progress)+'%'
|
|
|
|
self.progressBarSetText.emit(pStr)
|
|
|
|
self.progressBarUpdate.emit(int(progress))
|
|
|
|
|
2017-08-10 00:46:31 -04:00
|
|
|
def properties(self):
|
|
|
|
if self.customImg and (
|
|
|
|
not self.image or not os.path.exists(self.image)
|
|
|
|
):
|
|
|
|
return ['error']
|
|
|
|
return []
|
|
|
|
|
|
|
|
def error(self):
|
|
|
|
return "No image selected to represent life."
|
|
|
|
|
2017-08-09 16:46:59 -04:00
|
|
|
def frameRender(self, frameNo):
|
|
|
|
tick = math.floor(frameNo / self.tickRate)
|
|
|
|
grid = self.tickGrids[tick]
|
|
|
|
return self.drawGrid(grid)
|
|
|
|
|
|
|
|
def drawGrid(self, grid):
|
|
|
|
frame = BlankFrame(self.width, self.height)
|
2017-08-10 00:46:31 -04:00
|
|
|
|
|
|
|
def drawCustomImg():
|
|
|
|
try:
|
|
|
|
img = Image.open(self.image)
|
|
|
|
except Exception:
|
|
|
|
return
|
|
|
|
img = img.resize((self.pxWidth, self.pxHeight), Image.ANTIALIAS)
|
|
|
|
frame.paste(img, box=(drawPtX, drawPtY))
|
|
|
|
|
|
|
|
def drawShape():
|
|
|
|
drawer = ImageDraw.Draw(frame)
|
2017-08-10 09:12:48 -04:00
|
|
|
rect = (
|
|
|
|
(drawPtX, drawPtY),
|
|
|
|
(drawPtX + self.pxWidth, drawPtY + self.pxHeight)
|
|
|
|
)
|
|
|
|
shape = self.page.comboBox_shapeType.currentText().lower()
|
2017-08-10 00:46:31 -04:00
|
|
|
|
|
|
|
# Rectangle
|
2017-08-10 09:12:48 -04:00
|
|
|
if shape == 'rectangle':
|
2017-08-10 00:46:31 -04:00
|
|
|
drawer.rectangle(rect, fill=self.color)
|
|
|
|
|
2017-08-10 09:12:48 -04:00
|
|
|
# Elliptical
|
|
|
|
elif shape == 'elliptical':
|
2017-08-10 00:46:31 -04:00
|
|
|
drawer.ellipse(rect, fill=self.color)
|
|
|
|
|
|
|
|
tenthX, tenthY = scale(10, self.pxWidth, self.pxHeight, int)
|
|
|
|
smallerShape = (
|
|
|
|
(drawPtX + tenthX + int(tenthX / 4),
|
|
|
|
drawPtY + tenthY + int(tenthY / 2)),
|
|
|
|
(drawPtX + self.pxWidth - tenthX - int(tenthX / 4),
|
|
|
|
drawPtY + self.pxHeight - (tenthY + int(tenthY / 2)))
|
|
|
|
)
|
|
|
|
outlineShape = (
|
|
|
|
(drawPtX + int(tenthX / 4),
|
|
|
|
drawPtY + int(tenthY / 2)),
|
|
|
|
(drawPtX + self.pxWidth - int(tenthX / 4),
|
|
|
|
drawPtY + self.pxHeight - int(tenthY / 2))
|
|
|
|
)
|
|
|
|
# Circle
|
2017-08-10 09:12:48 -04:00
|
|
|
if shape == 'circle':
|
2017-08-10 00:46:31 -04:00
|
|
|
drawer.ellipse(outlineShape, fill=self.color)
|
|
|
|
drawer.ellipse(smallerShape, fill=(0,0,0,0))
|
|
|
|
|
|
|
|
# Lilypad
|
2017-08-10 09:12:48 -04:00
|
|
|
elif shape == 'lilypad':
|
2017-08-10 00:46:31 -04:00
|
|
|
drawer.pieslice(smallerShape, 290, 250, fill=self.color)
|
|
|
|
|
|
|
|
# Pac-Man
|
2017-08-10 09:12:48 -04:00
|
|
|
elif shape == 'pac-man':
|
2017-08-10 00:46:31 -04:00
|
|
|
drawer.pieslice(outlineShape, 35, 320, fill=self.color)
|
|
|
|
|
|
|
|
hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline
|
|
|
|
tX, tY = scale(33, self.pxWidth, self.pxHeight, int) # thirdline
|
|
|
|
qX, qY = scale(20, self.pxWidth, self.pxHeight, int) # quarterline
|
|
|
|
|
2017-08-10 09:12:48 -04:00
|
|
|
# Path
|
|
|
|
if shape == 'path':
|
|
|
|
drawer.ellipse(rect, fill=self.color)
|
|
|
|
rects = {
|
|
|
|
direction: False
|
|
|
|
for direction in (
|
|
|
|
'up', 'down', 'left', 'right',
|
|
|
|
)
|
|
|
|
}
|
|
|
|
for cell in nearbyCoords(x, y):
|
|
|
|
if grid.get(cell) is None:
|
|
|
|
continue
|
|
|
|
if cell[0] == x:
|
|
|
|
if cell[1] < y:
|
|
|
|
rects['up'] = True
|
|
|
|
if cell[1] > y:
|
|
|
|
rects['down'] = True
|
|
|
|
if cell[1] == y:
|
|
|
|
if cell[0] < x:
|
|
|
|
rects['left'] = True
|
|
|
|
if cell[0] > x:
|
|
|
|
rects['right'] = True
|
|
|
|
|
|
|
|
for direction, rect in rects.items():
|
|
|
|
if rect:
|
|
|
|
if direction == 'up':
|
|
|
|
sect = (
|
|
|
|
(drawPtX, drawPtY),
|
|
|
|
(drawPtX + self.pxWidth, drawPtY + hY)
|
|
|
|
)
|
|
|
|
elif direction == 'down':
|
|
|
|
sect = (
|
|
|
|
(drawPtX, drawPtY + hY),
|
|
|
|
(drawPtX + self.pxWidth,
|
|
|
|
drawPtY + self.pxHeight)
|
|
|
|
)
|
|
|
|
elif direction == 'left':
|
|
|
|
sect = (
|
|
|
|
(drawPtX, drawPtY),
|
|
|
|
(drawPtX + hX,
|
|
|
|
drawPtY + self.pxHeight)
|
|
|
|
)
|
|
|
|
elif direction == 'right':
|
|
|
|
sect = (
|
|
|
|
(drawPtX + hX, drawPtY),
|
|
|
|
(drawPtX + self.pxWidth,
|
|
|
|
drawPtY + self.pxHeight)
|
|
|
|
)
|
|
|
|
drawer.rectangle(sect, fill=self.color)
|
|
|
|
|
2017-08-10 00:46:31 -04:00
|
|
|
# Duck
|
2017-08-10 09:12:48 -04:00
|
|
|
elif shape == 'duck':
|
2017-08-10 00:46:31 -04:00
|
|
|
duckHead = (
|
|
|
|
(drawPtX + qX, drawPtY + qY),
|
|
|
|
(drawPtX + int(qX * 3), drawPtY + int(tY * 2))
|
|
|
|
)
|
|
|
|
duckBeak = (
|
|
|
|
(drawPtX + hX, drawPtY + qY),
|
|
|
|
(drawPtX + self.pxWidth + qX,
|
|
|
|
drawPtY + int(qY * 3))
|
|
|
|
)
|
|
|
|
duckWing = (
|
|
|
|
(drawPtX, drawPtY + hY),
|
|
|
|
rect[1]
|
|
|
|
)
|
|
|
|
duckBody = (
|
|
|
|
(drawPtX + int(qX / 4), drawPtY + int(qY * 3)),
|
|
|
|
(drawPtX + int(tX * 2), drawPtY + self.pxHeight)
|
|
|
|
)
|
|
|
|
drawer.ellipse(duckBody, fill=self.color)
|
|
|
|
drawer.ellipse(duckHead, fill=self.color)
|
|
|
|
drawer.pieslice(duckWing, 130, 200, fill=self.color)
|
|
|
|
drawer.pieslice(duckBeak, 145, 200, fill=self.color)
|
|
|
|
|
|
|
|
# Peace
|
2017-08-10 09:12:48 -04:00
|
|
|
elif shape == 'peace':
|
2017-08-10 00:46:31 -04:00
|
|
|
line = (
|
|
|
|
(drawPtX + hX - int(tenthX / 2), drawPtY + int(tenthY / 2)),
|
|
|
|
(drawPtX + hX + int(tenthX / 2),
|
|
|
|
drawPtY + self.pxHeight - int(tenthY / 2))
|
|
|
|
)
|
|
|
|
drawer.ellipse(outlineShape, fill=self.color)
|
|
|
|
drawer.ellipse(smallerShape, fill=(0,0,0,0))
|
|
|
|
drawer.rectangle(line, fill=self.color)
|
|
|
|
slantLine = lambda difference: (
|
|
|
|
((drawPtX + difference),
|
|
|
|
(drawPtY + self.pxHeight - qY)),
|
|
|
|
((drawPtX + hX),
|
|
|
|
(drawPtY + hY)),
|
|
|
|
)
|
|
|
|
drawer.line(
|
|
|
|
slantLine(qX),
|
|
|
|
fill=self.color,
|
|
|
|
width=tenthX
|
|
|
|
)
|
|
|
|
drawer.line(
|
|
|
|
slantLine(self.pxWidth - qX),
|
|
|
|
fill=self.color,
|
|
|
|
width=tenthX
|
|
|
|
)
|
2017-08-09 16:46:59 -04:00
|
|
|
|
|
|
|
for x, y in grid:
|
|
|
|
drawPtX = x * self.pxWidth
|
2017-08-10 00:46:31 -04:00
|
|
|
if drawPtX > self.width:
|
|
|
|
continue
|
2017-08-09 16:46:59 -04:00
|
|
|
drawPtY = y * self.pxHeight
|
2017-08-10 00:46:31 -04:00
|
|
|
if drawPtY > self.height:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if self.customImg:
|
|
|
|
drawCustomImg()
|
|
|
|
else:
|
|
|
|
drawShape()
|
2017-08-09 16:46:59 -04:00
|
|
|
|
|
|
|
if self.shadow:
|
|
|
|
shadImg = ImageEnhance.Contrast(frame).enhance(0.0)
|
|
|
|
shadImg = shadImg.filter(ImageFilter.GaussianBlur(5.00))
|
|
|
|
shadImg = ImageChops.offset(shadImg, -2, 2)
|
|
|
|
shadImg.paste(frame, box=(0, 0), mask=frame)
|
|
|
|
frame = shadImg
|
2017-08-10 21:57:06 -04:00
|
|
|
if self.showGrid:
|
|
|
|
drawer = ImageDraw.Draw(frame)
|
|
|
|
w, h = scale(0.05, self.width, self.height, int)
|
|
|
|
for x in range(self.pxWidth, self.width, self.pxWidth):
|
|
|
|
drawer.rectangle(
|
|
|
|
((x, 0),
|
|
|
|
(x + w, self.height)),
|
|
|
|
fill=self.color,
|
|
|
|
)
|
|
|
|
for y in range(self.pxHeight, self.height, self.pxHeight):
|
|
|
|
drawer.rectangle(
|
|
|
|
((0, y),
|
|
|
|
(self.width, y + h)),
|
|
|
|
fill=self.color,
|
|
|
|
)
|
|
|
|
|
2017-08-09 16:46:59 -04:00
|
|
|
return frame
|
|
|
|
|
|
|
|
def gridForTick(self, tick):
|
|
|
|
'''Given a tick number over 0, returns a new grid dict of tuples'''
|
|
|
|
lastGrid = self.tickGrids[tick - 1]
|
|
|
|
|
|
|
|
def neighbours(x, y):
|
|
|
|
return [
|
2017-08-10 09:12:48 -04:00
|
|
|
cell for cell in nearbyCoords(x, y)
|
|
|
|
if lastGrid.get(cell) is not None
|
2017-08-09 16:46:59 -04:00
|
|
|
]
|
|
|
|
|
|
|
|
newGrid = {}
|
|
|
|
for x, y in lastGrid:
|
|
|
|
surrounding = len(neighbours(x, y))
|
|
|
|
if surrounding == 2 or surrounding == 3:
|
|
|
|
newGrid[(x, y)] = True
|
|
|
|
potentialNewCells = set([
|
|
|
|
coordTup for origin in lastGrid
|
|
|
|
for coordTup in list(nearbyCoords(*origin))
|
|
|
|
])
|
|
|
|
for x, y in potentialNewCells:
|
|
|
|
if (x, y) in newGrid:
|
|
|
|
continue
|
|
|
|
surrounding = len(neighbours(x, y))
|
|
|
|
if surrounding == 3:
|
|
|
|
newGrid[(x, y)] = True
|
|
|
|
|
|
|
|
return newGrid
|
|
|
|
|
|
|
|
def savePreset(self):
|
|
|
|
pr = super().savePreset()
|
2017-08-10 17:27:59 -04:00
|
|
|
pr['GRID'] = alphabetizeDict(self.startingGrid)
|
2017-08-09 16:46:59 -04:00
|
|
|
return pr
|
|
|
|
|
|
|
|
def loadPreset(self, pr, *args):
|
2017-08-10 17:27:59 -04:00
|
|
|
self.startingGrid = dict(pr['GRID'])
|
2017-08-10 21:57:06 -04:00
|
|
|
if self.startingGrid:
|
|
|
|
for widget in self.shiftButtons:
|
|
|
|
widget.setEnabled(True)
|
|
|
|
super().loadPreset(pr, *args)
|
2017-08-10 09:12:48 -04:00
|
|
|
|
|
|
|
|
|
|
|
def nearbyCoords(x, y):
|
|
|
|
yield x + 1, y + 1
|
|
|
|
yield x + 1, y - 1
|
|
|
|
yield x - 1, y + 1
|
|
|
|
yield x - 1, y - 1
|
|
|
|
yield x, y + 1
|
|
|
|
yield x, y - 1
|
|
|
|
yield x + 1, y
|
|
|
|
yield x - 1, y
|