summaryrefslogtreecommitdiff
path: root/silx/gui/_glutils
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/_glutils')
-rw-r--r--silx/gui/_glutils/Context.py75
-rw-r--r--silx/gui/_glutils/FramebufferTexture.py165
-rw-r--r--silx/gui/_glutils/OpenGLWidget.py423
-rw-r--r--silx/gui/_glutils/Program.py202
-rw-r--r--silx/gui/_glutils/Texture.py352
-rw-r--r--silx/gui/_glutils/VertexBuffer.py266
-rw-r--r--silx/gui/_glutils/__init__.py43
-rw-r--r--silx/gui/_glutils/font.py163
-rw-r--r--silx/gui/_glutils/gl.py168
-rw-r--r--silx/gui/_glutils/utils.py121
10 files changed, 0 insertions, 1978 deletions
diff --git a/silx/gui/_glutils/Context.py b/silx/gui/_glutils/Context.py
deleted file mode 100644
index c62dbb9..0000000
--- a/silx/gui/_glutils/Context.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-2019 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"
-
-import contextlib
-
-
-class _DEFAULT_CONTEXT(object):
- """The default value for OpenGL context"""
- pass
-
-_context = _DEFAULT_CONTEXT
-"""The current OpenGL context"""
-
-
-def getCurrent():
- """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
- """
- return _context
-
-
-def setCurrent(context=_DEFAULT_CONTEXT):
- """Set a platform dependent OpenGL context
-
- :param context: Platform dependent GL context
- """
- global _context
- _context = context
-
-
-@contextlib.contextmanager
-def current(context):
- """Context manager setting the platform-dependent GL context
-
- :param context: Platform dependent GL context
- """
- previous_context = getCurrent()
- setCurrent(context)
- yield
- setCurrent(previous_context)
diff --git a/silx/gui/_glutils/FramebufferTexture.py b/silx/gui/_glutils/FramebufferTexture.py
deleted file mode 100644
index e065030..0000000
--- a/silx/gui/_glutils/FramebufferTexture.py
+++ /dev/null
@@ -1,165 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-2020 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._texture.prepare()
-
- self._previousFramebuffer = 0 # Used by with statement
-
- self._name = gl.glGenFramebuffers(1)
-
- with self: # Bind FBO
- # 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)
-
- @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)
- self._previousFramebuffer = None
-
- 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/OpenGLWidget.py b/silx/gui/_glutils/OpenGLWidget.py
deleted file mode 100644
index 5e3fcb8..0000000
--- a/silx/gui/_glutils/OpenGLWidget.py
+++ /dev/null
@@ -1,423 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2017-2020 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 a compatibility layer for OpenGL widget.
-
-It provides a compatibility layer for Qt OpenGL widget used in silx
-across Qt<=5.3 QtOpenGL.QGLWidget and QOpenGLWidget.
-"""
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "22/11/2019"
-
-
-import logging
-import sys
-
-from .. import qt
-from ..utils.glutils import isOpenGLAvailable
-from .._glutils import gl
-
-
-_logger = logging.getLogger(__name__)
-
-
-if not hasattr(qt, 'QOpenGLWidget') and not hasattr(qt, 'QGLWidget'):
- OpenGLWidget = None
-
-else:
- if hasattr(qt, 'QOpenGLWidget'): # PyQt>=5.4
- _logger.info('Using QOpenGLWidget')
- _BaseOpenGLWidget = qt.QOpenGLWidget
-
- else:
- _logger.info('Using QGLWidget')
- _BaseOpenGLWidget = qt.QGLWidget
-
- class _OpenGLWidget(_BaseOpenGLWidget):
- """Wrapper over QOpenGLWidget and QGLWidget"""
-
- sigOpenGLContextError = qt.Signal(str)
- """Signal emitted when an OpenGL context error is detected at runtime.
-
- It provides the error reason as a str.
- """
-
- def __init__(self, parent,
- alphaBufferSize=0,
- depthBufferSize=24,
- stencilBufferSize=8,
- version=(2, 0),
- f=qt.Qt.WindowFlags()):
- # True if using QGLWidget, False if using QOpenGLWidget
- self.__legacy = not hasattr(qt, 'QOpenGLWidget')
-
- self.__devicePixelRatio = 1.0
- self.__requestedOpenGLVersion = int(version[0]), int(version[1])
- self.__isValid = False
-
- if self.__legacy: # QGLWidget
- format_ = qt.QGLFormat()
- format_.setAlphaBufferSize(alphaBufferSize)
- format_.setAlpha(alphaBufferSize != 0)
- format_.setDepthBufferSize(depthBufferSize)
- format_.setDepth(depthBufferSize != 0)
- format_.setStencilBufferSize(stencilBufferSize)
- format_.setStencil(stencilBufferSize != 0)
- format_.setVersion(*self.__requestedOpenGLVersion)
- format_.setDoubleBuffer(True)
-
- super(_OpenGLWidget, self).__init__(format_, parent, None, f)
-
- else: # QOpenGLWidget
- super(_OpenGLWidget, self).__init__(parent, f)
-
- format_ = qt.QSurfaceFormat()
- format_.setAlphaBufferSize(alphaBufferSize)
- format_.setDepthBufferSize(depthBufferSize)
- format_.setStencilBufferSize(stencilBufferSize)
- format_.setVersion(*self.__requestedOpenGLVersion)
- format_.setSwapBehavior(qt.QSurfaceFormat.DoubleBuffer)
- self.setFormat(format_)
-
- # Enable receiving mouse move events when no buttons are pressed
- self.setMouseTracking(True)
-
- def getDevicePixelRatio(self):
- """Returns the ratio device-independent / device pixel size
-
- It should be either 1.0 or 2.0.
-
- :return: Scale factor between screen and Qt units
- :rtype: float
- """
- return self.__devicePixelRatio
-
- def getRequestedOpenGLVersion(self):
- """Returns the requested OpenGL version.
-
- :return: (major, minor)
- :rtype: 2-tuple of int"""
- return self.__requestedOpenGLVersion
-
- def getOpenGLVersion(self):
- """Returns the available OpenGL version.
-
- :return: (major, minor)
- :rtype: 2-tuple of int"""
- if self.__legacy: # QGLWidget
- supportedVersion = 0, 0
-
- # Go through all OpenGL version flags checking support
- flags = self.format().openGLVersionFlags()
- for version in ((1, 1), (1, 2), (1, 3), (1, 4), (1, 5),
- (2, 0), (2, 1),
- (3, 0), (3, 1), (3, 2), (3, 3),
- (4, 0)):
- versionFlag = getattr(qt.QGLFormat,
- 'OpenGL_Version_%d_%d' % version)
- if not versionFlag & flags:
- break
- supportedVersion = version
- return supportedVersion
-
- else: # QOpenGLWidget
- return self.format().version()
-
- # QOpenGLWidget methods
-
- def isValid(self):
- """Returns True if OpenGL is available.
-
- This adds extra checks to Qt isValid method.
-
- :rtype: bool
- """
- return self.__isValid and super(_OpenGLWidget, self).isValid()
-
- def defaultFramebufferObject(self):
- """Returns the framebuffer object handle.
-
- See :meth:`QOpenGLWidget.defaultFramebufferObject`
- """
- if self.__legacy: # QGLWidget
- return 0
- else: # QOpenGLWidget
- return super(_OpenGLWidget, self).defaultFramebufferObject()
-
- # *GL overridden methods
-
- def initializeGL(self):
- parent = self.parent()
- if parent is None:
- _logger.error('_OpenGLWidget has no parent')
- return
-
- # Check OpenGL version
- if self.getOpenGLVersion() >= self.getRequestedOpenGLVersion():
- try:
- gl.glGetError() # clear any previous error (if any)
- version = gl.glGetString(gl.GL_VERSION)
- except:
- version = None
-
- if version:
- self.__isValid = True
- else:
- errMsg = 'OpenGL not available'
- if sys.platform.startswith('linux'):
- errMsg += ': If connected remotely, ' \
- 'GLX forwarding might be disabled.'
- _logger.error(errMsg)
- self.sigOpenGLContextError.emit(errMsg)
- self.__isValid = False
-
- else:
- errMsg = 'OpenGL %d.%d not available' % \
- self.getRequestedOpenGLVersion()
- _logger.error('OpenGL widget disabled: %s', errMsg)
- self.sigOpenGLContextError.emit(errMsg)
- self.__isValid = False
-
- if self.isValid():
- parent.initializeGL()
-
- def paintGL(self):
- parent = self.parent()
- if parent is None:
- _logger.error('_OpenGLWidget has no parent')
- return
-
- if qt.BINDING in ('PyQt5', 'PySide2'):
- devicePixelRatio = self.window().windowHandle().devicePixelRatio()
-
- if devicePixelRatio != self.getDevicePixelRatio():
- # Update devicePixelRatio and call resizeOpenGL
- # as resizeGL is not always called.
- self.__devicePixelRatio = devicePixelRatio
- self.makeCurrent()
- parent.resizeGL(self.width(), self.height())
-
- if self.isValid():
- parent.paintGL()
-
- def resizeGL(self, width, height):
- parent = self.parent()
- if parent is None:
- _logger.error('_OpenGLWidget has no parent')
- return
-
- if self.isValid():
- # Call parent resizeGL with device-independent pixel unit
- # This works over both QGLWidget and QOpenGLWidget
- parent.resizeGL(self.width(), self.height())
-
-
-class OpenGLWidget(qt.QWidget):
- """OpenGL widget wrapper over QGLWidget and QOpenGLWidget
-
- This wrapper API implements a subset of QOpenGLWidget API.
- The constructor takes a different set of arguments.
- Methods returning object like :meth:`context` returns either
- QGL* or QOpenGL* objects.
-
- :param parent: Parent widget see :class:`QWidget`
- :param int alphaBufferSize:
- Size in bits of the alpha channel (default: 0).
- Set to 0 to disable alpha channel.
- :param int depthBufferSize:
- Size in bits of the depth buffer (default: 24).
- Set to 0 to disable depth buffer.
- :param int stencilBufferSize:
- Size in bits of the stencil buffer (default: 8).
- Set to 0 to disable stencil buffer
- :param version: Requested OpenGL version (default: (2, 0)).
- :type version: 2-tuple of int
- :param f: see :class:`QWidget`
- """
-
- def __init__(self, parent=None,
- alphaBufferSize=0,
- depthBufferSize=24,
- stencilBufferSize=8,
- version=(2, 0),
- f=qt.Qt.WindowFlags()):
- super(OpenGLWidget, self).__init__(parent, f)
-
- layout = qt.QHBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- self.setLayout(layout)
-
- self.__context = None
-
- _check = isOpenGLAvailable(version=version, runtimeCheck=False)
- if _OpenGLWidget is None or not _check:
- _logger.error('OpenGL-based widget disabled: %s', _check.error)
- self.__openGLWidget = None
- label = self._createErrorQLabel(_check.error)
- self.layout().addWidget(label)
-
- else:
- self.__openGLWidget = _OpenGLWidget(
- parent=self,
- alphaBufferSize=alphaBufferSize,
- depthBufferSize=depthBufferSize,
- stencilBufferSize=stencilBufferSize,
- version=version,
- f=f)
- # Async connection need, otherwise issue when hiding OpenGL
- # widget while doing the rendering..
- self.__openGLWidget.sigOpenGLContextError.connect(
- self._handleOpenGLInitError, qt.Qt.QueuedConnection)
- self.layout().addWidget(self.__openGLWidget)
-
- @staticmethod
- def _createErrorQLabel(error):
- """Create QLabel displaying error message in place of OpenGL widget
-
- :param str error: The error message to display"""
- label = qt.QLabel()
- label.setText('OpenGL-based widget disabled:\n%s' % error)
- label.setAlignment(qt.Qt.AlignCenter)
- label.setWordWrap(True)
- return label
-
- def _handleOpenGLInitError(self, error):
- """Handle runtime errors in OpenGL widget"""
- if self.__openGLWidget is not None:
- self.__openGLWidget.setVisible(False)
- self.__openGLWidget.setParent(None)
- self.__openGLWidget = None
-
- label = self._createErrorQLabel(error)
- self.layout().addWidget(label)
-
- # Additional API
-
- def getDevicePixelRatio(self):
- """Returns the ratio device-independent / device pixel size
-
- It should be either 1.0 or 2.0.
-
- :return: Scale factor between screen and Qt units
- :rtype: float
- """
- if self.__openGLWidget is None:
- return 1.
- else:
- return self.__openGLWidget.getDevicePixelRatio()
-
- def getDotsPerInch(self):
- """Returns current screen resolution as device pixels per inch.
-
- :rtype: float
- """
- screen = self.window().windowHandle().screen()
- if screen is not None:
- # TODO check if this is correct on different OS/screen
- # OK on macOS10.12/qt5.13.2
- dpi = screen.physicalDotsPerInch() * self.getDevicePixelRatio()
- else: # Fallback
- dpi = 96. * self.getDevicePixelRatio()
- return dpi
-
- def getOpenGLVersion(self):
- """Returns the available OpenGL version.
-
- :return: (major, minor)
- :rtype: 2-tuple of int"""
- if self.__openGLWidget is None:
- return 0, 0
- else:
- return self.__openGLWidget.getOpenGLVersion()
-
- # QOpenGLWidget API
-
- def isValid(self):
- """Returns True if OpenGL with the requested version is available.
-
- :rtype: bool
- """
- if self.__openGLWidget is None:
- return False
- else:
- return self.__openGLWidget.isValid()
-
- def context(self):
- """Return Qt OpenGL context object or None.
-
- See :meth:`QOpenGLWidget.context` and :meth:`QGLWidget.context`
- """
- if self.__openGLWidget is None:
- return None
- else:
- # Keep a reference on QOpenGLContext to make
- # else PyQt5 keeps creating a new one.
- self.__context = self.__openGLWidget.context()
- return self.__context
-
- def defaultFramebufferObject(self):
- """Returns the framebuffer object handle.
-
- See :meth:`QOpenGLWidget.defaultFramebufferObject`
- """
- if self.__openGLWidget is None:
- return 0
- else:
- return self.__openGLWidget.defaultFramebufferObject()
-
- def makeCurrent(self):
- """Make the underlying OpenGL widget's context current.
-
- See :meth:`QOpenGLWidget.makeCurrent`
- """
- if self.__openGLWidget is not None:
- self.__openGLWidget.makeCurrent()
-
- def update(self):
- """Async update of the OpenGL widget.
-
- See :meth:`QOpenGLWidget.update`
- """
- if self.__openGLWidget is not None:
- self.__openGLWidget.update()
-
- # QOpenGLWidget API to override
-
- def initializeGL(self):
- """Override to implement OpenGL initialization."""
- pass
-
- def paintGL(self):
- """Override to implement OpenGL rendering."""
- pass
-
- def resizeGL(self, width, height):
- """Override to implement resize of OpenGL framebuffer.
-
- :param int width: Width in device-independent pixels
- :param int height: Height in device-independent pixels
- """
- pass
diff --git a/silx/gui/_glutils/Program.py b/silx/gui/_glutils/Program.py
deleted file mode 100644
index 87eec5f..0000000
--- a/silx/gui/_glutils/Program.py
+++ /dev/null
@@ -1,202 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-2019 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 weakref
-
-import numpy
-
-from . import Context, gl
-
-_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 = weakref.WeakKeyDictionary()
-
- @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 = Context.getCurrent()
- 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 = Context.getCurrent()
-
- 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
deleted file mode 100644
index c72135a..0000000
--- a/silx/gui/_glutils/Texture.py
+++ /dev/null
@@ -1,352 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-2020 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"
-
-
-try:
- from collections import abc
-except ImportError: # Python2 support
- import collections as abc
-
-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
- (height, width) or (depth, height, width)
- :type shape: List[int]
- :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
-
- self._deferredUpdates = [(format_, data, None)]
-
- assert len(shape) in (2, 3)
- self._shape = tuple(shape)
- self._ndim = len(shape)
-
- self.texUnit = texUnit
-
- self._texParameterUpdates = {} # Store texture params to update
-
- self._minFilter = minFilter if minFilter is not None else gl.GL_NEAREST
- self._texParameterUpdates[gl.GL_TEXTURE_MIN_FILTER] = self._minFilter
-
- self._magFilter = magFilter if magFilter is not None else gl.GL_LINEAR
- self._texParameterUpdates[gl.GL_TEXTURE_MAG_FILTER] = self._magFilter
-
- self._name = None # Store texture ID
-
- if wrap is not None:
- if not isinstance(wrap, abc.Iterable):
- wrap = [wrap] * self.ndim
-
- assert len(wrap) == self.ndim
-
- self._texParameterUpdates[gl.GL_TEXTURE_WRAP_S] = wrap[-1]
- self._texParameterUpdates[gl.GL_TEXTURE_WRAP_T] = wrap[-2]
- if self.ndim == 3:
- self._texParameterUpdates[gl.GL_TEXTURE_WRAP_R] = wrap[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.
-
- It is None if not initialized or already discarded.
- """
- return self._name
-
- @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._texParameterUpdates[gl.GL_TEXTURE_MIN_FILTER] = 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._texParameterUpdates[gl.GL_TEXTURE_MAG_FILTER] = magFilter
-
- def _isPrepareRequired(self) -> bool:
- """Returns True if OpenGL texture needs to be updated.
-
- :rtype: bool
- """
- return (self._name is None or
- self._texParameterUpdates or
- self._deferredUpdates)
-
- def _prepareAndBind(self, texUnit=None):
- """Synchronizes the OpenGL texture"""
- if self._name is None:
- self._name = gl.glGenTextures(1)
-
- self._bind(texUnit)
-
- # Synchronizes texture parameters
- for pname, param in self._texParameterUpdates.items():
- gl.glTexParameter(self.target, pname, param)
- self._texParameterUpdates = {}
-
- # Copy data to texture
- for format_, data, offset in self._deferredUpdates:
- 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 offset is None: # Initialize texture
- 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)
-
- else: # Update already existing texture
- if self.ndim == 2:
- gl.glTexSubImage2D(gl.GL_TEXTURE_2D,
- 0,
- offset[1],
- offset[0],
- data.shape[1],
- data.shape[0],
- format_,
- type_,
- data)
-
- 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)
-
- self._deferredUpdates = []
-
- 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)
-
- def _unbind(self, texUnit=None):
- """Reset texture binding 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, 0)
-
- def prepare(self):
- """Synchronizes the OpenGL texture.
-
- This method must be called with a current OpenGL context.
- """
- if self._isPrepareRequired():
- self._prepareAndBind()
- self._unbind()
-
- def bind(self, texUnit=None):
- """Bind the texture to a texture unit.
-
- The OpenGL texture is updated if needed.
-
- This method must be called with a current OpenGL context.
-
- :param int texUnit: The texture unit to use
- """
- if self._isPrepareRequired():
- self._prepareAndBind(texUnit)
- else:
- self._bind(texUnit)
-
- def discard(self):
- """Delete associated OpenGL texture.
-
- This method must be called with a current OpenGL context.
- """
- if self._name is not None:
- gl.glDeleteTextures(self._name)
- self._name = None
- else:
- _logger.warning("Texture not initialized or already discarded")
-
- # with statement
-
- def __enter__(self):
- self.bind()
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- self._unbind()
-
- def update(self, format_, data, offset=(0, 0, 0), copy=True):
- """Update the content of the texture.
-
- Texture is not resized, so data must fit into texture with the
- given offset.
-
- This update is performed lazily during next call to
- :meth:`prepare` or :meth:`bind`.
- Data MUST not be changed until then.
-
- :param format_: The OpenGL format of the data
- :param data: The data to use to update the texture
- :param List[int] offset: Offset in the texture where to copy the data
- :param bool copy:
- True (default) to copy data, False to use as is (do not modify)
- """
- data = numpy.array(data, copy=copy, order='C')
- offset = tuple(offset)
-
- 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]
-
- self._deferredUpdates.append((format_, data, offset))
diff --git a/silx/gui/_glutils/VertexBuffer.py b/silx/gui/_glutils/VertexBuffer.py
deleted file mode 100644
index b74b748..0000000
--- a/silx/gui/_glutils/VertexBuffer.py
+++ /dev/null
@@ -1,266 +0,0 @@
-# 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,
- normalization=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.normalization = bool(normalization)
-
- @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"""
- normalization = gl.GL_TRUE if self.normalization else gl.GL_FALSE
- with self.vbo:
- gl.glVertexAttribPointer(attribute,
- self.dimension,
- self.type_,
- normalization,
- 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.normalization)
-
-
-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
deleted file mode 100644
index e88affd..0000000
--- a/silx/gui/_glutils/__init__.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-2019 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 .OpenGLWidget import OpenGLWidget # noqa
-from . import Context # 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
-from .utils import segmentTrianglesIntersection # noqa
diff --git a/silx/gui/_glutils/font.py b/silx/gui/_glutils/font.py
deleted file mode 100644
index 6a4c489..0000000
--- a/silx/gui/_glutils/font.py
+++ /dev/null
@@ -1,163 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2020 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 numpy
-
-from ..utils.image import convertQImageToArray
-from .. import qt
-
-_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 (devicePixelRatio != 1.0 and
- not hasattr(qt.QImage, 'setDevicePixelRatio')): # Qt 4
- _logger.error('devicePixelRatio not supported')
- devicePixelRatio = 1.0
-
- if not isinstance(font, qt.QFont):
- font = qt.QFont(font, size, weight, italic)
-
- # get text size
- image = qt.QImage(1, 1, qt.QImage.Format_RGB888)
- painter = qt.QPainter()
- painter.begin(image)
- painter.setPen(qt.Qt.white)
- painter.setFont(font)
- bounds = painter.boundingRect(
- qt.QRect(0, 0, 4096, 4096), qt.Qt.TextExpandTabs, text)
- painter.end()
-
- metrics = qt.QFontMetrics(font)
-
- # This does not provide the correct text bbox on macOS
- # size = metrics.size(qt.Qt.TextExpandTabs, text)
- # bounds = metrics.boundingRect(
- # qt.QRect(0, 0, size.width(), size.height()),
- # qt.Qt.TextExpandTabs,
- # text)
-
- # 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(int(width),
- int(bounds.height() * devicePixelRatio + 2),
- 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
deleted file mode 100644
index 608d9ce..0000000
--- a/silx/gui/_glutils/gl.py
+++ /dev/null
@@ -1,168 +0,0 @@
-# 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 bool(enable) == glGetBoolean(capacity):
- # Already in the right state: noop
- yield
- elif 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
deleted file mode 100644
index d5627ef..0000000
--- a/silx/gui/_glutils/utils.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2014-2019 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"
-
-import numpy
-
-from OpenGL.constants import BYTE_SIZES as _BYTE_SIZES
-from OpenGL.constants import ARRAY_TO_GL_TYPE_MAPPING as _ARRAY_TO_GL_TYPE_MAPPING
-
-
-def sizeofGLType(type_):
- """Returns the size in bytes of an element of type `type_`"""
- return _BYTE_SIZES[type_]
-
-
-def isSupportedGLType(type_):
- """Test if a numpy type or dtype can be converted to a GL type."""
- return numpy.dtype(type_).char in _ARRAY_TO_GL_TYPE_MAPPING
-
-
-def numpyToGLType(type_):
- """Returns the GL type corresponding the provided numpy type or dtype."""
- return _ARRAY_TO_GL_TYPE_MAPPING[numpy.dtype(type_).char]
-
-
-def segmentTrianglesIntersection(segment, triangles):
- """Check for segment/triangles intersection.
-
- This is based on signed tetrahedron volume comparison.
-
- See A. Kensler, A., Shirley, P.
- Optimizing Ray-Triangle Intersection via Automated Search.
- Symposium on Interactive Ray Tracing, vol. 0, p33-38 (2006)
-
- :param numpy.ndarray segment:
- Segment end points as a 2x3 array of coordinates
- :param numpy.ndarray triangles:
- Nx3x3 array of triangles
- :return: (triangle indices, segment parameter, barycentric coord)
- Indices of intersected triangles, "depth" along the segment
- of the intersection point and barycentric coordinates of intersection
- point in the triangle.
- :rtype: List[numpy.ndarray]
- """
- # TODO triangles from vertices + indices
- # TODO early rejection? e.g., check segment bbox vs triangle bbox
- segment = numpy.asarray(segment)
- assert segment.ndim == 2
- assert segment.shape == (2, 3)
-
- triangles = numpy.asarray(triangles)
- assert triangles.ndim == 3
- assert triangles.shape[1] == 3
-
- # Test line/triangles intersection
- d = segment[1] - segment[0]
- t0s0 = segment[0] - triangles[:, 0, :]
- edge01 = triangles[:, 1, :] - triangles[:, 0, :]
- edge02 = triangles[:, 2, :] - triangles[:, 0, :]
-
- dCrossEdge02 = numpy.cross(d, edge02)
- t0s0CrossEdge01 = numpy.cross(t0s0, edge01)
- volume = numpy.sum(dCrossEdge02 * edge01, axis=1)
- del edge01
- subVolumes = numpy.empty((len(triangles), 3), dtype=triangles.dtype)
- subVolumes[:, 1] = numpy.sum(dCrossEdge02 * t0s0, axis=1)
- del dCrossEdge02
- subVolumes[:, 2] = numpy.sum(t0s0CrossEdge01 * d, axis=1)
- subVolumes[:, 0] = volume - subVolumes[:, 1] - subVolumes[:, 2]
- intersect = numpy.logical_or(
- numpy.all(subVolumes >= 0., axis=1), # All positive
- numpy.all(subVolumes <= 0., axis=1)) # All negative
- intersect = numpy.where(intersect)[0] # Indices of intersected triangles
-
- # Get barycentric coordinates
- barycentric = subVolumes[intersect] / volume[intersect].reshape(-1, 1)
- del subVolumes
-
- # Test segment/triangles intersection
- volAlpha = numpy.sum(t0s0CrossEdge01[intersect] * edge02[intersect], axis=1)
- t = volAlpha / volume[intersect] # segment parameter of intersected triangles
- del t0s0CrossEdge01
- del edge02
- del volAlpha
- del volume
-
- inSegmentMask = numpy.logical_and(t >= 0., t <= 1.)
- intersect = intersect[inSegmentMask]
- t = t[inSegmentMask]
- barycentric = barycentric[inSegmentMask]
-
- # Sort intersecting triangles by t
- indices = numpy.argsort(t)
- return intersect[indices], t[indices], barycentric[indices]