diff options
| author | Brianna Rainey | 2026-01-22 16:40:37 -0500 |
|---|---|---|
| committer | Brianna Rainey | 2026-01-22 16:40:37 -0500 |
| commit | 801777e5348ed5e5665d2472f14f36673c253d66 (patch) | |
| tree | 93e56e1d9ddf39d3b0374782303ec7b2f1a3ed84 | |
| parent | a12be862e22bdec6a243a3f0b5f4f28d69084a2a (diff) | |
make Life component respond to audio
also adds a dissolve effect between frames and a kaleidoscope effect
the fancier shape types ignore audio for now. Fixes #91
| -rw-r--r-- | src/avp/components/life.py | 263 | ||||
| -rw-r--r-- | src/avp/components/life.ui | 82 |
2 files changed, 267 insertions, 78 deletions
diff --git a/src/avp/components/life.py b/src/avp/components/life.py index 5b719d1..9e5e202 100644 --- a/src/avp/components/life.py +++ b/src/avp/components/life.py | |||
| @@ -1,13 +1,15 @@ | |||
| 1 | from PyQt6 import QtGui, QtCore, QtWidgets | 1 | from PyQt6 import QtCore, QtWidgets |
| 2 | from PyQt6.QtGui import QUndoCommand | 2 | from PyQt6.QtGui import QUndoCommand |
| 3 | from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter | 3 | from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter, ImageOps |
| 4 | import os | 4 | import os |
| 5 | from copy import copy | ||
| 5 | import math | 6 | import math |
| 6 | import logging | 7 | import logging |
| 7 | 8 | ||
| 8 | 9 | ||
| 9 | from ..component import Component | 10 | from ..component import Component |
| 10 | from ..toolkit.frame import BlankFrame, scale | 11 | from ..toolkit.frame import BlankFrame, scale |
| 12 | from .original import Component as Visualizer | ||
| 11 | 13 | ||
| 12 | 14 | ||
| 13 | log = logging.getLogger("AVP.Component.Life") | 15 | log = logging.getLogger("AVP.Component.Life") |
| @@ -15,7 +17,7 @@ log = logging.getLogger("AVP.Component.Life") | |||
| 15 | 17 | ||
| 16 | class Component(Component): | 18 | class Component(Component): |
| 17 | name = "Conway's Game of Life" | 19 | name = "Conway's Game of Life" |
| 18 | version = "1.0.0" | 20 | version = "2.0.0" |
| 19 | 21 | ||
| 20 | def widget(self, *args): | 22 | def widget(self, *args): |
| 21 | super().widget(*args) | 23 | super().widget(*args) |
| @@ -62,6 +64,8 @@ class Component(Component): | |||
| 62 | "customImg": self.page.checkBox_customImg, | 64 | "customImg": self.page.checkBox_customImg, |
| 63 | "showGrid": self.page.checkBox_showGrid, | 65 | "showGrid": self.page.checkBox_showGrid, |
| 64 | "image": self.page.lineEdit_image, | 66 | "image": self.page.lineEdit_image, |
| 67 | "kaleidoscope": self.page.checkBox_kaleidoscope, | ||
| 68 | "sensitivity": self.page.spinBox_sensitivity, | ||
| 65 | }, | 69 | }, |
| 66 | colorWidgets={ | 70 | colorWidgets={ |
| 67 | "color": self.page.pushButton_color, | 71 | "color": self.page.pushButton_color, |
| @@ -106,6 +110,8 @@ class Component(Component): | |||
| 106 | 110 | ||
| 107 | def update(self): | 111 | def update(self): |
| 108 | self.updateGridSize() | 112 | self.updateGridSize() |
| 113 | |||
| 114 | # Hide/show widgets depending on state of "custom image" checkbox | ||
| 109 | if self.page.checkBox_customImg.isChecked(): | 115 | if self.page.checkBox_customImg.isChecked(): |
| 110 | self.page.label_color.setVisible(False) | 116 | self.page.label_color.setVisible(False) |
| 111 | self.page.lineEdit_color.setVisible(False) | 117 | self.page.lineEdit_color.setVisible(False) |
| @@ -124,6 +130,17 @@ class Component(Component): | |||
| 124 | self.page.label_image.setVisible(False) | 130 | self.page.label_image.setVisible(False) |
| 125 | self.page.lineEdit_image.setVisible(False) | 131 | self.page.lineEdit_image.setVisible(False) |
| 126 | self.page.pushButton_pickImage.setVisible(False) | 132 | self.page.pushButton_pickImage.setVisible(False) |
| 133 | |||
| 134 | # Disable audio sensitivity spinbox if not relevant | ||
| 135 | if ( | ||
| 136 | self.page.comboBox_shapeType.currentIndex() < 4 | ||
| 137 | or self.page.checkBox_customImg.isChecked() | ||
| 138 | ): | ||
| 139 | self.page.spinBox_sensitivity.setEnabled(True) | ||
| 140 | else: | ||
| 141 | self.page.spinBox_sensitivity.setEnabled(False) | ||
| 142 | |||
| 143 | # Disable arrow buttons to shift the grid if the grid is empty | ||
| 127 | enabled = len(self.startingGrid) > 0 | 144 | enabled = len(self.startingGrid) > 0 |
| 128 | for widget in self.shiftButtons: | 145 | for widget in self.shiftButtons: |
| 129 | widget.setEnabled(enabled) | 146 | widget.setEnabled(enabled) |
| @@ -144,16 +161,48 @@ class Component(Component): | |||
| 144 | self.pxHeight = math.ceil(self.height / self.gridHeight) | 161 | self.pxHeight = math.ceil(self.height / self.gridHeight) |
| 145 | 162 | ||
| 146 | def previewRender(self): | 163 | def previewRender(self): |
| 147 | return self.drawGrid(self.startingGrid) | 164 | image = self.drawGrid(self.startingGrid, self.color) |
| 165 | image = self.addKaleidoscopeEffect(image) | ||
| 166 | image = self.addShadow(image) | ||
| 167 | image = self.addGridLines(image) | ||
| 168 | return image | ||
| 148 | 169 | ||
| 149 | def preFrameRender(self, *args, **kwargs): | 170 | def preFrameRender(self, *args, **kwargs): |
| 150 | super().preFrameRender(*args, **kwargs) | 171 | super().preFrameRender(*args, **kwargs) |
| 151 | self.tickGrids = {0: self.startingGrid} | 172 | self.tickGrids = {0: self.startingGrid} |
| 152 | 173 | ||
| 174 | smoothConstantDown = 0.08 + 0 | ||
| 175 | smoothConstantUp = 0.8 - 0 | ||
| 176 | self.lastSpectrum = None | ||
| 177 | self.spectrumArray = {} | ||
| 178 | if self.sensitivity == 0: | ||
| 179 | return | ||
| 180 | |||
| 181 | for i in range(0, len(self.completeAudioArray), self.sampleSize): | ||
| 182 | if self.canceled: | ||
| 183 | break | ||
| 184 | self.lastSpectrum = Visualizer.transformData( | ||
| 185 | i, | ||
| 186 | self.completeAudioArray, | ||
| 187 | self.sampleSize, | ||
| 188 | smoothConstantDown, | ||
| 189 | smoothConstantUp, | ||
| 190 | self.lastSpectrum, | ||
| 191 | self.sensitivity, | ||
| 192 | ) | ||
| 193 | self.spectrumArray[i] = copy(self.lastSpectrum) | ||
| 194 | |||
| 195 | progress = int(100 * (i / len(self.completeAudioArray))) | ||
| 196 | if progress >= 100: | ||
| 197 | progress = 100 | ||
| 198 | pStr = "Analyzing audio: " + str(progress) + "%" | ||
| 199 | self.progressBarSetText.emit(pStr) | ||
| 200 | self.progressBarUpdate.emit(int(progress)) | ||
| 201 | |||
| 153 | def properties(self): | 202 | def properties(self): |
| 154 | if self.customImg and (not self.image or not os.path.exists(self.image)): | 203 | if self.customImg and (not self.image or not os.path.exists(self.image)): |
| 155 | return ["error"] | 204 | return ["error"] |
| 156 | return [] | 205 | return ["pcm"] if self.sensitivity > 0 else [] |
| 157 | 206 | ||
| 158 | def error(self): | 207 | def error(self): |
| 159 | return "No image selected to represent life." | 208 | return "No image selected to represent life." |
| @@ -169,65 +218,181 @@ class Component(Component): | |||
| 169 | # Delete old evolution data which we shouldn't need anymore | 218 | # Delete old evolution data which we shouldn't need anymore |
| 170 | if tick - 60 in self.tickGrids: | 219 | if tick - 60 in self.tickGrids: |
| 171 | del self.tickGrids[tick - 60] | 220 | del self.tickGrids[tick - 60] |
| 172 | return self.drawGrid(grid) | ||
| 173 | 221 | ||
| 174 | def drawGrid(self, grid): | 222 | # Fade difference between previous and current grid |
| 223 | previousGrid = self.tickGrids.get(tick - 1, set()) | ||
| 224 | newColor = self.color | ||
| 225 | if not self.customImg: | ||
| 226 | r, g, b = self.color | ||
| 227 | decay = 255 / self.tickRate | ||
| 228 | decayAmount = int(decay * (frameNo % self.tickRate)) | ||
| 229 | decayColor = ( | ||
| 230 | r, | ||
| 231 | g, | ||
| 232 | b, | ||
| 233 | 255 - decayAmount, | ||
| 234 | ) | ||
| 235 | newColor = (r, g, b, min(255, decayAmount * 2)) | ||
| 236 | previousGridImage = self.drawGrid( | ||
| 237 | previousGrid, | ||
| 238 | decayColor, | ||
| 239 | ( | ||
| 240 | None | ||
| 241 | if (not self.customImg and self.shapeType > 3) | ||
| 242 | or self.sensitivity < 1 | ||
| 243 | else self.spectrumArray[frameNo * self.sampleSize] | ||
| 244 | ), | ||
| 245 | ) | ||
| 246 | image = self.drawGrid( | ||
| 247 | grid, | ||
| 248 | newColor, | ||
| 249 | ( | ||
| 250 | None | ||
| 251 | if (not self.customImg and self.shapeType > 3) or self.sensitivity < 1 | ||
| 252 | else self.spectrumArray[frameNo * self.sampleSize] | ||
| 253 | ), | ||
| 254 | grid.intersection(previousGrid), | ||
| 255 | ) | ||
| 256 | if not self.customImg: | ||
| 257 | image = Image.alpha_composite(previousGridImage, image) | ||
| 258 | image = self.addKaleidoscopeEffect(image) | ||
| 259 | image = self.addShadow(image) | ||
| 260 | image = self.addGridLines(image) | ||
| 261 | return image | ||
| 262 | |||
| 263 | def addShadow(self, frame): | ||
| 264 | if not self.shadow: | ||
| 265 | return frame | ||
| 266 | |||
| 267 | shadImg = ImageEnhance.Contrast(frame).enhance(0.0) | ||
| 268 | shadImg = shadImg.filter(ImageFilter.GaussianBlur(5.00)) | ||
| 269 | shadImg = ImageChops.offset(shadImg, -2, 2) | ||
| 270 | shadImg.paste(frame, box=(0, 0), mask=frame) | ||
| 271 | frame = shadImg | ||
| 272 | return frame | ||
| 273 | |||
| 274 | def addGridLines(self, frame): | ||
| 275 | if not self.showGrid: | ||
| 276 | return frame | ||
| 277 | |||
| 278 | drawer = ImageDraw.Draw(frame) | ||
| 279 | w, h = scale(0.05, self.width, self.height, int) | ||
| 280 | for x in range(self.pxWidth, self.width, self.pxWidth): | ||
| 281 | drawer.rectangle( | ||
| 282 | ((x, 0), (x + w, self.height)), | ||
| 283 | fill=self.color, | ||
| 284 | ) | ||
| 285 | for y in range(self.pxHeight, self.height, self.pxHeight): | ||
| 286 | drawer.rectangle( | ||
| 287 | ((0, y), (self.width, y + h)), | ||
| 288 | fill=self.color, | ||
| 289 | ) | ||
| 290 | return frame | ||
| 291 | |||
| 292 | def addKaleidoscopeEffect(self, frame): | ||
| 293 | if not self.kaleidoscope: | ||
| 294 | return frame | ||
| 295 | |||
| 296 | flippedImage = frame.transpose(Image.Transpose.FLIP_TOP_BOTTOM) | ||
| 297 | frame.paste(flippedImage, (0, 0), mask=flippedImage) | ||
| 298 | |||
| 299 | flippedImage = frame.transpose(Image.Transpose.FLIP_LEFT_RIGHT) | ||
| 300 | frame.paste(flippedImage, (0, 0), mask=flippedImage) | ||
| 301 | |||
| 302 | flippedImage = frame.transpose(Image.Transpose.ROTATE_90) | ||
| 303 | frame.paste(flippedImage, (0, 0), mask=flippedImage) | ||
| 304 | |||
| 305 | flippedImage = frame.transpose(Image.Transpose.ROTATE_270) | ||
| 306 | frame.paste(flippedImage, (0, 0), mask=flippedImage) | ||
| 307 | return frame | ||
| 308 | |||
| 309 | def drawGrid(self, grid, color, spectrumData=None, didntChange=None): | ||
| 175 | frame = BlankFrame(self.width, self.height) | 310 | frame = BlankFrame(self.width, self.height) |
| 311 | if didntChange is None: | ||
| 312 | # this set would contain cell coords that did not change | ||
| 313 | # between the previous grid tick and this one | ||
| 314 | didntChange = set() | ||
| 176 | 315 | ||
| 177 | def drawCustomImg(): | 316 | def drawCustomImg(): |
| 178 | try: | 317 | try: |
| 179 | img = Image.open(self.image) | 318 | img = Image.open(self.image) |
| 180 | except Exception: | 319 | except Exception: |
| 181 | return | 320 | return |
| 182 | img = img.resize((self.pxWidth, self.pxHeight), Image.Resampling.LANCZOS) | 321 | img = img.resize( |
| 183 | frame.paste(img, box=(drawPtX, drawPtY)) | 322 | ( |
| 323 | (self.pxWidth + audioMorphWidth), | ||
| 324 | (self.pxHeight + audioMorphHeight), | ||
| 325 | ), | ||
| 326 | Image.Resampling.LANCZOS, | ||
| 327 | ) | ||
| 328 | frame.paste( | ||
| 329 | img, | ||
| 330 | box=( | ||
| 331 | (drawPtX - (audioMorphWidth * 2)), | ||
| 332 | (drawPtY - (audioMorphHeight * 2)), | ||
| 333 | ), | ||
| 334 | ) | ||
| 184 | 335 | ||
| 185 | def drawShape(): | 336 | def drawShape(x, y): |
| 186 | drawer = ImageDraw.Draw(frame) | 337 | drawer = ImageDraw.Draw(frame) |
| 187 | rect = ( | 338 | rect = ( |
| 188 | (drawPtX, drawPtY), | 339 | (drawPtX - audioMorphWidth, drawPtY - audioMorphHeight), |
| 189 | (drawPtX + self.pxWidth, drawPtY + self.pxHeight), | 340 | ( |
| 341 | drawPtX + self.pxWidth + audioMorphWidth, | ||
| 342 | drawPtY + self.pxHeight + audioMorphHeight, | ||
| 343 | ), | ||
| 190 | ) | 344 | ) |
| 191 | shape = self.page.comboBox_shapeType.currentText().lower() | 345 | shape = self.page.comboBox_shapeType.currentText().lower() |
| 346 | thisCellColor = color if (x, y) not in didntChange else (*color[:3], 255) | ||
| 192 | 347 | ||
| 193 | # Rectangle | 348 | # Rectangle |
| 194 | if shape == "rectangle": | 349 | if shape == "rectangle": |
| 195 | drawer.rectangle(rect, fill=self.color) | 350 | drawer.rectangle(rect, fill=thisCellColor) |
| 196 | 351 | ||
| 197 | # Elliptical | 352 | # Elliptical |
| 198 | elif shape == "elliptical": | 353 | elif shape == "elliptical": |
| 199 | drawer.ellipse(rect, fill=self.color) | 354 | drawer.ellipse(rect, fill=thisCellColor) |
| 200 | 355 | ||
| 201 | tenthX, tenthY = scale(10, self.pxWidth, self.pxHeight, int) | 356 | tenthX, tenthY = scale(10, self.pxWidth, self.pxHeight, int) |
| 202 | smallerShape = ( | 357 | smallerShape = ( |
| 203 | ( | 358 | ( |
| 204 | drawPtX + tenthX + int(tenthX / 4), | 359 | drawPtX + tenthX + int(tenthX / 4) - int(audioMorphWidth / 2), |
| 205 | drawPtY + tenthY + int(tenthY / 2), | 360 | drawPtY + tenthY + int(tenthY / 2) - int(audioMorphHeight / 2), |
| 206 | ), | 361 | ), |
| 207 | ( | 362 | ( |
| 208 | drawPtX + self.pxWidth - tenthX - int(tenthX / 4), | 363 | drawPtX |
| 209 | drawPtY + self.pxHeight - (tenthY + int(tenthY / 2)), | 364 | + self.pxWidth |
| 365 | - tenthX | ||
| 366 | - int(tenthX / 4) | ||
| 367 | + int(audioMorphWidth / 2), | ||
| 368 | drawPtY | ||
| 369 | + self.pxHeight | ||
| 370 | - (tenthY + int(tenthY / 2)) | ||
| 371 | + int(audioMorphHeight / 2), | ||
| 210 | ), | 372 | ), |
| 211 | ) | 373 | ) |
| 212 | outlineShape = ( | 374 | outlineShape = ( |
| 213 | (drawPtX + int(tenthX / 4), drawPtY + int(tenthY / 2)), | ||
| 214 | ( | 375 | ( |
| 215 | drawPtX + self.pxWidth - int(tenthX / 4), | 376 | drawPtX + int(tenthX / 4) - audioMorphWidth, |
| 216 | drawPtY + self.pxHeight - int(tenthY / 2), | 377 | drawPtY + int(tenthY / 2) - audioMorphHeight, |
| 378 | ), | ||
| 379 | ( | ||
| 380 | drawPtX + self.pxWidth - int(tenthX / 4) + audioMorphWidth, | ||
| 381 | drawPtY + self.pxHeight - int(tenthY / 2) + audioMorphHeight, | ||
| 217 | ), | 382 | ), |
| 218 | ) | 383 | ) |
| 219 | # Circle | 384 | # Circle |
| 220 | if shape == "circle": | 385 | if shape == "circle": |
| 221 | drawer.ellipse(outlineShape, fill=self.color) | 386 | drawer.ellipse(outlineShape, fill=thisCellColor) |
| 222 | drawer.ellipse(smallerShape, fill=(0, 0, 0, 0)) | 387 | drawer.ellipse(smallerShape, fill=(0, 0, 0, 0)) |
| 223 | 388 | ||
| 224 | # Lilypad | 389 | # Lilypad |
| 225 | elif shape == "lilypad": | 390 | elif shape == "lilypad": |
| 226 | drawer.pieslice(smallerShape, 290, 250, fill=self.color) | 391 | drawer.pieslice(smallerShape, 290, 250, fill=thisCellColor) |
| 227 | 392 | ||
| 228 | # Pie | 393 | # Pie |
| 229 | elif shape == "pie": | 394 | elif shape == "pie": |
| 230 | drawer.pieslice(outlineShape, 35, 320, fill=self.color) | 395 | drawer.pieslice(outlineShape, 35, 320, fill=thisCellColor) |
| 231 | 396 | ||
| 232 | hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline | 397 | hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline |
| 233 | tX, tY = scale(33, self.pxWidth, self.pxHeight, int) # thirdline | 398 | tX, tY = scale(33, self.pxWidth, self.pxHeight, int) # thirdline |
| @@ -235,7 +400,7 @@ class Component(Component): | |||
| 235 | 400 | ||
| 236 | # Path | 401 | # Path |
| 237 | if shape == "path": | 402 | if shape == "path": |
| 238 | drawer.ellipse(rect, fill=self.color) | 403 | drawer.ellipse(rect, fill=thisCellColor) |
| 239 | rects = { | 404 | rects = { |
| 240 | direction: False | 405 | direction: False |
| 241 | for direction in ( | 406 | for direction in ( |
| @@ -287,7 +452,7 @@ class Component(Component): | |||
| 287 | drawPtY + self.pxHeight, | 452 | drawPtY + self.pxHeight, |
| 288 | ), | 453 | ), |
| 289 | ) | 454 | ) |
| 290 | drawer.rectangle(sect, fill=self.color) | 455 | drawer.rectangle(sect, fill=thisCellColor) |
| 291 | 456 | ||
| 292 | # Duck | 457 | # Duck |
| 293 | elif shape == "duck": | 458 | elif shape == "duck": |
| @@ -304,10 +469,10 @@ class Component(Component): | |||
| 304 | (drawPtX + int(qX / 4), drawPtY + int(qY * 3)), | 469 | (drawPtX + int(qX / 4), drawPtY + int(qY * 3)), |
| 305 | (drawPtX + int(tX * 2), drawPtY + self.pxHeight), | 470 | (drawPtX + int(tX * 2), drawPtY + self.pxHeight), |
| 306 | ) | 471 | ) |
| 307 | drawer.ellipse(duckBody, fill=self.color) | 472 | drawer.ellipse(duckBody, fill=thisCellColor) |
| 308 | drawer.ellipse(duckHead, fill=self.color) | 473 | drawer.ellipse(duckHead, fill=thisCellColor) |
| 309 | drawer.pieslice(duckWing, 130, 200, fill=self.color) | 474 | drawer.pieslice(duckWing, 130, 200, fill=thisCellColor) |
| 310 | drawer.pieslice(duckBeak, 145, 200, fill=self.color) | 475 | drawer.pieslice(duckBeak, 145, 200, fill=thisCellColor) |
| 311 | 476 | ||
| 312 | # Peace | 477 | # Peace |
| 313 | elif shape == "peace": | 478 | elif shape == "peace": |
| @@ -321,9 +486,9 @@ class Component(Component): | |||
| 321 | drawPtY + self.pxHeight - int(tenthY / 2), | 486 | drawPtY + self.pxHeight - int(tenthY / 2), |
| 322 | ), | 487 | ), |
| 323 | ) | 488 | ) |
| 324 | drawer.ellipse(outlineShape, fill=self.color) | 489 | drawer.ellipse(outlineShape, fill=thisCellColor) |
| 325 | drawer.ellipse(smallerShape, fill=(0, 0, 0, 0)) | 490 | drawer.ellipse(smallerShape, fill=(0, 0, 0, 0)) |
| 326 | drawer.rectangle(line, fill=self.color) | 491 | drawer.rectangle(line, fill=thisCellColor) |
| 327 | 492 | ||
| 328 | def slantLine(difference): | 493 | def slantLine(difference): |
| 329 | return ( | 494 | return ( |
| @@ -334,8 +499,10 @@ class Component(Component): | |||
| 334 | (drawPtY + hY), | 499 | (drawPtY + hY), |
| 335 | ) | 500 | ) |
| 336 | 501 | ||
| 337 | drawer.line(slantLine(qX), fill=self.color, width=tenthX) | 502 | drawer.line(slantLine(qX), fill=thisCellColor, width=tenthX) |
| 338 | drawer.line(slantLine(self.pxWidth - qX), fill=self.color, width=tenthX) | 503 | drawer.line( |
| 504 | slantLine(self.pxWidth - qX), fill=thisCellColor, width=tenthX | ||
| 505 | ) | ||
| 339 | 506 | ||
| 340 | for x, y in grid: | 507 | for x, y in grid: |
| 341 | drawPtX = x * self.pxWidth | 508 | drawPtX = x * self.pxWidth |
| @@ -345,30 +512,16 @@ class Component(Component): | |||
| 345 | if drawPtY > self.height: | 512 | if drawPtY > self.height: |
| 346 | continue | 513 | continue |
| 347 | 514 | ||
| 515 | audioMorphWidth = ( | ||
| 516 | 0 if spectrumData is None else int(spectrumData[(drawPtX % 63) * 4] / 4) | ||
| 517 | ) | ||
| 518 | audioMorphHeight = ( | ||
| 519 | 0 if spectrumData is None else int(spectrumData[(drawPtY % 63) * 4] / 4) | ||
| 520 | ) | ||
| 348 | if self.customImg: | 521 | if self.customImg: |
| 349 | drawCustomImg() | 522 | drawCustomImg() |
| 350 | else: | 523 | else: |
| 351 | drawShape() | 524 | drawShape(x, y) |
| 352 | |||
| 353 | if self.shadow: | ||
| 354 | shadImg = ImageEnhance.Contrast(frame).enhance(0.0) | ||
| 355 | shadImg = shadImg.filter(ImageFilter.GaussianBlur(5.00)) | ||
| 356 | shadImg = ImageChops.offset(shadImg, -2, 2) | ||
| 357 | shadImg.paste(frame, box=(0, 0), mask=frame) | ||
| 358 | frame = shadImg | ||
| 359 | if self.showGrid: | ||
| 360 | drawer = ImageDraw.Draw(frame) | ||
| 361 | w, h = scale(0.05, self.width, self.height, int) | ||
| 362 | for x in range(self.pxWidth, self.width, self.pxWidth): | ||
| 363 | drawer.rectangle( | ||
| 364 | ((x, 0), (x + w, self.height)), | ||
| 365 | fill=self.color, | ||
| 366 | ) | ||
| 367 | for y in range(self.pxHeight, self.height, self.pxHeight): | ||
| 368 | drawer.rectangle( | ||
| 369 | ((0, y), (self.width, y + h)), | ||
| 370 | fill=self.color, | ||
| 371 | ) | ||
| 372 | 525 | ||
| 373 | return frame | 526 | return frame |
| 374 | 527 | ||
diff --git a/src/avp/components/life.ui b/src/avp/components/life.ui index 30cf9d0..a0c8999 100644 --- a/src/avp/components/life.ui +++ b/src/avp/components/life.ui | |||
| @@ -7,7 +7,7 @@ | |||
| 7 | <x>0</x> | 7 | <x>0</x> |
| 8 | <y>0</y> | 8 | <y>0</y> |
| 9 | <width>586</width> | 9 | <width>586</width> |
| 10 | <height>197</height> | 10 | <height>206</height> |
| 11 | </rect> | 11 | </rect> |
| 12 | </property> | 12 | </property> |
| 13 | <property name="windowTitle"> | 13 | <property name="windowTitle"> |
| @@ -29,24 +29,27 @@ | |||
| 29 | </item> | 29 | </item> |
| 30 | <item> | 30 | <item> |
| 31 | <widget class="QSpinBox" name="spinBox_tickRate"> | 31 | <widget class="QSpinBox" name="spinBox_tickRate"> |
| 32 | <property name="toolTip"> | ||
| 33 | <string>increase number for slower animation</string> | ||
| 34 | </property> | ||
| 32 | <property name="suffix"> | 35 | <property name="suffix"> |
| 33 | <string> frames per tick</string> | 36 | <string> frames per tick</string> |
| 34 | </property> | 37 | </property> |
| 35 | <property name="minimum"> | 38 | <property name="minimum"> |
| 36 | <number>1</number> | 39 | <number>10</number> |
| 37 | </property> | 40 | </property> |
| 38 | <property name="maximum"> | 41 | <property name="maximum"> |
| 39 | <number>30</number> | 42 | <number>240</number> |
| 40 | </property> | 43 | </property> |
| 41 | <property name="value"> | 44 | <property name="value"> |
| 42 | <number>5</number> | 45 | <number>60</number> |
| 43 | </property> | 46 | </property> |
| 44 | </widget> | 47 | </widget> |
| 45 | </item> | 48 | </item> |
| 46 | <item> | 49 | <item> |
| 47 | <spacer name="horizontalSpacer"> | 50 | <spacer name="horizontalSpacer"> |
| 48 | <property name="orientation"> | 51 | <property name="orientation"> |
| 49 | <enum>Qt::Horizontal</enum> | 52 | <enum>Qt::Orientation::Horizontal</enum> |
| 50 | </property> | 53 | </property> |
| 51 | <property name="sizeHint" stdset="0"> | 54 | <property name="sizeHint" stdset="0"> |
| 52 | <size> | 55 | <size> |
| @@ -103,7 +106,7 @@ | |||
| 103 | <item> | 106 | <item> |
| 104 | <spacer name="horizontalSpacer_5"> | 107 | <spacer name="horizontalSpacer_5"> |
| 105 | <property name="orientation"> | 108 | <property name="orientation"> |
| 106 | <enum>Qt::Horizontal</enum> | 109 | <enum>Qt::Orientation::Horizontal</enum> |
| 107 | </property> | 110 | </property> |
| 108 | <property name="sizeHint" stdset="0"> | 111 | <property name="sizeHint" stdset="0"> |
| 109 | <size> | 112 | <size> |
| @@ -194,7 +197,7 @@ | |||
| 194 | <item> | 197 | <item> |
| 195 | <spacer name="horizontalSpacer_2"> | 198 | <spacer name="horizontalSpacer_2"> |
| 196 | <property name="orientation"> | 199 | <property name="orientation"> |
| 197 | <enum>Qt::Horizontal</enum> | 200 | <enum>Qt::Orientation::Horizontal</enum> |
| 198 | </property> | 201 | </property> |
| 199 | <property name="sizeHint" stdset="0"> | 202 | <property name="sizeHint" stdset="0"> |
| 200 | <size> | 203 | <size> |
| @@ -258,7 +261,7 @@ | |||
| 258 | <item> | 261 | <item> |
| 259 | <spacer name="horizontalSpacer_8"> | 262 | <spacer name="horizontalSpacer_8"> |
| 260 | <property name="orientation"> | 263 | <property name="orientation"> |
| 261 | <enum>Qt::Horizontal</enum> | 264 | <enum>Qt::Orientation::Horizontal</enum> |
| 262 | </property> | 265 | </property> |
| 263 | <property name="sizeHint" stdset="0"> | 266 | <property name="sizeHint" stdset="0"> |
| 264 | <size> | 267 | <size> |
| @@ -273,10 +276,23 @@ | |||
| 273 | <item> | 276 | <item> |
| 274 | <layout class="QHBoxLayout" name="horizontalLayout_6"> | 277 | <layout class="QHBoxLayout" name="horizontalLayout_6"> |
| 275 | <item> | 278 | <item> |
| 279 | <widget class="QCheckBox" name="checkBox_kaleidoscope"> | ||
| 280 | <property name="text"> | ||
| 281 | <string>Kaleidoscope</string> | ||
| 282 | </property> | ||
| 283 | <property name="checked"> | ||
| 284 | <bool>true</bool> | ||
| 285 | </property> | ||
| 286 | </widget> | ||
| 287 | </item> | ||
| 288 | <item> | ||
| 276 | <widget class="QCheckBox" name="checkBox_shadow"> | 289 | <widget class="QCheckBox" name="checkBox_shadow"> |
| 277 | <property name="text"> | 290 | <property name="text"> |
| 278 | <string>Shadow</string> | 291 | <string>Shadow</string> |
| 279 | </property> | 292 | </property> |
| 293 | <property name="checked"> | ||
| 294 | <bool>true</bool> | ||
| 295 | </property> | ||
| 280 | </widget> | 296 | </widget> |
| 281 | </item> | 297 | </item> |
| 282 | <item> | 298 | <item> |
| @@ -289,7 +305,7 @@ | |||
| 289 | <item> | 305 | <item> |
| 290 | <spacer name="horizontalSpacer_6"> | 306 | <spacer name="horizontalSpacer_6"> |
| 291 | <property name="orientation"> | 307 | <property name="orientation"> |
| 292 | <enum>Qt::Horizontal</enum> | 308 | <enum>Qt::Orientation::Horizontal</enum> |
| 293 | </property> | 309 | </property> |
| 294 | <property name="sizeHint" stdset="0"> | 310 | <property name="sizeHint" stdset="0"> |
| 295 | <size> | 311 | <size> |
| @@ -309,7 +325,7 @@ | |||
| 309 | <string>Up</string> | 325 | <string>Up</string> |
| 310 | </property> | 326 | </property> |
| 311 | <property name="arrowType"> | 327 | <property name="arrowType"> |
| 312 | <enum>Qt::UpArrow</enum> | 328 | <enum>Qt::ArrowType::UpArrow</enum> |
| 313 | </property> | 329 | </property> |
| 314 | </widget> | 330 | </widget> |
| 315 | </item> | 331 | </item> |
| @@ -319,7 +335,7 @@ | |||
| 319 | <string>Down</string> | 335 | <string>Down</string> |
| 320 | </property> | 336 | </property> |
| 321 | <property name="arrowType"> | 337 | <property name="arrowType"> |
| 322 | <enum>Qt::DownArrow</enum> | 338 | <enum>Qt::ArrowType::DownArrow</enum> |
| 323 | </property> | 339 | </property> |
| 324 | </widget> | 340 | </widget> |
| 325 | </item> | 341 | </item> |
| @@ -329,7 +345,7 @@ | |||
| 329 | <string>Left</string> | 345 | <string>Left</string> |
| 330 | </property> | 346 | </property> |
| 331 | <property name="arrowType"> | 347 | <property name="arrowType"> |
| 332 | <enum>Qt::LeftArrow</enum> | 348 | <enum>Qt::ArrowType::LeftArrow</enum> |
| 333 | </property> | 349 | </property> |
| 334 | </widget> | 350 | </widget> |
| 335 | </item> | 351 | </item> |
| @@ -339,14 +355,14 @@ | |||
| 339 | <string>Right</string> | 355 | <string>Right</string> |
| 340 | </property> | 356 | </property> |
| 341 | <property name="arrowType"> | 357 | <property name="arrowType"> |
| 342 | <enum>Qt::RightArrow</enum> | 358 | <enum>Qt::ArrowType::RightArrow</enum> |
| 343 | </property> | 359 | </property> |
| 344 | </widget> | 360 | </widget> |
| 345 | </item> | 361 | </item> |
| 346 | <item> | 362 | <item> |
| 347 | <spacer name="horizontalSpacer_9"> | 363 | <spacer name="horizontalSpacer_9"> |
| 348 | <property name="orientation"> | 364 | <property name="orientation"> |
| 349 | <enum>Qt::Horizontal</enum> | 365 | <enum>Qt::Orientation::Horizontal</enum> |
| 350 | </property> | 366 | </property> |
| 351 | <property name="sizeHint" stdset="0"> | 367 | <property name="sizeHint" stdset="0"> |
| 352 | <size> | 368 | <size> |
| @@ -356,6 +372,23 @@ | |||
| 356 | </property> | 372 | </property> |
| 357 | </spacer> | 373 | </spacer> |
| 358 | </item> | 374 | </item> |
| 375 | <item> | ||
| 376 | <widget class="QLabel" name="label_sensitivity"> | ||
| 377 | <property name="text"> | ||
| 378 | <string>Audio Sensitivity</string> | ||
| 379 | </property> | ||
| 380 | </widget> | ||
| 381 | </item> | ||
| 382 | <item> | ||
| 383 | <widget class="QSpinBox" name="spinBox_sensitivity"> | ||
| 384 | <property name="maximum"> | ||
| 385 | <number>40</number> | ||
| 386 | </property> | ||
| 387 | <property name="value"> | ||
| 388 | <number>20</number> | ||
| 389 | </property> | ||
| 390 | </widget> | ||
| 391 | </item> | ||
| 359 | </layout> | 392 | </layout> |
| 360 | </item> | 393 | </item> |
| 361 | </layout> | 394 | </layout> |
| @@ -364,19 +397,22 @@ | |||
| 364 | <widget class="QTextBrowser" name="textBrowser"> | 397 | <widget class="QTextBrowser" name="textBrowser"> |
| 365 | <property name="html"> | 398 | <property name="html"> |
| 366 | <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> | 399 | <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> |
| 367 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> | 400 | <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> |
| 368 | p, li { white-space: pre-wrap; } | 401 | p, li { white-space: pre-wrap; } |
| 369 | </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> | 402 | hr { height: 1px; border-width: 0; } |
| 370 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Click the preview window to place a cell. Right-click to remove.</span></p> | 403 | li.unchecked::marker { content: "\2610"; } |
| 371 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- A cell with less than 2 neighbours will die from underpopulation</p> | 404 | li.checked::marker { content: "\2612"; } |
| 372 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- A cell with more than 3 neighbours will die from overpopulation.</p> | 405 | </style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> |
| 373 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">- An empty space surrounded by 3 live cells will cause reproduction.</p></body></html></string> | 406 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt; font-weight:600;">Click the preview window to place a cell. Right-click to remove.</span></p> |
| 407 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;">- A cell with less than 2 neighbours will die from underpopulation</span></p> | ||
| 408 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;">- A cell with more than 3 neighbours will die from overpopulation.</span></p> | ||
| 409 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:11pt;">- An empty space surrounded by 3 live cells will cause reproduction.</span></p></body></html></string> | ||
| 374 | </property> | 410 | </property> |
| 375 | <property name="tabStopDistance"> | 411 | <property name="tabStopDistance"> |
| 376 | <number>80</number> | 412 | <double>80.000000000000000</double> |
| 377 | </property> | 413 | </property> |
| 378 | <property name="textInteractionFlags"> | 414 | <property name="textInteractionFlags"> |
| 379 | <set>Qt::NoTextInteraction</set> | 415 | <set>Qt::TextInteractionFlag::NoTextInteraction</set> |
| 380 | </property> | 416 | </property> |
| 381 | <property name="openLinks"> | 417 | <property name="openLinks"> |
| 382 | <bool>false</bool> | 418 | <bool>false</bool> |
| @@ -388,7 +424,7 @@ p, li { white-space: pre-wrap; } | |||
| 388 | <item> | 424 | <item> |
| 389 | <spacer name="verticalSpacer"> | 425 | <spacer name="verticalSpacer"> |
| 390 | <property name="orientation"> | 426 | <property name="orientation"> |
| 391 | <enum>Qt::Vertical</enum> | 427 | <enum>Qt::Orientation::Vertical</enum> |
| 392 | </property> | 428 | </property> |
| 393 | <property name="sizeHint" stdset="0"> | 429 | <property name="sizeHint" stdset="0"> |
| 394 | <size> | 430 | <size> |
