如何让RGB变量随鼠标在图片上的位置实时更新
问题描述
我基于某个Stack Overflow解决方案编写了代码,实现选择图片并在标签页中显示,期望鼠标在图片上移动时实时显示对应像素的RGB值,但实际只有鼠标位于image_frame中Canvas以外的区域时数值才会更新,且显示的数值与图片像素无关。
原代码如下:
from tkinter import mainloop, Tk, Frame, Button, Label, Canvas, PhotoImage, NW from tkinter import ttk from tkinter import filedialog import tkinter as tk from PIL import Image, ImageTk class my_class(tk.Tk): def __init__(self): super().__init__() self.geometry=('1400x1400') self.filename = '' my_notebook = ttk.Notebook(self) my_notebook.pack(pady=5) self.selections = Frame(my_notebook, width = 1100, height = 700) self.selections.pack(fill = "both", expand=1) my_notebook.add(self.selections, text = "Selections") Button(self.selections, text = "Select an Image", command = self.get_image).place(x=10,y=40) self.image_frame = Frame(my_notebook, width = 1100, height = 700) self.image_frame.pack(fill = "both", expand=1) my_notebook.add(self.image_frame, text = "Image") self.my_canvas = Canvas(self.image_frame, width=800, height=600, bg="white") self.my_canvas.pack() self.rgb_var = tk.StringVar(self.image_frame, '0 0 0') self.rgb_label = tk.Label(self.image_frame, textvariable = self.rgb_var) self.rgb_label.pack() self.image_frame.bind('<Motion>', lambda e: self.get_rgb(e)) def get_image(self): self.filename = filedialog.askopenfilename(initialdir="D:/Python", title="select a file", filetypes = (("png files","*.png"),("jpg files","*.jpg"))) self.img = Image.open(self.filename) self.img_rgb = self.img.convert('RGB') dim_x, dim_y = self.img_rgb.size self.img_tk = ImageTk.PhotoImage(self.img_rgb.resize((dim_x, dim_y))) self.my_canvas.create_image(dim_x // 2, dim_y // 2, image = self.img_tk) def get_rgb(self, event): x, y = event.x, event.y try: rgb = self.img_rgb.getpixel((x, y)) self.rgb_var.set(rgb) except IndexError: pass # ignore errors if cursor is outside the image if __name__ == '__main__': app = my_class() app.geometry=('1200x900') app.mainloop()
解决方案
问题出在两个核心点:事件绑定对象错误,以及鼠标坐标与图片像素坐标不匹配。以下是修正步骤:
- 绑定正确的事件对象
鼠标在Canvas上移动时,事件会被Canvas捕获,不会传递给父容器image_frame。因此需要将<Motion>事件绑定到Canvas上:
# 替换原有的image_frame绑定 self.my_canvas.bind('<Motion>', lambda e: self.get_rgb(e))
- 修正坐标匹配问题
原代码中图片居中放置,导致鼠标的Canvas坐标与图片像素坐标不对应。将图片锚定在Canvas左上角,并让图片适配Canvas尺寸(避免超出显示区域):
在get_image方法中修改:
def get_image(self): self.filename = filedialog.askopenfilename(initialdir="D:/Python", title="select a file", filetypes = (("png files","*.png"),("jpg files","*.jpg"))) self.img = Image.open(self.filename) # 让图片适配Canvas尺寸,保持比例 canvas_w, canvas_h = self.my_canvas.winfo_width(), self.my_canvas.winfo_height() img_w, img_h = self.img.size scale = min(canvas_w / img_w, canvas_h / img_h) new_size = (int(img_w * scale), int(img_h * scale)) self.img_rgb = self.img.convert('RGB').resize(new_size) self.img_tk = ImageTk.PhotoImage(self.img_rgb) # 清空Canvas并将图片放在左上角 self.my_canvas.delete("all") self.my_canvas.create_image(0, 0, image=self.img_tk, anchor=NW) # 保存显示的图片尺寸,用于判断鼠标范围 self.img_display_size = new_size
- 修正RGB获取逻辑
现在鼠标在Canvas上的坐标就是图片的像素坐标,同时需要判断鼠标是否在图片范围内:
def get_rgb(self, event): x, y = event.x, event.y try: if 0 <= x < self.img_display_size[0] and 0 <= y < self.img_display_size[1]: rgb = self.img_rgb.getpixel((x, y)) self.rgb_var.set(f"RGB: {rgb[0]} {rgb[1]} {rgb[2]}") else: self.rgb_var.set('0 0 0') except (AttributeError, IndexError): self.rgb_var.set('0 0 0')
- 修复geometry设置错误
原代码中self.geometry=('1400x1400')是赋值操作,不是调用方法,需改为self.geometry('1400x1400'),主程序中的app.geometry=('1200x900')同理修正。
完整修正后代码
from tkinter import mainloop, Tk, Frame, Button, Label, Canvas, PhotoImage, NW from tkinter import ttk from tkinter import filedialog import tkinter as tk from PIL import Image, ImageTk class my_class(tk.Tk): def __init__(self): super().__init__() self.geometry('1400x1400') self.filename = '' self.img_display_size = (0, 0) # 保存显示的图片尺寸 my_notebook = ttk.Notebook(self) my_notebook.pack(pady=5) self.selections = Frame(my_notebook, width = 1100, height = 700) self.selections.pack(fill = "both", expand=1) my_notebook.add(self.selections, text = "Selections") Button(self.selections, text = "Select an Image", command = self.get_image).place(x=10,y=40) self.image_frame = Frame(my_notebook, width = 1100, height = 700) self.image_frame.pack(fill = "both", expand=1) my_notebook.add(self.image_frame, text = "Image") self.my_canvas = Canvas(self.image_frame, width=800, height=600, bg="white") self.my_canvas.pack() self.rgb_var = tk.StringVar(self.image_frame, '0 0 0') self.rgb_label = tk.Label(self.image_frame, textvariable = self.rgb_var) self.rgb_label.pack() # 绑定事件到Canvas self.my_canvas.bind('<Motion>', lambda e: self.get_rgb(e)) def get_image(self): self.filename = filedialog.askopenfilename(initialdir="D:/Python", title="select a file", filetypes = (("png files","*.png"),("jpg files","*.jpg"))) self.img = Image.open(self.filename) # 适配Canvas尺寸,保持比例 canvas_w, canvas_h = self.my_canvas.winfo_width(), self.my_canvas.winfo_height() img_w, img_h = self.img.size scale = min(canvas_w / img_w, canvas_h / img_h) new_size = (int(img_w * scale), int(img_h * scale)) self.img_rgb = self.img.convert('RGB').resize(new_size) self.img_tk = ImageTk.PhotoImage(self.img_rgb) # 清空Canvas并放置图片在左上角 self.my_canvas.delete("all") self.my_canvas.create_image(0, 0, image=self.img_tk, anchor=NW) self.img_display_size = new_size def get_rgb(self, event): x, y = event.x, event.y try: if 0 <= x < self.img_display_size[0] and 0 <= y < self.img_display_size[1]: rgb = self.img_rgb.getpixel((x, y)) self.rgb_var.set(f"RGB: {rgb[0]} {rgb[1]} {rgb[2]}") else: self.rgb_var.set('0 0 0') except (AttributeError, IndexError): self.rgb_var.set('0 0 0') if __name__ == '__main__': app = my_class() app.geometry('1200x900') app.mainloop()
内容的提问来源于stack exchange,提问作者Ted Ersek
相关产品推荐
相关产品推荐

