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