[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:
================
PATH:
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.
CLIP:
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.
COLOR:
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?).
SOURCE:
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.
SOURCE-PREMULTIPLY:
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:
================
Porter-Duff:
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)
Photographic:
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.
================
Notes
================
* 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