How to make a tkinter canvas rectangle with rounded corners?
Offering an alternate approach to tobias's method would be to indeed do it with one polygon.
This would have the advantage of being one canvas object if you are worried about optimization, or not having to worry about a tag system for referring to a single object.
The code is a bit longer, but very basic, as it is just utilizing the idea that when smoothing a polygon, you can give the same coordinate twice to 'stop' the smooth from occuring.
This is an example of what can be done:
from tkinter import *
root = Tk()
canvas = Canvas(root)
canvas.pack()
def round_rectangle(x1, y1, x2, y2, radius=25, **kwargs):
points = [x1+radius, y1,
x1+radius, y1,
x2-radius, y1,
x2-radius, y1,
x2, y1,
x2, y1+radius,
x2, y1+radius,
x2, y2-radius,
x2, y2-radius,
x2, y2,
x2-radius, y2,
x2-radius, y2,
x1+radius, y2,
x1+radius, y2,
x1, y2,
x1, y2-radius,
x1, y2-radius,
x1, y1+radius,
x1, y1+radius,
x1, y1]
return canvas.create_polygon(points, **kwargs, smooth=True)
my_rectangle = round_rectangle(50, 50, 150, 100, radius=20, fill="blue")
root.mainloop()
Using this function, you can just provide the normal coordinates that you would to a rectangle, and then specify the 'radius' which is rounded in the corners. The use of **kwargs
denotes that you can pass keyword arguments such as fill="blue"
, just as you usually could with a create_
method.
Although the coords look complex, it is just going around methodically to each point in the 'rectangle', giving each non-corner point twice.
If you didn't mind a rather long line of code, you could put all the coordinates on one line, making the function just 2 lines(!). This looks like:
def round_rectangle(x1, y1, x2, y2, r=25, **kwargs):
points = (x1+r, y1, x1+r, y1, x2-r, y1, x2-r, y1, x2, y1, x2, y1+r, x2, y1+r, x2, y2-r, x2, y2-r, x2, y2, x2-r, y2, x2-r, y2, x1+r, y2, x1+r, y2, x1, y2, x1, y2-r, x1, y2-r, x1, y1+r, x1, y1+r, x1, y1)
return canvas.create_polygon(points, **kwargs, smooth=True)
This produces the following (Note in mind this is ONE canvas object):
If you want to update the position of the rectangle after it has been created, you could use a function like this (if in the same scope as the original canvas
object):
def update_rectangle_coords(round_rect, x1, y1, x2, y2, r=25):
points = (x1+r, y1, x1+r, y1, x2-r, y1, x2-r, y1, x2, y1, x2, y1+r, x2, y1+r, x2, y2-r, x2, y2-r, x2, y2, x2-r, y2, x2-r, y2, x1+r, y2, x1+r, y2, x1, y2, x1, y2-r, x1, y2-r, x1, y1+r, x1, y1+r, x1, y1)
canvas.coords(round_rect, *points)
So, to update my_rectangle
's position (from the first code example), we could say:
update_rectangle_coords(my_rectangle, 20, 20, 100, 100)
I know this post already has an accepted answer for a rectangle. But for those looking for any polygon with rounded corners (rectangle included obviously), I made this code based on @SneakyTutle's answer.
roundPolygon(x_array, y_array, sharpness, **kwargs)
Result
The logic behind this is to enable smooth and place subpoints next to the vertex. This way, only the corners are going to be rounded and the rest of the polygon is kept flat.
from tkinter import *
root = Tk()
canvas = Canvas(root, width = 1000, height = 1000)
canvas.pack()
def roundPolygon(x, y, sharpness, **kwargs):
# The sharpness here is just how close the sub-points
# are going to be to the vertex. The more the sharpness,
# the more the sub-points will be closer to the vertex.
# (This is not normalized)
if sharpness < 2:
sharpness = 2
ratioMultiplier = sharpness - 1
ratioDividend = sharpness
# Array to store the points
points = []
# Iterate over the x points
for i in range(len(x)):
# Set vertex
points.append(x[i])
points.append(y[i])
# If it's not the last point
if i != (len(x) - 1):
# Insert submultiples points. The more the sharpness, the more these points will be
# closer to the vertex.
points.append((ratioMultiplier*x[i] + x[i + 1])/ratioDividend)
points.append((ratioMultiplier*y[i] + y[i + 1])/ratioDividend)
points.append((ratioMultiplier*x[i + 1] + x[i])/ratioDividend)
points.append((ratioMultiplier*y[i + 1] + y[i])/ratioDividend)
else:
# Insert submultiples points.
points.append((ratioMultiplier*x[i] + x[0])/ratioDividend)
points.append((ratioMultiplier*y[i] + y[0])/ratioDividend)
points.append((ratioMultiplier*x[0] + x[i])/ratioDividend)
points.append((ratioMultiplier*y[0] + y[i])/ratioDividend)
# Close the polygon
points.append(x[0])
points.append(y[0])
return canvas.create_polygon(points, **kwargs, smooth=TRUE)
my_rectangle = roundPolygon([50, 350, 350, 50], [50, 50, 350, 350], 10 , width=5, outline="#82B366", fill="#D5E8D4")
my_triangle = roundPolygon([50, 650, 50], [400, 700, 1000], 8 , width=5, outline="#82B366", fill="#D5E8D4")
root.mainloop()
I couldn't figure a nice way to normalize the sharpness. Anyway, something between 2 and 10 will be good for any case. Feel free to change the code as you wish.
Just for visualization, for a triangle with sharpness=8 the result code for the for loop is as follows. As you might note, if sharpness is 2, the sub-points are going to be placed in the middle of the vertex.
points = [
# Begin vertex
x[0], y[0],
# Between vertices
(7*x[0] + x[1])/8, (7*y[0] + y[1])/8,
(7*x[1] + x[0])/8, (7*y[1] + y[0])/8,
# Vertex
x[1], y[1],
# Between vertices
(7*x[1] + x[2])/8, (7*y[1] + y[2])/8,
(7*x[2] + x[1])/8, (7*y[2] + y[1])/8,
# Vertex
x[2], y[2],
# Between vertices
(7*x[2] + x[0])/8, (7*y[2] + y[0])/8,
(7*x[0] + x[2])/8, (7*y[0] + y[2])/8,
# End/Begin vertex
x[0], y[0]
]