gletools/texture.py
author Florian Boesch <pyalot@gmail.com>
Thu, 10 Mar 2011 18:27:37 +0100
changeset 69 da011bbc16d0
parent 63 b94a0194597b
permissions -rw-r--r--
added a check for the geometry shader
     1 # -*- coding: utf-8 -*-
     2 
     3 """
     4     :copyright: 2009 by Florian Boesch <pyalot@gmail.com>.
     5     :license: GNU AGPL v3 or later, see LICENSE for more details.
     6 """
     7 
     8 from __future__ import with_statement
     9 
    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
    13 
    14 try:
    15     import Image
    16     has_pil = True
    17 except:
    18     has_pil = False
    19 
    20 __all__ = ['Texture']
    21 
    22 class Object(object):
    23     def __init__(self, **kwargs):
    24         self.__dict__.update(kwargs)
    25 
    26 def gen_texture():
    27     id = GLuint()
    28     glGenTextures(1, byref(id))
    29     return id
    30 
    31 class Texture(Context):
    32 
    33     gl_byte = Object(
    34         obj = GLubyte,
    35         enum = GL_UNSIGNED_BYTE
    36     )
    37     gl_short = Object(
    38         obj = GLushort,
    39         enum = GL_UNSIGNED_SHORT
    40     )
    41     gl_float = Object(
    42         obj = GLfloat,
    43         enum = GL_FLOAT,
    44     )
    45     gl_half_float = Object(
    46         obj = GLfloat,
    47         enum = GL_FLOAT,
    48     )
    49 
    50     rgb = Object(
    51         enum = GL_RGB,
    52         count = 3,
    53     )
    54     rgba = Object(
    55         enum = GL_RGBA,
    56         count = 4,
    57     )
    58     luminance = Object(
    59         enum  = GL_LUMINANCE,
    60         count = 1,
    61     )
    62     alpha = Object(
    63         enum = GL_ALPHA,
    64         count = 1,
    65     )
    66     depth = Object(
    67         enum = GL_DEPTH_COMPONENT,
    68         count = 1,
    69     )
    70 
    71     specs = {
    72         GL_RGB:Object(
    73             pil = 'RGB',
    74             type = gl_byte,
    75             channels = rgb,
    76         ),
    77         GL_RGBA:Object(
    78             pil = 'RGBA',
    79             type = gl_byte,
    80             channels = rgba,
    81         ),
    82         GL_RGB16:Object(
    83             type = gl_short,
    84             channels = rgb,
    85         ),
    86         GL_RGBA32F:Object(
    87             pil = 'RGBA',
    88             type = gl_float,
    89             channels = rgba,
    90         ),
    91         GL_RGB16F:Object(
    92             type = gl_half_float,
    93             channels = rgb,
    94         ),
    95         GL_RGB32F:Object(
    96             pil = 'RGB',
    97             type = gl_float,
    98             channels = rgb,
    99         ),
   100         GL_LUMINANCE32F:Object(
   101             pil = 'L',
   102             type = gl_float,
   103             channels = luminance,
   104         ),
   105         GL_LUMINANCE:Object(
   106             type = gl_byte,
   107             channels = luminance,
   108         ),
   109         GL_ALPHA:Object(
   110             type = gl_byte,
   111             channels = alpha,
   112         ),
   113         GL_DEPTH_COMPONENT:Object(
   114             type = gl_float,
   115             channels = depth,
   116         ),
   117     }
   118 
   119     target = GL_TEXTURE_2D
   120     
   121     _get = GL_TEXTURE_BINDING_2D
   122 
   123     float_targets = [
   124         GL_RGBA32F,
   125         GL_RGB32F,
   126         GL_LUMINANCE32F,
   127     ]
   128 
   129     def bind(self, id):
   130         glBindTexture(self.target, id)
   131 
   132     def _enter(self):
   133         glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT)
   134         glActiveTexture(self.unit)
   135         glEnable(self.target)
   136 
   137     def _exit(self):
   138         glPopAttrib()
   139 
   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)')
   144 
   145         Context.__init__(self)
   146         self.clamp = clamp
   147         self.mipmap = mipmap
   148         self.width = width
   149         self.height = height
   150         self.format = format
   151         self.filter = filter
   152         self.unit = unit
   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()
   156         if data:
   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))
   162                 self.buffer = target
   163             else:
   164                 self.buffer = self.buffer_type(*data)
   165 
   166         else:
   167             self._buffer = None
   168 
   169         self.update()
   170         self.display = self.make_display()
   171    
   172     def get_buffer(self):
   173         if not self._buffer:
   174             self._buffer = self.buffer_type()
   175         return self._buffer
   176     def set_buffer(self, data):
   177         self._buffer = data
   178     buffer = property(get_buffer, set_buffer)
   179 
   180     def delete(self):
   181         glDeleteTextures(1, byref(self.id))
   182 
   183     @classmethod
   184     def open(cls, filename, format=GL_RGBA, filter=GL_LINEAR, unit=GL_TEXTURE0, mipmap=0):
   185         if not has_pil:
   186             raise DependencyException('PIL is requried to open image files')
   187         spec = cls.specs[format]
   188         pil_format = getattr(spec, 'pil', None)
   189         if not pil_format:
   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()
   195         
   196         if spec.type == cls.gl_float:
   197             data = map(lambda x: ord(x)/255.0, data)
   198         else:
   199             data = map(ord, data)
   200         
   201         return cls(width, height, format=format, filter=filter, unit=unit, data=data, mipmap=mipmap)
   202 
   203     @classmethod
   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) 
   207         return self
   208 
   209     def save(self, filename):
   210         self.retrieve()
   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)
   214             image.save(filename)
   215         else:
   216             raise Exception('cannot save non byte images')
   217         
   218     def make_display(self):
   219         import pyglet
   220         uvs = 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0
   221         x1 = 0.0
   222         y1 = 0.0
   223         z = 0.0
   224         x2 = self.width
   225         y2 = self.height
   226         verts = (
   227              x1,    y1,    z,
   228              x2,    y1,    z,
   229              x2,    y2,    z,
   230              x1,    y2,    z,
   231         )
   232 
   233         return pyglet.graphics.vertex_list(4,
   234             ('v3f', verts),
   235             ('t2f', uvs),
   236         )
   237     
   238     def draw(self, x=0, y=0, scale=1.0):
   239         with self:
   240             quad(
   241                 left=x, top=self.height+y, right=self.width+x, bottom=y, scale=scale
   242             )
   243 
   244     def set_data(self, data=None, clamp=False, level=0):
   245         with self:
   246             if isinstance(self.filter, tuple):
   247                 min_filter, mag_filter = self.filter
   248             else:
   249                 min_filter = self.filter
   250                 mag_filter = GL_LINEAR
   251 
   252             glTexParameteri(self.target, GL_TEXTURE_MIN_FILTER, min_filter)
   253             glTexParameteri(self.target, GL_TEXTURE_MAG_FILTER, mag_filter)
   254 
   255             if clamp:
   256                 if 's' in clamp:
   257                     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
   258                 if 't' in clamp:
   259                     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
   260            
   261             if data:
   262                 if self.mipmap:
   263                     gluBuild2DMipmaps(
   264                         self.target, self.mipmap,
   265                         self.width, self.height,
   266                         self.spec.channels.enum,
   267                         self.spec.type.enum,
   268                         data,
   269                     )
   270                 else:
   271                     glTexImage2D(
   272                         self.target, level, self.format,
   273                         self.width, self.height,
   274                         0,
   275                         self.spec.channels.enum, self.spec.type.enum,
   276                         data,
   277                     )
   278             else:
   279                 glTexImage2D(
   280                     self.target, level, self.format,
   281                     self.width/2**level, self.height/2**level,
   282                     #self.width, self.height,
   283                     #width, height,
   284                     0,
   285                     self.spec.channels.enum, self.spec.type.enum,
   286                     0,
   287                 )
   288 
   289             glFlush()
   290 
   291     def get_data(self, buffer):
   292         with self:
   293             glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT)
   294             glGetTexImage(
   295                 self.target, 0, self.spec.channels.enum, self.spec.type.enum,
   296                 buffer,
   297             )
   298             glPopClientAttrib()
   299             glFinish()
   300 
   301     def update(self):
   302         if self._buffer:
   303             self.set_data(self.buffer, self.clamp)
   304         else:
   305             self.set_data(clamp=self.clamp)
   306 
   307     def retrieve(self):
   308         self.get_data(self.buffer)
   309         glFinish()
   310 
   311     def __getitem__(self, (x, y)):
   312         x, y = x%self.width, y%self.height
   313 
   314         channels = self.spec.channels.count
   315         pos = (x + y * self.width) * channels
   316         if channels == 1:
   317             return self.buffer[pos]
   318         else:
   319             end = pos + channels
   320             return self.buffer[pos:end]
   321 
   322     def __setitem__(self, (x, y), value):
   323         x, y = x%self.width, y%self.height
   324 
   325         channels = self.spec.channels.count
   326         pos = (x + y * self.width) * channels
   327         if channels == 1:
   328             self.buffer[pos] = value
   329         else:
   330             end = pos + channels
   331             self.buffer[pos:end] = value
   332 
   333     def __iter__(self):
   334         channels = self.spec.channels.count
   335         if channels == 1:
   336             for value in self.buffer:
   337                 yield value
   338         else:
   339             for i in range(0, len(self.buffer), channels):
   340                 yield self.buffer[i:i+channels]
   341 
   342 class CubeMap(Context):
   343     '''
   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
   346     '''
   347 
   348     _get = GL_TEXTURE_BINDING_CUBE_MAP 
   349     target = GL_TEXTURE_CUBE_MAP
   350     unit = GL_TEXTURE0
   351 
   352     def __init__(self, width, height, data):
   353         Context.__init__(self)
   354         id = self.id = gen_texture()
   355         self.width = width
   356         self.height = height
   357 
   358         with self:
   359             glTexParameteri(self.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
   360             glTexParameteri(self.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
   361 
   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
   369 
   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)
   376     
   377     def set_data(self, target, data, index):
   378         glTexImage2D(
   379             target, 0, GL_RGBA,
   380             self.height, self.height,
   381             0,
   382             GL_RGBA, GL_UNSIGNED_BYTE,
   383             self.face_data(data, index),
   384         )
   385     
   386     def face_data(self, data, index):
   387         result = ''
   388         face_pitch = self.height*4
   389         full_pitch = self.width*4
   390 
   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]
   396 
   397         return result
   398     
   399     def bind(self, id):
   400         glBindTexture(self.target, id)
   401 
   402     def _enter(self):
   403         glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT)
   404         glActiveTexture(self.unit)
   405         glEnable(self.target)
   406 
   407     def _exit(self):
   408         glPopAttrib()
   409 
   410     @classmethod
   411     def open(cls, filename):
   412         if not has_pil:
   413             raise DependencyException('PIL is requried to open image files')
   414 
   415         image = Image.open(filename)
   416         image = image.convert('RGBA')
   417         width, height = image.size
   418         data = image.tostring()
   419         
   420         return cls(width, height, data)
   421 
   422 class Texture1D(Context):
   423     target = GL_TEXTURE_1D
   424     _get = GL_TEXTURE_BINDING_1D
   425 
   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)
   429         self.unit = unit
   430 
   431         self.id = gen_texture()
   432         self.bind(self.id)
   433         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
   434         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
   435         glTexImage1D(
   436             GL_TEXTURE_1D, 0, internal_format,
   437             len(data), 0,
   438             format, type,
   439             data,
   440         )
   441         self.bind(0)
   442 
   443     def _enter(self):
   444         glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT)
   445         glActiveTexture(self.unit)
   446         glEnable(self.target)
   447 
   448     def bind(self, id):
   449         glBindTexture(GL_TEXTURE_1D, id)
   450 
   451     def _exit(self):
   452         glPopAttrib()
   453 
   454 class ArrayTexture(Context):
   455     target = GL_TEXTURE_2D_ARRAY
   456     _get = GL_TEXTURE_BINDING_2D_ARRAY
   457 
   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)
   460         self.unit = unit
   461        
   462         self.id = gen_texture()
   463         self.bind(self.id)
   464       
   465         if mipmaps > 0:
   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)
   469         else:
   470             glTexParameteri(self.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
   471         glTexParameteri(self.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
   472 
   473         pointer = cast(c_char_p(data), c_void_p)
   474         glTexImage3D(
   475             self.target, 0, internal_format,
   476             width, height, slice_count, 0,
   477             format, type, pointer,
   478         )
   479 
   480         if mipmaps > 0:
   481             glGenerateMipmap(self.target)
   482 
   483         self.bind(0)
   484 
   485     @classmethod
   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)
   490 
   491         return cls(base_level, width, height, slice_count, format, type, internal_format, unit, mipmaps)
   492 
   493     def _enter(self):
   494         glPushAttrib(GL_TEXTURE_BIT)
   495         glActiveTexture(self.unit)
   496     
   497     def _exit(self):
   498         glPopAttrib()
   499     
   500     def bind(self, id):
   501         glBindTexture(self.target, id)