1 /* cc `pkg-config --cflags --libs cairo-xlib` cairo-spline.c -o cairo-spline */
3 /* Copyright © 2005 Carl Worth
5 * Permission is hereby granted, free of charge, to any person
6 * obtaining a copy of this software and associated documentation
7 * files (the "Software"), to deal in the Software without
8 * restriction, including without limitation the rights to use, copy,
9 * modify, merge, publish, distribute, sublicense, and/or sell copies
10 * of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32 #include <cairo-xlib.h>
33 #include <X11/extensions/Xrender.h>
35 #define EPSILON (1.0 / (2<<16))
36 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
38 typedef struct color {
49 typedef struct spline {
53 typedef struct quadr {
70 cairo_line_cap_t line_cap;
81 typedef struct callback_doc {
86 typedef int (*key_callback_t)(win_t *win);
88 typedef struct key_binding
93 key_callback_t callback;
96 static void win_init(win_t *win, Display *dpy);
97 static void win_deinit(win_t *win);
98 static void win_refresh(win_t *win);
99 static void win_select_events(win_t *win);
100 static void win_handle_events(win_t *win);
101 static void win_print_help(win_t *win);
103 static int quit_cb(win_t *win);
104 static int print_spline_cb(win_t *win);
105 static int zoom_in_cb(win_t *win);
106 static int zoom_out_cb(win_t *win);
107 static int trans_left_cb(win_t *win);
108 static int trans_right_cb(win_t *win);
109 static int trans_up_cb(win_t *win);
110 static int trans_down_cb(win_t *win);
111 static int flatten_cb(win_t *win);
112 static int smooth_cb(win_t *win);
113 static int widen_line(win_t *win);
114 static int narrow_line(win_t *win);
115 static int butt_cap_cb(win_t *win);
116 static int round_cap_cb(win_t *win);
118 static const double DEFAULT_XTRANS = 0.0;
119 static const double DEFAULT_YTRANS = 0.0;
120 static const double DEFAULT_TOLERANCE = .1;
121 static const double DEFAULT_ZOOM = 1.0;
122 static const cairo_line_cap_t DEFAULT_LINE_CAP = CAIRO_LINE_CAP_BUTT;
124 /* Showing off the problems with wide butt-capped splines that turn
125 sharply at the end. */
126 static const spline_t funky_fangs = {
127 { { 69.25, 48.185 }, { 40.225, 43.06 }, { 59.5, 34.5 }, { 59.4998, 35.2514 } }
130 /* A simple looping spline. */
131 static const spline_t ribbon = {
132 { {110, 20}, {310, 300}, {10, 310}, {210, 20} }
135 /* An even more difficult spline, (showing bugs in the "contour-based
136 * stroking" in cairo 1.12) */
137 static const spline_t contour_bugs = {
138 { { 207.25, 221.185 }, { 212.225, 229.06 }, { 211.5, 222.5 }, { 210.5, 224.5 } }
141 #define DEFAULT_SPLINE contour_bugs
142 static const double DEFAULT_LINE_WIDTH = 160;
144 static const callback_doc_t callback_doc[] = {
145 { quit_cb, "Exit the program" },
146 { print_spline_cb, "Print current spline coordinates on stdout" },
147 { zoom_in_cb, "Zoom in" },
148 { zoom_out_cb, "Zoom out" },
149 { trans_left_cb, "Translate left" },
150 { trans_right_cb, "Translate right" },
151 { trans_up_cb, "Translate up" },
152 { trans_down_cb, "Translate down" },
153 { flatten_cb, "Decrease rendering accuracy, (tolerance *= 10)" },
154 { smooth_cb, "Increase rendering accuracy, (tolerance /= 10)" },
155 { widen_line, "Widen line width" },
156 { narrow_line, "Narrow line width" },
157 { butt_cap_cb, "Set butt cap" },
158 { round_cap_cb, "Set round cap" },
161 static key_binding_t key_binding[] = {
162 /* Keysym, Alias, Keycode, callback */
163 { "Q", 0, 0, quit_cb },
164 { "Left", 0, 0, trans_left_cb },
165 { "Right", 0, 0, trans_right_cb },
166 { "Up", 0, 0, trans_up_cb },
167 { "Down", 0, 0, trans_down_cb },
168 { "Return", 0, 0, print_spline_cb },
169 { "plus", 0, 0, zoom_in_cb },
170 { "equal", 1, 0, zoom_in_cb },
171 { "minus", 0, 0, zoom_out_cb },
172 { "greater",0, 0, smooth_cb },
173 { "period", 1, 0, smooth_cb },
174 { "less", 0, 0, flatten_cb },
175 { "comma", 1, 0, flatten_cb },
176 { "W", 0, 0, widen_line },
177 { "N", 0, 0, narrow_line },
178 { "B", 0, 0, butt_cap_cb },
179 { "R", 0, 0, round_cap_cb },
183 main(int argc, char *argv[])
187 Display *dpy = XOpenDisplay(0);
190 fprintf(stderr, "Failed to open display: %s\n", XDisplayName(0));
196 win_print_help(&win);
198 win_handle_events(&win);
208 draw_control_line(cairo_t *cr, pt_t *a, pt_t *b, double width)
212 cairo_set_source_rgb(cr, 0, 0, 1);
213 cairo_set_line_width(cr, width);
215 cairo_move_to(cr, a->x, a->y);
216 cairo_line_to(cr, b->x, b->y);
223 draw_spline(cairo_t *cr, win_t *win)
225 spline_t *spline = &win->spline;
226 double zoom = win->zoom;
227 double drag_user_x = win->drag_pt.x;
228 double drag_user_y = win->drag_pt.y;
232 cairo_device_to_user (cr, &drag_user_x, &drag_user_y);
236 cairo_move_to(cr, spline->pt[0].x, spline->pt[0].y);
238 spline->pt[1].x, spline->pt[1].y,
239 spline->pt[2].x, spline->pt[2].y,
240 spline->pt[3].x, spline->pt[3].y);
243 if (win->click && cairo_in_stroke (cr, drag_user_x, drag_user_y)) {
249 cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
256 cairo_set_line_width (cr, 2);
257 cairo_move_to(cr, spline->pt[0].x, spline->pt[0].y);
259 spline->pt[1].x, spline->pt[1].y,
260 spline->pt[2].x, spline->pt[2].y,
261 spline->pt[3].x, spline->pt[3].y);
262 cairo_set_source_rgb (cr, 1.0, 1.0, 0.0);
266 draw_control_line(cr, &spline->pt[0], &spline->pt[1], 2.0 / zoom);
267 draw_control_line(cr, &spline->pt[3], &spline->pt[2], 2.0 / zoom);
269 for (i=0; i < 4; i++) {
272 cairo_set_source_rgba (cr, 1, 0, 0, 0.5);
276 spline->pt[i].x, spline->pt[i].y,
278 if (win->click && cairo_in_fill (cr, drag_user_x, drag_user_y)) {
279 win->active = (1<<i);
291 win_refresh(win_t *win)
293 Display *dpy = win->dpy;
295 cairo_surface_t *surface;
297 cairo_status_t status;
299 XFillRectangle(dpy, win->pix, win->gc, 0, 0, win->width, win->height);
301 surface = cairo_xlib_surface_create (dpy, win->pix,
302 DefaultVisual (dpy, win->scr),
303 win->width, win->height);
304 cr = cairo_create(surface);
306 cairo_set_source_rgb(cr, 0, 0, 0);
308 cairo_set_line_width(cr, win->line_width);
309 cairo_set_line_cap(cr, win->line_cap);
310 cairo_translate(cr, win->xtrans, win->ytrans);
311 cairo_scale(cr, win->zoom, win->zoom);
312 cairo_set_tolerance(cr, win->tolerance);
314 draw_spline(cr, win);
316 status = cairo_status(cr);
318 fprintf(stderr, "Cairo is unhappy: %s\n",
319 cairo_status_to_string(status));
323 cairo_surface_destroy (surface);
325 XCopyArea(win->dpy, win->pix, win->win, win->gc,
326 0, 0, win->width, win->height,
331 find_true_color_visual (Display *dpy, int scr, XVisualInfo *result)
334 XVisualInfo template;
336 XRenderPictFormat *format;
339 template.screen = scr;
340 template.class = TrueColor;
341 xvi = XGetVisualInfo (dpy,
357 win_init(win_t *win, Display *dpy)
363 XSetWindowAttributes wattr;
370 root = DefaultRootWindow(dpy);
371 win->scr = DefaultScreen(dpy);
373 if (! find_true_color_visual (dpy, DefaultScreen (dpy), &xvi)) {
374 printf ("No TrueColor visual available, exiting.\n");
378 wattr.event_mask = ExposureMask | StructureNotifyMask;
379 wattr.background_pixel = 0;
380 wattr.border_pixel = 0;
381 wattr.colormap = XCreateColormap (dpy, root, xvi.visual, AllocNone);
382 wmask = CWEventMask | CWBackPixel | CWBorderPixel | CWColormap;
384 win->win = XCreateWindow(dpy, root, 0, 0,
385 win->width, win->height, 0,
386 xvi.depth, InputOutput, xvi.visual,
389 win->pix = XCreatePixmap(dpy, win->win, win->width, win->height, xvi.depth);
390 gcv.foreground = WhitePixel(dpy, win->scr);
391 win->gc = XCreateGC(dpy, win->pix, GCForeground, &gcv);
392 XFillRectangle(dpy, win->pix, win->gc, 0, 0, win->width, win->height);
394 for (i=0; i < ARRAY_SIZE(key_binding); i++) {
396 keysym = XStringToKeysym(key_binding[i].key);
397 if (keysym == NoSymbol)
398 fprintf(stderr, "ERROR: No keysym for \"%s\"\n", key_binding[i].key);
400 key_binding[i].keycode = XKeysymToKeycode(dpy, keysym);
404 win->spline = DEFAULT_SPLINE;
405 win->tolerance = DEFAULT_TOLERANCE;
406 win->line_width = DEFAULT_LINE_WIDTH;
407 win->line_cap = DEFAULT_LINE_CAP;
408 win->zoom = DEFAULT_ZOOM;
409 win->xtrans = DEFAULT_XTRANS;
410 win->ytrans = DEFAULT_YTRANS;
413 win->drag_pt.x = 0.0;
414 win->drag_pt.y = 0.0;
417 win->needs_refresh = 0;
419 win_select_events(win);
421 XMapWindow(dpy, win->win);
425 win_deinit(win_t *win)
427 XFreeGC(win->dpy, win->gc);
428 XFreePixmap(win->dpy, win->pix);
429 XDestroyWindow(win->dpy, win->win);
433 win_select_events(win_t *win)
440 | StructureNotifyMask
442 XSelectInput(win->dpy, win->win, win->event_mask);
447 get_callback_doc(void *callback)
451 for (i=0; i < ARRAY_SIZE(callback_doc); i++)
452 if (callback_doc[i].callback == callback)
453 return callback_doc[i].doc;
455 return "<undocumented function>";
459 win_print_help(win_t *win)
463 printf("A cairo spline demonstration\n");
464 printf("Click and drag to move the spline or adjust its controls. Or:\n\n");
466 for (i=0; i < ARRAY_SIZE(key_binding); i++)
467 if (! key_binding[i].is_alias)
470 get_callback_doc(key_binding[i].callback));
474 win_handle_button_press(win_t *win, XButtonEvent *bev)
477 win->drag_pt.x = bev->x;
478 win->drag_pt.y = bev->y;
480 win->needs_refresh = 1;
484 win_handle_motion(win_t *win, XMotionEvent *mev)
488 if (win->active == 0)
491 for (i = 0; i < 4; i++) {
492 if (((1<<i) & win->active) == 0)
494 win->spline.pt[i].x += (mev->x - win->drag_pt.x) / win->zoom;
495 win->spline.pt[i].y += (mev->y - win->drag_pt.y) / win->zoom;
497 win->needs_refresh = 1;
500 win->drag_pt.x = mev->x;
501 win->drag_pt.y = mev->y;
505 win_handle_key_press(win_t *win, XKeyEvent *kev)
509 for (i=0; i < ARRAY_SIZE(key_binding); i++)
510 if (key_binding[i].keycode == kev->keycode)
511 return (key_binding[i].callback)(win);
517 win_grow_pixmap(win_t *win)
521 new = XCreatePixmap(win->dpy, win->win, win->width, win->height, DefaultDepth (win->dpy, win->scr));
522 XFillRectangle(win->dpy, new, win->gc, 0, 0, win->width, win->height);
523 XCopyArea(win->dpy, win->pix, new, win->gc, 0, 0, win->width, win->height, 0, 0);
524 XFreePixmap(win->dpy, win->pix);
526 win->needs_refresh = 1;
530 win_handle_configure(win_t *win, XConfigureEvent *cev)
534 if (cev->width > win->width || cev->height > win->height) {
538 win->width = cev->width;
539 win->height = cev->height;
542 win_grow_pixmap(win);
547 win_handle_expose(win_t *win, XExposeEvent *eev)
549 XCopyArea(win->dpy, win->pix, win->win, win->gc,
550 eev->x, eev->y, eev->width, eev->height,
555 win_handle_events(win_t *win)
562 if (!XPending(win->dpy) && win->needs_refresh) {
564 win->needs_refresh = 0;
567 XNextEvent(win->dpy, &xev);
571 win_handle_button_press(win, &xev.xbutton);
574 win_handle_motion(win, &xev.xmotion);
581 done = win_handle_key_press(win, &xev.xkey);
585 case ConfigureNotify:
586 win_handle_configure(win, &xev.xconfigure);
589 win_handle_expose(win, &xev.xexpose);
605 print_spline_cb(win_t *win)
607 pt_t *pt = win->spline.pt;
609 printf("{ { %.20g, %.20g }, { %.20g, %.20g }, { %.20g, %.20g }, { %.20g, %.20g } }\n",
619 zoom_in_cb(win_t *win)
623 win->needs_refresh = 1;
629 trans_left_cb(win_t *win)
631 win->xtrans -= win->width / 16.0;
633 win->needs_refresh = 1;
639 trans_right_cb(win_t *win)
641 win->xtrans += win->width / 16.0;
643 win->needs_refresh = 1;
649 trans_up_cb(win_t *win)
651 win->ytrans -= win->height / 16.0;
653 win->needs_refresh = 1;
659 trans_down_cb(win_t *win)
661 win->ytrans += win->height / 16.0;
663 win->needs_refresh = 1;
669 zoom_out_cb(win_t *win)
673 win->needs_refresh = 1;
679 flatten_cb(win_t *win)
681 win->tolerance *= 10;
683 win->needs_refresh = 1;
689 smooth_cb(win_t *win)
691 win->tolerance /= 10;
693 win->needs_refresh = 1;
699 widen_line(win_t *win)
701 win->line_width *= 2;
703 win->needs_refresh = 1;
709 narrow_line(win_t *win)
711 win->line_width /= 2;
713 win->needs_refresh = 1;
719 butt_cap_cb(win_t *win)
721 win->line_cap = CAIRO_LINE_CAP_BUTT;
723 win->needs_refresh = 1;
729 round_cap_cb(win_t *win)
731 win->line_cap = CAIRO_LINE_CAP_ROUND;
733 win->needs_refresh = 1;