feat: Preview window now uses seperate thread.
This commit is contained in:
parent
89c40a8ca1
commit
8bb655eb40
4 changed files with 84 additions and 57 deletions
|
@ -1,5 +1,14 @@
|
|||
# Changelog
|
||||
|
||||
## 0.10.x
|
||||
### 0.10.0: Multithreading for Preview Window
|
||||
- The preview window now processes images in a separate thread, and live update preview is enabled by default.
|
||||
- This improves UI responsiveness.
|
||||
- The image now resizes dynamically to fit the window when the window size changes.
|
||||
- Minor UI improvements.
|
||||
|
||||
---
|
||||
|
||||
## 0.9.x
|
||||
### 0.9.1: Patch for Unsuccessful Successful Update
|
||||
- Addressed a rare issue where the package did not update correctly using the updater.
|
||||
|
|
|
@ -12,7 +12,7 @@ from OptimaLab35.ui.exif_handler_window import ExifEditor
|
|||
from OptimaLab35.ui.simple_dialog import SimpleDialog # Import the SimpleDialog class
|
||||
from OptimaLab35 import __version__
|
||||
|
||||
from PySide6.QtCore import QRunnable, QThreadPool, Signal, QObject, QRegularExpression, Qt, QTimer
|
||||
from PySide6.QtCore import QRunnable, QThreadPool, Signal, QObject, QRegularExpression, Qt, QTimer, Slot
|
||||
|
||||
from PySide6 import QtWidgets
|
||||
from PySide6.QtWidgets import (
|
||||
|
@ -52,7 +52,7 @@ class OptimaLab35(QMainWindow, Ui_MainWindow):
|
|||
self.sd = SimpleDialog()
|
||||
|
||||
# Change UI elements
|
||||
self.change_statusbar(f"Using {self.o.name} v{self.o.version}", 5000)
|
||||
self.change_statusbar(f"{self.name} v{self.version}", 10000)
|
||||
self.set_title()
|
||||
self.default_ui_layout()
|
||||
self.define_gui_interaction()
|
||||
|
@ -501,10 +501,12 @@ class UpdaterWindow(QMainWindow, Ui_Updater_Window):
|
|||
|
||||
def start_long_press(self):
|
||||
"""Start the timer when button is pressed."""
|
||||
# brave AI
|
||||
self.timer.start(1000) # 1-second long press
|
||||
|
||||
def cancel_long_press(self):
|
||||
"""Cancel long press if released early."""
|
||||
# brave AI
|
||||
self.timer.stop()
|
||||
|
||||
def toggle_dev_ui(self):
|
||||
|
@ -520,8 +522,8 @@ class UpdaterWindow(QMainWindow, Ui_Updater_Window):
|
|||
self.ui.label_optima35_localversion.setText(self.optima35_localversion)
|
||||
|
||||
self.ui.label_latest_version.setText("Latest version")
|
||||
self.ui.label_optimalab35_latestversion.setText(self.ol35_last_state[1])
|
||||
self.ui.label_optima35_latestversion.setText(self.o35_last_state[1])
|
||||
self.ui.label_optimalab35_latestversion.setText("...")
|
||||
self.ui.label_optima35_latestversion.setText("...")
|
||||
|
||||
self.ui.update_and_restart_Button.setEnabled(False)
|
||||
|
||||
|
@ -706,14 +708,18 @@ class UpdaterWindow(QMainWindow, Ui_Updater_Window):
|
|||
|
||||
class PreviewWindow(QMainWindow, Ui_Preview_Window):
|
||||
values_selected = Signal(int, int, bool)
|
||||
# Large ChatGPT with rewrite and bug fixes from me.
|
||||
|
||||
def __init__(self):
|
||||
super(PreviewWindow, self).__init__()
|
||||
self.ui = Ui_Preview_Window()
|
||||
self.ui.setupUi(self)
|
||||
self.o = OptimaManager()
|
||||
self.threadpool = QThreadPool() # Thread pool for managing worker threads
|
||||
|
||||
self.ui.QLabel.setAlignment(Qt.AlignCenter)
|
||||
## Ui interaction
|
||||
|
||||
# UI interactions
|
||||
self.ui.load_Button.clicked.connect(self.browse_file)
|
||||
self.ui.update_Button.clicked.connect(self.update_preview)
|
||||
self.ui.close_Button.clicked.connect(self.close_window)
|
||||
|
@ -724,12 +730,8 @@ class PreviewWindow(QMainWindow, Ui_Preview_Window):
|
|||
# Connect UI elements to `on_ui_change`
|
||||
self.ui.brightness_spinBox.valueChanged.connect(self.on_ui_change)
|
||||
self.ui.brightness_Slider.valueChanged.connect(self.on_ui_change)
|
||||
#self.ui.reset_brightness_Button.clicked.connect(self.on_ui_change)
|
||||
|
||||
self.ui.contrast_spinBox.valueChanged.connect(self.on_ui_change)
|
||||
self.ui.contrast_Slider.valueChanged.connect(self.on_ui_change)
|
||||
#self.ui.reset_contrast_Button.clicked.connect(self.on_ui_change)
|
||||
|
||||
self.ui.grayscale_checkBox.stateChanged.connect(self.on_ui_change)
|
||||
|
||||
def on_ui_change(self):
|
||||
|
@ -738,69 +740,48 @@ class PreviewWindow(QMainWindow, Ui_Preview_Window):
|
|||
self.update_preview()
|
||||
|
||||
def browse_file(self):
|
||||
file = QFileDialog.getOpenFileName(self, caption = "Select File", filter = ("Images (*.png *.webp *.jpg *.jpeg)"))
|
||||
file = QFileDialog.getOpenFileName(self, caption="Select File", filter="Images (*.png *.webp *.jpg *.jpeg)")
|
||||
if file[0]:
|
||||
self.ui.image_path_lineEdit.setText(file[0])
|
||||
self.update_preview()
|
||||
|
||||
def process_image(self, path):
|
||||
"""Loads and processes the image with modifications."""
|
||||
# Refactored by ChatGPT
|
||||
if not os.path.isfile(path):
|
||||
return None
|
||||
def update_preview(self):
|
||||
"""Handles loading and displaying the image in a separate thread."""
|
||||
path = self.ui.image_path_lineEdit.text()
|
||||
|
||||
try:
|
||||
img = self.o.process_image_object(
|
||||
image_input_file=path, # Example: resize percentage
|
||||
watermark="PREVIEW",
|
||||
resize = 100,
|
||||
grayscale=self.ui.grayscale_checkBox.isChecked(),
|
||||
brightness=int(self.ui.brightness_spinBox.text()),
|
||||
contrast=int(self.ui.contrast_spinBox.text())
|
||||
)
|
||||
return QPixmap.fromImage(img)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Warning", "Error loading image...")
|
||||
print(f"Error loading image...\n{e}")
|
||||
return None
|
||||
worker = ImageProcessorWorker(
|
||||
path=path,
|
||||
optima_manager=self.o,
|
||||
brightness=int(self.ui.brightness_spinBox.text()),
|
||||
contrast=int(self.ui.contrast_spinBox.text()),
|
||||
grayscale=self.ui.grayscale_checkBox.isChecked(),
|
||||
callback=self.display_image # Callback to update UI
|
||||
)
|
||||
self.threadpool.start(worker) # Run worker in a thread
|
||||
|
||||
def display_image(self, pixmap):
|
||||
"""Adjusts the image to fit within the QLabel."""
|
||||
# ChatGPT
|
||||
if pixmap is None:
|
||||
QMessageBox.warning(self, "Warning", "Error processing image...")
|
||||
return
|
||||
|
||||
# Get max available size (QLabel size)
|
||||
max_size = self.ui.QLabel.size()
|
||||
max_width = max_size.width()
|
||||
max_height = max_size.height()
|
||||
|
||||
# Scale image to fit within the available space while maintaining aspect ratio
|
||||
scaled_pixmap = pixmap.scaled(
|
||||
max_width, max_height,
|
||||
Qt.KeepAspectRatio,
|
||||
Qt.SmoothTransformation
|
||||
)
|
||||
|
||||
# Set the scaled image
|
||||
scaled_pixmap = pixmap.scaled(max_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
self.ui.QLabel.setPixmap(scaled_pixmap)
|
||||
|
||||
# Adjust QLabel size to match image
|
||||
self.ui.QLabel.resize(scaled_pixmap.size())
|
||||
|
||||
def update_preview(self):
|
||||
"""Handles loading and displaying the image."""
|
||||
# ChatGPT
|
||||
path = self.ui.image_path_lineEdit.text()
|
||||
pixmap = self.process_image(path)
|
||||
self.display_image(pixmap)
|
||||
def resizeEvent(self, event):
|
||||
"""Triggered when the preview window is resized."""
|
||||
file_path = self.ui.image_path_lineEdit.text()
|
||||
if os.path.exists(file_path):
|
||||
self.update_preview() # Re-process and display the image
|
||||
super().resizeEvent(event) # Keep the default behavior
|
||||
|
||||
def close_window(self):
|
||||
# Emit the signal with the values from the spinboxes and checkbox
|
||||
# chatgpt
|
||||
if self.ui.checkBox.isChecked():
|
||||
self.values_selected.emit(self.ui.brightness_spinBox.value(), self.ui.contrast_spinBox.value(), self.ui.grayscale_checkBox.isChecked())
|
||||
self.close()
|
||||
"""Emits signal and closes the window."""
|
||||
if self.ui.checkBox.isChecked():
|
||||
self.values_selected.emit(self.ui.brightness_spinBox.value(), self.ui.contrast_spinBox.value(), self.ui.grayscale_checkBox.isChecked())
|
||||
self.close()
|
||||
|
||||
class WorkerSignals(QObject):
|
||||
# ChatGPT
|
||||
|
@ -851,6 +832,39 @@ class ImageProcessorRunnable(QRunnable):
|
|||
|
||||
self.signals.finished.emit()
|
||||
|
||||
class ImageProcessorWorker(QRunnable):
|
||||
"""Worker class to load and process the image in a separate thread."""
|
||||
# ChatGPT
|
||||
def __init__(self, path, optima_manager, brightness, contrast, grayscale, callback):
|
||||
super().__init__()
|
||||
self.path = path
|
||||
self.optima_manager = optima_manager
|
||||
self.brightness = brightness
|
||||
self.contrast = contrast
|
||||
self.grayscale = grayscale
|
||||
self.callback = callback # Function to call when processing is done
|
||||
|
||||
@Slot()
|
||||
def run(self):
|
||||
"""Runs the image processing in a separate thread."""
|
||||
if not os.path.isfile(self.path):
|
||||
self.callback(None)
|
||||
return
|
||||
|
||||
try:
|
||||
img = self.optima_manager.process_image_object(
|
||||
image_input_file=self.path,
|
||||
watermark="PREVIEW",
|
||||
resize=100,
|
||||
grayscale=self.grayscale,
|
||||
brightness=self.brightness,
|
||||
contrast=self.contrast
|
||||
)
|
||||
pixmap = QPixmap.fromImage(img)
|
||||
self.callback(pixmap)
|
||||
except Exception as e:
|
||||
print(f"Error processing image: {e}")
|
||||
self.callback(None)
|
||||
|
||||
def main():
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
|
|
@ -131,6 +131,7 @@ class Ui_Preview_Window(object):
|
|||
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
|
||||
self.live_update = QCheckBox(self.widget_4)
|
||||
self.live_update.setObjectName(u"live_update")
|
||||
self.live_update.setChecked(True)
|
||||
|
||||
self.verticalLayout_3.addWidget(self.live_update)
|
||||
|
||||
|
@ -196,7 +197,7 @@ class Ui_Preview_Window(object):
|
|||
#endif // QT_CONFIG(tooltip)
|
||||
self.grayscale_checkBox.setText(QCoreApplication.translate("Preview_Window", u"Black n White", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.live_update.setToolTip(QCoreApplication.translate("Preview_Window", u"Enable live updates to immediately preview changes as you adjust brightness, contrast, or grayscale. Be aware that this might impact performance, especially with large images.", None))
|
||||
self.live_update.setToolTip(QCoreApplication.translate("Preview_Window", u"Live update applies changes instantly. If the app becomes unresponsive or lags, disable this option.", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.live_update.setText(QCoreApplication.translate("Preview_Window", u"Live update", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
|
|
|
@ -191,11 +191,14 @@
|
|||
<item>
|
||||
<widget class="QCheckBox" name="live_update">
|
||||
<property name="toolTip">
|
||||
<string>Enable live updates to immediately preview changes as you adjust brightness, contrast, or grayscale. Be aware that this might impact performance, especially with large images.</string>
|
||||
<string>Live update applies changes instantly. If the app becomes unresponsive or lags, disable this option.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Live update</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue