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