aboutsummaryrefslogtreecommitdiff
path: root/src/avp/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/avp/components')
-rw-r--r--src/avp/components/life.py263
-rw-r--r--src/avp/components/life.ui82
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 @@
-from PyQt6 import QtGui, QtCore, QtWidgets
+from PyQt6 import QtCore, QtWidgets
from PyQt6.QtGui import QUndoCommand
-from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter
+from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter, ImageOps
import os
+from copy import copy
import math
import logging
from ..component import Component
from ..toolkit.frame import BlankFrame, scale
+from .original import Component as Visualizer
log = logging.getLogger("AVP.Component.Life")
@@ -15,7 +17,7 @@ log = logging.getLogger("AVP.Component.Life")
class Component(Component):
name = "Conway's Game of Life"
- version = "1.0.0"
+ version = "2.0.0"
def widget(self, *args):
super().widget(*args)
@@ -62,6 +64,8 @@ class Component(Component):
"customImg": self.page.checkBox_customImg,
"showGrid": self.page.checkBox_showGrid,
"image": self.page.lineEdit_image,
+ "kaleidoscope": self.page.checkBox_kaleidoscope,
+ "sensitivity": self.page.spinBox_sensitivity,
},
colorWidgets={
"color": self.page.pushButton_color,
@@ -106,6 +110,8 @@ class Component(Component):
def update(self):
self.updateGridSize()
+
+ # Hide/show widgets depending on state of "custom image" checkbox
if self.page.checkBox_customImg.isChecked():
self.page.label_color.setVisible(False)
self.page.lineEdit_color.setVisible(False)
@@ -124,6 +130,17 @@ class Component(Component):
self.page.label_image.setVisible(False)
self.page.lineEdit_image.setVisible(False)
self.page.pushButton_pickImage.setVisible(False)
+
+ # Disable audio sensitivity spinbox if not relevant
+ if (
+ self.page.comboBox_shapeType.currentIndex() < 4
+ or self.page.checkBox_customImg.isChecked()
+ ):
+ self.page.spinBox_sensitivity.setEnabled(True)
+ else:
+ self.page.spinBox_sensitivity.setEnabled(False)
+
+ # Disable arrow buttons to shift the grid if the grid is empty
enabled = len(self.startingGrid) > 0
for widget in self.shiftButtons:
widget.setEnabled(enabled)
@@ -144,16 +161,48 @@ class Component(Component):
self.pxHeight = math.ceil(self.height / self.gridHeight)
def previewRender(self):
- return self.drawGrid(self.startingGrid)
+ image = self.drawGrid(self.startingGrid, self.color)
+ image = self.addKaleidoscopeEffect(image)
+ image = self.addShadow(image)
+ image = self.addGridLines(image)
+ return image
def preFrameRender(self, *args, **kwargs):
super().preFrameRender(*args, **kwargs)
self.tickGrids = {0: self.startingGrid}
+ smoothConstantDown = 0.08 + 0
+ smoothConstantUp = 0.8 - 0
+ self.lastSpectrum = None
+ self.spectrumArray = {}
+ if self.sensitivity == 0:
+ return
+
+ for i in range(0, len(self.completeAudioArray), self.sampleSize):
+ if self.canceled:
+ break
+ self.lastSpectrum = Visualizer.transformData(
+ i,
+ self.completeAudioArray,
+ self.sampleSize,
+ smoothConstantDown,
+ smoothConstantUp,
+ self.lastSpectrum,
+ self.sensitivity,
+ )
+ self.spectrumArray[i] = copy(self.lastSpectrum)
+
+ progress = int(100 * (i / len(self.completeAudioArray)))
+ if progress >= 100:
+ progress = 100
+ pStr = "Analyzing audio: " + 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 []
+ return ["pcm"] if self.sensitivity > 0 else []
def error(self):
return "No image selected to represent life."
@@ -169,65 +218,181 @@ class Component(Component):
# Delete old evolution data which we shouldn't need anymore
if tick - 60 in self.tickGrids:
del self.tickGrids[tick - 60]
- return self.drawGrid(grid)
- def drawGrid(self, grid):
+ # Fade difference between previous and current grid
+ previousGrid = self.tickGrids.get(tick - 1, set())
+ newColor = self.color
+ if not self.customImg:
+ r, g, b = self.color
+ decay = 255 / self.tickRate
+ decayAmount = int(decay * (frameNo % self.tickRate))
+ decayColor = (
+ r,
+ g,
+ b,
+ 255 - decayAmount,
+ )
+ newColor = (r, g, b, min(255, decayAmount * 2))
+ previousGridImage = self.drawGrid(
+ previousGrid,
+ decayColor,
+ (
+ None
+ if (not self.customImg and self.shapeType > 3)
+ or self.sensitivity < 1
+ else self.spectrumArray[frameNo * self.sampleSize]
+ ),
+ )
+ image = self.drawGrid(
+ grid,
+ newColor,
+ (
+ None
+ if (not self.customImg and self.shapeType > 3) or self.sensitivity < 1
+ else self.spectrumArray[frameNo * self.sampleSize]
+ ),
+ grid.intersection(previousGrid),
+ )
+ if not self.customImg:
+ image = Image.alpha_composite(previousGridImage, image)
+ image = self.addKaleidoscopeEffect(image)
+ image = self.addShadow(image)
+ image = self.addGridLines(image)
+ return image
+
+ def addShadow(self, frame):
+ if not self.shadow:
+ return frame
+
+ 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 addGridLines(self, frame):
+ if not self.showGrid:
+ return frame
+
+ 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,
+ )
+ return frame
+
+ def addKaleidoscopeEffect(self, frame):
+ if not self.kaleidoscope:
+ return frame
+
+ flippedImage = frame.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
+ frame.paste(flippedImage, (0, 0), mask=flippedImage)
+
+ flippedImage = frame.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
+ frame.paste(flippedImage, (0, 0), mask=flippedImage)
+
+ flippedImage = frame.transpose(Image.Transpose.ROTATE_90)
+ frame.paste(flippedImage, (0, 0), mask=flippedImage)
+
+ flippedImage = frame.transpose(Image.Transpose.ROTATE_270)
+ frame.paste(flippedImage, (0, 0), mask=flippedImage)
+ return frame
+
+ def drawGrid(self, grid, color, spectrumData=None, didntChange=None):
frame = BlankFrame(self.width, self.height)
+ if didntChange is None:
+ # this set would contain cell coords that did not change
+ # between the previous grid tick and this one
+ didntChange = set()
def drawCustomImg():
try:
img = Image.open(self.image)
except Exception:
return
- img = img.resize((self.pxWidth, self.pxHeight), Image.Resampling.LANCZOS)
- frame.paste(img, box=(drawPtX, drawPtY))
+ img = img.resize(
+ (
+ (self.pxWidth + audioMorphWidth),
+ (self.pxHeight + audioMorphHeight),
+ ),
+ Image.Resampling.LANCZOS,
+ )
+ frame.paste(
+ img,
+ box=(
+ (drawPtX - (audioMorphWidth * 2)),
+ (drawPtY - (audioMorphHeight * 2)),
+ ),
+ )
- def drawShape():
+ def drawShape(x, y):
drawer = ImageDraw.Draw(frame)
rect = (
- (drawPtX, drawPtY),
- (drawPtX + self.pxWidth, drawPtY + self.pxHeight),
+ (drawPtX - audioMorphWidth, drawPtY - audioMorphHeight),
+ (
+ drawPtX + self.pxWidth + audioMorphWidth,
+ drawPtY + self.pxHeight + audioMorphHeight,
+ ),
)
shape = self.page.comboBox_shapeType.currentText().lower()
+ thisCellColor = color if (x, y) not in didntChange else (*color[:3], 255)
# Rectangle
if shape == "rectangle":
- drawer.rectangle(rect, fill=self.color)
+ drawer.rectangle(rect, fill=thisCellColor)
# Elliptical
elif shape == "elliptical":
- drawer.ellipse(rect, fill=self.color)
+ drawer.ellipse(rect, fill=thisCellColor)
tenthX, tenthY = scale(10, self.pxWidth, self.pxHeight, int)
smallerShape = (
(
- drawPtX + tenthX + int(tenthX / 4),
- drawPtY + tenthY + int(tenthY / 2),
+ drawPtX + tenthX + int(tenthX / 4) - int(audioMorphWidth / 2),
+ drawPtY + tenthY + int(tenthY / 2) - int(audioMorphHeight / 2),
),
(
- drawPtX + self.pxWidth - tenthX - int(tenthX / 4),
- drawPtY + self.pxHeight - (tenthY + int(tenthY / 2)),
+ drawPtX
+ + self.pxWidth
+ - tenthX
+ - int(tenthX / 4)
+ + int(audioMorphWidth / 2),
+ drawPtY
+ + self.pxHeight
+ - (tenthY + int(tenthY / 2))
+ + int(audioMorphHeight / 2),
),
)
outlineShape = (
- (drawPtX + int(tenthX / 4), drawPtY + int(tenthY / 2)),
(
- drawPtX + self.pxWidth - int(tenthX / 4),
- drawPtY + self.pxHeight - int(tenthY / 2),
+ drawPtX + int(tenthX / 4) - audioMorphWidth,
+ drawPtY + int(tenthY / 2) - audioMorphHeight,
+ ),
+ (
+ drawPtX + self.pxWidth - int(tenthX / 4) + audioMorphWidth,
+ drawPtY + self.pxHeight - int(tenthY / 2) + audioMorphHeight,
),
)
# Circle
if shape == "circle":
- drawer.ellipse(outlineShape, fill=self.color)
+ drawer.ellipse(outlineShape, fill=thisCellColor)
drawer.ellipse(smallerShape, fill=(0, 0, 0, 0))
# Lilypad
elif shape == "lilypad":
- drawer.pieslice(smallerShape, 290, 250, fill=self.color)
+ drawer.pieslice(smallerShape, 290, 250, fill=thisCellColor)
# Pie
elif shape == "pie":
- drawer.pieslice(outlineShape, 35, 320, fill=self.color)
+ drawer.pieslice(outlineShape, 35, 320, fill=thisCellColor)
hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline
tX, tY = scale(33, self.pxWidth, self.pxHeight, int) # thirdline
@@ -235,7 +400,7 @@ class Component(Component):
# Path
if shape == "path":
- drawer.ellipse(rect, fill=self.color)
+ drawer.ellipse(rect, fill=thisCellColor)
rects = {
direction: False
for direction in (
@@ -287,7 +452,7 @@ class Component(Component):
drawPtY + self.pxHeight,
),
)
- drawer.rectangle(sect, fill=self.color)
+ drawer.rectangle(sect, fill=thisCellColor)
# Duck
elif shape == "duck":
@@ -304,10 +469,10 @@ class Component(Component):
(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)
+ drawer.ellipse(duckBody, fill=thisCellColor)
+ drawer.ellipse(duckHead, fill=thisCellColor)
+ drawer.pieslice(duckWing, 130, 200, fill=thisCellColor)
+ drawer.pieslice(duckBeak, 145, 200, fill=thisCellColor)
# Peace
elif shape == "peace":
@@ -321,9 +486,9 @@ class Component(Component):
drawPtY + self.pxHeight - int(tenthY / 2),
),
)
- drawer.ellipse(outlineShape, fill=self.color)
+ drawer.ellipse(outlineShape, fill=thisCellColor)
drawer.ellipse(smallerShape, fill=(0, 0, 0, 0))
- drawer.rectangle(line, fill=self.color)
+ drawer.rectangle(line, fill=thisCellColor)
def slantLine(difference):
return (
@@ -334,8 +499,10 @@ class Component(Component):
(drawPtY + hY),
)
- drawer.line(slantLine(qX), fill=self.color, width=tenthX)
- drawer.line(slantLine(self.pxWidth - qX), fill=self.color, width=tenthX)
+ drawer.line(slantLine(qX), fill=thisCellColor, width=tenthX)
+ drawer.line(
+ slantLine(self.pxWidth - qX), fill=thisCellColor, width=tenthX
+ )
for x, y in grid:
drawPtX = x * self.pxWidth
@@ -345,30 +512,16 @@ class Component(Component):
if drawPtY > self.height:
continue
+ audioMorphWidth = (
+ 0 if spectrumData is None else int(spectrumData[(drawPtX % 63) * 4] / 4)
+ )
+ audioMorphHeight = (
+ 0 if spectrumData is None else int(spectrumData[(drawPtY % 63) * 4] / 4)
+ )
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
- 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,
- )
+ drawShape(x, y)
return frame
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 @@
<x>0</x>
<y>0</y>
<width>586</width>
- <height>197</height>
+ <height>206</height>
</rect>
</property>
<property name="windowTitle">
@@ -29,24 +29,27 @@
</item>
<item>
<widget class="QSpinBox" name="spinBox_tickRate">
+ <property name="toolTip">
+ <string>increase number for slower animation</string>
+ </property>
<property name="suffix">
<string> frames per tick</string>
</property>
<property name="minimum">
- <number>1</number>
+ <number>10</number>
</property>
<property name="maximum">
- <number>30</number>
+ <number>240</number>
</property>
<property name="value">
- <number>5</number>
+ <number>60</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -103,7 +106,7 @@
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -194,7 +197,7 @@
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -258,7 +261,7 @@
<item>
<spacer name="horizontalSpacer_8">
<property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -273,10 +276,23 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
+ <widget class="QCheckBox" name="checkBox_kaleidoscope">
+ <property name="text">
+ <string>Kaleidoscope</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QCheckBox" name="checkBox_shadow">
<property name="text">
<string>Shadow</string>
</property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
</widget>
</item>
<item>
@@ -289,7 +305,7 @@
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -309,7 +325,7 @@
<string>Up</string>
</property>
<property name="arrowType">
- <enum>Qt::UpArrow</enum>
+ <enum>Qt::ArrowType::UpArrow</enum>
</property>
</widget>
</item>
@@ -319,7 +335,7 @@
<string>Down</string>
</property>
<property name="arrowType">
- <enum>Qt::DownArrow</enum>
+ <enum>Qt::ArrowType::DownArrow</enum>
</property>
</widget>
</item>
@@ -329,7 +345,7 @@
<string>Left</string>
</property>
<property name="arrowType">
- <enum>Qt::LeftArrow</enum>
+ <enum>Qt::ArrowType::LeftArrow</enum>
</property>
</widget>
</item>
@@ -339,14 +355,14 @@
<string>Right</string>
</property>
<property name="arrowType">
- <enum>Qt::RightArrow</enum>
+ <enum>Qt::ArrowType::RightArrow</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_9">
<property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -356,6 +372,23 @@
</property>
</spacer>
</item>
+ <item>
+ <widget class="QLabel" name="label_sensitivity">
+ <property name="text">
+ <string>Audio Sensitivity</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="spinBox_sensitivity">
+ <property name="maximum">
+ <number>40</number>
+ </property>
+ <property name="value">
+ <number>20</number>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
</layout>
@@ -364,19 +397,22 @@
<widget class="QTextBrowser" name="textBrowser">
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Click the preview window to place a cell. Right-click to remove.&lt;/span&gt;&lt;/p&gt;
-&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;- A cell with less than 2 neighbours will die from underpopulation&lt;/p&gt;
-&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;- A cell with more than 3 neighbours will die from overpopulation.&lt;/p&gt;
-&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;- An empty space surrounded by 3 live cells will cause reproduction.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+hr { height: 1px; border-width: 0; }
+li.unchecked::marker { content: &quot;\2610&quot;; }
+li.checked::marker { content: &quot;\2612&quot;; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:600;&quot;&gt;Click the preview window to place a cell. Right-click to remove.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;- A cell with less than 2 neighbours will die from underpopulation&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;- A cell with more than 3 neighbours will die from overpopulation.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Ubuntu'; font-size:11pt;&quot;&gt;- An empty space surrounded by 3 live cells will cause reproduction.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="tabStopDistance">
- <number>80</number>
+ <double>80.000000000000000</double>
</property>
<property name="textInteractionFlags">
- <set>Qt::NoTextInteraction</set>
+ <set>Qt::TextInteractionFlag::NoTextInteraction</set>
</property>
<property name="openLinks">
<bool>false</bool>
@@ -388,7 +424,7 @@ p, li { white-space: pre-wrap; }
<item>
<spacer name="verticalSpacer">
<property name="orientation">
- <enum>Qt::Vertical</enum>
+ <enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>