http://ftp.x.org/contrib/applications/xscreensaver-2.23.tar.gz
[xscreensaver] / hacks / halo.c
1 /* xscreensaver, Copyright (c) 1993, 1995, 1996, 1997
2  *  Jamie Zawinski <jwz@netscape.com>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 /* I wanted to lay down new circles with TV:ALU-ADD instead of TV:ALU-XOR,
14    but X doesn't support arithmetic combinations of pixmaps!!  What losers.
15    I suppose I could crank out the 2's compliment math by hand, but that's
16    a real drag...
17
18    This would probably look good with shapes other than circles as well.
19
20  */
21
22 #include "screenhack.h"
23 #include <stdio.h>
24
25 struct circle {
26   int x, y, radius;
27   int increment;
28   int dx, dy;
29 };
30
31 static enum color_mode {
32   seuss_mode, ramp_mode, random_mode
33 } cmode;
34
35
36 static struct circle *circles;
37 static int count, global_count;
38 static Pixmap pixmap, buffer;
39 static int width, height, global_inc;
40 static int delay, delay2, cycle_delay;
41 static unsigned long fg_pixel, bg_pixel;
42 static GC draw_gc, erase_gc, copy_gc, merge_gc;
43 static Bool anim_p;
44 static Colormap cmap;
45
46 static int ncolors;
47 static XColor *colors;
48 static Bool cycle_p;
49 static int fg_index;
50 static int bg_index;
51
52
53 #define min(x,y) ((x)<(y)?(x):(y))
54 #define max(x,y) ((x)>(y)?(x):(y))
55
56 static void
57 init_circles_1 (Display *dpy, Window window)
58 {
59   int i;
60   count = (global_count ? global_count
61            : (3 + (random () % max (1, (min (width, height) / 50)))
62                 + (random () % max (1, (min (width, height) / 50)))));
63   circles = (struct circle *) malloc (count * sizeof (struct circle));
64   for (i = 0; i < count; i++)
65     {
66       circles [i].x = 10 + random () % (width - 20);
67       circles [i].y = 10 + random () % (height - 20);
68       if (global_inc)
69       circles [i].increment = global_inc;
70       else
71         { /* prefer smaller increments to larger ones */
72           int j = 8;
73           int inc = ((random()%j) + (random()%j) + (random()%j)) - ((j*3)/2);
74           if (inc < 0) inc = -inc + 3;
75           circles [i].increment = inc + 3;
76         }
77       circles [i].radius = random () % circles [i].increment;
78       circles [i].dx = ((random () % 3) - 1) * (1 + random () % 5);
79       circles [i].dy = ((random () % 3) - 1) * (1 + random () % 5);
80     }
81 }
82
83 static void
84 init_circles (Display *dpy, Window window)
85 {
86   XGCValues gcv;
87   XWindowAttributes xgwa;
88   char *mode_str = 0;
89   XGetWindowAttributes (dpy, window, &xgwa);
90   cmap = xgwa.colormap;
91   global_count = get_integer_resource ("count", "Integer");
92   if (global_count < 0) global_count = 0;
93   global_inc = get_integer_resource ("increment", "Integer");
94   if (global_inc < 0) global_inc = 0;
95   anim_p = get_boolean_resource ("animate", "Boolean");
96   delay = get_integer_resource ("delay", "Integer");
97   delay2 = get_integer_resource ("delay2", "Integer") * 1000000;
98   cycle_delay = get_integer_resource ("cycleDelay", "Integer");
99   mode_str = get_string_resource ("colorMode", "ColorMode");
100   if (! mode_str) cmode = random_mode;
101   else if (!strcmp (mode_str, "seuss"))  cmode = seuss_mode;
102   else if (!strcmp (mode_str, "ramp"))   cmode = ramp_mode;
103   else if (!strcmp (mode_str, "random")) cmode = random_mode;
104   else {
105     fprintf (stderr,
106              "%s: colorMode must be seuss, ramp, or random, not \"%s\"\n",
107              progname, mode_str);
108     exit (1);
109   }
110
111   if (mono_p) cmode = seuss_mode;
112   if (cmode == random_mode)
113     cmode = ((random()&3) == 1) ? ramp_mode : seuss_mode;
114
115   if (cmode == ramp_mode)
116     anim_p = False;    /* This combo doesn't work right... */
117
118   ncolors = get_integer_resource ("colors", "Colors");
119   if (ncolors < 2) ncolors = 2;
120   if (ncolors <= 2) mono_p = True;
121
122   if (mono_p)
123     colors = 0;
124   else
125     colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
126
127   cycle_p = mono_p ? False : get_boolean_resource ("cycle", "Cycle");
128
129
130   if (mono_p)
131     ;
132   else if (random() % (cmode == seuss_mode ? 2 : 10))
133     make_uniform_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
134                            True, &cycle_p, True);
135   else
136     make_smooth_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
137                           True, &cycle_p, True);
138
139   if (ncolors <= 2) mono_p = True;
140   if (mono_p) cycle_p = False;
141   if (mono_p) cmode = seuss_mode;
142
143   if (mono_p)
144     {
145       fg_pixel = get_pixel_resource ("foreground", "Foreground", dpy, cmap);
146       bg_pixel = get_pixel_resource ("background", "Background", dpy, cmap);
147     }
148   else
149     {
150       fg_index = 0;
151       bg_index = ncolors / 4;
152       if (fg_index == bg_index) bg_index++;
153       fg_pixel = colors[fg_index].pixel;
154       bg_pixel = colors[bg_index].pixel;
155     }
156
157   width = max (50, xgwa.width);
158   height = max (50, xgwa.height);
159
160 #ifdef DEBUG
161   width/=2; height/=2;
162 #endif
163
164   pixmap = XCreatePixmap (dpy, window, width, height, 1);
165   if (cmode == seuss_mode)
166     buffer = XCreatePixmap (dpy, window, width, height, 1);
167   else
168     buffer = 0;
169
170   gcv.foreground = 1;
171   gcv.background = 0;
172   draw_gc = XCreateGC (dpy, pixmap, GCForeground | GCBackground, &gcv);
173   gcv.foreground = 0;
174   erase_gc = XCreateGC (dpy, pixmap, GCForeground, &gcv);
175   gcv.foreground = fg_pixel;
176   gcv.background = bg_pixel;
177   copy_gc = XCreateGC (dpy, window, GCForeground | GCBackground, &gcv);
178
179   if (cmode == seuss_mode)
180     {
181       gcv.foreground = 1;
182       gcv.background = 0;
183       gcv.function = GXxor;
184       merge_gc = XCreateGC (dpy, pixmap,
185                             GCForeground | GCBackground | GCFunction, &gcv);
186     }
187   else
188     {
189       gcv.foreground = fg_pixel;
190       gcv.background = bg_pixel;
191       gcv.function = GXcopy;
192       merge_gc = XCreateGC (dpy, window,
193                             GCForeground | GCBackground | GCFunction, &gcv);
194     }
195
196   init_circles_1 (dpy, window);
197   XClearWindow (dpy, window);
198   if (buffer) XFillRectangle (dpy, buffer, erase_gc, 0, 0, width, height);
199 }
200
201 static void
202 run_circles (Display *dpy, Window window)
203 {
204   int i;
205   static int iterations = 0;
206   static int oiterations = 0;
207   static Bool first_time_p = True;
208   Bool done = False;
209   Bool inhibit_sleep = False;
210   XFillRectangle (dpy, pixmap, erase_gc, 0, 0, width, height);
211   for (i = 0; i < count; i++)
212     {
213       int radius = circles [i].radius;
214       int inc = circles [i].increment;
215
216       if (! (iterations & 1))  /* never stop on an odd number of iterations */
217         ;
218       else if (radius == 0)     /* eschew inf */
219         ;
220       else if (radius < 0)      /* stop when the circles are points */
221         done = True;
222       else                      /* stop when the circles fill the window */
223         {
224           /* Probably there's a simpler way to ask the musical question,
225              "is this square completely enclosed by this circle," but I've
226              forgotten too much trig to know it...  (That's not really the
227              right question anyway, but the right question is too hard.) */
228           double x1 = ((double) (-circles [i].x)) / ((double) radius);
229           double y1 = ((double) (-circles [i].y)) / ((double) radius);
230           double x2 = ((double) (width - circles [i].x)) / ((double) radius);
231           double y2 = ((double) (height - circles [i].y)) / ((double) radius);
232           x1 *= x1; x2 *= x2; y1 *= y1; y2 *= y2;
233           if ((x1 + y1) < 1 && (x2 + y2) < 1 && (x1 + y2) < 1 && (x2 + y1) < 1)
234             done = True;
235         }
236
237       if (radius > 0 &&
238           (cmode == seuss_mode ||       /* drawing all circles, or */
239            circles [0].increment < 0))  /* on the way back in */
240         {
241           XFillArc (dpy,
242                     (cmode == seuss_mode ? pixmap : window),
243                     (cmode == seuss_mode ? draw_gc : merge_gc),
244                     circles [i].x - radius, circles [i].y - radius,
245                     radius * 2, radius * 2, 0, 360*64);
246         }
247       circles [i].radius += inc;
248     }
249
250   if (cycle_p && cmode != seuss_mode)
251     {
252       struct timeval now;
253       static struct timeval then = { 0, };
254       unsigned long diff;
255 #ifdef GETTIMEOFDAY_TWO_ARGS
256       struct timezone tzp;
257       gettimeofday(&now, &tzp);
258 #else
259       gettimeofday(&now);
260 #endif
261       diff = (((now.tv_sec - then.tv_sec)  * 1000000) +
262               (now.tv_usec - then.tv_usec));
263       if (diff > cycle_delay)
264         {
265           rotate_colors (dpy, cmap, colors, ncolors, 1);
266           then = now;
267         }
268     }
269
270   if (anim_p && !first_time_p)
271     inhibit_sleep = !done;
272
273   if (done)
274     {
275       if (anim_p)
276         {
277           first_time_p = False;
278           for (i = 0; i < count; i++)
279             {
280               circles [i].x += circles [i].dx;
281               circles [i].y += circles [i].dy;
282               circles [i].radius %= circles [i].increment;
283               if (circles [i].x < 0 || circles [i].x >= width)
284                 {
285                   circles [i].dx = -circles [i].dx;
286                   circles [i].x += (2 * circles [i].dx);
287                 }
288               if (circles [i].y < 0 || circles [i].y >= height)
289                 {
290                   circles [i].dy = -circles [i].dy;
291                   circles [i].y += (2 * circles [i].dy);
292                 }
293             }
294         }
295       else if (circles [0].increment < 0)
296         {
297           /* We've zoomed out and the screen is blank -- re-pick the
298              center points, and shift the colors.
299            */
300           free (circles);
301           init_circles_1 (dpy, window);
302           if (! mono_p)
303             {
304               fg_index = (fg_index + 1) % ncolors;
305               bg_index = (fg_index + (ncolors/2)) % ncolors;
306               XSetForeground (dpy, copy_gc, colors [fg_index].pixel);
307               XSetBackground (dpy, copy_gc, colors [bg_index].pixel);
308             }
309         }
310 #if 1
311       /* Sometimes go out from the inside instead of the outside */
312       else if ((random () % 10) == 0)
313         {
314 # if 0
315           if (! mono_p)
316             {
317               unsigned long swap = fg_index;
318               fg_index = bg_index;
319               bg_index = swap;
320               XSetForeground (dpy, copy_gc, colors [fg_index].pixel);
321               XSetBackground (dpy, copy_gc, colors [bg_index].pixel);
322             }
323 # endif
324           iterations = 0; /* ick */
325           for (i = 0; i < count; i++)
326             circles [i].radius %= circles [i].increment;
327         }
328 #endif
329       else
330         {
331           oiterations = iterations;
332           for (i = 0; i < count; i++)
333             {
334               circles [i].increment = -circles [i].increment;
335               circles [i].radius += (2 * circles [i].increment);
336             }
337         }
338     }
339
340   if (buffer)
341     XCopyPlane (dpy, pixmap, buffer, merge_gc, 0, 0, width, height, 0, 0, 1);
342   else if (cmode != seuss_mode)
343     {
344
345       if (!mono_p)
346         {
347           fg_index++;
348           bg_index++;
349           if (fg_index >= ncolors) fg_index = 0;
350           if (bg_index >= ncolors) bg_index = 0;
351           XSetForeground (dpy, merge_gc, colors [fg_index].pixel);
352         }
353
354       if (circles [0].increment >= 0)
355         inhibit_sleep = True;
356       else if (done && cmode == seuss_mode)
357         XFillRectangle (dpy, window, merge_gc, 0, 0, width, height);
358     }
359   else
360     XCopyPlane (dpy, pixmap, window, merge_gc, 0, 0, width, height, 0, 0, 1);
361
362   /* buffer is only used in seuss-mode or anim-mode */
363   if (buffer && (anim_p
364                  ? (done || (first_time_p && (iterations & 1)))
365                  : (iterations & 1)))
366     {
367       XCopyPlane (dpy, buffer, window, copy_gc, 0, 0, width, height, 0, 0, 1);
368       XSync (dpy, True);
369       if (anim_p && done)
370         XFillRectangle (dpy, buffer, erase_gc, 0, 0, width, height);
371     }
372
373 #ifdef DEBUG
374   XCopyPlane (dpy, pixmap, window, copy_gc, 0,0,width,height,width,height, 1);
375   if (buffer)
376     XCopyPlane (dpy, buffer, window, copy_gc, 0,0,width,height,0,height, 1);
377   XSync (dpy, True);
378 #endif
379
380   if (done)
381     iterations = 0;
382   else
383     iterations++;
384
385   if (delay && !inhibit_sleep)
386     {
387       static Bool really_first_p = True;
388       int direction = 1;
389       int d = delay;
390       if (done && cycle_p && cmode != seuss_mode && !really_first_p)
391         {
392           d = delay2;
393           if (! (random() % 10))
394             direction = -1;
395         }
396       if (done)
397         really_first_p = False;
398
399       XSync(dpy, False);
400
401       if (cycle_p && cycle_delay)
402         {
403           int i = 0;
404           while (i < d)
405             {
406               rotate_colors (dpy, cmap, colors, ncolors, direction);
407               usleep(cycle_delay);
408               i += cycle_delay;
409             }
410         }
411       else
412         usleep (d);
413     }
414 }
415
416 \f
417 char *progclass = "Halo";
418
419 char *defaults [] = {
420   ".background:         black",
421   ".foreground:         white",
422   "*colorMode:          random",
423   "*colors:             100",
424   "*cycle:              true",
425   "*count:              0",
426   "*delay:              100000",
427   "*delay2:             20",
428   "*cycleDelay:         100000",
429   0
430 };
431
432 XrmOptionDescRec options [] = {
433   { "-count",           ".count",       XrmoptionSepArg, 0 },
434   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
435   { "-cycle-delay",     ".cycleDelay",  XrmoptionSepArg, 0 },
436   { "-animate",         ".animate",     XrmoptionNoArg, "True" },
437   { "-mode",            ".colorMode",   XrmoptionSepArg, 0 },
438   { "-colors",          ".colors",      XrmoptionSepArg, 0 },
439   { "-cycle",           ".cycle",       XrmoptionNoArg, "True" },
440   { "-no-cycle",        ".cycle",       XrmoptionNoArg, "False" },
441   { 0, 0, 0, 0 }
442 };
443
444 void
445 screenhack (Display *dpy, Window window)
446 {
447   init_circles (dpy, window);
448   while (1)
449     run_circles (dpy, window);
450 }