首页 > 解决方案 > 获取由 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()

我现在正在尝试填充由闭环限定的区域,但我似乎无法找到一种方法来做到这一点。我找不到区分闭环内部区域和闭环外部区域的方法。

有没有简单的方法可以做到这一点?

标签: pythontkintertkinter-canvas

解决方案


您可以将点保留(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()

推荐阅读