Как сделать холст из прямоугольника tkinter с закругленными углами?

Я хотел бы создать прямоугольник с закругленными углами. Я использую Canvas от Tkinter.

2 ответа

Решение

Предлагать альтернативный подход к методу Тобиаса было бы действительно сделать это с одним многоугольником.

Это может иметь преимущество в виде одного объекта Canvas, если вас беспокоит оптимизация или нет необходимости беспокоиться о системе тегов для ссылки на один объект.

Код немного длиннее, но очень простой, поскольку он просто использует идею о том, что при сглаживании многоугольника вы можете дважды задать одну и ту же координату, чтобы "остановить" сглаживание.

Это пример того, что можно сделать:

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()

Используя эту функцию, вы можете просто указать нормальные координаты прямоугольника, а затем указать "радиус", закругленный в углах. Использование **kwargs означает, что вы можете передавать ключевые аргументы, такие как fill="blue"так же, как вы обычно могли бы с create_ метод.

Хотя координаты выглядят сложными, они просто методично обходят каждую точку в "прямоугольнике", дважды давая каждой неугловой точке.

Если вы не возражаете против довольно длинной строки кода, вы можете поместить все координаты в одну строку, сделав функцию всего двумя строками (!). Это выглядит так:

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)

Это приводит к следующему (обратите внимание, что это ОДИН объект canvas):

Скругленный прямоугольник, созданный функцией

Я знаю, что у этого поста уже есть принятый ответ для прямоугольника. Но для тех, кто ищет любой многоугольник с закругленными углами (включая прямоугольник, очевидно), я сделал этот код на основе ответа @SneakyTutle.

roundPolygon(x_array, y_array, sharpness, **kwargs)

Результат

Образ

Логика заключается в том, чтобы включить сглаживание и разместить подпункты рядом с вершиной. Таким образом, будут закруглены только углы, а остальная часть многоугольника останется плоской.

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()

Я не мог придумать хороший способ нормализовать резкость. В любом случае, значение от 2 до 10 будет хорошо для любого случая. Не стесняйтесь изменять код по своему усмотрению.

Просто для визуализации для треугольника с резкостью =8 код результата цикла for выглядит следующим образом. Как вы могли заметить, если резкость равна 2, подточки будут размещены в середине вершины.

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]
    ]

Кажется, нет встроенного метода для этого. Самая близкая вещь была бы ломаной с smooth=1, но это все еще больше похоже на старый экран телевизора, причем его стороны также слегка изогнуты.

Вместо этого вы можете определить вспомогательную функцию, комбинируя скругленный прямоугольник из линий и дуг:

def rounded_rect(canvas, x, y, w, h, c):
    canvas.create_arc(x,   y,   x+2*c,   y+2*c,   start= 90, extent=90, style="arc")
    canvas.create_arc(x+w-2*c, y+h-2*c, x+w, y+h, start=270, extent=90, style="arc")
    canvas.create_arc(x+w-2*c, y,   x+w, y+2*c,   start=  0, extent=90, style="arc")
    canvas.create_arc(x,   y+h-2*c, x+2*c,   y+h, start=180, extent=90, style="arc")
    canvas.create_line(x+c, y,   x+w-c, y    )
    canvas.create_line(x+c, y+h, x+w-c, y+h  )
    canvas.create_line(x,   y+c, x,     y+h-c)
    canvas.create_line(x+w, y+c, x+w,   y+h-c)

Пример:

import tkinter
root = tkinter.Tk()
canvas = tkinter.Canvas(root)
canvas.pack()
rounded_rect(canvas, 20, 20, 60, 40, 10)
root.mainloop()

введите описание изображения здесь

Вы также можете предоставить другой **options параметр для установки ширины линии, цвета и т. д. для отдельных деталей, но проблема в том, что, например, линии и дуги используют разные параметры для цвета линии (fill а также outline соответственно). Кроме того, если вы хотите иметь закрашенный прямоугольник с закругленными углами, вам нужно будет указать это как второй метод, используя несколько прямоугольников.

Другие вопросы по тегам