creatumlibre.ui.manager.object_manager
1# pylint: disable=no-member 2 3import cv2 4from PyQt6.QtGui import QImage, QPixmap 5 6from creatumlibre.graphics.boolean_operations.image_boolean import merge 7from creatumlibre.ui.manager.image_handler import ImageHandler 8from creatumlibre.ui.mode.ui_input_mode import TransformMode 9 10 11class ObjectManager: 12 """Manages rectangular images (with optional masks) to produce one composited picture.""" 13 14 def __init__(self, file_path: str): 15 self.object_list = [] 16 self.actual_object_index = 0 17 self.zoom_factor = 1.0 18 19 self._add_new_image_by_filename(file_path) 20 21 def get_active_object_index(self): 22 """be sure, the index is valid""" 23 if not self.object_list: 24 return None 25 return min(self.actual_object_index, len(self.object_list) - 1) 26 27 def _add_new_image_by_filename(self, file_path): 28 new_np_image = cv2.imread(file_path) 29 image_instance = ImageHandler(new_np_image, (0, 0), False) 30 self.object_list.append(image_instance) 31 32 def get_base_image(self): 33 return self.object_list[0].get_image() if self.object_list else None 34 35 def get_active_image(self): 36 if (idx := self.get_active_object_index()) is None: 37 return None 38 return self.object_list[idx].get_image() 39 40 def get_promoted_object(self): 41 """get the promoted image by flag""" 42 for image_object in self.object_list: 43 if image_object.is_promoted: 44 return image_object 45 return None 46 47 def get_active_object(self): 48 if (idx := self.get_active_object_index()) is None: 49 return None 50 return self.object_list[idx] 51 52 def get_base_object(self): 53 return self.object_list[0] if self.object_list else None 54 55 def show_resulting_image(self) -> QPixmap: 56 """Composites all objects into a final image and returns it as QPixmap.""" 57 if not self.object_list: 58 return QPixmap() 59 60 base_image = self.object_list[0].copy() 61 62 for image_obj in self.object_list[1:]: 63 overlay_obj = image_obj.copy() 64 if image_obj.is_promoted: 65 overlay_obj.draw_selection_frame(TransformMode.NONE, self.zoom_factor) 66 if image_obj.is_selected: 67 overlay_obj.draw_selection_frame(TransformMode.SCALE, self.zoom_factor) 68 merge(overlay_obj, base_image) 69 70 return self._to_qpixmap(base_image.get_image()) 71 72 def _to_qpixmap(self, image) -> QPixmap: 73 """Converts cv2 image (BGR) to QPixmap with zoom applied.""" 74 zoomed = cv2.resize(image, (0, 0), fx=self.zoom_factor, fy=self.zoom_factor) 75 rgb = cv2.cvtColor(zoomed, cv2.COLOR_BGR2RGB) 76 height, width, channel = rgb.shape 77 q_image = QImage( 78 rgb.data, width, height, channel * width, QImage.Format.Format_RGB888 79 ) 80 return QPixmap.fromImage(q_image) 81 82 def delete_object(self, image_object: ImageHandler): 83 """Deletes object from list by value""" 84 if image_object in self.object_list: 85 index = self.object_list.index(image_object) 86 self.object_list.remove(image_object) 87 self.actual_object_index = max(0, index - 1) 88 89 def add_object(self, image_handler: ImageHandler, position: int | None = None): 90 """Adds a new object at the specified index or end.""" 91 insert_at = self.actual_object_index + 1 if position is None else position 92 self.object_list.insert(insert_at, image_handler) 93 self.actual_object_index = insert_at 94 95 def select_object(self, index: int): 96 """Sets the active object to manipulate.""" 97 if 0 <= index < len(self.object_list): 98 self.actual_object_index = index 99 100 def set_selected_object_by_click(self, position: tuple[int, int]) -> bool: 101 """scan all objects from top to bottom it is hit""" 102 for image_object in reversed(self.object_list[1:]): 103 if image_object.contains_point(position): 104 image_object.is_selected = True 105 image_object.position_before_drag = image_object.position 106 return True 107 return False 108 109 def update_selected_position(self, dx: int, dy: int): 110 """update all selected posiitons""" 111 for image_object in reversed(self.object_list[1:]): 112 if image_object.is_selected: 113 print(f"dx: {dx}, dy: {dy}") 114 image_object.set_position( 115 ( 116 image_object.position_before_drag[0] + dx, 117 image_object.position_before_drag[1] + dy, 118 ) 119 ) 120 121 def clear_selection(self): 122 """release all selections i.e: by Esc""" 123 for image_object in reversed(self.object_list): 124 image_object.is_selected = False 125 126 def copy_promoted(self, is_cut: bool): 127 """create a new object from the promoted image 128 param is_cut: if is cut: erase the underlying image 129 """ 130 new_object = self.get_promoted_object().copy() 131 self.add_object(new_object) 132 print(is_cut) 133 134 def delete_promoted(self): 135 """create a new object from the promoted image 136 param is_cut: if is cut: erase the underlying image 137 """ 138 self.delete_object(self.get_promoted_object()) 139 140 def get_tab_pixmap(self) -> QPixmap: 141 """Convenience method for tab manager to retrieve full composition.""" 142 return self.show_resulting_image() 143 144 def merge_selection(self): 145 """Finds the promoted object and merges it into the layer below.""" 146 # Find promoted ImageHandler 147 promoted = next( 148 (obj for obj in self.object_list if getattr(obj, "is_promoted", False)), 149 None, 150 ) 151 if not promoted: 152 return 153 154 index = self.object_list.index(promoted) 155 if index == 0: 156 # No underlying layer to merge into 157 return 158 159 target = self.object_list[index - 1] 160 161 merge(from_obj=promoted, to_obj=target) 162 163 # Remove promoted selection from stack 164 self.object_list.remove(promoted)
class
ObjectManager:
12class ObjectManager: 13 """Manages rectangular images (with optional masks) to produce one composited picture.""" 14 15 def __init__(self, file_path: str): 16 self.object_list = [] 17 self.actual_object_index = 0 18 self.zoom_factor = 1.0 19 20 self._add_new_image_by_filename(file_path) 21 22 def get_active_object_index(self): 23 """be sure, the index is valid""" 24 if not self.object_list: 25 return None 26 return min(self.actual_object_index, len(self.object_list) - 1) 27 28 def _add_new_image_by_filename(self, file_path): 29 new_np_image = cv2.imread(file_path) 30 image_instance = ImageHandler(new_np_image, (0, 0), False) 31 self.object_list.append(image_instance) 32 33 def get_base_image(self): 34 return self.object_list[0].get_image() if self.object_list else None 35 36 def get_active_image(self): 37 if (idx := self.get_active_object_index()) is None: 38 return None 39 return self.object_list[idx].get_image() 40 41 def get_promoted_object(self): 42 """get the promoted image by flag""" 43 for image_object in self.object_list: 44 if image_object.is_promoted: 45 return image_object 46 return None 47 48 def get_active_object(self): 49 if (idx := self.get_active_object_index()) is None: 50 return None 51 return self.object_list[idx] 52 53 def get_base_object(self): 54 return self.object_list[0] if self.object_list else None 55 56 def show_resulting_image(self) -> QPixmap: 57 """Composites all objects into a final image and returns it as QPixmap.""" 58 if not self.object_list: 59 return QPixmap() 60 61 base_image = self.object_list[0].copy() 62 63 for image_obj in self.object_list[1:]: 64 overlay_obj = image_obj.copy() 65 if image_obj.is_promoted: 66 overlay_obj.draw_selection_frame(TransformMode.NONE, self.zoom_factor) 67 if image_obj.is_selected: 68 overlay_obj.draw_selection_frame(TransformMode.SCALE, self.zoom_factor) 69 merge(overlay_obj, base_image) 70 71 return self._to_qpixmap(base_image.get_image()) 72 73 def _to_qpixmap(self, image) -> QPixmap: 74 """Converts cv2 image (BGR) to QPixmap with zoom applied.""" 75 zoomed = cv2.resize(image, (0, 0), fx=self.zoom_factor, fy=self.zoom_factor) 76 rgb = cv2.cvtColor(zoomed, cv2.COLOR_BGR2RGB) 77 height, width, channel = rgb.shape 78 q_image = QImage( 79 rgb.data, width, height, channel * width, QImage.Format.Format_RGB888 80 ) 81 return QPixmap.fromImage(q_image) 82 83 def delete_object(self, image_object: ImageHandler): 84 """Deletes object from list by value""" 85 if image_object in self.object_list: 86 index = self.object_list.index(image_object) 87 self.object_list.remove(image_object) 88 self.actual_object_index = max(0, index - 1) 89 90 def add_object(self, image_handler: ImageHandler, position: int | None = None): 91 """Adds a new object at the specified index or end.""" 92 insert_at = self.actual_object_index + 1 if position is None else position 93 self.object_list.insert(insert_at, image_handler) 94 self.actual_object_index = insert_at 95 96 def select_object(self, index: int): 97 """Sets the active object to manipulate.""" 98 if 0 <= index < len(self.object_list): 99 self.actual_object_index = index 100 101 def set_selected_object_by_click(self, position: tuple[int, int]) -> bool: 102 """scan all objects from top to bottom it is hit""" 103 for image_object in reversed(self.object_list[1:]): 104 if image_object.contains_point(position): 105 image_object.is_selected = True 106 image_object.position_before_drag = image_object.position 107 return True 108 return False 109 110 def update_selected_position(self, dx: int, dy: int): 111 """update all selected posiitons""" 112 for image_object in reversed(self.object_list[1:]): 113 if image_object.is_selected: 114 print(f"dx: {dx}, dy: {dy}") 115 image_object.set_position( 116 ( 117 image_object.position_before_drag[0] + dx, 118 image_object.position_before_drag[1] + dy, 119 ) 120 ) 121 122 def clear_selection(self): 123 """release all selections i.e: by Esc""" 124 for image_object in reversed(self.object_list): 125 image_object.is_selected = False 126 127 def copy_promoted(self, is_cut: bool): 128 """create a new object from the promoted image 129 param is_cut: if is cut: erase the underlying image 130 """ 131 new_object = self.get_promoted_object().copy() 132 self.add_object(new_object) 133 print(is_cut) 134 135 def delete_promoted(self): 136 """create a new object from the promoted image 137 param is_cut: if is cut: erase the underlying image 138 """ 139 self.delete_object(self.get_promoted_object()) 140 141 def get_tab_pixmap(self) -> QPixmap: 142 """Convenience method for tab manager to retrieve full composition.""" 143 return self.show_resulting_image() 144 145 def merge_selection(self): 146 """Finds the promoted object and merges it into the layer below.""" 147 # Find promoted ImageHandler 148 promoted = next( 149 (obj for obj in self.object_list if getattr(obj, "is_promoted", False)), 150 None, 151 ) 152 if not promoted: 153 return 154 155 index = self.object_list.index(promoted) 156 if index == 0: 157 # No underlying layer to merge into 158 return 159 160 target = self.object_list[index - 1] 161 162 merge(from_obj=promoted, to_obj=target) 163 164 # Remove promoted selection from stack 165 self.object_list.remove(promoted)
Manages rectangular images (with optional masks) to produce one composited picture.
def
get_active_object_index(self):
22 def get_active_object_index(self): 23 """be sure, the index is valid""" 24 if not self.object_list: 25 return None 26 return min(self.actual_object_index, len(self.object_list) - 1)
be sure, the index is valid
def
get_promoted_object(self):
41 def get_promoted_object(self): 42 """get the promoted image by flag""" 43 for image_object in self.object_list: 44 if image_object.is_promoted: 45 return image_object 46 return None
get the promoted image by flag
def
show_resulting_image(self) -> PyQt6.QtGui.QPixmap:
56 def show_resulting_image(self) -> QPixmap: 57 """Composites all objects into a final image and returns it as QPixmap.""" 58 if not self.object_list: 59 return QPixmap() 60 61 base_image = self.object_list[0].copy() 62 63 for image_obj in self.object_list[1:]: 64 overlay_obj = image_obj.copy() 65 if image_obj.is_promoted: 66 overlay_obj.draw_selection_frame(TransformMode.NONE, self.zoom_factor) 67 if image_obj.is_selected: 68 overlay_obj.draw_selection_frame(TransformMode.SCALE, self.zoom_factor) 69 merge(overlay_obj, base_image) 70 71 return self._to_qpixmap(base_image.get_image())
Composites all objects into a final image and returns it as QPixmap.
83 def delete_object(self, image_object: ImageHandler): 84 """Deletes object from list by value""" 85 if image_object in self.object_list: 86 index = self.object_list.index(image_object) 87 self.object_list.remove(image_object) 88 self.actual_object_index = max(0, index - 1)
Deletes object from list by value
def
add_object( self, image_handler: creatumlibre.ui.manager.image_handler.ImageHandler, position: int | None = None):
90 def add_object(self, image_handler: ImageHandler, position: int | None = None): 91 """Adds a new object at the specified index or end.""" 92 insert_at = self.actual_object_index + 1 if position is None else position 93 self.object_list.insert(insert_at, image_handler) 94 self.actual_object_index = insert_at
Adds a new object at the specified index or end.
def
select_object(self, index: int):
96 def select_object(self, index: int): 97 """Sets the active object to manipulate.""" 98 if 0 <= index < len(self.object_list): 99 self.actual_object_index = index
Sets the active object to manipulate.
def
set_selected_object_by_click(self, position: tuple[int, int]) -> bool:
101 def set_selected_object_by_click(self, position: tuple[int, int]) -> bool: 102 """scan all objects from top to bottom it is hit""" 103 for image_object in reversed(self.object_list[1:]): 104 if image_object.contains_point(position): 105 image_object.is_selected = True 106 image_object.position_before_drag = image_object.position 107 return True 108 return False
scan all objects from top to bottom it is hit
def
update_selected_position(self, dx: int, dy: int):
110 def update_selected_position(self, dx: int, dy: int): 111 """update all selected posiitons""" 112 for image_object in reversed(self.object_list[1:]): 113 if image_object.is_selected: 114 print(f"dx: {dx}, dy: {dy}") 115 image_object.set_position( 116 ( 117 image_object.position_before_drag[0] + dx, 118 image_object.position_before_drag[1] + dy, 119 ) 120 )
update all selected posiitons
def
clear_selection(self):
122 def clear_selection(self): 123 """release all selections i.e: by Esc""" 124 for image_object in reversed(self.object_list): 125 image_object.is_selected = False
release all selections i.e: by Esc
def
copy_promoted(self, is_cut: bool):
127 def copy_promoted(self, is_cut: bool): 128 """create a new object from the promoted image 129 param is_cut: if is cut: erase the underlying image 130 """ 131 new_object = self.get_promoted_object().copy() 132 self.add_object(new_object) 133 print(is_cut)
create a new object from the promoted image param is_cut: if is cut: erase the underlying image
def
delete_promoted(self):
135 def delete_promoted(self): 136 """create a new object from the promoted image 137 param is_cut: if is cut: erase the underlying image 138 """ 139 self.delete_object(self.get_promoted_object())
create a new object from the promoted image param is_cut: if is cut: erase the underlying image
def
get_tab_pixmap(self) -> PyQt6.QtGui.QPixmap:
141 def get_tab_pixmap(self) -> QPixmap: 142 """Convenience method for tab manager to retrieve full composition.""" 143 return self.show_resulting_image()
Convenience method for tab manager to retrieve full composition.
def
merge_selection(self):
145 def merge_selection(self): 146 """Finds the promoted object and merges it into the layer below.""" 147 # Find promoted ImageHandler 148 promoted = next( 149 (obj for obj in self.object_list if getattr(obj, "is_promoted", False)), 150 None, 151 ) 152 if not promoted: 153 return 154 155 index = self.object_list.index(promoted) 156 if index == 0: 157 # No underlying layer to merge into 158 return 159 160 target = self.object_list[index - 1] 161 162 merge(from_obj=promoted, to_obj=target) 163 164 # Remove promoted selection from stack 165 self.object_list.remove(promoted)
Finds the promoted object and merges it into the layer below.