[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[pygame] Dirty Rectangle Processing



I worked up a small example to illustrate the dirtyrect
processing method I mentioned earlier.
If you can make this faster, post your results here!
Note that all sprites have an alpha channel that must be maintained.

A screenshot of the output of this sample program can be seen at:

http://www.kamilche.com/images/pygame/DirtyRectsExample.jpg

The original source can be found at:

http://www.kamilche.com/images/pygame/dirtyrects.py

It's a simple program that fills the screen with moving colored boxes,
and illustrates how to toggle between fullscreen and windowed mode in a
cross-platform-compatible way.  It displays the current FPS in the top
left corner.



--Kamilche




'''

Pygame Dirtyrect Processing

An example of dirtyrect processing with Pygame.
Make it faster, if you can!  My speed:
604 FPS on a 2.6 gHz Windows 2000 box w/1 gig memory.
'''


import pygame import random import time import os import sys

screen = None
sprites = []

class Sprite(object):
    pos = (0, 0)
    size = (30, 30)
    rect = None
    paint = 0
    velocity = (3, 2)

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
        self.rect = pygame.Rect((0, 0, 0, 0))
        if type(self.pos) != list:
            self.pos = [random.randint(0, screen.size[0]-1),
                        random.randint(0, screen.size[1]-1)]
        self.size = list(self.size)
        self.velocity = list(self.velocity)
        sprites.append(self)
        self.Paint()

    def Paint(self):
        raise Exception("You must override the paint method!")

    def Draw(self, bg, dirtyrect):
        ' Draw the sprite'
        # Repaint if necessary
        if self.paint:
            self.Paint()
            self.paint = 0

        # Clip picture against the dirtyrect
        r = self.rect.clip(dirtyrect)
        if r:
            # Paint the clipped rect only
            sourcerect = pygame.Rect(r[0]-self.rect[0],
r[1]-self.rect[1], r.w, r.h)
            bg.blit(self.pic, r, sourcerect)

    def Move(self):
        ' Move the sprite'
        newx = self.pos[0] + self.velocity[0]
        newy = self.pos[1] + self.velocity[1]
        if 0 <= newx <= screen.size[0]:
            self.pos[0] = newx
            self.SetRect() # Not paint, because pic didn't change
        else:
            self.velocity[0] = -self.velocity[0]
        if 0 <= newy <= screen.size[1]:
            self.pos[1] = newy
            self.SetRect() # Not paint, because pic didn't change
        else:
            self.velocity[1] = -self.velocity[1]

    def Timer(self, now):
        self.Move()

    def SetRect(self):
        " Dirty this item's current location, set its rectangle based
on its"
        " current size and position, and dirty it again."
        if self.size[0] == 0 or self.size[1] == 0:
            return
        screen.Dirty(self.rect)
        self.rect.topleft = self.pos
        self.rect.size = self.size
        screen.Dirty(self.rect)

class Square(Sprite):

    def Paint(self):
        self.pic = pygame.Surface(self.size, pygame.SRCALPHA,
32).convert_alpha()
        self.pic.fill(self.backcolor)
        self.SetRect()


class Circle(Sprite):

    def Paint(self):
        self.pic = pygame.Surface(self.size, pygame.SRCALPHA,
32).convert_alpha()
        self.pic.fill((0, 0, 0, 0))
        radius = self.size[1]/2
        center = self.size[0]/2, self.size[1]/2
        pygame.draw.circle(self.pic, self.backcolor, center, radius, 0)
        self.SetRect()

class Text(Sprite):
    text = ''
    fontcolor = (255, 255, 255)
    font = None

    def __init__(self, **kwargs):
        self.font = pygame.font.Font(pygame.font.get_default_font(), 24)
        super(Text, self).__init__(**kwargs)
        self.SetText(self.text)

    def Paint(self):
        self.pic = self.font.render(self.text, 1,
self.fontcolor).convert_alpha()
        self.size = self.pic.get_size()
        self.SetRect()

    def SetText(self, text):
        self.text = text
        self.paint = 1 # Paint, because pic DID change.

    def Timer(self, now):
        pass

class FPS(Text):
    nexttime = 0
    ctr = 0

    def Timer(self, now):
        self.ctr += 1
        if now > self.nexttime:
            self.SetText("%d FPS" % self.ctr)
            self.ctr = 0
            self.nexttime = now + 1


class Screen(object): size = [800, 600] pos = [0, 0] dirtyrects = [] backcolor = (0, 64, 0) fullscreen = 0 bitdepth = 16 def __init__(self): self.CreateScreen()

    def Dirty(self, rect):
        " Mark a rectangle on the screen as needing to be redrawn
('Dirty' it)"
        if rect == None:
            # Redraw entire screen
            del self.dirtyrects[:]
            self.dirtyrects = [self.rect]
        else:
            # Redraw part of screen
            R = rect.clip(self.rect)
            i = R.collidelist(self.dirtyrects)
            if i > -1:
                # Add to existing dirty rectangle
                R.union_ip(self.dirtyrects[i])
                self.dirtyrects[i] = (R)
            else:
                # Add a new dirty rectangle
                self.dirtyrects.append(R)

    def Draw(self):
        ' Main render routine'
        while len(self.dirtyrects) > 0:
            r = self.dirtyrects[0]
            del self.dirtyrects[0]
            self.screen.fill(self.backcolor, r)
            for sprite in sprites:
                sprite.Draw(self.screen, r)
            pygame.display.update(r)

    def ToggleFullscreen(self):
        ' Toggle fullscreen mode'
        if self.fullscreen:
            self.fullscreen = 0
        else:
            self.fullscreen = 1
        self.CreateScreen()

    def CreateScreen(self):
        ' Create the main display screen'
        if self.fullscreen:
            flags = pygame.FULLSCREEN
        else:
            flags = 0
        minbitdepth = 16
        bitdepth = pygame.display.mode_ok(self.size, flags, self.bitdepth)
        if bitdepth < minbitdepth:
            raise Exception("Your system is unable to display %d bit
color in an %d x %d window!" % (minbitdepth, self.size[0], self.size[1]))
        self.screen = pygame.display.set_mode(self.size, flags, bitdepth)
        self.rect = pygame.Rect(0, 0, self.size[0], self.size[1])
        self.Dirty(None)

def main():
    global screen
    pygame.init()
    colors = [(255,   0,   0, 200),
              (  0, 255,   0, 200),
              (  0,   0, 255, 200),
              (255, 255,   0, 200),
              (0,   255, 255, 200),
              (255,   0, 255, 200),
              (255, 128,   0, 200),
              (0,   255, 128, 200),
              (128,   0, 255, 200),
              (255, 255, 255, 200)]
    screen = Screen()
    for i in range(10):
        Square(backcolor = colors[i])
    Circle(backcolor = (255, 255, 0, 128), size = [100, 100])
    FPS(pos = [0, 0])
    Text(pos = [150, 0], text = 'Hit ESC to stop, ALT-ENTER to toggle
fullscreen mode.')
    quit = 0
    while not quit:
        for sprite in sprites[:]:
            sprite.Timer(time.time())
        screen.Draw()
        pygame.event.pump()
        for e in pygame.event.get():
            if e.type == pygame.QUIT:
                quit = 1
            elif e.type == pygame.KEYDOWN:
                if e.key == pygame.K_ESCAPE:
                    quit = 1
                elif e.key == pygame.K_RETURN:
                    if pygame.key.get_mods() & pygame.KMOD_ALT:
                        screen.ToggleFullscreen()
    pygame.quit()

if sys.platform == 'win32':
    os.environ['SDL_VIDEO_WINDOW_POS'] = '3,23'
    os.environ['SDL_VIDEODRIVER'] = 'windib'

main()