summaryrefslogtreecommitdiff
path: root/silx/gui/_glutils
diff options
context:
space:
mode:
authorPicca Frédéric-Emmanuel <picca@synchrotron-soleil.fr>2017-08-18 14:48:52 +0200
committerPicca Frédéric-Emmanuel <picca@synchrotron-soleil.fr>2017-08-18 14:48:52 +0200
commitf7bdc2acff3c13a6d632c28c4569690ab106eed7 (patch)
tree9d67cdb7152ee4e711379e03fe0546c7c3b97303 /silx/gui/_glutils
Import Upstream version 0.5.0+dfsg
Diffstat (limited to 'silx/gui/_glutils')
-rw-r--r--silx/gui/_glutils/Context.py63
-rw-r--r--silx/gui/_glutils/FramebufferTexture.py164
-rw-r--r--silx/gui/_glutils/Program.py202
-rw-r--r--silx/gui/_glutils/Texture.py308
-rw-r--r--silx/gui/_glutils/VertexBuffer.py266
-rw-r--r--silx/gui/_glutils/__init__.py41
-rw-r--r--silx/gui/_glutils/font.py152
-rw-r--r--silx/gui/_glutils/gl.py165
-rw-r--r--silx/gui/_glutils/utils.py70
9 files changed, 1431 insertions, 0 deletions
diff --git a/silx/gui/_glutils/Context.py b/silx/gui/_glutils/Context.py
new file mode 100644
index 0000000..7600992
--- /dev/null
+++ b/silx/gui/_glutils/Context.py
@@ -0,0 +1,63 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2014-2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Abstraction of OpenGL context.
+
+It defines a way to get current OpenGL context to support multiple
+OpenGL contexts.
+"""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "25/07/2016"
+
+
+# context #####################################################################
+
+
+def _defaultGLContextGetter():
+ return None
+
+_glContextGetter = _defaultGLContextGetter
+
+
+def getGLContext():
+ """Returns platform dependent object of current OpenGL context.
+
+ This is useful to associate OpenGL resources with the context they are
+ created in.
+
+ :return: Platform specific OpenGL context
+ :rtype: None by default or a platform dependent object"""
+ return _glContextGetter()
+
+
+def setGLContextGetter(getter=_defaultGLContextGetter):
+ """Set a platform dependent function to retrieve the current OpenGL context
+
+ :param getter: Platform dependent GL context getter
+ :type getter: Function with no args returning the current OpenGL context
+ """
+ global _glContextGetter
+ _glContextGetter = getter
diff --git a/silx/gui/_glutils/FramebufferTexture.py b/silx/gui/_glutils/FramebufferTexture.py
new file mode 100644
index 0000000..b01eb41
--- /dev/null
+++ b/silx/gui/_glutils/FramebufferTexture.py
@@ -0,0 +1,164 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2014-2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Association of a texture and a framebuffer object for off-screen rendering.
+"""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "25/07/2016"
+
+
+import logging
+
+from . import gl
+from .Texture import Texture
+
+
+_logger = logging.getLogger(__name__)
+
+
+class FramebufferTexture(object):
+ """Framebuffer with a texture.
+
+ Aimed at off-screen rendering to texture.
+
+ :param internalFormat: OpenGL texture internal format
+ :param shape: Shape (height, width) of the framebuffer and texture
+ :type shape: 2-tuple of int
+ :param stencilFormat: Stencil renderbuffer format
+ :param depthFormat: Depth renderbuffer format
+ :param kwargs: Extra arguments for :class:`Texture` constructor
+ """
+
+ _PACKED_FORMAT = gl.GL_DEPTH24_STENCIL8, gl.GL_DEPTH_STENCIL
+
+ def __init__(self,
+ internalFormat,
+ shape,
+ stencilFormat=gl.GL_DEPTH24_STENCIL8,
+ depthFormat=gl.GL_DEPTH24_STENCIL8,
+ **kwargs):
+
+ self._texture = Texture(internalFormat, shape=shape, **kwargs)
+
+ self._previousFramebuffer = 0 # Used by with statement
+
+ self._name = gl.glGenFramebuffers(1)
+ gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self._name)
+
+ # Attachments
+ gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER,
+ gl.GL_COLOR_ATTACHMENT0,
+ gl.GL_TEXTURE_2D,
+ self._texture.name,
+ 0)
+
+ height, width = self._texture.shape
+
+ if stencilFormat is not None:
+ self._stencilId = gl.glGenRenderbuffers(1)
+ gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, self._stencilId)
+ gl.glRenderbufferStorage(gl.GL_RENDERBUFFER,
+ stencilFormat,
+ width, height)
+ gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER,
+ gl.GL_STENCIL_ATTACHMENT,
+ gl.GL_RENDERBUFFER,
+ self._stencilId)
+ else:
+ self._stencilId = None
+
+ if depthFormat is not None:
+ if self._stencilId and depthFormat in self._PACKED_FORMAT:
+ self._depthId = self._stencilId
+ else:
+ self._depthId = gl.glGenRenderbuffers(1)
+ gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, self._depthId)
+ gl.glRenderbufferStorage(gl.GL_RENDERBUFFER,
+ depthFormat,
+ width, height)
+ gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER,
+ gl.GL_DEPTH_ATTACHMENT,
+ gl.GL_RENDERBUFFER,
+ self._depthId)
+ else:
+ self._depthId = None
+
+ assert gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) == \
+ gl.GL_FRAMEBUFFER_COMPLETE
+ gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
+
+ @property
+ def shape(self):
+ """Shape of the framebuffer (height, width)"""
+ return self._texture.shape
+
+ @property
+ def texture(self):
+ """The texture this framebuffer is rendering to.
+
+ The life-cycle of the texture is managed by this object"""
+ return self._texture
+
+ @property
+ def name(self):
+ """OpenGL name of the framebuffer"""
+ if self._name is not None:
+ return self._name
+ else:
+ raise RuntimeError("No OpenGL framebuffer resource, \
+ discard has already been called")
+
+ def bind(self):
+ """Bind this framebuffer for rendering"""
+ gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.name)
+
+ # with statement
+
+ def __enter__(self):
+ self._previousFramebuffer = gl.glGetInteger(gl.GL_FRAMEBUFFER_BINDING)
+ self.bind()
+
+ def __exit__(self, exctype, excvalue, traceback):
+ gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self._previousFramebuffer)
+
+ def discard(self):
+ """Delete associated OpenGL resources including texture"""
+ if self._name is not None:
+ gl.glDeleteFramebuffers(self._name)
+ self._name = None
+
+ if self._stencilId is not None:
+ gl.glDeleteRenderbuffers(self._stencilId)
+ if self._stencilId == self._depthId:
+ self._depthId = None
+ self._stencilId = None
+ if self._depthId is not None:
+ gl.glDeleteRenderbuffers(self._depthId)
+ self._depthId = None
+
+ self._texture.discard() # Also discard the texture
+ else:
+ _logger.warning("Discard has already been called")
diff --git a/silx/gui/_glutils/Program.py b/silx/gui/_glutils/Program.py
new file mode 100644
index 0000000..48c12f5
--- /dev/null
+++ b/silx/gui/_glutils/Program.py
@@ -0,0 +1,202 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2014-2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""This module provides a class to handle shader program compilation."""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "25/07/2016"
+
+
+import logging
+
+import numpy
+
+from . import gl
+from .Context import getGLContext
+
+_logger = logging.getLogger(__name__)
+
+
+class Program(object):
+ """Wrap OpenGL shader program.
+
+ The program is compiled lazily (i.e., at first program :meth:`use`).
+ When the program is compiled, it stores attributes and uniforms locations.
+ So, attributes and uniforms must be used after :meth:`use`.
+
+ This object supports multiple OpenGL contexts.
+
+ :param str vertexShader: The source of the vertex shader.
+ :param str fragmentShader: The source of the fragment shader.
+ :param str attrib0:
+ Attribute's name to bind to position 0 (default: 'position').
+ On certain platform, this attribute MUST be active and with an
+ array attached to it in order for the rendering to occur....
+ """
+
+ def __init__(self, vertexShader, fragmentShader,
+ attrib0='position'):
+ self._vertexShader = vertexShader
+ self._fragmentShader = fragmentShader
+ self._attrib0 = attrib0
+ self._programs = {}
+
+ @staticmethod
+ def _compileGL(vertexShader, fragmentShader, attrib0):
+ program = gl.glCreateProgram()
+
+ gl.glBindAttribLocation(program, 0, attrib0.encode('ascii'))
+
+ vertex = gl.glCreateShader(gl.GL_VERTEX_SHADER)
+ gl.glShaderSource(vertex, vertexShader)
+ gl.glCompileShader(vertex)
+ if gl.glGetShaderiv(vertex, gl.GL_COMPILE_STATUS) != gl.GL_TRUE:
+ raise RuntimeError(gl.glGetShaderInfoLog(vertex))
+ gl.glAttachShader(program, vertex)
+ gl.glDeleteShader(vertex)
+
+ fragment = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
+ gl.glShaderSource(fragment, fragmentShader)
+ gl.glCompileShader(fragment)
+ if gl.glGetShaderiv(fragment,
+ gl.GL_COMPILE_STATUS) != gl.GL_TRUE:
+ raise RuntimeError(gl.glGetShaderInfoLog(fragment))
+ gl.glAttachShader(program, fragment)
+ gl.glDeleteShader(fragment)
+
+ gl.glLinkProgram(program)
+ if gl.glGetProgramiv(program, gl.GL_LINK_STATUS) != gl.GL_TRUE:
+ raise RuntimeError(gl.glGetProgramInfoLog(program))
+
+ attributes = {}
+ for index in range(gl.glGetProgramiv(program,
+ gl.GL_ACTIVE_ATTRIBUTES)):
+ name = gl.glGetActiveAttrib(program, index)[0]
+ namestr = name.decode('ascii')
+ attributes[namestr] = gl.glGetAttribLocation(program, name)
+
+ uniforms = {}
+ for index in range(gl.glGetProgramiv(program, gl.GL_ACTIVE_UNIFORMS)):
+ name = gl.glGetActiveUniform(program, index)[0]
+ namestr = name.decode('ascii')
+ uniforms[namestr] = gl.glGetUniformLocation(program, name)
+
+ return program, attributes, uniforms
+
+ def _getProgramInfo(self):
+ glcontext = getGLContext()
+ if glcontext not in self._programs:
+ raise RuntimeError(
+ "Program was not compiled for current OpenGL context.")
+ return self._programs[glcontext]
+
+ @property
+ def attributes(self):
+ """Vertex attributes names and locations as a dict of {str: int}.
+
+ WARNING:
+ Read-only usage.
+ To use only with a valid OpenGL context and after :meth:`use`
+ has been called for this context.
+ """
+ return self._getProgramInfo()[1]
+
+ @property
+ def uniforms(self):
+ """Program uniforms names and locations as a dict of {str: int}.
+
+ WARNING:
+ Read-only usage.
+ To use only with a valid OpenGL context and after :meth:`use`
+ has been called for this context.
+ """
+ return self._getProgramInfo()[2]
+
+ @property
+ def program(self):
+ """OpenGL id of the program.
+
+ WARNING:
+ To use only with a valid OpenGL context and after :meth:`use`
+ has been called for this context.
+ """
+ return self._getProgramInfo()[0]
+
+ # def discard(self):
+ # pass # Not implemented yet
+
+ def use(self):
+ """Make use of the program, compiling it if necessary"""
+ glcontext = getGLContext()
+
+ if glcontext not in self._programs:
+ self._programs[glcontext] = self._compileGL(
+ self._vertexShader,
+ self._fragmentShader,
+ self._attrib0)
+
+ if _logger.getEffectiveLevel() <= logging.DEBUG:
+ gl.glValidateProgram(self.program)
+ if gl.glGetProgramiv(
+ self.program, gl.GL_VALIDATE_STATUS) != gl.GL_TRUE:
+ _logger.debug('Cannot validate program: %s',
+ gl.glGetProgramInfoLog(self.program))
+
+ gl.glUseProgram(self.program)
+
+ def setUniformMatrix(self, name, value, transpose=True, safe=False):
+ """Wrap glUniformMatrix[2|3|4]fv
+
+ :param str name: The name of the uniform.
+ :param value: The 2D matrix (or the array of matrices, 3D).
+ Matrices are 2x2, 3x3 or 4x4.
+ :type value: numpy.ndarray with 2 or 3 dimensions of float32
+ :param bool transpose: Whether to transpose (True, default) or not.
+ :param bool safe: False: raise an error if no uniform with this name;
+ True: silently ignores it.
+
+ :raises KeyError: if no uniform corresponds to name.
+ """
+ assert value.dtype == numpy.float32
+
+ shape = value.shape
+ assert len(shape) in (2, 3)
+ assert shape[-1] in (2, 3, 4)
+ assert shape[-1] == shape[-2] # As in OpenGL|ES 2.0
+
+ location = self.uniforms.get(name)
+ if location is not None:
+ count = 1 if len(shape) == 2 else shape[0]
+ transpose = gl.GL_TRUE if transpose else gl.GL_FALSE
+
+ if shape[-1] == 2:
+ gl.glUniformMatrix2fv(location, count, transpose, value)
+ elif shape[-1] == 3:
+ gl.glUniformMatrix3fv(location, count, transpose, value)
+ elif shape[-1] == 4:
+ gl.glUniformMatrix4fv(location, count, transpose, value)
+
+ elif not safe:
+ raise KeyError('No uniform: %s' % name)
diff --git a/silx/gui/_glutils/Texture.py b/silx/gui/_glutils/Texture.py
new file mode 100644
index 0000000..9f09a86
--- /dev/null
+++ b/silx/gui/_glutils/Texture.py
@@ -0,0 +1,308 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2014-2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""This module provides a class wrapping OpenGL 2D and 3D texture."""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "04/10/2016"
+
+
+import collections
+from ctypes import c_void_p
+import logging
+
+import numpy
+
+from . import gl, utils
+
+
+_logger = logging.getLogger(__name__)
+
+
+class Texture(object):
+ """Base class to wrap OpenGL 2D and 3D texture
+
+ :param internalFormat: OpenGL texture internal format
+ :param data: The data to copy to the texture or None for an empty texture
+ :type data: numpy.ndarray or None
+ :param format_: Input data format if different from internalFormat
+ :param shape: If data is None, shape of the texture
+ :type shape: 2 or 3-tuple of int (height, width) or (depth, height, width)
+ :param int texUnit: The texture unit to use
+ :param minFilter: OpenGL texture minimization filter (default: GL_NEAREST)
+ :param magFilter: OpenGL texture magnification filter (default: GL_LINEAR)
+ :param wrap: Texture wrap mode for dimensions: (t, s) or (r, t, s)
+ If a single value is provided, it used for all dimensions.
+ :type wrap: OpenGL wrap mode or 2 or 3-tuple of wrap mode
+ """
+
+ def __init__(self, internalFormat, data=None, format_=None,
+ shape=None, texUnit=0,
+ minFilter=None, magFilter=None, wrap=None):
+
+ self._internalFormat = internalFormat
+ if format_ is None:
+ format_ = self.internalFormat
+
+ if data is None:
+ assert shape is not None
+ else:
+ assert shape is None
+ data = numpy.array(data, copy=False, order='C')
+ if format_ != gl.GL_RED:
+ shape = data.shape[:-1] # Last dimension is channels
+ else:
+ shape = data.shape
+
+ assert len(shape) in (2, 3)
+ self._shape = tuple(shape)
+ self._ndim = len(shape)
+
+ self.texUnit = texUnit
+
+ self._name = gl.glGenTextures(1)
+ self.bind(self.texUnit)
+
+ self._minFilter = None
+ self.minFilter = minFilter if minFilter is not None else gl.GL_NEAREST
+
+ self._magFilter = None
+ self.magFilter = magFilter if magFilter is not None else gl.GL_LINEAR
+
+ if wrap is not None:
+ if not isinstance(wrap, collections.Iterable):
+ wrap = [wrap] * self.ndim
+
+ assert len(wrap) == self.ndim
+
+ gl.glTexParameter(self.target,
+ gl.GL_TEXTURE_WRAP_S,
+ wrap[-1])
+ gl.glTexParameter(self.target,
+ gl.GL_TEXTURE_WRAP_T,
+ wrap[-2])
+ if self.ndim == 3:
+ gl.glTexParameter(self.target,
+ gl.GL_TEXTURE_WRAP_R,
+ wrap[0])
+
+ gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)
+
+ # This are the defaults, useless to set if not modified
+ # gl.glPixelStorei(gl.GL_UNPACK_ROW_LENGTH, 0)
+ # gl.glPixelStorei(gl.GL_UNPACK_SKIP_PIXELS, 0)
+ # gl.glPixelStorei(gl.GL_UNPACK_SKIP_ROWS, 0)
+ # gl.glPixelStorei(gl.GL_UNPACK_IMAGE_HEIGHT, 0)
+ # gl.glPixelStorei(gl.GL_UNPACK_SKIP_IMAGES, 0)
+
+ if data is None:
+ data = c_void_p(0)
+ type_ = gl.GL_UNSIGNED_BYTE
+ else:
+ type_ = utils.numpyToGLType(data.dtype)
+
+ if self.ndim == 2:
+ _logger.debug(
+ 'Creating 2D texture shape: (%d, %d),'
+ ' internal format: %s, format: %s, type: %s',
+ self.shape[0], self.shape[1],
+ str(self.internalFormat), str(format_), str(type_))
+
+ gl.glTexImage2D(
+ gl.GL_TEXTURE_2D,
+ 0,
+ self.internalFormat,
+ self.shape[1],
+ self.shape[0],
+ 0,
+ format_,
+ type_,
+ data)
+ else:
+ _logger.debug(
+ 'Creating 3D texture shape: (%d, %d, %d),'
+ ' internal format: %s, format: %s, type: %s',
+ self.shape[0], self.shape[1], self.shape[2],
+ str(self.internalFormat), str(format_), str(type_))
+
+ gl.glTexImage3D(
+ gl.GL_TEXTURE_3D,
+ 0,
+ self.internalFormat,
+ self.shape[2],
+ self.shape[1],
+ self.shape[0],
+ 0,
+ format_,
+ type_,
+ data)
+
+ gl.glBindTexture(self.target, 0)
+
+ @property
+ def target(self):
+ """OpenGL target type of this texture"""
+ return gl.GL_TEXTURE_2D if self.ndim == 2 else gl.GL_TEXTURE_3D
+
+ @property
+ def ndim(self):
+ """The number of dimensions: 2 or 3"""
+ return self._ndim
+
+ @property
+ def internalFormat(self):
+ """Texture internal format"""
+ return self._internalFormat
+
+ @property
+ def shape(self):
+ """Shape of the texture: (height, width) or (depth, height, width)"""
+ return self._shape
+
+ @property
+ def name(self):
+ """OpenGL texture name"""
+ if self._name is not None:
+ return self._name
+ else:
+ raise RuntimeError(
+ "No OpenGL texture resource, discard has already been called")
+
+ @property
+ def minFilter(self):
+ """Minifying function parameter (GL_TEXTURE_MIN_FILTER)"""
+ return self._minFilter
+
+ @minFilter.setter
+ def minFilter(self, minFilter):
+ if minFilter != self.minFilter:
+ self._minFilter = minFilter
+ self.bind()
+ gl.glTexParameter(self.target,
+ gl.GL_TEXTURE_MIN_FILTER,
+ self.minFilter)
+
+ @property
+ def magFilter(self):
+ """Magnification function parameter (GL_TEXTURE_MAG_FILTER)"""
+ return self._magFilter
+
+ @magFilter.setter
+ def magFilter(self, magFilter):
+ if magFilter != self.magFilter:
+ self._magFilter = magFilter
+ self.bind()
+ gl.glTexParameter(self.target,
+ gl.GL_TEXTURE_MAG_FILTER,
+ self.magFilter)
+
+ def discard(self):
+ """Delete associated OpenGL texture"""
+ if self._name is not None:
+ gl.glDeleteTextures(self._name)
+ self._name = None
+ else:
+ _logger.warning("Discard as already been called")
+
+ def bind(self, texUnit=None):
+ """Bind the texture to a texture unit.
+
+ :param int texUnit: The texture unit to use
+ """
+ if texUnit is None:
+ texUnit = self.texUnit
+ gl.glActiveTexture(gl.GL_TEXTURE0 + texUnit)
+ gl.glBindTexture(self.target, self.name)
+
+ # with statement
+
+ def __enter__(self):
+ self.bind()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ gl.glActiveTexture(gl.GL_TEXTURE0 + self.texUnit)
+ gl.glBindTexture(self.target, 0)
+
+ def update(self,
+ format_,
+ data,
+ offset=(0, 0, 0),
+ texUnit=None):
+ """Update the content of the texture.
+
+ Texture is not resized, so data must fit into texture with the
+ given offset.
+
+ :param format_: The OpenGL format of the data
+ :param data: The data to use to update the texture
+ :param offset: The offset in the texture where to copy the data
+ :type offset: 2 or 3-tuple of int
+ :param int texUnit:
+ The texture unit to use (default: the one provided at init)
+ """
+ data = numpy.array(data, copy=False, order='C')
+
+ assert data.ndim == self.ndim
+ assert len(offset) >= self.ndim
+ for i in range(self.ndim):
+ assert offset[i] + data.shape[i] <= self.shape[i]
+
+ gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)
+
+ # This are the defaults, useless to set if not modified
+ # gl.glPixelStorei(gl.GL_UNPACK_ROW_LENGTH, 0)
+ # gl.glPixelStorei(gl.GL_UNPACK_SKIP_PIXELS, 0)
+ # gl.glPixelStorei(gl.GL_UNPACK_SKIP_ROWS, 0)
+ # gl.glPixelStorei(gl.GL_UNPACK_IMAGE_HEIGHT, 0)
+ # gl.glPixelStorei(gl.GL_UNPACK_SKIP_IMAGES, 0)
+
+ self.bind(texUnit)
+
+ type_ = utils.numpyToGLType(data.dtype)
+
+ if self.ndim == 2:
+ gl.glTexSubImage2D(gl.GL_TEXTURE_2D,
+ 0,
+ offset[1],
+ offset[0],
+ data.shape[1],
+ data.shape[0],
+ format_,
+ type_,
+ data)
+ gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
+ else:
+ gl.glTexSubImage3D(gl.GL_TEXTURE_3D,
+ 0,
+ offset[2],
+ offset[1],
+ offset[0],
+ data.shape[2],
+ data.shape[1],
+ data.shape[0],
+ format_,
+ type_,
+ data)
+ gl.glBindTexture(gl.GL_TEXTURE_3D, 0)
diff --git a/silx/gui/_glutils/VertexBuffer.py b/silx/gui/_glutils/VertexBuffer.py
new file mode 100644
index 0000000..689b543
--- /dev/null
+++ b/silx/gui/_glutils/VertexBuffer.py
@@ -0,0 +1,266 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2014-2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""This module provides a class managing an OpenGL vertex buffer."""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "10/01/2017"
+
+
+import logging
+from ctypes import c_void_p
+import numpy
+
+from . import gl
+from .utils import numpyToGLType, sizeofGLType
+
+
+_logger = logging.getLogger(__name__)
+
+
+class VertexBuffer(object):
+ """Object handling an OpenGL vertex buffer object
+
+ :param data: Data used to fill the vertex buffer
+ :type data: numpy.ndarray or None
+ :param int size: Size in bytes of the buffer or None for data size
+ :param usage: OpenGL vertex buffer expected usage pattern:
+ GL_STREAM_DRAW, GL_STATIC_DRAW (default) or GL_DYNAMIC_DRAW
+ :param target: Target buffer:
+ GL_ARRAY_BUFFER (default) or GL_ELEMENT_ARRAY_BUFFER
+ """
+ # OpenGL|ES 2.0 subset:
+ _USAGES = gl.GL_STREAM_DRAW, gl.GL_STATIC_DRAW, gl.GL_DYNAMIC_DRAW
+ _TARGETS = gl.GL_ARRAY_BUFFER, gl.GL_ELEMENT_ARRAY_BUFFER
+
+ def __init__(self,
+ data=None,
+ size=None,
+ usage=None,
+ target=None):
+ if usage is None:
+ usage = gl.GL_STATIC_DRAW
+ assert usage in self._USAGES
+
+ if target is None:
+ target = gl.GL_ARRAY_BUFFER
+ assert target in self._TARGETS
+
+ self._target = target
+ self._usage = usage
+
+ self._name = gl.glGenBuffers(1)
+ self.bind()
+
+ if data is None:
+ assert size is not None
+ self._size = size
+ gl.glBufferData(self._target,
+ self._size,
+ c_void_p(0),
+ self._usage)
+ else:
+ data = numpy.array(data, copy=False, order='C')
+ if size is not None:
+ assert size <= data.nbytes
+
+ self._size = size or data.nbytes
+ gl.glBufferData(self._target,
+ self._size,
+ data,
+ self._usage)
+
+ gl.glBindBuffer(self._target, 0)
+
+ @property
+ def target(self):
+ """The target buffer of the vertex buffer"""
+ return self._target
+
+ @property
+ def usage(self):
+ """The expected usage of the vertex buffer"""
+ return self._usage
+
+ @property
+ def name(self):
+ """OpenGL Vertex Buffer object name (int)"""
+ if self._name is not None:
+ return self._name
+ else:
+ raise RuntimeError("No OpenGL buffer resource, \
+ discard has already been called")
+
+ @property
+ def size(self):
+ """Size in bytes of the Vertex Buffer Object (int)"""
+ if self._size is not None:
+ return self._size
+ else:
+ raise RuntimeError("No OpenGL buffer resource, \
+ discard has already been called")
+
+ def bind(self):
+ """Bind the vertex buffer"""
+ gl.glBindBuffer(self._target, self.name)
+
+ def update(self, data, offset=0, size=None):
+ """Update vertex buffer content.
+
+ :param numpy.ndarray data: The data to put in the vertex buffer
+ :param int offset: Offset in bytes in the buffer where to put the data
+ :param int size: If provided, size of data to copy
+ """
+ data = numpy.array(data, copy=False, order='C')
+ if size is None:
+ size = data.nbytes
+ assert offset + size <= self.size
+ with self:
+ gl.glBufferSubData(self._target, offset, size, data)
+
+ def discard(self):
+ """Delete the vertex buffer"""
+ if self._name is not None:
+ gl.glDeleteBuffers(self._name)
+ self._name = None
+ self._size = None
+ else:
+ _logger.warning("Discard has already been called")
+
+ # with statement
+
+ def __enter__(self):
+ self.bind()
+
+ def __exit__(self, exctype, excvalue, traceback):
+ gl.glBindBuffer(self._target, 0)
+
+
+class VertexBufferAttrib(object):
+ """Describes data stored in a vertex buffer
+
+ Convenient class to store info for glVertexAttribPointer calls
+
+ :param VertexBuffer vbo: The vertex buffer storing the data
+ :param int type_: The OpenGL type of the data
+ :param int size: The number of data elements stored in the VBO
+ :param int dimension: The number of `type_` element(s) in [1, 4]
+ :param int offset: Start offset of data in the vertex buffer
+ :param int stride: Data stride in the vertex buffer
+ """
+
+ _GL_TYPES = gl.GL_UNSIGNED_BYTE, gl.GL_FLOAT, gl.GL_INT
+
+ def __init__(self,
+ vbo,
+ type_,
+ size,
+ dimension=1,
+ offset=0,
+ stride=0,
+ normalisation=False):
+ self.vbo = vbo
+ assert type_ in self._GL_TYPES
+ self.type_ = type_
+ self.size = size
+ assert 1 <= dimension <= 4
+ self.dimension = dimension
+ self.offset = offset
+ self.stride = stride
+ self.normalisation = bool(normalisation)
+
+ @property
+ def itemsize(self):
+ """Size in bytes of a vertex buffer element (int)"""
+ return self.dimension * sizeofGLType(self.type_)
+
+ itemSize = itemsize # Backward compatibility
+
+ def setVertexAttrib(self, attribute):
+ """Call glVertexAttribPointer with objects information"""
+ normalisation = gl.GL_TRUE if self.normalisation else gl.GL_FALSE
+ with self.vbo:
+ gl.glVertexAttribPointer(attribute,
+ self.dimension,
+ self.type_,
+ normalisation,
+ self.stride,
+ c_void_p(self.offset))
+
+ def copy(self):
+ return VertexBufferAttrib(self.vbo,
+ self.type_,
+ self.size,
+ self.dimension,
+ self.offset,
+ self.stride,
+ self.normalisation)
+
+
+def vertexBuffer(arrays, prefix=None, suffix=None, usage=None):
+ """Create a single vertex buffer from multiple 1D or 2D numpy arrays.
+
+ It is possible to reserve memory before and after each array in the VBO
+
+ :param arrays: Arrays of data to store
+ :type arrays: Iterable of numpy.ndarray
+ :param prefix: If given, number of elements to reserve before each array
+ :type prefix: Iterable of int or None
+ :param suffix: If given, number of elements to reserve after each array
+ :type suffix: Iterable of int or None
+ :param int usage: vertex buffer expected usage or None for default
+ :returns: List of VertexBufferAttrib objects sharing the same vertex buffer
+ """
+ info = []
+ vbosize = 0
+
+ if prefix is None:
+ prefix = (0,) * len(arrays)
+ if suffix is None:
+ suffix = (0,) * len(arrays)
+
+ for data, pre, post in zip(arrays, prefix, suffix):
+ data = numpy.array(data, copy=False, order='C')
+ shape = data.shape
+ assert len(shape) <= 2
+ type_ = numpyToGLType(data.dtype)
+ size = shape[0] + pre + post
+ dimension = 1 if len(shape) == 1 else shape[1]
+ sizeinbytes = size * dimension * sizeofGLType(type_)
+ sizeinbytes = 4 * ((sizeinbytes + 3) >> 2) # 4 bytes alignment
+ copyoffset = vbosize + pre * dimension * sizeofGLType(type_)
+ info.append((data, type_, size, dimension,
+ vbosize, sizeinbytes, copyoffset))
+ vbosize += sizeinbytes
+
+ vbo = VertexBuffer(size=vbosize, usage=usage)
+
+ result = []
+ for data, type_, size, dimension, offset, sizeinbytes, copyoffset in info:
+ copysize = data.shape[0] * dimension * sizeofGLType(type_)
+ vbo.update(data, offset=copyoffset, size=copysize)
+ result.append(
+ VertexBufferAttrib(vbo, type_, size, dimension, offset, 0))
+ return result
diff --git a/silx/gui/_glutils/__init__.py b/silx/gui/_glutils/__init__.py
new file mode 100644
index 0000000..e86a58f
--- /dev/null
+++ b/silx/gui/_glutils/__init__.py
@@ -0,0 +1,41 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2014-2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""This package provides utility functions to handle OpenGL resources.
+
+The :mod:`gl` module provides a wrapper to OpenGL based on PyOpenGL.
+"""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "25/07/2016"
+
+
+# OpenGL convenient functions
+from .Context import getGLContext, setGLContextGetter # noqa
+from .FramebufferTexture import FramebufferTexture # noqa
+from .Program import Program # noqa
+from .Texture import Texture # noqa
+from .VertexBuffer import VertexBuffer, VertexBufferAttrib, vertexBuffer # noqa
+from .utils import sizeofGLType, isSupportedGLType, numpyToGLType # noqa
diff --git a/silx/gui/_glutils/font.py b/silx/gui/_glutils/font.py
new file mode 100644
index 0000000..566ae49
--- /dev/null
+++ b/silx/gui/_glutils/font.py
@@ -0,0 +1,152 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Text rasterisation feature leveraging Qt font and text layout support."""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "13/10/2016"
+
+
+import logging
+import sys
+import numpy
+from .. import qt
+from .._utils import convertQImageToArray
+
+
+_logger = logging.getLogger(__name__)
+
+
+def getDefaultFontFamily():
+ """Returns the default font family of the application"""
+ return qt.QApplication.instance().font().family()
+
+
+# Font weights
+ULTRA_LIGHT = 0
+"""Lightest characters: Minimum font weight"""
+
+LIGHT = 25
+"""Light characters"""
+
+NORMAL = 50
+"""Normal characters"""
+
+SEMI_BOLD = 63
+"""Between normal and bold characters"""
+
+BOLD = 74
+"""Thicker characters"""
+
+BLACK = 87
+"""Really thick characters"""
+
+ULTRA_BLACK = 99
+"""Thickest characters: Maximum font weight"""
+
+
+def rasterText(text, font,
+ size=-1,
+ weight=-1,
+ italic=False,
+ devicePixelRatio=1.0):
+ """Raster text using Qt.
+
+ It supports multiple lines.
+
+ :param str text: The text to raster
+ :param font: Font name or QFont to use
+ :type font: str or :class:`QFont`
+ :param int size:
+ Font size in points
+ Used only if font is given as name.
+ :param int weight:
+ Font weight in [0, 99], see QFont.Weight.
+ Used only if font is given as name.
+ :param bool italic:
+ True for italic font (default: False).
+ Used only if font is given as name.
+ :param float devicePixelRatio:
+ The current ratio between device and device-independent pixel
+ (default: 1.0)
+ :return: Corresponding image in gray scale and baseline offset from top
+ :rtype: (HxW numpy.ndarray of uint8, int)
+ """
+ if not text:
+ _logger.info("Trying to raster empty text, replaced by white space")
+ text = ' ' # Replace empty text by white space to produce an image
+
+ if not isinstance(font, qt.QFont):
+ font = qt.QFont(font, size, weight, italic)
+
+ metrics = qt.QFontMetrics(font)
+ size = metrics.size(qt.Qt.TextExpandTabs, text)
+ bounds = metrics.boundingRect(
+ qt.QRect(0, 0, size.width(), size.height()),
+ qt.Qt.TextExpandTabs,
+ text)
+
+ if (devicePixelRatio != 1.0 and
+ not hasattr(qt.QImage, 'setDevicePixelRatio')): # Qt 4
+ _logger.error('devicePixelRatio not supported')
+ devicePixelRatio = 1.0
+
+ # Add extra border and handle devicePixelRatio
+ width = bounds.width() * devicePixelRatio + 2
+ # align line size to 32 bits to ease conversion to numpy array
+ width = 4 * ((width + 3) // 4)
+ image = qt.QImage(width,
+ bounds.height() * devicePixelRatio,
+ qt.QImage.Format_RGB888)
+ if (devicePixelRatio != 1.0 and
+ hasattr(image, 'setDevicePixelRatio')): # Qt 5
+ image.setDevicePixelRatio(devicePixelRatio)
+
+ # TODO if Qt5 use Format_Grayscale8 instead
+ image.fill(0)
+
+ # Raster text
+ painter = qt.QPainter()
+ painter.begin(image)
+ painter.setPen(qt.Qt.white)
+ painter.setFont(font)
+ painter.drawText(bounds, qt.Qt.TextExpandTabs, text)
+ painter.end()
+
+ array = convertQImageToArray(image)
+
+ # RGB to R
+ array = numpy.ascontiguousarray(array[:, :, 0])
+
+ # Remove leading and trailing empty columns but one on each side
+ column_cumsum = numpy.cumsum(numpy.sum(array, axis=0))
+ array = array[:, column_cumsum.argmin():column_cumsum.argmax() + 2]
+
+ # Remove leading and trailing empty rows but one on each side
+ row_cumsum = numpy.cumsum(numpy.sum(array, axis=1))
+ min_row = row_cumsum.argmin()
+ array = array[min_row:row_cumsum.argmax() + 2, :]
+
+ return array, metrics.ascent() - min_row
diff --git a/silx/gui/_glutils/gl.py b/silx/gui/_glutils/gl.py
new file mode 100644
index 0000000..4b9a7bb
--- /dev/null
+++ b/silx/gui/_glutils/gl.py
@@ -0,0 +1,165 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2014-2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""This module loads PyOpenGL and provides a namespace for OpenGL."""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "25/07/2016"
+
+
+from contextlib import contextmanager as _contextmanager
+from ctypes import c_uint
+import logging
+
+_logger = logging.getLogger(__name__)
+
+import OpenGL
+# Set the following to true for debugging
+if _logger.getEffectiveLevel() <= logging.DEBUG:
+ _logger.debug('Enabling PyOpenGL debug flags')
+ OpenGL.ERROR_LOGGING = True
+ OpenGL.ERROR_CHECKING = True
+ OpenGL.ERROR_ON_COPY = True
+else:
+ OpenGL.ERROR_LOGGING = False
+ OpenGL.ERROR_CHECKING = False
+ OpenGL.ERROR_ON_COPY = False
+
+import OpenGL.GL as _GL
+from OpenGL.GL import * # noqa
+
+# Extentions core in OpenGL 3
+from OpenGL.GL.ARB import framebuffer_object as _FBO
+from OpenGL.GL.ARB.framebuffer_object import * # noqa
+from OpenGL.GL.ARB.texture_rg import GL_R32F, GL_R16F # noqa
+from OpenGL.GL.ARB.texture_rg import GL_R16, GL_R8 # noqa
+
+# PyOpenGL 3.0.1 does not define it
+try:
+ GLchar
+except NameError:
+ from ctypes import c_char
+ GLchar = c_char
+
+
+def testGL():
+ """Test if required OpenGL version and extensions are available.
+
+ This MUST be run with an active OpenGL context.
+ """
+ version = glGetString(GL_VERSION).split()[0] # get version number
+ major, minor = int(version[0]), int(version[2])
+ if major < 2 or (major == 2 and minor < 1):
+ raise RuntimeError(
+ "Requires at least OpenGL version 2.1, running with %s" % version)
+
+ from OpenGL.GL.ARB.framebuffer_object import glInitFramebufferObjectARB
+ from OpenGL.GL.ARB.texture_rg import glInitTextureRgARB
+
+ if not glInitFramebufferObjectARB():
+ raise RuntimeError(
+ "OpenGL GL_ARB_framebuffer_object extension required !")
+
+ if not glInitTextureRgARB():
+ raise RuntimeError("OpenGL GL_ARB_texture_rg extension required !")
+
+
+# Additional setup
+if hasattr(glget, 'addGLGetConstant'):
+ glget.addGLGetConstant(GL_FRAMEBUFFER_BINDING, (1,))
+
+
+@_contextmanager
+def enabled(capacity, enable=True):
+ """Context manager enabling an OpenGL capacity.
+
+ This is not checking the current state of the capacity.
+
+ :param capacity: The OpenGL capacity enum to enable/disable
+ :param bool enable:
+ True (default) to enable during context, False to disable
+ """
+ if enable:
+ glEnable(capacity)
+ yield
+ glDisable(capacity)
+ else:
+ glDisable(capacity)
+ yield
+ glEnable(capacity)
+
+
+def disabled(capacity, disable=True):
+ """Context manager disabling an OpenGL capacity.
+
+ This is not checking the current state of the capacity.
+
+ :param capacity: The OpenGL capacity enum to disable/enable
+ :param bool disable:
+ True (default) to disable during context, False to enable
+ """
+ return enabled(capacity, not disable)
+
+
+# Additional OpenGL wrapping
+
+def glGetActiveAttrib(program, index):
+ """Wrap PyOpenGL glGetActiveAttrib"""
+ bufsize = glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH)
+ length = GLsizei()
+ size = GLint()
+ type_ = GLenum()
+ name = (GLchar * bufsize)()
+
+ _GL.glGetActiveAttrib(program, index, bufsize, length, size, type_, name)
+ return name.value, size.value, type_.value
+
+
+def glDeleteRenderbuffers(buffers):
+ if not hasattr(buffers, '__len__'): # Support single int argument
+ buffers = [buffers]
+ length = len(buffers)
+ _FBO.glDeleteRenderbuffers(length, (c_uint * length)(*buffers))
+
+
+def glDeleteFramebuffers(buffers):
+ if not hasattr(buffers, '__len__'): # Support single int argument
+ buffers = [buffers]
+ length = len(buffers)
+ _FBO.glDeleteFramebuffers(length, (c_uint * length)(*buffers))
+
+
+def glDeleteBuffers(buffers):
+ if not hasattr(buffers, '__len__'): # Support single int argument
+ buffers = [buffers]
+ length = len(buffers)
+ _GL.glDeleteBuffers(length, (c_uint * length)(*buffers))
+
+
+def glDeleteTextures(textures):
+ if not hasattr(textures, '__len__'): # Support single int argument
+ textures = [textures]
+ length = len(textures)
+ _GL.glDeleteTextures((c_uint * length)(*textures))
diff --git a/silx/gui/_glutils/utils.py b/silx/gui/_glutils/utils.py
new file mode 100644
index 0000000..73af338
--- /dev/null
+++ b/silx/gui/_glutils/utils.py
@@ -0,0 +1,70 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2014-2017 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""This module provides conversion functions between OpenGL and numpy types.
+"""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "10/01/2017"
+
+from . import gl
+import numpy
+
+
+_GL_TYPE_SIZES = {
+ gl.GL_FLOAT: 4,
+ gl.GL_BYTE: 1,
+ gl.GL_SHORT: 2,
+ gl.GL_INT: 4,
+ gl.GL_UNSIGNED_BYTE: 1,
+ gl.GL_UNSIGNED_SHORT: 2,
+ gl.GL_UNSIGNED_INT: 4,
+}
+
+
+def sizeofGLType(type_):
+ """Returns the size in bytes of an element of type `type_`"""
+ return _GL_TYPE_SIZES[type_]
+
+
+_TYPE_CONVERTER = {
+ numpy.dtype(numpy.float32): gl.GL_FLOAT,
+ numpy.dtype(numpy.int8): gl.GL_BYTE,
+ numpy.dtype(numpy.int16): gl.GL_SHORT,
+ numpy.dtype(numpy.int32): gl.GL_INT,
+ numpy.dtype(numpy.uint8): gl.GL_UNSIGNED_BYTE,
+ numpy.dtype(numpy.uint16): gl.GL_UNSIGNED_SHORT,
+ numpy.dtype(numpy.uint32): gl.GL_UNSIGNED_INT,
+}
+
+
+def isSupportedGLType(type_):
+ """Test if a numpy type or dtype can be converted to a GL type."""
+ return numpy.dtype(type_) in _TYPE_CONVERTER
+
+
+def numpyToGLType(type_):
+ """Returns the GL type corresponding the provided numpy type or dtype."""
+ return _TYPE_CONVERTER[numpy.dtype(type_)]