1
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:
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
52
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
74 return PIL.Image.frombytes('RGBA', image.get_size(),
75 pygame.image.tostring(image, 'RGBA'))
76 else:
77
79 return PIL.Image.fromstring('RGBA', image.get_size(),
80 pygame.image.tostring(image, 'RGBA'))
81
83 _instance = None
84
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
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
194
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'
204
205 if dpy:
206 self.gui_dpy = os.environ['DISPLAY']
207 self.fb_dpy = dpy
208
209
210
211 try:
212 os.environ['DISPLAY'] = self.fb_dpy
213 pygame.init()
214 finally:
215 os.environ['DISPLAY'] = self.gui_dpy
216
217
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
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
246
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
256 self.sync_mode = not (syncsize <= 0 or (not sync))
257 if self.sync_mode:
258
259
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
266
267
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
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
286 self.sync(0)
287 else:
288 self._sync_low = None
289 self._sync_high = None
290 self.syncinfo = None
291
292
293
294
295
296 self.maxfliptime = 0
297 self.fliptimer = None
298
299 self.cursor(mouse)
300 pygame.event.set_grab(0)
301
302
303 self.fullscreen = self.flags & FULLSCREEN
304
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
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
315
316
317
318
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
327
328 self.screen = pygame.display.set_mode((1,1), self.flags)
329
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
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
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
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
386
387
388
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
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
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
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
455 pygame.event.clear()
456 pygame.display.toggle_fullscreen()
457 while 1:
458
459
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
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
559
560
561
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
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
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
611 """LEFT Shift key down?"""
612 pygame.event.pump()
613 return KMOD_LSHIFT & pygame.key.get_mods()
614
616 """RIGHT Shift key down?"""
617 pygame.event.pump()
618 return KMOD_RSHIFT & pygame.key.get_mods()
619
621 """LEFT Ctrl key down?"""
622 pygame.event.pump()
623 return KMOD_LCTRL & pygame.key.get_mods()
624
626 """RIGHT Ctrl key down?"""
627 pygame.event.pump()
628 return KMOD_RCTL & pygame.key.get_mods()
629
631 """Wait for keyboard to be clear (no pressed keys)."""
632 while len(self.checkkeys()) > 0:
633 pass
634
636 """Get list of pressed keys."""
637 pygame.event.pump()
638 return map(pygame.key.name, np.where(pygame.key.get_pressed())[0])
639
641 """Get list of pressed mouse buttons."""
642 pygame.event.pump()
643 return pygame.mouse.get_pressed()
644
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
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
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
775
776
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
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
961 _id = 0
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
1040 dwidth, dheight = width, height
1041
1042
1043 if dheight is None: dheight = dwidth
1044 if rheight is None: rheight = rwidth
1045
1046
1047 if dwidth and not rwidth:
1048 rwidth, rheight = dwidth, dheight
1049
1050
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
1066
1067
1068 self.im = pygame.image.load(fname).convert(32, pygame.SRCALPHA)
1069 if rwidth:
1070
1071
1072 self.im = pygame.transform.scale(self.im, (rwidth, rheight))
1073 self.userdict = {}
1074 setalpha = 0
1075 elif image:
1076 import copy
1077
1078
1079
1080
1081 try:
1082
1083 self.im = image.im.convert(32, pygame.SRCALPHA)
1084 self.userdict = copy.copy(image.userdict)
1085 except:
1086
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
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
1112
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
1121 self._id = Sprite._id
1122 Sprite._id = Sprite._id + 1
1123
1124 self.refresh()
1125
1126
1127 if setalpha:
1128 self.alpha[::] = 255
1129
1131
1132
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
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
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
1163 """Respects `centerorigin` flag."""
1164 return (self.X(xy[0]), self.Y(xy[1]))
1165
1167 """Respects `centerorigin` flag."""
1168 if self.centerorigin:
1169 return (self.w / 2) + x
1170 else:
1171 return x
1172
1174 """Respects `centerorigin` flag.
1175
1176 """
1177 if self.centerorigin:
1178 return (self.h / 2) - y
1179 else:
1180 return y
1181
1183 """Sets GL-pipeline rotation term - fast rotation on video card.
1184
1185 """
1186 self.rotation = deg
1187
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
1224
1225
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
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
1328
1329
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
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
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
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
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
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
1599 """Make sprite visible.
1600
1601 """
1602 self._on = 1
1603
1605 """Make sprite invisible.
1606
1607 """
1608 self._on = 0
1609
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
1620 """Get current on/off flag state.
1621
1622 :return: boolean on/off flag
1623
1624 """
1625 return self._on
1626
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
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
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
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[::]
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
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
1782 return pygame.image.save(self.displayim(), fname)
1783
1785 raise Obsolete, 'save_ppm has been removed'
1786
1788 raise Obsolete, 'save_ppm has been removed'
1789
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
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
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
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
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
1977 """Turn PolySprite on.
1978
1979 """
1980 self._on = 1
1981
1983 """Turn PolySprite off.
1984
1985 """
1986 self._on = 0
1987
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
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
2091 """Turn TextSprite on.
2092
2093 """
2094 self._on = 1
2095
2097 """Turn TextSprite off.
2098
2099 """
2100 self._on = 0
2101
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):
2111
2112
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
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
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
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
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
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
2259 """Delete all sprites from the display list.
2260
2261 Same as delete_all().
2262
2263 """
2264 self.delete()
2265
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
2313 if self.action and self.trigger and self.timer.ms() >= self.trigger:
2314 et = self.timer.ms()
2315 ret = self.trigger
2316 fn = self.action
2317 args = self.args
2318 self.after()
2319
2320 fn(ret, et, args)
2321
2322
2323 if preclear:
2324 if self.bg:
2325 self.fb.clear(color=self.bg)
2326 else:
2327 self.fb.clear()
2328
2329
2330 encodes = []
2331 for s in self.sprites:
2332 if (not s._ontime is None) and s._timer.ms() > s._ontime:
2333
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
2339 s._offtime = None
2340 s.off()
2341 encodes.append(s._offev)
2342 s.blit()
2343
2344
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
2359
2360
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
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
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
2435 ogl.glPopAttrib(ogl.GL_TEXTURE_BIT)
2436 ogl.glPopMatrix()
2437
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))
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)
2523 elif n == 3:
2524 color = color + (255,)
2525 elif n == 4:
2526 color = color
2527 except TypeError:
2528 color = (color, color, color, 255)
2529
2530 if gl:
2531 color = np.round(np.array(color)/255., 3)
2532 return color
2533
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
2555 return apply(barsprite, (w, h, angle, color), kw)
2556
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
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
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
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
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
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
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
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
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
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
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
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
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
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