add all dialogs and add setup

This commit is contained in:
Dariusz Majnert 2024-06-18 03:47:47 +02:00
parent cc0ee4dde1
commit 382e5bfd9e
20 changed files with 489 additions and 98 deletions

View File

@ -1,8 +0,0 @@
from .HCLDialog import HCLDialog
from .ResizeDialog import ResizeDialog
DIALOGS = [
HCLDialog,
ResizeDialog
]

View File

@ -1,5 +1,4 @@
from PyQt6.QtWidgets import QFileDialog, QWidget, QToolBar, QVBoxLayout, QPushButton
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QGroupBox
from PyQt6.QtCore import pyqtSignal
import numpy as np
@ -12,32 +11,46 @@ class DialogsPanel(QWidget):
def __init__(self, image_manager):
super().__init__()
self.mgr = image_manager
self.image_mgr = image_manager
self.image_mgr.on_close.connect(lambda : self.buttons_set_enabled(False))
self.image_mgr.on_open.connect(lambda : self.buttons_set_enabled(True))
self.dialog_buttons = []
main_layout = QVBoxLayout()
self.setLayout(main_layout)
operations_group_box = QGroupBox("Operations")
layout = QVBoxLayout()
self.setLayout(layout)
layout.setContentsMargins(75,25,75,25)
for DIALOG in DIALOGS:
btn = QPushButton(DIALOG.dialog_name(), self)
btn.setProperty(DIALOG_PROPERTY, DIALOG)
btn.clicked.connect(self.open_dialog)
btn.setEnabled(False)
self.dialog_buttons.append(btn)
layout.addWidget(btn)
operations_group_box.setLayout(layout)
main_layout.addWidget(operations_group_box)
def buttons_set_enabled(self, state):
for btn in self.dialog_buttons:
btn.setEnabled(state)
def open_dialog(self):
dialog_factory = self.sender().property(DIALOG_PROPERTY)
self.dialog = dialog_factory(self.mgr.image())
self.dialog = dialog_factory(self.image_mgr.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.image_mgr.mgr.update(self.dialog.last_processed)
self.dialog = None
def on_rejected(self):
self.mgr.refresh()
self.image_mgr.mgr.refresh()

View File

@ -18,7 +18,7 @@ class ImageCanvas(QGraphicsView):
self.setDragMode(self.DragMode.ScrollHandDrag)
self.setTransformationAnchor(self.ViewportAnchor.AnchorUnderMouse)
def wheelEvent(self, event: QWheelEvent):
def wheelEvent(self, event):
zoom_in_factor = 1.25
zoom_out_factor = 1.0 / zoom_in_factor
if event.angleDelta().y() > 0:
@ -27,7 +27,7 @@ class ImageCanvas(QGraphicsView):
self.scale(zoom_out_factor, zoom_out_factor)
def updatePixmap(self, image: QImage):
def updatePixmap(self, image):
pixmap = QPixmap.fromImage(image)
self._pixmapItem.setPixmap(pixmap)
if self.empty:

View File

@ -1,13 +1,7 @@
from PyQt6.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QApplication
import sys
from PyQt6.QtWidgets import QApplication, QLabel, QVBoxLayout, QHBoxLayout, QPushButton, QWidget, QFileDialog, QSlider, QLineEdit
from PyQt6.QtGui import QPixmap, QImage, QColor, QPainter, QPen
from PyQt6.QtCore import Qt, QPoint, QThread
import cv2
import numpy as np
from ImageCanvas import ImageCanvas
from ImageProcessingWorker import ImageProcessingWorker
from ImageManager import ImageManager
from ImageManagePanel import ImageManagePanel
from DialogsPanel import DialogsPanel
@ -25,7 +19,7 @@ class ImageEditor(QWidget):
self.img_manager.on_update.connect(self.display_image)
self.img_manager.on_close.connect(lambda : self.canvas.clear())
self.dialogs_panel = DialogsPanel(self.img_manager.mgr)
self.dialogs_panel = DialogsPanel(self.img_manager)
self.dialogs_panel.result_ready.connect(self.display_image)
@ -39,27 +33,36 @@ class ImageEditor(QWidget):
preview_panel = QVBoxLayout()
preview_panel.addWidget(self.canvas)
main_layout.addLayout(side_panel,2)
main_layout.addLayout(preview_panel, 3)
self.setLayout(main_layout)
def display_image(self, image, first_load = False):
self.setAcceptDrops(True)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.acceptProposedAction()
def dropEvent(self, event):
files = [url.toLocalFile() for url in event.mimeData().urls()]
if files:
self.img_manager.open(files[0])
def display_image(self, image):
height, width, channel = image.shape
bytes_per_line = 3 * width
self.canvas.updatePixmap(QImage(image.data, width, height, bytes_per_line, QImage.Format.Format_RGB888))
if first_load:
self.canvas.reset()
if __name__ == "__main__":
def main():
app = QApplication(sys.argv)
editor = ImageEditor()
editor.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()

View File

@ -1,42 +1,48 @@
from PyQt6.QtWidgets import QFileDialog, QWidget, QToolBar, QHBoxLayout, QPushButton
from PyQt6.QtWidgets import QFileDialog, QWidget, QHBoxLayout, QPushButton, QMessageBox, QGroupBox
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()
on_open = pyqtSignal()
def __init__(self):
super().__init__()
self.mgr = ImageManager(self._on_update)
layout = QHBoxLayout()
self.setLayout(layout)
main_layout = QHBoxLayout()
self.setLayout(main_layout)
self.open_button = QPushButton(QIcon.fromTheme("document-open"), "Open", self)
file_group_box = QGroupBox("File")
layout = QHBoxLayout()
self.open_button = QPushButton("Open", self)
layout.addWidget(self.open_button)
self.open_button.clicked.connect(self._open_image)
self.save_button = QPushButton(QIcon("save.png"), "Save", self)
self.save_button = QPushButton("Save", self)
layout.addWidget(self.save_button)
self.save_button.clicked.connect(self._save_image)
self.close_button = QPushButton(QIcon("close.png"), "Close", self)
self.close_button = QPushButton("Close", self)
layout.addWidget(self.close_button)
self.close_button.clicked.connect(self._close_image)
self.undo_button = QPushButton(QIcon("undo.png"), "Undo", self)
self.undo_button = QPushButton("Undo", self)
layout.addWidget(self.undo_button)
self.undo_button.clicked.connect(self._undo)
self.redo_button = QPushButton(QIcon("redo.png"), "Redo", self)
self.redo_button = QPushButton("Redo", self)
layout.addWidget(self.redo_button)
self.redo_button.clicked.connect(self._redo)
file_group_box.setLayout(layout)
main_layout.addWidget(file_group_box)
self._enable_buttons()
def _redo(self):
@ -61,13 +67,21 @@ class ImageManagePanel(QWidget):
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)
file_name, _ = QFileDialog.getOpenFileName(self, "Open Image File", "", "Image Files (*.png *.jpg *.jpeg *.bmp)")
self.open(file_name)
def _save_image(self):
file_name, _ = QFileDialog.getSaveFileName(self, "Save Image File", "", "Image Files (*.jpg *.png)")
try:
file_name, _ = QFileDialog.getSaveFileName(self, "Save Image File", "", "Image Files (*.png *.jpg *.jpeg *.bmp)")
if file_name:
self.mgr.save(file_name)
except Exception as e:
QMessageBox.critical(self, "Error Saving File", f"An error occurred while saving the file:\n{str(e)}")
def open(self, file_name):
try:
if file_name:
self.mgr.open(file_name)
self.on_open.emit()
except Exception as e:
QMessageBox.critical(self, "Error Opening File", f"An error occurred while opening the file:\n{str(e)}")

View File

@ -1,6 +1,5 @@
from collections import deque
import numpy as np
import cv2
class ImageManager():

View File

@ -1,12 +1,11 @@
import numpy as np
import cv2
from PyQt6.QtCore import pyqtSignal, pyqtSlot, QThread
from queue import Queue
import cv2
class ImageProcessingWorker(QThread):
result_ready = pyqtSignal(np.ndarray) # Signal to emit the processed image
update_values = pyqtSignal(dict) # Signal to receive new values
result_ready = pyqtSignal(np.ndarray)
update_values = pyqtSignal(dict)
def __init__(self, image, process_function):
super().__init__()

View File

@ -0,0 +1 @@
from .image_editor import ImageEditor

View File

@ -0,0 +1,54 @@
import numpy as np
import cv2
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QSlider
from PyQt6.QtCore import Qt
from .ImageParameterDialog import ImageParameterDialog
import ImageProcessingWorker
class BrightnessContrastDialog(ImageParameterDialog):
def __init__(self, image):
super().__init__(image, ImageProcessingWorker.RGBImageProcessingWorker)
self.setWindowTitle("Brightness/Contrast Adjustment")
self.layout = QVBoxLayout()
self.brightness_slider = QSlider(Qt.Orientation.Horizontal)
self.brightness_slider.setRange(-127, 127)
self.contrast_slider = QSlider(Qt.Orientation.Horizontal)
self.contrast_slider.setRange(-127, 127)
self.label1 = QLabel("Brightness: 0")
self.label2 = QLabel("Contrast: 0")
self.brightness_slider.valueChanged.connect(lambda value: self.update(self.label1, value, "Brightness"))
self.contrast_slider.valueChanged.connect(lambda value: self.update(self.label2, value, "Contrast"))
self.layout.addWidget(self.label1)
self.layout.addWidget(self.brightness_slider)
self.layout.addWidget(self.label2)
self.layout.addWidget(self.contrast_slider)
self.layout.addWidget(self.button_box)
self.setLayout(self.layout)
def update(self, label, value, slider_name):
label.setText(f"{slider_name}: {value}")
self.send_to_process({
'brightness': self.brightness_slider.value(),
'contrast': self.contrast_slider.value(),
})
def process_image(self, image, values):
brightness = values.get('brightness', 0.0) + 1
contrast = values.get('contrast', 0.0) + 1
c = (259 * (contrast + 255)) / (255 * (259 - contrast))
scaled = c * (image - 128) + 128 + brightness
return np.clip(scaled, 0, 255)
@classmethod
def dialog_name(cls):
return "Brightness-Contrast"

View File

@ -0,0 +1,75 @@
import numpy as np
import cv2
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QSlider, QHBoxLayout, QGroupBox
from PyQt6.QtCore import Qt
from .ImageParameterDialog import ImageParameterDialog
import ImageProcessingWorker
class ColorBalanceDialog(ImageParameterDialog):
def __init__(self, image):
super().__init__(image, ImageProcessingWorker.RGBImageProcessingWorker)
self.setWindowTitle("Color Balance Adjustment")
self.layout = QVBoxLayout()
self.sliders = {}
self.labels = {}
self.channel_names = ['Red', 'Green', 'Blue']
self.tone_names = ['Shadows', 'Midtones', 'Highlights']
for tone in self.tone_names:
tone_box = QGroupBox(tone)
tone_layout = QVBoxLayout()
for channel in self.channel_names:
slider = QSlider(Qt.Orientation.Horizontal)
slider.setRange(-100, 100)
slider.setValue(0)
label = QLabel(f"{channel}: 0")
slider.valueChanged.connect(lambda value, lbl=label, ch=channel, tn=tone: self.update(lbl, value, ch, tn))
self.sliders[(channel, tone)] = slider
self.labels[(channel, tone)] = label
tone_layout.addWidget(label)
tone_layout.addWidget(slider)
tone_box.setLayout(tone_layout)
self.layout.addWidget(tone_box)
self.layout.addWidget(self.button_box)
self.setLayout(self.layout)
def update(self, label, value, channel, tone):
label.setText(f"{channel}: {value}")
self.send_to_process(self.collect_values())
def collect_values(self):
values = {}
for channel in self.channel_names:
for tone in self.tone_names:
values[(channel, tone)] = self.sliders[(channel, tone)].value()
return values
def process_image(self, image, values):
img = image / 255.0
luminance = 0.299 * image[..., 0] + 0.587 * image[..., 1] + 0.114 * image[..., 2]
shadows_mask = luminance < 85
midtones_mask = (luminance >= 85) & (luminance <= 170)
highlights_mask = luminance > 170
for channel_idx, channel in enumerate(self.channel_names):
for tone, mask in zip(self.tone_names, [shadows_mask, midtones_mask, highlights_mask]):
adjustment = values[(channel, tone)]
if adjustment != 0:
factor = (259 * (adjustment + 255)) / (255 * (259 - adjustment))
img[..., channel_idx] = np.where(mask, img[..., channel_idx] * factor, img[..., channel_idx])
img = np.clip(img * 255, 0, 255)
return img
@classmethod
def dialog_name(cls):
return "Color Balance"

View File

@ -0,0 +1,42 @@
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QCheckBox
import cv2
from .ImageParameterDialog import ImageParameterDialog
import ImageProcessingWorker
class FlipDialog(ImageParameterDialog):
def __init__(self, image):
super().__init__(image, ImageProcessingWorker.ImageProcessingWorker)
self.setWindowTitle("Flip Image")
self.layout = QVBoxLayout()
self.horizontal_flip_checkbox = QCheckBox("Flip Horizontally")
self.horizontal_flip_checkbox.stateChanged.connect(self.update)
self.vertical_flip_checkbox = QCheckBox("Flip Vertically")
self.vertical_flip_checkbox.stateChanged.connect(self.update)
self.layout.addWidget(QLabel("Select flip options:"))
self.layout.addWidget(self.horizontal_flip_checkbox)
self.layout.addWidget(self.vertical_flip_checkbox)
self.layout.addWidget(self.button_box)
self.setLayout(self.layout)
def update(self):
self.send_to_process({
'flip_horizontal': self.horizontal_flip_checkbox.isChecked(),
'flip_vertical': self.vertical_flip_checkbox.isChecked()
})
def process_image(self, image, values):
if values['flip_horizontal']:
image = cv2.flip(image, 1)
if values['flip_vertical']:
image = cv2.flip(image, 0)
return image
@classmethod
def dialog_name(cls):
return "Flip Image"

View File

@ -1,19 +1,14 @@
import abc
import ImageProcessingWorker
import numpy as np
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QSlider
from PyQt6.QtCore import Qt
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
import ImageProcessingWorker
from .ImageParameterDialog import ImageParameterDialog
class HCLDialog(ImageParameterDialog):
def __init__(self, hsv_image):
super().__init__(hsv_image, ImageProcessingWorker.HLSImageProcessingWorker)
def __init__(self, image):
super().__init__(image, ImageProcessingWorker.HLSImageProcessingWorker)
self.setWindowTitle("HCL Correction")
self.layout = QVBoxLayout()
@ -27,9 +22,9 @@ class HCLDialog(ImageParameterDialog):
self.lightness_slider.setRange(-100, 100)
self.label1 = QLabel("Hue Value: 0")
self.label2 = QLabel("Chroma Value: 0")
self.label3 = QLabel("Lightness Value: 0")
self.label1 = QLabel("Hue: 0")
self.label2 = QLabel("Chroma: 0")
self.label3 = QLabel("Lightness: 0")
self.hue_slider.valueChanged.connect(lambda value: self.update(self.label1, value, "Hue"))
self.chroma_slider.valueChanged.connect(lambda value: self.update(self.label2, value, "Chroma"))
@ -46,7 +41,7 @@ class HCLDialog(ImageParameterDialog):
self.setLayout(self.layout)
def update(self, label, value, slider_name):
label.setText(f"{slider_name} Value: {value}")
label.setText(f"{slider_name}: {value}")
self.send_to_process({
'hue': self.hue_slider.value(),
'chroma': self.chroma_slider.value() / 100.0,
@ -55,21 +50,21 @@ class HCLDialog(ImageParameterDialog):
def process_image(self,hsv_image, values):
def process_image(self,image, values):
hue = values.get('hue', 0.0) / 2
chroma = values.get('chroma', 0.0)
lightness = values.get('lightness', 0.0)
hsv_image[..., 0] = (hsv_image[..., 0] + hue) % 180
image[..., 0] = (image[..., 0] + hue) % 180
hsv_image[..., 1] += (255 * lightness)
hsv_image[..., 1] = np.clip(hsv_image[..., 1], 0, 255)
image[..., 1] += (255 * lightness)
image[..., 1] = np.clip(image[..., 1], 0, 255)
hsv_image[..., 2] += (255 * chroma)
hsv_image[..., 2] = np.clip(hsv_image[..., 2], 0, 255)
image[..., 2] += (255 * chroma)
image[..., 2] = np.clip(image[..., 2], 0, 255)
return hsv_image
return image
@classmethod
def dialog_name(cls):

View File

@ -1,8 +1,4 @@
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
from PyQt6.QtWidgets import QDialog, QDialogButtonBox
class ImageParameterDialog(QDialog):
@ -34,9 +30,8 @@ class ImageParameterDialog(QDialog):
self.accept()
# @abc.abstractmethod
def process_image(self,image, values):
pass
return image
def result_ready(self):

View File

@ -1,13 +1,10 @@
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 PyQt6.QtWidgets import QLabel, QVBoxLayout, QHBoxLayout, QLineEdit, QCheckBox, QComboBox
from PyQt6.QtGui import QIntValidator
import ImageProcessingWorker
from .ImageParameterDialog import ImageParameterDialog
@ -29,7 +26,6 @@ class ResizeDialog(ImageParameterDialog):
self.setWindowTitle("Resizing")
self.layout = QVBoxLayout()
# Width input
self.width_label = QLabel("Width:")
self.width_field = QLineEdit()
self.width_field.setPlaceholderText("Enter width")
@ -37,7 +33,6 @@ class ResizeDialog(ImageParameterDialog):
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")
@ -45,17 +40,15 @@ class ResizeDialog(ImageParameterDialog):
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)
@ -71,11 +64,12 @@ class ResizeDialog(ImageParameterDialog):
self.setLayout(self.layout)
self.toggle_auto_scale() # Initially disable the width and height fields
self.toggle_auto_scale()
def toggle_auto_scale(self):
if self.auto_scale_checkbox.isChecked():
self.adjust_height()
self.update()
def adjust_height(self):

View File

@ -0,0 +1,87 @@
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QHBoxLayout, QLineEdit, QCheckBox, QSlider
from PyQt6.QtGui import QIntValidator
from PyQt6.QtCore import Qt
from .ImageParameterDialog import ImageParameterDialog
import ImageProcessingWorker
import numpy as np
import cv2
class RotationDialog(ImageParameterDialog):
def __init__(self, image):
super().__init__(image, ImageProcessingWorker.ImageProcessingWorker)
self.setWindowTitle("Rotation")
self.layout = QVBoxLayout()
self.angle_label = QLabel("Angle:")
self.angle_field = QLineEdit()
self.angle_field.setText("0")
self.angle_field.setPlaceholderText("Enter rotation angle")
self.angle_field.setValidator(QIntValidator(-360, 360))
self.angle_field.textEdited.connect(self.angle_text_changed)
self.angle_slider = QSlider(Qt.Orientation.Horizontal)
self.angle_slider.setRange(-360, 360)
self.angle_slider.setTickInterval(90)
self.angle_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
self.angle_slider.valueChanged.connect(self.angle_slider_changed)
self.keep_size_checkbox = QCheckBox("Keep original size")
self.keep_size_checkbox.setChecked(True)
self.keep_size_checkbox.stateChanged.connect(self.update)
input_layout = QHBoxLayout()
input_layout.addWidget(self.angle_label)
input_layout.addWidget(self.angle_field)
self.layout.addLayout(input_layout)
self.layout.addWidget(self.angle_slider)
self.layout.addWidget(self.keep_size_checkbox)
self.layout.addWidget(self.button_box)
self.setLayout(self.layout)
def angle_text_changed(self, text):
if text:
angle = int(text)
self.angle_slider.setValue(angle)
self.update()
def angle_slider_changed(self, value):
self.angle_field.setText(str(value))
self.update()
def update(self):
angle = self.angle_field.text()
if not angle:
self.set_accept_enable(False)
return
self.send_to_process({
'angle': float(angle),
'keep_size': self.keep_size_checkbox.isChecked()
})
def process_image(self, image, values):
angle = values['angle']
keep_size = values['keep_size']
height, width = image.shape[:2]
center = (width // 2, height // 2)
matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
if keep_size:
rotated_image = cv2.warpAffine(image, matrix, (width, height))
else:
cos = np.abs(matrix[0, 0])
sin = np.abs(matrix[0, 1])
new_width = int((height * sin) + (width * cos))
new_height = int((height * cos) + (width * sin))
matrix[0, 2] += (new_width / 2) - center[0]
matrix[1, 2] += (new_height / 2) - center[1]
rotated_image = cv2.warpAffine(image, matrix, (new_width, new_height))
self.set_accept_enable(True)
return rotated_image
@classmethod
def dialog_name(cls):
return "Rotate"

View File

@ -0,0 +1,40 @@
import numpy as np
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QSlider
from PyQt6.QtCore import Qt
from .ImageParameterDialog import ImageParameterDialog
import ImageProcessingWorker
class SaturationDialog(ImageParameterDialog):
def __init__(self, image):
super().__init__(image, ImageProcessingWorker.HSVImageProcessingWorker)
self.setWindowTitle("Saturation Adjustment")
self.layout = QVBoxLayout()
self.saturation_slider = QSlider(Qt.Orientation.Horizontal)
self.saturation_slider.setRange(-100, 100)
self.label = QLabel("Saturation: 0")
self.saturation_slider.valueChanged.connect(lambda value: self.update(self.label, value))
self.layout.addWidget(self.label)
self.layout.addWidget(self.saturation_slider)
self.layout.addWidget(self.button_box)
self.setLayout(self.layout)
def update(self, label, value):
label.setText(f"Saturation: {value}")
self.send_to_process({
'saturation': self.saturation_slider.value() / 100.0
})
def process_image(self, image, values):
saturation = values.get('saturation', 0.0)
image[..., 1] = np.clip(image[..., 1] * (1 + saturation), 0, 255)
return image
@classmethod
def dialog_name(cls):
return "Saturation"

View File

@ -0,0 +1,46 @@
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QSlider
from PyQt6.QtCore import Qt
from .ImageParameterDialog import ImageParameterDialog
import ImageProcessingWorker
import numpy as np
class TemperatureAdjustmentDialog(ImageParameterDialog):
def __init__(self, image):
super().__init__(image, ImageProcessingWorker.RGBImageProcessingWorker)
self.setWindowTitle("Temperature Adjustment")
self.layout = QVBoxLayout()
self.temperature_slider = QSlider(Qt.Orientation.Horizontal)
self.temperature_slider.setRange(-500, 500)
self.temperature_slider.setValue(0)
self.temperature_slider.setTickInterval(10)
self.temperature_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
self.label = QLabel("Temperature: 0")
self.temperature_slider.valueChanged.connect(self.update_temperature)
self.layout.addWidget(self.label)
self.layout.addWidget(self.temperature_slider)
self.layout.addWidget(self.button_box)
self.setLayout(self.layout)
def update_temperature(self, value):
self.label.setText(f"Temperature: {value}")
self.send_to_process({'temperature': value})
def process_image(self, image, values):
temperature = values.get('temperature', 0)
increment = temperature * 0.1
image[..., 2] += increment * -1
image[..., 0] += increment
image = np.clip(image, 0, 255)
return image.astype(np.uint8)
@classmethod
def dialog_name(cls):
return "Temperature Adjustment"

View File

@ -0,0 +1,19 @@
from .HCLDialog import HCLDialog
from .ResizeDialog import ResizeDialog
from .SaturationDialog import SaturationDialog
from .BrightnessContrastDialog import BrightnessContrastDialog
from .ColorBalanceDialog import ColorBalanceDialog
from .TemperatureDialog import TemperatureAdjustmentDialog
from .RotationDialog import RotationDialog
from .FlipDialog import FlipDialog
DIALOGS = [
ColorBalanceDialog,
TemperatureAdjustmentDialog,
HCLDialog,
SaturationDialog,
BrightnessContrastDialog,
ResizeDialog,
RotationDialog,
FlipDialog
]

View File

@ -0,0 +1,23 @@
from setuptools import setup, find_packages
setup(
name="image-editor",
version="1.0.0",
packages=find_packages(),
install_requires=[
"numpy",
"opencv-python",
"PyQt6"
],
entry_points={
'console_scripts': [
'run-image-editor=editor.ImageEditor:main',
],
},
classifiers=[
"Programming Language :: Python :: 3",
"License :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
)