diff options
Diffstat (limited to 'silx/gui/_glutils')
-rw-r--r-- | silx/gui/_glutils/Context.py | 75 | ||||
-rw-r--r-- | silx/gui/_glutils/FramebufferTexture.py | 165 | ||||
-rw-r--r-- | silx/gui/_glutils/OpenGLWidget.py | 423 | ||||
-rw-r--r-- | silx/gui/_glutils/Program.py | 202 | ||||
-rw-r--r-- | silx/gui/_glutils/Texture.py | 352 | ||||
-rw-r--r-- | silx/gui/_glutils/VertexBuffer.py | 266 | ||||
-rw-r--r-- | silx/gui/_glutils/__init__.py | 43 | ||||
-rw-r--r-- | silx/gui/_glutils/font.py | 163 | ||||
-rw-r--r-- | silx/gui/_glutils/gl.py | 168 | ||||
-rw-r--r-- | silx/gui/_glutils/utils.py | 121 |
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] |