| 9 | |
| 10 | |
| 11 | class ImageLabel(tk.Label): |
| 12 | def __init__(self, master, image_path=None, mode="cover", width=100, height=100, *args, **kwargs): |
| 13 | """ |
| 14 | mode: |
| 15 | - "fit" -> Keeps aspect ratio, fits inside label |
| 16 | - "cover" -> Covers label fully, cropping excess |
| 17 | """ |
| 18 | super().__init__(master, width=width, height=height, *args, **kwargs) |
| 19 | self.parent = master |
| 20 | self.image_path = image_path |
| 21 | self.mode = mode |
| 22 | self.original_image = None |
| 23 | self.photo = None |
| 24 | self.resize_job = None # Debounce job reference |
| 25 | |
| 26 | if mode not in ['fit', 'cover']: |
| 27 | raise Exception("Mode can only be fit or cover.") |
| 28 | |
| 29 | if image_path: |
| 30 | try: |
| 31 | self.original_image = Image.open(image_path) |
| 32 | self.photo = ImageTk.PhotoImage(self.original_image) |
| 33 | self.config(image=self.photo) |
| 34 | |
| 35 | self.force_resize() |
| 36 | except Exception as e: |
| 37 | print(f"Error loading image: {e}") |
| 38 | |
| 39 | self.after(100, self.init_events) |
| 40 | |
| 41 | def init_events(self): |
| 42 | self.parent.bind("<Configure>", self.on_resize) |
| 43 | |
| 44 | def on_resize(self, event=None): |
| 45 | """Debounce resizing to prevent rapid execution.""" |
| 46 | if self.resize_job: |
| 47 | self.after_cancel(self.resize_job) |
| 48 | self.resize_job = self.after(1, self.force_resize) # Debounce |
| 49 | |
| 50 | def force_resize(self): |
| 51 | """Resize image using actual widget size.""" |
| 52 | |
| 53 | if self.original_image is None: |
| 54 | return # Do nothing if no image is loaded |
| 55 | |
| 56 | width = self.winfo_width() |
| 57 | height = self.winfo_height() |
| 58 | |
| 59 | if width < 5 or height < 5: |
| 60 | return |
| 61 | |
| 62 | aspect_ratio = self.original_image.width / self.original_image.height |
| 63 | |
| 64 | if self.mode == "fit": |
| 65 | if width / height > aspect_ratio: |
| 66 | new_width = int(height * aspect_ratio) |
| 67 | new_height = height |
| 68 | else: |
nothing calls this directly
no outgoing calls
no test coverage detected