]> git.cworth.org Git - cairo-spline/blob - cairo-spline.c
Add cairo-spline demonstration program.
[cairo-spline] / cairo-spline.c
1 /* cc `pkg-config --cflags --libs cairo-xlib` cairo-spline.c -o cairo-spline */
2
3 /* Copyright © 2005 Carl Worth
4  *
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:
12  *
13  * The above copyright notice and this permission notice shall be
14  * included in all copies or substantial portions of the Software.
15  *
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
23  * SOFTWARE.
24  */
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <math.h>
30
31 #include <cairo.h>
32 #include <cairo-xlib.h>
33 #include <X11/extensions/Xrender.h>
34
35 #define EPSILON (1.0 / (2<<16))
36 #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
37
38 typedef struct color {
39     double red;
40     double green;
41     double blue;
42 } color_t;
43
44 typedef struct pt {
45     double x;
46     double y;
47 } pt_t;
48
49 typedef struct spline {
50     pt_t pt[4];
51 } spline_t;
52
53 typedef struct quadr {
54     pt_t pt[4];
55 } quadr_t;
56
57 typedef struct win {
58     Display *dpy;
59     int scr;
60     Window win;
61     GC gc;
62     Pixmap pix;
63     int width, height;
64     long event_mask;
65
66     int needs_refresh;
67
68     double tolerance;
69     double line_width;
70     cairo_line_cap_t line_cap;
71     spline_t spline;
72     double zoom;
73     double xtrans;
74     double ytrans;
75
76     int click;
77     pt_t drag_pt;
78     int active;
79 } win_t;
80
81 typedef struct callback_doc {
82     void *callback;
83     char *doc;
84 } callback_doc_t;
85
86 typedef int (*key_callback_t)(win_t *win);
87
88 typedef struct key_binding
89 {
90     char *key;
91     int is_alias;
92     KeyCode keycode;
93     key_callback_t callback;
94 } key_binding_t;
95
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);
102
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);
117
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;
123
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 } }
128 };
129
130 /* A simple looping spline. */
131 static const spline_t ribbon = {
132     { {110, 20}, {310, 300}, {10, 310}, {210, 20} }
133 };
134
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 } }
139 };
140
141 #define DEFAULT_SPLINE contour_bugs
142 static const double DEFAULT_LINE_WIDTH = 160;
143
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" },
159 };
160
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 },
180 };
181
182 int
183 main(int argc, char *argv[])
184 {
185     win_t win;
186
187     Display *dpy = XOpenDisplay(0);
188
189     if (dpy == NULL) {
190         fprintf(stderr, "Failed to open display: %s\n", XDisplayName(0));
191         return 1;
192     }
193
194     win_init(&win, dpy);
195
196     win_print_help(&win);
197
198     win_handle_events(&win);
199
200     win_deinit(&win);
201
202     XCloseDisplay(dpy);
203
204     return 0;
205 }
206
207 static void
208 draw_control_line(cairo_t *cr, pt_t *a, pt_t *b, double width)
209 {
210     cairo_save(cr);
211
212     cairo_set_source_rgb(cr, 0, 0, 1);
213     cairo_set_line_width(cr, width);
214
215     cairo_move_to(cr, a->x, a->y);
216     cairo_line_to(cr, b->x, b->y);
217     cairo_stroke(cr);
218
219     cairo_restore(cr);
220 }
221
222 static void
223 draw_spline(cairo_t *cr, win_t *win)
224 {
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;
229
230     int i;
231
232     cairo_device_to_user (cr, &drag_user_x, &drag_user_y);
233
234     cairo_save(cr);
235
236     cairo_move_to(cr, spline->pt[0].x, spline->pt[0].y);
237     cairo_curve_to(cr,
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);
241
242
243     if (win->click && cairo_in_stroke (cr, drag_user_x, drag_user_y)) {
244         win->active = 0xf;
245     }
246
247     cairo_save (cr);
248     {
249         cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
250
251         cairo_stroke(cr);
252     }
253     cairo_restore (cr);
254
255     cairo_save (cr);
256     cairo_set_line_width (cr, 2);
257     cairo_move_to(cr, spline->pt[0].x, spline->pt[0].y);
258     cairo_curve_to(cr,
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);
263     cairo_stroke (cr);
264     cairo_restore (cr);
265
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);
268
269     for (i=0; i < 4; i++) {
270         cairo_save(cr);
271
272         cairo_set_source_rgba (cr, 1, 0, 0, 0.5);
273
274         cairo_new_path (cr);
275         cairo_arc (cr,
276                    spline->pt[i].x, spline->pt[i].y,
277                    10, 0, 2 * M_PI);
278         if (win->click && cairo_in_fill (cr, drag_user_x, drag_user_y)) {
279             win->active = (1<<i);
280             win->click = 0;
281         }
282         cairo_fill (cr);
283
284         cairo_restore(cr);
285     }
286
287     cairo_restore(cr);
288 }
289
290 static void
291 win_refresh(win_t *win)
292 {
293     Display *dpy = win->dpy;
294
295     cairo_surface_t *surface;
296     cairo_t *cr;
297     cairo_status_t status;
298
299     XFillRectangle(dpy, win->pix, win->gc, 0, 0, win->width, win->height);
300
301     surface = cairo_xlib_surface_create (dpy, win->pix,
302                                          DefaultVisual (dpy, win->scr),
303                                          win->width, win->height);
304     cr = cairo_create(surface);
305
306     cairo_set_source_rgb(cr, 0, 0, 0);
307
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);
313
314     draw_spline(cr, win);
315
316     status = cairo_status(cr);
317     if (status) {
318         fprintf(stderr, "Cairo is unhappy: %s\n",
319                 cairo_status_to_string(status));
320     }
321
322     cairo_destroy(cr);
323     cairo_surface_destroy (surface);
324
325     XCopyArea(win->dpy, win->pix, win->win, win->gc,
326               0, 0, win->width, win->height,
327               0, 0);
328 }
329
330 int
331 find_true_color_visual (Display *dpy, int scr, XVisualInfo *result)
332 {
333     XVisualInfo *xvi;
334     XVisualInfo template;
335     int nvi;
336     XRenderPictFormat *format;
337     Visual *visual;
338
339     template.screen = scr;
340     template.class = TrueColor;
341     xvi = XGetVisualInfo (dpy, 
342                           VisualScreenMask |
343                           VisualClassMask,
344                           &template,
345                           &nvi);
346     if (xvi == NULL)
347         return 0;
348
349     *result = xvi[0];
350
351     XFree (xvi);
352
353     return 1;
354 }
355
356 static void
357 win_init(win_t *win, Display *dpy)
358 {
359     int i;
360     Window root;
361     XGCValues gcv;
362     XVisualInfo xvi;
363     XSetWindowAttributes wattr;
364     unsigned long wmask;
365
366     win->dpy = dpy;
367     win->width = 400;
368     win->height = 400;
369
370     root = DefaultRootWindow(dpy);
371     win->scr = DefaultScreen(dpy);
372
373     if (! find_true_color_visual (dpy, DefaultScreen (dpy), &xvi)) {
374         printf ("No TrueColor visual available, exiting.\n");
375         exit (1);
376     }
377
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;
383
384     win->win = XCreateWindow(dpy, root, 0, 0,
385                              win->width, win->height, 0,
386                              xvi.depth, InputOutput, xvi.visual,
387                              wmask, &wattr);
388
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);
393
394     for (i=0; i < ARRAY_SIZE(key_binding); i++) {
395         KeySym keysym;
396         keysym = XStringToKeysym(key_binding[i].key);
397         if (keysym == NoSymbol)
398             fprintf(stderr, "ERROR: No keysym for \"%s\"\n", key_binding[i].key);
399         else
400             key_binding[i].keycode = XKeysymToKeycode(dpy, keysym);
401     }
402
403     win->active = 0;
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;
411
412     win->click = 0;
413     win->drag_pt.x = 0.0;
414     win->drag_pt.y = 0.0;
415
416     win_refresh(win);
417     win->needs_refresh = 0;
418
419     win_select_events(win);
420
421     XMapWindow(dpy, win->win);
422 }
423
424 static void
425 win_deinit(win_t *win)
426 {
427     XFreeGC(win->dpy, win->gc);
428     XFreePixmap(win->dpy, win->pix);
429     XDestroyWindow(win->dpy, win->win);
430 }
431
432 static void
433 win_select_events(win_t *win)
434 {
435     win->event_mask = 
436           ButtonPressMask
437         | ButtonReleaseMask
438         | PointerMotionMask
439         | KeyPressMask
440         | StructureNotifyMask
441         | ExposureMask;
442     XSelectInput(win->dpy, win->win, win->event_mask);
443
444 }
445
446 static char *
447 get_callback_doc(void *callback)
448 {
449     int i;
450
451     for (i=0; i < ARRAY_SIZE(callback_doc); i++)
452         if (callback_doc[i].callback == callback)
453             return callback_doc[i].doc;
454
455     return "<undocumented function>";
456 }
457
458 static void
459 win_print_help(win_t *win)
460 {
461     int i;
462
463     printf("A cairo spline demonstration\n");
464     printf("Click and drag to move the spline or adjust its controls. Or:\n\n");
465
466     for (i=0; i < ARRAY_SIZE(key_binding); i++)
467         if (! key_binding[i].is_alias)
468             printf("%s:\t%s\n",
469                    key_binding[i].key,
470                    get_callback_doc(key_binding[i].callback));
471 }
472
473 static void
474 win_handle_button_press(win_t *win, XButtonEvent *bev)
475 {
476     win->click = 1;
477     win->drag_pt.x = bev->x;
478     win->drag_pt.y = bev->y;
479  
480     win->needs_refresh = 1;
481 }
482
483 static void
484 win_handle_motion(win_t *win, XMotionEvent *mev)
485 {
486     int i;
487
488     if (win->active == 0)
489         return;
490
491     for (i = 0; i < 4; i++) {
492         if (((1<<i) & win->active) == 0)
493             continue;
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;
496
497         win->needs_refresh = 1;
498     }
499
500     win->drag_pt.x = mev->x;
501     win->drag_pt.y = mev->y;
502 }
503
504 static int
505 win_handle_key_press(win_t *win, XKeyEvent *kev)
506 {
507     int i;
508
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);
512         
513     return 0;
514 }
515
516 static void
517 win_grow_pixmap(win_t *win)
518 {
519     Pixmap new;
520
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);
525     win->pix = new;
526     win->needs_refresh = 1;
527 }
528
529 static void
530 win_handle_configure(win_t *win, XConfigureEvent *cev)
531 {
532     int has_grown = 0;
533
534     if (cev->width > win->width || cev->height > win->height) {
535         has_grown = 1;
536     }
537
538     win->width = cev->width;
539     win->height = cev->height;
540
541     if (has_grown) {
542         win_grow_pixmap(win);
543     }
544 }
545
546 static void
547 win_handle_expose(win_t *win, XExposeEvent *eev)
548 {
549     XCopyArea(win->dpy, win->pix, win->win, win->gc,
550               eev->x, eev->y, eev->width, eev->height,
551               eev->x, eev->y);
552 }
553
554 static void
555 win_handle_events(win_t *win)
556 {
557     int done;
558     XEvent xev;
559
560     while (1) {
561
562         if (!XPending(win->dpy) && win->needs_refresh) {
563             win_refresh(win);
564             win->needs_refresh = 0;
565         }
566
567         XNextEvent(win->dpy, &xev);
568
569         switch(xev.type) {
570         case ButtonPress:
571             win_handle_button_press(win, &xev.xbutton);
572             break;
573         case MotionNotify:
574             win_handle_motion(win, &xev.xmotion);
575             break;
576         case ButtonRelease:
577             win->click = 0;
578             win->active = 0;
579             break;
580         case KeyPress:
581             done = win_handle_key_press(win, &xev.xkey);
582             if (done)
583                 return;
584             break;
585         case ConfigureNotify:
586             win_handle_configure(win, &xev.xconfigure);
587             break;
588         case Expose:
589             win_handle_expose(win, &xev.xexpose);
590             break;
591         }
592     }
593 }
594
595
596 /* Callbacks */
597
598 static int
599 quit_cb(win_t *win)
600 {
601     return 1;
602 }
603
604 static int
605 print_spline_cb(win_t *win)
606 {
607     pt_t *pt = win->spline.pt;
608
609     printf("{ { %.20g, %.20g }, { %.20g, %.20g }, { %.20g, %.20g }, { %.20g, %.20g } }\n",
610            pt[0].x, pt[0].y,
611            pt[1].x, pt[1].y,
612            pt[2].x, pt[2].y,
613            pt[3].x, pt[3].y);
614
615     return 0;
616 }
617
618 static int
619 zoom_in_cb(win_t *win)
620 {
621     win->zoom *= 1.1;
622
623     win->needs_refresh = 1;
624
625     return 0;
626 }
627
628 static int
629 trans_left_cb(win_t *win)
630 {
631     win->xtrans -= win->width / 16.0;
632
633     win->needs_refresh = 1;
634
635     return 0;
636 }
637
638 static int
639 trans_right_cb(win_t *win)
640 {
641     win->xtrans += win->width / 16.0;
642
643     win->needs_refresh = 1;
644
645     return 0;
646 }
647
648 static int
649 trans_up_cb(win_t *win)
650 {
651     win->ytrans -= win->height / 16.0;
652
653     win->needs_refresh = 1;
654
655     return 0;
656 }
657
658 static int
659 trans_down_cb(win_t *win)
660 {
661     win->ytrans += win->height / 16.0;
662
663     win->needs_refresh = 1;
664
665     return 0;
666 }
667
668 static int
669 zoom_out_cb(win_t *win)
670 {
671     win->zoom /= 1.1;
672
673     win->needs_refresh = 1;
674
675     return 0;
676 }
677
678 static int
679 flatten_cb(win_t *win)
680 {
681     win->tolerance *= 10;
682
683     win->needs_refresh = 1;
684
685     return 0;
686 }
687
688 static int
689 smooth_cb(win_t *win)
690 {
691     win->tolerance /= 10;
692
693     win->needs_refresh = 1;
694
695     return 0;
696 }
697
698 static int
699 widen_line(win_t *win)
700 {
701     win->line_width *= 2;
702
703     win->needs_refresh = 1;
704
705     return 0;
706 }
707
708 static int
709 narrow_line(win_t *win)
710 {
711     win->line_width /= 2;
712
713     win->needs_refresh = 1;
714
715     return 0;
716 }
717
718 static int
719 butt_cap_cb(win_t *win)
720 {
721     win->line_cap = CAIRO_LINE_CAP_BUTT;
722
723     win->needs_refresh = 1;
724
725     return 0;
726 }
727
728 static int
729 round_cap_cb(win_t *win)
730 {
731     win->line_cap = CAIRO_LINE_CAP_ROUND;
732
733     win->needs_refresh = 1;
734
735     return 0;
736 }