Source code for ccpn.ui.gui.lib.OpenGL.CcpnOpenGLABC
"""
By Functionality:
Zoom and pan:
Left-drag: pans the spectrum.
shift-left-drag: draws a zooming box and zooms the viewing window.
shift-middle-drag: draws a zooming box and zooms the viewing window.
shift-right-drag: draws a zooming box and zooms the viewing window.
Two successive shift-right-clicks: define zoombox
control-right click: reset the zoom
Peaks:
Left-click: select peak near cursor in a spectrum display, deselecting others
Control(Cmd)-left-click: (de)select peak near cursor in a spectrum display, adding/removing to selection.
Control(Cmd)-left-drag: selects peaks in an area specified by the dragged region.
Middle-drag: Moves a selected peak.
Control(Cmd)-Shift-Left-click: picks a peak at the cursor position, adding to selection
Control(Cmd)-shift-left-drag: picks peaks in an area specified by the dragged region.
Others:
Right-click: raises the context menu.
By Mouse button:
Left-click: select peak near cursor in a spectrum display, deselecting others
Control(Cmd)-left-click: (de)select peak near cursor in a spectrum display, adding/removing to selection.
Control(Cmd)-Shift-Left-click: picks a peak at the cursor position, adding to selection
Left-drag: pans the spectrum.
shift-left-drag: draws a zooming box and zooms the viewing window.
Control(Cmd)-left-drag: selects peaks in an area specified by the dragged region.
Control(Cmd)-shift-left-drag: picks peaks in an area specified by the dragged region.
shift-middle-drag: draws a zooming box and zooms the viewing window.
Right-click: raises the context menu.
control-right click: reset the zoom
Two successive shift-right-clicks: define zoombox
shift-right-drag: draws a zooming box and zooms the viewing window.
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (http://www.ccpn.ac.uk) 2014 - 2021"
__credits__ = ("Ed Brooksbank, Joanna Fox, Victoria A Higman, Luca Mureddu, Eliza Płoskoń",
"Timothy J Ragan, Brian O Smith, Gary S Thompson & Geerten W Vuister")
__licence__ = ("CCPN licence. See http://www.ccpn.ac.uk/v3-software/downloads/license")
__reference__ = ("Skinner, S.P., Fogh, R.H., Boucher, W., Ragan, T.J., Mureddu, L.G., & Vuister, G.W.",
"CcpNmr AnalysisAssign: a flexible platform for integrated NMR analysis",
"J.Biomol.Nmr (2016), 66, 111-124, http://doi.org/10.1007/s10858-016-0060-y")
#=========================================================================================
# Last code modification
#=========================================================================================
__modifiedBy__ = "$modifiedBy: Ed Brooksbank $"
__dateModified__ = "$dateModified: 2021-09-17 12:27:27 +0100 (Fri, September 17, 2021) $"
__version__ = "$Revision: 3.0.4 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: Ed Brooksbank $"
__date__ = "$Date: 2018-12-20 13:28:13 +0000 (Thu, December 20, 2018) $"
#=========================================================================================
# Start of code
#=========================================================================================
import sys
import re
# from threading import Thread
# from queue import Queue
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QPoint, Qt, pyqtSlot
from PyQt5.QtWidgets import QOpenGLWidget
import numpy as np
from ccpn.ui.gui.guiSettings import CCPNGLWIDGET_BACKGROUND, CCPNGLWIDGET_FOREGROUND, CCPNGLWIDGET_PICKCOLOUR, \
CCPNGLWIDGET_GRID, CCPNGLWIDGET_HIGHLIGHT, \
CCPNGLWIDGET_LABELLING, \
CCPNGLWIDGET_HEXBACKGROUND, CCPNGLWIDGET_ZOOMAREA, CCPNGLWIDGET_PICKAREA, \
CCPNGLWIDGET_SELECTAREA, CCPNGLWIDGET_ZOOMLINE, CCPNGLWIDGET_MOUSEMOVELINE, \
CCPNGLWIDGET_HARDSHADE
from ccpn.ui.gui.lib.OpenGL.CcpnOpenGLNotifier import GLNotifier
from ccpn.ui.gui.lib.OpenGL.CcpnOpenGLGlobal import GLGlobalData
from ccpn.ui.gui.lib.OpenGL.CcpnOpenGLFonts import GLString
from ccpn.ui.gui.lib.OpenGL.CcpnOpenGLArrays import GLRENDERMODE_REBUILD, GLVertexArray
from ccpn.ui.gui.lib.OpenGL.CcpnOpenGLViewports import GLViewports
from ccpn.ui.gui.lib.OpenGL.CcpnOpenGLWidgets import GLExternalRegion
import ccpn.ui.gui.lib.OpenGL.CcpnOpenGLDefs as GLDefs
from ccpn.ui.gui.guiSettings import getColours
from ccpn.ui.gui.lib.OpenGL import GL, GLU, GLUT
UNITS_PPM = 'ppm'
UNITS_HZ = 'Hz'
UNITS_POINT = 'point'
UNITS = [UNITS_PPM, UNITS_HZ, UNITS_POINT]
removeTrailingZero = re.compile(r'^(\d*[\d.]*?)\.?0*$')
[docs]class CcpnGLWidgetABC(QOpenGLWidget):
"""Widget to handle all visible spectra/peaks/integrals/multiplets
"""
AXIS_MARGINRIGHT = 50
AXIS_MARGINBOTTOM = 25
AXIS_LINE = 7
AXIS_OFFSET = 3
AXIS_INSIDE = False
YAXISUSEEFORMAT = False
INVERTXAXIS = True
INVERTYAXIS = True
AXISLOCKEDBUTTON = True
AXISLOCKEDBUTTONALLSTRIPS = True
SPECTRUMXZOOM = 1.0e1
SPECTRUMYZOOM = 1.0e1
SHOWSPECTRUMONPHASING = True
XAXES = GLDefs.XAXISUNITS
YAXES = GLDefs.YAXISUNITS
AXIS_MOUSEYOFFSET = AXIS_MARGINBOTTOM + (0 if AXIS_INSIDE else AXIS_LINE)
def __init__(self, parent=None, mainWindow=None, **kwds):
# add a flag so that scaling cannot be done until the gl attributes are initialised
self.glReady = False
super().__init__(parent=parent)
# flag to display paintGL but keep an empty screen
self._blankDisplay = False
self.mainWindow = mainWindow
if mainWindow:
self.application = mainWindow.application
self.project = mainWindow.application.project
self.current = mainWindow.application.current
else:
self.application = None
self.project = None
self.current = None
self._preferences = self.application.preferences.general
self.globalGL = None
self.setMouseTracking(True) # generate mouse events when button not pressed
# always respond to mouse events
self.setFocusPolicy(Qt.StrongFocus)
# initialise all attributes
self._initialiseAll()
# set a minimum size so that everything resizes nicely
self.setMinimumSize(self.AXIS_MARGINRIGHT + 10, self.AXIS_MARGINBOTTOM + 10)
# set the pyqtsignal responders
self.GLSignals = GLNotifier(parent=self)
self.GLSignals.glEvent.connect(self._glEvent)
def _initialiseAll(self):
"""Initialise all attributes for the display
"""
# if self.glReady: return
self.w = self.width()
self.h = self.height()
self.lastPos = QPoint()
self._mouseX = 0
self._mouseY = 0
self.pixelX = 1.0
self.pixelY = 1.0
self.deltaX = 1.0
self.deltaY = 1.0
# set initial axis limits - should be changed by strip.display..
self.axisL = -1.0
self.axisR = 1.0
self.axisT = 1.0
self.axisB = -1.0
self.storedZooms = []
self.base = None
self.spectrumValues = []
self.highlighted = False
self._drawSelectionBox = False
self._drawMouseMoveLine = False
self._drawDeltaOffset = False
self._selectionMode = 0
self._startCoordinate = None
self._endCoordinate = None
self.cursorCoordinate = np.zeros((4,), dtype=np.float32)
self._shift = False
self._command = False
self._key = ''
self._isSHIFT = ''
self._isCTRL = ''
self._isALT = ''
self._isMETA = ''
self.buildMarks = True
self._marksList = None
self._infiniteLines = []
self._regionList = None
self._orderedAxes = None
self._axisOrder = None
self._axisCodes = None
self._refreshMouse = False
self._successiveClicks = None # GWV: Store successive click events for zooming; None means first click not set
self._dottedCursorCoordinate = None
self._dottedCursorVisible = None
self.axesChanged = False
self.axisLabelling = {'0': [], '1': []}
self.gridList = []
self._gridVisible = True
self._sideBandsVisible = True
self._crosshairVisible = True
self._spectrumBordersVisible = True
self._axesVisible = True
self._aspectRatioMode = 0
self._showSpectraOnPhasing = False
self._xUnits = 0
self._yUnits = 0
self._drawRightAxis = True
self._drawBottomAxis = True
self._fullHeightRightAxis = False
self._fullWidthBottomAxis = False
self.modeDecimal = [False, False]
# here for completeness, although they should be updated in rescale
self._currentView = GLDefs.MAINVIEW
self._currentRightAxisView = GLDefs.RIGHTAXIS
self._currentRightAxisBarView = GLDefs.RIGHTAXISBAR
self._currentBottomAxisView = GLDefs.BOTTOMAXIS
self._currentBottomAxisBarView = GLDefs.BOTTOMAXISBAR
self._oldStripIDLabel = None
self.stripIDString = None
self._spectrumSettings = {}
self._newStripID = False
self._setColourScheme()
self._gridVisible = self._preferences.showGrid
self._sideBandsVisible = self._preferences.showSideBands
self._updateHTrace = False
self._updateVTrace = False
self._lastTracePoint = {} # [-1, -1]
self._applyXLimit = self._preferences.zoomXLimitApply
self._applyYLimit = self._preferences.zoomYLimitApply
self._intensityLimit = self._preferences.intensityLimit
self._GLIntegralLists = {}
self._GLIntegralLabels = {}
self._marksAxisCodes = []
self._regions = []
self._infiniteLines = []
self._buildTextFlag = True
self._buildMouse = True
self._mouseCoords = [-1.0, -1.0]
self.mouseString = None
# self.diffMouseString = None
self._symbolLabelling = 0
self._contourList = {}
self._uPMatrix = np.zeros((16,), dtype=np.float32)
self._uMVMatrix = np.zeros((16,), dtype=np.float32)
self._uVMatrix = np.zeros((16,), dtype=np.float32)
self._dataMatrix = np.zeros((16,), dtype=np.float32)
self._aMatrix = np.zeros((16,), dtype=np.float32)
self._IMatrix = np.zeros((16,), dtype=np.float32)
self._IMatrix[0:16] = [1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0]
self.vInv = None
self.mouseTransform = None
self._useTexture = np.zeros((1,), dtype=np.int)
self._axisScale = np.zeros((4,), dtype=np.float32)
self._background = np.zeros((4,), dtype=np.float32)
self._parameterList = np.zeros((4,), dtype=np.int32)
# self._view = np.zeros((4,), dtype=np.float32)
self.cursorCoordinate = np.zeros((4,), dtype=np.float32)
self.resetRangeLimits()
self._ordering = []
self._visibleOrdering = []
self._visibleOrderingDict = {}
self._visibleOrderingAxisCodes = ()
self._tilePosition = (0, 0)
[docs] def refreshDevicePixelRatio(self):
"""refresh the devicePixelRatio for the viewports
"""
newPixelRatio = self.devicePixelRatioF()
if newPixelRatio != self.lastPixelRatio:
self.lastPixelRatio = newPixelRatio
if hasattr(self, GLDefs.VIEWPORTSATTRIB):
self.viewports.devicePixelRatio = newPixelRatio
self.update()
[docs] def resetRangeLimits(self, allLimits=True):
# reset zoom limits for the display
self._minXRange, self._maxXRange = GLDefs.RANGELIMITS
self._minYRange, self._maxYRange = GLDefs.RANGELIMITS
self._maxX, self._minX = GLDefs.AXISLIMITS
self._maxY, self._minY = GLDefs.AXISLIMITS
if allLimits:
self._rangeXDefined = False
self._rangeYDefined = False
self._minXReached = False
self._minYReached = False
self._maxXReached = False
self._maxYReached = False
self._minReached = False
self._maxReached = False
def _setColourScheme(self):
"""Update colours from colourScheme
"""
self.colours = getColours()
self.hexBackground = self.colours[CCPNGLWIDGET_HEXBACKGROUND]
self.background = self.colours[CCPNGLWIDGET_BACKGROUND]
self.foreground = self.colours[CCPNGLWIDGET_FOREGROUND]
self.mousePickColour = self.colours[CCPNGLWIDGET_PICKCOLOUR]
self.gridColour = self.colours[CCPNGLWIDGET_GRID]
self.highlightColour = self.colours[CCPNGLWIDGET_HIGHLIGHT]
self._labellingColour = self.colours[CCPNGLWIDGET_LABELLING]
self.zoomAreaColour = self.colours[CCPNGLWIDGET_ZOOMAREA]
self.pickAreaColour = self.colours[CCPNGLWIDGET_PICKAREA]
self.selectAreaColour = self.colours[CCPNGLWIDGET_SELECTAREA]
self.zoomLineColour = self.colours[CCPNGLWIDGET_ZOOMLINE]
self.mouseMoveLineColour = self.colours[CCPNGLWIDGET_MOUSEMOVELINE]
self.zoomAreaColourHard = (*self.colours[CCPNGLWIDGET_ZOOMAREA][0:3], CCPNGLWIDGET_HARDSHADE)
self.pickAreaColourHard = (*self.colours[CCPNGLWIDGET_PICKAREA][0:3], CCPNGLWIDGET_HARDSHADE)
self.selectAreaColourHard = (*self.colours[CCPNGLWIDGET_SELECTAREA][0:3], CCPNGLWIDGET_HARDSHADE)
@pyqtSlot(dict)
def _glEvent(self, aDict):
"""process events from the application/popups and other strips
:param aDict - dictionary containing event flags:
"""
if aDict:
if aDict[GLNotifier.GLSOURCE] != self:
# check the params for actions and update the display
triggers = aDict[GLNotifier.GLTRIGGERS]
targets = aDict[GLNotifier.GLTARGETS]
if triggers or targets:
if GLNotifier.GLPREFERENCES in triggers:
self._preferencesUpdate()
self._rescaleXAxis(update=False)
self.stripIDString.renderMode = GLRENDERMODE_REBUILD
# repaint
self.update()
def _preferencesUpdate(self):
"""update GL values after the preferences have changed
"""
self._setColourScheme()
self.setBackgroundColour(self.background)
[docs] def initializeGL(self):
# GLversionFunctions = self.context().versionFunctions()
# GLversionFunctions.initializeOpenGLFunctions()
# self._GLVersion = GLversionFunctions.glGetString(GL.GL_VERSION)
# initialise a common to all OpenGL windows
self.globalGL = GLGlobalData(parent=self, mainWindow=self.mainWindow)
# initialise the arrays for the grid and axes
self.gridList = []
for li in range(3):
self.gridList.append(GLVertexArray(numLists=1,
renderMode=GLRENDERMODE_REBUILD,
blendMode=False,
drawMode=GL.GL_LINES,
dimension=2,
GLContext=self))
self._externalRegions = GLExternalRegion(project=self.project, GLContext=self, spectrumView=None,
integralListView=None)
self._selectionBox = GLVertexArray(numLists=1,
renderMode=GLRENDERMODE_REBUILD,
blendMode=True,
drawMode=GL.GL_QUADS,
dimension=3,
GLContext=self)
self._selectionOutline = GLVertexArray(numLists=1,
renderMode=GLRENDERMODE_REBUILD,
blendMode=True,
drawMode=GL.GL_LINES,
dimension=3,
GLContext=self)
self.viewports = GLViewports()
self._screenChanged()
# define the main viewports
self.viewports.addViewport(GLDefs.MAINVIEW, self, (0, 'a'), (self.AXIS_MARGINBOTTOM, 'a'),
(-self.AXIS_MARGINRIGHT, 'w'), (-self.AXIS_MARGINBOTTOM, 'h'))
self.viewports.addViewport(GLDefs.MAINVIEWFULLWIDTH, self, (0, 'a'), (self.AXIS_MARGINBOTTOM, 'a'),
(0, 'w'), (-self.AXIS_MARGINBOTTOM, 'h'))
self.viewports.addViewport(GLDefs.MAINVIEWFULLHEIGHT, self, (0, 'a'), (0, 'a'),
(-self.AXIS_MARGINRIGHT, 'w'), (0, 'h'))
# define the viewports for the right axis bar
self.viewports.addViewport(GLDefs.RIGHTAXIS, self, (-(self.AXIS_MARGINRIGHT + self.AXIS_LINE), 'w'),
(self.AXIS_MARGINBOTTOM, 'a'),
(self.AXIS_LINE, 'a'), (-self.AXIS_MARGINBOTTOM, 'h'))
self.viewports.addViewport(GLDefs.RIGHTAXISBAR, self, (-self.AXIS_MARGINRIGHT, 'w'),
(self.AXIS_MARGINBOTTOM, 'a'),
(0, 'w'), (-self.AXIS_MARGINBOTTOM, 'h'))
self.viewports.addViewport(GLDefs.FULLRIGHTAXIS, self, (-(self.AXIS_MARGINRIGHT + self.AXIS_LINE), 'w'),
(0, 'a'),
(self.AXIS_LINE, 'a'), (0, 'h'))
self.viewports.addViewport(GLDefs.FULLRIGHTAXISBAR, self, (-self.AXIS_MARGINRIGHT, 'w'), (0, 'a'),
(0, 'w'), (0, 'h'))
# define the viewports for the bottom axis bar
self.viewports.addViewport(GLDefs.BOTTOMAXIS, self, (0, 'a'), (self.AXIS_MARGINBOTTOM, 'a'),
(-self.AXIS_MARGINRIGHT, 'w'), (self.AXIS_LINE, 'a'))
self.viewports.addViewport(GLDefs.BOTTOMAXISBAR, self, (0, 'a'), (0, 'a'),
(-self.AXIS_MARGINRIGHT, 'w'), (self.AXIS_MARGINBOTTOM, 'a'))
self.viewports.addViewport(GLDefs.FULLBOTTOMAXIS, self, (0, 'a'), (self.AXIS_MARGINBOTTOM, 'a'),
(0, 'w'), (self.AXIS_LINE, 'a'))
self.viewports.addViewport(GLDefs.FULLBOTTOMAXISBAR, self, (0, 'a'), (0, 'a'),
(0, 'w'), (self.AXIS_MARGINBOTTOM, 'a'))
# define the full viewport
self.viewports.addViewport(GLDefs.FULLVIEW, self, (0, 'a'), (0, 'a'), (0, 'w'), (0, 'h'))
# # define the remaining corner
# self.viewports.addViewport(GLDefs.AXISCORNER, self, (-self.AXIS_MARGINRIGHT, 'w'), (0, 'a'), (0, 'w'), (self.AXIS_MARGINBOTTOM, 'a'))
# set strings for the overlay text
self.stripIDString = GLString(text='', font=self.globalGL.glSmallFont, x=0, y=0, GLContext=self, obj=None)
# This is the correct blend function to ignore stray surface blending functions
GL.glBlendFuncSeparate(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA, GL.GL_ONE, GL.GL_ONE)
self.setBackgroundColour(self.background)
_shader = self.globalGL._shaderProgramTex
_shader.makeCurrent()
_shader.setBlendEnabled(False)
_shader.setAlpha(1.0)
self.glReady = True
[docs] def paintGL(self):
w = self.w
h = self.h
GL.glClear(GL.GL_COLOR_BUFFER_BIT)
if self._blankDisplay:
return
# stop notifiers interfering with paint event
self.project.blankNotification()
currentShader = self.globalGL._shaderProgram1.makeCurrent()
# start with the grid mapped to (0..1, 0..1) to remove zoom errors here
currentShader.setProjectionAxes(self._uPMatrix, 0.0, 1.0, 0.0, 1.0, -1.0, 1.0)
currentShader.setPMatrix(self._uPMatrix)
# draw the grid components
# self.drawGrid()
# set the scale to the axis limits, needs addressing correctly, possibly same as grid
currentShader.setProjectionAxes(self._uPMatrix, self.axisL, self.axisR, self.axisB,
self.axisT, -1.0, 1.0)
currentShader.setPMatrix(self._uPMatrix)
# draw the spectra, need to reset the viewport
self.viewports.setViewport(self._currentView)
# change to the text shader
currentShader = self.globalGL._shaderProgramTex.makeCurrent()
currentShader.setProjectionAxes(self._uPMatrix, self.axisL, self.axisR, self.axisB, self.axisT, -1.0, 1.0)
currentShader.setPTexMatrix(self._uPMatrix)
self._axisScale[0:4] = [self.pixelX, self.pixelY, 1.0, 1.0]
currentShader.setAxisScale(self._axisScale)
self.enableTexture()
currentShader = self.globalGL._shaderProgram1.makeCurrent()
currentShader.setMVMatrix(self._IMatrix)
currentShader.setProjectionAxes(self._uPMatrix, 0.0, 1.0, 0.0, 1.0, -1.0, 1.0)
currentShader.setPMatrix(self._uPMatrix)
self.drawSelectionBox()
self.drawCursors()
self.globalGL._shaderProgramTex.makeCurrent()
self._setViewPortFontScale()
self.drawMouseCoords()
# make the overlay/axis solid
self.globalGL._shaderProgramTex.setBlendEnabled(False)
self.drawOverlayText()
self.drawAxisLabels()
self.globalGL._shaderProgramTex.setBlendEnabled(True)
self.disableTexture()
# use the current viewport matrix to display the last bit of the axes
currentShader = self.globalGL._shaderProgram1.makeCurrent()
currentShader.setProjectionAxes(self._uVMatrix, 0, w - self.AXIS_MARGINRIGHT, -1, h - self.AXIS_MOUSEYOFFSET,
-1.0, 1.0)
self.viewports.setViewport(self._currentView)
# why are these labelled the other way round?
currentShader.setPMatrix(self._uVMatrix)
currentShader.setMVMatrix(self._IMatrix)
# cheat for the moment to draw the axes (if visible)
if self.highlighted:
colour = self.highlightColour
else:
colour = self.foreground
GL.glDisable(GL.GL_BLEND)
GL.glColor4f(*colour)
GL.glBegin(GL.GL_LINES)
if self._drawBottomAxis:
GL.glVertex2d(0, 0)
GL.glVertex2d(w - self.AXIS_MARGINRIGHT, 0)
if self._drawRightAxis:
GL.glVertex2d(w - self.AXIS_MARGINRIGHT, 0)
GL.glVertex2d(w - self.AXIS_MARGINRIGHT, h - self.AXIS_MOUSEYOFFSET)
GL.glEnd()
# re-enable notifiers
self.project.unblankNotification()
[docs] def setBackgroundColour(self, col, silent=False):
"""
set all background colours in the shaders
:param col - vec4, 4 element list e.g.: [0.05, 0.05, 0.05, 1.0], very dark gray
"""
self.makeCurrent()
GL.glClearColor(*col)
self.background = col
self.globalGL._shaderProgramTex.makeCurrent()
self.globalGL._shaderProgramTex.setBackground(self.background)
self.globalGL._shaderProgramAlias.makeCurrent()
self.globalGL._shaderProgramAlias.setBackground(self.background)
self.globalGL._shaderProgramTexAlias.makeCurrent()
self.globalGL._shaderProgramTexAlias.setBackground(self.background)
if not silent:
self.update()
self.doneCurrent()
[docs] def enableTexture(self):
GL.glEnable(GL.GL_BLEND)
# GL.glEnable(GL.GL_TEXTURE_2D)
# GL.glBindTexture(GL.GL_TEXTURE_2D, self.globalGL.glSmallFont.textureId)
GL.glActiveTexture(GL.GL_TEXTURE0)
GL.glBindTexture(GL.GL_TEXTURE_2D, self.getSmallFont()._parent.textureId)
# GL.glActiveTexture(GL.GL_TEXTURE1)
# GL.glBindTexture(GL.GL_TEXTURE_2D, self.getSmallFont(transparent=True).textureId)
# # specific blend function for text overlay
# GL.glBlendFuncSeparate(GL.GL_SRC_ALPHA, GL.GL_DST_COLOR, GL.GL_ONE, GL.GL_ONE)
# # reset blend function
# GL.glBlendFuncSeparate(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA, GL.GL_ONE, GL.GL_ONE)