Package VisionEgg :: Module Textures
[frames] | no frames]

Source Code for Module VisionEgg.Textures

   1  # The Vision Egg: Textures 
   2  # 
   3  # Copyright (C) 2001-2004 Andrew Straw 
   4  # Copyright (C) 2004-2008 California Institute of Technology 
   5  # 
   6  # URL: <http://www.visionegg.org/> 
   7  # 
   8  # Distributed under the terms of the GNU Lesser General Public License 
   9  # (LGPL). See LICENSE.TXT that came with this file. 
  10   
  11  """ 
  12  Texture (images mapped onto polygons) stimuli. 
  13   
  14  """ 
  15   
  16  #################################################################### 
  17  # 
  18  #        Import all the necessary packages 
  19  # 
  20  #################################################################### 
  21   
  22  import logging                              # available in Python 2.3 
  23   
  24  import VisionEgg 
  25  import VisionEgg.Core 
  26  import VisionEgg.ParameterTypes as ve_types 
  27   
  28  import Image, ImageDraw                         # Python Imaging Library packages 
  29  import pygame.surface, pygame.image             # pygame 
  30  import math, types, os 
  31  import numpy 
  32  import numpy.oldnumeric as numpyNumeric, numpy.oldnumeric.mlab as MLab 
  33   
  34  import VisionEgg.GL as gl # get all OpenGL stuff in one namespace 
  35  import OpenGL.GLU as glu 
  36   
  37  # These modules are part of PIL and get loaded as needed by Image. 
  38  # They are listed here so that Gordon McMillan's Installer properly 
  39  # locates them.  You will not hurt anything other than your ability to 
  40  # make executables using Intaller if you remove these lines. 
  41  import _imaging 
  42  import ImageFile, ImageFileIO, BmpImagePlugin, JpegImagePlugin, PngImagePlugin 
  43   
  44  if Image.VERSION >= '1.1.3': 
  45      shrink_filter = Image.ANTIALIAS # Added in PIL 1.1.3 
  46  else: 
  47      shrink_filter = Image.BICUBIC # Fallback filtering 
  48   
  49  array_types = [numpy.ndarray] 
  50  # Allow use of numarray and original Numeric texels without requiring either 
  51  try: 
  52      import numarray 
  53      array_types.append( numarray.numarraycore.NumArray ) 
  54  except ImportError: 
  55      pass 
  56  try: 
  57      import Numeric as orig_Numeric 
  58      array_types.append( orig_Numeric.ArrayType ) 
  59  except ImportError: 
  60      pass 
  61   
62 -def convert_to_numpy_if_array(A):
63 if type(A) in array_types: 64 # with late release Numeric and numarray this is a view of the data 65 return numpy.asarray(A) 66 else: 67 return A
68 69 #################################################################### 70 # 71 # XXX ToDo: 72 73 # The main remaining feature to add to this module is automatic 74 # management of texture objects. This would allow many small images 75 # (e.g. a bit of text) to live in one large texture object. This 76 # would be much faster when many small textures are drawn in rapid 77 # succession. (Apparently this might not be such a big improvement on 78 # OS X. (See http://crystal.sourceforge.net/phpwiki/index.php?MacXGL) 79 80 # Here's a sample from Apple's TextureRange demo which is supposed 81 # to speed up texture transfers. 82 # glBindTextures( target, &texID); 83 # glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, 1); 84 # glTexImage2D(target, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,image_ptr); 85 # Update the texture with: 86 # glTexSubImage2D(target, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,image_ptr); 87 88 #################################################################### 89 90 #################################################################### 91 # 92 # Textures 93 # 94 #################################################################### 95
96 -def next_power_of_2(f):
97 return int(math.pow(2.0,math.ceil(math.log(f)/math.log(2.0))))
98
99 -def is_power_of_2(f):
100 return f == next_power_of_2(f)
101
102 -class Texture(object):
103 """A 2 dimensional texture. 104 105 The pixel data can come from an image file, an image file stream, 106 an instance of Image from the Python Imaging Library, a numpy 107 array, or None. 108 109 If the data is a numpy array, floating point numbers are assumed 110 to be in the range 0.0 to 1.0, and integers are assumed to be in 111 the range 0 to 255. The first index is the row (y position), the 112 second index is the column (x position), and if it's RGB or RGBA 113 data, the third index specifies the color band. Thus, if the 114 texel data was 640 pixels wide by 480 pixels tall, the array would 115 have shape (480,640) for luminance information, (480,640,3) for 116 RGB information, and (480,640,4) for RGBA information. 117 118 The 2D texture data is not sent to OpenGL (video texture memory) 119 until the load() method is called. The unload() method may be 120 used to remove the data from OpenGL. 121 122 A reference to the original image data is maintained.""" 123 124 __slots__ = ('texels', 125 'texture_object', 126 'size', 127 '_filename', 128 '_file_stream', 129 'buf_lf', 130 'buf_rf', 131 'buf_bf', 132 'buf_tf', 133 '_buf_l', 134 '_buf_r', 135 '_buf_b', 136 '_buf_t', 137 ) 138
139 - def __init__(self,texels=None,size=None):
140 """Creates instance of Texture object. 141 142 texels -- Texture data. If not specified, a blank white 143 texture is created. 144 size -- If a tuple, force size of texture data if possible, 145 raising an exception if not. If None, has no effect. 146 """ 147 148 if texels is None: # no texel data: make default 149 if size is None: 150 size = (256,256) # an arbitrary default size 151 texels = Image.new("RGB",size,(255,255,255)) # white 152 153 if type(texels) == types.FileType: 154 texels = Image.open(texels) # Attempt to open as an image file 155 elif type(texels) in (types.StringType,types.UnicodeType): 156 # is this string a filename or raw image data? 157 if os.path.isfile(texels): 158 # cache filename and file stream for later use (if possible) 159 self._filename = texels 160 self._file_stream = open(texels,"rb") 161 texels = Image.open(texels) # Attempt to open as an image stream 162 163 texels = convert_to_numpy_if_array(texels) 164 165 if isinstance(texels, Image.Image): # PIL Image 166 if texels.mode == 'P': # convert from paletted 167 texels = texels.convert('RGBX') 168 self.size = texels.size 169 elif isinstance(texels, pygame.surface.Surface): # pygame surface 170 self.size = texels.get_size() 171 elif isinstance(texels,numpy.ndarray): # numpy array 172 if len(texels.shape) == 3: 173 if texels.shape[2] not in [3,4]: 174 raise ValueError("Only luminance (rank 2), and RGB, RGBA (rank 3) arrays allowed") 175 elif len(texels.shape) != 2: 176 raise ValueError("Only luminance (rank 2), and RGB, RGBA (rank 3) arrays allowed") 177 self.size = ( texels.shape[1], texels.shape[0] ) 178 else: 179 raise TypeError("texel data could not be recognized. (Use a PIL Image, numpy array, or pygame surface.)") 180 181 self.texels = texels 182 self.texture_object = None 183 184 if size is not None and size != self.size: 185 raise ValueError("size was specified, but data could not be rescaled")
186
187 - def update(self):
188 """Update texture data 189 190 This method does nothing, but may be overriden in classes that 191 need to update their texture data whenever drawn. 192 193 It it called by the draw() method of any stimuli using 194 textures when its texture object is active, so it can safely 195 use put_sub_image() to manipulate its own texture data. 196 """ 197 pass
198
199 - def make_half_size(self):
200 if self.texture_object is not None: 201 raise RuntimeError("make_half_size() only available BEFORE texture loaded to OpenGL.") 202 203 if isinstance(self.texels,Image.Image): 204 w = self.size[0]/2 205 h = self.size[1]/2 206 small_texels = self.texels.resize((w,h),shrink_filter) 207 self.texels = small_texels 208 self.size = (w,h) 209 else: 210 raise NotImplementedError("Texture too large, but rescaling only implemented for PIL images.")
211
212 - def unload(self):
213 """Unload texture data from video texture memory. 214 215 This only removes data from the video texture memory if there 216 are no other references to the TextureObject instance. To 217 ensure this, all references to the texture_object argument 218 passed to the load() method should be deleted.""" 219 220 self.texture_object = None
221
222 - def get_texels_as_image(self):
223 """Return texel data as PIL image""" 224 if isinstance(self.texels,numpy.ndarray): 225 if len(self.texels.shape) == 2: 226 a = self.texels 227 if a.dtype == numpy.uint8: 228 mode = "L" 229 elif a.dtype == numpy.float32: 230 mode = "F" 231 else: 232 raise ValueError("unsupported image mode") 233 return Image.fromstring(mode, (a.shape[1], a.shape[0]), a.tostring()) 234 else: 235 raise NotImplementedError("Currently only luminance data can be converted to images") 236 elif isinstance(self.texels, Image.Image): 237 return self.texels 238 else: 239 raise NotImplementedError("Don't know how to convert texel data to PIL image")
240
241 - def get_pixels_as_image(self):
242 logger = logging.getLogger('VisionEgg.Textures') 243 logger.warning("Using deprecated method get_pixels_as_image(). " 244 "Use get_texels_as_image() instead.") 245 return self.get_texels_as_image()
246
247 - def load(self, 248 texture_object, 249 build_mipmaps = True, 250 rescale_original_to_fill_texture_object = False, 251 internal_format=gl.GL_RGB):
252 """Load texture data to video texture memory. 253 254 This will cause the texture data to become resident in OpenGL 255 video texture memory, enabling fast drawing. 256 257 The texture_object argument is used to specify an instance of 258 the TextureObject class, which is a wrapper for the OpenGL 259 texture object holding the resident texture. 260 261 To remove a texture from OpenGL's resident textures: TextureObject passed as the texture_object argument and 2) 262 call the unload() method""" 263 264 assert( isinstance( texture_object, TextureObject )) 265 assert( texture_object.dimensions == 2 ) 266 267 width, height = self.size 268 269 width_pow2 = next_power_of_2(width) 270 height_pow2 = next_power_of_2(height) 271 272 if rescale_original_to_fill_texture_object: 273 if not isinstance(self.texels,Image.Image): 274 raise NotImplementedError("Automatic rescaling not implemented for this texel data type.") 275 276 # fractional coverage 277 self.buf_lf = 0.0 278 self.buf_rf = float(width)/width_pow2 279 self.buf_bf = 0.0 280 self.buf_tf = float(height)/height_pow2 281 282 # absolute (texel units) coverage 283 self._buf_l = 0 284 self._buf_r = width 285 self._buf_b = 0 286 self._buf_t = height 287 288 if width != width_pow2 or height != height_pow2: 289 if isinstance(self.texels,numpy.ndarray): 290 if len(self.texels.shape) == 2: 291 buffer = numpy.zeros( (height_pow2,width_pow2), dtype=self.texels.dtype ) 292 buffer[0:height,0:width] = self.texels 293 elif len(self.texels.shape) == 3: 294 buffer = numpy.zeros( (height_pow2,width_pow2,self.texels.shape[2]), dtype=self.texels.dtype ) 295 buffer[0:height,0:width,:] = self.texels 296 else: 297 raise RuntimeError("Unexpected shape for self.texels") 298 299 elif isinstance(self.texels, Image.Image): # PIL Image 300 if rescale_original_to_fill_texture_object: 301 # reset coverage values 302 self.buf_lf = 0.0 303 self.buf_rf = 1.0 304 self.buf_bf = 0.0 305 self.buf_tf = 1.0 306 307 self._buf_l = 0 308 self._buf_r = width_pow2 309 self._buf_t = 0 310 self._buf_b = height_pow2 311 312 buffer = self.texels.resize((width_pow2,height_pow2),shrink_filter) 313 314 self.size = (width_pow2, height_pow2) 315 else: 316 buffer = Image.new(self.texels.mode,(width_pow2, height_pow2)) 317 buffer.paste( self.texels, (0,height_pow2-height,width,height_pow2)) 318 elif isinstance(self.texels, pygame.surface.Surface): # pygame surface 319 buffer = pygame.surface.Surface( (width_pow2, height_pow2), 320 self.texels.get_flags(), 321 self.texels.get_bitsize() ) 322 buffer.blit( self.texels, (0,height_pow2-height) ) 323 else: 324 raise RuntimeError("texel data not recognized - changed?") 325 else: 326 buffer = self.texels 327 328 # Put data in texture object 329 if not build_mipmaps: 330 texture_object.put_new_image( buffer, internal_format=internal_format, mipmap_level=0 ) 331 else: 332 if 0: 333 # Build mipmaps with GLU (faster, but currently broken) 334 texture_object.put_new_image_build_mipmaps( buffer, internal_format=internal_format ) 335 else: 336 # Build mipmaps in PIL 337 texture_object.put_new_image( buffer, internal_format=internal_format, mipmap_level=0 ) 338 if not isinstance(self.texels, Image.Image): # PIL Image 339 raise NotImplementedError( 340 "Building of mipmaps not implemented for this texel "+\ 341 "data type. (Use PIL Images or set parameter "+\ 342 "mipmaps_enabled = False.)") 343 this_width, this_height = self.size 344 biggest_dim = max(this_width,this_height) 345 mipmap_level = 1 346 while biggest_dim > 1: 347 this_width = this_width/2.0 348 this_height = this_height/2.0 349 350 width_pix = int(math.ceil(this_width)) 351 height_pix = int(math.ceil(this_height)) 352 shrunk = self.texels.resize((width_pix,height_pix),shrink_filter) 353 354 width_pow2 = next_power_of_2(width_pix) 355 height_pow2 = next_power_of_2(height_pix) 356 357 im = Image.new(shrunk.mode,(width_pow2,height_pow2)) 358 im.paste(shrunk,(0,height_pow2-height_pix,width_pix,height_pow2)) 359 360 texture_object.put_new_image( im, 361 mipmap_level=mipmap_level, 362 internal_format = internal_format, 363 check_opengl_errors = False, # no point -- we've already seen biggest texture work, we're just making mipmap 364 ) 365 366 mipmap_level += 1 367 biggest_dim = max(this_width,this_height) 368 369 # Keep reference to texture_object 370 self.texture_object = texture_object
371
372 - def get_texture_object(self):
373 return self.texture_object
374
375 -class TextureFromFile( Texture ):
376 """DEPRECATED."""
377 - def __init__(self, filename ):
378 logger = logging.getLogger('VisionEgg.Textures') 379 logger.warning("class TextureFromFile deprecated, use class " 380 "Texture instead.") 381 Texture.__init__(self, filename)
382
383 -class TextureObject(object):
384 """Texture data in OpenGL. Potentially resident in video texture memory. 385 386 This class encapsulates the state variables in OpenGL texture objects. Do not 387 change attribute values directly. Use the methods provided instead.""" 388 389 __slots__ = ( 390 'min_filter', 391 'mag_filter', 392 'wrap_mode_r', # if dimensions > 2 393 'wrap_mode_s', 394 'wrap_mode_t', # if dimensions > 1 395 'border_color', 396 'target', 397 'dimensions', 398 'gl_id', 399 '__gl_module__', 400 ) 401 402 _cube_map_side_names = ['positive_x', 'negative_x', 403 'positive_y', 'negative_y', 404 'positive_z', 'negative_z'] 405
406 - def __init__(