[cairo] Proposed simplified replacement for Cairo compositing operators.

Bill Spitzak spitzak at d2.com
Wed Oct 20 22:02:15 PDT 2004

As per the conference call, I tried to trim down the PorterDuff operators to 
ones that don't modify where alpha=0, and show how to emulate them. This grew 
into a much more detailed description:

The primary purpose is so that the path, clip, and source alphas can be 
unified *before* the compositing operation. This makes the per-pixel 
composite function as simple as possible. It also means the bounding box of 
changed pixels of all operations is the same.

I also believe the only operations eliminated are ones that are confusing and 
are rarely needed. And most of them can be emulated exactly with a few steps.

I have also minimal support for non-premultiplied surfaces.  Although 
unnecessary for photographic work, Non-premultiplied images are needed for 
GUI. This is primarily so that 8-bit images can be made that fade as expected 
into a background of a given color, and compositing the same image atop 
itself many times can approach any color, rather than some subset defined by 
the level of alpha. Microsoft's GDI+ has added a (somewhat undocumented, look 
for BLENDFUNCTION) flag to indicate source images are unpremultiplied. The 
PNG standard is also unpremultiplied. Most current implementations of 1-bit 
alpha (ie GIF, XPM, BMP) also act like the source is unpremultiplied. So 
there is significant precedence for this.

I have also seperated the "color" from the "source" and made it a multiplier. 
This is needed to emulate the GDI+ "fade" operation.

Graphics state:


Defines an alpha value 'p' over the entire output surface. This is set by the 
path construction operators. (also stroke and drawing glyphs act like a 
temporary path is created in the shape of the desired image)

In the initial graphics state, or if the path is empty, then p is 1 
everywhere. This is different than the current Cairo! A path containing 1 
moveto can be used to make a null path that is 0 everywhere. This is vital if 
invert-path is not supported, and appears to be nice for people expermenting 
with Cairo.

An "invert-path" operation that in effect makes p be 1-p is probably needed. 
It's main use is so that you can remove a shape from the current clip. It is 
also useful because it allows some Porter-Duff operations to be emulated when 
the source alpha 's' is 1.


Defines an alpha value 'k' over the entire output surface. In a new graphics 
state this is 1 everywhere. This is set by the "clip" operator, which 
replaces k with k*p.


Defines a constant color M and alpha m. These are set by the Cairo set-color 
and set-alpha calls. They are by defintion NOT premulitiplied (which appears 
to be how Cairo works now?).


Defines a color S and alpha s for every pixel. Defaults to 1 everywhere. This 
is set by the source surface, repeat flags, gradients, etc.


A boolean flag 'sp' that defines if S is premultiplied by s. This may be set 
by the same calls that set the source surface. Current Cairo acts like this 
is true.

Inputs to compositing operations:
There are only 4:

a = p*k*m*s

A = sp ? p*k*m*M*S : a*M*S

b = alpha of destination

B = color of destination

'a' and 'b' must be clamped to the 0-1 range. In any plausable implementation 
this can be assummed because p,k,m,s and b are clamped.

The compositing operations:


NOOP(*):	c = b		C = B	

OVER:		c = a+b(1-a)	C = A+B(1-a)

UNDER:		c = a+b(1-a)	C = B+A(1-b) (may want to call this "OUT")

ERASE:		c = b(1-a)	C = B(1-a)

ATOP:		c = b		C = Ab+B(1-a) (may want to call this "IN")

XOR(**):	c = a+b-2ab	C = A(1-b)+B(1-a) 

Non-premultiplied destinations:

OVERNP:		c = a+b(1-a)	C = c ? (A+Bb(1-a))/c : B

UNDERNP:	c = a+b(1-a)	C = c ? (Bb+A(1-b))/c : B

ERASENP:	c = b(1-a)	C = B

ATOPNP:		c = b		C = A+B(1-a)

XORNP(**):	c = a+b-2ab	C = c ? (A(1-b)+Bb(1-a))/c : B

OPAQUE:		c = a+b(1-a)	C = B (also works well on premultiplied dest)


PLUS:		c = a+b(1-a)	C = A+B

SCRN:		c = a+b(1-a)	C = A+B(1-A) (better than plus for sRGB images)

DODGE, BURN, etc (all the "photoshop" operators from SVG):
		c = a+b(1-a)	C = f(A,B) where f(0,B)==B

Note that c is in all cases is in the 0-1 range. And only 4 different 
functions for c are used (3 if xor is eliminated).

The NP and photographic operators can produce C outside the ranges of A and 
B. So can any operation when the premultiply flag is on and S > s. It is 
acceptable to clamp this to the range of the output surface in this case.

Photographic operators are not actually correct if destination has alpha 
other than 1 (whether you assumme destination is premultiplied or 
non-premultiplied). The behavior defined here is needed to emulate SVG and I 
don't think other possibilities are are important enough to do.

A and B and the calculation must be in fixed-point resolution high enough so 
that xa+x(1-a)==x for all colors x and all possible alphas a.

How to simulate the rest of the Porter-Duff operations:

Yes it is not possible to simulate "in" exactly!

clear:	No path, no source, color set to black with alpha=1, do ERASE

A:	clear, then OVER

B:	NOOP (*)

A over B: OVER

B over A: UNDER

A in B: Do ATOP with no source and color set to black with alpha=1,
  then do ATOP. Resulting c is wrong and cannot be fixed. If source
  has alpha==1 this can be done correctly with a third surface and
  invert path.

B in A: If source has alpha==1, invert path and do ERASE.  Otherwise
  it can be done with a third surface, but resulting c is wrong and
  cannot be fixed.

A out B: Can be done with a third surface

B out A: ERASE

A atop B: ATOP

B atop A: Can be done with a third surface

A xor B: XOR (**)

You may be worried that the "A" Porter-Duff operator is not supported without 
doing two operations. However if the source is solid alpha=1 then OVER can be 
used, and I believe all other actual uses can be done by making a 
non-compositing operation that completely replaces the contents of the 
current surface with the contents of another. I.E. I do not believe "COPY" is 
used for any reason other than initializing a surface.


* I would like to remove the NULL operation, even though it is legal under my 
scheme, due to the fact that it is trivial to emulate.

** I would also like to remove the XOR operation despite the fact that it 
cannot be emulated and is legal under my scheme. This is because it is 
totally useless and apparently confuses programmers.

More information about the cairo mailing list