[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) {
engine->vtable->fill(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. */
state->vtable->reset(state);
}
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