Home · Tutorial · Classes · Functions · Language · QSA Workbench · Qt Documentation · www.trolltech.com

Filters example

It is a common approach in software development to separate functionallity into different software components. In this article we try to describe how a scripting language combined with good C++ bindings can increase the flexibitly and reuse value of individual software components.

The application used in this article contains a set of processing components used for image filtering. For simplicity we will refer to these components as modules. In this example we provide a module for loading an image from disk and another for displaying the image on screen. In addition we provide some modules to do the processing in between.

What we wish to show with this application is how QSA can be used to script enable software components in such a way that it increases the overall flexibility with a minimum amount of programming effort. This gain comes from using already existing code and simply glue it together using QSA.

The Modules

First we will now go through how the modules in the application work and how they can be used from QSA. For the sake of reference, lets start with a simple example:

    var loader = new ImageSource;
    connect( loader, "output(Data)", Renderer, "input(Data)" );
    loader.file = "myimage.png";

What happens in this script is that we create one module, the ImageSource, used to load an image from disk. In addition we use the global module Renderer, used to display an image on the screen. We set up the chain of processing by connecting the output of the ImageSource to the input of the Renderer. We then set the filename for the ImageSource start processing by calling the function load() in the ImageSource. The ImageSource will then load the image and give the image to the Renderer which displays will it.

The ImageSource class in example above is really a C++ class that has been made available from script using functionallity in QSA. For this to work, the C++ class has be derived from QObject. When this requirement is met all the class' slots and properties are automatically accessible from the scripting language. Also, to be able to instantiate the class from script we also need to implement a subclass of QSObjectFactory that will, on demand from QSA, create an instnace of the module. The implementation of the module factory can be found in the sourcefiles: modulefactory.h and modulefactory.cpp.

When a module receives input via its input slot, it will process that input and send the result to all modules connected to its output signal. It is important to realise that only the setting up of processing chains and configuration of modules are done in the script. The real number crunching is done in C++ which means that the execution speed is limited only by the efficiency of the C++ implementation, while still obtaining the flexibility that a scripting language offers.

An image has been represented using a simple class called Data, as can be seen from the connect statement above. Objects of this class are not used from within the script so we do not provide any bindings for it. It is only visible in the connect statements and within the C++ modules.

A list of the modules implemented in the example and a short description of them follows below. The implementation of the modules can be viewed in modules.h and modules.cpp.

The Application

The application that has been written in this example is very simple if we assume that the modules exists (this is after all the idea, to script enable existing code to gain more flexibility). For user interface we use the QSA Workbench which gives us the possibility to edit and run scripts. In addition we implemented the module factory which exposes the modules.

Below is one of the sample scripts available in the example file main.qs. It uses all the available modules. Below is shown the two images of how the filtering works. Unprocessed (left) and processed (right). The example sets up the processing to first load the image. The image is then sent into the Threshold module which creates the steps in the resulting image. The thresholded image is then sent to the Smudger module where the edges are smoothed out a bit, causing a more gliding transition between the steps in the image. The next step is the BumpMapper module where the light source is positioned in the upper left region of the image causing this area to be illuminated while shadows are cast on the other side. Finally the image reaches the renderer which opens up a window displaying the resulting image.

    function doAll()
        var src = new ImageSource();
        var bump = new BumpMapper();
        var thres = new Threshold();
        var blur = new Smudger();

        connect( src,   'output(Data)', thres.input );
        connect( thres, 'output(Data)', blur.input );
        connect( blur,  'output(Data)', bump.input );
        connect( bump,  'output(Data)', Renderer.input );

        src.file = 'bump.jpg';
        thres.steps = 10;
        bump.smoothness = 4;
        bump.light = new Point( 150, 150 );
        bump.radius = 250;
        blur.iterations = 4;

As a simple exercise, try to change some of the module properties or reconnect them in a different order to see what happens when you run the function again.

Why not use a specialized GUI for this?

At this point one might be thinking that this is no different than the same code written in C++ using the components directly. This is true if one want to recompile for every small change one does, that is, if you have access to source code and a compiler at all.

It would also be possible to provide the same level of functionallity in a graphical application. One could write a configuration dialog for each module and write a widget for doing the connections graphically. Such a GUI takes time to write, and will most likely be task specific. Scripting the same functionallity can be done directly with only a minimal amount of programming, if any at all. There are also some features that are more difficult to reflect in a GUI, such as iterations and conditional execution. These are easily available in a scripting language, but are not trivial to represent in a graphical application.

Another common usage area of application scripting is the on-the-fly customization of existing functionallity, in which case one does not wish to spend time programming user interfaces, but rather hook together the pieces you want with a few scripting statements and let it run. For this purpose scripting is ideal.

Famous last words

We have now shown how wrapping C++ functionallity in QSA increases the flexibility and usability of existing code. We make use of the QSObjectFactory to enable use of C++ classes from script and we use the QSA Workbench to easily get the possility to edit and run scripts. All in all it shows how QSA can improve the functionallity of existsing application code.

Copyright © 2006 Trolltech Trademarks
QSA 1.2.2