This program was a sketch for the little bursts in the music sequencer, but the background makes for an interesting effect.
#!/usr/bin/python # Draw a cool burst on the screen. # Has the following interesting features: # - The area covered by any individual color remains constant as the # burst expands, once it's out far enough that there's an empty part # in the middle. I think. # - Nicely alpha-blends with background. # - Starts expanding rapidly and then slows down a bit. # - Renders at about 60fps on my laptop in 320x240, or 30fps in 640x480 # Currently has the following problems: # - the burst surface is bigger than needed # - I don't understand how to adjust the clock to do the right thing. # - I'd like the palette to make a nice gradual transition from white, # down to red, down to yellow, and then down to transparent. # - Too symmetrical. # - The code is too wordy. import pygame, Numeric, time, math burstsize = 40 fuzz = 20 def scale(mask, component): "Encode a floating-point color component in [0, 1] with a bitmask." mask = mask % (2**32) # make unsigned rv = int(mask * component) & mask # scale mask by weight if rv >= 2**31: rv -= 2**32 # convert back to signed rv = int(rv) # convert back to non-long return rv def pixel(masks, components): "Encode an (r, g, b, alpha) tuple according to given masks." return sum(map(scale, masks, components)) class World: def __init__(self, screen): self.screen = screen # make a simple, pretty background. bxs, bys = Numeric.indices(self.screen.get_size()) self.background = bxs * bys * bys / 128 surfsize = (burstsize+fuzz)*2 self.burstsurface = pygame.Surface((surfsize,surfsize)).convert_alpha() xs, ys = Numeric.indices(self.burstsurface.get_size()) (dx, dy) = (xs - surfsize/2, ys - surfsize/2) # distances from center self.rsq = dx*dx + dy*dy # square of distances from center # Compute where to blit the surface cx, cy = self.screen.get_rect().center self.blitdest = (cx - surfsize/2, cy - surfsize/2) # Red and green stay at 100%; blue fades from 80% down to 0, # and alpha fades from 90% down to 0. Then the last palette # value is entirely transparent. masks = self.burstsurface.get_masks() self.palette = Numeric.array([pixel(masks, (1, 1, 0.8 * float(fuzz**2-ii)/fuzz**2, 0.9 * float(fuzz**2-ii)/fuzz**2)) for ii in range(fuzz**2)] + [0]) self.palette = self.palette.astype(Numeric.Int32) # just to make sure def redraw(self): pygame.surfarray.blit_array(self.screen, self.background) # The threshold value of r-squared with maximum brightness. I # fiddled with this until it was kind of OK, but I don't # really like it. clock = burstsize**2 - ((-time.time() / 3 % 2 - 1) * burstsize)**2 # the per-pixel distance from that threshold burst = Numeric.absolute(self.rsq - clock) # mapped to fit the palette burst_clamped = Numeric.clip(burst, 0, fuzz**2).astype(Numeric.Int) burst_image = Numeric.take(self.palette, burst_clamped) pygame.surfarray.blit_array(self.burstsurface, burst_image) self.screen.blit(self.burstsurface, self.blitdest) def main(): pygame.init() # It gets really trippy if you specify certain bit depths in place # of 0. Like 22. I assume this is some kind of SDL bug, but it's # really cool. screen = pygame.display.set_mode((320, 240), pygame.FULLSCREEN, 0) world = World(screen) frames = 0 start = time.time() while 1: ev = pygame.event.poll() if ev.type == pygame.NOEVENT: world.redraw() pygame.display.flip() frames += 1 elif ev.type == pygame.QUIT: break elif ev.type == pygame.MOUSEBUTTONDOWN: break dur = time.time() - start print "%d frames; %.2f/sec" % (frames, frames/dur) if __name__ == '__main__': main()