python - 获取由 Tkinter 画布线限定的区域
问题描述
我有一个简短的代码,允许人们使用 tkinter 自由绘图,当他们释放鼠标按钮时,会在自由绘图的两个端点之间自动创建一条线,从而形成一个闭环。
这是我的代码:
from tkinter import *
from PIL import Image, ImageTk
class App(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.columnconfigure(0,weight=1)
self.rowconfigure(0,weight=1)
self.original = Image.open("C:/Users/elver/Pictures/living.jpg")
self.image = ImageTk.PhotoImage(self.original)
self.display = Canvas(self, bd=0, highlightthickness=0)
self.display.create_image(0, 0, image=self.image, anchor=NW, tags="IMG")
self.display.grid(row=0, sticky=W+E+N+S)
self.pack(fill=BOTH, expand=1)
self.bind("<Configure>", self.resize)
self.display.bind('<Button-1>', self.click)
self.display.bind('<B1-Motion>', self.move)
self.display.bind('<ButtonRelease-1>', self.release)
self.linelist = []
def resize(self, event):
size = (event.width, event.height)
resized = self.original.resize(size,Image.ANTIALIAS)
self.image = ImageTk.PhotoImage(resized)
self.display.delete("IMG")
self.display.create_image(0, 0, image=self.image, anchor=NW, tags="IMG")
def click(self, click_event):
global prev
prev = click_event
for x in range(0, len(self.linelist)-1):
self.display.delete(self.linelist[x])
self.linelist.clear()
self.display.create_image(0, 0, image=self.image, anchor=NW, tags="IMG")
def move(self, move_event):
global Canline
global prev
Canline=self.display.create_line(prev.x, prev.y, move_event.x, move_event.y, width=2)
self.linelist.append(Canline)
prev = move_event
#print(len(self.linelist))
def release(self, release_event):
global Canline
Canline=self.display.create_line(self.display.coords(self.linelist[1])[0], self.display.coords(self.linelist[1])[1], \
self.display.coords(self.linelist[len(self.linelist)-1])[0], self.display.coords(self.linelist[len(self.linelist)-1])[1], width=2)
root =Tk()
app = App(root)
app.mainloop()
我现在正在尝试填充由闭环限定的区域,但我似乎无法找到一种方法来做到这一点。我找不到区分闭环内部区域和闭环外部区域的方法。
有没有简单的方法可以做到这一点?
解决方案
您可以将点保留(event.x, event.y)
在列表中self.points
并使用此列表release
来绘制填充多边形:
create_polygon(self.points)
您甚至可以使用此列表中的第一个和最后一个元素来绘制结束线 - 所以您不需要从self.display.coords(self.lines[0])
和获取坐标self.display.coords(self.lines[-1])
first = self.points[0]
last = self.points[-1]
line = self.display.create_line(*first, *last, width=2)
具有许多其他更改的工作代码
import tkinter as tk
from PIL import Image, ImageTk
# --- classes ---
class App(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.pack(fill='both', expand=True)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.original = Image.open("Obrazy/images/image-800x600.jpg")
self.image = ImageTk.PhotoImage(self.original)
self.display = tk.Canvas(self, bd=0, highlightthickness=0)
self.display.create_image(0, 0, image=self.image, anchor='nw', tags="IMG")
self.display.grid(row=0, sticky='news')
self.display.bind('<Button-1>', self.click)
self.display.bind('<B1-Motion>', self.move)
self.display.bind('<ButtonRelease-1>', self.release)
self.bind("<Configure>", self.resize)
self.lines = []
self.points = []
self.polygon = None
def resize(self, event):
size = (event.width, event.height)
resized = self.original.resize(size, Image.ANTIALIAS)
self.image = ImageTk.PhotoImage(resized)
# replace image in object instead of deleting and creating object again
self.display.itemconfig("IMG", image=self.image)
#self.display.delete("IMG")
#self.display.create_image(0, 0, image=self.image, anchor='nw', tags="IMG")
# TODO: recalculate self.points to resize polygon ???
def click(self, event):
for item in self.lines:
self.display.delete(item)
if self.polygon:
self.display.delete(self.polygon)
self.polygon = None
self.lines.clear()
self.points.clear()
#self.lines = []
#self.points = []
self.points.append((event.x, event.y))
self.prev = event
# ??? I don't know what is this ????
#self.display.create_image(0, 0, image=self.image, anchor='nw', tags="IMG")
def move(self, event):
line = self.display.create_line(self.prev.x, self.prev.y, event.x, event.y, width=2)
self.lines.append(line)
self.points.append((event.x, event.y))
self.prev = event
def release(self, event):
#first = self.display.coords(self.lines[0])
#last = self.display.coords(self.lines[-1])
first = self.points[0]
last = self.points[-1]
line = self.display.create_line(*first, *last, width=2)
self.lines.append(line)
self.polygon = self.display.create_polygon(self.points, fill='red', outline='black', width=2)
# you could delet lines here if you don't need them
# --- main ---
root = tk.Tk()
app = App(root)
app.mainloop()
编辑1:
在这个版本中,我create_polygon
在开始时使用,后来我coords()
用来更新这个多边形中的点。这样我就不需要带行的列表,也不需要使用create_line()
.
它在绘图期间始终显示闭合线。
coords()
需要平面列表[x1, y1, x2, y1, ...]
而不是[(x1, y1), (x2, y2), ...]
所以我使用list.extend([x, y])
( list += [x, y]
) 代替list.append([x, y])
我fill=""
曾经在绘图过程中使用透明多边形,并且release()
我曾经将configitem()
其更改为fill="red"
def click(self, event):
# `coords()` needs flat list [x1, y1, x2, y2, ...] instead of [(x1, y1), (x2, y2), ...]
# so I use `list.extend(other_list)` (`list += other_list`) instead of `list.append(other_list)
self.points = [event.x, event.y]
# at start there is no polygon on screen so there is nothing to delete
if self.polygon:
self.display.delete(self.polygon)
# polygon can be created with one point so I can do it here - I don't have to do it in `move` like with `create_line`
# (BTW: `fill=""` creates transparent polygon)
self.polygon = self.display.create_polygon(self.points, fill='', outline='black', width=2)
def move(self, event):
# `coords()` needs flat list [x1, y1, x2, y2, ...] instead of [(x1, y1), (x2, y2), ...]
# so I use `list.extend(other_list)` (`list += other_list`) instead of `list.append(other_list)
self.points += [event.x, event.y]
# update existing polygon
self.display.coords(self.polygon, self.points)
def release(self, event):
# change fill color at the end
self.display.itemconfig(self.polygon, fill='red')
编辑2:
我意识到create_line
可以一次获得两个以上的点并创建多条线,所以我在这个版本中使用它。
我将点添加到列表中并用于coords()
更新现有行中的点。这样我只有一条线(有很多点 - 没有闭合线的多边形),我不需要带线的列表。
def click(self, event):
# `coords()` needs flat list [x1, y1, x2, y2, ...] instead of [(x1, y1), (x2, y2), ...]
# so I use `list.extend(other_list)` (`list += other_list`) instead of `list.append(other_list)
self.points = [event.x, event.y]
# at start there is no polygon on screen so there is nothing to delete
if self.polygon:
self.display.delete(self.polygon)
self.polygon = None # I need it in `move()`
# `create_line()` needs at least two points so I cann't create it here.
# I have to create it in `move()` when I will have two points
def move(self, event):
# `coords()` needs flat list [x1, y1, x2, y2, ...] instead of [(x1, y1), (x2, y2), ...]
# so I use `list.extend(other_list)` (`list += other_list`) instead of `list.append(other_list)
self.points += [event.x, event.y]
if not self.polygon:
# create line if not exists - now `self.points` have two points
self.polygon = self.display.create_line(self.points, width=2)
else:
# update existing line
self.display.coords(self.polygon, self.points)
def release(self, event):
# replace line with polygon to close it and fill it (BTW: `fill=""`if you want transparent polygon)
self.display.delete(self.polygon)
self.polygon = self.display.create_polygon(self.points, width=2, fill='red', outline='black')
EDIT 1的完整代码
import tkinter as tk
from PIL import Image, ImageTk
# --- classes ---
class App(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.pack(fill='both', expand=True)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.original = Image.open("image.jpg")
self.image = ImageTk.PhotoImage(self.original)
self.display = tk.Canvas(self, bd=0, highlightthickness=0)
self.display.create_image(0, 0, image=self.image, anchor='nw', tags="IMG")
self.display.grid(row=0, sticky='news')
self.display.bind('<Button-1>', self.click)
self.display.bind('<B1-Motion>', self.move)
self.display.bind('<ButtonRelease-1>', self.release)
self.bind("<Configure>", self.resize)
self.points = []
self.polygon = None
def resize(self, event):
size = (event.width, event.height)
resized = self.original.resize(size, Image.ANTIALIAS)
self.image = ImageTk.PhotoImage(resized)
# replace image in object instead of deleting and creating object again
self.display.itemconfig("IMG", image=self.image)
# TODO: recalculate self.points to resize polygon ???
def click(self, event):
# `coords()` needs flat list [x1, y1, x2, y2, ...] instead of [(x1, y1), (x2, y2), ...]
# so I use `list.extend(other_list)` (`list += other_list`) instead of `list.append(other_list)
self.points = [event.x, event.y]
# at start there is no polygon on screen so there is nothing to delete
if self.polygon:
self.display.delete(self.polygon)
# polygon can be created with one point so I can do it here - I don't have to do it in `move` like with `create_line`
# (BTW: `fill=""` creates transparent polygon)
self.polygon = self.display.create_polygon(self.points, fill='', outline='black', width=2)
def move(self, event):
# `coords()` needs flat list [x1, y1, x2, y2, ...] instead of [(x1, y1), (x2, y2), ...]
# so I use `list.extend(other_list)` (`list += other_list`) instead of `list.append(other_list)
self.points += [event.x, event.y]
# update existing polygon
self.display.coords(self.polygon, self.points)
def release(self, event):
# change fill color at the end
self.display.itemconfig(self.polygon, fill='red')
# --- main ---
root = tk.Tk()
app = App(root)
app.mainloop()
EDIT 2的完整代码
import tkinter as tk
from PIL import Image, ImageTk
# --- classes ---
class App(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.pack(fill='both', expand=True)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.original = Image.open("image.jpg")
self.image = ImageTk.PhotoImage(self.original)
self.display = tk.Canvas(self, bd=0, highlightthickness=0)
self.display.create_image(0, 0, image=self.image, anchor='nw', tags="IMG")
self.display.grid(row=0, sticky='news')
self.display.bind('<Button-1>', self.click)
self.display.bind('<B1-Motion>', self.move)
self.display.bind('<ButtonRelease-1>', self.release)
self.bind("<Configure>", self.resize)
self.points = []
self.polygon = None
def resize(self, event):
size = (event.width, event.height)
resized = self.original.resize(size, Image.ANTIALIAS)
self.image = ImageTk.PhotoImage(resized)
# replace image in object instead of deleting and creating object again
self.display.itemconfig("IMG", image=self.image)
# TODO: recalculate self.points to resize polygon ???
def click(self, event):
# `coords()` needs flat list [x1, y1, x2, y2, ...] instead of [(x1, y1), (x2, y2), ...]
# so I use `list.extend(other_list)` (`list += other_list`) instead of `list.append(other_list)
self.points = [event.x, event.y]
# at start there is no polygon on screen so there is nothing to delete
if self.polygon:
self.display.delete(self.polygon)
self.polygon = None # I need it in `move()`
# `create_line()` needs at least two points so I cann't create it here.
# I have to create it in `move()` when I will have two points
def move(self, event):
# `coords()` needs flat list [x1, y1, x2, y2, ...] instead of [(x1, y1), (x2, y2), ...]
# so I use `list.extend(other_list)` (`list += other_list`) instead of `list.append(other_list)
self.points += [event.x, event.y]
if not self.polygon:
# create line if not exists - now `self.points` have two points
self.polygon = self.display.create_line(self.points, width=2)
else:
# update existing line
self.display.coords(self.polygon, self.points)
def release(self, event):
# replace line with polygon to close it and fill it (BTW: `fill=""`if you want transparent polygon)
self.display.delete(self.polygon)
self.polygon = self.display.create_polygon(self.points, width=2, fill='red', outline='black')
# --- main ---
root = tk.Tk()
app = App(root)
app.mainloop()
推荐阅读
- sed - sed 脚本未在 Vagrantfile 中执行
- animation - 在 ThreeJS 中使用 KeyFrameTracks 同时动画立方体
- r - 如何使用时间序列滑块过滤数据
- python - 使用 Python 一次在文件夹中的多个文件上运行 perl 脚本
- java - 如何检索已存储在 firebase 数据库中并显示在回收站视图中的图像?
- python - 在“ Pipenv”中定义shell入口点的规范方法?
- xcode - 如何从零开始在 Xcode 中创建准系统“Hello World”项目?
- python - How can I iterate a dictionary which is into another dictionary to get specific values?
- html - 从网页中提取和选择链接
- java - Graphics2d Rotation 旋转所有对象