/usr/share/pyshared/pymt/texture.py is in python-pymt 0.5.1-0ubuntu3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 | '''
Texture: abstraction to handle GL texture, and region
'''
__all__ = ('Texture', 'TextureRegion')
import os
import re
from array import array
from pymt import pymt_logger
import OpenGL
from OpenGL.GL import GL_RGBA, GL_UNSIGNED_BYTE, GL_TEXTURE_MIN_FILTER, \
GL_TEXTURE_MAG_FILTER, GL_TEXTURE_WRAP_T, GL_TEXTURE_WRAP_S, \
GL_TEXTURE_2D, GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_RECTANGLE_ARB, \
GL_CLAMP_TO_EDGE, GL_LINEAR_MIPMAP_LINEAR, GL_GENERATE_MIPMAP, \
GL_TRUE, GL_LINEAR, GL_UNPACK_ALIGNMENT, GL_BGR, GL_BGRA, GL_RGB, \
glEnable, glDisable, glBindTexture, glTexParameteri, glTexImage2D, \
glTexSubImage2D, glFlush, glGenTextures, glDeleteTextures, \
GLubyte, glPixelStorei
from OpenGL.GL.NV.texture_rectangle import glInitTextureRectangleNV
from OpenGL.GL.ARB.texture_rectangle import glInitTextureRectangleARB
from OpenGL.extensions import hasGLExtension
# for a specific bug in 3.0.0, about deletion of framebuffer.
# same hack as FBO :(
OpenGLversion = tuple(int(re.match('^(\d+)', i).groups()[0]) \
for i in OpenGL.__version__.split('.'))
if OpenGLversion < (3, 0, 1):
try:
import numpy
have_numpy = True
except Exception:
have_numpy = False
def _nearest_pow2(v):
# From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
# Credit: Sean Anderson
v -= 1
v |= v >> 1
v |= v >> 2
v |= v >> 4
v |= v >> 8
v |= v >> 16
return v + 1
def _is_pow2(v):
# http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
return (v & (v - 1)) == 0
#
# Releasing texture through GC is problematic
# GC can happen in a middle of glBegin/glEnd
# So, to prevent that, call the _texture_release
# at flip time.
_texture_release_list = []
def _texture_release(*largs):
global _texture_release_list
for texture_id in _texture_release_list:
# try/except are here to prevent an error like this :
# Exception TypeError: "'NoneType' object is not callable"
# in <bound method Texture.__del__ of <pymt.texture.Texture
# object at 0x3a1acb0>> ignored
#
# It occured only when leaving the application.
# So, maybe numpy or pyopengl is unloaded, and have weird things happen.
#
try:
if OpenGLversion < (3, 0, 1) and have_numpy:
glDeleteTextures(numpy.array(texture_id))
else:
glDeleteTextures(texture_id)
except:
pass
# let the list to 0
_texture_release_list = []
class Texture(object):
'''Handle a OpenGL texture. This class can be used to create simple texture
or complex texture based on ImageData.'''
__slots__ = ('tex_coords', '_width', '_height', '_target', '_id', '_mipmap',
'_gl_wrap', '_gl_min_filter', '_gl_mag_filter', '_rectangle')
_has_bgr = None
_has_bgr_tested = False
_has_texture_nv = None
_has_texture_arb = None
def __init__(self, width, height, target, texid, mipmap=False, rectangle=False):
self.tex_coords = (0., 0., 1., 0., 1., 1., 0., 1.)
self._width = width
self._height = height
self._target = target
self._id = texid
self._mipmap = mipmap
self._gl_wrap = None
self._gl_min_filter = None
self._gl_mag_filter = None
self._rectangle = rectangle
def __del__(self):
# Add texture deletion outside GC call.
# This case happen if some texture have been not deleted
# before application exit...
if _texture_release_list is not None:
_texture_release_list.append(self.id)
@property
def mipmap(self):
'''Return True if the texture have mipmap enabled (readonly)'''
return self._mipmap
@property
def rectangle(self):
'''Return True if the texture is a rectangle texture (readonly)'''
return self._rectangle
@property
def id(self):
'''Return the OpenGL ID of the texture (readonly)'''
return self._id
@property
def target(self):
'''Return the OpenGL target of the texture (readonly)'''
return self._target
@property
def width(self):
'''Return the width of the texture (readonly)'''
return self._width
@property
def height(self):
'''Return the height of the texture (readonly)'''
return self._height
def flip_vertical(self):
'''Flip tex_coords for vertical displaying'''
a, b, c, d, e, f, g, h = self.tex_coords
self.tex_coords = (g, h, e, f, c, d, a, b)
def get_region(self, x, y, width, height):
'''Return a part of the texture, from (x,y) with (width,height)
dimensions'''
return TextureRegion(x, y, width, height, self)
def bind(self):
'''Bind the texture to current opengl state'''
glBindTexture(self.target, self.id)
def enable(self):
'''Do the appropriate glEnable()'''
glEnable(self.target)
def disable(self):
'''Do the appropriate glDisable()'''
glDisable(self.target)
def _get_min_filter(self):
return self._gl_min_filter
def _set_min_filter(self, x):
if x == self._gl_min_filter:
return
self.bind()
glTexParameteri(self.target, GL_TEXTURE_MIN_FILTER, x)
self._gl_min_filter = x
min_filter = property(_get_min_filter, _set_min_filter,
doc='''Get/set the GL_TEXTURE_MIN_FILTER property''')
def _get_mag_filter(self):
return self._gl_mag_filter
def _set_mag_filter(self, x):
if x == self._gl_mag_filter:
return
self.bind()
glTexParameteri(self.target, GL_TEXTURE_MAG_FILTER, x)
self._gl_mag_filter = x
mag_filter = property(_get_mag_filter, _set_mag_filter,
doc='''Get/set the GL_TEXTURE_MAG_FILTER property''')
def _get_wrap(self):
return self._gl_wrap
def _set_wrap(self, wrap):
if wrap == self._gl_wrap:
return
self.bind()
glTexParameteri(self.target, GL_TEXTURE_WRAP_S, wrap)
glTexParameteri(self.target, GL_TEXTURE_WRAP_T, wrap)
wrap = property(_get_wrap, _set_wrap,
doc='''Get/set the GL_TEXTURE_WRAP_S,T property''')
@staticmethod
def create(width, height, format=GL_RGBA, rectangle=False, mipmap=False):
'''Create a texture based on size.'''
target = GL_TEXTURE_2D
if rectangle:
if _is_pow2(width) and _is_pow2(height):
rectangle = False
else:
rectangle = False
try:
if Texture._has_texture_nv is None:
Texture._has_texture_nv = glInitTextureRectangleNV()
if Texture._has_texture_nv:
target = GL_TEXTURE_RECTANGLE_NV
rectangle = True
except Exception:
pass
try:
if Texture._has_texture_arb is None:
Texture._has_texture_arb = glInitTextureRectangleARB()
if not rectangle and Texture._has_texture_arb:
target = GL_TEXTURE_RECTANGLE_ARB
rectangle = True
except Exception:
pass
if not rectangle:
pymt_logger.debug(
'Texture: Missing support for rectangular texture')
else:
# Can't do mipmap with rectangle texture
mipmap = False
if rectangle:
texture_width = width
texture_height = height
else:
texture_width = _nearest_pow2(width)
texture_height = _nearest_pow2(height)
texid = glGenTextures(1)
texture = Texture(texture_width, texture_height, target, texid,
mipmap=mipmap)
texture.bind()
texture.wrap = GL_CLAMP_TO_EDGE
if mipmap:
texture.min_filter = GL_LINEAR_MIPMAP_LINEAR
#texture.mag_filter = GL_LINEAR_MIPMAP_LINEAR
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE)
else:
texture.min_filter = GL_LINEAR
texture.mag_filter = GL_LINEAR
if not Texture.is_gl_format_supported(format):
format = Texture.convert_gl_format(format)
data = (GLubyte * texture_width * texture_height *
Texture.gl_format_size(format))()
glTexImage2D(target, 0, format, texture_width, texture_height, 0,
format, GL_UNSIGNED_BYTE, data)
if rectangle:
texture.tex_coords = \
(0., 0., width, 0., width, height, 0., height)
glFlush()
if texture_width == width and texture_height == height:
return texture
return texture.get_region(0, 0, width, height)
@staticmethod
def create_from_data(im, rectangle=True, mipmap=False):
'''Create a texture from an ImageData class'''
format = Texture.mode_to_gl_format(im.mode)
texture = Texture.create(im.width, im.height,
format, rectangle=rectangle,
mipmap=mipmap)
if texture is None:
return None
texture.blit_data(im)
return texture
def blit_data(self, im, pos=(0, 0)):
'''Replace a whole texture with a image data'''
self.blit_buffer(im.data, size=(im.width, im.height),
mode=im.mode, pos=pos)
def blit_buffer(self, buffer, size=None, mode='RGB', format=None,
pos=(0, 0), buffertype=GL_UNSIGNED_BYTE):
'''Blit a buffer into a texture.
:Parameters:
`buffer` : str
Image data
`size` : tuple, default to texture size
Size of the image (width, height)
`mode` : str, default to 'RGB'
Image mode, can be one of RGB, RGBA, BGR, BGRA
`format` : glconst, default to None
if format is passed, it will be used instead of mode
`pos` : tuple, default to (0, 0)
Position to blit in the texture
`buffertype` : glglconst, default to GL_UNSIGNED_BYTE
Type of the data buffer
'''
if size is None:
size = self.size
if format is None:
format = self.mode_to_gl_format(mode)
target = self.target
glBindTexture(target, self.id)
glEnable(target)
# activate 1 alignement, of window failed on updating weird size
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
# need conversion ?
pdata, format = self._convert_buffer(buffer, format)
# transfer the new part of texture
glTexSubImage2D(target, 0, pos[0], pos[1],
size[0], size[1], format,
buffertype, pdata)
glFlush()
glDisable(target)
@staticmethod
def has_bgr():
if not Texture._has_bgr_tested:
pymt_logger.warning('Texture: BGR/BGRA format is not supported by'
'your graphic card')
pymt_logger.warning('Texture: Software conversion will be done to'
'RGB/RGBA')
Texture._has_bgr = hasGLExtension('GL_EXT_bgra')
Texture._has_bgr_tested = True
return Texture._has_bgr
@staticmethod
def is_gl_format_supported(format):
if format in (GL_BGR, GL_BGRA):
return not Texture.has_bgr()
return True
@staticmethod
def convert_gl_format(format):
if format == GL_BGR:
return GL_RGB
elif format == GL_BGRA:
return GL_RGBA
return format
def _convert_buffer(self, data, format):
# check if format is supported by user
ret_format = format
ret_buffer = data
# BGR / BGRA conversion not supported by hardware ?
if not Texture.is_gl_format_supported(format):
if format == GL_BGR:
ret_format = GL_RGB
a = array('b', data)
a[0::3], a[2::3] = a[2::3], a[0::3]
ret_buffer = a.tostring()
elif format == GL_BGRA:
ret_format = GL_RGBA
a = array('b', data)
a[0::4], a[2::4] = a[2::4], a[0::4]
ret_buffer = a.tostring()
else:
pymt_logger.critical('Texture: non implemented'
'%s texture conversion' % str(format))
raise Exception('Unimplemented texture conversion for %s' %
str(format))
return ret_buffer, ret_format
@property
def size(self):
return (self.width, self.height)
@staticmethod
def mode_to_gl_format(format):
if format == 'RGBA':
return GL_RGBA
elif format == 'BGRA':
return GL_BGRA
elif format == 'BGR':
return GL_BGR
else:
return GL_RGB
@staticmethod
def gl_format_size(format):
if format in (GL_RGB, GL_BGR):
return 3
elif format in (GL_RGBA, GL_BGRA):
return 4
raise Exception('Unsupported format size <%s>' % str(format))
def __str__(self):
return '<Texture size=(%d, %d)>' % self.size
class TextureRegion(Texture):
'''Handle a region of a Texture class. Useful for non power-of-2
texture handling.'''
__slots__ = ('x', 'y', 'owner')
def __init__(self, x, y, width, height, origin):
super(TextureRegion, self).__init__(
width, height, origin.target, origin.id)
self.x = x
self.y = y
self.owner = origin
# recalculate texture coordinate
origin_u1 = origin.tex_coords[0]
origin_v1 = origin.tex_coords[1]
origin_u2 = origin.tex_coords[2]
origin_v2 = origin.tex_coords[5]
scale_u = origin_u2 - origin_u1
scale_v = origin_v2 - origin_v1
u1 = x / float(origin.width) * scale_u + origin_u1
v1 = y / float(origin.height) * scale_v + origin_v1
u2 = (x + width) / float(origin.width) * scale_u + origin_u1
v2 = (y + height) / float(origin.height) * scale_v + origin_v1
self.tex_coords = (u1, v1, u2, v1, u2, v2, u1, v2)
def __del__(self):
# don't use self of owner !
pass
if 'PYMT_DOC' not in os.environ:
from pymt.clock import getClock
# install tick to release texture every 200ms
getClock().schedule_interval(_texture_release, 0.2)
|