Commits

michael b committed 1ea7a1f

worked on fx2 some, lots of new bugs. imagecache is really a mess, it needs some serious overhall

Comments (0)

Files changed (6)

 
     * Try implementing head tracking library! o.O
 	http://www.panda3d.org/forums/viewtopic.php?t=10589
+ 
+    * Saving the cache
+         * Add support to CachedMutable to serialize its cache
+         * cm.binary_serialize("filename.tar", saver(value)->returns fileobj)
+         * cm.binary_deserialize("filename.tar", loader(fileobj)->returns value)
+         * It will serialize it to a tarball file, like:
+                graphics.cache/
+                    0000.val
+                    0001.val
+                    0002.val
+                    info.js
+         * In info.js:
+             [ ["(S'<unbound method A.t>'\np0\n(I10\nI20\ntp1\ntp2\n.", "0000.val"] ]
+         * ALTERNATIVELY, encode everything in filenames:
+             * to encode: fn = pickle.dumps(key).encode("base64").replace("\n", "").replace("/", "-")
+             * to decode: key = pickle.loads(fn.replace("/", "-").decode("base64"))
+             * This way, we don't even need an info.js, we just loop through the files!
+             * Only good, I guess, if it doesn't reach a max filename length (99 I think)
+         * This can very much increase loading speed :)
+
 
 GARDEN CRISIS
     * Transition Garden Crisis into using fx2 and imagecache

camplay/camplay.py

 CYCLE = 2
 
 MOTION_MASK_FUZZINESS = 3
-MATTER_MASK_FUZZINESS = 2
+MATTER_MASK_FUZZINESS = 1
 
 MOTION_LENGTH = 3
 
 	def make_mask(target, source, background):
 		# Threshold
 		pygame.transform.threshold(target, source,
-				(0,255,0),(30,30,30),(0,0,0),1, background)
+				(255,255,255),(30, 30, 30),(0,0,0),1, background)
 		#def blit_mask(source, dest, destpos, mask, maskrect):
 		mask = pygame.mask.from_threshold(target, (0, 0, 0), (10, 10, 10, 10))
 		return mask
 	def get_rect(self):
 		return self.surface.get_rect()
 	
-	def blit_to_surface(self, source, dest):
-		tmp = pygame.Surface(dest.get_rect(), 0, self.surface)
-		tmp.blit( background, (0, 0) )
+	def blit_to_surface_feathered_edge(self, source, dest):
+		pass # TODO implement this, using mulitple thresholds, and different alpha levels
+		
+	def blit_to_surface_double_blitting(self, source, dest, rect=None):
+		# Note: it assumes that "source" is as big as "self.surface",
+		# and that rect refers to the area to confine blitting to
+		# in dest. The use of this is to crop a larger image into a 
+		# smaller image (ie, for the players)
 		tmp = source.copy()
-		tmp.blit(self.surface, self.get_rect().topleft, self.get_rect(), special_flags=pygame.BLEND_RGBA_MULT)
-		dest.blit(tmp, (0, 0), dest.get_rect().clip(self.get_rect()),pygame.BLEND_ADD)
+		if not rect:
+			rect = tmp.get_rect()
+		self.surface.set_colorkey((255, 255, 255))
+		tmp.blit(self.surface, (0, 0))
+		tmp.set_colorkey((0, 0, 0))
+		if rect:
+			dest.blit(tmp.subsurface(rect), (0, 0))
+		else:
+			dest.blit(tmp, (0, 0))
 	
-	def blit_to_surface_old(self, source, dest):
+
+	blit_to_surface = blit_to_surface_double_blitting
+	def blit_to_surface_blendadd(self, source, dest, rect=None):
+		# Note: this does not provide correct behavior
+		tmp = pygame.Surface(source.get_size(), 0, self.surface)
+		tmp.blit( source, (0, 0) )
+		tmp.blit( self.surface, (0,0), rect, pygame.BLEND_MULT )
+		dest.blit(tmp, (0, 0), rect, pygame.BLEND_ADD)
+	
+	def blit_to_surface_oldest(self, source, dest):
 		tmp = source.copy()
 		tmp.blit(self.surface, self.get_rect().topleft, self.get_rect(), special_flags=pygame.BLEND_RGBA_MULT)
 		dest.blit(tmp, (0, 0), dest.get_rect().clip(self.get_rect()),pygame.BLEND_ADD)
 		pygame.sprite.Sprite.__init__(self)
 		CachedMutable.__init__(self)
 	
-	@property
 	@cached
-	def surfacemask(self):
+	def get_surfacemask(self, precision=10):
 		# TODO test
 		"""
 		A BLACK & WHITE surface, representing the players mask
 		(but as a surface, not a pygame mask). 
 		"""
-		return SurfaceMask.outline_from_mask(self.mask)
+		return SurfaceMask.outline_from_mask(self.mask, precision)
+
 
 	@property
+	def surfacemask(self):
+		"""
+		A BLACK & WHITE surface, representing the players mask
+		(but as a surface, not a pygame mask). 
+		"""
+		return self.get_surfacemask()
+
 	@cached
+	def get_image(self):
+		surface = pygame.Surface(self.rect.size, SRCALPHA)
+		self.surfacemask.blit_to_surface(
+				self.player.camplay.snapshot, surface, self.rect)
+		return surface
+	
+	@property
 	def image(self):
-		# TODO test
-		surface = pygame.Surface(self.mask.get_size())
-		self.surfacemask.blit_to_surface(
-				self.player.camplay.display, surface)
-		return surface
+		return self.get_image()
 	
 	@property
 	@cached

camplay/capture.py

 
 
 
+class RecordingPlayerSprite(PlayerSprite):
+	@property
+	def image(self):
+		im = PlayerSprite.get_image(self)
+		w, h = im.get_size()
+		return pygame.transform.scale(im, (w/2, h/2))
 
-
-class PlayerSprite(PlayerSprite):
 	pass
 
 
 
-def main():
+def main(seconds=5):
 	pygame.init()
 	size = (640, 480)
 	screen = pygame.display.set_mode(size, 0)
 	camplay = CamPlay(screen, 
 			size, debug=False, clock=clock)
 	camplay.new_game(camplay.SINGLE,
-				player_sprite=PlayerSprite, 
+				player_sprite=RecordingPlayerSprite, 
 				autopause=False)
 	playersprite = camplay.players[0].sprite
 	imagecache.reset(resolution=(640, 480), datafolder='c_data')
 				camplay.calibrate()
 		camplay.tick(True)
 		camplay.player_sprite_group.update()
-		surfacemask = playersprite.surfacemask
-		shot = screen.copy()
-		#screen.fill((0, 0, 64))
 		screen.blit(im.surface, (0, 0))
-		surfacemask.blit_to_surface(shot, screen)
-		#screen.blit(playersprite.surfacemask.surface, (0, 0))
-		#camplay.player_sprite_group.draw(screen)
+		camplay.player_sprite_group.draw(screen)
 		
 		pygame.display.flip()
 		
 
 
 if __name__=='__main__':
-	main()
+	if len(sys.argv) > 1:
+		length = int(sys.argv[1])
+	else:
+		length = 5
 
+	main(length)
 
 
 
 
+
 __author__ = "Michael Bethencourt"
 __copyright__ = "Copyright 2011, Michael Bethencourt"
 __license__ = "GPL"
-__version__ = "3.0.9"
 __email__ = "michaelpb@gmail.com"
 __version__ = "0.1.0"
 
 RECT_UNSET = pygame.Rect((-1, -1), (0, 0))
 
 
-class Seconds(float):
-	per_tick = 1.0/60
 
-	def to_ticks(self):
-		return int(self * Seconds.per_tick)
-
+def seconds_to_ticks(seconds):
+	return int(seconds * 60.0)
 
 class Effect(object):
 	def __init__(self, length, easing=None, **k):
-		self.length = Seconds(length).to_ticks()
+		print length
+		self.length = seconds_to_ticks(length)
 		self.easing = easing
 		self.reset()
 		self.settings = k
 		self.tick = 0
 	
 	def is_done(self):
-		return self.tick > self.tick
+		return self.tick >= self.length
 	
 	def apply(self, sprite, tick=None):
-		tick = tick if tick else self.tick
+		if not tick:
+			tick = self.tick
+			self.tick += 1
+		else:
+			self.tick = tick
 		tick = self.easing(tick, self.length) if self.easing else tick
 		self.do(sprite, tick)
 
 	def __call__(self, sprite, tick=None):
-		return self.apply(sprit, tick)
+		return self.apply(sprite, tick)
 
 
 class EffectList(Effect, list):
 		list.__init__(self, a)
 
 		# Use the sum of lengths, if none is specified
-		length = k.get('length', [arg.length for arg in a])
+		length = k.get('length', sum([arg.length for arg in a]))
 		
 		# Use linear easing by default
 		easing = k.get('easing', None)
 
 	def reset(self):
 		self[:] = self._original_list
+		for effect in self:
+			effect.reset()
 		Effect.reset(self)
 		
 	def is_done(self):
 		effect.apply(sprite, tick)
 		# If finished, pop it off the list
 		if effect.is_done():
+			# TODO: bug, this screws up EffectList-level easing
+			# functions for sequential (ie Loop) operations.
+			self.tick = 0
 			self.pop(0)
+			self._on_pop(effect)
+
+	def on_pop(self, effect):
+		pass
 
 
 class Combine(EffectList):
 				self.remove(effect)
 
 class Loop(EffectList):
-	def do(self, sprite, tick):
-		effect = self[0]
-		effect.apply(sprite, tick)
-		# If finished, pop it off the list, and append it to the end
-		if effect.is_done():
-			self.pop(0)
+	def _on_pop(self, effect):
+		if effect == self._original_list[-1]:
+			self.reset()
+		else:
 			self.append(effect)
 			effect.reset()
 
 
-
 class Blocking(EffectList):
 	def __init__(self, blocking_effect, other_effect, **k):
 		EffectList.__init__(self, blocking_effect, other_effect, **k)
 class Grow(Effect):
 	def do(self, sprite, tick):
 		proportion = self.settings.get('proportion', 2.0)
-		if isinstance(proportion, float):
-			proportion = (proportion, proportion)
+		#if isinstance(proportion, float):
+		#	proportion = (proportion, proportion)
 		proportion = (tick*1.0 / self.length) * proportion
-		width, height = sprite.prect.size
-		sprite.scale((width*proportion[0], height*proportion[0]))
+		#width, height = sprite.prect.size
+		# TODO Add different units of measurement: pixels,
+		# sprite_widths, sprite_heights, screenwidths, screen_heights,
+		# and so on.
+		# Make all floats be in screen-widths for consistency.
+		# Otherwise, it will be relative things.
+		#sprite.scale_ip((width*proportion, height*proportion))
+		sprite.scale_ip(proportion)
 
 
 class Glide(Effect):
 	def do(self, sprite, tick):
 		sprite.fade_ip(int((1.0-(tick*1.0/self.length))*255))
 
-# TODO Implement a feature
-
 
 
 class easing:
 	if not pygame.mixer: print 'Warning, sound disabled'
 	pygame.init()
 	pygame.init()
-	screen = pygame.display.set_mode((468, 600))
+	screen = pygame.display.set_mode((600, 600))
 	pygame.display.set_caption('test')
 	image, rect = imagecache.load_image('imagetest.png')
 	background = pygame.Surface(screen.get_size())
-	background.fill((64, 64, 0))
 	clock = pygame.time.Clock()
 
 
-	imagecache.reset()
 	imagecache.reset_sound(datafolder='ih_data')
+	imagecache.reset(datafolder='ih_data')
+	s = imagecache.Sprite('barrel00.png', (0.4, 0.4), memory_cheap=True)
 
 	angle = 0.1
 	#gun = SuperSound('fx_gun0.ogg')
 	#pygame.mixer.music.set_volume(0.5)
 	is_held = False
 	siren = False
+
+	g = pygame.sprite.Group(s)
+	spin_effect = Loop(Spin(2.0), Grow(2.0))
 	while True:
 		for event in pygame.event.get():
 			if event.type == QUIT:
 				print "STOP PLAYING"
 				siren = not siren
 		update()
+		screen.fill((0, 0, 0))
+		spin_effect(s)
+		print s.rect 
+		#g.update()
+		g.draw(screen)
+		pygame.display.flip()
 		
 		clock.tick(60)
 

camplay/imagecache.py

 
 def cached(func):
 	def newfunc(self, *a, **k):
+		if 'cache_skip' in k:
+			# Skip cache, don't save
+			return func(self, *a, **k)
 		return self._cache_get(func, a, k)
 	return newfunc
 
 		a_bool = a if a else None 
 		k_bool = k if k.keys() else None 
 		key  = (func, (a_bool, k_bool))
+		# TODO: make it pickleable, like:
+		# key  = (str(func), (a_bool, k_bool))
+		# func I think is unbound, check if this fact is good or bad
 		try:
 			key_in = key in self.__cache
 		except TypeError, e:
 		CachedMutable.__init__(self)
 	
 	@cached
-	def load(self, filename, colorkey=Constants.PNG):
+	def load(self, filename, colorkey=Constants.PNG, **k):
 		"""
 		Loads a file with a given colorkey.  Note that the colorkey is
 		not remembered, and thus you cannot load a file twice with
 		different color keys.
 		If the file is a 
 		"""
-		fullname = os.path.join(self.datafolder, filename)
+		fullname = os.path.join(self.datafolder, filename, **k)
 		try:
 			image = pygame.image.load(fullname)
 		except pygame.error, message:
 		return image
 
 	@cached
-	def fade(self, filename, alpha):
+	def fade(self, filename, alpha, **k):
 		"""
 		Returns a version of the given file with all pixels reduced to
 		a certain alpha, for a fade out effect.
 		"""
 		# TODO have it degrade gracefully into using
 		# surface.set_alpha() on surfaces that support it.
-		surface = self.load(filename)
+		surface = self.load(filename, **k)
 		if alpha == 256:
 			return surface
 		else:
 			surface = surface.copy()
 		surface = self.load(filename).copy()
 		size = surface.get_size()
-		for y in xrange(size[1]):
-			for x in xrange(size[0]):
-				r,g,b,a = surface.get_at((x,y))
-				surface.set_at((x,y),(r,g,b,min(a, int(alpha))))
+		def fade():
+			for y in xrange(size[1]):
+				for x in xrange(size[0]):
+					r,g,b,a = surface.get_at((x,y))
+					surface.set_at((x,y),(r,g,b,min(a, int(alpha))))
 		return surface
 	
 	@cached
-	def rotate(self, filename, alpha, radians):
-		surface = self.fade(filename, alpha)
+	def rotate(self, filename, alpha, radians, **k):
+		if 'cache_skip' in k:
+			# Too expensive to skip the cache for fade and load
+			del k['cache_skip']
+		surface = self.fade(filename, alpha, **k)
 		degrees = math.degrees(radians)
 		if int(degrees) % 360 == 0: 
 			# Don't need to rotate at all
 		return surface
 	
 	@cached
-	def scale(self, filename, alpha, radians, dimensions):
+	def scale(self, filename, alpha, radians, dimensions, **k):
 		"""
 		Applies above effects, and scales to dimensions, where
 		dimensions is proportional to screen height and width.
 		"""
 		# TODO look into rotozoom
-		surface = self.rotate(filename, alpha, radians).copy()
+		surface = self.rotate(filename, alpha, radians, **k).copy()
 		target_size = Dimen.to_pixels2(dimensions, self.resolution)
 		surface = pygame.transform.scale(surface, target_size)
 		return surface
 	
 	@cached
-	def blur(self, filename, alpha, radians, dimensions, blur_amount):
+	def blur(self, filename, alpha, radians, dimensions, blur_amount, **k):
 		"""
 		Applies above effects, and blurs. 
 		"""
-		surface = self.scale(filename, alpha, radians, dimensions)
+		surface = self.scale(filename, alpha, radians, dimensions, **k)
 		if blur_amount <= 1:
 			return surface
 		else:
 		surface = pygame.transform.scale(surface, size)
 		return surface
 	
-	def blur_new(self, filename, alpha, radians, dimensions, blur_amount):
+	def blur_new(self, filename, alpha, radians, dimensions, blur_amount, **k):
 		"""
 		Eventually, this will replace the pixelation algorithm above,
 		and the above one will be renamed 'pixelate'
 		"""
-		surface = self.scale(filename, alpha, radians, dimensions)
+		surface = self.scale(filename, alpha, radians, dimensions, **k)
 		if blur_amount <= 1:
 			return surface
 		else:
 	
 
 	
-	#def get(self, filepath=None, alpha=None, radians=None, dimensions=None, blur_amount=None):
-	def get(self, filepath=None, alpha=None, radians=None, dimensions=None, blur_amount=None):
+	def get(self, filepath=None, alpha=None, radians=None, 
+			dimensions=None, blur_amount=None, memory_cheap=False):
+		"""
+		Get takes all the operations as arguments and returns the
+		resulting surface.  Memory cheap makes all operations except
+		for load and fade to be not cached.
+		"""
+		# TODO clean this up, make it much more uniform
 		lst = [filepath, alpha, radians, dimensions, blur_amount]
 		if any(map(lambda x: x is None, lst)):
 			raise ValueError("get() must specify all properties.")
-		return self.blur(filepath, alpha, radians, dimensions, blur_amount)
+		if not memory_cheap:
+			return self.blur(filepath, alpha, radians, dimensions, blur_amount)
+		else:
+			return self.blur(filepath, alpha, radians, dimensions, blur_amount, cache_skip=True)
 
 
 _image_cache = None
 			'radians': 0,
 			'dimensions': None,
 			'blur_amount': 1.0,
+			'memory_cheap': False,
 		}
 		self.d.update(k)
 	
 		return type(self)(**new_props)
 	
 	def swap_image(self, filepath):
+		# TODO fix bug about dimensions, add an option to "get dimensions"
+		# NOTE actually, just have it return both the surface and the rectangle,
+		# or something along those lines.
+		# OR have CachedImage grab the rectangle 
+		# In general, this needs to be revamped, to undo the "throb" effect
+		# during what should be a smooth rotation
 		return self._change('filepath', filepath)
 	
 	def fade(self, alpha):
 			new_props.update(properties)
 			self.image_cache.get(**new_props)
 
+
+class AnimatedCachedImage(object):
+	pass
+
 		
 
 class Sprite(pygame.sprite.Sprite, CachedImage):
 
 
 ##################################################################
-# The TextRect thingie                                          ##
+# The TextRect thingie
 ##################################################################
 class TextRectException:
-    def __init__(self, message = None):
-        self.message = message
-    def __str__(self):
-        return self.message
+	def __init__(self, message = None):
+		self.message = message
+	def __str__(self):
+		return self.message
 
 def render_textrect(string, font, rect, text_color, justification=0, surface=None):
-    """Returns a surface containing the passed text string, reformatted
-    to fit within the given rect, word-wrapping as necessary. The text
-    will be anti-aliased.
+	"""Returns a surface containing the passed text string, reformatted
+	to fit within the given rect, word-wrapping as necessary. The text
+	will be anti-aliased.
 
-    Takes the following arguments:
+	Takes the following arguments:
 
-    string - the text you wish to render. \n begins a new line.
-    font - a Font object
-    rect - a rectstyle giving the size of the surface requested.
-    text_color - a three-byte tuple of the rgb value of the
-                 text color. ex (0, 0, 0) = BLACK
-    justification - 0 (default) left-justified
-                    1 horizontally centered
-                    2 right-justified
+	string - the text you wish to render. \n begins a new line.
+	font - a Font object
+	rect - a rectstyle giving the size of the surface requested.
+	text_color - a three-byte tuple of the rgb value of the
+				 text color. ex (0, 0, 0) = BLACK
+	justification - 0 (default) left-justified
+					1 horizontally centered
+					2 right-justified
 
-    Returns the following values:
+	Returns the following values:
 
-    Success - a surface object with the text rendered onto it.
-    Failure - raises a TextRectException if the text won't fit onto the surface.
-    """
+	Success - a surface object with the text rendered onto it.
+	Failure - raises a TextRectException if the text won't fit onto the surface.
+	"""
 
-    import pygame
-    
-    final_lines = []
+	import pygame
+	
+	final_lines = []
 
-    requested_lines = string.splitlines()
+	requested_lines = string.splitlines()
 
-    # Create a series of lines that will fit on the provided
-    # rectangle.
+	# Create a series of lines that will fit on the provided
+	# rectangle.
 
-    for requested_line in requested_lines:
-        if font.size(requested_line)[0] > rect.width:
-            words = requested_line.split(' ')
-            # if any of our words are too long to fit, return.
-            for word in words:
-                if font.size(word)[0] >= rect.width:
-                    raise TextRectException, "The word " + word + " is too long to fit in the rect passed."
-            # Start a new line
-            accumulated_line = ""
-            for word in words:
-                test_line = accumulated_line + word + " "
-                # Build the line while the words fit.    
-                if font.size(test_line)[0] < rect.width:
-                    accumulated_line = test_line 
-                else: 
-                    final_lines.append(accumulated_line) 
-                    accumulated_line = word + " " 
-            final_lines.append(accumulated_line)
-        else: 
-            final_lines.append(requested_line) 
+	for requested_line in requested_lines:
+		if font.size(requested_line)[0] > rect.width:
+			words = requested_line.split(' ')
+			# if any of our words are too long to fit, return.
+			for word in words:
+				if font.size(word)[0] >= rect.width:
+					raise TextRectException, "The word " + word + " is too long to fit in the rect passed."
+			# Start a new line
+			accumulated_line = ""
+			for word in words:
+				test_line = accumulated_line + word + " "
+				# Build the line while the words fit.	
+				if font.size(test_line)[0] < rect.width:
+					accumulated_line = test_line 
+				else: 
+					final_lines.append(accumulated_line) 
+					accumulated_line = word + " " 
+			final_lines.append(accumulated_line)
+		else: 
+			final_lines.append(requested_line) 
 
-    # Let's try to write the text out on the surface.
+	# Let's try to write the text out on the surface.
 
-    if not surface:
-        surface = pygame.Surface(rect.size, SRCALPHA) 
+	if not surface:
+		surface = pygame.Surface(rect.size, SRCALPHA) 
 
-    accumulated_height = 0 
-    for line in final_lines: 
-        if accumulated_height + font.size(line)[1] >= rect.height:
-            raise TextRectException, "Once word-wrapped, the text string was too tall to fit in the rect."
-        if line != "":
-            tempsurface = font.render(line, 1, text_color)
-            if justification == 0:
-                surface.blit(tempsurface, (0, accumulated_height))
-            elif justification == 1:
-                surface.blit(tempsurface, ((rect.width - tempsurface.get_width()) / 2, accumulated_height))
-            elif justification == 2:
-                surface.blit(tempsurface, (rect.width - tempsurface.get_width(), accumulated_height))
-            else:
-                raise TextRectException, "Invalid justification argument: " + str(justification)
-        accumulated_height += font.size(line)[1]
+	accumulated_height = 0 
+	for line in final_lines: 
+		if accumulated_height + font.size(line)[1] >= rect.height:
+			raise TextRectException, "Once word-wrapped, the text string was too tall to fit in the rect."
+		if line != "":
+			tempsurface = font.render(line, 1, text_color)
+			if justification == 0:
+				surface.blit(tempsurface, (0, accumulated_height))
+			elif justification == 1:
+				surface.blit(tempsurface, ((rect.width - tempsurface.get_width()) / 2, accumulated_height))
+			elif justification == 2:
+				surface.blit(tempsurface, (rect.width - tempsurface.get_width(), accumulated_height))
+			else:
+				raise TextRectException, "Invalid justification argument: " + str(justification)
+		accumulated_height += font.size(line)[1]
 
-    return surface
+	return surface