from PyQt5 import QtGui, QtCore, QtWidgets from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter import os import math from component import Component from toolkit.frame import BlankFrame, scale class Component(Component): name = 'Conway\'s Game of Life' version = '1.0.0a' def widget(self, *args): super().widget(*args) self.scale = 32 self.updateGridSize() self.startingGrid = {} self.page.pushButton_pickImage.clicked.connect(self.pickImage) 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, 'customImg': self.page.checkBox_customImg, 'image': self.page.lineEdit_image, }, colorWidgets={ 'color': self.page.pushButton_color, }) self.page.spinBox_scale.setValue(self.scale) 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() def update(self): self.updateGridSize() 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) 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 ): if self.parent.canceled: break 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)) 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." 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) 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) rect = ( (drawPtX, drawPtY), (drawPtX + self.pxWidth, drawPtY + self.pxHeight) ) shape = self.page.comboBox_shapeType.currentText().lower() # Rectangle if shape == 'rectangle': drawer.rectangle(rect, fill=self.color) # Elliptical elif shape == 'elliptical': 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 if shape == 'circle': drawer.ellipse(outlineShape, fill=self.color) drawer.ellipse(smallerShape, fill=(0,0,0,0)) # Lilypad elif shape == 'lilypad': drawer.pieslice(smallerShape, 290, 250, fill=self.color) # Pac-Man elif shape == 'pac-man': 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 # 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) # Duck elif shape == 'duck': 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 elif shape == 'peace': 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 ) for x, y in grid: drawPtX = x * self.pxWidth if drawPtX > self.width: continue drawPtY = y * self.pxHeight if drawPtY > self.height: continue if self.customImg: drawCustomImg() else: drawShape() 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 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 [ cell for cell in nearbyCoords(x, y) if lastGrid.get(cell) is not None ] 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() pr['GRID'] = self.startingGrid return pr def loadPreset(self, pr, *args): super().loadPreset(pr, *args) self.startingGrid = pr['GRID'] 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