[Cairo] Color transforms

Bill Spitzak spitzak at d2.com
Wed Jul 16 11:25:40 PDT 2003

On Wednesday 16 July 2003 02:05 am, David Forster wrote:

> I know that Cairo at some point needs to be cleaned up to remove
> Xlib-isms and to allow new external rendering devices to be defined (I
> assume?).  So here's a proposal that should solve all problems:
> Make libcairo.so just be a thin framework for defining data types and
> passing calls of to a vtable, cairo_engine_t:

This is probably necessary, not just for the purpose you want, but so that 
Cairo can be used as a redirectable graphics interface for printing. This 
will also provide an obvious way to change the implementation (between xlib 
images, simulation with Xlib, and Xrender and OpenGL, and between various 
levels of accuracy, such as an all-floating-point-color version.

I have been designing something like this for fltk, based on an outside 
contribution called Fl_Device. Your recommendation looks a lot like 
Fl_Device, but I think some rearrangement can make it easier to implement new 
backends that are compatable with each other, and avoid the overhead of the 
"virtual function call" on many of the Cairo interfaces.

First I would design your structures much like a C++ object, so that several 
instances can share the "vtable", yet have local "instance variables". 
Secondly I would place some (maybe a lot) of Cairo's logic in the 
device-independent portion:

struct Cairo_Xlib_Engine {
  struct Cairo_Vtable* vtable;
  /* device-specific goes here, this is for an example only: */
  XDisplay display;
  XWindow window;
  XColormap colormap;
  XGC gc;

struct Cairo_Vtable {
  void (*identity)(Cairo_State*);
  void (*fill)(Cairo_State*);
  void (*stroke)(Caire_State*);

struct Cairo_State {
  Cairo_Engine* engine;
  Cairo_State* previous;
  /* portable state information (again for example, not a recommendation) */
  float A,B,C,D,X,Y; /* CTM */
  float (*path)[2]; /* maybe the path is done here? */
  int numpoints;

An implementation of a typical cairo function:

void cairo_fill(Cairo_State* state) {

void cairo_newpath(Cairo_State* state) {
   numpoints = 0;
   /* notice that the engine is NOT called */
/* Now the above is an example only, I am unsure where the division should be 
and whether there really should be any non-device-specific code. The above 
example is probably extreme, the path is likely to be put into the device. */

"Which device" is considered part of the state:

void cairo_push(Cairo_State* state) {
  Cairo_State* previous = new Cairo_State;
  *previous = *state;
  state->previous = previous;

void cairo_setdevice(Cairo_State* state, Cairo_Engine* device) {
  state->engine = device;
  /* It is required that changing the device resets everything. For many 	
devices an implementation the preserves state while not being used is
impossible or tremendously inefficient. */

Notice that all Xlib or other device-specific arguments are passed to the 
constructor for the engine, not Cairo. This means that machine-specific 
header files are not needed in order to call Cairo, which gets rid of 90% of 
my problems with the previous design. I can in fact make fltk support Cairo 
portably by adding a single "cairo_device(fltk::Window)" call which does not 
require any compromises in the design of Cairo or fltk at all.

The interface does not have to be very complex. One good idea from Fl_Device 
is that almost all device-specific stuff is not considered part of the core 
interface. It provides, for instance, a constructor for a postscript pipe (to 
lpr for printing) that takes a lot of arguments to determine how to scale and 
center the image on a sheet of paper, but all this is in the postscript pipe 
part and has nothing to do with Cairo. In addition the "newpage" command is 
part of the device-specific interface.

The other thing I think should be done is make all the transformations, and 
all path construction that is more complex than lineto be device-independent. 
This will avoid the need to reimplement these for every device, and to design 
the interface to allow the addition of new curve types easily.

The way to do this is to provide a "transformed" interface for adding to the 
path. Probably should take a packed array of points, rather than one call per 
point. Also an interface for transforming a given x,y. Curveto is then 
implemented portably by transforming the points, figuring out the 
subdivisions so the error is less than 1/4 transformed pixel, and then 
calling the transformed interface with the resulting line segments.

I am unsure if this "transformed" interface should be part of the engine, or 
should be itself part of the portable cairo state. Making it portable will 
also allow the portable part to do clipping (but clip paths will still need 
to be sent to the device so it can clip text and images). It will also allow 
push/pop of state to be done entirely portably.

                   ,~,~,~,~ ~ ~ ~ ~
     /\_       _|_========___         Bill Spitzak
 ~~~/\/\\~~~~~~\____________/~~~~~~~~ spitzak at d2.com

More information about the cairo mailing list