This example will demonstrate how to use the post-processing framework of Cornerstone. The post-processing framework is used to render the application to an auxiliary buffer (instead of the normal back buffer) and then filtered with image processing techniques to achieve different post-processing effects. This example shows one such effect where the application is slowly faded to black-and-white when idle and then animated back to full-color when one of the widgets is touched.
The post-processing framework functions with a set of post-process filters that form an ordered chain. When using post-processing the application is rendered using each filter in the chain sequentially. The original scene is rendered using the first filter and the result of that filtering is used as a source image for the next filter and so on. After the last filter the image is rendered to the back buffer and shown on the screen.
A custom filter can be implemented by deriving from the base class Luminous::PostProcessFilter and using a custom shader to perform the filtering. For this example we create a class called BlackWhiteFilter that derives from Luminous::PostProcessFilter and overrides the filter function. Shown here is the header file containing the class declaration:
The implementation of the class can be seen below:
The most important part of the filtering is the custom shader. The custom shader can be used to read the captured source image and to output filtered pixel values. The basic procedure for loading a custom shader is to load it from the disk and specifying the vertex description, shown below:
Custom shaders are described in more detail in the AdvancedRendering example. For reference both the vertex and fragment shaders are shown below. The vertex shader only performs the basic transformations while the fragment shader contain most of the relevant code for this example.
The vertex shader:
And the fragment shader:
In the C++ code the we need to pass the texture and uniform values that govern the behaviour of the shader. These are done in the filter function:
Here the style parameter is used for the filtering so we need to pass in the shader program and all uniforms. We calculate the variable t as a floating point value that specifies the ratio of 'colorness' or color saturation, more specifically the weight factor when performing linear interpolation between the luminance of a pixel and the original color of the pixel. The value 0.0 means full color and 1.0 means completely black-and-white. Note that the style already has the source texture attached with the name "tex", but the texture can also be manually retrieved from the ctx.texture() method.
In the last step we call the base implementation PostProcessFilter::filter. This will render a context sized quad using the specified style. The filter function is called by the render thread once for every area in the screen configuration.
In the fragment shader we first specify the uniform values we are using:
In the main body of the shader we first retrieve the original color value of the pixel from the texture sampler tex:
We then calculate the luma of the pixel:
Luma represents the achromatic element, or in other words the brightness, of an image. By calculating the luma value for each pixel in the image and using the luma value in the red, green and blue channels we can effectively convert the color of an image to a black-and-white version. The formula used in converting the color to luma values is Y' = 0.2126 R' + 0.7152 G' + 0.0722 B' and is specified in ITU-R Recommendation BT. 709.
Finally, we use the ratio we calculated previously to blend the color image and the black-and-white image:
In the main application we still need to tell the application to use our black-and-white filter and to reset the timestamp that defines the blending factor of the filter.
For implementing the reset function we use the built-in messaging system and define a eventProcess function that sets the timestamp to the near future whenever it recieves the "reset" message:
For actually using the filter we need to create an instance of the class and tell the application to use it. New filters can be added at any time after the application has been initialized:
For resetting the timestamp of the filter we add an event listener to each widget that is created in the application and setting the filter as the receiving node:
This will send a "reset" message to the filter every time any of the widgets is interacted with.
The rest of the main function is not explained here because the same code is used in the HelloImages example and is discussed in more detail there. For reference the main function is fully included below: