2b7e30fb79616a307fc67a50b7148d224e1a0e3e
[xscreensaver] / hacks / halo.c
1 /* xscreensaver, Copyright (c) 1993, 1995, 1996, 1997, 1998, 1999, 2003, 2006
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 struct state {
37   Display *dpy;
38   Window window;
39
40   struct circle *circles;
41   int count, global_count;
42   Pixmap pixmap, buffer;
43   int width, height, global_inc;
44   int delay, delay2;
45   unsigned long fg_pixel, bg_pixel;
46   GC draw_gc, erase_gc, copy_gc, merge_gc;
47   Bool anim_p;
48   Colormap cmap;
49
50   int ncolors;
51   XColor *colors;
52   int fg_index;
53   int bg_index;
54
55   int iterations;
56   Bool done_once;
57   Bool done_once_no_really;
58   int clear_tick;
59   struct timeval then;
60 };
61
62 #define min(x,y) ((x)<(y)?(x):(y))
63 #define max(x,y) ((x)>(y)?(x):(y))
64
65 static void
66 init_circles_1 (struct state *st)
67 {
68   int i;
69   st->count = (st->global_count ? st->global_count
70            : (3 + (random () % max (1, (min (st->width, st->height) / 50)))
71                 + (random () % max (1, (min (st->width, st->height) / 50)))));
72   st->circles = (struct circle *) malloc (st->count * sizeof (struct circle));
73   for (i = 0; i < st->count; i++)
74     {
75       st->circles [i].x = 10 + random () % (st->width - 20);
76       st->circles [i].y = 10 + random () % (st->height - 20);
77       if (st->global_inc)
78       st->circles [i].increment = st->global_inc;
79       else
80         { /* prefer smaller increments to larger ones */
81           int j = 8;
82           int inc = ((random()%j) + (random()%j) + (random()%j)) - ((j*3)/2);
83           if (inc < 0) inc = -inc + 3;
84           st->circles [i].increment = inc + 3;
85         }
86       st->circles [i].radius = random () % st->circles [i].increment;
87       st->circles [i].dx = ((random () % 3) - 1) * (1 + random () % 5);
88       st->circles [i].dy = ((random () % 3) - 1) * (1 + random () % 5);
89     }
90 }
91
92 static void *
93 halo_init (Display *dpy, Window window)
94 {
95   struct state *st = (struct state *) calloc (1, sizeof(*st));
96   XGCValues gcv;
97   XWindowAttributes xgwa;
98   char *mode_str = 0;
99   st->dpy = dpy;
100   st->window = window;
101   XGetWindowAttributes (st->dpy, st->window, &xgwa);
102   st->cmap = xgwa.colormap;
103   st->global_count = get_integer_resource (st->dpy, "count", "Integer");
104   if (st->global_count < 0) st->global_count = 0;
105   st->global_inc = get_integer_resource (st->dpy, "increment", "Integer");
106   if (st->global_inc < 0) st->global_inc = 0;
107   st->anim_p = get_boolean_resource (st->dpy, "animate", "Boolean");
108   st->delay = get_integer_resource (st->dpy, "delay", "Integer");
109   st->delay2 = get_integer_resource (st->dpy, "delay2", "Integer") * 1000000;
110   mode_str = get_string_resource (st->dpy, "colorMode", "ColorMode");
111   if (! mode_str) cmode = random_mode;
112   else if (!strcmp (mode_str, "seuss"))  cmode = seuss_mode;
113   else if (!strcmp (mode_str, "ramp"))   cmode = ramp_mode;
114   else if (!strcmp (mode_str, "random")) cmode = random_mode;
115   else {
116     fprintf (stderr,
117              "%s: colorMode must be seuss, ramp, or random, not \"%s\"\n",
118              progname, mode_str);
119     exit (1);
120   }
121
122   if (mono_p) cmode = seuss_mode;
123   if (cmode == random_mode)
124     cmode = ((random()&3) == 1) ? ramp_mode : seuss_mode;
125
126   if (cmode == ramp_mode)
127     st->anim_p = False;    /* This combo doesn't work right... */
128
129   st->ncolors = get_integer_resource (st->dpy, "colors", "Colors");
130   if (st->ncolors < 2) st->ncolors = 2;
131   if (st->ncolors <= 2) mono_p = True;
132
133   if (mono_p)
134     st->colors  = 0;
135   else
136     st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
137
138
139   if (mono_p)
140     ;
141   else if (random() % (cmode == seuss_mode ? 2 : 10))
142     make_uniform_colormap (st->dpy, xgwa.visual, st->cmap, st->colors, &st->ncolors,
143                            True, 0, True);
144   else
145     make_smooth_colormap (st->dpy, xgwa.visual, st->cmap, st->colors, &st->ncolors,
146                           True, 0, True);
147
148   if (st->ncolors <= 2) mono_p = True;
149   if (mono_p) cmode = seuss_mode;
150
151   if (mono_p)
152     {
153       st->fg_pixel = get_pixel_resource (st->dpy, st->cmap, "foreground", "Foreground");
154       st->bg_pixel = get_pixel_resource (st->dpy, st->cmap, "background", "Background");
155     }
156   else
157     {
158       st->fg_index = 0;
159       st->bg_index = st->ncolors / 4;
160       if (st->fg_index == st->bg_index) st->bg_index++;
161       st->fg_pixel = st->colors[st->fg_index].pixel;
162       st->bg_pixel = st->colors[st->bg_index].pixel;
163     }
164
165   st->width = max (50, xgwa.width);
166   st->height = max (50, xgwa.height);
167
168 #ifdef DEBUG
169   st->width/=2; st->height/=2;
170 #endif
171
172   st->pixmap = XCreatePixmap (st->dpy, st->window, st->width, st->height, 1);
173   if (cmode == seuss_mode)
174     st->buffer = XCreatePixmap (st->dpy, st->window, st->width, st->height, 1);
175   else
176     st->buffer = 0;
177
178   gcv.foreground = 1;
179   gcv.background = 0;
180   st->draw_gc = XCreateGC (st->dpy, st->pixmap, GCForeground | GCBackground, &gcv);
181   gcv.foreground = 0;
182   st->erase_gc = XCreateGC (st->dpy, st->pixmap, GCForeground, &gcv);
183   gcv.foreground = st->fg_pixel;
184   gcv.background = st->bg_pixel;
185   st->copy_gc = XCreateGC (st->dpy, st->window, GCForeground | GCBackground, &gcv);
186
187 #ifdef HAVE_COCOA
188   jwxyz_XSetAntiAliasing (dpy, st->draw_gc,  False);
189   jwxyz_XSetAntiAliasing (dpy, st->erase_gc, False);
190   jwxyz_XSetAntiAliasing (dpy, st->copy_gc,  False);
191 #endif
192
193   if (cmode == seuss_mode)
194     {
195       gcv.foreground = 1;
196       gcv.background = 0;
197       gcv.function = GXxor;
198       st->merge_gc = XCreateGC (st->dpy, st->pixmap,
199                             GCForeground | GCBackground | GCFunction, &gcv);
200     }
201   else
202     {
203       gcv.foreground = st->fg_pixel;
204       gcv.background = st->bg_pixel;
205       gcv.function = GXcopy;
206       st->merge_gc = XCreateGC (st->dpy, st->window,
207                             GCForeground | GCBackground | GCFunction, &gcv);
208     }
209
210   init_circles_1 (st);
211   XClearWindow (st->dpy, st->window);
212   if (st->buffer) XFillRectangle (st->dpy, st->buffer, st->erase_gc, 0, 0, st->width, st->height);
213   return st;
214 }
215
216 static unsigned long
217 halo_draw (Display *dpy, Window window, void *closure)
218 {
219   struct state *st = (struct state *) closure;
220   int i;
221   Bool done = False;
222   Bool inhibit_sleep = False;
223   int this_delay = st->delay;
224
225   XFillRectangle (st->dpy, st->pixmap, st->erase_gc, 0, 0, st->width, st->height);
226   for (i = 0; i < st->count; i++)
227     {
228       int radius = st->circles [i].radius;
229       int inc = st->circles [i].increment;
230
231       if (! (st->iterations & 1))  /* never stop on an odd number of iterations */
232         ;
233       else if (radius == 0)     /* eschew inf */
234         ;
235       else if (radius < 0)      /* stop when the circles are points */
236         done = True;
237       else                      /* stop when the circles fill the st->window */
238         {
239           /* Probably there's a simpler way to ask the musical question,
240              "is this square completely enclosed by this circle," but I've
241              forgotten too much trig to know it...  (That's not really the
242              right question anyway, but the right question is too hard.) */
243           double x1 = ((double) (-st->circles [i].x)) / ((double) radius);
244           double y1 = ((double) (-st->circles [i].y)) / ((double) radius);
245           double x2 = ((double) (st->width - st->circles [i].x)) / ((double) radius);
246           double y2 = ((double) (st->height - st->circles [i].y)) / ((double) radius);
247           x1 *= x1; x2 *= x2; y1 *= y1; y2 *= y2;
248           if ((x1 + y1) < 1 && (x2 + y2) < 1 && (x1 + y2) < 1 && (x2 + y1) < 1)
249             done = True;
250         }
251
252       if (radius > 0 &&
253           (cmode == seuss_mode ||       /* drawing all circles, or */
254            st->circles [0].increment < 0))      /* on the way back in */
255         {
256           XFillArc (st->dpy,
257                     (cmode == seuss_mode ? st->pixmap : st->window),
258                     (cmode == seuss_mode ? st->draw_gc : st->merge_gc),
259                     st->circles [i].x - radius, st->circles [i].y - radius,
260                     radius * 2, radius * 2, 0, 360*64);
261         }
262       st->circles [i].radius += inc;
263     }
264
265   if (st->anim_p && !st->done_once)
266     inhibit_sleep = !done;
267
268   if (done)
269     {
270       if (st->anim_p)
271         {
272           st->done_once = True;
273           for (i = 0; i < st->count; i++)
274             {
275               st->circles [i].x += st->circles [i].dx;
276               st->circles [i].y += st->circles [i].dy;
277               st->circles [i].radius %= st->circles [i].increment;
278               if (st->circles [i].x < 0 || st->circles [i].x >= st->width)
279                 {
280                   st->circles [i].dx = -st->circles [i].dx;
281                   st->circles [i].x += (2 * st->circles [i].dx);
282                 }
283               if (st->circles [i].y < 0 || st->circles [i].y >= st->height)
284                 {
285                   st->circles [i].dy = -st->circles [i].dy;
286                   st->circles [i].y += (2 * st->circles [i].dy);
287                 }
288             }
289         }
290       else if (st->circles [0].increment < 0)
291         {
292           /* We've zoomed out and the screen is blank -- re-pick the
293              center points, and shift the st->colors.
294            */
295           free (st->circles);
296           init_circles_1 (st);
297           if (! mono_p)
298             {
299               st->fg_index = (st->fg_index + 1) % st->ncolors;
300               st->bg_index = (st->fg_index + (st->ncolors/2)) % st->ncolors;
301               XSetForeground (st->dpy, st->copy_gc, st->colors [st->fg_index].pixel);
302               XSetBackground (st->dpy, st->copy_gc, st->colors [st->bg_index].pixel);
303             }
304         }
305       /* Sometimes go out from the inside instead of the outside */
306       else if (st->clear_tick == 0 && ((random () % 3) == 0))
307         {
308           st->iterations = 0; /* ick */
309           for (i = 0; i < st->count; i++)
310             st->circles [i].radius %= st->circles [i].increment;
311
312           st->clear_tick = ((random() % 8) + 4) | 1;   /* must be odd */
313         }
314       else
315         {
316           for (i = 0; i < st->count; i++)
317             {
318               st->circles [i].increment = -st->circles [i].increment;
319               st->circles [i].radius += (2 * st->circles [i].increment);
320             }
321         }
322     }
323
324   if (st->buffer)
325     XCopyPlane (st->dpy, st->pixmap, st->buffer, st->merge_gc, 0, 0, st->width, st->height, 0, 0, 1);
326   else if (cmode != seuss_mode)
327     {
328
329       if (!mono_p)
330         {
331           st->fg_index++;
332           st->bg_index++;
333           if (st->fg_index >= st->ncolors) st->fg_index = 0;
334           if (st->bg_index >= st->ncolors) st->bg_index = 0;
335           XSetForeground (st->dpy, st->merge_gc, st->colors [st->fg_index].pixel);
336         }
337
338       if (st->circles [0].increment >= 0)
339         inhibit_sleep = True;
340       else if (done && cmode == seuss_mode)
341         XFillRectangle (st->dpy, st->window, st->merge_gc, 0, 0, st->width, st->height);
342     }
343   else
344     XCopyPlane (st->dpy, st->pixmap, st->window, st->merge_gc, 0, 0, st->width, st->height, 0, 0, 1);
345
346   /* st->buffer is only used in seuss-mode or anim-mode */
347   if (st->buffer && (st->anim_p
348                  ? (done || (!st->done_once && (st->iterations & 1)))
349                  : (st->iterations & 1)))
350     {
351       XCopyPlane (st->dpy, st->buffer, st->window, st->copy_gc, 0, 0, st->width, st->height, 0, 0, 1);
352       if (st->anim_p && done)
353         XFillRectangle (st->dpy, st->buffer, st->erase_gc, 0, 0, st->width, st->height);
354     }
355
356 #ifdef DEBUG
357   XCopyPlane (st->dpy, st->pixmap, st->window, st->copy_gc, 0,0,st->width,st->height,st->width,st->height, 1);
358   if (st->buffer)
359     XCopyPlane (st->dpy, st->buffer, st->window, st->copy_gc, 0,0,st->width,st->height,0,st->height, 1);
360 #endif
361
362   if (done)
363     st->iterations = 0;
364   else
365     st->iterations++;
366
367   if (st->delay && !inhibit_sleep)
368     {
369       int d = st->delay;
370
371       if (cmode == seuss_mode && st->anim_p)
372         this_delay = d/100;
373       else
374         this_delay = d;
375
376       if (done)
377         st->done_once_no_really = True;
378     }
379
380   if (done && st->clear_tick > 0)
381     {
382       st->clear_tick--;
383       if (!st->clear_tick)
384         {
385           XClearWindow (st->dpy, st->window);
386           if (st->buffer) XFillRectangle (st->dpy, st->buffer, st->erase_gc, 0,0,st->width,st->height);
387         }
388     }
389
390   if (inhibit_sleep) this_delay = 0;
391
392   return this_delay;
393 }
394
395 \f
396
397 static const char *halo_defaults [] = {
398   ".background:         black",
399   ".foreground:         white",
400   "*colorMode:          random",
401   "*colors:             100",
402   "*count:              0",
403   "*delay:              100000",
404   "*delay2:             20",
405   "*increment:          0",
406   "*animate:            False",
407   0
408 };
409
410 static XrmOptionDescRec halo_options [] = {
411   { "-count",           ".count",       XrmoptionSepArg, 0 },
412   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
413   { "-animate",         ".animate",     XrmoptionNoArg, "True" },
414   { "-mode",            ".colorMode",   XrmoptionSepArg, 0 },
415   { "-colors",          ".colors",      XrmoptionSepArg, 0 },
416   { 0, 0, 0, 0 }
417 };
418
419 static void
420 halo_reshape (Display *dpy, Window window, void *closure, 
421                  unsigned int w, unsigned int h)
422 {
423 }
424
425 static Bool
426 halo_event (Display *dpy, Window window, void *closure, XEvent *event)
427 {
428   return False;
429 }
430
431 static void
432 halo_free (Display *dpy, Window window, void *closure)
433 {
434 }
435
436 XSCREENSAVER_MODULE ("Halo", halo)