ad1e26e7a6ef951834ec60f72b8c2bb37d400f1c
[xscreensaver] / hacks / qix.c
1 /* xscreensaver, Copyright (c) 1992-2008 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 #include "screenhack.h"
13 #include "alpha.h"
14 #include <stdio.h>
15
16 #define MAXPOLY 16
17 #define SCALE   6
18
19 struct qpoint {
20   int x, y;
21   int dx, dy;
22 };
23
24 struct qline {
25   struct qpoint *p;
26   XColor color;
27   Bool dead;
28 };
29
30 struct qix {
31   int id;
32   int fp;
33   int nlines;
34   int npoly;
35   struct qline *lines;
36 };
37
38 struct state {
39   Display *dpy;
40   Window window;
41
42   GC draw_gc, erase_gc;
43   unsigned int default_fg_pixel;
44   long maxx, maxy, max_spread, max_size;
45   int color_shift;
46   Bool random_p, solid_p, xor_p, transparent_p, gravity_p;
47   int delay;
48   int count;
49   Colormap cmap;
50   int npoly;
51   Bool additive_p;
52   Bool cmap_p;
53
54   GC *gcs[2];
55
56   int gtick;
57
58   struct qix **qixes;
59 };
60
61
62 static void
63 get_geom (struct state *st)
64 {
65   XWindowAttributes xgwa;
66   XGetWindowAttributes (st->dpy, st->window, &xgwa);
67   st->maxx = ((long)(xgwa.width+1)<<SCALE)  - 1;
68   st->maxy = ((long)(xgwa.height+1)<<SCALE) - 1;
69 }
70
71 static struct qix *
72 init_one_qix (struct state *st, int nlines)
73 {
74   int i, j;
75   struct qix *qix = (struct qix *) calloc (1, sizeof (struct qix));
76   qix->nlines = nlines;
77   qix->lines = (struct qline *) calloc (qix->nlines, sizeof (struct qline));
78   qix->npoly = st->npoly;
79   for (i = 0; i < qix->nlines; i++)
80     qix->lines[i].p = (struct qpoint *)
81       calloc(qix->npoly, sizeof(struct qpoint));
82
83 # ifndef HAVE_COCOA
84   if (!mono_p && !st->transparent_p)
85 # endif
86     {
87       hsv_to_rgb (random () % 360, frand (1.0), frand (0.5) + 0.5,
88                   &qix->lines[0].color.red, &qix->lines[0].color.green,
89                   &qix->lines[0].color.blue);
90       if (!XAllocColor (st->dpy, st->cmap, &qix->lines[0].color))
91         {
92           qix->lines[0].color.pixel = st->default_fg_pixel;
93           XQueryColor (st->dpy, st->cmap, &qix->lines[0].color);
94           if (!XAllocColor (st->dpy, st->cmap, &qix->lines[0].color))
95             abort ();
96         }
97     }
98
99   if (st->max_size == 0)
100     {
101       for (i = 0; i < qix->npoly; i++)
102         {
103           qix->lines[0].p[i].x = random () % st->maxx;
104           qix->lines[0].p[i].y = random () % st->maxy;
105         }
106     }
107   else
108     {
109       /*assert(qix->npoly == 2);*/
110       qix->lines[0].p[0].x = random () % st->maxx;
111       qix->lines[0].p[0].y = random () % st->maxy;
112       qix->lines[0].p[1].x = qix->lines[0].p[0].x + (random () % (st->max_size/2));
113       qix->lines[0].p[1].y = qix->lines[0].p[0].y + (random () % (st->max_size/2));
114       if (qix->lines[0].p[1].x > st->maxx) qix->lines[0].p[1].x = st->maxx;
115       if (qix->lines[0].p[1].y > st->maxy) qix->lines[0].p[1].y = st->maxy;
116     }
117
118   for (i = 0; i < qix->npoly; i++)
119     {
120       qix->lines[0].p[i].dx = (random () % (st->max_spread + 1)) - (st->max_spread /2);
121       qix->lines[0].p[i].dy = (random () % (st->max_spread + 1)) - (st->max_spread /2);
122     }
123   qix->lines[0].dead = True;
124
125   for (i = 1; i < qix->nlines; i++)
126     {
127       for(j=0; j<qix->npoly; j++)
128         qix->lines[i].p[j] = qix->lines[0].p[j];
129       qix->lines[i].color = qix->lines[0].color;
130       qix->lines[i].dead = qix->lines[0].dead;
131   
132 # ifndef HAVE_COCOA
133       if (!mono_p && !st->transparent_p)
134 # endif
135         if (!XAllocColor (st->dpy, st->cmap, &qix->lines[i].color))
136           abort ();
137     }
138   return qix;
139 }
140
141
142
143
144 static void *
145 qix_init (Display *dpy, Window window)
146 {
147   struct state *st = (struct state *) calloc (1, sizeof(*st));
148   int nlines;
149   XGCValues gcv;
150   XWindowAttributes xgwa;
151   st->dpy = dpy;
152   st->window = window;
153   XGetWindowAttributes (st->dpy, st->window, &xgwa);
154   st->cmap = xgwa.colormap;
155   st->count = get_integer_resource (st->dpy, "count", "Integer");
156   if (st->count <= 0) st->count = 1;
157   nlines = get_integer_resource (st->dpy, "segments", "Integer");
158   if (nlines <= 0) nlines = 20;
159   st->npoly = get_integer_resource(st->dpy, "poly", "Integer");
160   if (st->npoly <= 2) st->npoly = 2;
161   if (st->npoly > MAXPOLY) st->npoly = MAXPOLY;
162   get_geom (st);
163   st->max_spread = get_integer_resource (st->dpy, "spread", "Integer");
164   if (st->max_spread <= 0) st->max_spread = 10;
165   st->max_spread <<= SCALE;
166   st->max_size = get_integer_resource (st->dpy, "size", "Integer");
167   if (st->max_size < 0) st->max_size = 0;
168   st->max_size <<= SCALE;
169   st->random_p = get_boolean_resource (st->dpy, "random", "Boolean");
170   st->solid_p = get_boolean_resource (st->dpy, "solid", "Boolean");
171   st->xor_p = get_boolean_resource (st->dpy, "xor", "Boolean");
172   st->transparent_p = get_boolean_resource (st->dpy, "transparent", "Boolean");
173   st->gravity_p = get_boolean_resource(st->dpy, "gravity", "Boolean");
174   st->delay = get_integer_resource (st->dpy, "delay", "Integer");
175   st->color_shift = get_integer_resource (st->dpy, "colorShift", "Integer");
176   if (st->color_shift < 0 || st->color_shift >= 360) st->color_shift = 5;
177   if (st->delay < 0) st->delay = 0;
178
179   /* Clear up ambiguities regarding npoly */
180   if (st->solid_p) 
181     {
182       if (st->npoly != 2)
183         fprintf(stderr, "%s: Can't have -solid and -poly; using -poly 2\n",
184                 progname);
185       st->npoly = 2;
186     }      
187   if (st->npoly > 2)
188     {
189       if (st->max_size)
190         fprintf(stderr, "%s: Can't have -poly and -size; using -size 0\n",
191                 progname);
192       st->max_size = 0;
193     }
194
195   if (st->count == 1 && st->transparent_p)
196     st->transparent_p = False; /* it's a no-op */
197
198   if (st->transparent_p && CellsOfScreen (DefaultScreenOfDisplay (st->dpy)) <= 2)
199     {
200       fprintf (stderr, "%s: -transparent only works on color displays.\n",
201                progname);
202       st->transparent_p = False;
203     }
204
205   if (st->xor_p && !st->transparent_p)
206     mono_p = True;
207
208   st->gcs[0] = st->gcs[1] = 0;
209   gcv.foreground = st->default_fg_pixel =
210     get_pixel_resource (st->dpy, st->cmap, "foreground", "Foreground");
211
212   st->additive_p = get_boolean_resource (st->dpy, "additive", "Boolean");
213   st->cmap_p = has_writable_cells (xgwa.screen, xgwa.visual);
214
215 # ifndef HAVE_COCOA
216   if (st->transparent_p)
217     {
218       unsigned long *plane_masks = 0;
219       unsigned long base_pixel;
220       int nplanes = st->count;
221       int i;
222
223       allocate_alpha_colors (xgwa.screen, xgwa.visual, st->cmap,
224                              &nplanes, st->additive_p, &plane_masks,
225                              &base_pixel);
226
227       if (nplanes <= 1)
228         {
229           fprintf (stderr,
230          "%s: couldn't allocate any color planes; turning -transparent off.\n",
231                    progname);
232           st->transparent_p = False;
233           if (st->xor_p)
234             goto NON_TRANSPARENT_XOR;
235           else
236             goto NON_TRANSPARENT;
237         }
238       else if (nplanes != st->count)
239         {
240           fprintf (stderr,
241                    "%s: only allocated %d color planes (instead of %d).\n",
242                    progname, nplanes, st->count);
243           st->count = nplanes;
244         }
245
246       st->gcs[0] = (GC *) malloc (st->count * sizeof (GC));
247       st->gcs[1] = (st->xor_p
248                     ? st->gcs[0]
249                     : (GC *) malloc (st->count * sizeof (GC)));
250
251       for (i = 0; i < st->count; i++)
252         {
253           gcv.plane_mask = plane_masks [i];
254           gcv.foreground = ~0;
255
256 /*  argh, I'm not sure how to make "-subtractive" work in truecolor...
257           if (!cmap_p && !additive_p)
258             gcv.function = GXclear;
259  */
260
261           if (st->xor_p)
262             {
263               gcv.function = GXxor;
264               st->gcs [0][i] = XCreateGC (st->dpy, st->window,
265                                           GCForeground|GCFunction|GCPlaneMask,
266                                           &gcv);
267             }
268           else
269             {
270               st->gcs [0][i] = XCreateGC (st->dpy, st->window, 
271                                           GCForeground|GCPlaneMask,
272                                           &gcv);
273               gcv.foreground = 0;
274               st->gcs [1][i] = XCreateGC (st->dpy, st->window, 
275                                           GCForeground|GCPlaneMask,
276                                           &gcv);
277 # ifdef HAVE_COCOA
278            /* jwxyz_XSetAntiAliasing (st->dpy, st->gcs [0][i], False);
279               jwxyz_XSetAntiAliasing (st->dpy, st->gcs [1][i], False); */
280               if (st->transparent_p)
281                 {
282                   jwxyz_XSetAlphaAllowed (dpy, st->gcs [0][i], True);
283                   jwxyz_XSetAlphaAllowed (dpy, st->gcs [1][i], True);
284                 }
285 # endif /* HAVE_COCOA */
286             }
287         }
288
289       if (plane_masks)
290         free (plane_masks);
291
292       XSetWindowBackground (st->dpy, st->window, base_pixel);
293       XClearWindow (st->dpy, st->window);
294     }
295   else
296 #endif /* !HAVE_COCOA */
297   if (st->xor_p)
298     {
299 #ifndef HAVE_COCOA
300     NON_TRANSPARENT_XOR:
301 #endif
302       gcv.function = GXxor;
303       gcv.foreground =
304         (st->default_fg_pixel /* ^ get_pixel_resource (st->dpy, st->cmap,
305                                                 "background", "Background")*/);
306       st->draw_gc = st->erase_gc = XCreateGC(st->dpy,st->window,GCForeground|GCFunction,&gcv);
307     }
308   else
309     {
310 #ifndef HAVE_COCOA
311     NON_TRANSPARENT:
312 #endif
313       st->draw_gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
314       gcv.foreground = get_pixel_resource (st->dpy, st->cmap,
315                                            "background", "Background");
316       st->erase_gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
317     }
318
319 #ifdef HAVE_COCOA
320   if (st->transparent_p)
321     jwxyz_XSetAlphaAllowed (dpy, st->draw_gc, True);
322 #endif
323
324   st->qixes = (struct qix **) malloc ((st->count + 1) * sizeof (struct qix *));
325   st->qixes [st->count] = 0;
326   while (st->count--)
327     {
328       st->qixes [st->count] = init_one_qix (st, nlines);
329       st->qixes [st->count]->id = st->count;
330     }
331
332 # ifdef HAVE_COCOA
333   /* line-mode leaves turds without this. */
334   jwxyz_XSetAntiAliasing (st->dpy, st->erase_gc, False);
335   jwxyz_XSetAntiAliasing (st->dpy, st->draw_gc,  False);
336 # endif
337
338   return st;
339 }
340
341 static void
342 free_qline (struct state *st,
343             struct qline *qline,
344             struct qline *prev,
345             struct qix *qix)
346 {
347   int i;
348   if (qline->dead || !prev)
349     ;
350   else if (st->solid_p)
351     {
352       XPoint points [4];
353       /*assert(qix->npoly == 2);*/
354       points [0].x = qline->p[0].x >> SCALE; 
355       points [0].y = qline->p[0].y >> SCALE;
356       points [1].x = qline->p[1].x >> SCALE;
357       points [1].y = qline->p[1].y >> SCALE;
358       points [2].x = prev->p[1].x >> SCALE;
359       points [2].y = prev->p[1].y >> SCALE;
360       points [3].x = prev->p[0].x >> SCALE;
361       points [3].y = prev->p[0].y >> SCALE;
362       XFillPolygon (st->dpy, st->window,
363                     (st->transparent_p && st->gcs[1]
364                      ? st->gcs[1][qix->id]
365                      : st->erase_gc),
366                     points, 4, Complex, CoordModeOrigin);
367     }
368   else
369     {
370       /*  XDrawLine (dpy, window, (transparent_p ? gcs[1][qix->id] : erase_gc),
371                      qline->p1.x, qline->p1.y, qline->p2.x, qline->p2.y);*/
372       XPoint points[MAXPOLY+1];
373       for(i = 0; i < qix->npoly; i++)
374         {
375           points[i].x = qline->p[i].x >> SCALE;
376           points[i].y = qline->p[i].y >> SCALE;
377         }
378       points[qix->npoly] = points[0];
379       XDrawLines(st->dpy, st->window,
380                  (st->transparent_p && st->gcs[1] 
381                   ? st->gcs[1][qix->id]
382                   : st->erase_gc),
383                  points, qix->npoly+1, CoordModeOrigin);
384     }
385
386   if (!mono_p && !st->transparent_p)
387     XFreeColors (st->dpy, st->cmap, &qline->color.pixel, 1, 0);
388
389   qline->dead = True;
390 }
391
392 static void
393 add_qline (struct state *st,
394            struct qline *qline,
395            struct qline *prev_qline,
396            struct qix *qix)
397 {
398   int i;
399
400   for(i=0; i<qix->npoly; i++)
401     qline->p[i] = prev_qline->p[i];
402   qline->color = prev_qline->color;
403   qline->dead = prev_qline->dead;
404
405 #define wiggle(point,delta,max)                                         \
406   if (st->random_p) delta += (random () % (1 << (SCALE+1))) - (1 << SCALE);     \
407   if (delta > st->max_spread) delta = st->max_spread;                           \
408   else if (delta < -st->max_spread) delta = -st->max_spread;                    \
409   point += delta;                                                       \
410   if (point < 0) point = 0, delta = -delta, point += delta<<1;          \
411   else if (point > max) point = max, delta = -delta, point += delta<<1;
412
413   if (st->gravity_p)
414     for(i=0; i<qix->npoly; i++)
415       qline->p[i].dy += 3;
416
417   for (i = 0; i < qix->npoly; i++)
418     {
419       wiggle (qline->p[i].x, qline->p[i].dx, st->maxx);
420       wiggle (qline->p[i].y, qline->p[i].dy, st->maxy);
421     }
422
423   if (st->max_size)
424     {
425       /*assert(qix->npoly == 2);*/
426       if (qline->p[0].x - qline->p[1].x > st->max_size)
427         qline->p[0].x = qline->p[1].x + st->max_size
428           - (st->random_p ? random() % st->max_spread : 0);
429       else if (qline->p[1].x - qline->p[0].x > st->max_size)
430         qline->p[1].x = qline->p[0].x + st->max_size
431           - (st->random_p ? random() % st->max_spread : 0);
432       if (qline->p[0].y - qline->p[1].y > st->max_size)
433         qline->p[0].y = qline->p[1].y + st->max_size
434           - (st->random_p ? random() % st->max_spread : 0);
435       else if (qline->p[1].y - qline->p[0].y > st->max_size)
436         qline->p[1].y = qline->p[0].y + st->max_size
437           - (st->random_p ? random() % st->max_spread : 0);
438     }
439
440 #ifndef HAVE_COCOA
441   if (!mono_p && !st->transparent_p)
442 #endif
443     {
444       XColor desired;
445
446       int h;
447       double s, v;
448       rgb_to_hsv (qline->color.red, qline->color.green, qline->color.blue,
449                   &h, &s, &v);
450       h = (h + st->color_shift) % 360;
451       hsv_to_rgb (h, s, v,
452                   &qline->color.red, &qline->color.green, &qline->color.blue);
453
454       qline->color.flags = DoRed | DoGreen | DoBlue;
455       desired = qline->color;
456       if (XAllocColor (st->dpy, st->cmap, &qline->color))
457         {
458           /* XAllocColor returns the actual RGB that the hardware let us
459              allocate.  Restore the requested values into the XColor struct
460              so that limited-resolution hardware doesn't cause the cycle to
461              get "stuck". */
462           qline->color.red = desired.red;
463           qline->color.green = desired.green;
464           qline->color.blue = desired.blue;
465         }
466       else
467         {
468           qline->color = prev_qline->color;
469           if (!XAllocColor (st->dpy, st->cmap, &qline->color))
470             abort (); /* same color should work */
471         }
472
473 # ifdef HAVE_COCOA
474           if (st->transparent_p)
475             {
476               /* give a non-opaque alpha to the color */
477               unsigned long pixel = qline->color.pixel;
478               unsigned long amask = BlackPixelOfScreen (0);
479               unsigned long a = (0xBBBBBBBB & amask);
480               pixel = (pixel & (~amask)) | a;
481               qline->color.pixel = pixel;
482             }
483 # endif /* HAVE_COCOA */
484
485       XSetForeground (st->dpy, st->draw_gc, qline->color.pixel);
486     }
487   if (! st->solid_p)
488     {
489       /*  XDrawLine (dpy, window, (transparent_p ? gcs[0][qix->id] : draw_gc),
490                      qline->p1.x, qline->p1.y, qline->p2.x, qline->p2.y);*/
491       XPoint points[MAXPOLY+1];
492       for (i = 0; i < qix->npoly; i++)
493         {
494           points[i].x = qline->p[i].x >> SCALE;
495           points[i].y = qline->p[i].y >> SCALE;
496         }
497       points[qix->npoly] = points[0];
498       XDrawLines(st->dpy, st->window, 
499                  (st->transparent_p && st->gcs[0]
500                   ? st->gcs[0][qix->id]
501                   : st->draw_gc),
502                  points, qix->npoly+1, CoordModeOrigin);
503     }
504   else if (!prev_qline->dead)
505     {
506       XPoint points [4];
507       points [0].x = qline->p[0].x >> SCALE;
508       points [0].y = qline->p[0].y >> SCALE;
509       points [1].x = qline->p[1].x >> SCALE;
510       points [1].y = qline->p[1].y >> SCALE;
511       points [2].x = prev_qline->p[1].x >> SCALE;
512       points [2].y = prev_qline->p[1].y >> SCALE;
513       points [3].x = prev_qline->p[0].x >> SCALE;
514       points [3].y = prev_qline->p[0].y >> SCALE;
515       XFillPolygon (st->dpy, st->window,
516                     (st->transparent_p && st->gcs[0]
517                      ? st->gcs[0][qix->id]
518                      : st->draw_gc),
519                     points, 4, Complex, CoordModeOrigin);
520     }
521
522   qline->dead = False;
523 }
524
525 static void
526 qix1 (struct state *st, struct qix *qix)
527 {
528   int ofp = qix->fp - 1;
529   if (ofp < 0) ofp = qix->nlines - 1;
530   if (st->gtick++ == 500)
531     get_geom (st), st->gtick = 0;
532   free_qline (st, &qix->lines [qix->fp],
533               &qix->lines[(qix->fp + 1) % qix->nlines], qix);
534   add_qline (st, &qix->lines[qix->fp], &qix->lines[ofp], qix);
535   if ((++qix->fp) >= qix->nlines)
536     qix->fp = 0;
537 }
538
539
540 static unsigned long
541 qix_draw (Display *dpy, Window window, void *closure)
542 {
543   struct state *st = (struct state *) closure;
544   struct qix **q1 = st->qixes;
545   struct qix **qn;
546   for (qn = q1; *qn; qn++)
547     qix1 (st, *qn);
548   return st->delay;
549 }
550
551 static void
552 qix_reshape (Display *dpy, Window window, void *closure, 
553                  unsigned int w, unsigned int h)
554 {
555   struct state *st = (struct state *) closure;
556   get_geom (st);
557 }
558
559 static Bool
560 qix_event (Display *dpy, Window window, void *closure, XEvent *event)
561 {
562   return False;
563 }
564
565 static void
566 qix_free (Display *dpy, Window window, void *closure)
567 {
568   struct state *st = (struct state *) closure;
569   if (st->gcs[0])
570     free (st->gcs[0]);
571   if (st->gcs[1] && st->gcs[0] != st->gcs[1])
572     free (st->gcs[1]);
573   free (st->qixes);
574   free (st);
575 }
576
577 \f
578 static const char *qix_defaults [] = {
579   ".background: black",
580   ".foreground: white",
581   "*fpsSolid:   true",
582   "*count:      4",
583   "*segments:   250",
584   "*poly:       2",
585   "*spread:     8",
586   "*size:       200",
587   "*colorShift: 3",
588   "*solid:      true",
589   "*delay:      10000",
590   "*random:     false",
591   "*xor:        false",
592   "*transparent:true",
593   "*gravity:    false",
594   "*additive:   true",
595   0
596 };
597
598 static XrmOptionDescRec qix_options [] = {
599   { "-count",           ".count",       XrmoptionSepArg, 0 },
600   { "-segments",        ".segments",    XrmoptionSepArg, 0 },
601   { "-poly",            ".poly",        XrmoptionSepArg, 0 },
602   { "-spread",          ".spread",      XrmoptionSepArg, 0 },
603   { "-size",            ".size",        XrmoptionSepArg, 0 },
604   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
605   { "-color-shift",     ".colorShift",  XrmoptionSepArg, 0 },
606   { "-random",          ".random",      XrmoptionNoArg, "true" },
607   { "-linear",          ".random",      XrmoptionNoArg, "false" },
608   { "-solid",           ".solid",       XrmoptionNoArg, "true" },
609   { "-hollow",          ".solid",       XrmoptionNoArg, "false" },
610   { "-xor",             ".xor",         XrmoptionNoArg, "true" },
611   { "-no-xor",          ".xor",         XrmoptionNoArg, "false" },
612   { "-transparent",     ".transparent", XrmoptionNoArg, "true" },
613   { "-non-transparent", ".transparent", XrmoptionNoArg, "false" },
614   { "-gravity",         ".gravity",     XrmoptionNoArg, "true" },
615   { "-no-gravity",      ".gravity",     XrmoptionNoArg, "false" },
616   { "-additive",        ".additive",    XrmoptionNoArg, "true" },
617   { "-subtractive",     ".additive",    XrmoptionNoArg, "false" },
618   { 0, 0, 0, 0 }
619 };
620
621 XSCREENSAVER_MODULE ("Qix", qix)