#include #include #define CHECK_SIZE 16 #define SOURCE_COLOR 0.8, 0.3, 0.2 #define DEST_COLOR 0.2, 0.2, 0.4 #define LIGHT 0.95 #define DARK 0.87 #define LIGHT_CHECK LIGHT, LIGHT, LIGHT #define DARK_CHECK DARK, DARK, DARK #define SCALE_FACTOR 0.8 #define TEXT_SIZE (1 / SCALE_FACTOR) * 14.0 #define SUBHEADER_SIZE (1 / SCALE_FACTOR) * 16.0 #define HEADER_SIZE (1 / SCALE_FACTOR) * 18.0 static cairo_t * get_cairo (int width, int height) { cairo_surface_t *target = cairo_image_surface_create ( CAIRO_FORMAT_ARGB32, SCALE_FACTOR * width + 0.5, SCALE_FACTOR * height + 0.5); cairo_t *cr; int i, j; cr = cairo_create (target); cairo_set_source_rgb (cr, DARK_CHECK); cairo_paint (cr); cairo_scale (cr, SCALE_FACTOR, SCALE_FACTOR); 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, LIGHT_CHECK); cairo_rectangle (cr, i, j, CHECK_SIZE, CHECK_SIZE); cairo_fill (cr); } } cairo_push_group (cr); return cr; } static void 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); } static void ampersand_path (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, "&"); } static void draw_ampersand (cairo_t *cr) { ampersand_path (cr); cairo_set_source_rgb (cr, DEST_COLOR); cairo_fill (cr); } static void polygon_path (cairo_t *cr) { cairo_rel_move_to (cr, 10, 10); cairo_rel_line_to (cr, 65, 38); cairo_rel_line_to (cr, -35, 32); } static void draw_polygon (cairo_t *cr) { polygon_path (cr); cairo_set_source_rgb (cr, SOURCE_COLOR); cairo_fill (cr); } static void create_ampersand (void) { cairo_t *cr = get_cairo (90, 90); cairo_move_to (cr, 0, 0); draw_ampersand (cr); finish (cr, "ampersand.png"); } static void create_polygon (void) { cairo_t *cr = get_cairo (90, 90); cairo_move_to (cr, 0, 0); draw_polygon (cr); finish (cr, "polygon.png"); } typedef struct { char name[16]; cairo_operator_t op; } op_info_t; 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_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_font_size (cr, TEXT_SIZE); 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); } #define CELL_WIDTH 128 #define CELL_HEIGHT 128 #define XPAD (CELL_WIDTH / 4) #define YPAD (32) static void cell_text (cairo_t *cr, double x, double y, const char *text) { cairo_set_source_rgb (cr, 0, 0, 0); centered_text (cr, x + CELL_WIDTH / 2.0 - 20.0, y + CELL_HEIGHT - SCALE_FACTOR * 30, text); } 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_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 (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); } cell_text (cr, x, y, name); cairo_restore (cr); } #define COLS 4 static void draw_table (cairo_t *cr, const op_info_t *ops, int n_ops, int n_columns) { int i; cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); cairo_paint (cr); for (i = 0; i < n_ops; ++i) { double x = CELL_WIDTH * (i % n_columns) + XPAD; double y = CELL_HEIGHT * (i / n_columns) + YPAD; draw_cell (cr, x, y, ops[i].op, ops[i].name); } } static void create_porter_duff_table (void) { static const op_info_t ops[] = { { "Source", CAIRO_OPERATOR_SOURCE }, { "Atop", CAIRO_OPERATOR_ATOP }, { "Over", CAIRO_OPERATOR_OVER }, { "In", CAIRO_OPERATOR_IN }, { "Out", CAIRO_OPERATOR_OUT }, { "Dest", CAIRO_OPERATOR_DEST }, { "Dest Atop", CAIRO_OPERATOR_DEST_ATOP }, { "Dest Over", CAIRO_OPERATOR_DEST_OVER }, { "Dest In", CAIRO_OPERATOR_DEST_IN }, { "Dest Out", CAIRO_OPERATOR_DEST_OUT }, { "Clear", CAIRO_OPERATOR_CLEAR }, { "Xor", CAIRO_OPERATOR_XOR }, }; cairo_t *cr = get_cairo (CELL_WIDTH * 5 + XPAD, (CELL_HEIGHT) * 3 + 2 * YPAD); draw_table (cr, ops, sizeof (ops) / sizeof (ops[0]), 5); finish (cr, "table.png"); } static void draw_blend_none (cairo_t *cr, cairo_operator_t blend, double x, double y) { cairo_push_group (cr); cairo_push_group (cr); cairo_set_source_rgb (cr, DEST_COLOR); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_paint (cr); cairo_set_operator (cr, blend); cairo_set_source_rgb (cr, SOURCE_COLOR); cairo_paint (cr); cairo_pop_group_to_source (cr); cairo_move_to (cr, x, y); ampersand_path (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_fill (cr); cairo_pop_group_to_source (cr); cairo_move_to (cr, x, y); polygon_path (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_fill (cr); } static void draw_blend_source (cairo_t *cr, cairo_operator_t blend, double x, double y) { cairo_push_group (cr); cairo_push_group (cr); cairo_set_source_rgb (cr, SOURCE_COLOR); cairo_paint (cr); cairo_push_group (cr); cairo_set_source_rgb (cr, DEST_COLOR); cairo_paint (cr); cairo_set_source_rgb (cr, SOURCE_COLOR); cairo_set_operator (cr, blend); cairo_paint (cr); cairo_pop_group_to_source (cr); cairo_move_to (cr, x, y); ampersand_path (cr); cairo_fill (cr); cairo_pop_group_to_source (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_paint (cr); cairo_pop_group_to_source (cr); cairo_move_to (cr, x, y); polygon_path (cr); cairo_fill (cr); } static void draw_blend_dest (cairo_t *cr, cairo_operator_t blend, double x, double y) { cairo_move_to (cr, x, y); draw_dest (cr); cairo_push_group (cr); cairo_push_group (cr); cairo_set_source_rgb (cr, DEST_COLOR); cairo_paint (cr); cairo_set_source_rgb (cr, SOURCE_COLOR); cairo_set_operator (cr, blend); cairo_paint (cr); cairo_pop_group_to_source (cr); cairo_move_to (cr, x, y); ampersand_path (cr); cairo_fill (cr); cairo_pop_group_to_source (cr); cairo_move_to (cr, x, y); polygon_path (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_fill (cr); } static void draw_blend_row (cairo_t *cr, double x, double y, cairo_operator_t blend, const char *name) { char text[128]; #define ENDASH "\342\200\223" draw_blend_none (cr, blend, x, y); sprintf (text, "%s None", name); cell_text (cr, x, y, ENDASH); x += CELL_WIDTH; draw_blend_source (cr, blend, x, y); sprintf (text, "%s Source", name); cell_text (cr, x, y, ENDASH); x += CELL_WIDTH; draw_blend_dest (cr, blend, x, y); sprintf (text, "%s Dest", name); cell_text (cr, x, y, ENDASH); x += CELL_WIDTH; draw_cell (cr, x, y, blend, ""); sprintf (text, "%s Both", name); cell_text (cr, x, y, ENDASH); } #define FIRSTCOL 160 static void blend_header (cairo_t *cr, double x, double y, const char *header) { cairo_save (cr); cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size (cr, SUBHEADER_SIZE); cairo_move_to (cr, XPAD - FIRSTCOL + 8, y + CELL_HEIGHT / 2 + 6); cairo_show_text (cr, header); cairo_restore (cr); } static void create_color_dodge_table (void) { static const op_info_t ops[] = { { "Clear", CAIRO_OPERATOR_CLEAR }, { "Out", CAIRO_OPERATOR_OUT }, { "Dest Out", CAIRO_OPERATOR_DEST_OUT }, { "Xor", CAIRO_OPERATOR_XOR }, { "In", CAIRO_OPERATOR_IN, }, { "Source", CAIRO_OPERATOR_SOURCE }, { "Atop", CAIRO_OPERATOR_ATOP }, { "Over", CAIRO_OPERATOR_OVER }, { "Dest In", CAIRO_OPERATOR_DEST_IN }, { "Dest Atop", CAIRO_OPERATOR_DEST_ATOP }, { "Dest", CAIRO_OPERATOR_DEST }, { "Dest Over", CAIRO_OPERATOR_DEST_OVER }, }; #define N_ROWS 9 cairo_t *cr = get_cairo (CELL_WIDTH * 4 + XPAD + FIRSTCOL, (CELL_HEIGHT) * N_ROWS + 4 * YPAD); double x, y; int n_columns = 4; int i; cairo_rectangle (cr, 0, 0, CELL_WIDTH * 4 + XPAD + FIRSTCOL, 3 * YPAD); cairo_rectangle (cr, 0, 3 * YPAD + N_ROWS * CELL_HEIGHT, CELL_WIDTH * 4 + XPAD + FIRSTCOL, 3 * YPAD); cairo_set_source_rgb (cr, LIGHT_CHECK); cairo_fill (cr); cairo_translate (cr, 0, 2 * YPAD); for (i = YPAD + CELL_HEIGHT; i < CELL_HEIGHT * N_ROWS + 2 * YPAD; i += 2 * CELL_HEIGHT) { cairo_set_source_rgba (cr, 1, 1, 1, 0.3); cairo_rectangle (cr, 0, i, CELL_WIDTH * 4 + XPAD + FIRSTCOL, CELL_HEIGHT); cairo_fill (cr); } cairo_set_source_rgb (cr, 0, 0, 0); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_font_size (cr, HEADER_SIZE); cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_move_to (cr, XPAD, -YPAD / 2); cairo_show_text (cr, "Blend Mode"); cairo_move_to (cr, XPAD + FIRSTCOL + 110, - YPAD/2); cairo_show_text (cr, "Source and Dest Regions"); cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size (cr, TEXT_SIZE); cairo_move_to (cr, XPAD + FIRSTCOL + 7, YPAD / 2); cairo_show_text (cr, "Neither"); cairo_move_to (cr, XPAD + FIRSTCOL + 2 + CELL_WIDTH, YPAD / 2); cairo_show_text (cr, "Source"); cairo_move_to (cr, XPAD + FIRSTCOL + 17 + 2 * CELL_WIDTH, YPAD / 2); cairo_show_text (cr, "Dest"); cairo_move_to (cr, XPAD + FIRSTCOL + 17 + 3 * CELL_WIDTH, YPAD / 2); cairo_show_text (cr, "Both"); cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size (cr, TEXT_SIZE); cairo_move_to (cr, XPAD, YPAD + CELL_HEIGHT / 2 + 6); cairo_show_text (cr, "Zero"); cairo_move_to (cr, XPAD, YPAD + CELL_HEIGHT + CELL_HEIGHT / 2 + 6); cairo_show_text (cr, "Source"); cairo_move_to (cr, XPAD, YPAD + 2 * CELL_HEIGHT + CELL_HEIGHT / 2 + 6); cairo_show_text (cr, "Dest"); cairo_push_group (cr); cairo_translate (cr, FIRSTCOL - 8, 8); draw_table (cr, ops, sizeof (ops) / sizeof (ops[0]), n_columns); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); x = CELL_WIDTH * (12 % n_columns) + XPAD; y = CELL_HEIGHT * (12 / n_columns) + YPAD; draw_blend_row (cr, x, y, CAIRO_OPERATOR_SCREEN, "Screen"); blend_header (cr, x, y, "Screen"); y += CELL_HEIGHT; draw_blend_row (cr, x, y, CAIRO_OPERATOR_MULTIPLY, "Multiply"); blend_header (cr, x, y, "Multiply"); y += CELL_HEIGHT; draw_blend_row (cr, x, y, CAIRO_OPERATOR_COLOR_DODGE, "CDodge"); blend_header (cr, x, y, "Color Dodge"); y += CELL_HEIGHT; draw_blend_row (cr, x, y, CAIRO_OPERATOR_HSL_LUMINOSITY, "HSL Lum"); blend_header (cr, x, y, "HSL Luminosity"); y += CELL_HEIGHT; draw_blend_row (cr, x, y, CAIRO_OPERATOR_HARD_LIGHT, "HLight"); blend_header (cr, x, y, "Hard Light"); y += CELL_HEIGHT; /* draw_blend_row (cr, x, y, CAIRO_OPERATOR_SOFT_LIGHT, "SLight"); */ /* blend_header (cr, x, y, "Soft Light"); */ /* y += CELL_HEIGHT; */ draw_blend_row (cr, x, y, CAIRO_OPERATOR_ADD, "SLight"); blend_header (cr, x, y, "Plus"); y += CELL_HEIGHT; cairo_pop_group_to_source (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_paint (cr); finish (cr, "colordodge-table.png"); } static void create_op (cairo_operator_t op, const char *name, const char *filename) { cairo_t *cr = get_cairo (CELL_WIDTH, CELL_HEIGHT); cairo_move_to (cr, 0, 0); draw_cell (cr, 20, 16, op, name); finish (cr, filename); } static void clear_to_white (cairo_t *cr) { cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgb (cr, 1, 1, 1); cairo_paint (cr); } static void draw_pixel_box (cairo_t *cr) { cairo_save (cr); cairo_set_line_width (cr, 1.4142135623730951); cairo_set_source_rgb (cr, 0, 0, 0); cairo_rectangle (cr, 1, 1, 178, 178); cairo_move_to (cr, 1, 1); cairo_line_to (cr, 179, 179); cairo_move_to (cr, 179, 1); cairo_line_to (cr, 1, 179); cairo_stroke (cr); cairo_restore (cr); } static void general_pixel_diagram (const char *filename) { cairo_t *cr = get_cairo (180, 180); clear_to_white (cr); draw_pixel_box (cr); cairo_set_source_rgb (cr, 0, 0, 0); centered_text (cr, 90, 45, "Both"); centered_text (cr, 35, 90, "Source"); centered_text (cr, 145, 90, "Dest"); centered_text (cr, 90, 147, "Neither"); finish (cr, filename); } static void over_diagram (const char *filename) { cairo_t *cr = get_cairo (180, 180); cairo_move_to (cr, 1, 1); cairo_line_to (cr, 90, 90); cairo_line_to (cr, 179, 1); cairo_set_source_rgb (cr, SOURCE_COLOR); cairo_fill (cr); cairo_set_source_rgb (cr, 1, 1, 1); centered_text (cr, 90, 45, "Both"); cairo_move_to (cr, 1, 1); cairo_line_to (cr, 90, 90); cairo_line_to (cr, 1, 179); cairo_set_source_rgb (cr, SOURCE_COLOR); cairo_fill (cr); cairo_set_source_rgb (cr, 1, 1, 1); centered_text (cr, 35, 90, "Source"); cairo_move_to (cr, 179, 1); cairo_line_to (cr, 90, 90); cairo_line_to (cr, 179, 179); cairo_set_source_rgb (cr, DEST_COLOR); cairo_fill (cr); cairo_set_source_rgb (cr, 1, 1, 1); centered_text (cr, 145, 90, "Dest"); cairo_set_source_rgb (cr, 0, 0, 0); centered_text (cr, 90, 147, "Neither"); cairo_new_path (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); draw_pixel_box (cr); finish (cr, filename); } static void dest_atop_diagram (const char *filename) { cairo_t *cr = get_cairo (180, 180); cairo_move_to (cr, 1, 1); cairo_line_to (cr, 90, 90); cairo_line_to (cr, 179, 1); cairo_set_source_rgb (cr, DEST_COLOR); cairo_fill (cr); cairo_set_source_rgb (cr, 1, 1, 1); centered_text (cr, 90, 45, "Both"); cairo_move_to (cr, 1, 1); cairo_line_to (cr, 90, 90); cairo_line_to (cr, 1, 179); cairo_set_source_rgb (cr, SOURCE_COLOR); cairo_fill (cr); cairo_set_source_rgb (cr, 1, 1, 1); centered_text (cr, 35, 90, "Source"); cairo_set_source_rgb (cr, 0, 0, 0); centered_text (cr, 145, 90, "Dest"); centered_text (cr, 90, 147, "Neither"); cairo_new_path (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); draw_pixel_box (cr); finish (cr, filename); } static void color_dodge_diagram (const char *filename) { cairo_t *cr = get_cairo (180, 180); cairo_move_to (cr, 1, 1); cairo_line_to (cr, 90, 90); cairo_line_to (cr, 179, 1); cairo_set_source_rgb (cr, DEST_COLOR); cairo_fill_preserve (cr); cairo_set_source_rgb (cr, SOURCE_COLOR); cairo_set_operator (cr, CAIRO_OPERATOR_COLOR_DODGE); cairo_fill (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_source_rgb (cr, 1, 1, 1); centered_text (cr, 90, 45, "Both"); cairo_move_to (cr, 1, 1); cairo_line_to (cr, 90, 90); cairo_line_to (cr, 1, 179); cairo_set_source_rgb (cr, SOURCE_COLOR); cairo_fill (cr); cairo_move_to (cr, 179, 1); cairo_line_to (cr, 90, 90); cairo_line_to (cr, 179, 179); cairo_set_source_rgb (cr, DEST_COLOR); cairo_fill (cr); cairo_set_source_rgb (cr, 1, 1, 1); centered_text (cr, 35, 90, "Source"); centered_text (cr, 145, 90, "Dest"); cairo_set_source_rgb (cr, 0, 0, 0); centered_text (cr, 90, 147, "Neither"); draw_pixel_box (cr); finish (cr, filename); } static void color_dodge_dest_diagram (const char *filename) { cairo_t *cr = get_cairo (180, 180); cairo_move_to (cr, 1, 1); cairo_line_to (cr, 90, 90); cairo_line_to (cr, 179, 1); cairo_set_source_rgb (cr, DEST_COLOR); cairo_fill_preserve (cr); cairo_set_source_rgb (cr, SOURCE_COLOR); cairo_set_operator (cr, CAIRO_OPERATOR_COLOR_DODGE); cairo_fill (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_source_rgb (cr, 1, 1, 1); centered_text (cr, 90, 45, "Both"); cairo_move_to (cr, 179, 1); cairo_line_to (cr, 90, 90); cairo_line_to (cr, 179, 179); cairo_set_source_rgb (cr, DEST_COLOR); cairo_fill (cr); cairo_set_source_rgb (cr, 0, 0, 0); centered_text (cr, 35, 90, "Source"); cairo_set_source_rgb (cr, 1, 1, 1); centered_text (cr, 145, 90, "Dest"); cairo_set_source_rgb (cr, 0, 0, 0); centered_text (cr, 90, 147, "Neither"); draw_pixel_box (cr); finish (cr, filename); } static void color_dodge_none (void) { cairo_t *cr = get_cairo (CELL_WIDTH, CELL_HEIGHT); draw_blend_none (cr, CAIRO_OPERATOR_COLOR_DODGE, 20, 16); finish (cr, "colordodge-none.png"); } static void color_dodge_dest (void) { cairo_t *cr = get_cairo (CELL_WIDTH, CELL_HEIGHT); draw_blend_dest (cr, CAIRO_OPERATOR_COLOR_DODGE, 20, 16); finish (cr, "colordodge-dest.png"); } static void color_dodge_source (void) { cairo_t *cr = get_cairo (CELL_WIDTH, CELL_HEIGHT); draw_blend_source (cr, CAIRO_OPERATOR_COLOR_DODGE, 20, 16); cell_text (cr, 20, 16, ""); finish (cr, "colordodge-source.png"); } int main (int argc, char **argv) { create_op (CAIRO_OPERATOR_SOURCE, "Source", "source.png"); create_op (CAIRO_OPERATOR_DEST, "Destination", "dest.png"); create_op (CAIRO_OPERATOR_DEST_ATOP, "Dest Atop", "destatop.png"); create_op (CAIRO_OPERATOR_OVER, "Over", "over.png"); create_porter_duff_table (); general_pixel_diagram ("diagram.png"); over_diagram ("over-diagram.png"); dest_atop_diagram ("destatop-diagram.png"); color_dodge_diagram ("colordodge-diagram.png"); color_dodge_dest_diagram ("colordodge-dest-diagram.png"); color_dodge_none (); color_dodge_source (); color_dodge_dest (); create_op (CAIRO_OPERATOR_COLOR_DODGE, "Color Dodge", "colordodge-both.png"); create_color_dodge_table (); return 0; }