Remove border from opencv generated ellipse in pygame
Question:
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:
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.
Answers:
You do have an alpha channel… However, OpenCV is dumb, doesn’t know what "alpha" is. It simply blends the commanded values of each channel with what’s there already (black!), and what you’re getting is effectively premultiplied alpha.
You need to tell pygame about this, and hope that it knows how to deal with that.
If that’s not possible, you’ll have to un-premultiply, i.e. divide, your RGB channels by the A channel. Then those border pixels are actually white, instead of already being blended with black, and the blending will work properly, assuming you can blend with "straight alpha" semantics.
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.)
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:
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.
You do have an alpha channel… However, OpenCV is dumb, doesn’t know what "alpha" is. It simply blends the commanded values of each channel with what’s there already (black!), and what you’re getting is effectively premultiplied alpha.
You need to tell pygame about this, and hope that it knows how to deal with that.
If that’s not possible, you’ll have to un-premultiply, i.e. divide, your RGB channels by the A channel. Then those border pixels are actually white, instead of already being blended with black, and the blending will work properly, assuming you can blend with "straight alpha" semantics.
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.)