-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathimage_viewer.py
More file actions
126 lines (97 loc) · 4.05 KB
/
image_viewer.py
File metadata and controls
126 lines (97 loc) · 4.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk, ImageDraw
import numpy as np
import threading
class ImageViewer:
def __init__(self, window_title="Image Viewer", window_size=(800, 600)):
"""Initialize the image viewer window
Args:
window_title (str): Title of the window
window_size (tuple): Initial window size as (width, height)
"""
self.root = tk.Tk()
self.root.title(window_title)
self.root.geometry(f"{window_size[0]}x{window_size[1]}")
# Create a frame to hold the image
self.frame = ttk.Frame(self.root)
self.frame.pack(fill=tk.BOTH, expand=True)
# Create a label to display the image
self.image_label = ttk.Label(self.frame)
self.image_label.pack(fill=tk.BOTH, expand=True)
# Store the PhotoImage reference
self.photo = None
# Store the current PIL Image
self.current_image = None
# Click handler callback
self.click_callback = None
# Bind click event
self.image_label.bind('<Button-1>', self._on_click)
def set_click_handler(self, callback):
"""Set the callback function for click events"""
self.click_callback = callback
def _on_click(self, event):
"""Internal click handler that passes raw coordinates to callback"""
if self.click_callback:
self.click_callback(event.x, event.y)
def update_image(self, image):
"""Update the displayed image
Args:
image: Can be a PIL Image, numpy array, or path to image file
"""
if isinstance(image, np.ndarray):
# Convert numpy array to PIL Image
image = Image.fromarray(image)
elif isinstance(image, str):
# Load image from file
image = Image.open(image)
# Store the current image
self.current_image = image
# Get current display size
display_size = (self.frame.winfo_width(), self.frame.winfo_height())
# Create a copy before thumbnail to avoid modifying original
display_image = image.copy()
display_image.thumbnail(display_size, Image.Resampling.LANCZOS)
# Convert to PhotoImage for display
self.photo = ImageTk.PhotoImage(display_image)
self.image_label.configure(image=self.photo)
def update(self):
"""Update the window - call this in your main loop"""
self.root.update()
def close(self):
"""Close the window"""
self.root.destroy()
def draw_circle(self, x: int, y: int, radius: int = 10):
"""Draw a red circle at the specified coordinates.
Args:
x (int): X coordinate
y (int): Y coordinate
radius (int): Radius of the circle in pixels
duration (float): How long to show the circle in seconds
"""
if not self.current_image:
return
# Create a copy of the current image
img = self.current_image.copy()
# Create a drawing object
draw = ImageDraw.Draw(img)
# Draw red circle
draw.ellipse([x-radius, y-radius, x+radius, y+radius], outline='red', width=5)
# Update the image
self.update_image(img)
# no need to remove circle, it will get pulled off in next frame update
if __name__ == "__main__":
# Example usage
viewer = ImageViewer()
# Create a test image
test_image = Image.new('RGB', (400, 300), color='red')
viewer.update_image(test_image)
# Example click handler
def on_click(x, y):
print(f"Clicked at ({x}, {y})")
viewer.set_click_handler(on_click)
try:
while True:
viewer.update()
except KeyboardInterrupt:
viewer.close()