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