From 9986b1c829caa12bcea120bb37ebb57ab5e0e874 Mon Sep 17 00:00:00 2001
From: tassaron
Date: Thu, 6 Jul 2017 12:40:03 -0400
Subject: image options to mirror & saturate colours
and some friendly docstrings
---
freeze.py | 1 +
src/command.py | 5 ++++
src/components/image.py | 21 +++++++++++++++-
src/components/image.ui | 64 ++++++++++++++++++++++++++++++++++++++++++++++++-
src/core.py | 2 +-
src/mainwindow.py | 9 ++++++-
src/presetmanager.py | 4 ++++
src/preview_thread.py | 18 ++++++++++----
src/video_thread.py | 7 ++++++
9 files changed, 122 insertions(+), 9 deletions(-)
diff --git a/freeze.py b/freeze.py
index a81f325..3266f45 100644
--- a/freeze.py
+++ b/freeze.py
@@ -33,6 +33,7 @@ buildOptions = dict(
"PIL.Image",
"PIL.ImageQt",
"PIL.ImageDraw",
+ "PIL.ImageEnhance",
],
include_files=deps,
)
diff --git a/src/command.py b/src/command.py
index ee0e48d..be194d8 100644
--- a/src/command.py
+++ b/src/command.py
@@ -1,3 +1,8 @@
+'''
+ When using commandline mode, this module's object handles interpreting
+ the arguments and giving them to Core, which tracks the main program state.
+ Then it immediately exports a video.
+'''
from PyQt5 import QtCore
import argparse
import os
diff --git a/src/components/image.py b/src/components/image.py
index 4ccfc80..c9da137 100644
--- a/src/components/image.py
+++ b/src/components/image.py
@@ -1,4 +1,4 @@
-from PIL import Image, ImageDraw
+from PIL import Image, ImageDraw, ImageEnhance
from PyQt5 import QtGui, QtCore, QtWidgets
import os
@@ -20,7 +20,9 @@ class Component(Component):
page.pushButton_image.clicked.connect(self.pickImage)
page.spinBox_scale.valueChanged.connect(self.update)
page.spinBox_rotate.valueChanged.connect(self.update)
+ page.spinBox_color.valueChanged.connect(self.update)
page.checkBox_stretch.stateChanged.connect(self.update)
+ page.checkBox_mirror.stateChanged.connect(self.update)
page.spinBox_x.valueChanged.connect(self.update)
page.spinBox_y.valueChanged.connect(self.update)
@@ -31,9 +33,11 @@ class Component(Component):
self.imagePath = self.page.lineEdit_image.text()
self.scale = self.page.spinBox_scale.value()
self.rotate = self.page.spinBox_rotate.value()
+ self.color = self.page.spinBox_color.value()
self.xPosition = self.page.spinBox_x.value()
self.yPosition = self.page.spinBox_y.value()
self.stretched = self.page.checkBox_stretch.isChecked()
+ self.mirror = self.page.checkBox_mirror.isChecked()
self.parent.drawPreview()
super().update()
@@ -56,33 +60,48 @@ class Component(Component):
frame = BlankFrame(width, height)
if self.imagePath and os.path.exists(self.imagePath):
image = Image.open(self.imagePath)
+
+ # Modify image's appearance
+ if self.color != 100:
+ image = ImageEnhance.Color(image).enhance(
+ float(self.color / 100)
+ )
+ if self.mirror:
+ image = image.transpose(Image.FLIP_LEFT_RIGHT)
if self.stretched and image.size != (width, height):
image = image.resize((width, height), Image.ANTIALIAS)
if self.scale != 100:
newHeight = int((image.height / 100) * self.scale)
newWidth = int((image.width / 100) * self.scale)
image = image.resize((newWidth, newHeight), Image.ANTIALIAS)
+
+ # Paste image at correct position
frame.paste(image, box=(self.xPosition, self.yPosition))
if self.rotate != 0:
frame = frame.rotate(self.rotate)
+
return frame
def loadPreset(self, pr, presetName=None):
super().loadPreset(pr, presetName)
self.page.lineEdit_image.setText(pr['image'])
self.page.spinBox_scale.setValue(pr['scale'])
+ self.page.spinBox_color.setValue(pr['color'])
self.page.spinBox_rotate.setValue(pr['rotate'])
self.page.spinBox_x.setValue(pr['x'])
self.page.spinBox_y.setValue(pr['y'])
self.page.checkBox_stretch.setChecked(pr['stretched'])
+ self.page.checkBox_mirror.setChecked(pr['mirror'])
def savePreset(self):
return {
'preset': self.currentPreset,
'image': self.imagePath,
'scale': self.scale,
+ 'color': self.color,
'rotate': self.rotate,
'stretched': self.stretched,
+ 'mirror': self.mirror,
'x': self.xPosition,
'y': self.yPosition,
}
diff --git a/src/components/image.ui b/src/components/image.ui
index 33488f8..e549ed0 100644
--- a/src/components/image.ui
+++ b/src/components/image.ui
@@ -208,10 +208,17 @@
+ -
+
+
+ Mirror
+
+
+
-
- Rotation
+ Rotate
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
@@ -290,6 +297,61 @@
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Color
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ QAbstractSpinBox::UpDownArrows
+
+
+ %
+
+
+ 0
+
+
+ 999
+
+
+ 1
+
+
+ 100
+
+
+
+
+
-
diff --git a/src/core.py b/src/core.py
index 9ea9666..5623039 100644
--- a/src/core.py
+++ b/src/core.py
@@ -1,5 +1,5 @@
'''
- Home to the Core class which tracks the program state
+ Home to the Core class which tracks program state. Used by GUI & commandline
'''
import sys
import os
diff --git a/src/mainwindow.py b/src/mainwindow.py
index e8a3221..1c6bbc4 100644
--- a/src/mainwindow.py
+++ b/src/mainwindow.py
@@ -1,3 +1,9 @@
+'''
+ When using GUI mode, this module's object (the main window) takes
+ user input to construct a program state (stored in the Core object).
+ This shows a preview of the video being created and allows for saving
+ projects and exporting the video at a later time.
+'''
from PyQt5 import QtCore, QtGui, uic, QtWidgets
from PyQt5.QtWidgets import QMenu, QShortcut
from queue import Queue
@@ -79,6 +85,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.previewWorker = preview_thread.Worker(self, self.previewQueue)
self.previewWorker.moveToThread(self.previewThread)
self.previewWorker.imageCreated.connect(self.showPreviewImage)
+ self.previewWorker.error.connect(self.cleanUp)
self.previewThread.start()
self.timer = QtCore.QTimer(self)
@@ -296,11 +303,11 @@ class MainWindow(QtWidgets.QMainWindow):
QtWidgets.QShortcut("Ctrl+End", self.window, self.moveComponentBottom)
QtWidgets.QShortcut("Ctrl+r", self.window, self.removeComponent)
+ @QtCore.pyqtSlot()
def cleanUp(self):
self.timer.stop()
self.previewThread.quit()
self.previewThread.wait()
- self.autosave()
def updateWindowTitle(self):
appName = 'Audio Visualizer'
diff --git a/src/presetmanager.py b/src/presetmanager.py
index 805b93e..40aa73f 100644
--- a/src/presetmanager.py
+++ b/src/presetmanager.py
@@ -1,3 +1,7 @@
+'''
+ Preset manager object handles all interactions with presets, including
+ the context menu accessed from MainWindow.
+'''
from PyQt5 import QtCore, QtWidgets
import string
import os
diff --git a/src/preview_thread.py b/src/preview_thread.py
index e58f04e..afb5e50 100644
--- a/src/preview_thread.py
+++ b/src/preview_thread.py
@@ -1,3 +1,7 @@
+'''
+ Thread that runs to create QImages for MainWindow's preview label.
+ Processes a queue of component lists.
+'''
from PyQt5 import QtCore, QtGui, uic
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PIL import Image
@@ -11,6 +15,7 @@ from copy import copy
class Worker(QtCore.QObject):
imageCreated = pyqtSignal(['QImage'])
+ error = pyqtSignal()
def __init__(self, parent=None, queue=None):
QtCore.QObject.__init__(self)
@@ -59,12 +64,15 @@ class Worker(QtCore.QObject):
"This is a fatal error." %
str(component),
detail=str(e),
- icon='Warning'
+ icon='Warning',
+ parent=None # mainwindow is in a different thread
)
- quit(1)
-
- self._image = ImageQt(frame)
- self.imageCreated.emit(QtGui.QImage(self._image))
+ from frame import BlankFrame
+ self.imageCreated.emit(ImageQt(BlankFrame))
+ self.error.emit()
+ break
+ else:
+ self.imageCreated.emit(ImageQt(frame))
except Empty:
True
diff --git a/src/video_thread.py b/src/video_thread.py
index aed4d60..d35a37a 100644
--- a/src/video_thread.py
+++ b/src/video_thread.py
@@ -1,3 +1,10 @@
+'''
+ Thread created to export a video. It has a slot to begin export using
+ an input file, output path, and component list. During export multiple
+ threads are created to render the video as quickly as possible. Signals
+ are emitted to update MainWindow's progress bar, detail text, and preview.
+ Export can be cancelled with cancel() + reset()
+'''
from PyQt5 import QtCore, QtGui, uic
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PIL import Image, ImageDraw, ImageFont
--
cgit v1.2.3