c26e736746cfef80bb4a0fc7c50132ee65e60814
[xscreensaver] / hacks / qix.c
1 /* xscreensaver, Copyright (c) 1992, 1995 Jamie Zawinski <jwz@netscape.com>
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 <stdio.h>
14
15 struct qpoint {
16   int x, y;
17   int dx, dy;
18 };
19
20 struct qline {
21   struct qpoint p1, p2;
22   XColor color;
23   Bool dead;
24 };
25
26 struct qix {
27   int id;
28   int fp;
29   int nlines;
30   struct qline *lines;
31 };
32
33 static GC draw_gc, erase_gc;
34 static unsigned int default_fg_pixel;
35 static int maxx, maxy, max_spread, max_size, color_shift;
36 static Bool random_p, solid_p, xor_p, transparent_p;
37 static int delay;
38 static int count;
39 static Colormap cmap;
40 static unsigned long base_pixel;
41
42 static GC *gcs[2];
43
44 static void
45 get_geom (dpy, window)
46      Display *dpy;
47      Window window;
48 {
49   XWindowAttributes xgwa;
50   XGetWindowAttributes (dpy, window, &xgwa);
51   maxx = xgwa.width;
52   maxy = xgwa.height;
53 }
54
55 static struct qix *
56 init_one_qix (dpy, window, nlines)
57      Display *dpy;
58      Window window;
59      int nlines;
60 {
61   int i;
62   struct qix *qix = (struct qix *) calloc (1, sizeof (struct qix));
63   qix->nlines = nlines;
64   qix->lines = (struct qline *) calloc (qix->nlines, sizeof (struct qline));
65   
66   if (!mono_p && !transparent_p)
67     {
68       hsv_to_rgb (random () % 360, frand (1.0), frand (0.5) + 0.5,
69                   &qix->lines[0].color.red, &qix->lines[0].color.green,
70                   &qix->lines[0].color.blue);
71       if (!XAllocColor (dpy, cmap, &qix->lines[0].color))
72         {
73           qix->lines[0].color.pixel = default_fg_pixel;
74           XQueryColor (dpy, cmap, &qix->lines[0].color);
75           if (!XAllocColor (dpy, cmap, &qix->lines[0].color))
76             abort ();
77         }
78     }
79   qix->lines[0].p1.x = random () % maxx;
80   qix->lines[0].p1.y = random () % maxy;
81   if (max_size == 0)
82     {
83       qix->lines[0].p2.x = random () % maxx;
84       qix->lines[0].p2.y = random () % maxy;
85     }
86   else
87     {
88       qix->lines[0].p2.x = qix->lines[0].p1.x + (random () % (max_size/2));
89       qix->lines[0].p2.y = qix->lines[0].p1.y + (random () % (max_size/2));
90       if (qix->lines[0].p2.x > maxx) qix->lines[0].p2.x = maxx;
91       if (qix->lines[0].p2.y > maxy) qix->lines[0].p2.y = maxy;
92     }
93   qix->lines[0].p1.dx = (random () % (max_spread + 1)) - (max_spread / 2);
94   qix->lines[0].p1.dy = (random () % (max_spread + 1)) - (max_spread / 2);
95   qix->lines[0].p2.dx = (random () % (max_spread + 1)) - (max_spread / 2);
96   qix->lines[0].p2.dy = (random () % (max_spread + 1)) - (max_spread / 2);
97   qix->lines[0].dead = True;
98   for (i = 1; i < qix->nlines; i++)
99     {
100       qix->lines[i] = qix->lines[0];
101       if (!mono_p && !transparent_p)
102         if (!XAllocColor (dpy, cmap, &qix->lines[i].color))
103           abort ();
104     }
105   return qix;
106 }
107
108 /* I don't believe this fucking language doesn't have builtin exponentiation.
109    I further can't believe that the fucking ^ character means fucking XOR!! */
110 static int i_exp(i,j)
111      int i, j;
112 {
113   int k = 1;
114   while (j--) k *= i;
115   return k;
116 }
117
118
119 static void
120 merge_colors (argc, argv, into_color, mask, increment_p)
121      int argc;
122      XColor **argv;
123      XColor *into_color;
124      int mask;
125      Bool increment_p;
126 {
127   int j;
128   *into_color = *argv [0];
129   into_color->pixel |= mask;
130 #define SHORT_INC(x,y) (x = ((((x)+(y)) > 0xFFFF) ? 0xFFFF : ((x)+(y))))
131 #define SHORT_DEC(x,y) (x = ((((x)-(y)) < 0)      ? 0      : ((x)-(y))))
132   for (j = 1; j < argc; j++)
133     if (increment_p)
134       {
135         SHORT_INC (into_color->red,   argv[j]->red);
136         SHORT_INC (into_color->green, argv[j]->green);
137         SHORT_INC (into_color->blue,  argv[j]->blue);
138       }
139     else
140       {
141         SHORT_DEC (into_color->red,   argv[j]->red);
142         SHORT_DEC (into_color->green, argv[j]->green);
143         SHORT_DEC (into_color->blue,  argv[j]->blue);
144       }
145 #undef SHORT_INC
146 #undef SHORT_DEC
147 }
148
149 /* fill in all the permutations of colors that XAllocColorCells() has 
150    allocated for us.  Thanks Ron, you're an additive kind of guy. */
151 static void
152 permute_colors (pcolors, colors, count, plane_masks, increment_p)
153      XColor *pcolors, *colors;
154      int count;
155      unsigned long *plane_masks;
156      Bool increment_p;
157 {
158   int out = 0;
159   int max = i_exp (2, count);
160   if (count > 31) abort ();
161   for (out = 1; out < max; out++)
162     {
163       XColor *argv [32];
164       int this_mask = 0;
165       int argc = 0;
166       int bit;
167       for (bit = 0; bit < 32; bit++)
168         if (out & (1<<bit))
169           {
170             argv [argc++] = &pcolors [bit];
171             this_mask |= plane_masks [bit];
172           }
173       merge_colors (argc, argv, &colors [out-1], this_mask, increment_p);
174     }
175 }
176
177
178 static struct qix **
179 init_qix (dpy, window)
180      Display *dpy;
181      Window window;
182 {
183   int nlines;
184   struct qix **qixes;
185   XGCValues gcv;
186   XWindowAttributes xgwa;
187   XGetWindowAttributes (dpy, window, &xgwa);
188   cmap = xgwa.colormap;
189   count = get_integer_resource ("count", "Integer");
190   if (count <= 0) count = 1;
191   nlines = get_integer_resource ("segments", "Integer");
192   if (nlines <= 0) nlines = 20;
193   get_geom (dpy, window);
194   max_spread = get_integer_resource ("spread", "Integer");
195   if (max_spread <= 0) max_spread = 10;
196   max_size = get_integer_resource ("size", "Integer");
197   if (max_size < 0) max_size = 0;
198   random_p = get_boolean_resource ("random", "Boolean");
199   solid_p = get_boolean_resource ("solid", "Boolean");
200   xor_p = get_boolean_resource ("xor", "Boolean");
201   transparent_p = get_boolean_resource ("transparent", "Boolean");
202   delay = get_integer_resource ("delay", "Integer");
203   color_shift = get_integer_resource ("colorShift", "Integer");
204   if (color_shift < 0 || color_shift >= 360) color_shift = 5;
205   if (delay < 0) delay = 0;
206
207   if (count == 1 && transparent_p)
208     transparent_p = False; /* it's a no-op */
209
210   if (transparent_p && CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
211     {
212       fprintf (stderr, "%s: -transparent only works on color displays.\n",
213                progname);
214       transparent_p = False;
215     }
216
217   if (xor_p && !transparent_p)
218     mono_p = True;
219
220   gcs[0] = gcs[1] = 0;
221   gcv.foreground = default_fg_pixel =
222     get_pixel_resource ("foreground", "Foreground", dpy, cmap);
223
224   if (transparent_p)
225     {
226       Bool increment_p = get_boolean_resource ("additive", "Boolean");
227       unsigned long plane_masks [32];
228       XColor *pcolors, *colors;
229       int nplanes = count;
230       int i, total_colors;
231
232       /* permutations would be harder if the number of planes didn't fit
233          in an int.  Who has >32-bit displays anyway... */
234       if (nplanes > 31) nplanes = 31;
235
236       while (nplanes > 1 &&
237              !XAllocColorCells (dpy, cmap, False, plane_masks, nplanes,
238                                 &base_pixel, 1))
239         nplanes--;
240
241       if (nplanes <= 1)
242         {
243           fprintf (stderr,
244          "%s: couldn't allocate any color planes; turning -transparent off.\n",
245                    progname);
246           transparent_p = False;
247           if (xor_p)
248             goto NON_TRANSPARENT_XOR;
249           else
250             goto NON_TRANSPARENT;
251         }
252       else if (nplanes != count)
253         {
254           fprintf (stderr,
255                    "%s: only allocated %d color planes (instead of %d).\n",
256                    progname, nplanes, count);
257           count = nplanes;
258         }
259
260       gcs[0] = (GC *) malloc (count * sizeof (GC));
261       gcs[1] = xor_p ? gcs[0] : (GC *) malloc (count * sizeof (GC));
262       total_colors = i_exp (2, count);
263       pcolors = (XColor *) calloc (count, sizeof (XColor));
264       colors = (XColor *) calloc (total_colors, sizeof (XColor));
265       for (i = 0; i < count; i++)
266         {
267           gcv.plane_mask = plane_masks [i];
268           gcv.foreground = ~0;
269           if (xor_p)
270             {
271               gcv.function = GXxor;
272               gcs [0][i] = XCreateGC (dpy, window,
273                                       GCForeground|GCFunction|GCPlaneMask,
274                                       &gcv);
275             }
276           else
277             {
278               gcs [0][i] = XCreateGC (dpy, window, GCForeground|GCPlaneMask,
279                                       &gcv);
280               gcv.foreground = 0;
281               gcs [1][i] = XCreateGC (dpy, window, GCForeground|GCPlaneMask,
282                                       &gcv);
283             }
284
285           /* pick the "primary" (not in that sense) colors.
286              If we are in subtractive mode, pick higher intensities. */
287           hsv_to_rgb (random () % 360, frand (1.0),
288                       frand (0.5) + (increment_p ? 0.2 : 0.5),
289                       &pcolors[i].red, &pcolors[i].green, &pcolors[i].blue);
290
291           pcolors [i].flags = DoRed | DoGreen | DoBlue;
292           pcolors [i].pixel = base_pixel | plane_masks [i];
293         }
294       permute_colors (pcolors, colors, count, plane_masks, increment_p);
295       /* clone the default background of the window into our "base" pixel */
296       colors [total_colors - 1].pixel =
297         get_pixel_resource ("background", "Background", dpy, cmap);
298       XQueryColor (dpy, cmap, &colors [total_colors - 1]);
299       colors [total_colors - 1].pixel = base_pixel;
300       XStoreColors (dpy, cmap, colors, total_colors);
301       XSetWindowBackground (dpy, window, base_pixel);
302       XClearWindow (dpy, window);
303     }
304   else if (xor_p)
305     {
306     NON_TRANSPARENT_XOR:
307       gcv.function = GXxor;
308       gcv.foreground =
309         (default_fg_pixel ^ get_pixel_resource ("background", "Background",
310                                                 dpy, cmap));
311       draw_gc = erase_gc = XCreateGC(dpy,window,GCForeground|GCFunction,&gcv);
312     }
313   else
314     {
315     NON_TRANSPARENT:
316       draw_gc = XCreateGC (dpy, window, GCForeground, &gcv);
317       gcv.foreground = get_pixel_resource ("background", "Background",
318                                            dpy, cmap);
319       erase_gc = XCreateGC (dpy, window, GCForeground, &gcv);
320     }
321
322   qixes = (struct qix **) malloc ((count + 1) * sizeof (struct qix *));
323   qixes [count] = 0;
324   while (count--)
325     {
326       qixes [count] = init_one_qix (dpy, window, nlines);
327       qixes [count]->id = count;
328     }
329   return qixes;
330 }
331
332 static void
333 free_qline (dpy, window, cmap, qline, prev, qix)
334      Display *dpy;
335      Window window;
336      Colormap cmap;
337      struct qline *qline, *prev;
338      struct qix *qix;
339 {
340   if (qline->dead || !prev)
341     ;
342   else if (solid_p)
343     {
344       XPoint points [4];
345       points [0].x = qline->p1.x; points [0].y = qline->p1.y;
346       points [1].x = qline->p2.x; points [1].y = qline->p2.y;
347       points [2].x = prev->p2.x; points [2].y = prev->p2.y;
348       points [3].x = prev->p1.x; points [3].y = prev->p1.y;
349       XFillPolygon (dpy, window, (transparent_p ? gcs[1][qix->id] : erase_gc),
350                     points, 4, Complex, CoordModeOrigin);
351     }
352   else
353     XDrawLine (dpy, window, (transparent_p ? gcs[1][qix->id] : erase_gc),
354                qline->p1.x, qline->p1.y, qline->p2.x, qline->p2.y);
355
356   if (!mono_p && !transparent_p)
357     XFreeColors (dpy, cmap, &qline->color.pixel, 1, 0);
358
359   qline->dead = True;
360 }
361
362 static void
363 add_qline (dpy, window, cmap, qline, prev_qline, qix)
364      Display *dpy;
365      Window window;
366      Colormap cmap;
367      struct qline *qline, *prev_qline;
368      struct qix *qix;
369 {
370   *qline = *prev_qline;
371
372 #define wiggle(point,delta,max)                                         \
373   if (random_p) delta += (random () % 3) - 1;                           \
374   if (delta > max_spread) delta = max_spread;                           \
375   else if (delta < -max_spread) delta = -max_spread;                    \
376   point += delta;                                                       \
377   if (point < 0) point = 0, delta = -delta, point += delta<<1;          \
378   else if (point > max) point = max, delta = -delta, point += delta<<1;
379   
380   wiggle (qline->p1.x, qline->p1.dx, maxx);
381   wiggle (qline->p1.y, qline->p1.dy, maxy);
382   wiggle (qline->p2.x, qline->p2.dx, maxx);
383   wiggle (qline->p2.y, qline->p2.dy, maxy);
384
385   if (max_size)
386     {
387       if (qline->p1.x - qline->p2.x > max_size)
388         qline->p1.x = qline->p2.x + max_size
389           - (random_p ? random() % max_spread : 0);
390       else if (qline->p2.x - qline->p1.x > max_size)
391         qline->p2.x = qline->p1.x + max_size
392           - (random_p ? random() % max_spread : 0);
393       if (qline->p1.y - qline->p2.y > max_size)
394         qline->p1.y = qline->p2.y + max_size
395           - (random_p ? random() % max_spread : 0);
396       else if (qline->p2.y - qline->p1.y > max_size)
397         qline->p2.y = qline->p1.y + max_size
398           - (random_p ? random() % max_spread : 0);
399     }
400
401   if (!mono_p && !transparent_p)
402     {
403       XColor desired;
404       cycle_hue (&qline->color, color_shift);
405       qline->color.flags = DoRed | DoGreen | DoBlue;
406       desired = qline->color;
407       if (XAllocColor (dpy, cmap, &qline->color))
408         {
409           /* XAllocColor returns the actual RGB that the hardware let us
410              allocate.  Restore the requested values into the XColor struct
411              so that limited-resolution hardware doesn't cause cycle_hue to
412              get "stuck". */
413           qline->color.red = desired.red;
414           qline->color.green = desired.green;
415           qline->color.blue = desired.blue;
416         }
417       else
418         {
419           qline->color = prev_qline->color;
420           if (!XAllocColor (dpy, cmap, &qline->color))
421             abort (); /* same color should work */
422         }
423       XSetForeground (dpy, draw_gc, qline->color.pixel);
424     }
425   if (! solid_p)
426     XDrawLine (dpy, window, (transparent_p ? gcs[0][qix->id] : draw_gc),
427                qline->p1.x, qline->p1.y, qline->p2.x, qline->p2.y);
428   else if (!prev_qline->dead)
429     {
430       XPoint points [4];
431       points [0].x = qline->p1.x; points [0].y = qline->p1.y;
432       points [1].x = qline->p2.x; points [1].y = qline->p2.y;
433       points [2].x = prev_qline->p2.x; points [2].y = prev_qline->p2.y;
434       points [3].x = prev_qline->p1.x; points [3].y = prev_qline->p1.y;
435       XFillPolygon (dpy, window, (transparent_p ? gcs[0][qix->id] : draw_gc),
436                     points, 4, Complex, CoordModeOrigin);
437     }
438
439   qline->dead = False;
440 }
441
442 static void
443 qix1 (dpy, window, qix)
444      Display *dpy;
445      Window window;
446      struct qix *qix;
447 {
448   int ofp = qix->fp - 1;
449   static int gtick = 0;
450   if (gtick++ == 500)
451     get_geom (dpy, window), gtick = 0;
452   if (ofp < 0) ofp = qix->nlines - 1;
453   free_qline (dpy, window, cmap, &qix->lines [qix->fp],
454               &qix->lines[(qix->fp + 1) % qix->nlines], qix);
455   add_qline (dpy, window, cmap, &qix->lines[qix->fp], &qix->lines[ofp], qix);
456   if ((++qix->fp) >= qix->nlines)
457     qix->fp = 0;
458 }
459
460 \f
461 char *progclass = "Qix";
462
463 char *defaults [] = {
464   "Qix.background:      black",         /* to placate SGI */
465   "Qix.foreground:      white",
466   "*count:      1",
467   "*segments:   50",
468   "*spread:     8",
469   "*size:       0",
470   "*colorShift: 3",
471   "*solid:      false",
472   "*delay:      10000",
473   "*random:     true",
474   "*xor:        false",
475   "*transparent:false",
476   "*additive:   true",
477   0
478 };
479
480 XrmOptionDescRec options [] = {
481   { "-count",           ".count",       XrmoptionSepArg, 0 },
482   { "-segments",        ".segments",    XrmoptionSepArg, 0 },
483   { "-spread",          ".spread",      XrmoptionSepArg, 0 },
484   { "-size",            ".size",        XrmoptionSepArg, 0 },
485   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
486   { "-color-shift",     ".colorShift",  XrmoptionSepArg, 0 },
487   { "-random",          ".random",      XrmoptionNoArg, "true" },
488   { "-linear",          ".random",      XrmoptionNoArg, "false" },
489   { "-solid",           ".solid",       XrmoptionNoArg, "true" },
490   { "-hollow",          ".solid",       XrmoptionNoArg, "false" },
491   { "-xor",             ".xor",         XrmoptionNoArg, "true" },
492   { "-no-xor",          ".xor",         XrmoptionNoArg, "false" },
493   { "-transparent",     ".transparent", XrmoptionNoArg, "true" },
494   { "-non-transparent", ".transparent", XrmoptionNoArg, "false" },
495   { "-additive",        ".additive",    XrmoptionNoArg, "true" },
496   { "-subtractive",     ".additive",    XrmoptionNoArg, "false" },
497 };
498 int options_size = (sizeof (options) / sizeof (options[0]));
499
500 void
501 screenhack (dpy, window)
502      Display *dpy;
503      Window window;
504 {
505   struct qix **q1 = init_qix (dpy, window);
506   struct qix **qn;
507   while (1)
508     for (qn = q1; *qn; qn++)
509       {
510         qix1 (dpy, window, *qn);
511         XSync (dpy, True);
512         if (delay) usleep (delay);
513       }
514 }