Tkinter: set a color for points selected by the user

Viewed 1451

I have an image saved as image.png. The workflow of my task is this:

  1. Load image in Tkinter along with a "select two points" button below the image
  2. The user left-clicks with the mouse twice on two points of the image
  3. When he selects the first point, that specific point gets highlighted (say in red, or whichever color); then he selects the second point and also this second point gets highlighted
  4. The (x,y) coordinates of the two points are stored in global variables and will be later used
  5. Once the user has selected two points, a second "Done!" button appears. When this button is clicked the GUI closes. N.B I would like the two points to remain highlighted until the user clicks the closing button so that he has an idea of where he/she clicked

I managed to solve all steps, except step 3. The most similar thing that I found is to create a rectangle with canvas.create_rectangle(x,y,x+1,y+1,fill="red"), but first I would prefer a circle and second I don't manage to link the canvas to my Label

Any help would be appreciated :D

here is my code so far:

root = Tk()  # create a window

frame = Frame(root)  # define upper frame
middleframe = Frame(root)  # define middle frame
exitFrame = Frame(root)  # define exit frame
frame.pack()  # pack the frame
middleframe.pack()  # pack the subframe
exitFrame.pack(side = 'bottom')  # pack the exit frame

# function that closes the GUI
def close_window(): 
    root.destroy()

# load the image
img = PhotoImage(file="image.png")  # save the image
panel = Label(frame, image=img)  # display the image as a label
panel.grid(row=0, column=0)  # pack the image

# make the user select some points
global x_Coordinates  # initialize empty list for storing x-axis coordinates
global y_Coordinates  # initialize empty list for storing y-axis coordinates
x_Coordinates = []
y_Coordinates = []

clicks = 0
def countClicks():
  global clicks # this will use the variable to count
  clicks = clicks + 1  # increment "clicks"
  if clicks == 2: # if the user has selected 2 points, add a button that closes the window
      exit_button = Button(exitFrame, state = "normal", text = "Done!", command = close_window)  # link the closing function to the button
      exit_button.grid(row=2, column=0, pady=5)  # set button position with "grid"        
pass

def selectPoints():  # function called when user clicks the button "select two points"
    panel.bind("<Button 1>", saveCoordinates)  #  link the function to the left-mouse-click event
    exit_button = Button (exitFrame, state = "disabled", text = "Done!", command = close_window)  # link closing function to the button
    exit_button.grid(row=2, column=0, pady=5)  # set button position with "grid"
    button_select_points.config(state = "disabled") # switch button state to "disabled"

def saveCoordinates(event): # function called when left-mouse-button is clicked   
    x_coordinate = event.x  # save x and y coordinates selected by the user
    y_coordinate = event.y
    x_Coordinates.append(x_coordinate)  # append to external list
    y_Coordinates.append(y_coordinate)  # append to external list
    countClicks()  # invoke function "countClicks"

button_select_points = Button(middleframe, text = "select two points", command = selectPoints)  # insert button and link it to "selectPoints"
button_select_points.grid(row=1, column=0, pady=5)  # set button position with "grid"

root.mainloop()  # keep the GUI open
2 Answers

Unless you want to do more than set a few pixels, it would probably be easier for you to instead use a Canvas widget, which has some higher-level drawing primitives (e.g. rectangles and ovals).

(Here's some fairly comprehensive Tkinter documentation about Canvas widgets.)

Below is your code with modifications to do this (plus a few others to make it more readable by following the PEP 8 - Style Guide for Python Code guidelines and with the removal of some things I thought were unnecessary and/or too redundant).

It defines a new helper function named create_circle() to simplify calls the more-general Canvas widget's create_oval() method. This is now called in your saveCoordinates() function (which is now what's bound to the "<Button 1>" event of the new Canvas object instead of the Label you were using).

from tkinter import *

root = Tk()  # create a window

frame = Frame(root)  # define upper frame
middleframe = Frame(root)  # define middle frame
exitFrame = Frame(root)  # define exit frame
frame.pack()  # pack the frame
middleframe.pack()  # pack the subframe
exitFrame.pack(side='bottom')  # pack the exit frame

# function that closes the GUI
def close_window():
    root.destroy()

img = PhotoImage(file="myimage.png")  # load the image
canvas = Canvas(frame, width=img.width(), height=img.height(), borderwidth=0)
canvas.grid(row=0, column=0)
canvas.create_image(0, 0, image=img, anchor=NW)

# make the user select some points
x_Coordinates = []  # list for storing x-axis coordinates
y_Coordinates = []  # list for storing y-axis coordinates
clicks = 0

def create_circle(canvas, x, y, radius, **kwargs):
    return canvas.create_oval(x-radius, y-radius, x+radius, y+radius, **kwargs)

def countClicks():
    global clicks

    clicks += 1
    # if the user has selected 2 points, add a button that closes the window
    if clicks == 2:
        # link the closing function to the button
        exit_button = Button(exitFrame, state="normal", text="Done!",
                             command=close_window)
        exit_button.grid(row=2, column=0, pady=5)  # set button position with "grid"

def selectPoints():  # function called when user clicks the button "select two points"
    # link the function to the left-mouse-click event
    canvas.bind("<Button 1>", saveCoordinates)
    # link closing function to the button
    exit_button = Button (exitFrame, state="disabled", text="Done!",
                          command=close_window)
    exit_button.grid(row=2, column=0, pady=5)  # set button position with "grid"
    button_select_points.config(state="disabled") # switch button state to "disabled"

def saveCoordinates(event): # function called when left-mouse-button is clicked
    x_coordinate = event.x  # save x and y coordinates selected by the user
    y_coordinate = event.y
    x_Coordinates.append(x_coordinate)
    y_Coordinates.append(y_coordinate)
    # Display a small dot showing position of point.
    create_circle(canvas, x_coordinate, y_coordinate, radius=3, fill='red')
    countClicks()

# insert button and link it to "selectPoints"
button_select_points = Button(middleframe, text="select two points",
                              command=selectPoints)
button_select_points.grid(row=1, column=0, pady=5)

root.mainloop()  # keep the GUI open
  1. When he selects the first point, that specific point gets highlighted (say in red, or whichever color); then he selects the second point and also this second point gets highlighted

I managed to solve all steps, except step 3

The PhotoImage class has methods for setting the color of a pixel. For example, to set the pixel at the x/y of the event to red, do this:

img.put(("red",), to=(event.x, event.y))

Since a single pixel is really hard to see, you can draw a small 3x3 rectangle centered around the point fairly easily. The following example puts the color red to the pixels in a square from event.x-1, event.y-1 to event.x+1, event.y+1:

img.put(("red",), to=(event.x-1, event.y-1, event.x+1, event.y+1))

The first argument to the put method is a list of colors, which can either be known color names or rgb specifications (eg: #ff0000 for red, etc). If here is not enough data to fill the specified region, the provided data will be tiled.

The to argument specifies either a single x/y coordinate or two x/y coordinates that define a rectangular region.

Related