pygame-community / pygame-ce

🐍🎮 pygame - Community Edition is a FOSS Python library for multimedia applications (like games). Built on top of the excellent SDL library.
https://pyga.me
862 stars 141 forks source link

rounding corners with pg.draw.rect is broken with negative values #2285

Open AndreyVarvar opened 1 year ago

AndreyVarvar commented 1 year ago

Environment: (Those are probably irrelevant, but still)

Current behavior: pygame.draw.rect() has a set of parameters, responsible for rounding the corners. There is a border_radius parameter, which is responsible for all 4 corners, and then there are 4 parameters, that are responsible for a single corner assigned to them. Default value of each parameter is -1, however, if every parameter, responsible for rounding the corners, is negative, and one of them is positive, the shape becomes weird (as in the image)

Screen Shot 2023-07-03 at 22 15 47

This behaviour is NOT present, when all values are negative, no matter what they are, as long as they are negative, which led me to conclusion, that this behaviour is not expected

Expected behaviour:

no broken corners

Steps to reproduce:

1) type pg.draw.rect(surface, color, rect, 0, -n, -n, +n, -n, -n) where n is any number 2) run the script and view the result

NOTE: only the "master" border_radius parameter sets the size of the drawn corners, each individual parameter responsible for only a single corner does not affect the outcome.

Test code

import pygame as pg
pg.init()
running = True
display = pg.display.set_mode((600, 600))

while running:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            pg.quit()
            quit()

    display.fill("black")

    pg.draw.rect(display, (255, 255, 255), (100, 100, 50, 50), 4, -100, -10, -10, 1, -10)
    pg.draw.rect(display, (255, 255, 255), (100, 400, 50, 50), 100, -100, -10, -10, 1, -10)
    pg.draw.rect(display, (255, 255, 255), (400, 400, 50, 50), 0, -100, -10, -10, 1, -10)

    pg.display.update()
AndreyVarvar commented 1 year ago

Additional information: If you mess with the width parameter, you could get weird results like these:

Screen Shot 2023-07-05 at 19 00 41 Screen Shot 2023-07-05 at 19 01 02
AndreyVarvar commented 1 year ago

More additional information: This effect is present on all pygame versions, i tested all pygame-ce versions and recent pygame versions, and they all had the same result. Python version also does not seem to have effect on this.

AndreyVarvar commented 1 year ago

Even more additional information: After doing some more experimenting, i found out, that by having the same setup, you could pass negative width and/or height into the rect parameter of the pg.draw.rect() function. Without that setup, the rect simply would not be drawn, but with broke parameters the rect IS BEING DRAWN, just the x and y position are offset-ed (expected, if you have negative width or height). If either width or height in the rect parameter are set to 1 (both have to be positive), the rect will be draw as normal, just like you would without messing with the rounding parameters.

AndreyVarvar commented 1 year ago

Extra additional information:

https://github.com/pygame-community/pygame-ce/assets/91492313/7a8c8ff4-c900-4a7b-aedb-b9ef9c93fbd3

The video shows, that the problem cannot be solved just by simply setting the border_radius to 0 whenever it is smaller than 0, the problem is somewhere deep in the code.

code:

import pygame as pg
pg.init()
running = True
display = pg.display.set_mode((600, 600))
clock = pg.time.Clock()
n = 50
delta = -1

while running:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            pg.quit()
            quit()

    display.fill("black")

    pg.draw.rect(display, (255, 255, 255), (100, 100, 50, 50), 4, n, n, n, -n, n)
    pg.draw.rect(display, (255, 255, 255), (100, 400, 50, 50), 100, n, n, n, -n, n)
    pg.draw.rect(display, (255, 255, 255), (400, 400, 50, 50), 0, n, n, n, -n, n)

    n += delta

    pg.display.update()

    if n == -100:
        delta = 1
    elif n == 10:
        delta = -1

    clock.tick(10)
AndreyVarvar commented 1 year ago

Turns out the problem CAN be solve by simply making the border_radius parameter be greater than 0, if it ever goes smaller than 0. But there is a small side-effect:

https://github.com/pygame-community/pygame-ce/assets/91492313/39b0ea5a-a16d-47be-b888-d2908b1a8ca4

From the data i've collected so far, my guess is that the maximum "roundness" of a corner is determined by the available width/height, depending on what is smaller, but when the corners are broken, the width and height are starting the increase (for some reason, i dont know, it's just a guess).