1
2
3
4
5
6
7
8
9
10
11 """
12 Texture (images mapped onto polygons) stimuli.
13
14 """
15
16
17
18
19
20
21
22 import logging
23
24 import VisionEgg
25 import VisionEgg.Core
26 import VisionEgg.ParameterTypes as ve_types
27
28 import Image, ImageDraw
29 import pygame.surface, pygame.image
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
35 import OpenGL.GLU as glu
36
37
38
39
40
41 import _imaging
42 import ImageFile, ImageFileIO, BmpImagePlugin, JpegImagePlugin, PngImagePlugin
43
44 if Image.VERSION >= '1.1.3':
45 shrink_filter = Image.ANTIALIAS
46 else:
47 shrink_filter = Image.BICUBIC
48
49 array_types = [numpy.ndarray]
50
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
63 if type(A) in array_types:
64
65 return numpy.asarray(A)
66 else:
67 return A
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
97 return int(math.pow(2.0,math.ceil(math.log(f)/math.log(2.0))))
98
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:
149 if size is None:
150 size = (256,256)
151 texels = Image.new("RGB",size,(255,255,255))
152
153 if type(texels) == types.FileType:
154 texels = Image.open(texels)
155 elif type(texels) in (types.StringType,types.UnicodeType):
156
157 if os.path.isfile(texels):
158
159 self._filename = texels
160 self._file_stream = open(texels,"rb")
161 texels = Image.open(texels)
162
163 texels = convert_to_numpy_if_array(texels)
164
165 if isinstance(texels, Image.Image):
166 if texels.mode == 'P':
167 texels = texels.convert('RGBX')
168 self.size = texels.size
169 elif isinstance(texels, pygame.surface.Surface):
170 self.size = texels.get_size()
171 elif isinstance(texels,numpy.ndarray):
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
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
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
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
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
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
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):
300 if rescale_original_to_fill_texture_object:
301
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):
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
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
334 texture_object.put_new_image_build_mipmaps( buffer, internal_format=internal_format )
335 else:
336
337 texture_object.put_new_image( buffer, internal_format=internal_format, mipmap_level=0 )
338 if not isinstance(self.texels, Image.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,
364 )
365
366 mipmap_level += 1
367 biggest_dim = max(this_width,this_height)
368
369
370 self.texture_object = texture_object
371
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',
393 'wrap_mode_s',
394 'wrap_mode_t',
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