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()

Reply via email to