""" bubblePaint_0_2_1.py Eric Pavey - 2009-09-19 warpcat@sbcglobal.net http://www.akeric.com/blog/?page_id=659 Make bubbles!!! updates: v.0.2.1 * Updating hotkeys to have better color control * Updating onscreen docs with better formatting. * Added "blur" filter to background image. * Added a bunch more brush images. ToDo: * "settle dry" behavior * Create "drip" behavior * Create "push brush" * Create blocking rigid bodies Maybe do, maybe not: * Dry to separate layers (maybe not, bad performance) * Images for bubbles (maybe not? (slow)) * Audio for bubble creation, drying, and undoing (maybe not, sounds funny). Directions: * See the "setupOverlay" function below. Snowflake images discovered here: http://snowflakebentley.com/snowflakes.htm http://www.its.caltech.edu/~atomic/snowcrystals/ Flowers: http://www.flickr.com/photos/fotos_ilca/2033281145/, used with permission. """ __version__ = '0.2.1' #-------------- # imports & initis import random import math import glob import copy import sys import os import re import pygame from pygame.locals import * import pymunk from pymunk import Vec2d as Vec2d pygame.init() pymunk.init_pymunk() #-------------- # Constants and Global Vars FRAMERATE = 30 # Control the gravity of the scene: GRAV = 100 # damping: 0 = full damping, 1 = no damping (seems backwards...) DAMPING = .05 # make our bubbles get more of the mouse motion: BUBBLE_VEL_MULT = 1 # How much spin do bubbles pick up from mouse motion? BUBBLE_SPIN_MULT = .1 # Dir where we save our screenshots, relative to app location SCREENSHOT_DIR = "screenshot" # dir where we save our brushes, relative to app location: BRUSH_DIR = "brushes" # How much to blur the background surface when it is "dried" DRYBLUR = 2 # Turn on debug graphics, etc. DEBUG = False # Change size of screen font: FONTSIZE = 14 #---------------- # classes and functions: class Bubble(object): """ A 'dynamic bubble object' displayed to the screen. """ def __init__(self, drawSurf, drySurf, space, radius, color, image, pos=(0,0), impulse=(0,0), friction=10): """ drawSurf : pygame.Surface that this bubble will draw to (main screen). drySurf : pygame.Surface that this bubble will 'dry' to (background surface) space : pymunk.Space radius : float pos : (#, #) : Starting (x, y) position in space impulse : (#, #) : vector to apply to the initial velocity of our bubbles, to make them 'move' a bit based on the mouse motion at time of creation. color : pygame.Color. Will be duplicated for this object. friction : float : 0->1.0 (usefull?) image : string : Relative path to image on disk to draw. """ self.drawSurf = drawSurf self.drySurf = drySurf self.surfWidth, self.surfHeight = self.drawSurf.get_size() # Update PyMunk stuff:-------------------- self.space = space self.radius = random.uniform(.85, 1.15)*radius self.mass = math.pi * pow(radius, 2) # Make inertia: self.inertia = pymunk.moment_for_circle(self.mass, 0, self.radius, (0,0)) # Create rigid body: self.body = pymunk.Body(self.mass, self.inertia) # Set init posiiton: self.body.position = pos # randomly rotate the body: self.body.angle = random.uniform(0.0, math.pi*2) # Add collision shape self.shape = pymunk.Circle(self.body, self.radius, (0,0)) self.shape.friction = friction # Add both body and shape to our space for sim self.space.add(self.body, self.shape) # Give our bubbles some default motion based on the mouse direction: # Remembe to flip the y axis.... self.body.velocity = Vec2d((impulse[0], -impulse[1]))*BUBBLE_VEL_MULT if impulse[0] >= 0: self.body.angular_velocity = -impulse[0]*BUBBLE_SPIN_MULT else: self.body.angular_velocity = impulse[0]*BUBBLE_SPIN_MULT #self.body.angular_velocity = 10 * random.choice([1,-1]) # Add shading info:----------------------- self.color = self.colorAdjust(color) # Create the texture to display: self.image = texture_load(image) # texture is presumed to be square: self.texSize = self.image.get_size() if self.texSize[0] != self.texSize[1]: raise Exception("Image '%s' does not have the same x,y resolution, needs to be square" %texture) # Tint the texture based on color: self.image.fill(self.color, special_flags=BLEND_RGBA_MULT) # Resize texture based on radius. Make slightly larger than collision surface. self.image = pygame.transform.smoothscale(self.image, (int(self.radius*2.5), int(self.radius*2.5))) # backup version of our texture used for scaling: self._image = self.image.copy() # Currently, will be spinning when birthed: self.rotating = True def __del__(self): # If this bubble is deleted, also remove it's pymunk goodies from the current space: self.space.remove(self.shape, self.body) def draw(self, mode): """ Draw to screen, or dry to screen. If mode = 'draw', will draw to the self.drawSurf If mode = 'dry', will draw (dry) to the self.drySurf """ pos = (int(self.body.position.x), self.surfHeight-int(self.body.position.y)) # If the bubble is still rotating, then compute new rotated bubble surface. # Otherwise, lock rotation values and disable future rotation, and surface # generation. Speeds up draw on non-rotating bubbles. if abs(self.body.angular_velocity) > .1 and self.rotating: rot = (self.body.angle * 180.0) / math.pi self.image = pygame.transform.rotozoom(self._image, rot, 1.0) else: self.rotating = False self.rect = self.image.get_rect() self.rect.center = pos if mode == 'draw': self.drawSurf.blit(self.image, self.rect) elif mode == 'dry': self.drySurf.blit(self.image, self.rect) else: raise Exception("mode must be either 'draw' or 'dry'") if DEBUG: # use to debug draw rotation lines: end = (math.cos(self.body.angle)*self.radius+pos[0], -math.sin(self.body.angle)*self.radius+pos[1]) pygame.draw.line(self.drawSurf, complimentary_color(self.color), pos, end) def boundsCheck(self): # Test to see if out of bounds of screen or not if -self.radius > self.body.position.x \ or self.body.position.x > self.surfWidth+self.radius \ or -self.radius > self.body.position.y \ or self.body.position.y > self.surfHeight+self.radius: return True else: return False def colorAdjust(self, color, modAmt=10): # subtly modifiy our color when painting. Currently only modifies hue copyCol = copy.copy(color) hsva = copyCol.hsva modH = random.uniform(-modAmt,modAmt) newHue = int((hsva[0]+modH)%360) try: copyCol.hsva = (int(newHue), int(hsva[1]), int(hsva[2]), int(hsva[3])) except ValueError, e: print "Applied Color Values (something is wrong with one or more):", (int(newHue), int(hsva[1]), int(hsva[2]), int(hsva[3])) print e return Color(copyCol.r, copyCol.g, copyCol.b, copyCol.a) class BubbleBrush(object): """ Create a representation of the brush that bubbles will be 'drawn with' on screen. Will follow the position of the mouse. """ def __init__(self, surface, drySurf, space, color, bubbleGroup, pressure=1, radius=64): """ surface : pygame.Surface to draw to. drySurf : pygame.Surface that bubbles this brush creates will 'dry' to (background surface) space : pymunk.Space color : pygame.Color. This color is expected to change external to this object, thus modifying the object on the fly. bubbleGroup : list : the Bubbles created by the brush are added to this for drawing. pressure : int : how many bubbles to make at once. radius : float """ self.surface = surface self.drySurf = drySurf self.surfWidth, self.surfHeight = self.surface.get_size() self.space = space self.color = color self.bubbleGroup = bubbleGroup self.pressure = pressure self.radius = radius self.pos = pygame.mouse.get_pos() self.relPos = pygame.mouse.get_rel() self.minRadius = 3.0 self.maxRadius = self.surfHeight / 4.0 self.brushes = getBrushes() self.brushIndex = 4 self.texture = None self.image = None self._image = None self.texSize = None self.setBrush() self.events = None self.heldKeys = None # store our own internal color values, since when a Color object's # hue or saturation goes to zero, it will zero out the other members. self.hue = int(color.hsva[0]) self.sat = int(color.hsva[1]) self.val = int(color.hsva[2]) self.alpha = int(color.hsva[3]) def setBrush(self): # Choose a random brush texture based on our current brush index: self.texture = random.choice(self.brushes[self.brushIndex]) # Create the texture to display: self.image = texture_load(self.texture) self._image = self.image.copy() # texture is presumed to be square: self.texSize = self.image.get_size() if self.texSize[0] != self.texSize[1]: raise Exception("Image '%s' does not have the same x,y resolution, needs to be square" %self.texture) def set_events(self, events): """ Pass the pygame event list into the brush. These are passed in during every loop to query what events the brush cares about have been held down. """ self.events = events def set_heldkeys(self, heldKeys): """ Pass the pygame list of held keys into the brush These are passed in during every loop to query what keys the brush cares about have been held down. """ self.heldKeys = heldKeys def drawBrush(self): """ Physically draw the brush, define behavior, capture mouse press, etc. """ # Draw self to surface at mouse position. self.pos = pygame.mouse.get_pos() self.relPos = pygame.mouse.get_rel() # Tint the texture based on color: self.image = self._image.copy() self.image.fill(self.color, special_flags=BLEND_RGBA_MULT) # Resize image based on radius. Make slightly larger than collision surface. resizeSurf = pygame.transform.smoothscale(self.image, (int(self.radius*2.5), int(self.radius*2.5))) self.rect = resizeSurf.get_rect() self.rect.center = self.pos self.surface.blit(resizeSurf, self.rect) pygame.draw.circle(self.surface, Color("white"), self.pos, int(self.radius), 1) pygame.draw.circle(self.surface, Color("white"), self.pos, 3, 1) # Event Detection---------------------------- # Mouse Button Checking:------------------ mouseButtons = pygame.mouse.get_pressed() # If the LMB is held, draw circles: if mouseButtons[0]: self.paint() # If the MMB (wheel) is held, change radius: if mouseButtons[1]: self.radius = self.radius - self.relPos[1]*.5 if self.radius > self.maxRadius: self.radius = self.maxRadius if self.radius < self.minRadius: self.radius = self.minRadius # If the RMB is held, + either q, w, or e: change color if mouseButtons[2]: if self.heldKeys[K_q]: self.colorMod('hue') if self.heldKeys[K_w]: self.colorMod('sat') if self.heldKeys[K_e]: self.colorMod('val') # Event checking:------------------------------ # Mouse wheel, for changing the brush image to draw: for event in self.events: if event.type == MOUSEBUTTONDOWN: # Mouse wheel up if event.button == 4: self.brushIndex += 1 if self.brushIndex > len(self.brushes)-1: self.brushIndex = 0 self.setBrush() # Mouse wheel down if event.button == 5: self.brushIndex -= 1 if self.brushIndex < 0: self.brushIndex = len(self.brushes)-1 self.setBrush() # key-press, for changing pressure elif event.type == KEYDOWN: for i,e in enumerate((K_1, K_2, K_3, K_4, K_5, K_6, K_7, K_8, K_9)): if event.key == e: self.pressure = i+1 def colorMod(self, mod): # Change the brush color based on mouse motion and keypress #hsva = self.color.hsva revY = -self.relPos[1] offset = int((self.relPos[0] + revY) * .25) if mod == "hue": self.hue = int((self.hue+offset)%360) if mod == "sat": self.sat = int(self.sat+offset) if self.sat > 100: self.sat = 100 if self.sat < 0: self.sat = 0 if mod == "val": self.val = int(self.val+offset) if self.val > 100: self.val = 100 if self.val < 0: self.val = 0 try: self.color.hsva = (int(self.hue), int(self.sat), int(self.val), int(self.alpha)) #print self.color.hsva except ValueError, e: print e, ": ", int(self.hue), int(self.sat), int(self.val), int(self.alpha) def paint(self): """ paint bubbles to the screen. """ for i in range(self.pressure): # we modify the Y axis, because PyMunk coords are different from PyGame. randX = random.uniform(-2, 2) randY = random.uniform(-2, 2) pos = (self.pos[0] + randX, self.pos[1] + randY) self.bubbleGroup.append( Bubble(self.surface, self.drySurf, self.space, self.radius, self.color, self.texture, (pos[0], self.surfHeight-pos[1]), self.relPos) ) # now reset which brush image to use next time: self.setBrush() def getColorUnder(self): # set the current color to the color under the brush self.color = self.surface.get_at(self.pos) def getComplimentary(self): # get the complimentary ('opposite') color based on the current color of the brush. self.color = complimentary_color(self.color) class DrawSurf(object): """ Create a surface used for drawing to the screen. """ def __init__(self, destSurf, color=None): self.color = color self.destSurf = destSurf self.surface = self.makeSurf() def makeSurf(self): """ Make a new pygame.Surface th same size as the given destSurf """ layer = None if self.color is not None: layer = pygame.Surface(self.destSurf.get_size(), flags=pygame.HWSURFACE) layer = layer.convert() layer.fill(self.color) else: layer = pygame.Surface(self.destSurf.get_size(), flags=pygame.SRCALPHA|pygame.HWSURFACE, depth=32) layer.convert_alpha() layer.fill((0,0,0,0)) return layer def draw(self): # Draw this surface to the defined drawSurface self.destSurf.blit(self.surface, (0,0)) def update(self, color): # used when updating the color of the bg (will clear any blitted info to it) self.color = color self.surface.fill(self.color) def complimentary_color(col): # Given a color, find the complimentary color to it. newC = copy.copy(col) hsva = newC.hsva newHue = (hsva[0]+180)%360 newC.hsva = (newHue, hsva[1], hsva[2], hsva[3]) return newC def setupOverlay(overlay, font): """ Draw text to our overlay screen. """ class Row(object): # Class to store the current row location on our UI # Each time it is called, it will increment its value def __init__(self, val): self._val = 0 self.orig = val @property def val(self): self._val = self._val + self.orig return self._val row = Row(FONTSIZE) width, height = overlay.get_size() # Top text renderTextTop = [] renderTextTop.append( font.render("-----------------------------------------", 1, Color("white")) ) renderTextTop.append( font.render("t : Toggle this information screen (pros toggle off while painting...)", 1, Color("white")) ) renderTextTop.append( font.render("Window is resizable", 1, Color("white")) ) renderTextTop.append( font.render("Expects a three-button mouse, with mouse wheel. Can't vouch for results otherwise :)", 1, Color("white")) ) renderTextTop.append( font.render("Please visit the homepage for source code, tips, and more instruction: http://www.akeric.com/blog/?page_id=659", 1, Color("white")) ) renderTextTop.append( font.render("", 1, Color("white")) ) renderTextTop.append( font.render("PAINTING---------------------------------", 1, Color("white")) ) renderTextTop.append( font.render("LMB : DRAW Bubbles", 1, Color("white")) ) renderTextTop.append( font.render("MMB-hold-move up/down : change bubble SIZE", 1, Color("white")) ) renderTextTop.append( font.render("Mouse-wheel : Select a new BRUSH CATEGORY from the current brush library", 1, Color("white")) ) renderTextTop.append( font.render(" * You can make your own custom brushes, please visit the web site for directions", 1, Color("white")) ) renderTextTop.append( font.render("Numbers 1-9: Set the current 'spray pressure' (number of bubbles sprayed out at once", 1, Color("white")) ) renderTextTop.append( font.render("You can hold down multiple mouse buttons and keys at the same time while painting to get different effects", 1, Color("white")) ) renderTextTop.append( font.render("", 1, Color("white")) ) renderTextTop.append( font.render("COLORS-----------------------------------", 1, Color("white")) ) renderTextTop.append( font.render("Brush COLOR ('hue/saturation/value')is mapped to keys 'q, w, e':", 1, Color("white")) ) renderTextTop.append( font.render(" * Hold RMB + 'q' + move mouse : change brush HUE (color spectrum)", 1, Color("white")) ) renderTextTop.append( font.render(" * Hold RMB + 'w' + move mouse : change brush SATURATION (grey -> full color)", 1, Color("white")) ) renderTextTop.append( font.render(" * Hold RMB + 'e' + move mouse : change brush VALUE (dark -> light)", 1, Color("white")) ) renderTextTop.append( font.render(" * Mouse motion: Right/up = positive values. Left/down = negative values", 1, Color("white")) ) renderTextTop.append( font.render("'p' : Will UPDATE the color of the brush, based on what color is under the brush", 1, Color("white")) ) renderTextTop.append( font.render("'o' : Will SWAP the current brush color with its complimentary (opposite) color", 1, Color("white")) ) renderTextTop.append( font.render("'b' : Set the BACKGROUND color to the current brush color (will clear the background of any *dried* bubbles)", 1, Color("white")) ) renderTextTop.append( font.render("", 1, Color("white")) ) renderTextTop.append( font.render("BUBBLE MANAGEMENT------------------------", 1, Color("white")) ) renderTextTop.append( font.render("'c' : Clear/remove all *dynamic* bubbles. Doesn't modify the background image", 1, Color("white")) ) renderTextTop.append( font.render("'d' : 'Dry' the dynamic bubbles to the background, allowing painting on top. Button can be held down", 1, Color("white")) ) renderTextTop.append( font.render("'f' : 'Filter' the background image. Button can be held down. Currently only supports 'blur'", 1, Color("white")) ) renderTextTop.append( font.render("", 1, Color("white")) ) renderTextTop.append( font.render("GRAVITY----------------------------------", 1, Color("white")) ) renderTextTop.append( font.render("'g' : Toggles on\off 'gravity'. Default direction is 'down'", 1, Color("white")) ) renderTextTop.append( font.render("'arrows' : Defines direction of gravity, and turns gravity on", 1, Color("white")) ) renderTextTop.append( font.render("", 1, Color("white")) ) renderTextTop.append( font.render("", 1, Color("white")) ) renderTextTop.append( font.render("SCENE MANAGEMENT-------------------------", 1, Color("white")) ) renderTextTop.append( font.render("'n' : Start a new session: Be CAREFUL, will clear *everything*", 1, Color("white")) ) renderTextTop.append( font.render("'s' : Save a 'bubblePaint.####.png' image in the application's directory...", 1, Color("white")) ) renderTextTop.append( font.render(" ...based on the current resolution of the window", 1, Color("white")) ) renderTextTop.append( font.render("'ctrl+z' : Undo \ remove bubbles in order of creation (no redo). Button can be held down", 1, Color("white")) ) renderTextTop.append( font.render("'Esc' : Exit application", 1, Color("white")) ) # bottom text: pyVer = font.render("Running Python version: "+str(sys.version.split(" ")[0]), 1, Color("white")) pygVer = font.render("Running PyGame version: "+str(pygame.ver), 1, Color("white")) pyMuk = font.render("Running PyMunk version: "+str(pymunk.version), 1, Color("white")) for rt in renderTextTop: overlay.blit(rt, (8, row.val)) overlay.blit(pyVer, (8, height-(FONTSIZE*4))) overlay.blit(pygVer, (8, height-(FONTSIZE*3))) overlay.blit(pyMuk, (8, height-(FONTSIZE*2))) def updateStats(surface, font, clock, bubList, brush, useGrav): """ Update stats on the screen for the user as they paint. """ width, height = surface.get_size() fps = font.render("FPS : %.2f"%clock.get_fps(), 1, Color("white")) numBub = font.render("Num Bubbles : %s"%len(bubList), 1, Color("white")) sprayPress = font.render("Spray Pressure : %s"%brush.pressure, 1, Color("white")) gravState = "On" if useGrav != 1: gravState = "Off" grav = font.render("Gravity : %s"%gravState, 1, Color("white")) surface.blit(fps, (width-256, 16)) surface.blit(numBub, (width-256, 32)) surface.blit(sprayPress, (width-256, 48)) surface.blit(grav, (width-256, 64)) def saveImage(screen): """ Save the current image to the working directory of the program. """ # Make the screenshot dir if it doesn't exist: if not os.path.isdir(os.path.join(".", SCREENSHOT_DIR)): os.mkdir(SCREENSHOT_DIR) currentImages = glob.glob(os.path.join(SCREENSHOT_DIR, "*.png")) numList = [0] for img in currentImages: i = os.path.splitext(img)[0] try: num = re.findall('[0-9]+$', i)[0] numList.append(int(num)) except IndexError: print "found no num in", i numList = sorted(numList) newNum = numList[-1]+1 saveName = os.path.join(SCREENSHOT_DIR, 'bubblePaint.%04d.png' % newNum) savePath = os.path.join(os.path.split(sys.argv[0])[0], saveName) print "Saving %s" %savePath pygame.image.save(screen, saveName) def toggle_grav(space, useGrav, currentGrav): """ Used to turn on\off gravity. """ if useGrav: space.gravity = currentGrav else: space.gravity = (0.0, 0.0) def texture_load(path, colorkey=None): """ Modified version from the monkey PyGame tutorial. path : string : relative path to the texture colorkey : If None, no colorkey will be used. If -1, color at (0,0) will be used to define transparency. If Color, or (int,int,int), will use that RGB value for transparency. It should be noted that if the texture has alpha, this will be disregarded. return : Surface """ # load texture try: surf = pygame.image.load(path) except pygame.error, message: print 'Cannot load image:', path raise SystemExit, message # convert based on alpha: if surf.get_alpha() is not None: surf = surf.convert_alpha() else: surf = surf.convert() # If not alpha, check for colorkey and set: if colorkey is not None: if colorkey is -1: colorkey = surf.get_at((0,0)) surf.set_colorkey(colorkey, pygame.RLEACCEL) return surf def getBrushes(brushDir = BRUSH_DIR): """ Return a list of values representing [[brushA, brushB, etc], [etc] ]: Where each sub-list is a relative path to all the .png images for a given subdir under BRUSH_DIR Used for our BubbleBrush to select different brushes. """ sub = os.walk(brushDir) subdirList = sub.next()[1] brushList = [] for sd in subdirList: brushList.append(glob.glob(os.path.join(brushDir, sd, "*.png")) ) return brushList def blurSurf(surface, amt): """ Blur the given surface by the given 'ammount'. Only values 1 and greater are valid. Value 1 = no blur. """ if amt < 1.0: raise ValueError("Arg 'amt' must be greater than 1.0, passed in value is %s"%amt) scale = 1.0/float(amt) surf_size = surface.get_size() scale_size = (int(surf_size[0]*scale), int(surf_size[1]*scale)) surf = pygame.transform.smoothscale(surface, scale_size) surf = pygame.transform.smoothscale(surf, surf_size) return surf #---------------- # Main program def main(): # Yes, this, is bubblepaint. print "Running Python version:", sys.version print "Running PyGame version:", pygame.ver print "Running PyMunk version:", pymunk.version print "Running BubblePaint version:", __version__ info = pygame.display.Info() # Make the default PyGame screen 3/4 the res of the computer screen: screen_size = (int(info.current_w*.75), int(info.current_h*.75)) #---------------------------------------------------------- # Top level objects that are passed around: # Color of the paint, that the bubbleBrush uses, and the current bubbles # to be painted: colorPaint = Color("orange") # Color of the background: colorBackground = Color("black") # our font: font = pygame.font.Font("Courier.ttf", FONTSIZE) # Main Screen setup clock = pygame.time.Clock() pygame.mouse.set_visible(0) screen = pygame.display.set_mode(screen_size, pygame.RESIZABLE) # make our background object: backgroundLayer = DrawSurf(screen, colorBackground) # pumunk setup space = pymunk.Space() space.damping = DAMPING space.resize_static_hash() space.resize_active_hash() # list that holds all of our active bubbles bubbleGroup = [] brush = BubbleBrush(screen, backgroundLayer.surface, space, colorPaint, bubbleGroup) displayOverlay = 1 useGrav = 0 grav_down = (0.0, -GRAV) grav_up = (0.0, GRAV) grav_left = (-GRAV, 0.0) grav_right = (GRAV, 0.0) currentGrav = grav_down #---------------------------------------------------------- # Main loop: looping = True while looping: # Lock framerate: clock.tick(FRAMERATE) # update our pymunk sim: space.step(1/float(FRAMERATE)) res = screen.get_size() firstBrush = brush.brushes[brush.brushIndex][0] brushDir = firstBrush.split("\\")[1] pygame.display.set_caption(("BubblePaint %s - "+ "FPS: %.2f - "+ "Num Bubbles: %s - "+ "Resolution: x%s, y%s - "+ "Current Brush: %s") % (__version__, clock.get_fps(), len(bubbleGroup), res[0], res[1], brushDir) ) #---------------------------------------------------------- # detect for events doScreenshot = False getColor = False # get all events, and held keys: events = pygame.event.get() heldKeys = pygame.key.get_pressed() # pass events and held keys into our brush: brush.set_events(events) brush.set_heldkeys(heldKeys) # Willl execute when a button is pressed, not held: for event in events: if event.type == pygame.QUIT: # allow for exit: looping = False elif event.type == pygame.VIDEORESIZE: # When resizing the UI: screen_size = event.size screen = pygame.display.set_mode(screen_size, pygame.RESIZABLE) oldBg = backgroundLayer.surface.copy() backgroundLayer = DrawSurf(screen, backgroundLayer.color) # reapply the old background image, so we don't loose it: backgroundLayer.surface.blit(oldBg, (0,0)) #update our brush with the new screen size: brush.surfWidth, brush.surfHeight = screen_size # update all our bubbles with the new screen size: for b in bubbleGroup: b.surfWidth, b.surfHeight = screen_size # Is this necessary? space.resize_static_hash() space.resize_active_hash() elif event.type == KEYDOWN: if event.key == K_ESCAPE: # allow for exit: looping = False if event.key == K_s: # do screenshot doScreenshot = True if event.key == K_n: # start a new session for b in bubbleGroup[:]: bubbleGroup.pop() backgroundLayer.surface.fill(backgroundLayer.color) if event.key == K_c: # clear the screen of bubbles for b in bubbleGroup[:]: bubbleGroup.pop() if event.key == K_t: # toggle the UI display layers. displayOverlay = abs(displayOverlay-1) if event.key == K_g: useGrav = abs(useGrav-1) toggle_grav(space, useGrav, currentGrav) if event.key == K_UP: currentGrav = grav_up useGrav = 1 toggle_grav(space, 1, currentGrav) pass if event.key == K_DOWN: currentGrav = grav_down useGrav = 1 toggle_grav(space, 1, currentGrav) pass if event.key == K_LEFT: currentGrav = grav_left useGrav = 1 toggle_grav(space, 1, currentGrav) pass if event.key == K_RIGHT: currentGrav = grav_right useGrav = 1 toggle_grav(space, 1, currentGrav) pass if event.key == K_p: # if we're updating the 'palette' of the brush: getColor = True if event.key == K_o: # update the brush with complimentary color: brush.getComplimentary() if event.key == K_b: # Set background color to brush color: backgroundLayer.update(brush.color) # Will repetedly execute when a button is held down. # Undo: mods = pygame.key.get_mods() if heldKeys[K_z]: if mods == KMOD_LCTRL or mods == KMOD_RCTRL: try: bubbleGroup.pop() except IndexError: pass # Dry bubbles to the background if heldKeys[K_d]: for b in bubbleGroup[:]: # need to update this based on possible screen resizing b.drySurf = backgroundLayer.surface b.draw('dry') #b.dry() bubbleGroup.pop() # Apply filter to the dried background. Currently is "blur" if heldKeys[K_f]: backgroundLayer.surface = blurSurf(backgroundLayer.surface, DRYBLUR) #---------------------------------------------------------- # Draw backgroundLayer.draw() for bubble in bubbleGroup[:]: bubble.draw('draw') if bubble.boundsCheck(): bubbleGroup.remove(bubble) if getColor: brush.getColorUnder() if doScreenshot: saveImage(screen) brush.drawBrush() if displayOverlay: setupOverlay(screen, font) updateStats(screen, font, clock, bubbleGroup, brush, useGrav) # update our display: pygame.display.flip() #------------ # Execution from shell\icon: if __name__ == "__main__": print "Starting BubblePaint" main() print "Exiting BubblePaint"