# Encoding: UTF-8
"""A Layer that provides some bling
With EffectLayer, it is possible to colorize, fade, or pixelate groups of
graphics objects.
This wors by rendering the layer's contents to a texture, and then drawing the
texture with effects applied.
Currently, it needs the Framebuffer Object (FBO) OpenGL extension to work.
.. note::
This code is experimental. Expect it to not work.
"""
from pyglet import gl
from gillcup import properties
from gillcup_graphics import Layer
from gillcup_graphics.offscreen import fbo
from gillcup_graphics.mainwindow import Window
from gillcup_graphics import objects
[docs]class EffectLayer(Layer):
"""A Layer that can colorize, fade, or pixelate its contents as a whole
"""
color = red, green, blue = objects.color_property
opacity = objects.opacity_property
mosaic = mosaic_x, mosaic_y = properties.ScaleProperty(2,
docstring=u"""Pixelation of this layer
For example, if mosaic=(2, 4), the layer will be drawn using 2×4 blocks
""")
_opacity_data = None
[docs] def need_offscreen(self):
"""Return true if off-screen rendering is needed
Off-screen rendering is only done if needed, i.e. if ``color``,
``opacity`` or ``mosaic`` don't have their default values.
Subclasses should extend this method if they need off-screen
rendering in more circumstances.
"""
return (not all(0.99 < n < 1.01 for n in self.mosaic) or
self.opacity < 0.99 or
not all(0.99 < c < 1.01 for c in self.color)
)
def draw(self, window, transformation, **kwargs):
if window and self.need_offscreen():
parent_texture_size = kwargs.get('parent_texture_size')
if parent_texture_size:
parent_width, parent_height = parent_texture_size
else:
parent_width = window.width
parent_height = window.height
width = max(1, int(parent_width / max(1, self.mosaic_x)))
height = max(1, int(parent_height / max(1, self.mosaic_y)))
if self._opacity_data:
framebuffer, w, h = self._opacity_data
else:
framebuffer = w = h = None
if (w, h) != (width, height):
if framebuffer:
framebuffer.destroy()
framebuffer = fbo.FBO(width, height)
self._opacity_data = framebuffer, width, height
kwargs['parent_texture_size'] = width, height
with framebuffer.bind_draw() as parent_framebuffer:
gl.glClearColor(0, 0, 0, 0)
gl.glClear(gl.GL_COLOR_BUFFER_BIT)
with transformation.state:
super(EffectLayer, self).draw(window=window,
transformation=transformation, **kwargs)
self.blit_buffer(
framebuffer=framebuffer,
parent_framebuffer=parent_framebuffer,
width=width,
height=height,
parent_width=parent_width,
parent_height=parent_height,
window=window,
transformation=transformation,
**kwargs)
else:
if self._opacity_data:
framebuffer, w, h = self._opacity_data
framebuffer.destroy()
self._opacity_data = None
super(EffectLayer, self).draw(window=window,
transformation=transformation, **kwargs)
def blit_buffer(self, framebuffer, parent_width, parent_height, **kwargs):
"""Draw the texture into the parent scene
.. warning:
This method's arguments are not part of the API yet and may change
at any time.
"""
gl.glViewport(0, 0, parent_width, parent_height)
gl.glTexParameteri(gl.GL_TEXTURE_2D,
gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST)
gl.glBindTexture(gl.GL_TEXTURE_2D, framebuffer.texture_id)
gl.glEnable(gl.GL_TEXTURE_2D)
gl.glColor4fv((gl.GLfloat * 4)(*self.color + (self.opacity, )))
gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA) # premultipl.
gl.glBegin(gl.GL_TRIANGLE_STRIP)
gl.glTexCoord2f(0, 0)
gl.glVertex2i(0, 0)
gl.glTexCoord2f(0, parent_height)
gl.glVertex2i(0, parent_height)
gl.glTexCoord2f(parent_width, 0)
gl.glVertex2i(parent_width, 0)
gl.glTexCoord2f(parent_width, parent_height)
gl.glVertex2i(parent_width, parent_height)
gl.glEnd()
gl.glTexParameteri(gl.GL_TEXTURE_2D,
gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
gl.glDisable(gl.GL_TEXTURE_2D)
gl.glTexParameteri(gl.GL_TEXTURE_2D,
gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST)
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
gl.glViewport(0, 0, parent_width, parent_height)
def _blit_buffer_direct(self, framebuffer, parent_framebuffer,
width, height, parent_width, parent_height,
transformation, **kwargs):
# XXX: Use this instead of blit_buffer when we don't need
# colorizing/opacity
transformation.reset()
gl.glViewport(0, 0, width, height)
gl.glBindFramebufferEXT(gl.GL_READ_FRAMEBUFFER_EXT,
framebuffer.framebuffer_id)
gl.glReadBuffer(gl.GL_COLOR_ATTACHMENT0_EXT)
gl.glBindFramebufferEXT(gl.GL_DRAW_FRAMEBUFFER_EXT,
parent_framebuffer.framebuffer_id)
gl.glBlitFramebufferEXT(0, 0, width, height, 0, 0,
parent_width, parent_height,
gl.GL_COLOR_BUFFER_BIT, gl.GL_NEAREST)
gl.glDisable(gl.GL_TEXTURE_2D)
[docs]class RecordingLayer(EffectLayer):
"""A layer that records its contents as an image
After this layer is drawn, the picture is available in the ``last_image``
attribute as a pyglet ImageData object.
"""
last_image = None
def need_offscreen(self):
return True
def blit_buffer(self, framebuffer, **kwargs):
self.last_image = framebuffer.get_image_data()
super(RecordingLayer, self).blit_buffer(framebuffer=framebuffer,
**kwargs)
[docs] def get_image(self, width, height):
"""Draw this layer in a new invisible window and return the ImageData
"""
window = Window(self, width=width, height=height, visible=False)
window.manual_draw()
return self.last_image