Package pype :: Module sprite
[frames] | no frames]

Source Code for Module pype.sprite

   1  # -*- Mode: Python; tab-width: 4; py-indent-offset: 4; -*- 
   2   
   3  """Pygame/SDL/OpenGL based sprite engine. 
   4   
   5  Pygame-based sprite engine for doing real-time graphics 
   6  during behavioral/neurophysiological experiments. 
   7   
   8  Author -- James A. Mazer (mazerj@gmail.com) 
   9   
  10  """ 
  11   
  12  import os 
  13  import sys 
  14  import time 
  15  import string 
  16  import types 
  17  import math 
  18   
  19  try: 
  20          from guitools import Logger 
  21          from pypedebug import keyboard 
  22          from events import MARKFLIP 
  23          from pypeerrors import Obsolete 
  24  except ImportError: 
25 - def Logger(s, *args):
26 sys.stderr.write(s)
27 28 try: 29 import numpy as np 30 except ImportError: 31 Logger('sprite: numpy is required.\n') 32 sys.exit(1) 33 34 try: 35 import OpenGL 36 OpenGL.ERROR_CHECKING = False 37 OpenGL.ERROR_LOGGING = False 38 OpenGL.ERROR_ON_COPY = True 39 import OpenGL.GL as ogl 40 except ImportError: 41 Logger('sprite: python opengl OpenGL package required.\n') 42 sys.exit(1) 43 44 45 try: 46 import pygame 47 from pygame.constants import * 48 if pygame.version.vernum[0] <= 1 and pygame.version.vernum[1] < 9: 49 Logger('sprite: pygame >= 1.9 required') 50 sys.exit(1) 51 # force pygame to use numpy over Numeric in case they are both 52 # installed 53 pygame.surfarray.use_arraytype('numpy') 54 except ImportError: 55 Logger('sprite: python pygame package required.\n' % __name__) 56 sys.exit(1) 57 58 59 GLASS = (0,0,0,0) 60 WHITE = (255,255,255) 61 BLACK = (1,1,1) 62 RED = (255,1,1) 63 GREEN = (1,255,1) 64 BLUE = (1,1,255,1) 65 YELLOW = (255,255,1) 66 MAGENTA = (255,1,255) 67 CYAN = (1,255,255) 68 69 import PIL.Image 70 71 if PIL.Image.VERSION >= '1.1.7': 72 # newer versions of PIL use .frombytes() instead of .fromstring()
73 - def image2pil(image):
74 return PIL.Image.frombytes('RGBA', image.get_size(), 75 pygame.image.tostring(image, 'RGBA'))
76 else: 77 # old version
78 - def image2pil(image):
79 return PIL.Image.fromstring('RGBA', image.get_size(), 80 pygame.image.tostring(image, 'RGBA'))
81
82 -class FrameBuffer(object):
83 _instance = None 84
85 - def __new__(cls, *args, **kwargs):
86 """This ensure a single instantation. 87 """ 88 if cls._instance is None: 89 cls._instance = super(FrameBuffer, cls).__new__(cls) 90 cls._init = 1 91 else: 92 cls._init = 0 93 return cls._instance
94
95 - def __init__(self, dpy, width, height, fullscreen, 96 bg=(128, 128, 128), 97 sync=1, syncsize=50, syncx=None, 98 syncy=None, synclevel=255, 99 mouse=0, app=None, eyefn=None, 100 fbw=None, fbh=None, 101 xscale=1.0, yscale=1.0):
102 """pygame+GL-based framebuffer class for pype. 103 104 Wrapper for the pygame platform-independent interface to the 105 framebuffer hardware. This should provide a platform 106 independent handle to the video hardware, which is now 107 accessed via OpenGL. Only one instance per application. 108 109 :param dpy: string containing name of X11 display to contact 110 (None for default, taken from os.environ['DISPLAY'] 111 112 :param width, height: (pixels) size of requested display. This 113 corresponds to the PHYSICAL size of the display. In full 114 screen mode, it must match one of the available hardware 115 modes. See [xy]scale and fb[wh] below. 116 117 :param fullscreen: (boolean) full screen or windowed mode? 118 119 :param bg: default background color. This can be a color 120 triple (r,g,b) or a grey scale value. Actually, it can 121 be anything that the C() function in this module can 122 parse. 123 124 :param sync: boolean flag indicating whether or not to setup 125 (and subsequently use) a sync pulse placed on the 126 monkey's video display for external synchronization 127 (this is to drive photodiode that can be used to 128 detect the presence or absence of the syncpulse). 129 130 :param syncx, syncy: Position of the sync pulse generator spot 131 in standard sprite coordinates - (0,0) is screen 132 center, positive to left and up. *NB* this indicates 133 the position for the *center* of the sync spot, so if 134 you are at a corner, you should probably double the 135 size to get the expected value. Default position 136 (when syncx and syncy are not specified or are None) 137 is lower right hand corner. 138 139 :param syncsize: (pixels) size of sync pulse (sync pulse will 140 be syncsize x syncsize pixels and located in the lower 141 right corner of the screen (if syncsize <= 0, then 142 sync pulses are disabled - same as sync=0) 143 144 :param mouse: show mouse cursor? default is no 145 146 :param app: (PypeApp) application handle 147 148 :param eyefn: (optional fn that returns (x,y)) If provided should 149 be a function that returns the current eye position. Flip's 150 will add a mark on the framebuffer at this location (for 151 debugging!) 152 153 :param fb[wh]: (optional) specify the size of the internal 154 framebuffer. If specified and not equal to width and 155 height above, specifies the size of the virtual framebuffer 156 that will be scaled onto the physical display. 157 158 :param [xy]scale: (optional) alternative (older) method for 159 specifying size of framebuffer. The internal framebuffer 160 will be set to be (width*xscale, height*yscale). fb[wh] 161 overrides this parameter. 162 163 :return: nothing 164 165 """ 166 167 if not FrameBuffer._init: return 168 169 self.app = app # store for joybut access.. 170 self.eyefn = eyefn 171 172 self.record = 0 173 self._keystack = [] 174 self._font = None 175 176 if fbw: 177 xscale = fbw / float(width) 178 if fbh: 179 yscale = fbh / float(height) 180 self.xscale = xscale 181 self.yscale = yscale 182 self.gamma = (1.0, 1.0, 1.0) 183 if not width or not height: 184 Logger('sprite: must specify display width and height\n') 185 else: 186 self.physicalw = width 187 self.physicalh = height 188 self.w = int(0.5 + width * self.xscale) 189 self.h = int(0.5 + height * self.yscale) 190 self.hw = self.w / 2 191 self.hh = self.h / 2 192 193 # Mon Oct 30 15:16:46 2017 mazer 194 # note: `self.flags = self.flags | NOFRAME` breaks things on osx! 195 self.flags = OPENGL | DOUBLEBUF 196 if fullscreen: 197 self.flags = self.flags | HWSURFACE | FULLSCREEN 198 else: 199 if sys.platform.startswith('linux'): 200 self.flags = self.flags | NOFRAME 201 202 if sys.platform.startswith('linux'): 203 os.environ['__GL_SYNC_TO_VBLANK'] = '1' # nvidia specific! 204 205 if dpy: 206 self.gui_dpy = os.environ['DISPLAY'] 207 self.fb_dpy = dpy 208 209 # change DISPLAY transiently to allow separate servers for 210 # the stimulus display and the gui 211 try: 212 os.environ['DISPLAY'] = self.fb_dpy 213 pygame.init() 214 finally: 215 os.environ['DISPLAY'] = self.gui_dpy 216 217 # position display window at upper right corner based on screen size 218 modes = pygame.display.list_modes() 219 if not (self.physicalw, self.physicalh) in modes: 220 Logger('sprite: warning %dx%d not an avialable resolution\n' % 221 (self.physicalw, self.physicalh)) 222 if fullscreen: 223 Logger('sprite: available modes are %s\n' % \ 224 pygame.display.list_modes()) 225 else: 226 # anchor non-fullscreen window in upper right corner of screen 227 (dpyw, dpyh) = pygame.display.list_modes()[0] 228 os.environ['SDL_VIDEO_WINDOW_POS'] = '%d,%d' % \ 229 (dpyw - self.physicalw, 0,) 230 231 try: 232 if pygame.display.mode_ok((self.physicalw, self.physicalh), 233 self.flags) == 0: 234 Logger('sprite: mode %dx%d:%s not available.\n' % 235 (self.physicalw, self.physicalh, ppflag(self.flags))) 236 Logger('sprite: available modes are %s\n' % modes) 237 sys.exit(1) 238 except pygame.error: 239 Logger('FrameBuffer: check X server status!'); 240 sys.exit(1) 241 242 Logger("sprite: physical=%dx%d" % (self.physicalw, self.physicalh)) 243 Logger("sprite: framebuffer=%dx%d" % (self.w, self.h)) 244 245 # in theory, it works to open and close the pygame display 246 # on the fly to hide the graphics window.. 247 self.screen_open(init=1) 248 Logger('sprite: display is %dx%d %s\n' % \ 249 (self.physicalw, self.physicalh, ppflag(self.flags))) 250 Logger('sprite: fb is %dx%d %s\n' % \ 251 (self.w, self.h, ppflag(self.flags))) 252 253 self.bg = bg 254 255 # disable the sync pulse in config with 'SYNCSIZE: -1' 256 self.sync_mode = not (syncsize <= 0 or (not sync)) 257 if self.sync_mode: 258 # If sync pulse location is not specified, then put it in 259 # the lower right corner: 260 if syncx is None: 261 syncx = int(round(self.w/2)) 262 if syncy is None: 263 syncy = int(round(-self.h/2)) 264 265 # if sync position is [-1,+1] -- consider it as normalized 266 # position relative to the screen geometry (-1,-1) is lower 267 # left corner.. 268 if abs(syncx) <= 1: 269 syncx = syncx * int(round(self.w/2)) 270 if abs(syncy) <= 1: 271 syncy = syncy * int(round(self.h/2)) 272 273 # pre-build sync/photodiode driving sprites: 274 self._sync_low = Sprite(syncsize, syncsize, syncx, syncy, 275 name='sync_low', on=1, fb=self) 276 self._sync_low.fill((1, 1, 1)) 277 self._sync_low.render() 278 279 self._sync_high = Sprite(syncsize, syncsize, syncx, syncy, 280 name='sync_high', on=1, fb=self) 281 self._sync_high.fill((synclevel, synclevel, synclevel)) 282 self._sync_high.render() 283 284 self.syncinfo = (syncx, syncy, syncsize) 285 # initial sync state is OFF 286 self.sync(0) 287 else: 288 self._sync_low = None 289 self._sync_high = None 290 self.syncinfo = None 291 292 # Timer initialized and used by flip() method for 293 # checking for frame rate glitches. To enage the 294 # timer check, set maxfliptime to some positive value 295 # min ms in your task: 296 self.maxfliptime = 0 297 self.fliptimer = None 298 299 self.cursor(mouse) 300 pygame.event.set_grab(0) 301 302 # From here on out, use 'fullscreen' to track full screen mode 303 self.fullscreen = self.flags & FULLSCREEN
304
305 - def screen_open(self, init=0):
306 self.screen = pygame.display.set_mode((self.physicalw, self.physicalh), 307 self.flags) 308 pygame.display.set_caption('framebuf') 309 310 if init: 311 # initialize OpenGL subsystem 312 ogl.glOrtho(0.0, self.physicalw, 0.0, self.physicalh, 0.0, 1.0) 313 ogl.glScale(1. / self.xscale, 1. / self.yscale, 1.0) 314 # in theory -- can put any static, global transformations you 315 # want here -- say to invert the stimuli or scale it, eg: 316 # ogl.glScale(0.5, 0.5, 1.0) 317 # or 318 # ogl.glOrtho(-self.w, 2*self.w, -self.h, 2*self.h, 0.0, 1.0) 319 ogl.glEnable(ogl.GL_LINE_SMOOTH) 320 ogl.glEnable(ogl.GL_BLEND) 321 ogl.glBlendFunc(ogl.GL_SRC_ALPHA, ogl.GL_ONE_MINUS_SRC_ALPHA) 322 ogl.glEnable(ogl.GL_TEXTURE_2D) 323 ogl.glEnable(ogl.GL_COLOR_MATERIAL) 324 bpp = pygame.display.gl_get_attribute(GL_DEPTH_SIZE)
325
326 - def screen_close(self):
327 # hide screen by making it tiny (homebrew iconify) 328 self.screen = pygame.display.set_mode((1,1), self.flags)
329
330 - def __del__(self):
331 """FrameBuffer cleanup. 332 333 Delete held sprites and shut down pygame subsystem. 334 """ 335 try: 336 del self._sync_low 337 del self._sync_high 338 except AttributeError: 339 pass 340 if pygame: 341 pygame.display.quit()
342
343 - def calcfps(self, duration=250):
344 """Estimate frames per second. 345 346 Try to determine the approximate frame rate automatically. 347 X11R6 doesn't provide a way to set or query the current video 348 frame rate. To circumvent this, we just flip the page a few 349 times and compute the median inter-frame interval. 350 351 *NB* This is always going to be a rought estimate, you 352 should always adjust the /etc/X11/XFConfig-4 file to set the 353 exact frame rate and keep track of in with your data. 354 355 :param duration: period of time to flip/test (in ms) 356 357 :return: float, current frame rate in Hz. 358 359 """ 360 # try to estimate current frame rate 361 oldsync = self.sync_mode 362 self.sync_mode = 0 363 self.clear((1,1,1)) 364 self.flip() 365 self.clear((2,2,2)) 366 self.flip() 367 368 # page flip for up to a second.. 369 intervals = [] 370 self.flip() 371 start = time.time() 372 a = start 373 while time.time()-start <= 1: 374 self.flip() 375 b = time.time() 376 intervals.append(1000*(b-a)) 377 a = b 378 379 self.sync_mode = oldsync 380 381 if len(intervals) <= 1: 382 Logger('sprite: failed to estimate frames per second, using 60Hz\n') 383 return 60 384 385 # compute estimated frame rate (Hz) based on median inter-frame 386 # interval 387 # this is ROUGH median - if len(intervals) is odd, then it's 388 # not quite right, but it should be close enough.. 389 sd = np.std(intervals) 390 m = np.mean(intervals) 391 k = [] 392 for i in intervals: 393 if sd == 0 or abs(i-m) < (2*sd): 394 k.append(i) 395 if len(k) < 1: 396 Logger('sprite: calcfps - no photodiode? Assuming 60\n') 397 return 60 398 km = np.mean(k) 399 if km > 0.0: 400 return round(1000.0 / km) 401 else: 402 return -1.0
403
404 - def set_gamma(self, r, g=None, b=None):
405 """Set hardware gamma correction values (if possible). 406 407 Set hardware display gammas using pygame/SDL gamma function. 408 Not all hardware supports hardware gamma correction, so this 409 may not always work. 410 411 :param r: (Float) if this is the only argument supplied, then this is 412 the simply luminance gamma value and all three guns 413 are set to this value. 414 415 :param g,b: (Float) green and blue gamma values. If you specify g, 416 you'ld better specify b as well. Arguments are 417 floating point numbers (1.0 is no gamma correction at 418 all). 419 420 :return: TRUE on success (ie., if the hardware supports gamma 421 correction) 422 423 """ 424 if g is None: g = r 425 if b is None: b = r 426 self.gamma = (r,g,b) 427 return pygame.display.set_gamma(r, g, b)
428
429 - def cursor(self, on=0):
430 """Turn display of the mouse cursor on or off. 431 432 :param on: boolean; 1 to turn the cursor on, 0 to turn it off 433 434 :return: nothing 435 436 """ 437 pygame.mouse.set_visible(on);
438
439 - def cursorpos(self):
440 """Query current mouse position and bar state (any button) in 441 framebuffer window. 442 443 :return: (x, y, bardown, lshift, rshift) 444 445 """ 446 (x, y) = pygame.mouse.get_pos() 447 (b1, b2, b3) = pygame.mouse.get_pressed() 448 k = pygame.key.get_mods() 449 return (x, y, b1, b2, b3, \ 450 k and pygame.KMOD_LSHIFT, 451 k and pygame.KMOD_RSHIFT,)
452 453
454 - def togglefs(self, wait=0):
455 pygame.event.clear() 456 pygame.display.toggle_fullscreen() 457 while 1: 458 # wait for an expose event to indicate the switch has 459 # completed (resync/redraw/etc) 460 e = pygame.event.poll() 461 if e.type == pygame.VIDEOEXPOSE: 462 break 463 464 self.fullscreen = not self.fullscreen 465 466 if wait and self.fullscreen: 467 self.clear() 468 self.string(0, 0, '[hit key/button to clear]', (255,255,255)) 469 self.flip() 470 while len(self.checkkeys()) == 0: 471 if self.app and self.app.joybut(0): 472 break 473 self.clear() 474 self.flip() 475 pygame.event.clear()
476
477 - def sync(self, state, flip=None):
478 """Draw/set sync pulse sprite. 479 480 Set the status of the sync pulse. 481 482 :param state: (boolean) 1=light, 0=dark 483 484 :param flip: (boolean) do a page flip afterwards? 485 486 :return: nothing 487 488 """ 489 self._sync_state = state 490 if flip: 491 self.flip()
492
493 - def sync_toggle(self, flip=None):
494 """Toggle sync pulse state. 495 496 Toggle the status of the sync pulse. Light->dark; dark->light. 497 498 :param flip: (boolean) do a page flip afterwards? 499 500 :return: nothing 501 502 """ 503 self.sync(not self._sync_state, flip=flip)
504
505 - def clear(self, color=None, flip=None):
506 """Clear framebuffer. 507 508 Clear framebuffer to specified color (or default background 509 color, if no color is specfied). 510 511 :param color: color to fill with (scalar pixel value for solid 512 grey; triple scalars for an rgb value - anything C() 513 in this module understands is ok). If no color is 514 specified, then self.bg is used (set in the __init__ 515 method). 516 517 :param flip: (boolean) do a page flip afterwards? 518 519 :return: nothing 520 521 """ 522 if color is None: 523 color = self.bg 524 525 ogl.glPushMatrix() 526 ogl.glPushAttrib(ogl.GL_COLOR_BUFFER_BIT) 527 528 apply(ogl.glClearColor, C(color, gl=1)) 529 ogl.glClear(ogl.GL_COLOR_BUFFER_BIT) 530 531 ogl.glPopAttrib(ogl.GL_COLOR_BUFFER_BIT) 532 ogl.glPopMatrix() 533 534 if flip: 535 self.flip()
536
537 - def flip(self, doflip=1):
538 """Flip framebuffer (sync'd to vert-retrace, if possible). 539 540 Draw the syncpulse sprite (if enabled) with the appropriate 541 polarity, blit to the screen and then perform a page flip. 542 543 In general, this method should block until the flip occurs, 544 however, not all video hardware until linux supports blocking 545 on page flips. So be careful and check your hardware. You 546 should be able to use the calcfps() method to get a rough 547 idea of the framerate, if it's very fast (>100 Hz), chances 548 are that the hardware doesn't support blocking on flips. 549 550 """ 551 if self.sync_mode: 552 if self._sync_state == 1: 553 self._sync_high.blit() 554 elif self._sync_state == 0: 555 self._sync_low.blit() 556 557 if not doflip: 558 # this is in case you just want to update the sync pulse 559 # but not actually do the flip (ie, if you're using the 560 # MpegMovie object, or something similar, that writes 561 # directly to the frame buffer and does it's own flip..) 562 return 563 564 if self.eyefn: 565 (x, y) = self.eyefn() 566 self.rectangle(x, y, 3, 3, (255, 1, 1, 128)) 567 568 # make sure all stimuli are written to the surface. 569 ogl.glFinish() 570 571 if not self.screen is None: 572 pygame.display.flip() 573 if self.app: 574 self.app.encode(MARKFLIP) 575 576 if self.fliptimer is None: 577 self.fliptimer = time.time() 578 else: 579 t = time.time() 580 if self.maxfliptime: 581 elapsed = t - self.fliptimer 582 if elapsed > self.maxfliptime: 583 Logger('warning: %dms flip\n' % elapsed) 584 self.fliptimer = t 585 586 if self.record: 587 from pype import PypeApp 588 recno = PypeApp().record_id 589 fname = PypeApp().record_file 590 if fname is None: 591 return 592 if fname.startswith('/dev/null'): 593 fname = 'null' 594 ms = int(round(1000.0*(time.time()))) 595 f = '%s_%04d_%020d.jpg' % (fname, recno, ms) 596 self.snapshot(f) 597 PypeApp().encode('SNAPSHOT ' + f)
598
599 - def recordtog(self, state=None):
600 if state is None: 601 self.record = not self.record 602 else: 603 self.record = state 604 if self.record: 605 os.system('/bin/rm -f /tmp/pype*.jpg') 606 sys.stderr.write('[Recording is ON]\n') 607 else: 608 sys.stderr.write('[Recording is OFF]\n')
609
610 - def checklshift(self):
611 """LEFT Shift key down?""" 612 pygame.event.pump() 613 return KMOD_LSHIFT & pygame.key.get_mods()
614
615 - def checkrshift(self):
616 """RIGHT Shift key down?""" 617 pygame.event.pump() 618 return KMOD_RSHIFT & pygame.key.get_mods()
619
620 - def checklctrl(self):
621 """LEFT Ctrl key down?""" 622 pygame.event.pump() 623 return KMOD_LCTRL & pygame.key.get_mods()
624
625 - def checkrctrl(self):
626 """RIGHT Ctrl key down?""" 627 pygame.event.pump() 628 return KMOD_RCTL & pygame.key.get_mods()
629
630 - def waitkeys(self):
631 """Wait for keyboard to be clear (no pressed keys).""" 632 while len(self.checkkeys()) > 0: 633 pass
634
635 - def checkkeys(self):
636 """Get list of pressed keys.""" 637 pygame.event.pump() 638 return map(pygame.key.name, np.where(pygame.key.get_pressed())[0])
639
640 - def checkmouse(self):
641 """Get list of pressed mouse buttons.""" 642 pygame.event.pump() 643 return pygame.mouse.get_pressed()
644
645 - def clearkey(self):
646 """Clear keyboard input queue. 647 648 Clear the keyboard buffer. The way things are currently setup 649 any keystrokes coming into pype are pushed into a queue, the 650 getkey() method below returns the next key in the queue 651 652 """ 653 while 1: 654 if len(pygame.event.get()) == 0: 655 return
656
657 - def mouseposition(self):
658 """Query mouse position. 659 660 Returns (x,y) coords of mouse in frame buffer coordinates. 661 662 """ 663 pygame.event.pump() 664 pos = pygame.mouse.get_pos() 665 return (pos[0] - self.hw, -pos[1] + self.hh)
666
667 - def getkey(self, wait=None, down=1):
668 """Get next keystroke from queue. 669 670 Return the next key in the keyboard input queue and pop 671 the keystroke off the queue stack. 672 673 :param wait: flag indicating whether or not to wait for a 674 keystroke to occur. 675 676 :param down: (boolean) boolean flag indicating whether to only 677 accept downstrokes (default is true) 678 679 :return: keystroke value; negative for key-up, positive for 680 key-down, 0 if no keystrokes are available in the 681 queue. 682 683 """ 684 c = 0 685 if pygame.display.get_init(): 686 if len(self._keystack) > 0: 687 c = self._keystack[0] 688 self._keystack = self._keystack[1:] 689 else: 690 while not c: 691 if not down: 692 events = pygame.event.get([KEYUP,KEYDOWN]) 693 else: 694 events = pygame.event.get([KEYDOWN]) 695 if len(events) > 0: 696 if events[0] == KEYUP: 697 c = -(events[0].key) 698 else: 699 c = events[0].key 700 elif not wait: 701 break 702 return c
703
704 - def ungetkey(self, c):
705 """'unget' keystroke. 706 707 Push a keystroke event back onto the keyboard queue. Keyboard queue 708 is simulated in pype as a stack, see getkey() method for details. 709 710 :param c: keystroke to push back (same syntax as getkey() method above. 711 712 """ 713 self._keystack.append(c)
714
715 - def snapshot(self, filename, size=None):
716 """Save screen snapshot to file. 717 718 Take snapshot of the framebuffer and write it to the specified 719 file. 720 721 :param filename: name of output file; PIL is used to write the 722 file and PIL automatically determines the filetype 723 from filename's extension. 724 725 :param size: optional size of the snapshot; PIL is used to 726 rescale the image to this size. If size is left 727 unspecified, then the snapshot is written at the 728 screen's true resolution. 729 730 :return: PIL Image structure containing the snapshot (can be 731 converted th PhotoImage for display in 732 Tkinter/Canvas..) 733 734 """ 735 pil = image2pil(self.screen) 736 if filename: 737 if size: 738 pil.resize(size).save(filename) 739 Logger("Wrote %s screen to: %s\n" % (size,filename)) 740 else: 741 pil.save(filename) 742 Logger("Wrote screen to: %s\n" % filename) 743 return pil
744
745 - def string(self, x, y, s, color, flip=None, bg=None, size=30, rotation=0.0):
746 """Draw string on framebuffer. 747 748 Write text string in default font on the framebuffer screen 749 at the specified location. This is primarily useful for running 750 psychophysical studies and debugging. 751 752 *NB* Requires the SDL_ttf package 753 754 :param x, y: coordinates of string *CENTER* 755 756 :param s: string to write 757 758 :param color: RGB or greyscale color (anything C() understands) 759 760 :param flip: (boolean) flip after write 761 762 :param bg: None or C()-compatible color spec 763 764 :param size: (pixels) font size 765 766 :return: nothing 767 768 """ 769 color = C(color) 770 if self._font is None: 771 pygame.font.init() 772 self._font = {} 773 774 # Try to use a cached copy of font if it's already been 775 # loaded, otherwise load it and cache it. So, this can 776 # be slow the first time it's called 777 try: 778 font = self._font[size] 779 except KeyError: 780 try: 781 from pype import pypelib 782 fontfile = pypelib('cour.ttf') 783 self._font[size] = pygame.font.Font(fontfile, size) 784 except ImportError: 785 self._font[size] = pygame.font.Font(None, size) 786 787 s = self._font[size].render(s, 0, color) 788 if not bg is None: 789 self.rectangle(x, y, s.get_width()*1.1, s.get_height*1.1, 790 bg, width=0) 791 792 tex = _texture_create(pygame.image.tostring(s, 'RGBA', 1), 793 s.get_width(), s.get_height()) 794 _texture_blit(self, tex, x, y, rotation=rotation) 795 _texture_del(tex) 796 797 if flip: 798 self.flip()
799
800 - def rectangle(self, cx, cy, w, h, color, width=0, flip=None):
801 """Draw rectangle directly on framebuffer. 802 803 :param cx, cy: world coords of the rectangle's *CENTER* 804 805 :param w, h: (pixels) width and height of the rectangle 806 807 :param color: C() legal color specification 808 809 :param width: 0 means fill the rectangle with the specfied 810 color, anything else means draw the outline of the 811 rectangle in the specified color with strokes of the 812 specified width. 813 814 :return: nothing 815 816 """ 817 818 cx = cx - (w/2) 819 cy = cy - (h/2) 820 oc = ((cx, cy), (cx+w, cy), (cx+w, cy+h), (cx, cy+h)) 821 self.lines([(cx, cy), (cx+w, cy), (cx+w, cy+h), (cx, cy+h)], 822 width=width, color=color, flip=flip, closed=1)
823
824 - def lines(self, pts, color, width=1, contig=1, closed=0, joins=0, flip=None):
825 """Draw line directly on framebuffer. 826 827 Use pygame primitive to draw a straight line on the framebuffer 828 829 :param pts: [(x,y)...] world coords of polyline points 830 831 :param color: any color acceptable to C() 832 833 :param width: (pixels) width of line (0 for filled polygon) 834 835 :param contig: (bool) if true (default), contiguous sequence 836 of lines. Otherwise, each pair of points as 837 interpreted as single broken line. 838 839 :param closed: (bool) closed polygon? 840 841 :param joins: (in) fake joins with circles? (circle r ~ joins*width) 842 843 :param flip: (boolean) flip after write 844 845 :return: nothing 846 847 """ 848 ogl.glPushMatrix() 849 ogl.glPushAttrib(ogl.GL_CURRENT_BIT) 850 if width == 0: 851 ogl.glBegin(ogl.GL_POLYGON) 852 else: 853 ogl.glLineWidth(width) 854 if closed: 855 ogl.glBegin(ogl.GL_LINE_LOOP) 856 elif contig: 857 ogl.glBegin(ogl.GL_LINE_STRIP) 858 else: 859 ogl.glBegin(ogl.GL_LINES) 860 apply(ogl.glColor4f, C(color, gl=1)) 861 for (x, y) in pts: 862 x = x + self.hw 863 y = y + self.hh 864 ogl.glVertex(x, y) 865 ogl.glEnd() 866 ogl.glPopAttrib(ogl.GL_CURRENT_BIT) 867 ogl.glPopMatrix() 868 869 if joins > 0: 870 for (x,y) in pts: 871 self.circle(x, y, joins*(width/2), color) 872 873 if flip: self.flip()
874
875 - def line(self, start, stop, color, width=1, flip=None):
876 """Draw line directly on framebuffer. 877 878 Use pygame primitive to draw a straight line on the framebuffer 879 880 :param start: (x,y) world coords of line's starting point 881 882 :param stop: (x,y) world coords of line's ending point 883 884 :param color: any color acceptable to C() 885 886 :param width: (pixels) width of line 887 888 :param flip: (boolean) flip after write 889 890 :return: nothing 891 892 """ 893 self.lines([start, stop], color, width=width, flip=flip) 894 if flip: self.flip()
895
896 - def circle(self, cx, cy, r, color, width=0, flip=None):
897 """Draw circle directly on framebuffer. 898 899 *NB* Tue Apr 25 10:14:33 2006 mazer - GL coords are 900 different!!! circles were flipped/mirrored around the x-axis 901 in GL-mode!!!! 902 903 :param cx, cy: center coords of the circle 904 905 :param r: (pixels) radius 906 907 :param color: anything C() understands 908 909 :param width: (pixels) width of circle. If width==0, then the 910 circle gets filled instead of just drawing an outline 911 912 :return: nothing 913 914 """ 915 916 t = np.linspace(0, 2*np.pi, num=100, endpoint=False) 917 x = r * np.cos(t) + cx 918 y = r * np.sin(t) + cy 919 self.lines(zip(x,y), color, width=width, closed=1, flip=flip)
920
921 -def genaxes(w, h, rw, rh, typecode=np.float64, inverty=0):
922 """Generate two arrays descripting sprite x- and y-coordinate axes 923 (like Matlab MESHGRID). 924 925 *NB* By default the coordinate system is matrix/matlab, which 926 means that negative values are at the top of the sprite and 927 increase going down the screen. This is fine if all you use the 928 function for is to compute eccentricity to shaping envelopes, but 929 wrong for most math. Use inverty=1 to get proper world coords.. 930 931 :param w, h: scalar values indicating the display width and 932 height of the sprite in needing axes in pixels 933 934 :param rw, rh: scalar values indicating the "real" width and 935 height of the sprite (specifies actually array size) 936 937 :param typecode: numpy-style typecode for the output array 938 939 :param inverty: (boolean) if true, then axes are matlab-style with 940 0th row at the top, y increasing in the downward direction 941 942 :return: pair of vectors (xaxis, yaxis) where the dimensions of 943 each vector are (w, 1) and (1, h) respectively. 944 945 Mon Mar 3 13:24:10 2014 mazer 946 NOTE: there was a small fencpost error here (I think) -- linspace 947 is right, arange is likely wrong: 948 >> x = np.arange(0, w) - ((w - 1) / 2.0) 949 >> y = np.arange(0, h) - ((h - 1) / 2.0) 950 951 """ 952 if h is None: 953 (w, h) = w # size supplied as pair/tuple 954 x = np.linspace(-w/2.0, w/2.0, rw) 955 y = np.linspace(-h/2.0, h/2.0, rh) 956 if inverty: 957 y = y[::-1] 958 return x.astype(typecode)[:,np.newaxis],y.astype(typecode)[np.newaxis,:]
959
960 -class ScaledSprite(object):
961 _id = 0 # unique id for each sprite 962
963 - def __init__(self, width=None, height=None, rwidth=None, rheight=None, 964 x=0, y=0, depth=0, 965 fname=None, name=None, fb=None, on=1, 966 image=None, 967 icolor='black', ifill='yellow', 968 centerorigin=False, rotation=0.0, contrast=1.0):
969 """ScaledSprite Object -- pype's main tool for graphics generation. 970 971 This is a basic sprite object. The improvement here is that the 972 sprite has both a real size (rwidth x rheight) and a display size 973 (dwidth x dheight). If these match, it's a standard 974 Sprite object. If they don't match, the coordinate system for sprite 975 makes it look like it's display size, so gratings etc are computed 976 properly. But only rwidth x rheight pixels are kepts around, so 977 you can use coarse sprites plus OpenGL scaling to get more speed. 978 979 The standard Sprite class is now a subclass of ScaledSprite with 980 rwidth==dwidth and rheight==dheight.. 981 982 :param width, height: (int) sprite size on display in pixels -- this 983 is the physical size once the sprite gets drawn on the display 984 985 :param rwidth, rheight: (int/float) virtual sprite size -- this is the one 986 that actual computations get done on, so smaller is faster! 987 Ff these are <1.0, then they're assumed to be x and y-scaling 988 factors and applied to width and height 989 990 :param x, y: (int) sprite position in pixels 991 992 :param depth: (int; default 0) depth order for DisplayList (0=top) 993 994 :param fb: (FrameBuffer object) associated FrameBuffer object 995 996 :param on: (boolean) on/off (for DisplayList) 997 998 :param image: (Sprite object) seed sprite to get data from 999 1000 :param fname: (string) Filename of image to load sprite data from. 1001 1002 :param name: (string) Sprite name/info 1003 1004 :param icolor: (string) icon outline (for UserDisplay) 1005 1006 :param ifill: (string) icon fille (for UserDisplay) 1007 1008 :param centerorigin: (boolean; default=0) This defines the 1009 internal coordinate system used for the sprite (ie, if 1010 you're doing math on the sprite axes with numpy). If 1011 False (default), then the upper left corner of the 1012 sprite is (0,0) and increasing y-values go DOWN, 1013 increasing x-values go RIGHT. If True, then (0,0) corresponds 1014 to sprite's center increasing y-values go UP and 1015 increasing y-values go RIGHT. 1016 1017 :param rotation: (float degrees) GL-pipeline rotation 1018 factor. Very fast pre-blit rotation. 1019 1020 :param contrast: (float 0-1.0) GL-pipeline contrast scaling 1021 factor. Very fast pre-blit scaling of all pixel 1022 values. 1023 1024 """ 1025 1026 self.x = x 1027 self.y = y 1028 self.depth = depth 1029 self.fb = fb 1030 self._on = on 1031 self.icolor = icolor 1032 self.ifill = ifill 1033 self.centerorigin = centerorigin 1034 self.rotation = rotation 1035 self.contrast = contrast 1036 self.texture = None 1037 self.pim = None 1038 1039 # for backward compatibilty 1040 dwidth, dheight = width, height 1041 1042 # if only height is provided, assume square 1043 if dheight is None: dheight = dwidth 1044 if rheight is None: rheight = rwidth 1045 1046 # if only display size is specified, make real same (no scaling) 1047 if dwidth and not rwidth: 1048 rwidth, rheight = dwidth, dheight 1049 1050 # if only real size is specified, make display same (again, no scaling) 1051 if rwidth and not dwidth: 1052 dwidth, dheight = rwidth, rheight 1053 1054 if not image and not fname and rwidth < 1.0: 1055 rwidth = int(round(dwidth * rwidth)) 1056 if not image and not fname and rheight < 1.0: 1057 rheight = int(round(dheight * rheight)) 1058 1059 if rwidth > dwidth: 1060 rwidth = dwidth 1061 if rheight > dheight: 1062 rheight = dheight 1063 1064 if fname: 1065 # load image data from file; if a real size is specififed, 1066 # resize image to the specified real size. display size 1067 # can be use to scale it back up... 1068 self.im = pygame.image.load(fname).convert(32, pygame.SRCALPHA) 1069 if rwidth: 1070 # if virtual size is specified, image is downscaled to 1071 # the virtual size for speed 1072 self.im = pygame.transform.scale(self.im, (rwidth, rheight)) 1073 self.userdict = {} 1074 setalpha = 0 1075 elif image: 1076 import copy 1077 # make a copy of the source sprite/image; like image data, 1078 # if a real size is specified, resize image to the 1079 # specified real size. display size can be use to scale it 1080 # back up... 1081 try: 1082 # pype Image object 1083 self.im = image.im.convert(32, pygame.SRCALPHA) 1084 self.userdict = copy.copy(image.userdict) 1085 except: 1086 # pygame image/surface 1087 self.im = image.convert(32, pygame.SRCALPHA) 1088 self.userdict = {} 1089 if rwidth: 1090 self.im = pygame.transform.scale(self.im, (rwidth, rheight)) 1091 setalpha = 0 1092 else: 1093 # new image from scratch of specified real size 1094 self.im = pygame.Surface((rwidth, rheight), \ 1095 flags=pygame.SRCALPHA, depth=32) 1096 self.userdict = {} 1097 setalpha = 1 1098 1099 if rwidth is None: 1100 rwidth = self.im.get_width() 1101 rheight = self.im.get_height() 1102 if dheight is None: 1103 dwidth = rwidth 1104 dheight = rheight 1105 1106 self.xscale = float(dwidth) / float(rwidth) 1107 self.yscale = float(dheight) / float(rheight) 1108 1109 self.im.set_colorkey((0,0,0,0)) 1110 1111 # make sure every sprite gets a name for debugging 1112 # purposes.. 1113 if name: 1114 self.name = name 1115 elif fname: 1116 self.name = "file:%s" % fname 1117 else: 1118 self.name = "#%d" % Sprite._id 1119 1120 # do not mess with _id!!! 1121 self._id = Sprite._id 1122 Sprite._id = Sprite._id + 1 1123 1124 self.refresh() 1125 1126 # this is to fix a Lucid problem, not tracked down now.. 1127 if setalpha: 1128 self.alpha[::] = 255
1129
1130 - def refresh(self):
1131 # this should be called each time the size or surface 1132 # data for a sprite gets changed 1133 1134 self.w = self.im.get_width() 1135 self.h = self.im.get_height() 1136 self.dw = int(round(self.xscale * self.w)) 1137 self.dh = int(round(self.yscale * self.h)) 1138 1139 self.ax, self.ay = genaxes(self.dw, self.dh, self.w, self.h, 1140 inverty=0) 1141 self.xx, self.yy = genaxes(self.dw, self.dh, self.w, self.h, 1142 inverty=1) 1143 self.array = pygame.surfarray.pixels3d(self.im) 1144 self.alpha = pygame.surfarray.pixels_alpha(self.im) 1145 self.im.set_colorkey((0,0,0,0))
1146
1147 - def __del__(self):
1148 """Sprite clean up. 1149 1150 Remove the name of the sprite from the global 1151 list of sprites to facilitate detection of un-garbage 1152 collected sprites 1153 1154 """ 1155 self.render(clear=1)
1156
1157 - def __repr__(self):
1158 return ('<ScaledSprite "%s"@(%d,%d) V%dx%d D%dx%d depth=%d on=%d>' % 1159 (self.name, self.x, self.y, 1160 self.w, self.h, self.dw, self.dh, self.depth, self._on,))
1161
1162 - def XY(self, xy):
1163 """Respects `centerorigin` flag.""" 1164 return (self.X(xy[0]), self.Y(xy[1]))
1165
1166 - def X(self, x):
1167 """Respects `centerorigin` flag.""" 1168 if self.centerorigin: 1169 return (self.w / 2) + x 1170 else: 1171 return x
1172
1173 - def Y(self, y):
1174 """Respects `centerorigin` flag. 1175 1176 """ 1177 if self.centerorigin: 1178 return (self.h / 2) - y 1179 else: 1180 return y
1181
1182 - def set_rotation(self, deg):
1183 """Sets GL-pipeline rotation term - fast rotation on video card. 1184 1185 """ 1186 self.rotation = deg
1187
1188 - def set_contrast(self, c):
1189 """Sets GL-pipeline contrast term - uses video hardware for alpha. 1190 1191 :param c: contrast value - [0-1] or [0-100] 1192 1193 """ 1194 if c >= 0: 1195 self.contrast = c / 100.0 1196 else: 1197 self.contrast = c
1198
1199 - def asPhotoImage(self, alpha=None, xscale=None, yscale=None):
1200 """Convert sprite to a Tkinter displayable PhotoImage. 1201 1202 This depends on the python imaging library (PIL) 1203 1204 :param alpha: (optional) alpha value for the PhotoImage 1205 1206 :param xscale,yscale: (optional) x and y scale factor for 1207 resizing image. If you specify one, you must specify 1208 both! 1209 1210 :return: PhotoImage represenation of the sprite's pixels. 1211 1212 """ 1213 1214 import PIL.ImageTk, PIL.ImageFilter 1215 1216 pil = self.asImage(xscale=xscale, yscale=yscale) 1217 pil = pil.resize((self.dw, self.dh)) 1218 pil = pil.rotate(self.rotation, expand=1) 1219 if not alpha is None: 1220 pil.putalpha(alpha) 1221 self.pim = PIL.ImageTk.PhotoImage(pil) 1222 1223 # NOTE: hanging pim off sprite prevents garbage collection until 1224 # the sprite is deleted (otherwise you can end up with a handle 1225 # to a garbage-collected PIL image. 1226 return self.pim
1227
1228 - def asImage(self, xscale=None, yscale=None):
1229 """Convert sprite to a PIL Image. 1230 1231 This depends on the python imaging library (PIL) 1232 1233 :param xscale,yscale: (optional) x and y scale factor for 1234 resizing image. If you specify one, you must specify 1235 both! 1236 1237 :return: Image represenation of the sprite's pixels. 1238 1239 """ 1240 1241 pil = image2pil(self.im) 1242 pil = pil.resize((self.dw, self.dh)) 1243 if xscale or yscale: 1244 (w, h) = pil.size 1245 pil = pil.resize((int(round(w*xscale)), int(round(h*yscale)))) 1246 pil = pil.rotate(self.rotation, expand=1) 1247 return pil
1248
1249 - def set_alpha(self, a):
1250 """Set global alpha value. 1251 1252 Set transparency of the *entire* sprite to this value. 1253 1254 :param a: alpha value (0-255; 0 is fully transparent, 255 is 1255 fully opaque). 1256 1257 :return: nothing 1258 """ 1259 self.alpha[::] = a
1260
1261 - def line(self, x1, y1, x2, y2, color, width=1):
1262 """Draw line of specified color in sprite. 1263 1264 *NB* not the same syntax as the FrameBuffer.line() method!! 1265 1266 :param x1,y1: starting coords of line in world coords 1267 1268 :param x2,y2: ending coords of line in world coords 1269 1270 :param color: line color in C() parseable format 1271 1272 :param width: (pixels) line width 1273 1274 :return: nothing 1275 1276 """ 1277 start = (self.X(x1),self.Y(y1)) 1278 stop = (self.X(x2),self.Y(y2)) 1279 pygame.draw.line(self.im, C(color), start, stop, width)
1280
1281 - def fill(self, color):
1282 """Fill sprite with specficied color. 1283 1284 Like clear method above, but requires color to be specified 1285 1286 :param color: C() parseable color specification 1287 1288 :return: nothing 1289 1290 """ 1291 color = C(color) 1292 for n in range(3): self.array[:,:,n] = color[n] 1293 self.alpha[::] = color[3]
1294
1295 - def clear(self, color=(1,1,1)):
1296 """Clear sprite to specified color. 1297 1298 Set's all pixels in the sprite to the indicated color, or black 1299 if no color is specified. 1300 1301 :param color: C() parseable color specification 1302 1303 :return: nothing 1304 1305 """ 1306 self.fill(color)
1307
1308 - def noise(self, thresh=0.5):
1309 """Fill sprite with white noise. 1310 1311 Fill sprite with binary white noise. Threshold can be used 1312 to specified the DC level of the noise. 1313 1314 :param thresh: threshold value [0-1] for binarizing the noise 1315 signal. Default's to 0.5 (half black/half white) 1316 1317 :return: nothing 1318 1319 """ 1320 for n in range(3): 1321 if n == 0: 1322 m = np.random.uniform(1, 255, size=(self.w, self.h)) 1323 if thresh: 1324 m = np.where(np.greater(m, thresh*255), 1325 255, 1).astype(np.uint8) 1326 self.array[:,:,n] = m[::].astype(np.uint8) 1327 # for some reason starting with lucid (10.04LTS) you need 1328 # to explicitly set alpha here. This means you need to make 1329 # sure to apply alpha masks AFTER you do a noise fill! 1330 self.alpha[::] = 255
1331
1332 - def circlefill(self, color, r=None, x=0, y=0, width=0):
1333 """Draw filled circle in sprite. 1334 1335 Only pixels inside the circle are affected. 1336 1337 :param color: C() parseable color spec 1338 1339 :param r: (pixels) circle radius 1340 1341 :param x, y: circle center position in world coords (defaults to 0,0) 1342 1343 :param width: (pixels) pen width; if 0, then draw a filled circle 1344 1345 :return: nothing 1346 1347 """ 1348 if r is None: r = self.w / 2 1349 x = self.X(x) 1350 y = self.Y(y) 1351 1352 color = C(color) 1353 h = np.hypot(self.xx-x, self.yy-y) 1354 if width == 0: 1355 for n in range(3): 1356 self.array[np.less_equal(h, r),n] = color[n] 1357 self.alpha[np.less_equal(h, r)] = color[3] 1358 else: 1359 for n in range(3): 1360 self.array[np.less_equal(abs(h-r), width)] = color[n] 1361 self.alpha[np.less_equal(abs(h-r), width)] = color[3]
1362 1363
1364 - def circle(self, color, r=None, x=0, y=0, mask=True):
1365 """Draw filled circle into sprite. 1366 1367 Pixels inside the specified circle are filled to specified 1368 color. If mask is True (default), the rest of the sprite is 1369 set to be 100% transparent (alpha=0). 1370 1371 :param color: C() parseable color spec 1372 1373 :param r: radius of circular mask 1374 1375 :param x, y: (pixels) center coords of circular mask (world 1376 coordinates) 1377 1378 :param mask: (bool; def=True) mask off rest of sprite? 1379 1380 :return: nothing 1381 1382 """ 1383 if mask: 1384 self.fill((0,0,0,0)) 1385 self.circlefill(color, r=r, x=x, y=y)
1386
1387 - def rect(self, x, y, w, h, color, mask=False):
1388 """Draw a *filled* rectangle of the specifed color on a sprite. 1389 1390 *NB* 1391 - parameter sequence is not the same order as circlefill() 1392 1393 :param x, y: world coords for rectangle's center 1394 1395 :param w, h: (pixels) width and height of rectangle 1396 1397 :param color: C() parseable color spec 1398 1399 :param mask: (bool; def=True) mask off rest of sprite? 1400 1401 :return: nothing 1402 1403 """ 1404 if mask: 1405 self.fill((0,0,0,0)) 1406 1407 color = C(color) 1408 x = self.w/2 + x - w/2 1409 y = self.h/2 - y - h/2 1410 mx = max(0,x) 1411 my = max(0,y) 1412 1413 for n in range(3): self.array[mx:(x+w),my:(y+h),n] = color[n] 1414 self.alpha[mx:(x+w),my:(y+h)] = color[3]
1415
1416 - def rotateCCW(self, angle, preserve_size=1):
1417 """Counter clockwise (CCW) rotation. 1418 1419 """ 1420 self.rotate(-angle, preserve_size=preserve_size)
1421
1422 - def rotateCW(self, angle, preserve_size=1):
1423 """Clockwise (CW) rotation. 1424 1425 """ 1426 self.rotate(angle, preserve_size=preserve_size)
1427
1428 - def rotate(self, angle, preserve_size=True):
1429 """Lossy rotation of spite image data. 1430 1431 CW rotation of sprite about the center using pygame.transform 1432 primitives. This is a *non-invertable* deformation -- 1433 multiple rotations will accumulate errors, so if you're doing 1434 that, keep an original copy and rotate the copy each time. 1435 1436 :param angle: angle of rotation in degrees (CW) 1437 1438 :param preserve_size: (boolean; def True) if true, the rotated 1439 sprite is clipped back down to the size of the 1440 original image. Otherwise, it'll end up whatever size 1441 is necessary to preserve all the pixels. 1442 1443 :return: nothing - works on sprite in-place 1444 1445 """ 1446 if self.xscale != 1.0 or self.yscale != 1.0: 1447 Logger("rotate only works for unscaled sprites!\n") 1448 1449 new = pygame.transform.rotate(self.im, -angle) 1450 if preserve_size: 1451 w = new.get_width() 1452 h = new.get_height() 1453 x = (w/2) - (self.w/2) 1454 y = (h/2) - (self.h/2) 1455 new = new.subsurface(x, y, self.w, self.h) 1456 self.im = new 1457 self.refresh()
1458 1459
1460 - def scale(self, new_width, new_height):
1461 """Fast resizing of sprite using pygame.rotozoom. 1462 1463 Can scale up or down equally well. Changes the data within the 1464 sprite, so it's not really invertable. If you want to save the 1465 original image data, first clone() and then scale(). 1466 1467 :param new_width: (pixels) new width 1468 1469 :param new_height: (pixels) new height 1470 1471 :return: nothing 1472 1473 Warning: in general, this is obsolete -- you should use 1474 xscale/yscale to do scaling through the GL pipeline 1475 1476 """ 1477 1478 nw = int(round(self.w * new_width / float(self.dw))) 1479 nh = int(round(self.h * new_height / float(self.dh))) 1480 self.im = pygame.transform.scale(self.im, (nw, nh)) 1481 self.refresh()
1482 1483
1484 - def hard_aperture(self, r, x=0, y=0):
1485 """Apply hard-edged circular vignette/mask in place. 1486 1487 This works by setting pixels outside the specified region to 1488 (0,0,0,0). Formerly known as `image_circmask` and `circmask`. 1489 1490 """ 1491 mask = np.where(np.less(np.hypot(self.ax-x,self.ay+y), r), 1, 0) 1492 a = pygame.surfarray.pixels2d(self.im) 1493 a[::] = mask * a
1494
1495 - def alpha_aperture(self, r, x=0, y=0):
1496 """Apply hard-edged circular vignette/mask in place using 1497 alpha channel 1498 1499 :param r: (pixels) radius 1500 1501 :param x,y: (pixels) optional aperture center (0,0 is center) 1502 1503 :return: nothing 1504 1505 """ 1506 d = np.where(np.less(np.hypot(self.ax-x, self.ay+y), r), 1507 255, 0).astype(np.uint8) 1508 self.alpha[::] = d
1509
1510 - def alpha_gradient(self, r1, r2, x=0, y=0):
1511 """Add soft-edged apperature (gradient vignette) 1512 1513 Vignette the sprite using a soft, linear, circular aperture. 1514 This draws a linearly ramped mask of the specified size into 1515 the alpha channel. 1516 1517 :param r1,r2: (pixels) inner,outer radii of ramp 1518 1519 :param x,y: (pixels) optional aperture center (0,0 is center) 1520 1521 :return: nothing 1522 1523 """ 1524 d = 255 - (255 * (np.hypot(self.ax-x,self.ay+y)-r1) / (r2-r1)) 1525 d = np.where(np.less(d, 0), 0, 1526 np.where(np.greater(d, 255), 255, d)).astype(np.uint8) 1527 self.alpha[::] = d
1528
1529 - def alpha_gradient2(self, r1, r2, bg, x=0, y=0):
1530 """Add soft-edged apperature (gradient vignette) 1531 1532 Like alpha_gradient() above, but mask is applied directly to 1533 the image data by doing alpha blending in software, under the 1534 assumption that the background is a solid fill of value 'bg'. 1535 1536 :param r1,r2: (pixels) inner,outer radii 1537 1538 :param bg: background color in C() compatible format 1539 1540 :param x,y: (pixels) coords of mask center in world coords 1541 1542 :return: nothing 1543 1544 """ 1545 d = 1.0 - ((np.hypot(self.ax-x, self.ay+y) - r1) / (r2 - r1)) 1546 alpha = clip(d, 0.0, 1.0) 1547 i = pygame.surfarray.pixels3d(self.im) 1548 alpha = np.transpose(np.array((alpha,alpha,alpha)), axes=[1,2,0]) 1549 if is_sequence(bg): 1550 bgi = np.zeros(i.shape) 1551 for n in range(3): 1552 bgi[:,:,n] = bg[n] 1553 else: 1554 bgi = bg 1555 i[::] = ((alpha * i.astype(np.float)) + 1556 ((1.0-alpha) * bgi)).astype(np.uint8) 1557 self.alpha[::] = 255;
1558
1559 - def dim(self, mult, meanval=128.0):
1560 """Reduce sprite contrast 1561 1562 Reduce sprite's contrast. Modifies sprite's image data. 1563 v = (1-mult)*(v-mean), where v is the pixel values. 1564 1565 *NB* This assumes the mean value of the image data is 1566 'meanval', which is not always the case. If it's not, then you 1567 need to compute and pass in the mean value explicitly. 1568 1569 :param mult: scale factor 1570 1571 :param meanval: assumed mean pixel value [0-255] 1572 1573 :return: nothing 1574 1575 """ 1576 pixs = pygame.surfarray.pixels3d(self.im) 1577 pixs[::] = (float(meanval) + ((1.0-mult) * 1578 (pixs.astype(np.float)-float(meanval)))).astype(np.uint8)
1579
1580 - def thresh(self, threshval):
1581 Logger('Warning: Sprite.thresh --> Sprite.threshold\n') 1582 return self.threshold(threshval)
1583
1584 - def threshold(self, threshval):
1585 """Threshold sprite image data 1586 1587 Threshold (binarize) sprite's image data 1588 v = (v > thresh) ? 255 : 1, where v is pixel value 1589 1590 :param threshval: threshold (0-255) 1591 1592 :return: nothing 1593 1594 """ 1595 pixs = pygame.surfarray.pixels3d(self.im) 1596 pixs[::] = np.where(np.less(pixs, threshval), 1, 255).astype(np.uint8)
1597
1598 - def on(self):
1599 """Make sprite visible. 1600 1601 """ 1602 self._on = 1
1603
1604 - def off(self):
1605 """Make sprite invisible. 1606 1607 """ 1608 self._on = 0
1609
1610 - def toggle(self):
1611 """Flip on/off flag. 1612 1613 :return: current state of flag. 1614 1615 """ 1616 self._on = not self._on 1617 return self._on
1618
1619 - def state(self):
1620 """Get current on/off flag state. 1621 1622 :return: boolean on/off flag 1623 1624 """ 1625 return self._on
1626
1627 - def moveto(self, x, y):
1628 """Move sprite to new absolute location. 1629 1630 :param x,y: (pixels) framebuffer coords of new location 1631 1632 :return: nothing 1633 1634 """ 1635 self.x = x 1636 self.y = y
1637
1638 - def rmove(self, dx=0, dy=0):
1639 """Relative move. 1640 1641 Shifts sprite by dx,dy pixels relative to current position. 1642 1643 :param dx,dy: (pixels) framebuffer coords of new location 1644 1645 :return: nothing 1646 1647 """ 1648 self.x = self.x + dx 1649 self.y = self.y + dy
1650
1651 - def blit(self, x=None, y=None, fb=None, flip=None, force=0, 1652 textureonly=None):
1653 """Copy sprite to screen (block transfer). 1654 1655 1. x,y,fb etc are all internal properties of each sprite. You 1656 don't need to supply them here unless you want to change 1657 them at the same time you blit 1658 1659 2. Don't forget to *flip* the framebuffer after blitting! 1660 1661 :param x,y: (pixels) optional new screen location 1662 1663 :param fb: frame buffer 1664 1665 :param flip: (boolean) flip after blit? 1666 1667 :param force: (boolean) override on/off flag? 1668 1669 """ 1670 if fb is None: 1671 fb = self.fb 1672 1673 if not (fb is None or (not force and not self._on)): 1674 if x is None: 1675 x = self.x 1676 else: 1677 self.x = x 1678 1679 if y is None: 1680 y = self.y 1681 else: 1682 self.y = y 1683 1684 if self.texture: 1685 # pre-rendered sprite... 1686 tex = self.texture 1687 else: 1688 rgba = pygame.image.tostring(self.im, 'RGBA', 1) 1689 tex = _texture_create(rgba, self.w, self.h) 1690 _texture_blit(self.fb, tex, x, y, 1691 rotation=self.rotation, 1692 contrast=self.contrast, 1693 xscale=self.xscale, yscale=self.yscale) 1694 if not self.texture: 1695 _texture_del(tex) 1696 1697 if flip: 1698 fb.flip()
1699
1700 - def fastblit(self):
1701 raise Obsolete, 'Use blit method instead of fastblit.'
1702
1703 - def render(self, clear=False):
1704 """Render image data into GL texture memory for speed. 1705 1706 """ 1707 if self.texture: 1708 _texture_del(self.texture) 1709 if not clear: 1710 s = pygame.image.tostring(self.im, 'RGBA', 1) 1711 self.texture = _texture_create(s, self.w, self.h)
1712
1713 - def subimage(self, x, y, w, h, center=None):
1714 """Extract sub-region of sprite into new sprite. 1715 1716 Generates a new sprite containing image data from specified 1717 sub-region of this sprite. Image data is copied, not shared 1718 or referenced -- changes to result will not affet original. 1719 1720 :param x,y: (pixels) coordinates of subregion (0,0) is upper 1721 left corner of parent/src sprite 1722 1723 :param w,h: (pixels) width and height of subregion 1724 1725 :param center: (boolean) Does (x,y) refer to the center or 1726 upper left corner of sprite? 1727 1728 :return: new sprite 1729 1730 """ 1731 if self.xscale != 1.0 or self.yscale != 1.0: 1732 Logger("Can't subimage scaled sprites yet!\n") 1733 return None 1734 1735 if center: 1736 x = self.X(x) - (w/2) 1737 y = self.Y(y) - (h/2) 1738 1739 return ScaledSprite(image=self.im.subsurface((x, y, w, h)), 1740 rwidth=int(round(w)), rheight=int(round(h)), 1741 x=self.x, y=self.y, 1742 depth=self.depth, fb=self.fb, on=self._on)
1743
1744 - def clone(self):
1745 """Duplicate sprite (aka deepcopy). 1746 1747 Create no sprite with all the same data, including image and 1748 alpha, duplicated. Like subimage, data are copied, not referenced, 1749 so changes to clone will not affect parent. 1750 1751 """ 1752 import copy 1753 1754 s = ScaledSprite(width=self.dw, height=self.dh, 1755 rwidth=self.w, rheight=self.h, 1756 image=self, 1757 x=self.x, y=self.y, 1758 on=self._on, depth=self.depth, 1759 rotation=self.rotation, contrast=self.contrast, 1760 name='clone of '+self.name, 1761 fb=self.fb) 1762 s.alpha[::] = self.alpha[::] # copy the alpha mask too.. 1763 s.userdict = copy.copy(self.userdict) 1764 return s
1765
1766 - def setdir(self, angle, vel):
1767 raise Obsolete, 'do not use setdir'
1768
1769 - def displayim(self):
1770 return pygame.transform.scale(self.im, (self.dw, self.dh))
1771
1772 - def save(self, fname, mode='w'):
1773 """Write sprite as image to file 1774 1775 Image is saved to file using pygame image tools. Filename 1776 extension determines the output format (png and jpg ok). If 1777 the sprite is scaled or anything like that, it will be saved 1778 as displayed, which may not match the in-memory image data. 1779 1780 """ 1781 # use pygame's save function to write image to file (PNG, JPG) 1782 return pygame.image.save(self.displayim(), fname)
1783
1784 - def save_ppm(self, fname, mode='w'):
1785 raise Obsolete, 'save_ppm has been removed'
1786
1787 - def save_alpha_pgm(self, fname, mode='w'):
1788 raise Obsolete, 'save_ppm has been removed'
1789
1790 -class Sprite(ScaledSprite):
1791 """Sprite object (wrapper for pygame surface class). 1792 1793 See ScaledSprite() class for info -- Sprite instances are just 1794 ScaledSprites that with no scaling (ie, physical and virtual 1795 sizes are the same). 1796 1797 """ 1798
1799 - def __init__(self, width=100, height=100, x=0, y=0, depth=0, 1800 fb=None, on=1, image=None, fname=None, 1801 name=None, icolor='black', ifill='yellow', 1802 centerorigin=0, rotation=0.0, contrast=1.0, scale=1.0):
1803 """Sprite Object -- pype's main tool for graphics generation. 1804 1805 :param width, height: (int) sprite size in pixels 1806 1807 :param x, y: (int) sprite position in pixels 1808 1809 :param depth: (int; default 0) sprite stacking order for 1810 DisplayList (0 is on top) 1811 1812 1813 :param fb: (FrameBuffer object) associated FrameBuffer object 1814 1815 :param on: (boolean) on/off (for DisplayList) 1816 1817 :param image: (Sprite object) seed sprite to get data from 1818 1819 :param fname: (string) Filename of image to load sprite data from. 1820 1821 :param name: (string) Sprite name/info 1822 1823 :param icolor: (string) icon color (for UserDisplay) 1824 1825 :param ifill: (string) icon fill (for UserDisplay) 1826 1827 :param centerorigin: (boolean; default=0) If 0, then the upper 1828 left corner of the sprite is 0,0 and increasing y goes 1829 DOWN, increase x goes RIGHT. If 1, then 0,0 is in the 1830 center of the sprite and increasing y goes UP and 1831 increase X goes RIGHT. 1832 1833 :param rotation: (float degrees) GL-pipeline rotation 1834 factor. Very fast pre-blit rotation. 1835 1836 :param contrast: (float 0-1.0) GL-pipeline contrast scaling 1837 factor. Very fast pre-blit scaling of all pixel 1838 values. 1839 1840 :param scale: (float) GL-pipeline spatial scaling 1841 factor. Very fast pre-blit scaling of all pixel 1842 values. 1843 1844 """ 1845 1846 if fname: 1847 width, height = None, None 1848 else: 1849 width, height = width*scale, height*scale 1850 1851 ScaledSprite.__init__(self, 1852 width=width, height=height, 1853 rwidth=width, rheight=height, 1854 x=x, y=y, depth=depth, 1855 fb=fb, on=on, image=image, 1856 fname=fname, name=name, 1857 icolor=icolor, ifill=ifill, 1858 centerorigin=centerorigin, 1859 rotation=rotation, contrast=contrast)
1860
1861 -class PolySprite(object):
1862 """`Sprite`-like Polygon. 1863 1864 This is a like a sprite, but instead of image data, has a vector 1865 of points that define a polygon. Could be easily extended into a 1866 SplineSprite() class etc. 1867 """ 1868 1869 _id = 0 1870
1871 - def __init__(self, points, color, fb, 1872 closed=0, width=1, joins=1, line=0, contig=1, 1873 on=1, depth=0, name=None, x=0, y=0):
1874 """PolySprite instantiation method 1875 1876 :param points: (pixels) polygon vertices. List of (x,y) pairs, 1877 where (0,0) is screen center, positive y is up, 1878 positive x is to the right. 1879 1880 :param color: line color 1881 1882 :param fb: framebuffer 1883 1884 :param closed: (boolean) open or closed polygon? 1885 1886 :param width: (pixels) line width 1887 1888 :param line: (bool) if true, draw lines, else filled polygon 1889 1890 :param contig: (bool) if true (default), contiguous sequence 1891 of lines. Otherwise, each pair of points as 1892 interpreted as single broken line. 1893 1894 :param joins: (boolean) join lines and use end-caps 1895 1896 :param on: (boolean) just like regular Sprite class 1897 1898 :param depth: depth of sprite (for DisplayList below). The 1899 DisplayList class draws sprites in depth order, with 1900 large depths being draw first (ie, 0 is the top-most 1901 layer of sprites) 1902 1903 :param name: debugging name (string) of the sprite; if not 1904 set, then either the filename or a unique random name 1905 is used instead. 1906 1907 """ 1908 1909 if name: 1910 self.name = name 1911 else: 1912 self.name = "PolySprite%d" % PolySprite._id 1913 1914 self._id = PolySprite._id 1915 PolySprite._id = PolySprite._id + 1 1916 1917 self.fb = fb 1918 self.points = [] 1919 self.color = color 1920 self.closed = closed 1921 self.width = width 1922 self.line = line 1923 self.joins = joins 1924 self.contig = contig 1925 self._on = on 1926 self.depth = depth 1927 self.x0 = x 1928 self.y0 = y 1929 self.points = np.array(points) 1930 self.pim = None
1931
1932 - def __repr__(self):
1933 return ('<PolySprite "%s"@(%d,%d) #%d depth=%d on=%d>' % 1934 (self.name, self.x0, self.y0, self._id, self.depth, self._on))
1935
1936 - def clone(self):
1937 return PolySprite( 1938 self.points, self.color, self.fb, 1939 closed=self.closed, width=self.width, line=self.line, 1940 joins=self.joins, on=self.on, depth=self.depth, 1941 name='clone of '+self.name, x=self.x0, y=self.y0)
1942
1943 - def moveto(self, x, y):
1944 """Absolute move. 1945 1946 """ 1947 self.x0 = x 1948 self.y0 = y
1949
1950 - def rmove(self, dx, dy):
1951 """Relative move. 1952 1953 """ 1954 self.x0 = self.x0 + dx 1955 self.y0 = self.y0 + dy
1956
1957 - def blit(self, flip=None, force=None):
1958 """Draw PolySprite. 1959 1960 """ 1961 if not self._on and not force: 1962 return 1963 1964 if self.line: 1965 w = self.width 1966 else: 1967 w = 0 1968 1969 self.fb.lines(self.points+[self.x0, self.y0], 1970 color=self.color, width=w, 1971 joins=self.joins, 1972 contig=self.contig, 1973 closed=self.closed, 1974 flip=flip)
1975
1976 - def on(self):
1977 """Turn PolySprite on. 1978 1979 """ 1980 self._on = 1
1981
1982 - def off(self):
1983 """Turn PolySprite off. 1984 1985 """ 1986 self._on = 0
1987
1988 - def toggle(self):
1989 """Toggle PolySprite (off->on or on->off) 1990 1991 """ 1992 self._on = not self._on 1993 return self._on
1994
1995 -class TextSprite(object):
1996 """`Sprite`-like Text object. 1997 1998 This is a like a sprite, but instead of image data, has text. 1999 2000 """ 2001 2002 _id = 0 2003
2004 - def __init__(self, x, y, s, fb=None, 2005 fg=(255,1,1), bg=None, rotation=0, size=30, 2006 on=1, depth=0, name=None):
2007 """TextSprite instantiation method 2008 2009 :param x,y: (pixels) center position 2010 2011 :param s: (string) text to display 2012 2013 :param fb: framebuffer 2014 2015 :param bg,bg: fg/bg color (bg=None for no background/transparent) 2016 2017 :param rotation: (deg) 2018 2019 :param size: (pix height) 2020 2021 :param on: (boolean) just like regular Sprite class 2022 2023 :param depth: depth of sprite (for DisplayList below). The 2024 DisplayList class draws sprites in depth order, with 2025 large depths being draw first (ie, 0 is the top-most 2026 layer of sprites) 2027 2028 :param name: debugging name (string) of the sprite; if not 2029 set, then either the filename or a unique random name 2030 is used instead. 2031 2032 """ 2033 2034 if name: 2035 self.name = name 2036 else: 2037 self.name = "TextSprite%d" % PolySprite._id 2038 2039 self._id = TextSprite._id 2040 TextSprite._id = TextSprite._id + 1 2041 2042 self.fb = fb 2043 self.x = x 2044 self.y = y 2045 self.s = s 2046 self.fg = fg 2047 self.bg = bg 2048 self.rotation = rotation 2049 self.size = size 2050 self._on = on 2051 self.depth = depth 2052 self.pim = None
2053
2054 - def __repr__(self):
2055 return ('<TextSprite "%s"@(%d,%d) #%d depth=%d on=%d>' % 2056 (self.name, self.x, self.y, self._id, self.depth, self._on))
2057
2058 - def clone(self):
2059 return TextSprite(self.x, self.y, self.s, 2060 fb=self.fb, fg=self.fg, bg=self.bg, 2061 rotation=self.rotation, size=self.size, 2062 on=self.on, depth=self.depth, 2063 name='clone of '+self.name)
2064
2065 - def moveto(self, x, y):
2066 """Absolute move. 2067 2068 """ 2069 self.x = x 2070 self.y = y
2071
2072 - def rmove(self, dx, dy):
2073 """Relative move. 2074 2075 """ 2076 self.x = self.x + dx 2077 self.y = self.y + dy
2078
2079 - def blit(self, flip=None, force=None):
2080 """Draw TextSprite. 2081 2082 """ 2083 if not self._on and not force: 2084 return 2085 2086 self.fb.string(self.x, self.y, self.s, self.fg, 2087 flip=flip, bg=self.bg, 2088 rotation=self.rotation, size=self.size)
2089
2090 - def on(self):
2091 """Turn TextSprite on. 2092 2093 """ 2094 self._on = 1
2095
2096 - def off(self):
2097 """Turn TextSprite off. 2098 2099 """ 2100 self._on = 0
2101
2102 - def toggle(self):
2103 """Toggle TextSprite (off->on or on->off) 2104 2105 """ 2106 self._on = not self._on 2107 return self._on
2108
2109 - def set(self, text):
2110 self.s = text
2111 2112
2113 -class MovieDecoder(object):
2114 - def __init__(self, filename):
2115 """MPEG movie decoder. 2116 2117 :param filename: (string) specifies filename of movie 2118 2119 """ 2120 self.mov = pygame.movie.Movie(filename) 2121 (self.w, self.h) = self.mov.get_size() 2122 self.surf = pygame.Surface((self.w, self.h)) 2123 self.mov.set_display(self.surf)
2124
2125 - def getframe(self, n, sprite=None):
2126 """get nth frame from movie. 2127 2128 :param n: (int) Frame number. If beyond length of movie, return's None 2129 2130 :param sprite: (Sprite obj) optional sprite to render into 2131 2132 :return: None for failure of past end of movie. If sprite is specified, 2133 returns 1, else rendered frame as numpy array. 2134 2135 """ 2136 if self.mov.render_frame(n) == n: 2137 a = pygame.surfarray.array3d(self.surf) 2138 if sprite: 2139 sprite.array[:] = a 2140 return 1 2141 else: 2142 return a 2143 else: 2144 return None
2145
2146 -class DisplayList(object):
2147 """Sprite-list manager. 2148 2149 DisplayList collects and manages a set of Sprites so you can worry 2150 about other things. 2151 2152 Each sprite is assigned a priority or stacking order. Low 2153 numbers on top, high numbers on the bottom. When you ask the 2154 display list to update, the list is sorted and blitted bottom 2155 to top (the order of two sprites with the same priority is 2156 undefined). 2157 2158 The other useful thing is that the UserDisplay class (see 2159 userdpy.py) has a display() method that takes a display list 2160 and draws simplified versions of each sprite on the user display 2161 so the user can see (approximately) what the monkeys is seeing. 2162 2163 *NB* Sprites are only drawn when they are *on* (see on() and off() 2164 methods for Sprites). 2165 2166 """ 2167
2168 - def __init__(self, fb, comedi=True):
2169 """Instantiation method. 2170 2171 :param fb: framebuffer associated with this list. This is sort 2172 of redundant, since each sprite knows which 2173 framebuffer it lives on too. It's currently only used 2174 for clearing and flipping the framebuffer after 2175 updates. 2176 2177 """ 2178 2179 if comedi: 2180 from pype import Timer 2181 else: 2182 from simpletimer import Timer 2183 2184 self.sprites = [] 2185 self.fb = fb 2186 self.bg = fb.bg 2187 self.timer = Timer() 2188 self.trigger = None 2189 self.action = None
2190 2191
2192 - def __del__(self):
2193 """Delete method. 2194 2195 Called when the display list is deleted. Goes through and 2196 tries to delete all the member sprites. The idea is that if 2197 you delete the display list at the each of each trial, this 2198 function will clean up all your sprites automatically. 2199 2200 """ 2201 for s in self.sprites: 2202 del s
2203
2204 - def add(self, s, timer=None, 2205 on=None, onev=None, off=None, offev=None):
2206 """Add sprite to display list. 2207 2208 Each time self.update is called, the timer will be 2209 checked and used to decide whether to turn this 2210 sprite on or off. 2211 2212 :param s: (Sprite/list of Sprites) sprites are added in depth order 2213 :param timer: (Timer) Timer object to use as clock 2214 :param on: (ms) time to turn sprite on 2215 :param onev: (str) encode to store for on event 2216 :param off: (ms) time to turn sprite off 2217 :param offev: (str) encode to store for off event 2218 2219 :return: nothing 2220 2221 """ 2222 if is_sequence(s): 2223 for i in s: 2224 self.add(i, timer=timer, 2225 on=on, onev=onev, off=off, offev=offev) 2226 else: 2227 s._timer = timer 2228 s._ontime = on 2229 s._onev = onev 2230 s._offtime = off 2231 s._offev = offev 2232 for ix in range(0, len(self.sprites)): 2233 if s.depth > self.sprites[ix].depth: 2234 self.sprites.insert(ix, s) 2235 return 2236 self.sprites.append(s)
2237
2238 - def delete(self, s=None):
2239 """Delete single or multiple sprites from display list. 2240 2241 *NB* Same as clear() and delete_all() methods if called with 2242 no arguments. 2243 2244 :param s: (sprite) sprite (or list of sprites) to delete, or 2245 None for delete all sprites. 2246 2247 :return: nothing 2248 2249 """ 2250 if s is None: 2251 self.sprites = [] 2252 elif is_sequence(s): 2253 for i in s: 2254 self.delete(i) 2255 else: 2256 self.sprites.remove(s)
2257
2258 - def clear(self):
2259 """Delete all sprites from the display list. 2260 2261 Same as delete_all(). 2262 2263 """ 2264 self.delete()
2265
2266 - def delete_all(self):
2267 """Delete all sprites from display list. 2268 2269 Same as clear(). 2270 2271 """ 2272 self.delete(None)
2273
2274 - def after(self, time=None, action=None, args=None):
2275 """Set up an action to be called in the future. 2276 2277 After 'time' ms, the action function will be called 2278 with three arguments: requested elapsed time, actual 2279 elapsed time and the user-specfied args. 2280 2281 :param time: (ms) ms on the timer 2282 2283 :param action: (fn) function to call 2284 2285 :param args: (anything) passed as third arg to 'action' function 2286 2287 :return: nothing 2288 2289 """ 2290 2291 if time: 2292 self.timer.reset() 2293 self.trigger = time 2294 self.action = action 2295 self.args = args
2296
2297 - def update(self, flip=None, preclear=1):
2298 """Draw sprites on framebuffer 2299 2300 1. Clear screen to background color (if specified) 2301 2. Draw all sprites (that are 'on') from bottom to top 2302 3. Optionally do a page flip. 2303 2304 :param flip: (boolean) flip after? 2305 2306 :param preclear: (boolean) clear first? 2307 2308 :return: list of encodes (from timer-triggered sprites) 2309 2310 """ 2311 2312 # before we do anything, see if a trigger time's been reached 2313 if self.action and self.trigger and self.timer.ms() >= self.trigger: 2314 et = self.timer.ms() # actual elapsed time 2315 ret = self.trigger # requested elapsed time 2316 fn = self.action # function to call 2317 args = self.args # args to pass to fn 2318 self.after() # clear trigger 2319 2320 fn(ret, et, args) # run action 2321 2322 # clear screen to background.. 2323 if preclear: 2324 if self.bg: 2325 self.fb.clear(color=self.bg) 2326 else: 2327 self.fb.clear() 2328 2329 # draw sprites in depth order 2330 encodes = [] 2331 for s in self.sprites: 2332 if (not s._ontime is None) and s._timer.ms() > s._ontime: 2333 # turn sprite on and clear on trigger 2334 s._ontime = None 2335 s.on() 2336 encodes.append(s._onev) 2337 elif (not s._offtime is None) and s._timer.ms() > s._offtime: 2338 # turn sprite off and clear off trigger 2339 s._offtime = None 2340 s.off() 2341 encodes.append(s._offev) 2342 s.blit() 2343 2344 # possibly flip screen.. 2345 if flip: 2346 self.fb.flip() 2347 return encodes
2348
2349 -def _texture_del(texture):
2350 """Delete existing GL texture on video card. 2351 2352 :note: INTERNAL USE ONLY 2353 2354 """ 2355 2356 if 0: 2357 # 2358 # THIS IS NOT CORRECT - NEED TO TRACK DOWN SOURCE!!!! 2359 # 2360 # I think this changed with 3.1.0 release... not sure! 2361 if OpenGL.version.__version__ > "3.0.0": 2362 (textureid, w, h) = texture 2363 ogl.glDeleteTextures(np.array([textureid])) 2364 else: 2365 ogl.glDeleteTextures(texture[0]) 2366 ogl.glDeleteTextures(texture[0])
2367 2368
2369 -def _texture_create(rgbastr, w, h):
2370 """Create GL texture on video card. 2371 2372 :note: INTERNAL USE ONLY 2373 2374 """ 2375 ogl.glPushAttrib(ogl.GL_TEXTURE_BIT) 2376 textureid = ogl.glGenTextures(1) 2377 ogl.glBindTexture(ogl.GL_TEXTURE_2D, textureid) 2378 ogl.glTexParameteri(ogl.GL_TEXTURE_2D, 2379 ogl.GL_TEXTURE_MAG_FILTER, ogl.GL_LINEAR) 2380 ogl.glTexParameteri(ogl.GL_TEXTURE_2D, 2381 ogl.GL_TEXTURE_MIN_FILTER, ogl.GL_LINEAR) 2382 ogl.glTexImage2D(ogl.GL_TEXTURE_2D, 0, ogl.GL_RGBA, 2383 w, h, 0, ogl.GL_RGBA, ogl.GL_UNSIGNED_BYTE, rgbastr) 2384 ogl.glPopAttrib(ogl.GL_TEXTURE_BIT) 2385 return (textureid, w, h)
2386
2387 -def _texture_blit(fb, texture, x, y, 2388 rotation=0, draw=1, contrast=1.0, xscale=1.0, yscale=1.0):
2389 """Transfer (BLIT) GL texture to display. 2390 2391 (x, y) are center coords, where (0,0) is screen center. 2392 2393 The default settings for scaling of texture mapps are for 2394 linear interoplation for both scale up and scale down. These 2395 can be set/changed using glTexParameteri() to set 2396 GL_TEXTURE_MIN_FILTER (default is GL_NEAREST_MIPMAP_LINEAR) 2397 and/or GL_TEXTURE_MAG_FILTER (default is GL_LINEAR). First is 2398 for scaling down, second scaling up. 2399 2400 :note: INTERNAL USE ONLY 2401 2402 """ 2403 2404 (texture, w, h) = texture 2405 nw = w * xscale 2406 nh = h * yscale 2407 x = (x + fb.hw) 2408 y = (y + fb.hh) 2409 cx = x - (nw/2) 2410 cy = y - (nh/2) 2411 2412 ogl.glPushMatrix() 2413 ogl.glPushAttrib(ogl.GL_TEXTURE_BIT) 2414 2415 ogl.glBindTexture(ogl.GL_TEXTURE_2D, texture) 2416 2417 # use the GL pipeline to rotate the texture 2418 if not rotation == 0: 2419 ogl.glTranslate(x, y, 0) 2420 ogl.glRotate(rotation, 0, 0, 1.0) 2421 ogl.glTranslate(-x, -y, 0) 2422 2423 ic = ((0,0), (1,0), (1,1), (0,1)) 2424 oc = ((cx, cy), (cx+nw, cy), (cx+nw, cy+nh), (cx, cy+nh)) 2425 2426 ogl.glBegin(ogl.GL_QUADS) 2427 #ogl.glColor4f(contrast, contrast, contrast, 1.0) 2428 ogl.glColor4f(1.0, 1.0, 1.0, contrast) 2429 for n in range(4): 2430 ogl.glTexCoord2f(ic[n][0], ic[n][1]) 2431 ogl.glVertex(oc[n][0], oc[n][1]) 2432 ogl.glEnd() 2433 2434 # restore GL state 2435 ogl.glPopAttrib(ogl.GL_TEXTURE_BIT) 2436 ogl.glPopMatrix()
2437
2438 -def is_sequence(x):
2439 """Is arg a sequence-type? 2440 2441 """ 2442 return (type(x) is types.ListType) or (type(x) is types.TupleType)
2443
2444 -def colr(*args, **kwargs):
2445 """Improved C() -- converts argument to RGBA color in useful way. 2446 By default generates values 0-255, but normal keyword is True, 2447 colors are assumed to be specified/generated on the range [0-1]. 2448 2449 Handled cases: 2450 colr() -> transparent color key (0,0,0,0) 2451 colr(r,g,b) 2452 colr((r,g,b)) 2453 colr(lum) --> (lum,lum,lum) 2454 colr(r,g,b,a) 2455 colr((r,g,b,a)) 2456 colr(lum) --> (lum,lum,lum, 255) 2457 colr('name') --> simple named color 2458 colr('r,g,b') --> color as string 2459 colr('r,g,b,a') --> color with alpha as string 2460 2461 """ 2462 normal = kwargs.has_key('normal') and kwargs['normal'] 2463 if normal: 2464 max = 1.0 2465 else: 2466 max = 255 2467 2468 if len(args) == 0: 2469 c = np.array((0,0,0,0)) # transparent 2470 elif len(args) == 1: 2471 if type(args[0]) is types.StringType: 2472 if args[0].lower() == 'black' or args[0].lower() == 'k': 2473 c = np.array((1,1,1,max)) 2474 elif args[0].lower() == 'white' or args[0].lower() == 'w': 2475 c = np.array((max,max,max,max)) 2476 elif args[0].lower() == 'red' or args[0].lower() == 'r': 2477 c = np.array((max,0,0,max)) 2478 elif args[0].lower() == 'green' or args[0].lower() == 'g': 2479 c = np.array((0,max,0,max)) 2480 elif args[0].lower() == 'blue' or args[0].lower() == 'b': 2481 c = np.array((0,0,max,max)) 2482 else: 2483 c = np.array(map(float, string.split(args[0], ','))) 2484 else: 2485 try: 2486 l = len(args[0]) 2487 if l > 1: 2488 c = np.array(args[0]) 2489 else: 2490 c = np.array((args[0][0], args[0][0], args[0][0],)) 2491 except TypeError: 2492 c = np.array((args[0], args[0], args[0],)) 2493 else: 2494 c = np.array(args) 2495 if len(c) < 4: 2496 if not normal: 2497 c = np.concatenate((c, [255],)) 2498 else: 2499 c = np.concatenate((c, [1.0],)) 2500 2501 if not normal: 2502 c = np.round(c) 2503 2504 return c
2505 2506 2507
2508 -def C(color, gl=0):
2509 """Color specification/normalization 2510 2511 Takes number of similar color specifications and tries to convert 2512 them all into a common format compatible with pygame, that is a 2513 length 4 tuple: (red, green, blue, alpha) where the values vary 2514 [0-255], where: 0=off/transparent and 255=on/opaque. 2515 2516 :param gl: (boolean) if true, then values range [0-1], else [0-255] 2517 2518 """ 2519 try: 2520 n = len(color) 2521 if n == 1: 2522 color = (color, color, color, 255) # scalar grayscale value 2523 elif n == 3: 2524 color = color + (255,) # RGB triple, add A 2525 elif n == 4: 2526 color = color # RGBA quad 2527 except TypeError: 2528 color = (color, color, color, 255) # non-sequence, assume scalar gray 2529 2530 if gl: 2531 color = np.round(np.array(color)/255., 3) 2532 return color
2533
2534 -def barsprite(w, h, angle, color, **kw):
2535 """Make a bar (creates new sprite). 2536 2537 Front end for the Sprite instantiation method. This makes a sprite 2538 of the specificed width and hight, fills with the specified color 2539 (or noise) and rotates the sprite to the specified angle. 2540 2541 This *really* should be a class that inherits from Sprite(). 2542 2543 :return: (sprite) requested bar 2544 2545 """ 2546 s = apply(Sprite, (w, h), kw) 2547 if color == (0,0,0): 2548 s.noise(0.50) 2549 else: 2550 s.fill(color) 2551 s.rotate(angle, 0) 2552 return s
2553
2554 -def barspriteCW(w, h, angle, color, **kw):
2555 return apply(barsprite, (w, h, angle, color), kw)
2556
2557 -def barspriteCCW(w, h, angle, color, **kw):
2558 return apply(barsprite, (w, h, -angle, color), kw)
2559
2560 -def fixsprite(x, y, fb, fix_size, color, bg):
2561 """Fixation target sprite generator. 2562 2563 Generates a simple sprite containing an "industry standard" 2564 fixation target :-) Target is a solid filled circle of the 2565 specified color. 2566 2567 :param x,y: (pixels) location of target (center) 2568 2569 :param fb: frame buffer 2570 2571 :param fix_size: (pixels) radius of target (use _ZERO_ for single 2572 pixel target) 2573 2574 :param color: fill color 2575 2576 :param bg: background color 2577 2578 :return: new sprite containing the target 2579 2580 """ 2581 if fix_size > 0: 2582 d = 1 + 2 * fix_size # make it odd 2583 fixspot = Sprite(d, d, x, y, fb=fb, depth=0, on=0) 2584 fixspot.fill(bg) 2585 fixspot.circle(color=color, r=fix_size) 2586 else: 2587 fixspot = Sprite(1, 1, x, y, fb=fb, depth=0, on=0) 2588 fixspot.fill(color) 2589 return fixspot
2590
2591 -def fixsprite2(x, y, fb, fix_size, fix_ring, color, bg):
2592 """fancy fixation target sprite generator 2593 2594 Generates a sprite containing a circular fixation target 2595 surrounded by an annulus of black (to increase detectability). 2596 2597 :param x,y: (pixels) location of target (center) 2598 2599 :param fb: frame buffer 2600 2601 :param fix_size: (pixels) radius of target (use _ZERO_ for single 2602 pixel target) 2603 2604 :param fix_ring: (pixels) radius of annulus 2605 2606 :param color: fill color 2607 2608 :param bg: background color 2609 2610 :return: new sprite containing the target 2611 2612 """ 2613 2614 d = 1 + 2 * fix_size 2615 ringd = 1 + 2 * fix_ring 2616 2617 fixspot = Sprite(ringd, ringd, x, y, fb=fb, depth=0, on=0) 2618 fixspot.fill(bg) 2619 2620 fixspot.circle(color=(1,1,1), r=fix_ring) 2621 if fix_size > 0: 2622 fixspot.circle(color=color, r=fix_size) 2623 else: 2624 fixspot.pix(fixspot.w/2, fixspot.h/2, color) 2625 2626 return fixspot
2627
2628 -def quickinit(dpy=":0.0", w=100, h=100, fullscreen=0, mouse=0):
2629 """Quickly setup and initialize framebuffer 2630 2631 :param dpy: (string) display string 2632 2633 :param w,h: (pixels) width and height of display 2634 2635 :param fullscreen: (boolean) full screen mode (default is no) 2636 2637 :param mouse: (boolean) now mouse cursor? (default is no) 2638 2639 :return: live frame buffer 2640 2641 """ 2642 if dpy is None: 2643 dpy = os.environ['DISPLAY'] 2644 return FrameBuffer(dpy, w, h, fullscreen, 2645 sync=None, mouse=mouse)
2646
2647 -def quickfb(w, h, fullscreen=0):
2648 """Get a quick (windowed) FrameBuffer for scripts and testing. 2649 2650 :param w,h: (pixels) width and height of display 2651 2652 :return: live frame buffer 2653 2654 """ 2655 return FrameBuffer(os.environ['DISPLAY'], 2656 w, h, fullscreen=fullscreen, 2657 sync=None, mouse=1)
2658 2659
2660 -def ppflag(flags):
2661 flagd = { 2662 'DOUBLEBUF': DOUBLEBUF, 2663 'HWSURFACE': HWSURFACE, 2664 'FULLSCREEN': FULLSCREEN, 2665 'OPENGL': OPENGL, 2666 'FULLSCREEN': FULLSCREEN, 2667 } 2668 2669 s = None 2670 for k in flagd.keys(): 2671 if flags & flagd[k]: 2672 if s: s = s + '|' + k 2673 else: s = k 2674 return s
2675
2676 -def drawtest(fb, s):
2677 """Draw standard test pattern. 2678 """ 2679 2680 import pypeversion 2681 from spritetools import singrat 2682 2683 fb.clear((1,1,1)) 2684 if s: s.blit(force=1) 2685 2686 # draw fixed 10 cycles/display vert grating 2687 dy = -100 2688 gr = Sprite(fb.w, fb.w, 0, dy, fb=fb) 2689 singrat(gr, 10.0, 90.0, 0.0) 2690 gr = gr.subimage(0, 0, gr.w, 50) 2691 gr.alpha[:] = 250 2692 gr.blit(force=1) 2693 fb.string(0, dy, '< 10 cycles >', (255, 0, 0)) 2694 2695 if 1: 2696 # alpha tests 2697 s = Sprite(x=50, y=50, width=150, height=150, 2698 fb=fb, on=1) 2699 s.fill((255,1,1)) 2700 s.alpha[::]=128 2701 s.blit() 2702 s = Sprite(x=-50, y=-50, width=150, height=150, 2703 fb=fb, on=1) 2704 s.fill((1,255,1)) 2705 s.alpha[::]=128 2706 s.blit() 2707 2708 s = Sprite(x=-fb.w/2, y=-fb.h/2, 2709 width=150, height=150, fb=fb, on=1) 2710 s.fill((255,255,1)) 2711 s.alpha[::]=128 2712 s.blit() 2713 2714 s = Sprite(x=-100, y=100, 2715 width=100, height=100, fb=fb, on=1) 2716 s.array[:] = 128 2717 s.array[1:25,:,0] = 255 2718 s.array[:,1:25,1] = 1 2719 s.array[1:25,1:25,2] = 1 2720 s.alpha[:] = 128 2721 s.blit() 2722 2723 s = Sprite(x=200, y=55, width=50, height=50, 2724 fb=fb, on=1, centerorigin=0) 2725 s.fill((255,1,255)) 2726 s.line(0, 0, 0, 25, (255,255,255), 5) 2727 s.line(0, 0, 25, 25, (255,255,255), 5) 2728 s.blit() 2729 2730 if 1: 2731 # sprite line drawing 2732 s = Sprite(x=200, y=-55, width=50, height=50, 2733 fb=fb, on=1, centerorigin=1) 2734 s.fill((255,1,255)) 2735 s.line(0, 0, 0, 25, (255,255,255), 5) 2736 s.line(0, 0, 25, 25, (255,255,255), 5) 2737 s.blit() 2738 2739 if 1: 2740 # sprite fills/rectangles 2741 s = Sprite(x=-200, y=55, width=50, height=50, 2742 fb=fb, on=1, centerorigin=1) 2743 s.fill((255,255,255)) 2744 2745 s.rect(0, 0, 25, 25, (255,0,0)) 2746 s.rect(10, 10, 25, 25, (255,255,0)) 2747 s.rect(-10, -10, 25, 25, (0,255,0)) 2748 s.rect(0, 0, 5, 5, (1,1,1)) 2749 2750 s.line(0,-25,0,25, (128,128,128), width=1) 2751 s.line(-25,0,25,0, (128,128,128), width=1) 2752 s.blit() 2753 2754 if 1: 2755 # direct draws to framebuffer w/o sprite 2756 fb.rectangle(200, 200, 30, 30, (255,255,255,128)) 2757 fb.circle(-200, 200, 20, (1,1,1, 128)) 2758 fb.line((0, 0), (-100,-100), width=5, 2759 color=(128,128,128)) 2760 fb.line((0, 0), (-100,-100), width=1, 2761 color=(0,0,255)) 2762 2763 fb.lines([(0,0),(0,100),(100,0)], closed=1, 2764 width=0, color=(255,0,255)) 2765 2766 fb.circle(150, -150, 50, (255,255,1,128), 0) 2767 fb.circle(150, -150, 40, (255,1,1,255), 10) 2768 fb.string(0, 0, '[pype%s]' % pypeversion.PypeVersion, 2769 (0, 255, 255)) 2770 2771 if 1: 2772 # test PolySprite 2773 pts = [(100,0), (25,25), (0,100), (-25,25), (-100,0)] 2774 s = PolySprite(pts, (255,255,1), fb, line=1, width=3) 2775 s.blit() 2776 2777 s = Sprite(50,50,100,100, fb=fb) 2778 s.fill((128,255,128,128)) 2779 s.blit() 2780 2781 s = tickbox(100, 100, 50, 50, 3, (1,1,1), fb) 2782 s.blit() 2783 2784 2785 fb.sync(0) 2786 fb.flip()
2787 2788
2789 -def tickbox(x, y, width, height, lwidth, color, fb):
2790 fs = 1.50 2791 pts = np.array(( (0, 1), (0, fs), (0, -1), (0, -fs), 2792 (1, 0), (fs, 0), (-1, 0), (-fs, 0) )) 2793 pts[:,0] = pts[:,0] * width / 2.0 + x 2794 pts[:,1] = pts[:,1] * height / 2.0 + y 2795 s = PolySprite(pts, color=color, fb=fb, joins=0, line=1, 2796 contig=0, width=lwidth) 2797 return s
2798 2799 if __name__ == '__main__': 2800 from simpletimer import Timer 2801 from spritetools import * 2802
2803 - def drawtest2(fb):
2804 s1 = Sprite(100, 100, -100, 0, fb=fb) 2805 s2 = Sprite(100, 100, 100, 0, fb=fb, scale=1) 2806 s2.alpha_gradient(r1=40, r2=50) 2807 for n in range(0,2*360,10): 2808 fb.clear() 2809 alphabar(s1, 10, 100, n, WHITE) 2810 s1.blit() 2811 s2.moveto(0, 0) 2812 singrat(s2, 1, 0.0, n, WHITE) 2813 s2.blit() 2814 s2.moveto(100, 0) 2815 s2.threshold(127) 2816 s2.blit() 2817 fb.string(0, 0, 'test2', (255, 0, 0)) 2818 fb.flip()
2819 2820
2821 - def drawtest3(fb):
2822 x = -200 2823 ss = [] 2824 s = 1.0 2825 for n in range(5): 2826 ss.append(Sprite(width=100, height=100, x=x, y=-100, 2827 scale=s,fb=fb)) 2828 singrat(ss[-1], 1, 0.0, n, WHITE) 2829 ss.append(ScaledSprite(rwidth=int(s*100), rheight=int(s*100), 2830 x=x, y=100, fb=fb, 2831 fname='lib/testpat.png')) 2832 x = x + 100 2833 s = s / 2.0 2834 2835 for n in range(0,2*360,10): 2836 fb.clear() 2837 for s in ss: 2838 s.set_rotation(-n) 2839 s.blit() 2840 fb.string(0, 0, 'test3', (255, 0, 0)) 2841 fb.flip() 2842 if 0: 2843 print '%d deg, <return>' % (n,), 2844 sys.stdin.readline()
2845
2846 - def drawtest4(fb):
2847 x = -200 2848 w = 100 2849 ss = [] 2850 2851 sig = 20 2852 s = ScaledSprite(width=sig*6, x=0, y=0, fb=fb) 2853 for n in range(0,2*360,10): 2854 fb.clear() 2855 gabor(s, 3.0, float(n), 45., sig, WHITE) 2856 s.blit() 2857 fb.string(0, 0, 'test4', (255, 0, 0)) 2858 fb.flip()
2859 2860 fb=quickfb(500,500) 2861 2862 drawtest4(fb) 2863 sys.stdout.write('<hit return to continue>'); sys.stdin.readline() 2864 drawtest3(fb) 2865 sys.stdout.write('<hit return to continue>'); sys.stdin.readline() 2866 drawtest2(fb) 2867 sys.stdout.write('<hit return to continue>'); sys.stdin.readline() 2868