creatumlibre.ui.dialogs.color_adjustment_dialog
1from PyQt6.QtCore import Qt 2from PyQt6.QtWidgets import ( 3 QDialog, 4 QHBoxLayout, 5 QLabel, 6 QPushButton, 7 QSlider, 8 QVBoxLayout, 9) 10 11from creatumlibre.graphics.filters.enhancers import ( 12 adjust_brightness, 13 adjust_contrast, 14 adjust_rgb, 15 adjust_saturation, 16) 17from creatumlibre.ui.dialogs.color_adjustment_dialog_css import ( 18 BTN_APPLY, 19 BTN_CANCEL, 20 MAIN_DIALOG, 21) 22 23 24class ColorAdjustmentDialog(QDialog): 25 """Dialog for adjusting brightness, saturation, contrast, and color balance.""" 26 27 def __init__(self, parent, tab_manager): 28 super().__init__(parent) 29 self.tab_manager = tab_manager # Reference to the tab manager 30 self.apply_button = QPushButton("Apply") 31 self.cancel_button = QPushButton("Cancel") 32 self.sliders = {} 33 self.labels = {} 34 self.base_image_snapshot = None # unchanged image 35 36 self._init_layout() 37 38 def _init_layout(self): 39 40 self.setWindowTitle("Color Adjustments") 41 self.setFixedSize(300, 480) 42 self.setStyleSheet(MAIN_DIALOG) 43 44 button_layout = QHBoxLayout() 45 46 self.apply_button.setStyleSheet(BTN_APPLY) 47 self.apply_button.clicked.connect(self.apply_changes) 48 button_layout.addWidget(self.apply_button) 49 50 self.cancel_button.setStyleSheet(BTN_CANCEL) 51 self.cancel_button.clicked.connect(self.cancel_changes) 52 button_layout.addWidget(self.cancel_button) 53 54 self.slider_settings = { 55 "Brightness": { 56 "default": 0, 57 "range": (-100, 100), 58 "tick_interval": 10, 59 "scale_function": lambda value: 1 + (value / 100 * 1), 60 "apply_function": adjust_brightness, 61 }, 62 "Saturation": { 63 "default": 0, 64 "range": (-100, 100), 65 "tick_interval": 10, 66 "scale_function": lambda value: 1 + (value / 100 * 2), 67 "apply_function": adjust_saturation, 68 }, 69 "Contrast": { 70 "default": 0, 71 "range": (-100, 100), 72 "tick_interval": 10, 73 "scale_function": lambda value: 1 + (value / 100 * 1.0), 74 "apply_function": adjust_contrast, 75 }, 76 "Red": { 77 "default": 0, 78 "range": (-100, 100), 79 "tick_interval": 10, 80 "scale_function": lambda value: value / 100, 81 "apply_function": lambda img, value: adjust_rgb(img, value, "Red"), 82 }, 83 "Green": { 84 "default": 0, 85 "range": (-100, 100), 86 "tick_interval": 10, 87 "scale_function": lambda value: value / 100, 88 "apply_function": lambda img, value: adjust_rgb(img, value, "Green"), 89 }, 90 "Blue": { 91 "default": 0, 92 "range": (-100, 100), 93 "tick_interval": 10, 94 "scale_function": lambda value: value / 100, 95 "apply_function": lambda img, value: adjust_rgb(img, value, "Blue"), 96 }, 97 } 98 99 dialog_layout = QVBoxLayout(self) 100 101 for adjustment, config in self.slider_settings.items(): 102 label = QLabel( 103 adjustment 104 ) # This will be replaced with translation support later 105 self.labels[adjustment] = label 106 slider = QSlider(Qt.Orientation.Horizontal) 107 slider.setTickPosition(QSlider.TickPosition.TicksBelow) 108 slider.setTickInterval(config["tick_interval"]) 109 slider.setRange(*config["range"]) # Uses dict values 110 slider.setValue(config["default"]) 111 self.labels[adjustment].setText(f"{adjustment} [{slider.value():.2f}]") 112 113 slider.valueChanged.connect(self.apply_all_adjustments) 114 115 dialog_layout.addWidget(label) 116 dialog_layout.addWidget(slider) 117 self.sliders[adjustment] = slider 118 119 dialog_layout.addLayout(button_layout) # add buttons 120 self.setLayout(dialog_layout) 121 122 def showEvent(self, event): 123 """Runs logic when the dialog is shown.""" 124 super().showEvent(event) 125 126 promoted = self.get_promoted_object() 127 if promoted: 128 self.base_image_snapshot = promoted.get_image().copy() 129 self.apply_all_adjustments() 130 131 return # Already has a promoted object 132 133 # Promote base image if no selection is active 134 active_tab = self.tab_manager.get_active_tab() 135 if not active_tab: 136 return 137 138 manager = active_tab.get("manager") 139 base_object = manager.get_base_object() 140 141 if base_object: 142 # Promote full image as fallback 143 height, width = base_object.get_image().shape[:2] 144 fake_selection = (0, 0, width, height) 145 146 # Make RegionManager believe the full image is selected 147 base_object.region_manager.set_bounding_rect(*fake_selection) 148 base_object.region_manager.initialize_mask(base_object.get_image().shape) 149 150 new_obj = base_object.extract_selection_as_new_image() 151 if new_obj: 152 manager.add_object(new_obj) 153 self.base_image_snapshot = new_obj.get_image().copy() 154 self.apply_all_adjustments() 155 self.tab_manager.refresh_tab_display( 156 self.tab_manager.get_active_tab_index() 157 ) 158 159 def apply_all_adjustments(self): 160 promoted = self.get_promoted_object() 161 if promoted is None: 162 return 163 164 image = self.base_image_snapshot.copy() # Work on a copy for preview 165 166 for name, config in self.slider_settings.items(): 167 value = self.sliders[name].value() 168 scale = config["scale_function"](value) 169 image = config["apply_function"](image, scale) 170 mapped_value = config["scale_function"](value) 171 self.labels[name].setText(f"{name} [{mapped_value:.2f}]") 172 173 promoted.set_image(image) 174 175 self.tab_manager.refresh_tab_display(self.tab_manager.get_active_tab_index()) 176 177 def get_promoted_object(self): 178 """Finds the promoted object in the current tab's object manager.""" 179 active_tab = self.tab_manager.get_active_tab() 180 if not active_tab: 181 return None 182 183 object_manager = active_tab.get("manager") 184 if not object_manager: 185 return None 186 187 for obj in object_manager.object_list: 188 if getattr(obj, "is_promoted", False): 189 return obj 190 return None 191 192 def apply_changes(self): 193 obj_manager = self.tab_manager.get_active_tab().get("manager") 194 obj_manager.merge_selection() 195 self.tab_manager.refresh_tab_display(self.tab_manager.get_active_tab_index()) 196 self.accept() 197 198 def cancel_changes(self): 199 """Cancel adjustments, delete the promoted layer""" 200 promoted = self.get_promoted_object() 201 if promoted: 202 active_tab = self.tab_manager.get_active_tab() 203 object_manager = active_tab.get("manager") 204 object_manager.object_list.remove(promoted) 205 self.tab_manager.refresh_tab_display( 206 self.tab_manager.get_active_tab_index() 207 ) 208 self.reject()
class
ColorAdjustmentDialog(PyQt6.QtWidgets.QDialog):
25class ColorAdjustmentDialog(QDialog): 26 """Dialog for adjusting brightness, saturation, contrast, and color balance.""" 27 28 def __init__(self, parent, tab_manager): 29 super().__init__(parent) 30 self.tab_manager = tab_manager # Reference to the tab manager 31 self.apply_button = QPushButton("Apply") 32 self.cancel_button = QPushButton("Cancel") 33 self.sliders = {} 34 self.labels = {} 35 self.base_image_snapshot = None # unchanged image 36 37 self._init_layout() 38 39 def _init_layout(self): 40 41 self.setWindowTitle("Color Adjustments") 42 self.setFixedSize(300, 480) 43 self.setStyleSheet(MAIN_DIALOG) 44 45 button_layout = QHBoxLayout() 46 47 self.apply_button.setStyleSheet(BTN_APPLY) 48 self.apply_button.clicked.connect(self.apply_changes) 49 button_layout.addWidget(self.apply_button) 50 51 self.cancel_button.setStyleSheet(BTN_CANCEL) 52 self.cancel_button.clicked.connect(self.cancel_changes) 53 button_layout.addWidget(self.cancel_button) 54 55 self.slider_settings = { 56 "Brightness": { 57 "default": 0, 58 "range": (-100, 100), 59 "tick_interval": 10, 60 "scale_function": lambda value: 1 + (value / 100 * 1), 61 "apply_function": adjust_brightness, 62 }, 63 "Saturation": { 64 "default": 0, 65 "range": (-100, 100), 66 "tick_interval": 10, 67 "scale_function": lambda value: 1 + (value / 100 * 2), 68 "apply_function": adjust_saturation, 69 }, 70 "Contrast": { 71 "default": 0, 72 "range": (-100, 100), 73 "tick_interval": 10, 74 "scale_function": lambda value: 1 + (value / 100 * 1.0), 75 "apply_function": adjust_contrast, 76 }, 77 "Red": { 78 "default": 0, 79 "range": (-100, 100), 80 "tick_interval": 10, 81 "scale_function": lambda value: value / 100, 82 "apply_function": lambda img, value: adjust_rgb(img, value, "Red"), 83 }, 84 "Green": { 85 "default": 0, 86 "range": (-100, 100), 87 "tick_interval": 10, 88 "scale_function": lambda value: value / 100, 89 "apply_function": lambda img, value: adjust_rgb(img, value, "Green"), 90 }, 91 "Blue": { 92 "default": 0, 93 "range": (-100, 100), 94 "tick_interval": 10, 95 "scale_function": lambda value: value / 100, 96 "apply_function": lambda img, value: adjust_rgb(img, value, "Blue"), 97 }, 98 } 99 100 dialog_layout = QVBoxLayout(self) 101 102 for adjustment, config in self.slider_settings.items(): 103 label = QLabel( 104 adjustment 105 ) # This will be replaced with translation support later 106 self.labels[adjustment] = label 107 slider = QSlider(Qt.Orientation.Horizontal) 108 slider.setTickPosition(QSlider.TickPosition.TicksBelow) 109 slider.setTickInterval(config["tick_interval"]) 110 slider.setRange(*config["range"]) # Uses dict values 111 slider.setValue(config["default"]) 112 self.labels[adjustment].setText(f"{adjustment} [{slider.value():.2f}]") 113 114 slider.valueChanged.connect(self.apply_all_adjustments) 115 116 dialog_layout.addWidget(label) 117 dialog_layout.addWidget(slider) 118 self.sliders[adjustment] = slider 119 120 dialog_layout.addLayout(button_layout) # add buttons 121 self.setLayout(dialog_layout) 122 123 def showEvent(self, event): 124 """Runs logic when the dialog is shown.""" 125 super().showEvent(event) 126 127 promoted = self.get_promoted_object() 128 if promoted: 129 self.base_image_snapshot = promoted.get_image().copy() 130 self.apply_all_adjustments() 131 132 return # Already has a promoted object 133 134 # Promote base image if no selection is active 135 active_tab = self.tab_manager.get_active_tab() 136 if not active_tab: 137 return 138 139 manager = active_tab.get("manager") 140 base_object = manager.get_base_object() 141 142 if base_object: 143 # Promote full image as fallback 144 height, width = base_object.get_image().shape[:2] 145 fake_selection = (0, 0, width, height) 146 147 # Make RegionManager believe the full image is selected 148 base_object.region_manager.set_bounding_rect(*fake_selection) 149 base_object.region_manager.initialize_mask(base_object.get_image().shape) 150 151 new_obj = base_object.extract_selection_as_new_image() 152 if new_obj: 153 manager.add_object(new_obj) 154 self.base_image_snapshot = new_obj.get_image().copy() 155 self.apply_all_adjustments() 156 self.tab_manager.refresh_tab_display( 157 self.tab_manager.get_active_tab_index() 158 ) 159 160 def apply_all_adjustments(self): 161 promoted = self.get_promoted_object() 162 if promoted is None: 163 return 164 165 image = self.base_image_snapshot.copy() # Work on a copy for preview 166 167 for name, config in self.slider_settings.items(): 168 value = self.sliders[name].value() 169 scale = config["scale_function"](value) 170 image = config["apply_function"](image, scale) 171 mapped_value = config["scale_function"](value) 172 self.labels[name].setText(f"{name} [{mapped_value:.2f}]") 173 174 promoted.set_image(image) 175 176 self.tab_manager.refresh_tab_display(self.tab_manager.get_active_tab_index()) 177 178 def get_promoted_object(self): 179 """Finds the promoted object in the current tab's object manager.""" 180 active_tab = self.tab_manager.get_active_tab() 181 if not active_tab: 182 return None 183 184 object_manager = active_tab.get("manager") 185 if not object_manager: 186 return None 187 188 for obj in object_manager.object_list: 189 if getattr(obj, "is_promoted", False): 190 return obj 191 return None 192 193 def apply_changes(self): 194 obj_manager = self.tab_manager.get_active_tab().get("manager") 195 obj_manager.merge_selection() 196 self.tab_manager.refresh_tab_display(self.tab_manager.get_active_tab_index()) 197 self.accept() 198 199 def cancel_changes(self): 200 """Cancel adjustments, delete the promoted layer""" 201 promoted = self.get_promoted_object() 202 if promoted: 203 active_tab = self.tab_manager.get_active_tab() 204 object_manager = active_tab.get("manager") 205 object_manager.object_list.remove(promoted) 206 self.tab_manager.refresh_tab_display( 207 self.tab_manager.get_active_tab_index() 208 ) 209 self.reject()
Dialog for adjusting brightness, saturation, contrast, and color balance.
ColorAdjustmentDialog(parent, tab_manager)
28 def __init__(self, parent, tab_manager): 29 super().__init__(parent) 30 self.tab_manager = tab_manager # Reference to the tab manager 31 self.apply_button = QPushButton("Apply") 32 self.cancel_button = QPushButton("Cancel") 33 self.sliders = {} 34 self.labels = {} 35 self.base_image_snapshot = None # unchanged image 36 37 self._init_layout()
def
showEvent(self, event):
123 def showEvent(self, event): 124 """Runs logic when the dialog is shown.""" 125 super().showEvent(event) 126 127 promoted = self.get_promoted_object() 128 if promoted: 129 self.base_image_snapshot = promoted.get_image().copy() 130 self.apply_all_adjustments() 131 132 return # Already has a promoted object 133 134 # Promote base image if no selection is active 135 active_tab = self.tab_manager.get_active_tab() 136 if not active_tab: 137 return 138 139 manager = active_tab.get("manager") 140 base_object = manager.get_base_object() 141 142 if base_object: 143 # Promote full image as fallback 144 height, width = base_object.get_image().shape[:2] 145 fake_selection = (0, 0, width, height) 146 147 # Make RegionManager believe the full image is selected 148 base_object.region_manager.set_bounding_rect(*fake_selection) 149 base_object.region_manager.initialize_mask(base_object.get_image().shape) 150 151 new_obj = base_object.extract_selection_as_new_image() 152 if new_obj: 153 manager.add_object(new_obj) 154 self.base_image_snapshot = new_obj.get_image().copy() 155 self.apply_all_adjustments() 156 self.tab_manager.refresh_tab_display( 157 self.tab_manager.get_active_tab_index() 158 )
Runs logic when the dialog is shown.
def
apply_all_adjustments(self):
160 def apply_all_adjustments(self): 161 promoted = self.get_promoted_object() 162 if promoted is None: 163 return 164 165 image = self.base_image_snapshot.copy() # Work on a copy for preview 166 167 for name, config in self.slider_settings.items(): 168 value = self.sliders[name].value() 169 scale = config["scale_function"](value) 170 image = config["apply_function"](image, scale) 171 mapped_value = config["scale_function"](value) 172 self.labels[name].setText(f"{name} [{mapped_value:.2f}]") 173 174 promoted.set_image(image) 175 176 self.tab_manager.refresh_tab_display(self.tab_manager.get_active_tab_index())
def
get_promoted_object(self):
178 def get_promoted_object(self): 179 """Finds the promoted object in the current tab's object manager.""" 180 active_tab = self.tab_manager.get_active_tab() 181 if not active_tab: 182 return None 183 184 object_manager = active_tab.get("manager") 185 if not object_manager: 186 return None 187 188 for obj in object_manager.object_list: 189 if getattr(obj, "is_promoted", False): 190 return obj 191 return None
Finds the promoted object in the current tab's object manager.
def
cancel_changes(self):
199 def cancel_changes(self): 200 """Cancel adjustments, delete the promoted layer""" 201 promoted = self.get_promoted_object() 202 if promoted: 203 active_tab = self.tab_manager.get_active_tab() 204 object_manager = active_tab.get("manager") 205 object_manager.object_list.remove(promoted) 206 self.tab_manager.refresh_tab_display( 207 self.tab_manager.get_active_tab_index() 208 ) 209 self.reject()
Cancel adjustments, delete the promoted layer