Search code examples
pythonopencvpygameborderellipse

Remove border from opencv generated ellipse in pygame


I've set up this window where I blit an ellipse to the screen. I would like to have the ellipse fully white with a smooth border. It looks acceptable, but when I add the ellipse to a white background, the border of the ellipse shows up.

import pygame
import cv2
import numpy as np

pygame.init()

# Set up the Pygame window
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))

def drawAACircle(surf, color, center, radius, width, angle):
    circle_image = np.zeros((radius*2, radius*2, 4), dtype = np.uint8)
    circle_image = cv2.ellipse(circle_image, (radius, radius), (radius-width, radius-width), (angle*-.5)-90 , 0, angle, (*color, 255), width, lineType=cv2.LINE_AA)  
    #draw it on the surface
    surf.blit(pygame.image.frombuffer(circle_image.tobytes(), circle_image.shape[1::-1], "RGBA").convert_alpha(), (center[0]-radius, center[1]-radius))

# Wait for the user to close the window
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()
    
    screen.fill((255,255,255))

    drawAACircle(screen, (255,255,255), (screen_width/2,screen_height/2), 200, 20, 360)

    # Update the display
    pygame.display.flip()

I noticed the border when I changed the background to fully white:

black and white ellipse

I'm currently developing a decal tool where I can stack multiple ellipses on top of each other. I refresh the background screen with a copy() like this:

def takeSnapshot():
    global snapshot
    snapshot = screen.copy()

def clearSnapshot():
    global snapshot
    snapshot = None

----
# Run the main loop
running = True
while running:
    events = pygame.event.get()
    for event in events:
        if event.type == pygame.QUIT:
            running = False      

    
    # Clear the screen
    if snapshot is None:
        screen.fill((0,0,0))
         
    else:
        screen.blit(snapshot, (0,0))

I've tried gfx.aapolygon icm with a filled polygon. But I just can't get it as crisp and clean as the opencv ellipse.

If someone knows an alternative I'll be happy to hear, or I might be overlooking something where I could just get the opencv ellipse fully white.

EDIT: just to make it clear, I've chosen the opencv ellipse for the thickness option, be able to make an arc shape, and the smooth looks.


Solution

  • The problem is that antialiasing is applied not only to the alpha channel, but also to the color channels (RGB channels). Basically, this means that the color channels are already multiplied by the alpha channel. You have to use the mode "BLEND_PREMULTIPLIED" to blend this texture correctly (see blit):

    def drawAACircle(surf, color, center, radius, width, angle):
        circle_image = np.zeros((radius*2, radius*2, 4), dtype = np.uint8)
        circle_image = cv2.ellipse(circle_image, (radius, radius), (radius-width, radius-width), (angle*-.5)-90 , 0, angle, (*color, 255), width, lineType=cv2.LINE_AA)  
        circle_surf = pygame.image.frombuffer(circle_image.tobytes(), circle_image.shape[1::-1], "RGBA")
        pos = (center[0]-radius, center[1]-radius)
        
        surf.blit(circle_surf, pos, special_flags=pygame.BLEND_PREMULTIPLIED)
    

    (I'm not showing the result here because it's just all white.)