#
# colourbarcanvas.py - Render a colour bar using OpenGL and matplotlib.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :class:`ColourBarCanvas`.
The :class:`ColourBarCanvas` uses a :class:`.ColourBar` draw a colour bar
(with labels), and then renders said colour bar as a texture using OpenGL.
See the :mod:`~fsleyes.controls.colourbar` and
:mod:`fsleyes_widgets.utils.colourbarbitmap` modules for details on how the
colour bar is created.
"""
import logging
import numpy as np
import OpenGL.GL as gl
import fsleyes_props as props
import fsl.utils.idle as idle
import fsleyes.controls.colourbar as cbar
import fsleyes.gl.textures as textures
log = logging.getLogger(__name__)
[docs]class ColourBarCanvas(props.HasProperties):
"""Contains logic to render a colour bar as an OpenGL texture. """
highDpi = props.Boolean(default=False)
"""Scale colour bar canvas for high-resolution screens. """
barSize = props.Percentage(default=100)
"""Size of the colour bar along its major axis, as a proportion of
the available space.
"""
[docs] def __init__(self, overlayList, displayCtx):
"""Adds a few listeners to the properties of this object, to update
the colour bar when they change.
"""
self.__tex = None
self.__name = '{}_{}'.format(self.__class__.__name__, id(self))
self.__cbar = cbar.ColourBar(overlayList, displayCtx)
self.__cbar.register(self.__name, self.updateColourBarTexture)
self.addListener('barSize', self.__name, self.updateColourBarTexture)
self.addListener('highDpi', self.__name, self.__highDpiChanged)
@property
def colourBar(self):
"""Returns a reference to the :class:`.ColourBar` object that actually
generates the colour bar bitmap.
"""
return self.__cbar
[docs] def updateColourBarTexture(self, *a):
"""Called whenever the colour bar texture needs to be updated. """
def update():
self.__genColourBarTexture()
self.Refresh()
name = '{}_updateColourBarTexture'.format(id(self))
idle.idle(update, name=name, skipIfQueued=True)
[docs] def _initGL(self):
"""Called automatically by the OpenGL canvas target superclass (see the
:class:`.WXGLCanvasTarget` and :class:`.OSMesaCanvasTarget` for
details).
Generates the colour bar texture.
"""
self.__genColourBarTexture()
def __highDpiChanged(self, *a):
"""Called when the :attr:`highDpi` property changes. Calls the
:meth:`.GLCanvasTarget.EnableHighDPI` method.
"""
self.EnableHighDPI(self.highDpi)
self.updateColourBarTexture()
[docs] def destroy(self):
"""Should be called when this ``ColourBarCanvas`` is no longer needed.
Destroys the :class:`.Texture2D` and :class:`.ColourBar` instances
used to render the colour bar.
"""
self.__cbar.deregister(self.__name)
self.__cbar.destroy()
if self.__tex is not None:
self.__tex.destroy()
self.removeListener('barSize', self.__name)
self.removeListener('highDpi', self.__name)
self.__tex = None
self.__cbar = None
def __genColourBarTexture(self):
"""Retrieves a colour bar bitmap from the :class:`.ColourBar`, and
copies it to a :class:`.Texture2D`.
"""
if not self._setGLContext():
return
# we may have already
# been destroyed
if self.__cbar is None:
return
w, h = self.GetSize()
if w < 50 or h < 50:
return
if self.__cbar.orientation == 'vertical': h = h * self.barSize / 100.0
else: w = w * self.barSize / 100.0
scale = self.GetScale()
bitmap = self.__cbar.colourBar(w, h, scale)
# The bitmap has shape W*H*4, but
# Texture2D instances need it in
# shape 4*W*H
if bitmap is None:
return
bitmap = np.fliplr(bitmap).transpose([2, 0, 1])
if self.__tex is None:
self.__tex = textures.Texture2D('{}_{}'.format(
type(self).__name__, id(self)), interp=gl.GL_LINEAR)
self.__tex.set(data=bitmap)
[docs] def _draw(self):
"""Renders the colour bar texture using all available canvas space."""
if self.__tex is None or not self._setGLContext():
return
width, height = self.GetScaledSize()
# viewport
gl.glViewport(0, 0, width, height)
gl.glMatrixMode(gl.GL_PROJECTION)
gl.glLoadIdentity()
gl.glOrtho(0, 1, 0, 1, -1, 1)
gl.glMatrixMode(gl.GL_MODELVIEW)
gl.glLoadIdentity()
gl.glClearColor(*self.__cbar.bgColour)
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
gl.glEnable(gl.GL_BLEND)
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
gl.glShadeModel(gl.GL_FLAT)
xmin, xmax = 0, 1
ymin, ymax = 0, 1
off = (100 - self.barSize) / 100.0
if self.colourBar.orientation == 'vertical':
ymin += off / 2.0
ymax -= off / 2.0
else:
xmin += off / 2.0
xmax -= off / 2.0
self.__tex.drawOnBounds(0, xmin, xmax, ymin, ymax, 0, 1)