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.

ObjectManager(file_path: str)
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)
object_list
actual_object_index
zoom_factor
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_base_image(self):
33    def get_base_image(self):
34        return self.object_list[0].get_image() if self.object_list else None
def get_active_image(self):
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()
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 get_active_object(self):
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]
def get_base_object(self):
53    def get_base_object(self):
54        return self.object_list[0] if self.object_list else None
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.

def delete_object( self, image_object: creatumlibre.ui.manager.image_handler.ImageHandler):
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.