获取由 Tkinter canvas 行界定的区域

Get area bounded by Tkinter canvas lines

我有一个简短的代码,允许人们使用 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')

编辑 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()

编辑 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()