7cab38d68b531bd635b6723b28cf7d6ea29db3a2
[xscreensaver] / hacks / halo.c
1 /* xscreensaver, Copyright (c) 1993, 1995, 1996, 1997, 1998, 1999
2  *  Jamie Zawinski <jwz@jwz.org>
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   /* If the visual isn't color-indexed, don't bother trying to
130      allocate writable cells. */
131   if (cycle_p && !has_writable_cells (xgwa.screen, xgwa.visual))
132     cycle_p = False;
133
134
135   if (mono_p)
136     ;
137   else if (random() % (cmode == seuss_mode ? 2 : 10))
138     make_uniform_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
139                            True, &cycle_p, True);
140   else
141     make_smooth_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
142                           True, &cycle_p, True);
143
144   if (ncolors <= 2) mono_p = True;
145   if (mono_p) cycle_p = False;
146   if (mono_p) cmode = seuss_mode;
147
148   if (mono_p)
149     {
150       fg_pixel = get_pixel_resource ("foreground", "Foreground", dpy, cmap);
151       bg_pixel = get_pixel_resource ("background", "Background", dpy, cmap);
152     }
153   else
154     {
155       fg_index = 0;
156       bg_index = ncolors / 4;
157       if (fg_index == bg_index) bg_index++;
158       fg_pixel = colors[fg_index].pixel;
159       bg_pixel = colors[bg_index].pixel;
160     }
161
162   width = max (50, xgwa.width);
163   height = max (50, xgwa.height);
164
165 #ifdef DEBUG
166   width/=2; height/=2;
167 #endif
168
169   pixmap = XCreatePixmap (dpy, window, width, height, 1);
170   if (cmode == seuss_mode)
171     buffer = XCreatePixmap (dpy, window, width, height, 1);
172   else
173     buffer = 0;
174
175   gcv.foreground = 1;
176   gcv.background = 0;
177   draw_gc = XCreateGC (dpy, pixmap, GCForeground | GCBackground, &gcv);
178   gcv.foreground = 0;
179   erase_gc = XCreateGC (dpy, pixmap, GCForeground, &gcv);
180   gcv.foreground = fg_pixel;
181   gcv.background = bg_pixel;
182   copy_gc = XCreateGC (dpy, window, GCForeground | GCBackground, &gcv);
183
184   if (cmode == seuss_mode)
185     {
186       gcv.foreground = 1;
187       gcv.background = 0;
188       gcv.function = GXxor;
189       merge_gc = XCreateGC (dpy, pixmap,
190                             GCForeground | GCBackground | GCFunction, &gcv);
191     }
192   else
193     {
194       gcv.foreground = fg_pixel;
195       gcv.background = bg_pixel;
196       gcv.function = GXcopy;
197       merge_gc = XCreateGC (dpy, window,
198                             GCForeground | GCBackground | GCFunction, &gcv);
199     }
200
201   init_circles_1 (dpy, window);
202   XClearWindow (dpy, window);
203   if (buffer) XFillRectangle (dpy, buffer, erase_gc, 0, 0, width, height);
204 }
205
206 static void
207 run_circles (Display *dpy, Window window)
208 {
209   int i;
210   static int iterations = 0;
211   static int oiterations = 0;
212   static Bool first_time_p = True;
213   Bool done = False;
214   Bool inhibit_sleep = False;
215   static int clear_tick = 0;
216
217   XFillRectangle (dpy, pixmap, erase_gc, 0, 0, width, height);
218   for (i = 0; i < count; i++)
219     {
220       int radius = circles [i].radius;
221       int inc = circles [i].increment;
222
223       if (! (iterations & 1))  /* never stop on an odd number of iterations */
224         ;
225       else if (radius == 0)     /* eschew inf */
226         ;
227       else if (radius < 0)      /* stop when the circles are points */
228         done = True;
229       else                      /* stop when the circles fill the window */
230         {
231           /* Probably there's a simpler way to ask the musical question,
232              "is this square completely enclosed by this circle," but I've
233              forgotten too much trig to know it...  (That's not really the
234              right question anyway, but the right question is too hard.) */
235           double x1 = ((double) (-circles [i].x)) / ((double) radius);
236           double y1 = ((double) (-circles [i].y)) / ((double) radius);
237           double x2 = ((double) (width - circles [i].x)) / ((double) radius);
238           double y2 = ((double) (height - circles [i].y)) / ((double) radius);
239           x1 *= x1; x2 *= x2; y1 *= y1; y2 *= y2;
240           if ((x1 + y1) < 1 && (x2 + y2) < 1 && (x1 + y2) < 1 && (x2 + y1) < 1)
241             done = True;
242         }
243
244       if (radius > 0 &&
245           (cmode == seuss_mode ||       /* drawing all circles, or */
246            circles [0].increment < 0))  /* on the way back in */
247         {
248           XFillArc (dpy,
249                     (cmode == seuss_mode ? pixmap : window),
250                     (cmode == seuss_mode ? draw_gc : merge_gc),
251                     circles [i].x - radius, circles [i].y - radius,
252                     radius * 2, radius * 2, 0, 360*64);
253         }
254       circles [i].radius += inc;
255     }
256
257   if (cycle_p && cmode != seuss_mode)
258     {
259       struct timeval now;
260       static struct timeval then = { 0, };
261       unsigned long diff;
262 #ifdef GETTIMEOFDAY_TWO_ARGS
263       struct timezone tzp;
264       gettimeofday(&now, &tzp);
265 #else
266       gettimeofday(&now);
267 #endif
268       diff = (((now.tv_sec - then.tv_sec)  * 1000000) +
269               (now.tv_usec - then.tv_usec));
270       if (diff > cycle_delay)
271         {
272           rotate_colors (dpy, cmap, colors, ncolors, 1);
273           then = now;
274         }
275     }
276
277   if (anim_p && !first_time_p)
278     inhibit_sleep = !done;
279
280   if (done)
281     {
282       if (anim_p)
283         {
284           first_time_p = False;
285           for (i = 0; i < count; i++)
286             {
287               circles [i].x += circles [i].dx;
288               circles [i].y += circles [i].dy;
289               circles [i].radius %= circles [i].increment;
290               if (circles [i].x < 0 || circles [i].x >= width)
291                 {
292                   circles [i].dx = -circles [i].dx;
293                   circles [i].x += (2 * circles [i].dx);
294                 }
295               if (circles [i].y < 0 || circles [i].y >= height)
296                 {
297                   circles [i].dy = -circles [i].dy;
298                   circles [i].y += (2 * circles [i].dy);
299                 }
300             }
301         }
302       else if (circles [0].increment < 0)
303         {
304           /* We've zoomed out and the screen is blank -- re-pick the
305              center points, and shift the colors.
306            */
307           free (circles);
308           init_circles_1 (dpy, window);
309           if (! mono_p)
310             {
311               fg_index = (fg_index + 1) % ncolors;
312               bg_index = (fg_index + (ncolors/2)) % ncolors;
313               XSetForeground (dpy, copy_gc, colors [fg_index].pixel);
314               XSetBackground (dpy, copy_gc, colors [bg_index].pixel);
315             }
316         }
317       /* Sometimes go out from the inside instead of the outside */
318       else if (clear_tick == 0 && ((random () % 3) == 0))
319         {
320           iterations = 0; /* ick */
321           for (i = 0; i < count; i++)
322             circles [i].radius %= circles [i].increment;
323
324           clear_tick = ((random() % 8) + 4) | 1;   /* must be odd */
325         }
326       else
327         {
328           oiterations = iterations;
329           for (i = 0; i < count; i++)
330             {
331               circles [i].increment = -circles [i].increment;
332               circles [i].radius += (2 * circles [i].increment);
333             }
334         }
335     }
336
337   if (buffer)
338     XCopyPlane (dpy, pixmap, buffer, merge_gc, 0, 0, width, height, 0, 0, 1);
339   else if (cmode != seuss_mode)
340     {
341
342       if (!mono_p)
343         {
344           fg_index++;
345           bg_index++;
346           if (fg_index >= ncolors) fg_index = 0;
347           if (bg_index >= ncolors) bg_index = 0;
348           XSetForeground (dpy, merge_gc, colors [fg_index].pixel);
349         }
350
351       if (circles [0].increment >= 0)
352         inhibit_sleep = True;
353       else if (done && cmode == seuss_mode)
354         XFillRectangle (dpy, window, merge_gc, 0, 0, width, height);
355     }
356   else
357     XCopyPlane (dpy, pixmap, window, merge_gc, 0, 0, width, height, 0, 0, 1);
358
359   /* buffer is only used in seuss-mode or anim-mode */
360   if (buffer && (anim_p
361                  ? (done || (first_time_p && (iterations & 1)))
362                  : (iterations & 1)))
363     {
364       XCopyPlane (dpy, buffer, window, copy_gc, 0, 0, width, height, 0, 0, 1);
365       XSync (dpy, False);
366       if (anim_p && done)
367         XFillRectangle (dpy, buffer, erase_gc, 0, 0, width, height);
368     }
369
370 #ifdef DEBUG
371   XCopyPlane (dpy, pixmap, window, copy_gc, 0,0,width,height,width,height, 1);
372   if (buffer)
373     XCopyPlane (dpy, buffer, window, copy_gc, 0,0,width,height,0,height, 1);
374   XSync (dpy, False);
375 #endif
376
377   if (done)
378     iterations = 0;
379   else
380     iterations++;
381
382   if (delay && !inhibit_sleep)
383     {
384       static Bool really_first_p = True;
385       int direction = 1;
386       int d = delay;
387       if (done && cycle_p && cmode != seuss_mode && !really_first_p)
388         {
389           d = delay2;
390           if (! (random() % 10))
391             direction = -1;
392         }
393
394       XSync(dpy, False);
395       screenhack_handle_events (dpy);
396
397       if (cycle_p && cycle_delay)
398         {
399           int i = 0;
400           while (i < d)
401             {
402               rotate_colors (dpy, cmap, colors, ncolors, direction);
403               usleep(cycle_delay);
404               screenhack_handle_events (dpy);
405               i += cycle_delay;
406             }
407         }
408       else if (cmode != seuss_mode &&
409                done && !really_first_p && cycle_delay > 0)
410         usleep (cycle_delay * 50);
411       else
412         usleep (d);
413
414       if (done)
415         really_first_p = False;
416     }
417
418   if (done && clear_tick > 0)
419     {
420       clear_tick--;
421       if (!clear_tick)
422         {
423           XClearWindow (dpy, window);
424           if (buffer) XFillRectangle (dpy, buffer, erase_gc, 0,0,width,height);
425         }
426     }
427 }
428
429 \f
430 char *progclass = "Halo";
431
432 char *defaults [] = {
433   ".background:         black",
434   ".foreground:         white",
435   "*colorMode:          random",
436   "*colors:             100",
437   "*cycle:              true",
438   "*count:              0",
439   "*delay:              100000",
440   "*delay2:             20",
441   "*cycleDelay:         100000",
442   0
443 };
444
445 XrmOptionDescRec options [] = {
446   { "-count",           ".count",       XrmoptionSepArg, 0 },
447   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
448   { "-cycle-delay",     ".cycleDelay",  XrmoptionSepArg, 0 },
449   { "-animate",         ".animate",     XrmoptionNoArg, "True" },
450   { "-mode",            ".colorMode",   XrmoptionSepArg, 0 },
451   { "-colors",          ".colors",      XrmoptionSepArg, 0 },
452   { "-cycle",           ".cycle",       XrmoptionNoArg, "True" },
453   { "-no-cycle",        ".cycle",       XrmoptionNoArg, "False" },
454   { 0, 0, 0, 0 }
455 };
456
457 void
458 screenhack (Display *dpy, Window window)
459 {
460   init_circles (dpy, window);
461   while (1)
462     {
463       run_circles (dpy, window);
464       screenhack_handle_events (dpy);
465     }
466 }