|
|
|
Intention / Introduction to OperationsOperations are objects abstracting the rendering backend from the client. They provide an API that let the client build macros that are performing a chain of rendering operations. Operations are introduced to provide an easy way to apply all kinds of transformations and filters to one object. For example, you may want to apply a Rotate operation, a Blur operation and a ChangeContrast operation over one rectangle. To let clients do this easily, these operations can be chained into Macros. The client is provided with an API resembling the unterlying Cairo calls, similar to this: // Set a custom item up that draws a rectangle. Canvas::Operation::Macro macro; ///< A Macro is an operation chain. Canvas::Operation::VectorMoveTo moveto(x1, y1); Canvas::Operation::VectorLineTo lineto(x2, y2); Canvas::Operation::VectorStroke stroke; Canvas::Operation::Blur blur(20); Canvas::Item item; ///< An item in the canvas. // Put all operations into one chain. The resulting macro will // - Create a (vectorial representation of a) line. macro.append(moveto); macro.append(lineto); // - Draw that line to the RGBA buffer. macro.append(stroke); // - Apply a blur with intesity 20 to the buffer. macro.append(blur); // Now, tell the canvas item to use that macro. item.set_macro(macro); // Now, it is possible to alter the line shape... lineto.set(10, 20); // ...or even to alter the operation chain completely. Canvas::Operation::VectorElipse elipse(x1, y1, x2, y2); Canvas::Operation::Rotate rotate(180); macro.remove(moveto); macro.remove(lineto); macro.insert_before(stroke, elipse); macro.insert_before(blur, rotate); Considerations
ImplementationThe Operation implementation consists of the following classes: The Operation Class
/// The component in the composite pattern.
/**
* Defines a common interface for all Operations. Operations can be connected
* to each other.
*/
class Operation {
public:
virtual ~Operation() { };
/// Notifies the Operation that it has received an inbound connection.
/**
* Each Operation can be connected to other Operations, with one inbound
* connection and one outbound connection.
* When another Operation has connected itself, it should call this method
* to ensure that no more than one inbound connection can be created.
* Since not all Operations are compatible with each other, when another
* Operation wants to connect to this Operation, it must also make sure that
* it has sufficient capabilities to do that. This is done through the
* _capabilities argument.
* \param A bitmask listing the capabilities of the Operation that wants to
* connect.
* \return TRUE if the Operations are compatible, were not yet connected, and
* the connection was successfully made, FALSE otherwise.
*/
virtual const bool notify_connect(int _capabilities);
/// Notifies the Operation that the inbound connection has been removed
/**
* Each Operation can be connected to two other Operations, one inbound
* connection and one outbound connection.
* When another Operation disconnected itself, it should call this method.
*/
virtual void notify_disconnect(void);
/// Create an outbound connection to another Operation.
/**
* Each Operation can be connected to two other Operations, one inbound
* connection and one outbound connection. This method connects the outbound
* direction of this plug to the inbound connection of another. It also notifies
* the remote Operation using notifiy_connect().
* \param The operation that we want to connect.
* \return TRUE if the connection was successfully made, FALSE otherwise.
*/
virtual const bool connect_to(Operation* _operation);
/// Disconnects the outbound connection.
/**
* Each Operation can be connected to two other Operations, one inbound
* connection and one outbound connection. This method disconnects the
* outbound direction of this plug from another. It also notifies
* the remote Operation using notifiy_disconnect().
*/
virtual void disconnect(void);
/// Returns whether this Operation is executable.
/**
* Only the first operation in a Macro can be executed, since all other
* Operations are triggered automatically through the chain.
* \return TRUE if the Operation is executable, FALSE otherwise.
*/
virtual const bool can_execute(void);
/// Executes the operation.
/**
* Only the first operation in a Macro can be executed, since all other
* operations are triggered automatically through the chain.
* During the operation, the proceed_fragment() method of the connected
* Operation may be called to perform a part of a task.
* When the operation is finished, the proceed() method of the connected
* Operation is called.
* \param _plane A reference to the Plane to draw to.
* \return TRUE if the Operation was successfully finished, FALSE otherwise.
*/
virtual const bool execute(Plane* _plane);
/// Inserts the given Operation into the Macro before an existing Operation.
/**
* This method is only implemented by Macros, Operations will always
* return NULL.
* Adds the given Operation into the Macro, inserting it before the
* Operation given in _existing. Returns a pointer to the inserted Operation,
* or NULL on failure. If _manage is TRUE, the Macro will automatically
* delete the Operation (and free the resources) on removal of the Component
* or on destroyal of the parent Macro.
* \param _existing A pointer to an Operation that is already in the Macro.
* \param _operation A pointer to the Operation that is to be inserted.
* \param _manage When TRUE, the Macro takes care of freeing the _operation
* memory.
* \return A pointer to the inserted Operation.
*/
virtual Operation* insert_before(Operation* _existing,
Operation* _operation,
bool _manage = FALSE);
/// Appends the given Operation to the end of the chain in the Macro.
/**
* This method is only implemented by Macros, Operations will always
* return NULL.
* Adds the given operation into the Macro, appending it to the Operation
* chain. Returns a pointer to the Operation, or NULL on failure. If _manage
* is TRUE, the Macro will automatically delete the Operation (and free the
* resources) on removal of the Component or on destroyal of the Macro.
* \param _operation A pointer to the Operation that is to be appended.
* \param _manage When TRUE, the Macro takes care of freeing the _operation
* memory.
* \return A pointer to the inserted Operation.
*/
virtual Operation* append(Operation* _operation, bool _manage = FALSE);
/// Removes an Operation from a Macro.
/**
* This method is only implemented by Macros, do not use it on Operations.
* Removes the given Operation from the Macro. If _manage was set TRUE
* on insertion, the object is also being destroyed.
* \param _operation A pointer to the Operation that is to be removed.
*/
virtual void remove(Operation* _operation);
/// Whether the Operation acts as a cache.
/**
* \return TRUE if this Operation acts as a cache, FALSE otherwise.
*/
virtual bool caches(void) { return FALSE; }
protected:
/// Proceeds with the Operation.
/**
* This method does the same as execute(), except that it can also be called
* on Operations that are not the first in the chain.
* \param _plane A reference to the Plane to draw to.
* \return TRUE if the Operation was successfully finished, FALSE otherwise.
*/
virtual const bool proceed(Plane* _plane);
/// Proceeds with a fragment of the operation.
/**
* This method does the same as proceed(), except that it performs only a
* part of the task. This is useful in slow Operations to give a more
* responsive impression to the user.
* \param _plane A reference to the Plane to draw to.
* \return TRUE if the fragment was successfully processed, FALSE otherwise.
*/
virtual const bool proceed_fragment(Plane* _plane);
bool has_inbound_connection;
Operation* outbound;
};
The Plane Class
class Plane {
public:
/// Instantiate a new Plane.
/**
* \param _x The initial width in pixels.
* \param _y The initial height in pixels.
*/
Plane(double _x, double _y);
~Plane();
/// Resizes the plane.
/**
* By default, when the buffer is resized its content is destroyed.
* By passing the _copy parameter, copies the old content into the
* new buffer, with an optional offset.
* \param _w The new width of the buffer in pixels.
* \param _h The new height of the buffer in pixels.
* \param _copy If TRUE, the buffer contents are not destroyed.
* \param _xoff The x-offset at which the old buffer is copied into the new buffer.
* \param _xoff The y-offset at which the old buffer is copied into the new buffer.
* \return TRUE on success, FALSE = out of memory.
*/
bool resize(double _w,
double _h,
bool _copy = FALSE,
double _xoff = 0,
double _yoff = 0);
/// Renders using the attached Macro.
/**
* \param _hard If TRUE, all caches are ignored (hard refresh).
*/
void render(bool _hard = FALSE);
/// Attaches an Operation.
/**
* \param _operation A pointer to the Operation responsible for drawing.
*/
void set_operation(Operation* _operation);
protected:
/// Callback, invoked whenever the Operation was changed.
/**
* FIXME
*/
void on_operation_changed(void);
cairo_t* cr;
unsigned char* buffer; ///< The RGBA buffer.
gulong alloc; ///< The buffer size in bytes.
double w, h; ///< The size of the boundary box in pixels.
double stride;
Operation* operation; ///< A pointer to the responsible Operation.
Operation* cache; ///< A pointer to the last valid cache in the Operation.
};
Use CaseA client creates the following simple Operation chain: VectorElipse -> VectorStroke -> WritePNG and executes the resulting Macro. Outline:
|
|