#
# filter.py -
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :class:`Filter` class, which provides an interface
to loading and running simple filter shader programs, which require a
:class:`.Texture2D` as their input.
"""
import collections
import OpenGL.GL as gl
import fsleyes.gl as fslgl
import fsleyes.gl.shaders as shaders
GL14_CONSTANTS = collections.defaultdict(dict, {
'smooth' : ['kernSize']
})
"""This dictionary contains the names of any constant parameters that are
required by GL14 filter implementations. It is used by the :meth:`Filter.set`
method.
The :meth:`Filter.set` method allows both uniform and constant paramters to be
updated, but when a constant parameter is updated, the shader needs to be
recompiled.
"""
[docs]class Filter(object):
"""A ``Filter`` object encapsulates a shader program which applies some
sort of image filter to a :class:`.Texture2D`.
All filters use the same vertex shader, which is called
``filter_vert.glsl`` or ``filter_vert.prog``.
Filter fragment shaders are assumed to have the name
``filter_[name]_frag.glsl`` or ``filter_[name]_frag.prog``, where
``[name]`` is the name of the filter that can be passed to
:meth:`__init__`.
Filter fragment shaders must define the following varyings/uniforms/
attributes:
- ``texture`` - the 2D texture which contains the filter input
- ``fragTexCoord`` - The texture coordinate, as passed through from the
vertex shader.
Other settable filter parameters must be declared as uniforms, and can be
set via the :meth:`set` method.
"""
[docs] def __init__(self, filterName, texture):
"""Create a ``Filter``.
:arg filterName: Name of the filter to create.
:arg texture: Number of the texture unit that the filter input
texture will be bound to. This must be specified
when the shader program is compiled, to support
OpenGL 1.4.
"""
basename = filterName
filterName = 'filter_{}'.format(filterName)
vertSrc = shaders.getVertexShader( 'filter')
fragSrc = shaders.getFragmentShader(filterName)
self.__texture = texture
self.__basename = basename
if float(fslgl.GL_COMPATIBILITY) >= 2.1:
self.__shader = shaders.GLSLShader(vertSrc, fragSrc)
else:
constants = {n : 1 for n in GL14_CONSTANTS[basename]}
self.__shader = shaders.ARBPShader(
vertSrc,
fragSrc,
shaders.getShaderDir(),
{'texture' : texture},
constants=constants)
[docs] def destroy(self):
"""Must be called when this ``Filter`` is no longer needed. Destroys
the shader program.
"""
self.__shader.destroy()
self.__shader = None
[docs] def set(self, **kwargs):
"""Set filter parameters. This method must be called before
:meth:`apply` or :meth:`osApply` can be called, even if no parameters
need to be set.
The filter parameters vary depending on the specific filter that is
used.
"""
shader = self.__shader
texture = self.__texture
basename = self.__basename
shader.load()
kwargs = dict(kwargs)
glver = float(fslgl.GL_COMPATIBILITY)
needRecompile = False
if glver >= 2.1:
kwargs['texture'] = texture
for name, value in kwargs.items():
if glver >= 2.1:
shader.set(name, value)
else:
if name in GL14_CONSTANTS[basename]:
needRecompile = (needRecompile or
shader.setConstant(name, value))
else:
shader.setFragParam(name, value)
if needRecompile:
shader.recompile()
shader.unload()
[docs] def apply(self,
source,
zpos,
xmin,
xmax,
ymin,
ymax,
xax,
yax,
xform=None,
**kwargs):
"""Apply the filter to the given ``source`` texture, and render the
results according to the given bounds.
:arg source: :class:`.Texture2D` instance to apply the filter to
:arg zpos: Position along the Z axis, in the display coordinate
system.
:arg xmin: Minimum X axis coordinate.
:arg xmax: Maximum X axis coordinate.
:arg ymin: Minimum Y axis coordinate.
:arg ymax: Maximum Y axis coordinate.
:arg xax: Display space axis which maps to the horizontal
screen axis.
:arg yax: Display space axis which maps to the vertical screen
axis.
:arg xform: Transformation matrix to appply to vertices.
All other keyword arguments are passed through to the
:meth:`.Texture2D.draw` method of the ``source`` texture.
"""
shader = self.__shader
vertices = source.generateVertices(
zpos, xmin, xmax, ymin, ymax, xax, yax, xform)
texCoords = source.generateTextureCoords()
shader.load()
shader.loadAtts()
shader.setAtt('texCoord', texCoords)
if float(fslgl.GL_COMPATIBILITY) >= 2.1:
shader.setAtt('vertex', vertices)
source.draw(**kwargs)
else:
source.draw(vertices=vertices, **kwargs)
shader.unloadAtts()
shader.unload()
[docs] def osApply(self, source, dest, clearDest=True, **kwargs):
"""Apply the filter to the given ``source`` texture, rendering
the results to the given ``dest`` texture.
This method can be used for ping-ponging, by using two
:class:`.RenderTexture` objects, and swapping the ``source`` and
``dest`` parameters on each iteration.
:arg source: :class:`.Texture2D` instance to apply the filter to
:arg dest: :class:`.RenderTexture` instance to render the result
to
:arg clearDest: If ``True`` (the default), the ``dest`` texture is
cleared before the draw.
All other arguments are passed to the :meth:`apply` method.
"""
with dest.target(0, 1, (0, 0, 0), (1, 1, 1)):
if clearDest:
gl.glClear(gl.GL_COLOR_BUFFER_BIT |
gl.GL_DEPTH_BUFFER_BIT |
gl.GL_STENCIL_BUFFER_BIT)
self.apply(source, 0.5, 0, 1, 0, 1, 0, 1, **kwargs)