I want to update the polygon on each mouse click. The below code redraw new polygon when new position is obtained from mouse click. I want to update the polygon or get new one (delete old one). How to do it. Here is the complete as suggested. Tkinter library of python is used.
import math
from Tkinter import *
from PIL import Image, ImageDraw
import Image, ImageTk
coord=[] # for saving coord of each click position
Dict_Polygons={} # Dictionary for saving polygons
list_of_points=[]
# Function to get the co-ordianates of mouse clicked position
def draw_polygons(event):
mouse_xy = (event.x, event.y)
func_Draw_polygons(mouse_xy)
# Function to draw polygon
def func_Draw_polygons(mouse_xy):
center_x, center_y = mouse_xy
#draw dot over position which is clicked
x1, y1 = (center_x - 1), (center_y - 1)
x2, y2 = (center_x + 1), (center_y + 1)
canvas.create_oval(x1, y1, x2, y2, fill='green', outline='green', width=5)
# add clicked positions to list
list_of_points.append(mouse_xy)
numberofPoint=len(list_of_points)
# Draw polygon
if numberofPoint>2:
poly=canvas.create_polygon(list_of_points, fill='', outline='green', width=2)
canvas.coords(poly,)
elif numberofPoint==2 :
print('line')
canvas.create_line(list_of_points)
else:
print('dot')
# ImageDraw.ImageDraw.polygon((list_of_points), fill=None, outline=None)
print(list_of_points)
##########################################################################
# Main function
if __name__ == '__main__':
root = tk.Tk()
# Input image
img = Image.open("e.png")
# Draw canvas for input image to pop up image for clicks
filename = ImageTk.PhotoImage(img)
canvas = Canvas(root,height=img.size[0],width=img.size[0])
canvas.image = filename
canvas.create_image(0,0,anchor='nw',image=filename)
canvas.pack()
# bind function to canvas to generate event
canvas.bind("<Button 3>", draw_polygons)
root.mainloop()
Does this get you any closer to your solution? It re-draws based on the list of points each time a new point is added to the list.
import math
#change to tkinter for python3
from Tkinter import *
#from PIL import Image, ImageDraw
#import Image, ImageTk
coord=[] # for saving coord of each click position
Dict_Polygons={} # Dictionary for saving polygons
list_of_points=[]
poly = None
# Function to get the co-ordianates of mouse clicked position
def draw_polygons(event):
mouse_xy = (event.x, event.y)
func_Draw_polygons(mouse_xy)
# Function to draw polygon
def func_Draw_polygons(mouse_xy):
global poly, list_of_points
center_x, center_y = mouse_xy
canvas.delete(ALL)
list_of_points.append((center_x, center_y))
for pt in list_of_points:
x, y = pt
#draw dot over position which is clicked
x1, y1 = (x - 1), (y - 1)
x2, y2 = (x + 1), (y + 1)
canvas.create_oval(x1, y1, x2, y2, fill='green', outline='green', width=5)
# add clicked positions to list
numberofPoint=len(list_of_points)
# Draw polygon
if numberofPoint>2:
poly=canvas.create_polygon(list_of_points, fill='', outline='green', width=2)
elif numberofPoint==2 :
print('line')
canvas.create_line(list_of_points)
else:
print('dot')
# ImageDraw.ImageDraw.polygon((list_of_points), fill=None, outline=None)
print(list_of_points)
##########################################################################
# Main function
if __name__ == '__main__':
root = Tk()
canvas = Canvas(root,height=200,width=200)
canvas.pack()
# bind function to canvas to generate event
canvas.bind("<Button 3>", draw_polygons)
root.mainloop()
Related
I am working on creating a drawing program. Presently I can DRAG and DRAW in a rectangle. While outside of the rectangle, I want to create several colours boxes to select different colours and send that colour into this Tkinter function :
win.bind('<B1-Motion>', motion)
The issue is that I am unsure how to do it. When I try to send the "colour" into the motion event, I get an error because I am unsure how to send an additional argument into it.
from graphics import *
import time
win = GraphWin("mouse", 500, 500)
win.master.attributes('-topmost', True)
rect=Rectangle(Point(20,20), Point(200,300))
rect.draw(win)
def motion(event): # how can I enter another argument?
x1, y1 = event.x, event.y
if (x1>20 and x1<200) and (y1>20 and y1<300):
point1 = Point(x1, y1)
point1.setFill("blue") # want to send a value into here
point1.draw(win)
else:
pass
def drawingit():
coord = win.checkMouse()
if coord == None:
print("nothing clicked")
elif (coord.getX()>20 and coord.getX()<200) and (coord.getY()>20 and coord.getY()<300):
win.bind('<B1-Motion>', motion)
else:
point1 = Point(coord.getX(), coord.getY())
point1.setFill("red")
point1.draw(win)
time.sleep(.04)
while True:
drawingit()
win.mainloop()
EDIT : added green colour rectangle
from graphics import *
import time
win = GraphWin("mouse", 500, 500)
win.master.attributes('-topmost', True)
rect=Rectangle(Point(20,20), Point(200,300))
rect.draw(win)
rectorange=Rectangle(Point(20,430), Point(200,460))
rectorange.setFill("green")
rectorange.draw(win)
pick_colour="black"
def motion(event):
x1, y1 = event.x, event.y
if (x1>20 and x1<200) and (y1>20 and y1<300):
point1 = Point(x1, y1)
point1.setFill(pick_colour)
point1.draw(win)
else:
pass
def drawingit():
coord = win.checkMouse()
if coord == None:
print("nothing clicked")
#time.sleep(0.5)
elif (coord.getX()>20 and coord.getX()<200) and (coord.getY()>20 and coord.getY()<300):
win.bind('<B1-Motion>', motion)
elif (coord.getX()>20 and coord.getX()<200) and (coord.getY()>430 and coord.getY()<460):
pick_colour="green"
else:
point1 = Point(coord.getX(), coord.getY())
point1.setFill("black")
point1.draw(win)
time.sleep(.04)
while True:
drawingit()
win.mainloop()
I have an app that displays the webcam using OpenCV then this is converted to Tkinter (for GUI purposes). I can implement a mouse drawing function but the image update loop keeps covering it. I have tried raise/lower on canvas and labels but to no avail. Anyone got any ideas on this please?
Update Loop:
def update(self):
isTrue, frame = self.vid.getFrame()
#Convert frame to TK and put on canvas
if isTrue:
self.photo = ImageTk.PhotoImage(image=PIL.Image.fromarray(frame))
self.image = self.canvas.create_image(0, 0, image=self.photo, anchor=tk.NW)
self.window.after(10, self.update)
Draw Function:
def paint(self, event):
white = (255, 255, 255)
x1, y1 = (event.x - 2), (event.y - 2)
x2, y2 = (event.x + 2), (event.y + 2)
self.canvas.create_oval(x1, y1, x2, y2, fill="white", outline="white")
I am new to GUI programming. I want to create something that can load an image and then I can either measure the part of the picture I want and can load it into a cropping method or have to return the area of the crop. Does anyone have suggestions for accomplishing that?
I have a program with PySimpleGUI that can load an image, but I am not sure where to go from there to get the capacity to measure the item.
Using sg.Graph to load your image, and set drag_submits=True and enable_events=True to generate events when mouse click or drag. It will return the coordinate of mouse, then you can get the position of mouse or width/height of selected area.
following code to demo.
import base64
from io import BytesIO
from PIL import Image
import PySimpleGUI as sg
def update(x0, y0, x1, y1):
"""
Update rectangle information
"""
print(repr(x0), repr(y0), repr(x1), repr(y1))
window['-START-'].update(f'Start: ({x0}, {y0})')
window['-STOP-' ].update(f'Start: ({x1}, {y1})')
window['-BOX-' ].update(f'Box: ({abs(x1-x0+1)}, {abs(y1-y0+1)})')
# Generate an image with size (400, 400)
imgdata = base64.b64decode(sg.EMOJI_BASE64_HAPPY_LAUGH)
image = Image.open(BytesIO(imgdata))
im = image.resize((400, 400), resample=Image.CUBIC)
with BytesIO() as output:
im.save(output, format="PNG")
data = output.getvalue()
layout = [
[sg.Graph((400, 400), (0, 0), (400, 400), key='-GRAPH-',
drag_submits=True, enable_events=True, background_color='green')],
[sg.Text("Start: None", key="-START-"),
sg.Text("Stop: None", key="-STOP-"),
sg.Text("Box: None", key="-BOX-")],
]
window = sg.Window("Measure", layout, finalize=True)
graph = window['-GRAPH-']
graph.draw_image(data=data, location=(0, 400))
x0, y0 = None, None
x1, y1 = None, None
colors = ['blue', 'white']
index = False
figure = None
while True:
event, values = window.read(timeout=100)
if event == sg.WINDOW_CLOSED:
break
elif event in ('-GRAPH-', '-GRAPH-+UP'):
if (x0, y0) == (None, None):
x0, y0 = values['-GRAPH-']
x1, y1 = values['-GRAPH-']
update(x0, y0, x1, y1)
if event == '-GRAPH-+UP':
x0, y0 = None, None
if figure:
graph.delete_figure(figure)
if None not in (x0, y0, x1, y1):
figure = graph.draw_rectangle((x0, y0), (x1, y1), line_color=colors[index])
index = not index
window.close()
I created some code to show 2 balls moving, but when I run it, it doesn't show the movement of the balls.Furthermore it stops running and ignores the infinite loop This is my code up to now:
import tkinter as tk
class ObjectHolder:
def __init__(self, pos, velocity, radius, id):
self.id = id # the id of the canvas shape
self.pos = pos # the position of the object
self.r = radius # the radius of incluence of the object
self.velocity = velocity # the velocity of the object
def moveobject(object):
x = object.pos[0] + object.velocity[0] # moves the object where
y = object.pos[1] + object.velocity[1] # 0=x and 1=y
object.pos = (x, y)
canvas.move(object, x, y)
class App():
def __init__(self, canvas):
self.canvas = canvas
self.objects = []
for i in range(0, 2):
position = ((i+1)*100, (i+1)*100)
velocity = (-(i+1)*10, -(i+1)*10)
radius = (i + 1) * 20
x1 = position[0]-radius
y1 = position[1]-radius
x2 = position[0]+radius
y2 = position[1]+radius
id = canvas.create_oval(x1, y1, x2, y2)
self.objects.append(ObjectHolder(position, velocity, radius, id))
self.symulation(self.objects)
def symulation(self, objects):
for object in objects: # this moves each object
ObjectHolder.moveobject(object)
# This part doesn't work. It is supposed to update the canvas
# and repeat forever.
self.canvas.update()
root.update()
self.canvas.after(50, self.symulation, objects)
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=600, bg="light blue")
canvas.pack()
App(canvas)
There are a number of issues with your code. One big one was the way you were updating the position of the existing canvas objects. The move() method needs to know the amount of movement (change in x and y value), not the new absolute position.
When I fixed that it turned out that the velocities were too big, so I reduced them to only be 10% of the values you had.
Another problem was with the way the ObjectHolder class was implemented. For one thing, the moveobject() method had no self argument, which it should have been using instead of having an object argument. You should probably also rename the method simply move().
The code below runs and does animate the movement.
import tkinter as tk
class ObjectHolder:
def __init__(self, pos, velocity, radius, id):
self.id = id # the id of the canvas shape
self.pos = pos # the position of the object
self.r = radius # the radius of incluence of the object
self.velocity = velocity # the velocity of the object
def moveobject(self):
x, y = self.pos
dx, dy = self.velocity
self.pos = (x + dx, y + dy)
canvas.move(self.id, dx, dy) # Amount of movement, not new position.
class App():
def __init__(self, canvas):
self.canvas = canvas
self.objects = []
for i in range(0, 2):
position = ((i+1)*100, (i+1)*100)
# velocity = (-(i+1)*10, -(i+1)*10)
velocity = (-(i+1), -(i+1)) # Much slower speed...
radius = (i + 1) * 20
x1 = position[0]-radius
y1 = position[1]-radius
x2 = position[0]+radius
y2 = position[1]+radius
id = canvas.create_oval(x1, y1, x2, y2)
self.objects.append(ObjectHolder(position, velocity, radius, id))
self.simulation(self.objects)
def simulation(self, objects):
for object in objects: # this moves each object
object.moveobject()
# This part doesn't work. It is supposed to update the canvas
# and repeat forever.
# self.canvas.update() # Not needed.
# root.update() # Not needed.
self.canvas.after(50, self.simulation, objects)
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=600, bg="light blue")
canvas.pack()
app = App(canvas)
root.mainloop() # Added.
I've got the following code that produces a plot that can interactively be modified. Clicking / holding the left mouse button sets the marker position, Holding the right button and moving the mouse moves the plotted data in direction x and using the mouse wheel zooms in/out. Additionally, resizing the window calls figure.tight_layout() so that the size of the axes is adapted to the window size.
# coding=utf-8
from __future__ import division
from Tkinter import *
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from numpy import arange, sin, pi
matplotlib.use('TkAgg')
class PlotFrame(Frame):
def __init__(self, master, **ops):
Frame.__init__(self, master, **ops)
self.figure = Figure()
self.axes_main = self.figure.add_subplot(111)
for i in range(10):
t = arange(0, 300, 0.01)
s = sin(0.02 * pi * (t + 10 * i))
self.axes_main.plot(t, s)
self.plot = FigureCanvasTkAgg(self.figure, master=self)
self.plot.show()
self.plot.get_tk_widget().pack(fill=BOTH, expand=1)
self.dragging = False
self.dragging_button = None
self.mouse_pos = [0, 0]
self.marker = self.figure.axes[0].plot((0, 0), (-1, 1), 'black', linewidth=3)[0]
self.plot.mpl_connect('button_press_event', self.on_button_press)
self.plot.mpl_connect('button_release_event', self.on_button_release)
self.plot.mpl_connect('motion_notify_event', self.on_mouse_move)
self.plot.mpl_connect('scroll_event', self.on_mouse_scroll)
self.plot.mpl_connect("resize_event", self.on_resize)
def on_resize(self, _):
self.figure.tight_layout()
def axes_size(self):
pos = self.axes_main.get_position()
bbox = self.figure.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
width, height = bbox.width * self.figure.dpi, bbox.height * self.figure.dpi
axis_size = [(pos.x1 - pos.x0) * width, (pos.y1 - pos.y0) * height]
return axis_size
def on_button_press(self, event):
# right mouse button clicked
if not self.dragging and event.button in (1, 3):
self.dragging = True
self.dragging_button = event.button
self.mouse_pos = [event.x, event.y]
# left mouse button clicked
if event.button == 1 and event.xdata is not None:
self.move_marker(event.xdata)
def on_button_release(self, event):
if self.dragging and self.dragging_button == event.button:
self.dragging = False
def on_mouse_move(self, event):
if self.dragging and self.dragging_button == 3:
dx = event.x - self.mouse_pos[0]
self.mouse_pos = [event.x, event.y]
x_min, x_max = self.figure.axes[0].get_xlim()
x_range = x_max - x_min
x_factor = x_range / self.axes_size()[0]
self.figure.axes[0].set_xlim([x_min - dx * x_factor, x_max - dx * x_factor])
self.plot.draw()
elif self.dragging and self.dragging_button == 1:
self.move_marker(event.xdata)
def on_mouse_scroll(self, event):
if event.xdata is None:
return
zoom_direction = -1 if event.button == 'up' else 1
zoom_factor = 1 + .4 * zoom_direction
x_min, x_max = self.figure.axes[0].get_xlim()
min = event.xdata + (x_min - event.xdata) * zoom_factor
max = event.xdata + (x_max - event.xdata) * zoom_factor
self.figure.axes[0].set_xlim([min, max])
self.plot.draw()
def move_marker(self, x_position):
y_min, y_max = self.figure.axes[0].get_ylim()
self.marker.set_data((x_position, x_position), (y_min, y_max))
self.plot.draw()
if __name__ == '__main__':
gui = Tk()
vf = PlotFrame(gui)
vf.pack(fill=BOTH, expand=1)
gui.mainloop()
The implementation works fine, but rendering is really slow when displaying a lot of lines. How can I make rendering faster? As you can see in the implementation above, the whole plot is drawn completely every time anything changes which shouldn't be necessary. My thoughts on this:
Resizing the window: draw everything
Zooming: draw everything
Moving the marker: just redraw the marker (one line) instead of drawing everything
Moving the plot in x direction: move the pixels currently displayed in the plot left/right and only draw pixels that are moved into the visible area
Drawing everything when resizing/zooming is fine for me, but I really need faster drawing of the latter two modifications. I already looked into matplotlib's animations, but as far as I understood, they won't help in my case. Any help is greatly appreciated, thanks!
The solution seems to be to cache elements that get redrawn as you said:
One major thing that gets redrawn is the background:
# cache the background
background = fig.canvas.copy_from_bbox(ax.bbox)
After caching restore it using restore region then just re-draw the points/line at every call you need
# restore background
fig.canvas.restore_region(background)
# redraw just the points
ax.draw_artist(points)
# fill in the axes rectangle
fig.canvas.blit(ax.bbox)
To optimize drawing blitting can be used. With it only given artists (those that were changed) will be rendered instead of the whole figure.
Motplotlib uses that technique internally in the animation module. You can use Animation class in it as a reference to implement the same behaviour in your code. Look at the _blit_draw() and several related functions after it in the sources.