[Cairo] Color transforms

David Forster dforste at arbornet.org
Wed Jul 16 02:05:42 PDT 2003


I'm working on some code that builds up a 2D scene graph similar to
how Macromedia's Flash works (but not Flash).  Each graphic on the
screen (shape, text, image) can have applied to it a coordinate
transform and, optionally, a color transform.  It seems Flash just
allows a multiply and add transform, but I'd like to do more like a
color->b&w transform.

So while I can apply this color transform to my own graphics, external
libraries like libsvg are troublesome, since they just communicate
with Cairo directly.  I know libsvg exports a vtable, but not all
libraries may be as nice.  I'd like to be able to interrupt API calls.


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:

cairo.h:
#define CAIRO_NULL_ENGINE_ID 0x0000
typedef struct cairo_engine_s cairo_engine_t;
struct cairo_engine_s
{
   cairo_engine_t *previous;
   void           *state;
   int             api; /* See later... */
   int             version;  /* Standard packed 0xMMmmpp */
   cairo_state_t  *cairo_state; /* Necessary? */
   void (*move_to)(void *state, cairo_engine_t *engine,
                   double x, double y);
   ... all functions ...
};

cairo_state_t *cairo_create(cairo_engine_t *engine);
/* ... */
void cairo_move_to(cairo_state_t *state, double x, double y);

Real work will be handled by device-specific libraries.  These will
export a cairo_engine_t and a device-specific API:

cairo-xlib.h:
/* Top level engines like xlib always have ->previous = NULL */
cairo_engine_t *cairo_xlib_get_engine(Display *dpy, Window win);
cairo_state_t  *cairo_xlib_create(Display *dpy, Window win);

Note the `previous,' `state,' and `api' fields.  The `state' pointer
is for device specific rendering state, while `previous' is for
stacking engines.  So, for me to interrupt set_color():

my_engine = malloc(sizeof(cairo_engine_t));
memset(my_engine, 0, sizeof(cairo_engine_t));
my_engine->previous = cairo_get_engine(state);
my_engine->state = (my_state_t*)my_state;
my_engine->id = CAIRO_NULL_ENGINE_ID;
my_engine->version = 0;
my_engine->cairo_state = state; /* ? */
my_engine->set_color = (void*)my_set_color;
cairo_set_engine(state, my_engine);

Note that I really should define my own CAIRO_MY_ENGINE_ID.  Is it
worth allowing such laziness?  Engine IDs must be unique...hmm...

Anyways, now whenever a library calls cairo_set_color() they get my
function instead:

void my_set_color(my_state_t *state, cairo_engine_t *engine,
		  float r, float g, float b)
{
  float rt, gt, bt;

  /* In reality support more complex transforms and no transform
     (pass-thru)...  We could get state from engine->state, but this
     seems more convenient (no casting). */
  
  rt = r * state->colorTransform.r;
  gt = g * state->colorTransform.g;
  bt = b * state->colorTransform.b;

  /* This is not good from a performance stand point, I
     know...Suggestions welcome.

     cairo_engine_active() searches for an engine with a non-NULL
     pointer at the given address, found by a macro for readability.
     This involves searching linearly through ->previous.  I don't
     think this is AS bad as it seems.  A macro/inline function may
     further reduce the effects.  The first engine must define every
     function and those functions will of course not chain call.
  */

  engine = cairo_engine_next(engine, CAIRO_ENGINE_OFFSET(set_color));
  engine->set_color(engine->state, engine, rt, gt, bt);
  return;
}

void my_set_ctransform(my_state_t *state, my_color_t color);

typedef struct my_state_s my_state_t;
struct my_state_s
{
   cairo_state_t *state;
   my_color_t     colorTransform;
};

Now in my code when ever I want to set a transform, I use
my_set_ctransform() with my_state_t.  It might be a little messy to
now carry around both cairo_state_t and my_state_t, but I don't see a
way around this.  This way of things might seem a little tedious, but
I don't expect interrupting functions will be common (but very useful
when needed).

This will also allow device-specific functions:

cairo_pdf_embed_font(cairo_pdf_state_t *state, void *buffer, size_t size);
/* NOTE:  I know very little about PDF...But I know you can embed
          fonts in there somewhere...;) */

Some further APIs to tie up loose ends:

cairo.h:
int cairo_engine_major(cairo_state_t *state, int engine_api);
int cairo_engine_minor(cairo_state_t *state, int engine_api);
int cairo_engine_patch(cairo_state_t *state, int engine_api);
/* CAIRO_NULL_ENGINE_ID never matches... */
int cairo_engine_supports(cairo_state_t *state, int engine_api);
void *cairo_engine_state(cairo_state_t *state, int engine_api);

cairo-pdf.h:
#define CAIRO_PDF_ENGINE_ID 0x0010

cairo-opengl.h
#define CAIRO_OPENGL_ENGINE_ID 0x0011

cairo-xcb.h
#define CAIRO_XCB_ENGINE_ID 0x0012

Then the developer:

if (cairo_engine_supports(state, CAIRO_PDF_ENGINE_ID))
  {
    void *pdf_state = cairo_engine_state(state, CAIRO_PDF_ENGINE_ID);
    /* ... find the right font and put in buffer ... */
    cairo_pdf_embed_font(pdf_state, font, font_size);
    /* Or would cairo_pdf_* know how to fish for it's own state from
       cairo_state_t*?  If the user does it, only have to find it
       once. */
  }

Each CAIRO_*_ENGINE_ID would have to be unique, but that should be
somewhat easy to keep straight.

Problems might arise if multiple engines get stacked on top of each
other without some coordination.  Each engine should only accept and
produce valid input/output and not expect to be the first one to see
the arguments.

As a further advantage, libcario.so could define a standard debug
engine which may do some argument checking and provide verbose output,
and/or a first engine that just produces trace output.

Sorry for such a long post but I thought I'd be thorough...What do
people think?  I think this would add a lot of flexibility to Cairo.

Thanks,
-Dave




More information about the cairo mailing list