summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSøren Sandmann <ssp@redhat.com>2013-01-08 15:59:55 -0500
committerSøren Sandmann <ssp@redhat.com>2013-01-08 15:59:55 -0500
commitc4348044e4161c5566be39a200481415413de5a7 (patch)
tree7bc11efb76ef79b8309845f342ac89d6860d6de2
initial checkin
-rw-r--r--blend.txt155
-rw-r--r--pics.c208
2 files changed, 363 insertions, 0 deletions
diff --git a/blend.txt b/blend.txt
new file mode 100644
index 0000000..d1165ec
--- /dev/null
+++ b/blend.txt
@@ -0,0 +1,155 @@
+Porter/Duff, cardboard, blend modes, and CSS compositing
+
+Porter/Duff
+
+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.
+
+Consider these two images:
+
+ SRC ("&") DEST ("triangle")
+
+If we combine them and zoom in on one pixel, we get this:
+
+ \ /
+ \ /
+ \/
+ SRC x DEST /\
+ / \
+ / \
+
+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.
+
+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 source image is tailored by the background
+image, and then held up in front of it:
+
+ SRC atop DEST
+
+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'.
+
+Here are all of the Porter/Duff operators:
+
+
+
+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.
+
+
+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:
+
+ Source-only: Source
+ Destination-only: Destination
+ Both: Source
+
+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
+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
+modified to be something other than 'source'. For example the
+'SoftLight' blend mode computes a blend of source and destination
+according to this formula:
+
+ <softlight formula>
+
+This looks like this:
+
+ src SOFTLIGHT dest
+
+And if we zoom in on a pixel, we get this:
+
+ zoomed in on a pixel
+
+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.
+
+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.
+
+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.
+
+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:
+
+ 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:
+
+ - Is the source tailored to the destination?
+ - Is the destination tailored to the source?
+ - What happens in the area where both source and destination are present?
+
+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.
+
+
+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.
diff --git a/pics.c b/pics.c
new file mode 100644
index 0000000..4c6ad23
--- /dev/null
+++ b/pics.c
@@ -0,0 +1,208 @@
+#include <cairo.h>
+
+#define CHECK_SIZE 16
+
+static cairo_t *
+get_cairo (int width, int height)
+{
+ cairo_surface_t *target =
+ cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+ cairo_t *cr;
+ int i, j;
+
+ cr = cairo_create (target);
+
+ for (i = 0; i < width; i += CHECK_SIZE)
+ for (j = 0; j < height; j += CHECK_SIZE)
+ {
+ int light = !!(((i / CHECK_SIZE) ^ (j / CHECK_SIZE)) & 1);
+
+ if (light)
+ cairo_set_source_rgb (cr, 0.95, 0.95, 0.95);
+ else
+ cairo_set_source_rgb (cr, 0.9, 0.9, 0.9);
+
+ cairo_rectangle (cr, i, j, CHECK_SIZE, CHECK_SIZE);
+ cairo_fill (cr);
+ }
+ return cr;
+}
+
+static void
+finish (cairo_t *cr, const char *filename)
+{
+ cairo_surface_t *target = cairo_get_target (cr);
+
+ cairo_surface_write_to_png (target, filename);
+ cairo_surface_destroy (target);
+ cairo_destroy (cr);
+}
+
+static void
+draw_ampersand (cairo_t *cr)
+{
+ cairo_rel_move_to (cr, 5, 70.0);
+ cairo_select_font_face (cr, "Sans",
+ CAIRO_FONT_SLANT_NORMAL,
+ CAIRO_FONT_WEIGHT_BOLD);
+ cairo_set_font_size (cr, 80);
+ cairo_text_path (cr, "&");
+ cairo_set_source_rgb (cr, 0.2, 0.2, 0.4);
+ cairo_fill (cr);
+}
+
+static void
+draw_polygon (cairo_t *cr)
+{
+ cairo_rel_move_to (cr, 10, 10);
+ cairo_rel_line_to (cr, 65, 35);
+ cairo_rel_line_to (cr, -38, 35);
+
+ cairo_set_source_rgb (cr, 0.8, 0.3, 0.2);
+ cairo_fill (cr);
+}
+
+static void
+create_ampersand (void)
+{
+ cairo_t *cr = get_cairo (80, 80);
+
+ draw_ampersand (cr);
+
+ finish (cr, "ampersand.png");
+}
+
+static void
+create_polygon (void)
+{
+ cairo_t *cr = get_cairo (80, 80);
+
+ draw_polygon (cr);
+
+ finish (cr, "polygon.png");
+}
+
+static struct {
+ char name[16];
+ cairo_operator_t op;
+} ops[] =
+ {
+ "Source", CAIRO_OPERATOR_SOURCE,
+ "Over", CAIRO_OPERATOR_OVER,
+ "Atop", CAIRO_OPERATOR_ATOP,
+ "In", CAIRO_OPERATOR_IN,
+ "Out", CAIRO_OPERATOR_OUT,
+
+ "Dest", CAIRO_OPERATOR_DEST,
+ "Dest Over", CAIRO_OPERATOR_DEST_OVER,
+ "Dest Atop", CAIRO_OPERATOR_DEST_ATOP,
+ "Dest In", CAIRO_OPERATOR_DEST_IN,
+ "Dest Out", CAIRO_OPERATOR_DEST_OUT,
+
+ "Clear", CAIRO_OPERATOR_CLEAR,
+ "Xor", CAIRO_OPERATOR_XOR,
+ };
+
+static void
+draw_source (cairo_t *cr)
+{
+ draw_polygon (cr);
+}
+
+static void
+draw_dest (cairo_t *cr)
+{
+ draw_ampersand (cr);
+}
+
+static void
+centered_text (cairo_t *cr, double x, double y, const char *text)
+{
+ double x1, y1, x2, y2;
+
+ cairo_save (cr);
+ cairo_select_font_face (cr, "Sans",
+ CAIRO_FONT_SLANT_NORMAL,
+ CAIRO_FONT_WEIGHT_NORMAL);
+ cairo_new_path (cr);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_set_font_size (cr, 14.0);
+ cairo_text_path (cr, text);
+ cairo_path_extents (cr, &x1, &y1, &x2, &y2);
+ cairo_new_path (cr);
+ printf ("in x %f %f %s\n", x, x2 - x1, text);
+ cairo_move_to (cr, x - (x2 - x1) / 2.0, y);
+ cairo_show_text (cr, 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)
+
+ cairo_t *cr = get_cairo (CELL_WIDTH * 5 + XPAD,
+ (CELL_HEIGHT) * 3 + 2 * YPAD);
+ int i;
+
+ 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);
+
+ }
+ finish (cr, "table.png");
+}
+
+int
+main (int argc, char *argv)
+{
+ create_ampersand ();
+ create_polygon ();
+ create_porter_duff_table ();
+}