1 # -*- coding: utf-8 -*-
4 :copyright: 2009 by Florian Boesch <pyalot@gmail.com>.
5 :license: GNU AGPL v3 or later, see LICENSE for more details.
8 from __future__ import with_statement
10 from gletools.gl import *
11 from .util import Context, DependencyException, quad, ExtensionMissing
12 from ctypes import string_at, sizeof, byref, c_char_p, cast, c_void_p, POINTER, memmove, c_ubyte, string_at
23 def __init__(self, **kwargs):
24 self.__dict__.update(kwargs)
28 glGenTextures(1, byref(id))
31 class Texture(Context):
35 enum = GL_UNSIGNED_BYTE
39 enum = GL_UNSIGNED_SHORT
45 gl_half_float = Object(
67 enum = GL_DEPTH_COMPONENT,
100 GL_LUMINANCE32F:Object(
103 channels = luminance,
107 channels = luminance,
113 GL_DEPTH_COMPONENT:Object(
119 target = GL_TEXTURE_2D
121 _get = GL_TEXTURE_BINDING_2D
130 glBindTexture(self.target, id)
133 glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT)
134 glActiveTexture(self.unit)
135 glEnable(self.target)
140 def __init__(self, width, height, format=GL_RGBA, filter=GL_LINEAR, unit=GL_TEXTURE0, data=None, mipmap=0, clamp=False):
141 if format in self.float_targets:
142 if not gl_info.have_extension('GL_ARB_texture_float'):
143 raise ExtensionMissing('no floating point texture support (GL_ARB_texture_float)')
145 Context.__init__(self)
153 spec = self.spec = self.specs[format]
154 self.buffer_type = (spec.type.obj * (width * height * spec.channels.count))
155 id = self.id = gen_texture()
157 if isinstance(data, str):
158 pointer = cast(c_char_p(data), c_void_p)
159 source = self.buffer_type.from_address(pointer.value)
160 target = self.buffer_type()
161 memmove(target, source, sizeof(source))
164 self.buffer = self.buffer_type(*data)
170 self.display = self.make_display()
172 def get_buffer(self):
174 self._buffer = self.buffer_type()
176 def set_buffer(self, data):
178 buffer = property(get_buffer, set_buffer)
181 glDeleteTextures(1, byref(self.id))
184 def open(cls, filename, format=GL_RGBA, filter=GL_LINEAR, unit=GL_TEXTURE0, mipmap=0):
186 raise DependencyException('PIL is requried to open image files')
187 spec = cls.specs[format]
188 pil_format = getattr(spec, 'pil', None)
190 raise Exception('cannot load')
191 image = Image.open(filename)
192 image = image.convert(pil_format)
193 width, height = image.size
194 data = image.tostring()
196 if spec.type == cls.gl_float:
197 data = map(lambda x: ord(x)/255.0, data)
199 data = map(ord, data)
201 return cls(width, height, format=format, filter=filter, unit=unit, data=data, mipmap=mipmap)
204 def raw_open(cls, filename, width, height, format=GL_RGBA, filter=GL_LINEAR, unit=GL_TEXTURE0, mipmap=0, clamp=False):
205 data = open(filename, 'rb').read()
206 self = cls(width, height, data=data, format=format, filter=filter, unit=unit, mipmap=mipmap, clamp=clamp)
209 def save(self, filename):
211 if self.spec.type == self.gl_byte:
212 source = string_at(self.buffer, sizeof(self.buffer))
213 image = Image.fromstring(self.spec.pil, (self.width, self.height), source)
216 raise Exception('cannot save non byte images')
218 def make_display(self):
220 uvs = 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0
233 return pyglet.graphics.vertex_list(4,
238 def draw(self, x=0, y=0, scale=1.0):
241 left=x, top=self.height+y, right=self.width+x, bottom=y, scale=scale
244 def set_data(self, data=None, clamp=False, level=0):
246 if isinstance(self.filter, tuple):
247 min_filter, mag_filter = self.filter
249 min_filter = self.filter
250 mag_filter = GL_LINEAR
252 glTexParameteri(self.target, GL_TEXTURE_MIN_FILTER, min_filter)
253 glTexParameteri(self.target, GL_TEXTURE_MAG_FILTER, mag_filter)
257 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
259 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
264 self.target, self.mipmap,
265 self.width, self.height,
266 self.spec.channels.enum,
272 self.target, level, self.format,
273 self.width, self.height,
275 self.spec.channels.enum, self.spec.type.enum,
280 self.target, level, self.format,
281 self.width/2**level, self.height/2**level,
282 #self.width, self.height,
285 self.spec.channels.enum, self.spec.type.enum,
291 def get_data(self, buffer):
293 glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT)
295 self.target, 0, self.spec.channels.enum, self.spec.type.enum,
303 self.set_data(self.buffer, self.clamp)
305 self.set_data(clamp=self.clamp)
308 self.get_data(self.buffer)
311 def __getitem__(self, (x, y)):
312 x, y = x%self.width, y%self.height
314 channels = self.spec.channels.count
315 pos = (x + y * self.width) * channels
317 return self.buffer[pos]
320 return self.buffer[pos:end]
322 def __setitem__(self, (x, y), value):
323 x, y = x%self.width, y%self.height
325 channels = self.spec.channels.count
326 pos = (x + y * self.width) * channels
328 self.buffer[pos] = value
331 self.buffer[pos:end] = value
334 channels = self.spec.channels.count
336 for value in self.buffer:
339 for i in range(0, len(self.buffer), channels):
340 yield self.buffer[i:i+channels]
342 class CubeMap(Context):
344 Assumes a texturelayout of all 6 faces as a single row of:
345 right = +x, back = -z, left = -x, front = +z, bottom = -y, top = +y
348 _get = GL_TEXTURE_BINDING_CUBE_MAP
349 target = GL_TEXTURE_CUBE_MAP
352 def __init__(self, width, height, data):
353 Context.__init__(self)
354 id = self.id = gen_texture()
359 glTexParameteri(self.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
360 glTexParameteri(self.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
362 right = GL_TEXTURE_CUBE_MAP_POSITIVE_X
363 left = GL_TEXTURE_CUBE_MAP_NEGATIVE_X
364 top = GL_TEXTURE_CUBE_MAP_POSITIVE_Y
365 bottom = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
366 front = GL_TEXTURE_CUBE_MAP_POSITIVE_Z
367 back = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
368 face_size = (self.height**2) * 4
370 self.set_data(right, data, 0)
371 self.set_data(back, data, 1)
372 self.set_data(left, data, 2)
373 self.set_data(front, data, 3)
374 self.set_data(bottom, data, 4)
375 self.set_data(top, data, 5)
377 def set_data(self, target, data, index):
380 self.height, self.height,
382 GL_RGBA, GL_UNSIGNED_BYTE,
383 self.face_data(data, index),
386 def face_data(self, data, index):
388 face_pitch = self.height*4
389 full_pitch = self.width*4
391 for y in range(self.height):
392 offset = y*full_pitch
393 start = offset + index*face_pitch
394 end = start + face_pitch
395 result += data[start:end]
400 glBindTexture(self.target, id)
403 glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT)
404 glActiveTexture(self.unit)
405 glEnable(self.target)
411 def open(cls, filename):
413 raise DependencyException('PIL is requried to open image files')
415 image = Image.open(filename)
416 image = image.convert('RGBA')
417 width, height = image.size
418 data = image.tostring()
420 return cls(width, height, data)
422 class Texture1D(Context):
423 target = GL_TEXTURE_1D
424 _get = GL_TEXTURE_BINDING_1D
426 def __init__(self, data, unit=GL_TEXTURE0, ctype=c_ubyte, format=GL_LUMINANCE, type=GL_UNSIGNED_BYTE, internal_format=GL_LUMINANCE):
427 Context.__init__(self)
428 data = (ctype*len(data))(*data)
431 self.id = gen_texture()
433 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
434 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
436 GL_TEXTURE_1D, 0, internal_format,
444 glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT)
445 glActiveTexture(self.unit)
446 glEnable(self.target)
449 glBindTexture(GL_TEXTURE_1D, id)
454 class ArrayTexture(Context):
455 target = GL_TEXTURE_2D_ARRAY
456 _get = GL_TEXTURE_BINDING_2D_ARRAY
458 def __init__(self, data, width, height, slice_count, format=GL_RGBA, type=GL_UNSIGNED_BYTE, internal_format=GL_RGBA, unit=GL_TEXTURE0, mipmaps=4):
459 Context.__init__(self)
462 self.id = gen_texture()
466 glTexParameteri(self.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
467 glTexParameteri(self.target, GL_TEXTURE_BASE_LEVEL, 0)
468 glTexParameteri(self.target, GL_TEXTURE_MAX_LEVEL, mipmaps)
470 glTexParameteri(self.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
471 glTexParameteri(self.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
473 pointer = cast(c_char_p(data), c_void_p)
475 self.target, 0, internal_format,
476 width, height, slice_count, 0,
477 format, type, pointer,
481 glGenerateMipmap(self.target)
486 def raw_open(cls, names, width, height, format=GL_RGBA, type=GL_UNSIGNED_BYTE, internal_format=GL_RGBA, unit=GL_TEXTURE0, ctype=c_ubyte, channels=4, mipmaps=4):
487 slices = [open(name, 'rb').read() for name in names]
488 slice_count = len(slices)
489 base_level = ''.join(slices)
491 return cls(base_level, width, height, slice_count, format, type, internal_format, unit, mipmaps)
494 glPushAttrib(GL_TEXTURE_BIT)
495 glActiveTexture(self.unit)
501 glBindTexture(self.target, id)