1

I need to draw a circle filled with random gray colors and a black outline using pygame. This is what it should look like:

enter image description here

The radius increases by expansion_speed * dt every frame and the surface is updated 60 times per second, so however this is achieved (if even possible) needs to be fast. I tried masking an stored texture but that was too slow. My next idea was to read the pixels from this stored texture and only replace the difference between the last and current surfaces. I tried this too but was unable to translate the idea to code.

So how can this be done?

bzrr
  • 1,490
  • 3
  • 20
  • 39
  • Is the noise moving each frame, staying constant, or being scaled up with the circle? – piojo Feb 26 '15 at 05:16
  • The noise is constant, only the circle can expand and move. – bzrr Feb 26 '15 at 05:57
  • When you masked the stored texture, were you using a shader? Python is ill-suited to fast pixel-level manipulation. I think you could easily use the masking option, if you handle the masking with a shader that takes two textures as the input. – piojo Feb 26 '15 at 06:23
  • And as for your "update only the difference" idea, that would certainly be faster than regenerating the whole thing. Some issues I see are that you would need to keep track of which noise pieces overlap with the circle's edge, so they can be redrawn when the circle expands. And then it's just a problem of choosing where to draw the new noise particles in the new available space. If you try it and post the code, I'm sure you can get some specific help. – piojo Feb 26 '15 at 06:26
  • I just used another surface as suggested here: http://stackoverflow.com/questions/16880128/pygame-is-there-any-way-to-only-blit-or-update-in-a-mask/16930209#16930209 – bzrr Feb 26 '15 at 06:34
  • It looks like that's just doing the same thing a shader would do, but in python code. If you put the operations in a shader, the GPU will do it and it will be easily fast enough. If you do it the simpler way and put one texture physically in front of the other, it will still be rendered by the GPU and will probably be fast enough. But big transparent things that cover the screen actually can chew up performance, so that's not a sure thing. – piojo Feb 26 '15 at 06:51
  • I should clarify, I'm sure the function you used for masking is very efficient. It's not a silly per-pixel manipulation. But it's still not in the same league as using the GPU to composite two images at the same time as they're drawn. – piojo Feb 26 '15 at 07:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/71746/discussion-between-piojo-and-jovito). – piojo Feb 26 '15 at 07:41

2 Answers2

0

See my update to your previous related question. It has some info about performance. You could try to enable hardware acceleration in fullscreen mode, but I never personally tried it, so can't give good advice how to do it properly. Just use two differnt colorkeys for extracting circle from noise and putting the whole surface to the display. Note that if your Noise surface has pixels same as colorkey color then they also become transparent.
This example I think is what you are trying to get, move the circle with mouse and hold CTRL key to change radius.
Images:
2xtile1.png 2xtile2.png

import os, pygame
pygame.init()
w = 800
h = 600
DISP = pygame.display.set_mode((w, h), 0, 24)
clock = pygame.time.Clock( ) 

tile1 = pygame.image.load("2xtile1.png").convert()
tile2 = pygame.image.load("2xtile2.png").convert()
tw = tile1.get_width()
th = tile1.get_height()
Noise = pygame.Surface ((w,h))
Background = pygame.Surface ((w,h))
for py in range(0, h/th + 2) :
    for px in range(0, w/tw + 2):
        Noise.blit(tile1, (px*(tw-1), py*(th-1) ) )
        Background.blit(tile2, (px*(tw-1), py*(th-1) ) )

color_key1 = (0, 0, 0)
color_key2 = (1, 1, 1)
Circle = pygame.Surface ((w,h))
Circle.set_colorkey(color_key1)
Mask = pygame.Surface ((w,h))
Mask.fill(color_key1)
Mask.set_colorkey(color_key2)

strokecolor = (10, 10, 10)
DISP.blit(Background,(0,0))

def put_circle(x0, y0, r, stroke):
    pygame.draw.circle(Mask, strokecolor, (x0,y0), r, 0)
    pygame.draw.circle(Mask, color_key2, (x0,y0), r - stroke, 0)
    Circle.blit(Noise,(0,0))
    Circle.blit(Mask,(0,0))
    dirtyrect  = (x0 - r, y0 - r, 2*r, 2*r)
    Mask.fill(color_key1, dirtyrect)
    DISP.blit(Circle, (0,0))

X = w/2
Y = h/2
R = 100
stroke = 2
FPS = 25
MainLoop = True
pygame.mouse.set_visible(False)
pygame.event.set_grab(True)

while MainLoop :
    clock.tick(FPS)
    pygame.event.pump()
    Keys = pygame.key.get_pressed()
    MR  = pygame.mouse.get_rel()        # get mouse shift
    if Keys [pygame.K_ESCAPE] :
        MainLoop = False
    if Keys [pygame.K_LCTRL] :
        R = R + MR[0]
        if R <= stroke : R = stroke
    else :
        X = X + MR[0] 
        Y = Y + MR[1] 
    DISP.blit(Background,(0,0))
    put_circle(X, Y, R, stroke)
    pygame.display.flip( )

pygame.mouse.set_visible(True)
pygame.event.set_grab(False)
pygame.quit( )
Community
  • 1
  • 1
Mikhail V
  • 1,416
  • 1
  • 14
  • 23
  • @Jovito try this demo. You should also change the title of the question, since it is about masking the surface in a circle, not drawing a circle. – Mikhail V Mar 01 '15 at 00:58
0

Many years ago we had a font rendering challenge with the Pygame project.

Someone created an animated static text for the contest but it was far too slow.

We put our heads together and made a much quicker version. Step one was to create a smallish image with random noise. Something like 64x64. You may need a bigger image if your final image is large enough to notice the tiling.

Every frame you blit the tiled noise using a random offset. Then you take an image with the mask, in your case an inverted circle, and draw that on top. That should give you a final image containing just the unmasked noise.

The results were good. In our case it was not noticeable that the noise was just jittering around. That may be because the text did not have a large unobstrcted area. I'd be concerned your large circle would make the trick appear obvious. i guess if you really had a large enough tiled image it would still work.

The results and final source code are still online at the Pygame website, http://www.pygame.org/pcr/static_text/index.php

static text

Peter Shinners
  • 3,686
  • 24
  • 24
  • Yay for long-term backward compatibility, the code still runs on Python2.7 and a version of Pygame that isn't 13 years old! – Peter Shinners Mar 01 '15 at 02:28