http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[xscreensaver] / hacks / boxfit.c
1 /* xscreensaver, Copyright (c) 2005-2008 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  * Boxfit -- fills space with a gradient of growing boxes or circles.
12  *
13  * Written by jwz, 21-Feb-2005.
14  *
15  * Inspired by http://www.levitated.net/daily/levBoxFitting.html
16  */
17
18 #include "screenhack.h"
19 #include <stdio.h>
20 #include "xpm-pixmap.h"
21
22 #define ALIVE   1
23 #define CHANGED 2
24 #define UNDEAD  4
25
26 typedef struct {
27   unsigned long fill_color;
28   short x, y, w, h;
29   char flags;
30 } box;
31
32 typedef struct {
33   Display *dpy;
34   Window window;
35   XWindowAttributes xgwa;
36   GC gc;
37   unsigned long fg_color, bg_color;
38   int border_size;
39   int spacing;
40   int inc;
41
42   Bool circles_p;
43   Bool growing_p;
44   Bool color_horiz_p;
45
46   int box_count;
47   int boxes_size;
48   int nboxes;
49   box *boxes;
50
51   XImage *image;
52   int ncolors;
53   XColor *colors;
54   int delay;
55   int countdown;
56
57   Bool done_once;
58   async_load_state *img_loader;
59   Pixmap loading_pixmap;
60
61 } state;
62
63
64 static void
65 reset_boxes (state *st)
66 {
67   int mode = -1;
68
69   st->nboxes = 0;
70   st->growing_p = True;
71   st->color_horiz_p = random() & 1;
72
73   if (st->done_once && st->colors)
74     free_colors (st->dpy, st->xgwa.colormap, st->colors, st->ncolors);
75
76   if (!st->done_once)
77     {
78       char *s = get_string_resource (st->dpy, "mode", "Mode");
79       if (!s || !*s || !strcasecmp (s, "random"))
80         mode = -1;
81       else if (!strcasecmp (s, "squares") || !strcasecmp (s, "square"))
82         mode = 0;
83       else if (!strcasecmp (s, "circles") || !strcasecmp (s, "circle"))
84         mode = 1;
85       else
86         {
87           fprintf (stderr,
88                    "%s: mode must be random, squares, or circles, not '%s'\n",
89                    progname, s);
90           exit (1);
91         }
92     }
93
94   if (mode == -1)
95     st->circles_p = random() & 1;
96   else
97     st->circles_p = (mode == 1);
98
99   st->done_once = True;
100
101   if (st->image || get_boolean_resource (st->dpy, "grab", "Boolean"))
102     {
103       if (st->image) XDestroyImage (st->image);
104       st->image = 0;
105
106       if (st->loading_pixmap) abort();
107       if (st->img_loader) abort();
108       if (!get_boolean_resource (st->dpy, "peek", "Boolean"))
109         st->loading_pixmap = XCreatePixmap (st->dpy, st->window,
110                                             st->xgwa.width, st->xgwa.height,
111                                             st->xgwa.depth);
112
113       XClearWindow (st->dpy, st->window);
114       st->img_loader = load_image_async_simple (0, st->xgwa.screen, 
115                                                 st->window,
116                                                 (st->loading_pixmap
117                                                  ? st->loading_pixmap
118                                                  : st->window), 
119                                                 0, 0);
120     }
121   else
122     {
123       st->ncolors = get_integer_resource (st->dpy, "colors", "Colors");  /* re-get */
124       if (st->ncolors < 1) st->ncolors = 1;
125       make_smooth_colormap (st->dpy, st->xgwa.visual, st->xgwa.colormap,
126                             st->colors, &st->ncolors, True, 0, False);
127       if (st->ncolors < 1) abort();
128       XClearWindow (st->dpy, st->window);
129     }
130 }
131
132
133 static void
134 reshape_boxes (state *st)
135 {
136   int i;
137   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
138   for (i = 0; i < st->nboxes; i++)
139     {
140       box *b = &st->boxes[i];
141       b->flags |= CHANGED;
142     }
143 }
144
145 static void *
146 boxfit_init (Display *dpy, Window window)
147 {
148   XGCValues gcv;
149   state *st = (state *) calloc (1, sizeof (*st));
150
151   st->dpy = dpy;
152   st->window = window;
153   st->delay = get_integer_resource (dpy, "delay", "Integer");
154
155   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
156 /*  XSelectInput (dpy, window, st->xgwa.your_event_mask | ExposureMask);*/
157
158   if (! get_boolean_resource (dpy, "grab", "Boolean"))
159     {
160       st->ncolors = get_integer_resource (dpy, "colors", "Colors");
161       if (st->ncolors < 1) st->ncolors = 1;
162       st->colors = (XColor *) malloc (sizeof(XColor) * st->ncolors);
163     }
164
165   st->inc = get_integer_resource (dpy, "growBy", "GrowBy");
166   st->spacing = get_integer_resource (dpy, "spacing", "Spacing");
167   st->border_size = get_integer_resource (dpy, "borderSize", "BorderSize");
168   st->fg_color = get_pixel_resource (st->dpy, st->xgwa.colormap, 
169                                      "foreground", "Foreground");
170   st->bg_color = get_pixel_resource (st->dpy, st->xgwa.colormap,
171                                      "background", "Background");
172   if (st->inc < 1) st->inc = 1;
173   if (st->border_size < 0) st->border_size = 0;
174
175   gcv.line_width = st->border_size;
176   gcv.background = st->bg_color;
177   st->gc = XCreateGC (st->dpy, st->window, GCBackground|GCLineWidth, &gcv);
178
179   st->box_count = get_integer_resource (dpy, "boxCount", "BoxCount");
180   if (st->box_count < 1) st->box_count = 1;
181
182   st->nboxes = 0;
183   st->boxes_size = st->box_count * 2;
184   st->boxes = (box *) calloc (st->boxes_size, sizeof(*st->boxes));
185
186   reset_boxes (st);
187
188   reshape_boxes (st);
189   return st;
190 }
191
192
193
194 static Bool
195 boxes_overlap_p (box *a, box *b, int pad)
196 {
197   /* Two rectangles overlap if the max of the tops is less than the
198      min of the bottoms and the max of the lefts is less than the min
199      of the rights.
200    */
201 # undef MAX
202 # undef MIN
203 # define MAX(A,B) ((A)>(B)?(A):(B))
204 # define MIN(A,B) ((A)<(B)?(A):(B))
205
206   int maxleft  = MAX(a->x - pad, b->x);
207   int maxtop   = MAX(a->y - pad, b->y);
208   int minright = MIN(a->x + a->w + pad + pad - 1, b->x + b->w);
209   int minbot   = MIN(a->y + a->h + pad + pad - 1, b->y + b->h);
210   return (maxtop < minbot && maxleft < minright);
211 }
212
213
214 static Bool
215 circles_overlap_p (box *a, box *b, int pad)
216 {
217   int ar = a->w/2;      /* radius */
218   int br = b->w/2;
219   int ax = a->x + ar;   /* center */
220   int ay = a->y + ar;
221   int bx = b->x + br;
222   int by = b->y + br;
223   int d2 = (((bx - ax) * (bx - ax)) +   /* distance between centers squared */
224             ((by - ay) * (by - ay)));
225   int r2 = ((ar + br + pad) *           /* sum of radii squared */
226             (ar + br + pad));
227   return (d2 < r2);
228 }
229
230
231 static Bool
232 box_collides_p (state *st, box *a, int pad)
233 {
234   int i;
235
236   /* collide with wall */
237   if (a->x - pad < 0 ||
238       a->y - pad < 0 ||
239       a->x + a->w + pad + pad >= st->xgwa.width ||
240       a->y + a->h + pad + pad >= st->xgwa.height)
241     return True;
242
243   /* collide with another box */
244   for (i = 0; i < st->nboxes; i++)
245     {
246       box *b = &st->boxes[i];
247       if (a != b &&
248           (st->circles_p
249            ? circles_overlap_p (a, b, pad)
250            : boxes_overlap_p (a, b, pad)))
251         return True;
252     }
253
254   return False;
255 }
256
257
258 static unsigned int
259 grow_boxes (state *st)
260 {
261   int inc2 = st->inc + st->spacing + st->border_size;
262   int i;
263   int live_count = 0;
264
265   /* check box collisions, and grow if none.
266    */
267   for (i = 0; i < st->nboxes; i++)
268     {
269       box *a = &st->boxes[i];
270       if (!(a->flags & ALIVE)) continue;
271
272       if (box_collides_p (st, a, inc2))
273         {
274           a->flags &= ~ALIVE;
275           continue;
276         }
277       
278       live_count++;
279       a->x -= st->inc;
280       a->y -= st->inc;
281       a->w += st->inc + st->inc;
282       a->h += st->inc + st->inc;
283       a->flags |= CHANGED;
284     }
285
286   /* Add more boxes.
287    */
288   while (live_count < st->box_count)
289     {
290       box *a;
291       st->nboxes++;
292       if (st->boxes_size <= st->nboxes)
293         {
294           st->boxes_size = (st->boxes_size * 1.2) + st->nboxes;
295           st->boxes = (box *)
296             realloc (st->boxes, st->boxes_size * sizeof(*st->boxes));
297           if (! st->boxes)
298             {
299               fprintf (stderr, "%s: out of memory (%d boxes)\n",
300                        progname, st->boxes_size);
301               exit (1);
302             }
303         }
304
305       a = &st->boxes[st->nboxes-1];
306       a->flags = CHANGED;
307
308       for (i = 0; i < 100; i++)
309         {
310           a->x = inc2 + (random() % (st->xgwa.width  - inc2));
311           a->y = inc2 + (random() % (st->xgwa.height - inc2));
312           a->w = 0;
313           a->h = 0;
314
315           if (! box_collides_p (st, a, inc2))
316             {
317               a->flags |= ALIVE;
318               live_count++;
319               break;
320             }
321         }
322
323       if (! (a->flags & ALIVE) ||       /* too many retries; */
324           st->nboxes > 65535)           /* that's about 1MB of box structs. */
325         {
326           st->nboxes--;                 /* go into "fade out" mode now. */
327           st->growing_p = False;
328           return 2000000; /* make customizable... */
329         }
330
331       /* Pick colors for this box */
332       if (st->image)
333         {
334           int w = st->image->width;
335           int h = st->image->height;
336           a->fill_color = XGetPixel (st->image, a->x % w, a->y % h);
337         }
338       else
339         {
340           int n = (st->color_horiz_p
341                    ? (a->x * st->ncolors / st->xgwa.width)
342                    : (a->y * st->ncolors / st->xgwa.height));
343           a->fill_color   = st->colors [n % st->ncolors].pixel;
344         }
345     }
346
347   return st->delay;
348 }
349
350
351 static unsigned int
352 shrink_boxes (state *st)
353 {
354   int i;
355   int remaining = 0;
356
357   for (i = 0; i < st->nboxes; i++)
358     {
359       box *a = &st->boxes[i];
360
361       if (a->w <= 0 || a->h <= 0) continue;
362
363       a->x += st->inc;
364       a->y += st->inc;
365       a->w -= st->inc + st->inc;
366       a->h -= st->inc + st->inc;
367       a->flags |= CHANGED;
368       if (a->w < 0) a->w = 0;
369       if (a->h < 0) a->h = 0;
370
371       if (a->w > 0 && a->h > 0)
372         remaining++;
373     }
374
375   if (remaining == 0) {
376     reset_boxes (st);
377     return 1000000;
378   } else {
379     return st->delay;
380   }
381 }
382
383
384 static void
385 draw_boxes (state *st)
386 {
387   int i;
388   for (i = 0; i < st->nboxes; i++)
389     {
390       box *b = &st->boxes[i];
391
392       if (b->flags & UNDEAD) continue;
393       if (! (b->flags & CHANGED)) continue;
394       b->flags &= ~CHANGED;
395
396       if (!st->growing_p)
397         {
398           /* When shrinking, black out an area outside of the border
399              before re-drawing the box.
400            */
401           int margin = st->inc + st->border_size;
402
403           XSetForeground (st->dpy, st->gc, st->bg_color);
404           if (st->circles_p)
405             XFillArc (st->dpy, st->window, st->gc,
406                       b->x - margin, b->y - margin,
407                       b->w + (margin*2), b->h + (margin*2),
408                       0, 360*64);
409           else
410             XFillRectangle (st->dpy, st->window, st->gc,
411                       b->x - margin, b->y - margin,
412                       b->w + (margin*2), b->h + (margin*2));
413
414           if (b->w <= 0 || b->h <= 0)
415             b->flags |= UNDEAD;   /* really very dead now */
416         }
417
418       if (b->w <= 0 || b->h <= 0) continue;
419
420       XSetForeground (st->dpy, st->gc, b->fill_color);
421
422       if (st->circles_p)
423         XFillArc (st->dpy, st->window, st->gc, b->x, b->y, b->w, b->h,
424                   0, 360*64);
425       else
426         XFillRectangle (st->dpy, st->window, st->gc, b->x, b->y, b->w, b->h);
427
428       if (st->border_size > 0)
429         {
430           unsigned int bd = (st->image
431                              ? st->fg_color
432                              : st->colors [(b->fill_color + st->ncolors/2)
433                                            % st->ncolors].pixel);
434           XSetForeground (st->dpy, st->gc, bd);
435           if (st->circles_p)
436             XDrawArc (st->dpy, st->window, st->gc, b->x, b->y, b->w, b->h,
437                       0, 360*64);
438           else
439             XDrawRectangle (st->dpy, st->window, st->gc,
440                             b->x, b->y, b->w, b->h);
441         }
442     }
443 }
444
445
446 static unsigned long
447 boxfit_draw (Display *dpy, Window window, void *closure)
448 {
449   state *st = (state *) closure;
450   int delay;
451
452   if (st->img_loader)   /* still loading */
453     {
454       st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
455       if (! st->img_loader)  /* just finished */
456         {
457           st->image = XGetImage (st->dpy,
458                                  (st->loading_pixmap ? st->loading_pixmap :
459                                   st->window),
460                                  0, 0,
461                                  st->xgwa.width, st->xgwa.height, ~0L, 
462                                  ZPixmap);
463           if (st->loading_pixmap) XFreePixmap (st->dpy, st->loading_pixmap);
464           XSetWindowBackground (st->dpy, st->window, st->bg_color);
465           if (st->loading_pixmap)
466             XClearWindow (st->dpy, st->window);
467           else
468             st->countdown = 2000000;
469           st->loading_pixmap = 0;
470         }
471       return st->delay;
472     }
473
474   if (st->countdown > 0)
475     {
476       st->countdown -= st->delay;
477       if (st->countdown <= 0)
478         {
479           st->countdown = 0;
480           XClearWindow (st->dpy, st->window);
481         }
482       return st->delay;
483     }
484
485   if (st->growing_p) {
486     draw_boxes (st);
487     delay = grow_boxes (st);
488   } else {
489     delay = shrink_boxes (st);
490     draw_boxes (st);
491   }
492   return delay;
493 }
494
495 static void
496 boxfit_reshape (Display *dpy, Window window, void *closure, 
497                  unsigned int w, unsigned int h)
498 {
499   state *st = (state *) closure;
500   reshape_boxes (st);
501 }
502
503 static Bool
504 boxfit_event (Display *dpy, Window window, void *closure, XEvent *event)
505 {
506   state *st = (state *) closure;
507   if (event->xany.type == ButtonPress) {
508     st->growing_p = !st->growing_p;
509     return True;
510   }
511   return False;
512 }
513
514 static void
515 boxfit_free (Display *dpy, Window window, void *closure)
516 {
517 }
518
519
520 static const char *boxfit_defaults [] = {
521   ".background:            black",
522   ".foreground:            #444444",
523   "*fpsSolid:              true",
524   "*delay:                 20000",
525   "*mode:                  random",
526   "*colors:                64",
527   "*boxCount:              50",
528   "*growBy:                1",
529   "*spacing:               1",
530   "*borderSize:            1",
531   "*grab:                  False",
532   "*peek:                  False",
533   "*grabDesktopImages:     False",   /* HAVE_COCOA */
534   "*chooseRandomImages:    True",    /* HAVE_COCOA */
535   0
536 };
537
538 static XrmOptionDescRec boxfit_options [] = {
539   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
540   { "-colors",          ".colors",              XrmoptionSepArg, 0 },
541   { "-count",           ".boxCount",            XrmoptionSepArg, 0 },
542   { "-growby",          ".growBy",              XrmoptionSepArg, 0 },
543   { "-spacing",         ".spacing",             XrmoptionSepArg, 0 },
544   { "-border",          ".borderSize",          XrmoptionSepArg, 0 },
545   { "-mode",            ".mode",                XrmoptionSepArg, 0 },
546   { "-circles",         ".mode",                XrmoptionNoArg, "circles" },
547   { "-squares",         ".mode",                XrmoptionNoArg, "squares" },
548   { "-random",          ".mode",                XrmoptionNoArg, "random"  },
549   { "-grab",            ".grab",                XrmoptionNoArg, "True"    },
550   { "-no-grab",         ".grab",                XrmoptionNoArg, "False"   },
551   { "-peek",            ".peek",                XrmoptionNoArg, "True"    },
552   { "-no-peek",         ".peek",                XrmoptionNoArg, "False"   },
553   { 0, 0, 0, 0 }
554 };
555
556
557 XSCREENSAVER_MODULE ("BoxFit", boxfit)