获取以Tkinter画布线为边界的区域

2024-04-25 05:57:26 发布

您现在位置:Python中文网/ 问答频道 /正文

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

我现在正试图填充闭合环所包围的区域,但我似乎找不到一种方法来填充。 我找不到一种方法来区分闭环内的区域和闭环外的区域

有没有一个简单的方法


Tags: imageselfeventimgmovelenbinddef
1条回答
网友
1楼 · 发布于 2024-04-25 05:57:26

您可以在列表self.points上保留点(event.x, event.y),并在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()

相关问题 更多 >