1

I need to create a surface that has a bounding circle. Anything drawn on that surface should not be visible outside that bounding circle. I've tried using masks, subsurfaces, srcalpha, etc., but nothing seems to work.

My attempt:

w = ss.get_width  ()
h = ss.get_height ()    
    
TRANSPARENT = (255, 255, 255,   0)
OPAQUE      = (  0,   0,   0, 225)
    
crop = pygame.Surface ((w, h), pygame.SRCALPHA, ss)
crop.fill (TRANSPARENT)
    
c = round (w / 2), round (h / 2)
r = 1
pygame.gfxdraw.     aacircle (crop, *c, r, OPAQUE)
pygame.gfxdraw.filled_circle (crop, *c, r, OPAQUE)
    
ss = crop.subsurface (crop.get_rect ())
App.set_subsurface (self, ss)

Later...

self.ss.fill (BLACK)
self.ss.blit (self.background, ORIGIN)

The background is a square image. It should be cropped into the shape of a circle and rendered on screen

Solution based on notes from Rabbid76:

def draw_scene (self, temp=None):
        if temp is None: temp = self.ss
        # 1. Draw everything on a surface with the same size as the window (background and scene).
        size = temp.get_size ()
        temp = pygame.Surface (size)
        self.draw_cropped_scene (temp)

        # 2. create the surface with the white circle.      
        self.cropped_background = pygame.Surface (size, pygame.SRCALPHA)
        self.crop ()

        # 3. blit the former surface on the white circle.
        self.cropped_background.blit (temp, ORIGIN, special_flags=pygame.BLEND_RGBA_MIN)
    
        # 4. blit the whole thing on the window.
        self.ss.blit (self.cropped_background, ORIGIN)

    def draw_cropped_scene (self, temp): App.draw_scene (self, temp)    

An example implementation of crop() is:

def crop (self):
        o, bounds = self.bounds
        bounds = tr (bounds) # round elements of tuple
        pygame.gfxdraw.     aaellipse (self.cropped_background, *bounds, *bounds, OPAQUE)
        pygame.gfxdraw.filled_ellipse (self.cropped_background, *bounds, *bounds, OPAQUE)

1 Answers1

3

The background is a square image. It should be cropped into the shape of a circle and rendered on screen

You can achieve this by using the blend mode BLEND_RGBA_MIN (see pygame.Surface.blit).

Create a transparent pygame.Surface with the same size as self.background. Draw a whit circle in the middle of the Surface and blend the background on this Surface using the blend mode BLEND_RGBA_MIN. Finally you can blit it on the screen:

size = self.background.get_size()
self.cropped_background = pygame.Surface(size, pygame.SRCALPHA)
pygame.draw.ellipse(self.cropped_background, (255, 255, 255, 255), (0, 0, *size))
self.cropped_background.blit(self.background, (0, 0), special_flags=pygame.BLEND_RGBA_MIN)
self.ss.fill(BLACK)
self.ss.blit(self.cropped_background, ORIGIN)

Minimal example: repl.it/@Rabbid76/PyGame-ClipCircularRegion-1

import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))

background = pygame.Surface(window.get_size())
for x in range(5):
    for y in range(5):
        color = (255, 255, 255) if (x+y) % 2 == 0 else (255, 0, 0)
        pygame.draw.rect(background, color, (x*50, y*50, 50, 50))

size = background.get_size()
cropped_background = pygame.Surface(size, pygame.SRCALPHA)
pygame.draw.ellipse(cropped_background, (255, 255, 255, 255), (0, 0, *size))
cropped_background.blit(background, (0, 0), special_flags=pygame.BLEND_RGBA_MIN)

run = True
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        
    window.fill(0)
    window.blit(cropped_background, (0, 0))
    pygame.display.flip()

See Also How to fill only certain circular parts of the window in pygame?

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • I feel like we're getting warmer. size = ss.get_size() self.cropped_background = pygame.Surface(size, pygame.SRCALPHA) pygame.draw.ellipse(self.cropped_background, (255, 255, 255, 255), (0, 0, *size)) self.cropped_background.blit(ss, (0, 0), special_flags=pygame.BLEND_RGBA_MIN) However, calling App.set_subsurface (self, ss) causes no cropping to happen when set_background() is called later, and calling App.set_subsurface (self, cropped_background) causes nothing to be drawn when set_background() is called. – Innovations Anonymous Sep 26 '20 at 09:34
  • @user1456735 I have no idea what you want to say. We are not getting warmer, but you overcomplicate things. It is not possible that something is cropped when you _blit_ on the window. You have to do it the other way around. 1. Draw everything on a surface with the same size as the window (background and scene). 2. create the surface with the white circle. 3 _blit_ the former surface on the white circle. 4. _blit_ the whole thing on the window. – Rabbid76 Sep 26 '20 at 09:41
  • 1
    That clarified it. Thanks. Doing it the other way around worked. – Innovations Anonymous Sep 26 '20 at 10:20