summaryrefslogtreecommitdiff
path: root/src/silx/gui/_glutils
diff options
context:
space:
mode:
authorPicca Frédéric-Emmanuel <picca@debian.org>2024-02-05 16:30:07 +0100
committerPicca Frédéric-Emmanuel <picca@debian.org>2024-02-05 16:30:07 +0100
commit04095a69f18767d222b16fae5b40f2b712cd6f7e (patch)
treed20abd3ee2f237319443e9dfd7500ad55d29a33d /src/silx/gui/_glutils
parent3427caf0e96690e56aac6231a91df8f0f7a64fc2 (diff)
New upstream version 2.0.0+dfsg
Diffstat (limited to 'src/silx/gui/_glutils')
-rw-r--r--src/silx/gui/_glutils/Context.py2
-rw-r--r--src/silx/gui/_glutils/FramebufferTexture.py68
-rw-r--r--src/silx/gui/_glutils/OpenGLWidget.py142
-rw-r--r--src/silx/gui/_glutils/Program.py33
-rw-r--r--src/silx/gui/_glutils/Texture.py106
-rw-r--r--src/silx/gui/_glutils/VertexBuffer.py82
-rw-r--r--src/silx/gui/_glutils/__init__.py2
-rw-r--r--src/silx/gui/_glutils/font.py158
-rw-r--r--src/silx/gui/_glutils/gl.py60
-rw-r--r--src/silx/gui/_glutils/test/__init__.py2
-rw-r--r--src/silx/gui/_glutils/test/test_gl.py4
-rw-r--r--src/silx/gui/_glutils/utils.py7
12 files changed, 292 insertions, 374 deletions
diff --git a/src/silx/gui/_glutils/Context.py b/src/silx/gui/_glutils/Context.py
index d2ddaa3..c0def5c 100644
--- a/src/silx/gui/_glutils/Context.py
+++ b/src/silx/gui/_glutils/Context.py
@@ -36,8 +36,10 @@ import contextlib
class _DEFAULT_CONTEXT(object):
"""The default value for OpenGL context"""
+
pass
+
_context = _DEFAULT_CONTEXT
"""The current OpenGL context"""
diff --git a/src/silx/gui/_glutils/FramebufferTexture.py b/src/silx/gui/_glutils/FramebufferTexture.py
index 75db264..6d1a8d9 100644
--- a/src/silx/gui/_glutils/FramebufferTexture.py
+++ b/src/silx/gui/_glutils/FramebufferTexture.py
@@ -53,13 +53,14 @@ class FramebufferTexture(object):
_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):
-
+ 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()
@@ -69,24 +70,28 @@ class FramebufferTexture(object):
with self: # Bind FBO
# Attachments
- gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER,
- gl.GL_COLOR_ATTACHMENT0,
- gl.GL_TEXTURE_2D,
- self._texture.name,
- 0)
+ 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)
+ 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
@@ -96,13 +101,15 @@ class FramebufferTexture(object):
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)
+ 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
@@ -110,7 +117,8 @@ class FramebufferTexture(object):
if status != gl.GL_FRAMEBUFFER_COMPLETE:
_logger.error(
"OpenGL framebuffer initialization not complete, display may fail (error %d)",
- status)
+ status,
+ )
@property
def shape(self):
@@ -130,8 +138,10 @@ class FramebufferTexture(object):
if self._name is not None:
return self._name
else:
- raise RuntimeError("No OpenGL framebuffer resource, \
- discard has already been called")
+ raise RuntimeError(
+ "No OpenGL framebuffer resource, \
+ discard has already been called"
+ )
def bind(self):
"""Bind this framebuffer for rendering"""
diff --git a/src/silx/gui/_glutils/OpenGLWidget.py b/src/silx/gui/_glutils/OpenGLWidget.py
index d35bb73..59fa4f0 100644
--- a/src/silx/gui/_glutils/OpenGLWidget.py
+++ b/src/silx/gui/_glutils/OpenGLWidget.py
@@ -1,6 +1,6 @@
# /*##########################################################################
#
-# Copyright (c) 2017-2021 European Synchrotron Radiation Facility
+# Copyright (c) 2017-2023 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
@@ -43,16 +43,16 @@ from .._glutils import gl
_logger = logging.getLogger(__name__)
-if not hasattr(qt, 'QOpenGLWidget') and not hasattr(qt, 'QGLWidget'):
+if not hasattr(qt, "QOpenGLWidget") and not hasattr(qt, "QGLWidget"):
_OpenGLWidget = None
else:
- if hasattr(qt, 'QOpenGLWidget'): # PyQt>=5.4
- _logger.info('Using QOpenGLWidget')
+ if hasattr(qt, "QOpenGLWidget"): # PyQt>=5.4
+ _logger.info("Using QOpenGLWidget")
_BaseOpenGLWidget = qt.QOpenGLWidget
else:
- _logger.info('Using QGLWidget')
+ _logger.info("Using QGLWidget")
_BaseOpenGLWidget = qt.QGLWidget
class _OpenGLWidget(_BaseOpenGLWidget):
@@ -64,14 +64,17 @@ else:
It provides the error reason as a str.
"""
- def __init__(self, parent,
- alphaBufferSize=0,
- depthBufferSize=24,
- stencilBufferSize=8,
- version=(2, 0),
- f=qt.Qt.Widget):
+ def __init__(
+ self,
+ parent,
+ alphaBufferSize=0,
+ depthBufferSize=24,
+ stencilBufferSize=8,
+ version=(2, 0),
+ f=qt.Qt.Widget,
+ ):
# True if using QGLWidget, False if using QOpenGLWidget
- self.__legacy = not hasattr(qt, 'QOpenGLWidget')
+ self.__legacy = not hasattr(qt, "QOpenGLWidget")
self.__devicePixelRatio = 1.0
self.__requestedOpenGLVersion = int(version[0]), int(version[1])
@@ -131,12 +134,23 @@ else:
# 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)
+ 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
@@ -171,13 +185,13 @@ else:
def initializeGL(self):
parent = self.parent()
if parent is None:
- _logger.error('_OpenGLWidget has no parent')
+ _logger.error("_OpenGLWidget has no parent")
return
# Check OpenGL version
if self.getOpenGLVersion() >= self.getRequestedOpenGLVersion():
try:
- gl.glGetError() # clear any previous error (if any)
+ gl.glGetError() # clear any previous error (if any)
version = gl.glGetString(gl.GL_VERSION)
except:
version = None
@@ -185,18 +199,19 @@ else:
if version:
self.__isValid = True
else:
- errMsg = 'OpenGL not available'
- if sys.platform.startswith('linux'):
- errMsg += ': If connected remotely, ' \
- 'GLX forwarding might be disabled.'
+ 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)
+ errMsg = "OpenGL %d.%d not available" % self.getRequestedOpenGLVersion()
+ _logger.error("OpenGL widget disabled: %s", errMsg)
self.sigOpenGLContextError.emit(errMsg)
self.__isValid = False
@@ -206,7 +221,7 @@ else:
def paintGL(self):
parent = self.parent()
if parent is None:
- _logger.error('_OpenGLWidget has no parent')
+ _logger.error("_OpenGLWidget has no parent")
return
devicePixelRatio = self.window().windowHandle().devicePixelRatio()
@@ -224,7 +239,7 @@ else:
def resizeGL(self, width, height):
parent = self.parent()
if parent is None:
- _logger.error('_OpenGLWidget has no parent')
+ _logger.error("_OpenGLWidget has no parent")
return
if self.isValid():
@@ -256,12 +271,15 @@ class OpenGLWidget(qt.QWidget):
:param f: see :class:`QWidget`
"""
- def __init__(self, parent=None,
- alphaBufferSize=0,
- depthBufferSize=24,
- stencilBufferSize=8,
- version=(2, 0),
- f=qt.Qt.Widget):
+ def __init__(
+ self,
+ parent=None,
+ alphaBufferSize=0,
+ depthBufferSize=24,
+ stencilBufferSize=8,
+ version=(2, 0),
+ f=qt.Qt.Widget,
+ ):
super(OpenGLWidget, self).__init__(parent, f)
layout = qt.QHBoxLayout(self)
@@ -272,24 +290,26 @@ class OpenGLWidget(qt.QWidget):
_check = isOpenGLAvailable(version=version, runtimeCheck=False)
if _OpenGLWidget is None or not _check:
- _logger.error('OpenGL-based widget disabled: %s', _check.error)
+ _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)
+ return
+
+ 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):
@@ -297,7 +317,7 @@ class OpenGLWidget(qt.QWidget):
:param str error: The error message to display"""
label = qt.QLabel()
- label.setText('OpenGL-based widget disabled:\n%s' % error)
+ label.setText("OpenGL-based widget disabled:\n%s" % error)
label.setAlignment(qt.Qt.AlignCenter)
label.setWordWrap(True)
return label
@@ -323,7 +343,7 @@ class OpenGLWidget(qt.QWidget):
:rtype: float
"""
if self.__openGLWidget is None:
- return 1.
+ return 1.0
else:
return self.__openGLWidget.getDevicePixelRatio()
@@ -333,13 +353,17 @@ class OpenGLWidget(qt.QWidget):
: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
+ if screen is None:
+ return 96.0 * self.getDevicePixelRatio()
+
+ physicalDPI = screen.physicalDotsPerInch()
+ if physicalDPI > 1000.0:
+ _logger.error(
+ "Reported screen DPI too high: %f, using default value instead",
+ physicalDPI,
+ )
+ physicalDPI = 96.0
+ return physicalDPI * self.getDevicePixelRatio()
def getOpenGLVersion(self):
"""Returns the available OpenGL version.
diff --git a/src/silx/gui/_glutils/Program.py b/src/silx/gui/_glutils/Program.py
index d61c07d..b2adacf 100644
--- a/src/silx/gui/_glutils/Program.py
+++ b/src/silx/gui/_glutils/Program.py
@@ -55,8 +55,7 @@ class Program(object):
array attached to it in order for the rendering to occur....
"""
- def __init__(self, vertexShader, fragmentShader,
- attrib0='position'):
+ def __init__(self, vertexShader, fragmentShader, attrib0="position"):
self._vertexShader = vertexShader
self._fragmentShader = fragmentShader
self._attrib0 = attrib0
@@ -66,7 +65,7 @@ class Program(object):
def _compileGL(vertexShader, fragmentShader, attrib0):
program = gl.glCreateProgram()
- gl.glBindAttribLocation(program, 0, attrib0.encode('ascii'))
+ gl.glBindAttribLocation(program, 0, attrib0.encode("ascii"))
vertex = gl.glCreateShader(gl.GL_VERTEX_SHADER)
gl.glShaderSource(vertex, vertexShader)
@@ -79,8 +78,7 @@ class Program(object):
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:
+ if gl.glGetShaderiv(fragment, gl.GL_COMPILE_STATUS) != gl.GL_TRUE:
raise RuntimeError(gl.glGetShaderInfoLog(fragment))
gl.glAttachShader(program, fragment)
gl.glDeleteShader(fragment)
@@ -90,16 +88,15 @@ class Program(object):
raise RuntimeError(gl.glGetProgramInfoLog(program))
attributes = {}
- for index in range(gl.glGetProgramiv(program,
- gl.GL_ACTIVE_ATTRIBUTES)):
+ for index in range(gl.glGetProgramiv(program, gl.GL_ACTIVE_ATTRIBUTES)):
name = gl.glGetActiveAttrib(program, index)[0]
- namestr = name.decode('ascii')
+ 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')
+ namestr = name.decode("ascii")
uniforms[namestr] = gl.glGetUniformLocation(program, name)
return program, attributes, uniforms
@@ -107,8 +104,7 @@ class Program(object):
def _getProgramInfo(self):
glcontext = Context.getCurrent()
if glcontext not in self._programs:
- raise RuntimeError(
- "Program was not compiled for current OpenGL context.")
+ raise RuntimeError("Program was not compiled for current OpenGL context.")
return self._programs[glcontext]
@property
@@ -152,16 +148,15 @@ class Program(object):
if glcontext not in self._programs:
self._programs[glcontext] = self._compileGL(
- self._vertexShader,
- self._fragmentShader,
- self._attrib0)
+ 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))
+ 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)
@@ -198,4 +193,4 @@ class Program(object):
gl.glUniformMatrix4fv(location, count, transpose, value)
elif not safe:
- raise KeyError('No uniform: %s' % name)
+ raise KeyError("No uniform: %s" % name)
diff --git a/src/silx/gui/_glutils/Texture.py b/src/silx/gui/_glutils/Texture.py
index 76bdcd8..aac380d 100644
--- a/src/silx/gui/_glutils/Texture.py
+++ b/src/silx/gui/_glutils/Texture.py
@@ -1,6 +1,6 @@
# /*##########################################################################
#
-# Copyright (c) 2014-2020 European Synchrotron Radiation Facility
+# Copyright (c) 2014-2023 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
@@ -28,11 +28,7 @@ __license__ = "MIT"
__date__ = "04/10/2016"
-try:
- from collections import abc
-except ImportError: # Python2 support
- import collections as abc
-
+from collections import abc
from ctypes import c_void_p
import logging
@@ -62,10 +58,17 @@ class Texture(object):
: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):
-
+ 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
@@ -74,7 +77,7 @@ class Texture(object):
assert shape is not None
else:
assert shape is None
- data = numpy.array(data, copy=False, order='C')
+ data = numpy.array(data, copy=False, order="C")
if format_ != gl.GL_RED:
shape = data.shape[:-1] # Last dimension is channels
else:
@@ -164,9 +167,7 @@ class Texture(object):
:rtype: bool
"""
- return (self._name is None or
- self._texParameterUpdates or
- self._deferredUpdates)
+ return self._name is None or self._texParameterUpdates or self._deferredUpdates
def _prepareAndBind(self, texUnit=None):
"""Synchronizes the OpenGL texture"""
@@ -200,10 +201,14 @@ class Texture(object):
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_))
+ "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,
@@ -214,14 +219,20 @@ class Texture(object):
0,
format_,
type_,
- data)
+ 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_))
+ "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,
@@ -233,32 +244,37 @@ class Texture(object):
0,
format_,
type_,
- data)
+ 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)
+ 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)
+ 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 = []
@@ -340,7 +356,7 @@ class Texture(object):
:param bool copy:
True (default) to copy data, False to use as is (do not modify)
"""
- data = numpy.array(data, copy=copy, order='C')
+ data = numpy.array(data, copy=copy, order="C")
offset = tuple(offset)
assert data.ndim == self.ndim
diff --git a/src/silx/gui/_glutils/VertexBuffer.py b/src/silx/gui/_glutils/VertexBuffer.py
index 65fff86..d71bbeb 100644
--- a/src/silx/gui/_glutils/VertexBuffer.py
+++ b/src/silx/gui/_glutils/VertexBuffer.py
@@ -50,15 +50,12 @@ class VertexBuffer(object):
: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):
+ 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
@@ -76,20 +73,14 @@ class VertexBuffer(object):
if data is None:
assert size is not None
self._size = size
- gl.glBufferData(self._target,
- self._size,
- c_void_p(0),
- self._usage)
+ gl.glBufferData(self._target, self._size, c_void_p(0), self._usage)
else:
- data = numpy.array(data, copy=False, order='C')
+ 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.glBufferData(self._target, self._size, data, self._usage)
gl.glBindBuffer(self._target, 0)
@@ -109,8 +100,10 @@ class VertexBuffer(object):
if self._name is not None:
return self._name
else:
- raise RuntimeError("No OpenGL buffer resource, \
- discard has already been called")
+ raise RuntimeError(
+ "No OpenGL buffer resource, \
+ discard has already been called"
+ )
@property
def size(self):
@@ -118,8 +111,10 @@ class VertexBuffer(object):
if self._size is not None:
return self._size
else:
- raise RuntimeError("No OpenGL buffer resource, \
- discard has already been called")
+ raise RuntimeError(
+ "No OpenGL buffer resource, \
+ discard has already been called"
+ )
def bind(self):
"""Bind the vertex buffer"""
@@ -132,7 +127,7 @@ class VertexBuffer(object):
: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')
+ data = numpy.array(data, copy=False, order="C")
if size is None:
size = data.nbytes
assert offset + size <= self.size
@@ -172,14 +167,9 @@ class VertexBufferAttrib(object):
_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):
+ 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_
@@ -201,21 +191,25 @@ class VertexBufferAttrib(object):
"""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))
+ 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)
+ 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):
@@ -241,7 +235,7 @@ def vertexBuffer(arrays, prefix=None, suffix=None, usage=None):
suffix = (0,) * len(arrays)
for data, pre, post in zip(arrays, prefix, suffix):
- data = numpy.array(data, copy=False, order='C')
+ data = numpy.array(data, copy=False, order="C")
shape = data.shape
assert len(shape) <= 2
type_ = numpyToGLType(data.dtype)
@@ -250,8 +244,7 @@ def vertexBuffer(arrays, prefix=None, suffix=None, usage=None):
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))
+ info.append((data, type_, size, dimension, vbosize, sizeinbytes, copyoffset))
vbosize += sizeinbytes
vbo = VertexBuffer(size=vbosize, usage=usage)
@@ -260,6 +253,5 @@ def vertexBuffer(arrays, prefix=None, suffix=None, usage=None):
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))
+ result.append(VertexBufferAttrib(vbo, type_, size, dimension, offset, 0))
return result
diff --git a/src/silx/gui/_glutils/__init__.py b/src/silx/gui/_glutils/__init__.py
index a7a4bee..9526ba4 100644
--- a/src/silx/gui/_glutils/__init__.py
+++ b/src/silx/gui/_glutils/__init__.py
@@ -1,6 +1,6 @@
# /*##########################################################################
#
-# Copyright (c) 2014-2019 European Synchrotron Radiation Facility
+# Copyright (c) 2014-2022 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
diff --git a/src/silx/gui/_glutils/font.py b/src/silx/gui/_glutils/font.py
index bee9745..4c0268e 100644
--- a/src/silx/gui/_glutils/font.py
+++ b/src/silx/gui/_glutils/font.py
@@ -1,6 +1,6 @@
# /*##########################################################################
#
-# Copyright (c) 2016-2022 European Synchrotron Radiation Facility
+# Copyright (c) 2016-2023 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
@@ -28,162 +28,12 @@ __license__ = "MIT"
__date__ = "13/10/2016"
-import logging
-import numpy
-
from .. import qt
-from ..utils.image import convertQImageToArray
-
-try:
- from ..utils.matplotlib import rasterMathText
-except ImportError:
- rasterMathText = None
-_logger = logging.getLogger(__name__)
+# Expose rasterMathText as part of this module
+from ..utils.matplotlib import rasterMathText as rasterText # noqa
-def getDefaultFontFamily():
+def getDefaultFontFamily() -> str:
"""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 rasterTextQt(text, font, size=-1, weight=-1, italic=False, devicePixelRatio=1.0):
- """Raster text using Qt.
-
- It supports multiple lines.
-
- :param str text: The text to raster
- :param font: Font name or QFont to use
- :type font: str or :class:`QFont`
- :param int size:
- Font size in points
- Used only if font is given as name.
- :param int weight:
- Font weight in [0, 99], see QFont.Weight.
- Used only if font is given as name.
- :param bool italic:
- True for italic font (default: False).
- Used only if font is given as name.
- :param float devicePixelRatio:
- The current ratio between device and device-independent pixel
- (default: 1.0)
- :return: Corresponding image in gray scale and baseline offset from top
- :rtype: (HxW numpy.ndarray of uint8, int)
- """
- if not text:
- _logger.info("Trying to raster empty text, replaced by white space")
- text = " " # Replace empty text by white space to produce an image
-
- if not isinstance(font, qt.QFont):
- font = qt.QFont(font, size, weight, italic)
-
- # 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
- )
- 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/rows but one on each side
- filled_rows = numpy.nonzero(numpy.sum(array, axis=1))[0]
- filled_columns = numpy.nonzero(numpy.sum(array, axis=0))[0]
- if len(filled_rows) == 0 or len(filled_columns) == 0:
- return array, metrics.ascent()
-
- min_row = max(0, filled_rows[0] - 1)
- array = array[
- min_row : filled_rows[-1] + 2,
- max(0, filled_columns[0] - 1) : filled_columns[-1] + 2,
- ]
-
- return array, metrics.ascent() - min_row
-
-
-def rasterText(text, font, size=-1, weight=-1, italic=False, devicePixelRatio=1.0):
- """Raster text using Qt or matplotlib if there may be math syntax.
-
- 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 rasterMathText is not None and text.count("$") >= 2:
- return rasterMathText(text, font, size, weight, italic, devicePixelRatio)
- else:
- return rasterTextQt(text, font, size, weight, italic, devicePixelRatio)
diff --git a/src/silx/gui/_glutils/gl.py b/src/silx/gui/_glutils/gl.py
index fb0a3fa..aff7617 100644
--- a/src/silx/gui/_glutils/gl.py
+++ b/src/silx/gui/_glutils/gl.py
@@ -1,6 +1,6 @@
# /*##########################################################################
#
-# Copyright (c) 2014-2017 European Synchrotron Radiation Facility
+# Copyright (c) 2014-2022 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
@@ -31,13 +31,19 @@ __date__ = "25/07/2016"
from contextlib import contextmanager as _contextmanager
from ctypes import c_uint
import logging
+import sys
+from typing import Optional
+
+from packaging.version import Version
+
_logger = logging.getLogger(__name__)
import OpenGL
+
# Set the following to true for debugging
if _logger.getEffectiveLevel() <= logging.DEBUG:
- _logger.debug('Enabling PyOpenGL debug flags')
+ _logger.debug("Enabling PyOpenGL debug flags")
OpenGL.ERROR_LOGGING = True
OpenGL.ERROR_CHECKING = True
OpenGL.ERROR_ON_COPY = True
@@ -46,8 +52,14 @@ else:
OpenGL.ERROR_CHECKING = False
OpenGL.ERROR_ON_COPY = False
+if sys.version_info >= (3, 12) and Version(OpenGL.__version__) <= Version("3.1.7"):
+ # Python3.12 patch: see https://github.com/mcfletch/pyopengl/pull/100
+ OpenGL.FormatHandler.by_name("ctypesparameter").check.append("_ctypes.CArgObject")
+
+
import OpenGL.GL as _GL
from OpenGL.GL import * # noqa
+import OpenGL.platform
# Extentions core in OpenGL 3
from OpenGL.GL.ARB import framebuffer_object as _FBO
@@ -60,9 +72,22 @@ try:
GLchar
except NameError:
from ctypes import c_char
+
GLchar = c_char
+def getPlatform() -> Optional[str]:
+ """Returns the name of the PyOpenGL class handling the platform.
+
+ E.g., GLXPlatform, EGLPlatform
+ """
+ try:
+ platform = OpenGL.platform.PLATFORM
+ except AttributeError:
+ return None
+ return platform.__class__.__name__
+
+
def getVersion() -> tuple:
"""Returns the GL version as tuple of integers.
@@ -74,7 +99,7 @@ def getVersion() -> tuple:
if isinstance(desc, bytes):
desc = desc.decode("ascii")
version = desc.split(" ", 1)[0]
- return tuple([int(i) for i in version.split('.')])
+ return tuple([int(i) for i in version.split(".")])
except Exception as e:
raise ValueError("GL version not properly formatted") from e
@@ -90,21 +115,23 @@ def testGL() -> bool:
_logger.error("OpenGL version >=2.1 required, running with %s" % version)
return False
- from OpenGL.GL.ARB.framebuffer_object import glInitFramebufferObjectARB
- from OpenGL.GL.ARB.texture_rg import glInitTextureRgARB
+ if major == 2:
+ from OpenGL.GL.ARB.framebuffer_object import glInitFramebufferObjectARB
+ from OpenGL.GL.ARB.texture_rg import glInitTextureRgARB
- if not glInitFramebufferObjectARB():
- _logger.error("OpenGL GL_ARB_framebuffer_object extension required!")
- return False
+ if not glInitFramebufferObjectARB():
+ _logger.error("OpenGL GL_ARB_framebuffer_object extension required!")
+ return False
+
+ if not glInitTextureRgARB():
+ _logger.error("OpenGL GL_ARB_texture_rg extension required!")
+ return False
- if not glInitTextureRgARB():
- _logger.error("OpenGL GL_ARB_texture_rg extension required!")
- return False
return True
# Additional setup
-if hasattr(glget, 'addGLGetConstant'):
+if hasattr(glget, "addGLGetConstant"):
glget.addGLGetConstant(GL_FRAMEBUFFER_BINDING, (1,))
@@ -145,6 +172,7 @@ def disabled(capacity, disable=True):
# Additional OpenGL wrapping
+
def glGetActiveAttrib(program, index):
"""Wrap PyOpenGL glGetActiveAttrib"""
bufsize = glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH)
@@ -158,28 +186,28 @@ def glGetActiveAttrib(program, index):
def glDeleteRenderbuffers(buffers):
- if not hasattr(buffers, '__len__'): # Support single int argument
+ 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
+ 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
+ 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
+ if not hasattr(textures, "__len__"): # Support single int argument
textures = [textures]
length = len(textures)
_GL.glDeleteTextures((c_uint * length)(*textures))
diff --git a/src/silx/gui/_glutils/test/__init__.py b/src/silx/gui/_glutils/test/__init__.py
index 5ad4c28..e9dd44d 100644
--- a/src/silx/gui/_glutils/test/__init__.py
+++ b/src/silx/gui/_glutils/test/__init__.py
@@ -1,6 +1,6 @@
# /*##########################################################################
#
-# Copyright (c) 2015-2022 European Synchrotron Radiation Facility
+# Copyright (c) 2015-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
diff --git a/src/silx/gui/_glutils/test/test_gl.py b/src/silx/gui/_glutils/test/test_gl.py
index be9332b..d719c08 100644
--- a/src/silx/gui/_glutils/test/test_gl.py
+++ b/src/silx/gui/_glutils/test/test_gl.py
@@ -26,11 +26,11 @@ from .. import gl
def test_version_bytes(mocker):
- mocker.patch('silx.gui._glutils.gl.glGetString', return_value=b"3.0 Mock")
+ mocker.patch("silx.gui._glutils.gl.glGetString", return_value=b"3.0 Mock")
assert gl.getVersion() == (3, 0)
def test_version_str(mocker):
"""In case glGetString returns str"""
- mocker.patch('silx.gui._glutils.gl.glGetString', return_value="3.0 Mock")
+ mocker.patch("silx.gui._glutils.gl.glGetString", return_value="3.0 Mock")
assert gl.getVersion() == (3, 0)
diff --git a/src/silx/gui/_glutils/utils.py b/src/silx/gui/_glutils/utils.py
index 49b431a..56ac935 100644
--- a/src/silx/gui/_glutils/utils.py
+++ b/src/silx/gui/_glutils/utils.py
@@ -94,8 +94,9 @@ def segmentTrianglesIntersection(segment, triangles):
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
+ numpy.all(subVolumes >= 0.0, axis=1), # All positive
+ numpy.all(subVolumes <= 0.0, axis=1),
+ ) # All negative
intersect = numpy.where(intersect)[0] # Indices of intersected triangles
# Get barycentric coordinates
@@ -112,7 +113,7 @@ def segmentTrianglesIntersection(segment, triangles):
del volAlpha
del volume
- inSegmentMask = numpy.logical_and(t >= 0., t <= 1.)
+ inSegmentMask = numpy.logical_and(t >= 0.0, t <= 1.0)
intersect = intersect[inSegmentMask]
t = t[inSegmentMask]
barycentric = barycentric[inSegmentMask]