diff options
author | Søren Sandmann <ssp@redhat.com> | 2013-01-08 15:59:55 -0500 |
---|---|---|
committer | Søren Sandmann <ssp@redhat.com> | 2013-01-08 15:59:55 -0500 |
commit | c4348044e4161c5566be39a200481415413de5a7 (patch) | |
tree | 7bc11efb76ef79b8309845f342ac89d6860d6de2 |
initial checkin
-rw-r--r-- | blend.txt | 155 | ||||
-rw-r--r-- | pics.c | 208 |
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. @@ -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 (); +} |