Compare commits
No commits in common. "cc0ee4dde107670c1aa53281176bc9f0c39cf56b" and "2f3f618f41b3a591162931b1beb528ac5111c2f0" have entirely different histories.
cc0ee4dde1
...
2f3f618f41
|
|
@ -1,43 +0,0 @@
|
|||
from PyQt6.QtWidgets import QFileDialog, QWidget, QToolBar, QVBoxLayout, QPushButton
|
||||
from PyQt6.QtGui import QIcon
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
import numpy as np
|
||||
|
||||
from dialogs import DIALOGS
|
||||
|
||||
DIALOG_PROPERTY = "dialog"
|
||||
|
||||
class DialogsPanel(QWidget):
|
||||
result_ready = pyqtSignal(np.ndarray)
|
||||
|
||||
def __init__(self, image_manager):
|
||||
super().__init__()
|
||||
self.mgr = image_manager
|
||||
self.dialog_buttons = []
|
||||
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
for DIALOG in DIALOGS:
|
||||
btn = QPushButton(DIALOG.dialog_name(), self)
|
||||
btn.setProperty(DIALOG_PROPERTY, DIALOG)
|
||||
btn.clicked.connect(self.open_dialog)
|
||||
|
||||
self.dialog_buttons.append(btn)
|
||||
layout.addWidget(btn)
|
||||
|
||||
|
||||
def open_dialog(self):
|
||||
dialog_factory = self.sender().property(DIALOG_PROPERTY)
|
||||
self.dialog = dialog_factory(self.mgr.image())
|
||||
self.dialog.result_ready().connect(lambda img : self.result_ready.emit(img))
|
||||
self.dialog.accepted.connect(self.on_accepted)
|
||||
self.dialog.rejected.connect(self.on_rejected)
|
||||
self.dialog.exec()
|
||||
|
||||
def on_accepted(self):
|
||||
self.mgr.update(self.dialog.last_processed)
|
||||
self.dialog = None
|
||||
|
||||
def on_rejected(self):
|
||||
self.mgr.refresh()
|
||||
|
|
@ -1,20 +1,18 @@
|
|||
import abc
|
||||
import ImageProcessingWorker
|
||||
|
||||
|
||||
from ImageProcessingWorker import ImageProcessingWorker
|
||||
import numpy as np
|
||||
|
||||
from PyQt6.QtWidgets import QApplication, QLabel, QVBoxLayout, QHBoxLayout, QPushButton, QWidget, QFileDialog, QSlider, QLineEdit, QDialog
|
||||
from PyQt6.QtGui import QPixmap, QImage, QColor, QPainter, QPen
|
||||
from PyQt6.QtCore import Qt, QPoint, QThread
|
||||
|
||||
from .ImageParameterDialog import ImageParameterDialog
|
||||
from ImageParameterDialog import ImageParameterDialog
|
||||
|
||||
|
||||
class HCLDialog(ImageParameterDialog):
|
||||
class HueDialog(ImageParameterDialog):
|
||||
def __init__(self, hsv_image):
|
||||
super().__init__(hsv_image, ImageProcessingWorker.HLSImageProcessingWorker)
|
||||
self.setWindowTitle("HCL Correction")
|
||||
super().__init__(hsv_image)
|
||||
self.setWindowTitle("Hue Correction Dialog")
|
||||
self.layout = QVBoxLayout()
|
||||
|
||||
self.hue_slider = QSlider(Qt.Orientation.Horizontal)
|
||||
|
|
@ -41,7 +39,6 @@ class HCLDialog(ImageParameterDialog):
|
|||
self.layout.addWidget(self.chroma_slider)
|
||||
self.layout.addWidget(self.label3)
|
||||
self.layout.addWidget(self.lightness_slider)
|
||||
self.layout.addWidget(self.button_box)
|
||||
|
||||
self.setLayout(self.layout)
|
||||
|
||||
|
|
@ -57,20 +54,15 @@ class HCLDialog(ImageParameterDialog):
|
|||
|
||||
def process_image(self,hsv_image, values):
|
||||
hue = values.get('hue', 0.0) / 2
|
||||
chroma = values.get('chroma', 0.0)
|
||||
lightness = values.get('lightness', 0.0)
|
||||
chroma = values.get('chroma', 0.0) + 1.0
|
||||
lightness = values.get('lightness', 0.0) + 1.0
|
||||
|
||||
hsv_image[..., 0] = (hsv_image[..., 0] + hue) % 180
|
||||
|
||||
hsv_image[..., 1] += (255 * lightness)
|
||||
hsv_image[..., 1] = np.clip(hsv_image[..., 1], 0, 255)
|
||||
# Adjust chroma (saturation)
|
||||
hsv_image[..., 1] = np.clip(hsv_image[..., 1] * chroma, 0, 255)
|
||||
|
||||
hsv_image[..., 2] += (255 * chroma)
|
||||
hsv_image[..., 2] = np.clip(hsv_image[..., 2], 0, 255)
|
||||
# Adjust lightness (value)
|
||||
hsv_image[..., 2] = np.clip(hsv_image[..., 2] * lightness, 0, 255)
|
||||
|
||||
|
||||
return hsv_image
|
||||
|
||||
@classmethod
|
||||
def dialog_name(cls):
|
||||
return "Hue-Chroma-Lightness"
|
||||
return hsv_image
|
||||
|
|
@ -4,7 +4,7 @@ from PyQt6.QtCore import Qt
|
|||
|
||||
class ImageCanvas(QGraphicsView):
|
||||
_pixmapItem: QGraphicsPixmapItem
|
||||
empty = True
|
||||
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
|
@ -30,16 +30,6 @@ class ImageCanvas(QGraphicsView):
|
|||
def updatePixmap(self, image: QImage):
|
||||
pixmap = QPixmap.fromImage(image)
|
||||
self._pixmapItem.setPixmap(pixmap)
|
||||
if self.empty:
|
||||
self.reset()
|
||||
self.empty = False
|
||||
|
||||
def clear(self):
|
||||
self.scene.removeItem(self._pixmapItem)
|
||||
self._pixmapItem = QGraphicsPixmapItem()
|
||||
self.scene.addItem(self._pixmapItem)
|
||||
|
||||
self.empty = True
|
||||
|
||||
def reset(self):
|
||||
self.resetTransform() # Reset the zoom level to default
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
from PyQt6.QtWidgets import QFileDialog, QWidget, QToolBar, QHBoxLayout, QPushButton
|
||||
from PyQt6.QtGui import QIcon
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
import numpy as np
|
||||
|
||||
from ImageManager import ImageManager
|
||||
|
||||
class ImageManagePanel(QWidget):
|
||||
on_update = pyqtSignal(np.ndarray)
|
||||
on_close = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.mgr = ImageManager(self._on_update)
|
||||
|
||||
layout = QHBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
self.open_button = QPushButton(QIcon.fromTheme("document-open"), "Open", self)
|
||||
layout.addWidget(self.open_button)
|
||||
self.open_button.clicked.connect(self._open_image)
|
||||
|
||||
self.save_button = QPushButton(QIcon("save.png"), "Save", self)
|
||||
layout.addWidget(self.save_button)
|
||||
self.save_button.clicked.connect(self._save_image)
|
||||
|
||||
self.close_button = QPushButton(QIcon("close.png"), "Close", self)
|
||||
layout.addWidget(self.close_button)
|
||||
self.close_button.clicked.connect(self._close_image)
|
||||
|
||||
self.undo_button = QPushButton(QIcon("undo.png"), "Undo", self)
|
||||
layout.addWidget(self.undo_button)
|
||||
self.undo_button.clicked.connect(self._undo)
|
||||
|
||||
self.redo_button = QPushButton(QIcon("redo.png"), "Redo", self)
|
||||
layout.addWidget(self.redo_button)
|
||||
self.redo_button.clicked.connect(self._redo)
|
||||
|
||||
self._enable_buttons()
|
||||
|
||||
def _redo(self):
|
||||
self.mgr.redo()
|
||||
self._enable_buttons()
|
||||
|
||||
def _undo(self):
|
||||
self.mgr.undo()
|
||||
self._enable_buttons()
|
||||
|
||||
def _on_update(self, image):
|
||||
self.on_update.emit(image)
|
||||
self._enable_buttons()
|
||||
|
||||
def _enable_buttons(self):
|
||||
self.undo_button.setEnabled(self.mgr.can_undo())
|
||||
self.redo_button.setEnabled(self.mgr.can_redo())
|
||||
|
||||
|
||||
def _close_image(self):
|
||||
self.mgr.close()
|
||||
self.on_close.emit()
|
||||
|
||||
def _open_image(self):
|
||||
file_name, _ = QFileDialog.getOpenFileName(self, "Open Image File", "", "Image Files (*.jpg *.jpeg *.png)")
|
||||
if file_name:
|
||||
self.mgr.open(file_name)
|
||||
|
||||
def _save_image(self):
|
||||
file_name, _ = QFileDialog.getSaveFileName(self, "Save Image File", "", "Image Files (*.jpg *.png)")
|
||||
if file_name:
|
||||
self.mgr.save(file_name)
|
||||
|
||||
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
from collections import deque
|
||||
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
class ImageManager():
|
||||
|
||||
def __init__(self, update_fn):
|
||||
super().__init__()
|
||||
self.update_fn = update_fn
|
||||
self.close()
|
||||
|
||||
def undo(self):
|
||||
if len(self._undo_deque) == 0:
|
||||
return
|
||||
prev = self._undo_deque.pop()
|
||||
self._redo_deque.append(self._curr_image)
|
||||
self._curr_image = prev
|
||||
self.update_fn(self._curr_image)
|
||||
|
||||
def redo(self):
|
||||
if len(self._redo_deque) == 0:
|
||||
return
|
||||
latter = self._redo_deque.pop()
|
||||
self._undo_deque.append(self._curr_image)
|
||||
self._curr_image = latter
|
||||
self.update_fn(self._curr_image)
|
||||
|
||||
def update(self, new_image):
|
||||
self._undo_deque.append(self._curr_image)
|
||||
self._redo_deque = deque()
|
||||
self._curr_image = new_image.copy()
|
||||
self.update_fn(self._curr_image)
|
||||
|
||||
def open(self, file_name):
|
||||
self.close()
|
||||
self._curr_image = cv2.imread(file_name)
|
||||
self._curr_image = cv2.cvtColor(self._curr_image, cv2.COLOR_BGR2RGB)
|
||||
self.update_fn(self._curr_image)
|
||||
|
||||
|
||||
def close(self):
|
||||
self._undo_deque = deque()
|
||||
self._redo_deque = deque()
|
||||
self._curr_image = None
|
||||
|
||||
def save(self, file_name):
|
||||
if self._curr_image is not None:
|
||||
cv2.imwrite(file_name, cv2.cvtColor(self._curr_image, cv2.COLOR_RGB2BGR))
|
||||
|
||||
def refresh(self):
|
||||
self.update_fn(self._curr_image)
|
||||
|
||||
def image(self):
|
||||
return self._curr_image
|
||||
|
||||
def can_redo(self):
|
||||
return len(self._redo_deque) > 0
|
||||
|
||||
def can_undo(self):
|
||||
return len(self._undo_deque) > 0
|
||||
33
jezyki-skryptowe/image-editor/ImageParameterDialog.py
Normal file
33
jezyki-skryptowe/image-editor/ImageParameterDialog.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import abc
|
||||
from ImageProcessingWorker import ImageProcessingWorker
|
||||
|
||||
from PyQt6.QtWidgets import QApplication, QLabel, QVBoxLayout, QHBoxLayout, QPushButton, QWidget, QFileDialog, QSlider, QLineEdit, QDialog
|
||||
from PyQt6.QtGui import QPixmap, QImage, QColor, QPainter, QPen
|
||||
from PyQt6.QtCore import Qt, QPoint, QThread
|
||||
|
||||
|
||||
class ImageParameterDialog(QDialog):
|
||||
|
||||
def __init__(self, hsv_image):
|
||||
self._hsv_image = hsv_image
|
||||
self.setup_worker()
|
||||
super().__init__()
|
||||
|
||||
def setup_worker(self):
|
||||
self.worker = ImageProcessingWorker(self._hsv_image, self.process_image)
|
||||
self.update_values_signal = self.worker.update_values
|
||||
self.update_values_signal.connect(self.worker.process_image)
|
||||
|
||||
self.worker.start()
|
||||
|
||||
|
||||
# @abc.abstractmethod
|
||||
def process_image(self,hsv_image, values):
|
||||
pass
|
||||
|
||||
|
||||
def result_ready(self):
|
||||
return self.worker.result_ready
|
||||
|
||||
def send_to_process(self, values):
|
||||
self.update_values_signal.emit(values)
|
||||
|
|
@ -35,41 +35,11 @@ class ImageProcessingWorker(QThread):
|
|||
|
||||
img = self.image.copy()
|
||||
processed_image = self.process_function(img, values)
|
||||
postprocessed_image = self.postprocess(processed_image)
|
||||
processed_image = processed_image.astype('uint8')
|
||||
rgb_image = cv2.cvtColor(processed_image, cv2.COLOR_HSV2RGB)
|
||||
|
||||
self.result_ready.emit(postprocessed_image)
|
||||
|
||||
def postprocess(self, processed_image):
|
||||
return processed_image
|
||||
self.result_ready.emit(rgb_image)
|
||||
|
||||
def stop(self):
|
||||
self.queue(None)
|
||||
self.wait()
|
||||
|
||||
|
||||
class RGBImageProcessingWorker(ImageProcessingWorker):
|
||||
def __init__(self, image, process_function):
|
||||
img = image.astype('float32')
|
||||
super().__init__(img, process_function)
|
||||
|
||||
def postprocess(self, processed_image):
|
||||
return processed_image.astype('uint8')
|
||||
|
||||
|
||||
class HSVImageProcessingWorker(ImageProcessingWorker):
|
||||
def __init__(self, image, process_function):
|
||||
img = cv2.cvtColor(image, cv2.COLOR_RGB2HSV).astype('float32')
|
||||
super().__init__(img, process_function)
|
||||
|
||||
def postprocess(self, processed_image):
|
||||
processed_image = processed_image.astype('uint8')
|
||||
return cv2.cvtColor(processed_image, cv2.COLOR_HSV2RGB)
|
||||
|
||||
class HLSImageProcessingWorker(ImageProcessingWorker):
|
||||
def __init__(self, image, process_function):
|
||||
img = cv2.cvtColor(image, cv2.COLOR_RGB2HLS).astype('float32')
|
||||
super().__init__(img, process_function)
|
||||
|
||||
def postprocess(self, processed_image):
|
||||
processed_image = processed_image.astype('uint8')
|
||||
return cv2.cvtColor(processed_image, cv2.COLOR_HLS2RGB)
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
import abc
|
||||
|
||||
from PyQt6.QtWidgets import QWidget, QDialog, QDialogButtonBox
|
||||
from PyQt6.QtGui import QPixmap, QImage, QColor, QPainter, QPen
|
||||
from PyQt6.QtCore import Qt, QPoint, QThread
|
||||
|
||||
|
||||
class ImageParameterDialog(QDialog):
|
||||
|
||||
def __init__(self, image, worker_factory):
|
||||
super().__init__()
|
||||
self._image = image
|
||||
self.last_processed = image
|
||||
self.worker_factory = worker_factory
|
||||
self.setup_worker()
|
||||
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
|
||||
def setup_worker(self):
|
||||
self.worker = self.worker_factory(self._image, self.process_image)
|
||||
self.worker.result_ready.connect(self._update_last_processed)
|
||||
self.update_values_signal = self.worker.update_values
|
||||
self.update_values_signal.connect(self.worker.process_image)
|
||||
|
||||
self.worker.start()
|
||||
|
||||
def _update_last_processed(self, new_image):
|
||||
self.last_processed = new_image
|
||||
|
||||
def _on_accepted(self):
|
||||
self.worker.stop()
|
||||
self.accept()
|
||||
|
||||
|
||||
# @abc.abstractmethod
|
||||
def process_image(self,image, values):
|
||||
pass
|
||||
|
||||
|
||||
def result_ready(self):
|
||||
return self.worker.result_ready
|
||||
|
||||
def send_to_process(self, values):
|
||||
self.update_values_signal.emit(values)
|
||||
|
||||
def set_accept_enable(self, value):
|
||||
self.button_box.button(QDialogButtonBox.StandardButton.Ok).setEnabled(value)
|
||||
|
||||
|
||||
@classmethod
|
||||
def dialog_name(cls):
|
||||
return cls.__name__
|
||||
|
||||
@classmethod
|
||||
def dialog_icon(cls):
|
||||
return None
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
import ImageProcessingWorker
|
||||
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
from PyQt6.QtWidgets import (QApplication, QLabel, QVBoxLayout, QHBoxLayout, QPushButton, QWidget, QFileDialog, QSlider,
|
||||
QLineEdit, QDialog, QCheckBox, QComboBox)
|
||||
from PyQt6.QtGui import QPixmap, QImage, QColor, QPainter, QPen, QIntValidator
|
||||
from PyQt6.QtCore import Qt, QPoint, QThread
|
||||
|
||||
from .ImageParameterDialog import ImageParameterDialog
|
||||
|
||||
|
||||
INTERPOLATION_MAP = {
|
||||
"Nearest": cv2.INTER_NEAREST,
|
||||
"Linear": cv2.INTER_LINEAR,
|
||||
"Area": cv2.INTER_AREA,
|
||||
"Cubic": cv2.INTER_CUBIC,
|
||||
"Lanczos4": cv2.INTER_LANCZOS4
|
||||
}
|
||||
|
||||
class ResizeDialog(ImageParameterDialog):
|
||||
def __init__(self, image):
|
||||
super().__init__(image, ImageProcessingWorker.ImageProcessingWorker)
|
||||
|
||||
self.original_height, self.original_width, _ = image.shape
|
||||
|
||||
|
||||
self.setWindowTitle("Resizing")
|
||||
self.layout = QVBoxLayout()
|
||||
|
||||
# Width input
|
||||
self.width_label = QLabel("Width:")
|
||||
self.width_field = QLineEdit()
|
||||
self.width_field.setPlaceholderText("Enter width")
|
||||
self.width_field.setText(str(self.original_width))
|
||||
self.width_field.setValidator(QIntValidator(1, 10000))
|
||||
self.width_field.textEdited.connect(self.width_changed)
|
||||
|
||||
# Height input
|
||||
self.height_label = QLabel("Height:")
|
||||
self.height_field = QLineEdit()
|
||||
self.height_field.setPlaceholderText("Enter height")
|
||||
self.height_field.setText(str(self.original_height))
|
||||
self.height_field.setValidator(QIntValidator(1, 10000))
|
||||
self.height_field.textEdited.connect(self.height_changed)
|
||||
|
||||
# Auto-scaling checkbox
|
||||
self.auto_scale_checkbox = QCheckBox("Auto-scale")
|
||||
self.auto_scale_checkbox.setChecked(True)
|
||||
self.auto_scale_checkbox.stateChanged.connect(self.toggle_auto_scale)
|
||||
|
||||
# Interpolation method dropdown
|
||||
self.interpolation_label = QLabel("Interpolation Method:")
|
||||
self.interpolation_dropdown = QComboBox()
|
||||
self.interpolation_dropdown.addItems(list(INTERPOLATION_MAP.keys()))
|
||||
self.interpolation_dropdown.currentIndexChanged.connect(self.update)
|
||||
# Layout for input fields
|
||||
input_layout = QHBoxLayout()
|
||||
input_layout.addWidget(self.width_label)
|
||||
input_layout.addWidget(self.width_field)
|
||||
input_layout.addWidget(self.height_label)
|
||||
input_layout.addWidget(self.height_field)
|
||||
|
||||
self.layout.addLayout(input_layout)
|
||||
self.layout.addWidget(self.auto_scale_checkbox)
|
||||
self.layout.addWidget(self.interpolation_label)
|
||||
self.layout.addWidget(self.interpolation_dropdown)
|
||||
|
||||
self.layout.addWidget(self.button_box)
|
||||
|
||||
self.setLayout(self.layout)
|
||||
|
||||
self.toggle_auto_scale() # Initially disable the width and height fields
|
||||
|
||||
def toggle_auto_scale(self):
|
||||
if self.auto_scale_checkbox.isChecked():
|
||||
self.adjust_height()
|
||||
|
||||
|
||||
def adjust_height(self):
|
||||
if self.auto_scale_checkbox.isChecked():
|
||||
try:
|
||||
width = int(self.width_field.text())
|
||||
height = int((width / self.original_width) * self.original_height)
|
||||
self.height_field.setText(str(height))
|
||||
except ValueError:
|
||||
self.height_field.clear()
|
||||
|
||||
def adjust_width(self):
|
||||
if self.auto_scale_checkbox.isChecked():
|
||||
try:
|
||||
height = int(self.height_field.text())
|
||||
width = int((height / self.original_height) * self.original_width)
|
||||
self.width_field.setText(str(width))
|
||||
except ValueError:
|
||||
self.width_field.clear()
|
||||
|
||||
def height_changed(self):
|
||||
self.adjust_width()
|
||||
self.update()
|
||||
|
||||
def width_changed(self):
|
||||
self.adjust_height()
|
||||
self.update()
|
||||
|
||||
|
||||
|
||||
def update(self):
|
||||
width = self.width_field.text()
|
||||
height = self.height_field.text()
|
||||
if (not width or not height):
|
||||
self.set_accept_enable(False)
|
||||
return
|
||||
self.send_to_process({
|
||||
'width': int(width),
|
||||
'height': int(height),
|
||||
'auto_scale': self.auto_scale_checkbox.isChecked(),
|
||||
'interpolation': self.interpolation_dropdown.currentText()
|
||||
})
|
||||
|
||||
def process_image(self, image, values):
|
||||
if values['width'] == 0 or values['height'] == 0:
|
||||
return image
|
||||
|
||||
interpolation = INTERPOLATION_MAP[values['interpolation']]
|
||||
try:
|
||||
resized_image = cv2.resize(image, (values['width'], values['height']), interpolation=interpolation)
|
||||
except:
|
||||
self.set_accept_enable(False)
|
||||
return image
|
||||
self.set_accept_enable(True)
|
||||
return resized_image
|
||||
|
||||
@classmethod
|
||||
def dialog_name(cls):
|
||||
return "Resize"
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
from .HCLDialog import HCLDialog
|
||||
from .ResizeDialog import ResizeDialog
|
||||
|
||||
|
||||
DIALOGS = [
|
||||
HCLDialog,
|
||||
ResizeDialog
|
||||
]
|
||||
|
|
@ -7,34 +7,62 @@ import numpy as np
|
|||
|
||||
from ImageCanvas import ImageCanvas
|
||||
from ImageProcessingWorker import ImageProcessingWorker
|
||||
from ImageManager import ImageManager
|
||||
from ImageManagePanel import ImageManagePanel
|
||||
from DialogsPanel import DialogsPanel
|
||||
from HueDialog import HueDialog
|
||||
|
||||
def process_image_function(image, values):
|
||||
saturation = values.get('saturation', 0.0)
|
||||
contrast = values.get('contrast', 1.0)
|
||||
brightness = values.get('brightness', 0)
|
||||
|
||||
|
||||
# Adjust saturation
|
||||
hsv_image[:, :, 1] += saturation * 255
|
||||
hsv_image[:, :, 1] = np.clip(hsv_image[:, :, 1], 0, 255)
|
||||
|
||||
# Adjust brightness and contrast
|
||||
hsv_image[:, :, 2] = np.clip(hsv_image[:, :, 2] * contrast + brightness, 0, 255)
|
||||
|
||||
|
||||
return rgb_image
|
||||
|
||||
|
||||
class ImageEditor(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
||||
self.setWindowTitle("Image Editor")
|
||||
self.setGeometry(100, 100, 800, 600)
|
||||
|
||||
self.canvas = ImageCanvas()
|
||||
|
||||
self.img_manager = ImageManagePanel()
|
||||
self.img_manager.on_update.connect(self.display_image)
|
||||
self.img_manager.on_close.connect(lambda : self.canvas.clear())
|
||||
# self.image_label = QLabel()
|
||||
# self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.dialogs_panel = DialogsPanel(self.img_manager.mgr)
|
||||
self.dialogs_panel.result_ready.connect(self.display_image)
|
||||
self.open_button = QPushButton("Open Image")
|
||||
self.open_button.clicked.connect(self.open_image)
|
||||
|
||||
self.save_button = QPushButton("Save Image")
|
||||
self.save_button.clicked.connect(self.save_image)
|
||||
|
||||
self.hcl_button = QPushButton("Hue-Chroma-Lightness")
|
||||
self.hcl_button.clicked.connect(self.open_hcl)
|
||||
|
||||
|
||||
self.resolution_label = QLabel("Resolution:")
|
||||
self.width_input = QLineEdit()
|
||||
self.height_input = QLineEdit()
|
||||
|
||||
self.aspect_ratio_label = QLabel("Aspect Ratio:")
|
||||
self.aspect_ratio_input = QLineEdit()
|
||||
|
||||
|
||||
main_layout = QHBoxLayout()
|
||||
|
||||
side_panel = QVBoxLayout()
|
||||
|
||||
side_panel.addWidget(self.img_manager, 1)
|
||||
side_panel.addWidget(self.dialogs_panel, 9)
|
||||
side_panel.addWidget(self.open_button)
|
||||
side_panel.addWidget(self.save_button)
|
||||
side_panel.addWidget(self.hcl_button)
|
||||
|
||||
preview_panel = QVBoxLayout()
|
||||
preview_panel.addWidget(self.canvas)
|
||||
|
|
@ -47,15 +75,55 @@ class ImageEditor(QWidget):
|
|||
self.setLayout(main_layout)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def open_image(self):
|
||||
file_name, _ = QFileDialog.getOpenFileName(self, "Open Image File", "", "Image Files (*.jpg *.jpeg *.png)")
|
||||
if file_name:
|
||||
self.image = cv2.imread(file_name)
|
||||
self.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)
|
||||
|
||||
self.image_as_hsv = cv2.cvtColor(self.image, cv2.COLOR_RGB2HSV).astype('float32')
|
||||
|
||||
self.display_image(self.image, first_load=True)
|
||||
|
||||
def display_image(self, image, first_load = False):
|
||||
height, width, channel = image.shape
|
||||
bytes_per_line = 3 * width
|
||||
|
||||
q_image = QPixmap.fromImage(QImage(image.data, width, height, bytes_per_line, QImage.Format.Format_RGB888))
|
||||
self.canvas.updatePixmap(QImage(image.data, width, height, bytes_per_line, QImage.Format.Format_RGB888))
|
||||
|
||||
if first_load:
|
||||
self.canvas.reset()
|
||||
|
||||
def save_image(self):
|
||||
file_name, _ = QFileDialog.getSaveFileName(self, "Save Image File", "", "Image Files (*.jpg *.png)")
|
||||
if file_name and hasattr(self, 'image'):
|
||||
cv2.imwrite(file_name, cv2.cvtColor(self.image, cv2.COLOR_RGB2BGR))
|
||||
|
||||
def open_hcl(self):
|
||||
self.dialog = HueDialog(self.image_as_hsv)
|
||||
self.dialog.result_ready().connect(self.display_image)
|
||||
self.dialog.exec()
|
||||
|
||||
|
||||
def update_image(self):
|
||||
print(self.saturation_slider.value(), self.contrast_slider.value(), self.brightness_slider.value())
|
||||
if self.image_as_hsv is not None:
|
||||
if self.worker is None:
|
||||
self.setup_worker()
|
||||
|
||||
values = {
|
||||
'saturation': self.saturation_slider.value() / 100.0,
|
||||
'contrast': self.contrast_slider.value() / 100.0,
|
||||
'brightness': self.brightness_slider.value()
|
||||
}
|
||||
self.update_values_signal.emit(values) # Emit new values to the worker
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user