Developer Information about

This section describes the high-level architecture of . The architecture is very simple yet very powerful for dynamic selection. It was strongly inspired by the pattern behind the class java.security.Provider from the Java language. This document is separated in two distinct sections, each dealing with a different view of the library, one from the user point of view, the other from the developer's.

  1. General Architecture
  2. Adding Algorithms

1 - General Architecture

The general architecture of is composed of five major actors :

  1. class Halftoner
  2. class HalftonerFactory
  3. class Instanciator
  4. class HalftonerSpec
  5. class Image

Each of these actor is described in its own section. Each description also includes the relationship with the other classes. Please note that the library has its own namespace (halftoner) that should be taken into account. The code examples below do not take the namespace into account, not because it was forgotten, but for clarity.

1.1 - Class Halftoner

Class Halftoner provides a common interface for all implementation of halftoning algorithms. It defines only one method that allows the halftoning to take place. Halftoner objects should rarely be created, they should be requested by the provider class HalftonerFactory.

The complete API documentation of class Halftoner can be found by following this link.

1.2 - Class HalftonerFactory

Class HalftonerFactory plays the role of a provider, providing specific instances of Halftoners. It stores keys to the available algorithms that map to Instanciators. The access to an algorithm is as easy as:


#include <Halftoner.h>
#include <HalftonerFactory.h>

void f()
{
   const char* key = HalftonerFactory::FLOYD_STEINBERG;
   Halftoner* halftoner =
                    HalftonerFactory::getInstance(key);

//...
}

As it can be seen, this fragment of code has no knowledge about the class that implements the algorithm, nor where it comes from (whether it was included in the library or is a custom algorithm). It only knows that it exists and can halftone images (in this case using the error diffusion technique created by the Floyd-Steinberg pair).

Finally, class HalftonerFactory makes visible the keys to the algorithms that are already included in the library.

The complete API documentation of class HalftonerFactory can be found by following this link.

1.3 - Class Instanciator

Class Instanciator performs the individual creation of Halftoners. This class is the equivalent of the java idiom:


Class clazz = Class.forName("some.classname");
some.classname obj = (some.classname) clazz.newInstance();

There is a child class of class Instanciator for each available algorithm. If your are not developing your own algorithm, you do not need to concern yourself with that class. However if you do, section 2 explain how to create and registering your own Instanciator.

The complete API documentation of class Instanciator can be found by following this link.

1.4 - Class HalftonerSpec

Class HalftonerSpec provides a type for the parameter object pattern. Many halftoning algorithms can be customized and this interface provides a generic solution. HalftonerSpecs are created when needed and passed to the halftoner when a halftoning pass is needed.

An implementation of class Halftoner is free in its behaviour on how to handle the HalftonerSpec parameter. Its behaviour should be documented however.

The complete API documentation of class HalftonerSpec can be found by following this link.

1.5 - Class Image

Class Image represents a single-channel image. Each pixels has a maximum of 256 distinct values (a greyscale image in other words). Image objects are immutable, their dimensions cannot be changed during their lifetime. However, the image data itself is mutable, each individual pixel can have its intensity changed.

Images can be created at runtime or by the operations offered by the class ImageIO.

The complete API documentation of class Image can be found by following this link.


2 - Adding Algorithms

A user that wishes to implement his own version of an algorithm can do so easily. The process requires six steps:

  1. code and test the algorithm
  2. make a class that extends class Halftoner
  3. make a class that extends class HalftonerSpec if necessary
  4. create an a class that extends class Instanciator
  5. register the Instanciator with class HalftonerFactory
  6. use that algorithm

Each step is described in detail below.

2.1 - Code and Test the Algorithm

The first step is to code and test the algorithm that will be added. This step is primordial as a buggy algorithm is not that much useful.

2.2 - Make a Child-class of Halftoner

Once the confidence in the algorithm is acquired, it is time to turn its code into a class that is a child-class of the abstract class Halftoner and overwrite the method halftone().


#include <Halftoner.h>

class HalftonerSpec;
class Image;

class yourHalftoner : public Halftoner
{
   // your method and variables

   public:
      yourHalftoner() throw();
      virtual ~yourHalftoner() throw();

      Image*
      halftone
         (
            const Image& source,
            const HalftonerSpec* spec
         )
         throw (std::invalid_argument);
};

The code from step 1 is refactored in such a way as to implement the method halftone(). There are no restriction for the creation of other methods.

2.3 - Make a Child-class of HalftonerSpec

If the algorithm that is to be added can be parametrized, this step provides a guide to the creation of a parameter object, otherwise this section can be skipped and 0 should be passed to Halftoner::halftone().

Class HalftonerSpec defines no method. It is a class that only provides a common type. Anything can be done in child-classes and the Halftoner child-class just created must know how this parameter object is laid down. Usually in yourClass::halftone():


halftoner::Image*
yourHalftoner::halftone (const Image& source,
                         const HalftonerSpec* spec)
                         throw (std::invalid_argument)
{
   const yourSpec* yspec = dynamic_cast<yourSpec*>(spec);
   if (0 == yspec)
   {
      std::string message("spec is not a yourSpec*");
      throw std::invalid_argument(message);
   }

// extract parameters
}

2.4 - Make a Child-class of Instanciator

Class HalftonerFactory stores Instanciators that creates Halftoner objects when requested. Instanciators can be seen as pointers to constructors. To create such an object, make a child-class of Instanciator and implement the function operator operator()(). Usually, returning a new instance is enough:


#include <Instanciator.h>

class yourInstanciator : public Instanciator
{
   public:
      Halftoner* operator()() const throw()
      {
         Halftoner* halftoner = new yourHalftoner();
         return halftoner;
      }
};

2.5 - Registration

Once all the required objects (Halftoner, HalftonerSpec, Instanciator) have been defined, it is time to register the new algorithm. The registration is simple: at the beginning of a program, a call to HalftonerFactory::addHalftoner is all that is required. A unique key needs to be defined and remembered so that the newly added algorithm can be called back.


#include <HalftonerFactory.h>

void f()
{
   const char* yourKey = "yourKey";
   Instanciator* instanciator = new yourInstanciator();
   HalftonerFactory::addHalftoner(yourKey, instanciator);

// ...
}

Once registered, the factory takes charge of the memory of the Instanciators, so it is strongly suggested that the Instanciator be created on the heap and never deleted manually, its memory will be deleted at the end of the program by the factory. A similar process also happens for the key.

2.6 - Common Use

Using the new algorithm, once registered, is as simple as using the built-ins. The new algorithm is requested by the method HalftonerFactory::getInstance() with the correct key.


void f()
{
   const char* key = "yourKey";
   Halftoner* yourHalftoner =
                      HalftonerFactory::getInstance(key);

//...
}