summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSøren Sandmann <ssp@redhat.com>2013-01-08 22:40:28 -0500
committerSøren Sandmann <ssp@redhat.com>2013-01-08 22:40:28 -0500
commit16c7e28a9c8c59d06be6172ce5806005b41ba14f (patch)
tree37667f0ac02a71d509e3fb4df2488922ec961e74
parentc4348044e4161c5566be39a200481415413de5a7 (diff)
updates
-rw-r--r--blend.txt216
-rw-r--r--pics.c122
2 files changed, 206 insertions, 132 deletions
diff --git a/blend.txt b/blend.txt
index d1165ec..b2b4763 100644
--- a/blend.txt
+++ b/blend.txt
@@ -6,20 +6,20 @@ In the Porter/Duff compositing algebra, images are extended with an
alpha channel that determines on a per-pixel basis whether the image
is there or not. When the alpha channel is 1, the image is fully
there, when it is 0, the image isn't there at all. In other words, the
-alpha channel describes the the *shape* of the image, it does not say
-anything about translucency. The reason we have alpha values between 0
-and 1 is so that the shapes of the images can be antialiased.
-
-The way to think of images with an alpha channel in the Porter/Duff
-model is as irregularly shaped pieces of cardboard, not as colored
-glass.
+alpha channel describes the the shape of the image, it does not
+describe opacity. The reason we have alpha values between 0 and 1 is
+so that the shape can be antialiased. The way to think of images with
+an alpha channel in the Porter/Duff model is as irregularly shaped
+pieces of cardboard, not as colored glass.
Consider these two images:
- SRC ("&") DEST ("triangle")
+ SRC ("triangle") DEST ("ampersand")
If we combine them and zoom in on one pixel, we get this:
+Each individual pixel can be divided into four regions:
+
\ /
\ /
\/
@@ -27,40 +27,47 @@ If we combine them and zoom in on one pixel, we get this:
/ \
/ \
-Each individual pixel can be divided into four areas: one where only
-the source is present, one where only the destination is present, one
-where both are present, and one where neither are present.
+One where only the source is present, one where only the destination
+is present, one where both are present, and one where neither are
+present.
+
+By deciding on what happens in each of the four regions, various
+effects can be generated. For example, if the destination-only region
+is treated as blank, the source-only region is filled with the source
+color, and the 'both' region is filled with the destination color like
+this:
-By deciding on what happens in each of the four area, various effects
-can be achieved. For example, if the source-only area is treated as
-blank, the destination area as destination, and the 'both' area as
-'source':
+ \/
+ /\
- \/
- /\
+the effect is as if the destination image is trimmed to match the
+source image, and then held up in front of it:
-the effect is as if the source image is tailored by the background
-image, and then held up in front of it:
+ SRC dest_atop DEST
- SRC atop DEST
+The Porter/Duff operator that does this is called DEST_ATOP.
-The Porter/Duff operator that does this is called ATOP. There are
-twelve of these operators, each one characterized by its behavior in
-the three areas: source, destination and both (the 'neither' area is
-always blank). The source-only area can be either 'source' or
-'blank'. The destination-only area can be either 'destination' or
-'blank'. The 'both' area can be either 'blank', 'source', or
-'destination'.
+There are twelve of these operators, each one characterized by its
+behavior in the three regions: source, destination and both (the
+'neither' region is always blank). The source-only region can be either
+'source' or 'blank'. The destination-only region can be either
+'destination' or 'blank'. The 'both' region can be either 'blank',
+'source', or 'destination'.
Here are all of the Porter/Duff operators:
+ [table.png]
+The formula for the operators is a linear combination of the contents
+of the four regions, where the weights are the areas of each region:
-The important thing to understand is that despite being referred to as
-"alpha blending", and despite alpha often being used as a form of
-translucency, conceptually Porter/Duff doesn't provide any blending at
-all. All it provides is a way to overlay, combine and tailor pieces of
-cardboard in various ways.
+ A_src * { C_s, 0 } + A_dest * { C_d, 0 } + A_both * { C_s, C_d, 0 }
+
+It is important to understand that despite being referred to as "alpha
+blending", and despite alpha often being used as a form of
+translucency, conceptually Porter/Duff is *not* a way to blend the
+source and destination shapes. It is a way to overlay, combine and cut
+them as if they were pieces of cardboard.
Blending
@@ -68,64 +75,55 @@ Blending
Photoshop and the Gimp have the concept of layers which are images
stacked on top of each other. In Porter/Duff, stacking things on top
of each other is done with the "Over" operator, which is also what
-Photoshop/Gimp use by default to composite layers. This operator's
-characterization is the following:
+Photoshop/Gimp use by default to composite layers. It looks like this:
- Source-only: Source
- Destination-only: Destination
- Both: Source
+ <over>
Conceptually, two pieces of cardboard are held up with one in front of
the other. Neither shape is tailored in any way, and in places where
both are present, only the top layer is visible.
A layer in these programs also has an associated Blend Mode. The
-default blend mode is 'Normal' and it corresponds to the regular OVER
+default blend mode is 'Normal' which corresponds to the regular OVER
operator from Porter/Duff. When a blend mode other than Normal is
-chosen, the behavior in the 'both' area of the Porter/Duff pixel is
+chosen, the behavior in the 'both' region of the Porter/Duff pixel is
modified to be something other than 'source'. For example the
-'SoftLight' blend mode computes a blend of source and destination
+'Color Dodge' blend mode computes a blend of source and destination
according to this formula:
- <softlight formula>
-
-This looks like this:
+ <color dodge formula>
- src SOFTLIGHT dest
+It looks like this:
-And if we zoom in on a pixel, we get this:
+ src ColorDodge dest
- zoomed in on a pixel
+The pixel diagram looks like this:
-This is actual blending of the source and destination. Even when the
-alpha value of both source and destination is 1, the resulting image
-is still going to be a blend of source and destination.
+ Zoomed Color Dodge
-Because layers in Photoshop and Gimp are not tailored to each other
-except for layer masks, the compositing of the layer stack is always
-done with the source-only and destination-only areas fixed to source
-and destination respectively.
+Here the source and destination are actually blended in the region
+where they overlap, where the regular Porter/Duff operators always
+choose either the source, the destination or neither in the
+overlapping region.
-A while back, such blend modes were added to pixman as new operators
-along with the existing Porter/Duff operators, and then subsequently
-added to cairo and the X11 Render extension. They are sufficient to
-implement the PDF drawing model, and also to implement layers in a
-Gimp/Photoshop application. They don't support tailoring of source and
-destination the way the original Porter/Duff operators do.
+Layers in Photoshop and Gimp are not tailored to each other (except
+we'll ignore layer masks here), so the compositing of the layer stack
+is done with the source-only and destination-only region fixed to
+source and destination respectively. However, there is nothing in
+principle stopping us from setting the source-only and
+destination-only regions to blank in the blend mode operators so that
+we could support tailoring along with blending. For example, here is
+the operator that has 0 in the 'source-only' region, D in the
+'destination-only' region, and ColorDodge blending in the 'both'
+region:
-However, there is nothing in principle stopping us from setting the
-source-only and destination-only areas to blank in the blend mode
-operators so that we can support tailoring along with blending. For
-example, here is something similar to ATOP with softlight blending for
-the area where the images intersect:
+ ColorDodge Atopish
- src ATOP/softlight dest
-
-This idea naturally leads to a coherent extension of the Porter/Duff
-algebra, where the 'both' behavior can be chosen from a set of blend
-modes, three of which are the original 'source', 'dest', and
-'none'. The number of operators available in this model is large, and
-all of them are characterized by the answer to three questions:
+This idea naturally leads to an extension of the Porter/Duff algebra,
+where the 'both' behavior can be chosen from a set of blend modes,
+three of which are the original 'source', 'dest', and 'none'. There is
+a large number of operators available in this model, and each of them
+is characterized by the answer to three questions:
- Is the source tailored to the destination?
- Is the destination tailored to the source?
@@ -133,23 +131,69 @@ all of them are characterized by the answer to three questions:
By answering 'none', 'source', or 'destination' to the third question,
the twelve original porter/duff operators fall out as special
-cases. With one additional blend mode, 'plus', the ADD operator falls
-out as well. If we were to generalize the operators in
-pixman/cairo/X11, this is the model I think we should be looking at.
+cases. The general formula is exactly the same:
+
+ A_src * { C_s, 0 } + A_dest * { C_d, 0 } + A_both * B(C_s, C_d)
+
+The only difference is that B(C_s, C_d) can now be defined by several
+other formulas than just { C_s, C_d, 0 }. With one additional blend
+mode, 'plus', the ADD operator falls out as well.
+
+This model is a simple and coherent way to extend the Porter/Duff
+compositing algebra with blend modes. If we were to generalize the
+operators in pixman/cairo/X11, this is the model we should be looking
+at.
+
+CSS compositing spec
There is a proposal for extending CSS
[https://dvcs.w3.org/hg/FXTF/rawfile/tip/compositing/index.html] with
support for blending and compositing that also tries to unify
-porter/duff compositing with blend modes. In my opinion that proposal
-makes a serious mistake in that it allows *both* a Porter/Duff
-operator *and* a blend mode to be specified at the same time. This is
-wrong-headed because the Porter/Duff operator and the blend mode will
-both imply a certain behavior for the 'both' part of a pixel, and if
-they don't agree, it is not clear what will happen.
-
-To get around this, the proposed specification says that a blending of
-source and destination should first be computed, and then the blended
-result should be composited with the destination, but the result is
-conceptually muddy, and the simple geometric interpretation from
-Porter/Duff is lost.
+porter/duff compositing with blend modes. In this scheme a Porter/Duff
+operator and a blend mode are chosen orthogonally.
+
+For a given pixel, the source and the destination are blended
+according to the chosen blendmode, and then a linear interpolation
+between the source and the blended result is computed with the
+destination alpha as the interpolation weight. This result is then
+used as the source in compositing with the chosen Porter/Duff
+operator. The full formula is this:
+
+ Fa * as * [ (1 - ad) * Cs + ad * B(s,d) ] + Fb * ad * Cd
+
+where Fa and Fb are weight factors corresponding to the chosen
+Porter/Duff operator.
+
+This formula has some useful properties: If the blend mode is chosen
+to be Normal, that is, B(s,d) = Cs, then the formula becomes identical
+to whatever Porter/Duff operator is involved. If the Porter/Duff
+operator is chosen to be Over, then the formula becomes identical to
+the Photoshop/Gimp layer blending. When the operator is Source, the
+formula produces the atop-like result shown above.
+
+However, there is no conceptual underlying reason that the formula has
+these properties. You can't give a graphical interpretation of it the
+way you can with Porter/Duff; the useful properties the formula has
+might as well be numerological accidents. In fact, for many operators,
+the formula produces nonsensical results.
+
+For example, here is what it looks like when the operator is DEST_OVER
+and the blend mode is ColorDodge:
+
+ DEST_OVER / COLOR_DODGE
+
+What is this? It's DEST_OVER, except that the antialiasing is messed
+up with a weird purple shadow. There is no situation where this is the
+operation you need.
+
+There is also a bunch of operators where the blend mode is simply
+ignored: DEST, DEST_IN, DEST_OUT, CLEAR.
+
+The fundamental problem is that it doesn't make any sense to select a
+Porter/Duff compositing operators and a blend mode orthogonally. The
+Porter/Duff operator and the blend mode both specify what happens in
+the region where source and destination overlaps, and if they disagree
+on what happens there, it is not clear what happens. The answer to
+this is not to pick some formula that happens to produce useful
+results in certain special cases.
diff --git a/pics.c b/pics.c
index 4c6ad23..d9b571e 100644
--- a/pics.c
+++ b/pics.c
@@ -1,3 +1,4 @@
+#include <stdio.h>
#include <cairo.h>
#define CHECK_SIZE 16
@@ -25,6 +26,9 @@ get_cairo (int width, int height)
cairo_rectangle (cr, i, j, CHECK_SIZE, CHECK_SIZE);
cairo_fill (cr);
}
+
+ cairo_push_group (cr);
+
return cr;
}
@@ -33,6 +37,10 @@ finish (cairo_t *cr, const char *filename)
{
cairo_surface_t *target = cairo_get_target (cr);
+ cairo_pop_group_to_source (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_paint (cr);
+
cairo_surface_write_to_png (target, filename);
cairo_surface_destroy (target);
cairo_destroy (cr);
@@ -65,8 +73,9 @@ draw_polygon (cairo_t *cr)
static void
create_ampersand (void)
{
- cairo_t *cr = get_cairo (80, 80);
+ cairo_t *cr = get_cairo (90, 90);
+ cairo_move_to (cr, 0, 0);
draw_ampersand (cr);
finish (cr, "ampersand.png");
@@ -75,8 +84,9 @@ create_ampersand (void)
static void
create_polygon (void)
{
- cairo_t *cr = get_cairo (80, 80);
+ cairo_t *cr = get_cairo (90, 90);
+ cairo_move_to (cr, 0, 0);
draw_polygon (cr);
finish (cr, "polygon.png");
@@ -137,72 +147,92 @@ centered_text (cairo_t *cr, double x, double y, const char *text)
cairo_restore (cr);
}
-static void
-create_porter_duff_table (void)
-{
#define CELL_WIDTH 120
#define CELL_HEIGHT 130
#define XPAD (CELL_WIDTH / 4)
#define YPAD (CELL_WIDTH / 4)
+static void
+draw_cell (cairo_t *cr,
+ double x, double y,
+ cairo_operator_t op,
+ const char *name)
+{
+ cairo_save (cr);
+
+ printf ("move to %f %f for %s\n", x, y, name);
+ cairo_rectangle (cr, x - 10, y, CELL_WIDTH + 20, CELL_HEIGHT);
+ cairo_clip (cr);
+
+ /* Cairo's source and clear are clip-to-self, but we
+ * want to draw the actual Porter/Duff operator,
+ * so those need to be special cased.
+ */
+ if (op == CAIRO_OPERATOR_SOURCE)
+ {
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_move_to (cr, x, y);
+ draw_source (cr);
+ }
+ else if (op != CAIRO_OPERATOR_CLEAR)
+ {
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_move_to (cr, x, y);
+ draw_dest (cr);
+ cairo_move_to (cr, x, y);
+ cairo_set_operator (cr, op);
+ draw_source (cr);
+ }
+
+ centered_text (cr,
+ x + CELL_WIDTH / 2.0 - 20.0,
+ y + CELL_HEIGHT - 30, name);
+
+ cairo_restore (cr);
+}
+
+static void
+create_porter_duff_table (void)
+{
cairo_t *cr = get_cairo (CELL_WIDTH * 5 + XPAD,
(CELL_HEIGHT) * 3 + 2 * YPAD);
int i;
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint (cr);
for (i = 0; i < 12; ++i)
{
double x = CELL_WIDTH * (i % 5) + XPAD;
double y = CELL_HEIGHT * (i / 5) + YPAD;
- cairo_save (cr);
-
- printf ("move to %f %f for %s\n", x, y, ops[i].name);
- cairo_push_group (cr);
-
- cairo_set_source_rgb (cr, 1, 0, 0);
- cairo_rectangle (cr, x, y, CELL_WIDTH, CELL_HEIGHT);
- cairo_clip (cr);
-
- cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
- cairo_paint (cr);
-
- /* Cairo's source and clear are clip-to-self, but we
- * want to draw the actual Porter/Duff operator,
- * so those need to be special cased.
- */
- if (ops[i].op == CAIRO_OPERATOR_SOURCE)
- {
- cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
- cairo_move_to (cr, x, y);
- draw_source (cr);
- }
- else if (ops[i].op != CAIRO_OPERATOR_CLEAR)
- {
- cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
- cairo_move_to (cr, x, y);
- draw_dest (cr);
- cairo_move_to (cr, x, y);
- cairo_set_operator (cr, ops[i].op);
- draw_source (cr);
- }
-
- centered_text (cr,
- x + CELL_WIDTH / 2.0 - 20.0,
- y + CELL_HEIGHT - 30, ops[i].name);
-
- cairo_pop_group_to_source (cr);
- cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
- cairo_paint (cr);
- cairo_restore (cr);
-
+ draw_cell (cr, x, y, ops[i].op, ops[i].name);
}
+
finish (cr, "table.png");
}
+static void
+create_op (cairo_operator_t op, const char *name, const char *filename)
+{
+ cairo_t *cr = get_cairo (CELL_WIDTH + 20, CELL_HEIGHT);
+ char fn[64] = { 0 };
+
+ cairo_move_to (cr, 0, 0);
+
+ draw_cell (cr, 30, 10, op, name);
+
+ snprintf (fn, 63, "%s.png", filename);
+ finish (cr, fn);
+}
+
int
main (int argc, char *argv)
{
create_ampersand ();
create_polygon ();
+ create_op (CAIRO_OPERATOR_COLOR_DODGE, "Color Dodge", "colordodge.png");
+ create_op (CAIRO_OPERATOR_DEST_ATOP, "Dest Atop", "destatop.png");
+ create_op (CAIRO_OPERATOR_OVER, "Over", "over.png");
create_porter_duff_table ();
}