60d2fd4c1015a0c8d14547671b40866663f46941
[xscreensaver] / hacks / slidescreen.c
1 /* xscreensaver, Copyright (c) 1992-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
12 #include "screenhack.h"
13
14 enum { DOWN = 0, LEFT, UP, RIGHT };
15 enum { VERTICAL, HORIZONTAL };
16
17 struct state {
18   Display *dpy;
19   Window window;
20
21   int grid_size;
22   int pix_inc;
23   int hole_x, hole_y;
24   int bitmap_w, bitmap_h;
25   int xoff, yoff;
26   int grid_w, grid_h;
27   int delay, delay2;
28   int duration;
29   GC gc;
30   unsigned long fg, bg;
31   int max_width, max_height;
32   int early_i;
33
34   int draw_rnd, draw_i;
35   int draw_x, draw_y, draw_ix, draw_iy, draw_dx, draw_dy;
36   int draw_dir, draw_w, draw_h, draw_size, draw_inc;
37   int draw_last;
38   int draw_initted;
39
40   time_t start_time;
41   async_load_state *img_loader;
42 };
43
44
45 static void *
46 slidescreen_init (Display *dpy, Window window)
47 {
48   struct state *st = (struct state *) calloc (1, sizeof(*st));
49   XWindowAttributes xgwa;
50   Visual *visual;
51   XGCValues gcv;
52   long gcflags;
53
54   st->dpy = dpy;
55   st->window = window;
56   XGetWindowAttributes (st->dpy, st->window, &xgwa);
57   st->img_loader = load_image_async_simple (0, xgwa.screen, st->window,
58                                             st->window, 0, 0);
59   st->start_time = time ((time_t) 0);
60
61   visual = xgwa.visual;
62   st->max_width = xgwa.width;
63   st->max_height = xgwa.height;
64
65   st->delay = get_integer_resource (st->dpy, "delay", "Integer");
66   st->delay2 = get_integer_resource (st->dpy, "delay2", "Integer");
67   st->duration = get_integer_resource (st->dpy, "duration", "Seconds");
68   st->grid_size = get_integer_resource (st->dpy, "gridSize", "Integer");
69   st->pix_inc = get_integer_resource (st->dpy, "pixelIncrement", "Integer");
70
71   /* Don't let the grid be smaller than 5x5 */
72   while (st->grid_size > xgwa.width / 5)
73     st->grid_size /= 2;
74   while (st->grid_size > xgwa.height / 5)
75     st->grid_size /= 2;
76
77   if (st->delay < 0) st->delay = 0;
78   if (st->delay2 < 0) st->delay2 = 0;
79   if (st->duration < 1) st->duration = 1;
80   if (st->pix_inc < 1) st->pix_inc = 1;
81   if (st->grid_size < 1) st->grid_size = 1;
82
83
84   {
85     XColor fgc, bgc;
86     char *fgs = get_string_resource(st->dpy, "background", "Background");
87     char *bgs = get_string_resource(st->dpy, "foreground", "Foreground");
88     Bool fg_ok, bg_ok;
89     if (!XParseColor (st->dpy, xgwa.colormap, fgs, &fgc))
90       XParseColor (st->dpy, xgwa.colormap, "black", &bgc);
91     if (!XParseColor (st->dpy, xgwa.colormap, bgs, &bgc))
92       XParseColor (st->dpy, xgwa.colormap, "gray", &fgc);
93
94     fg_ok = XAllocColor (st->dpy, xgwa.colormap, &fgc);
95     bg_ok = XAllocColor (st->dpy, xgwa.colormap, &bgc);
96
97     /* If we weren't able to allocate the two colors we want from the
98        colormap (which is likely if the screen has been grabbed on an
99        8-bit SGI visual -- don't ask) then just go through the map
100        and find the closest color to the ones we wanted, and use those
101        pixels without actually allocating them.
102      */
103     if (fg_ok)
104       st->fg = fgc.pixel;
105     else
106       st->fg = 0;
107
108     if (bg_ok)
109       st->bg = bgc.pixel;
110     else
111       st->bg = 1;
112
113 #ifndef HAVE_COCOA
114     if (!fg_ok || bg_ok)
115       {
116         int i;
117         unsigned long fgd = ~0;
118         unsigned long bgd = ~0;
119         int max = visual_cells (xgwa.screen, xgwa.visual);
120         XColor *all = (XColor *) calloc(sizeof (*all), max);
121         for (i = 0; i < max; i++)
122           {
123             all[i].flags = DoRed|DoGreen|DoBlue;
124             all[i].pixel = i;
125           }
126         XQueryColors (st->dpy, xgwa.colormap, all, max);
127         for(i = 0; i < max; i++)
128           {
129             long rd, gd, bd;
130             unsigned long dd;
131             if (!fg_ok)
132               {
133                 rd = (all[i].red   >> 8) - (fgc.red   >> 8);
134                 gd = (all[i].green >> 8) - (fgc.green >> 8);
135                 bd = (all[i].blue  >> 8) - (fgc.blue  >> 8);
136                 if (rd < 0) rd = -rd;
137                 if (gd < 0) gd = -gd;
138                 if (bd < 0) bd = -bd;
139                 dd = (rd << 1) + (gd << 2) + bd;
140                 if (dd < fgd)
141                   {
142                     fgd = dd;
143                     st->fg = all[i].pixel;
144                     if (dd == 0)
145                       fg_ok = True;
146                   }
147               }
148
149             if (!bg_ok)
150               {
151                 rd = (all[i].red   >> 8) - (bgc.red   >> 8);
152                 gd = (all[i].green >> 8) - (bgc.green >> 8);
153                 bd = (all[i].blue  >> 8) - (bgc.blue  >> 8);
154                 if (rd < 0) rd = -rd;
155                 if (gd < 0) gd = -gd;
156                 if (bd < 0) bd = -bd;
157                 dd = (rd << 1) + (gd << 2) + bd;
158                 if (dd < bgd)
159                   {
160                     bgd = dd;
161                     st->bg = all[i].pixel;
162                     if (dd == 0)
163                       bg_ok = True;
164                   }
165               }
166
167             if (fg_ok && bg_ok)
168               break;
169           }
170         XFree(all);
171       }
172 #endif /* !HAVE_COCOA */
173   }
174
175   gcv.foreground = st->fg;
176   gcv.function = GXcopy;
177   gcv.subwindow_mode = IncludeInferiors;
178   gcflags = GCForeground |GCFunction;
179   if (use_subwindow_mode_p(xgwa.screen, st->window)) /* see grabscreen.c */
180     gcflags |= GCSubwindowMode;
181   st->gc = XCreateGC (st->dpy, st->window, gcflags, &gcv);
182
183   return st;
184 }
185
186 static void
187 draw_grid (struct state *st)
188 {
189   int i;
190   Drawable d;
191   int border;
192   XWindowAttributes xgwa;
193
194   XGetWindowAttributes (st->dpy, st->window, &xgwa);
195
196   border = get_integer_resource (st->dpy, "internalBorderWidth",
197                                  "InternalBorderWidth");
198
199   XGetWindowAttributes (st->dpy, st->window, &xgwa);
200   st->bitmap_w = xgwa.width;
201   st->bitmap_h = xgwa.height;
202
203   st->grid_w = st->bitmap_w / st->grid_size;
204   st->grid_h = st->bitmap_h / st->grid_size;
205   st->hole_x = random () % st->grid_w;
206   st->hole_y = random () % st->grid_h;
207   st->xoff = (st->bitmap_w - (st->grid_w * st->grid_size)) / 2;
208   st->yoff = (st->bitmap_h - (st->grid_h * st->grid_size)) / 2;
209
210   d = st->window;
211
212   st->early_i = -10;
213   st->draw_last = -1;
214
215   if (border)
216     {
217       int half = border/2;
218       int half2 = (border & 1 ? half+1 : half);
219       XSetForeground(st->dpy, st->gc, st->bg);
220       for (i = 0; i < st->bitmap_w; i += st->grid_size)
221         {
222           int j;
223           for (j = 0; j < st->bitmap_h; j += st->grid_size)
224             XDrawRectangle (st->dpy, d, st->gc,
225                             st->xoff+i+half2, st->yoff+j+half2,
226                             st->grid_size-border-1, st->grid_size-border-1);
227         }
228
229       XSetForeground(st->dpy, st->gc, st->fg);
230       for (i = 0; i <= st->bitmap_w; i += st->grid_size)
231         XFillRectangle (st->dpy, d, st->gc, st->xoff+i-half, st->yoff, border, st->bitmap_h);
232       for (i = 0; i <= st->bitmap_h; i += st->grid_size)
233         XFillRectangle (st->dpy, d, st->gc, st->xoff, st->yoff+i-half, st->bitmap_w, border);
234     }
235
236   if (st->xoff)
237     {
238       XFillRectangle (st->dpy, d, st->gc, 0, 0, st->xoff, st->bitmap_h);
239       XFillRectangle (st->dpy, d, st->gc, st->bitmap_w - st->xoff, 0, st->xoff, st->bitmap_h);
240     }
241   if (st->yoff)
242     {
243       XFillRectangle (st->dpy, d, st->gc, 0, 0, st->bitmap_w, st->yoff);
244       XFillRectangle (st->dpy, d, st->gc, 0, st->bitmap_h - st->yoff, st->bitmap_w, st->yoff);
245     }
246 }
247
248
249 static int
250 slidescreen_draw_early (struct state *st)
251 {
252   while (st->early_i < 0)
253     {
254       st->early_i++;
255       return 1;
256     }
257
258   /* for (early_i = 0; early_i < grid_size; early_i += pix_inc) */
259    {
260      XPoint points [3];
261      points[0].x = st->xoff + st->grid_size * st->hole_x;
262      points[0].y = st->yoff + st->grid_size * st->hole_y;
263      points[1].x = points[0].x + st->grid_size;
264      points[1].y = points[0].y;
265      points[2].x = points[0].x;
266      points[2].y = points[0].y + st->early_i;
267      XFillPolygon (st->dpy, st->window, st->gc, points, 3, Convex, CoordModeOrigin);
268
269      points[1].x = points[0].x;
270      points[1].y = points[0].y + st->grid_size;
271      points[2].x = points[0].x + st->early_i;
272      points[2].y = points[0].y + st->grid_size;
273      XFillPolygon (st->dpy, st->window, st->gc, points, 3, Convex, CoordModeOrigin);
274
275      points[0].x = points[1].x + st->grid_size;
276      points[0].y = points[1].y;
277      points[2].x = points[0].x;
278      points[2].y = points[0].y - st->early_i;
279      XFillPolygon (st->dpy, st->window, st->gc, points, 3, Convex, CoordModeOrigin);
280
281      points[1].x = points[0].x;
282      points[1].y = points[0].y - st->grid_size;
283      points[2].x = points[1].x - st->early_i;
284      points[2].y = points[1].y;
285      XFillPolygon (st->dpy, st->window, st->gc, points, 3, Convex, CoordModeOrigin);
286    }
287
288    st->early_i += st->pix_inc;
289    if (st->early_i < st->grid_size)
290      return 1;
291
292    XFillRectangle (st->dpy, st->window, st->gc,
293                    st->xoff + st->grid_size * st->hole_x,
294                    st->yoff + st->grid_size * st->hole_y,
295                    st->grid_size, st->grid_size);
296    return 0;
297 }
298
299
300 static unsigned long
301 slidescreen_draw (Display *dpy, Window window, void *closure)
302 {
303   struct state *st = (struct state *) closure;
304   int this_delay = st->delay;
305
306   /* this code is a total kludge, but who cares, it works... */
307
308   if (st->img_loader)   /* still loading */
309     {
310       st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
311       if (! st->img_loader) {  /* just finished */
312         st->start_time = time ((time_t) 0);
313         draw_grid (st);
314       }
315       return st->delay;
316     }
317
318   if (!st->img_loader &&
319       st->start_time + st->duration < time ((time_t) 0)) {
320     XWindowAttributes xgwa;
321     XGetWindowAttributes(st->dpy, st->window, &xgwa);
322     st->img_loader = load_image_async_simple (0, xgwa.screen, st->window,
323                                               st->window, 0, 0);
324     st->start_time = time ((time_t) 0);
325     st->draw_initted = 0;
326     return st->delay;
327   }
328
329   if (! st->draw_initted)
330     {
331       if (!slidescreen_draw_early (st))
332         {
333           st->draw_initted = 1;
334           return st->delay2;
335         }
336       else
337         return st->delay;
338     }
339
340  if (st->draw_i == 0)
341    {
342      if (st->draw_last == -1) st->draw_last = random () % 2;
343
344      /* alternate between horizontal and vertical slides */
345      /* note that draw_dir specifies the direction the _hole_ moves, not the tiles */
346      if (st->draw_last == VERTICAL) {
347        if (((st->grid_w > 1) ? st->draw_rnd = random () % (st->grid_w - 1) : 0)
348            < st->hole_x) {
349          st->draw_dx = -1; st->draw_dir = LEFT;  st->hole_x -= st->draw_rnd;
350        } else {
351          st->draw_dx =  1; st->draw_dir = RIGHT; st->draw_rnd -= st->hole_x;
352        }
353        st->draw_dy = 0; st->draw_w = st->draw_size = st->draw_rnd + 1; st->draw_h = 1;
354        st->draw_last = HORIZONTAL;
355      } else {
356        if (((st->grid_h > 1) ? st->draw_rnd = random () % (st->grid_h - 1) : 0)
357            < st->hole_y) {
358          st->draw_dy = -1; st->draw_dir = UP;    st->hole_y -= st->draw_rnd;
359        } else {
360          st->draw_dy =  1; st->draw_dir = DOWN;  st->draw_rnd -= st->hole_y;
361        }
362        st->draw_dx = 0; st->draw_h = st->draw_size = st->draw_rnd + 1; st->draw_w = 1;
363        st->draw_last = VERTICAL;
364      }
365  
366      st->draw_ix = st->draw_x = st->xoff + (st->hole_x + st->draw_dx) * st->grid_size;
367      st->draw_iy = st->draw_y = st->yoff + (st->hole_y + st->draw_dy) * st->grid_size;
368      st->draw_inc = st->pix_inc;
369
370    }
371
372  /* for (draw_i = 0; draw_i < grid_size; draw_i += draw_inc) */
373    {
374      int fx, fy, tox, toy;
375      if (st->draw_inc + st->draw_i > st->grid_size)
376        st->draw_inc = st->grid_size - st->draw_i;
377      tox = st->draw_x - st->draw_dx * st->draw_inc;
378      toy = st->draw_y - st->draw_dy * st->draw_inc;
379
380      fx = (st->draw_x < 0 ? 0 : st->draw_x > st->max_width  ? st->max_width  : st->draw_x);
381      fy = (st->draw_y < 0 ? 0 : st->draw_y > st->max_height ? st->max_height : st->draw_y);
382      tox = (tox < 0 ? 0 : tox > st->max_width  ? st->max_width  : tox);
383      toy = (toy < 0 ? 0 : toy > st->max_height ? st->max_height : toy);
384
385      XCopyArea (st->dpy, st->window, st->window, st->gc,
386                 fx, fy,
387                 st->grid_size * st->draw_w, st->grid_size * st->draw_h,
388                 tox, toy);
389
390      st->draw_x -= st->draw_dx * st->draw_inc;
391      st->draw_y -= st->draw_dy * st->draw_inc;
392      switch (st->draw_dir)
393        {
394        case DOWN: XFillRectangle (st->dpy, st->window, st->gc,
395                                st->draw_ix, st->draw_y + st->grid_size * st->draw_h, st->grid_size * st->draw_w, st->draw_iy - st->draw_y);
396          break;
397        case LEFT: XFillRectangle (st->dpy, st->window, st->gc, st->draw_ix, st->draw_iy, st->draw_x - st->draw_ix, st->grid_size * st->draw_h);
398          break;
399        case UP: XFillRectangle (st->dpy, st->window, st->gc, st->draw_ix, st->draw_iy, st->grid_size * st->draw_w, st->draw_y - st->draw_iy);
400          break;
401        case RIGHT: XFillRectangle (st->dpy, st->window, st->gc,
402                                st->draw_x + st->grid_size * st->draw_w, st->draw_iy, st->draw_ix - st->draw_x, st->grid_size * st->draw_h);
403          break;
404        }
405    }
406
407    st->draw_i += st->draw_inc;
408    if (st->draw_i >= st->grid_size)
409      {
410        st->draw_i = 0;
411
412        switch (st->draw_dir)
413          {
414          case DOWN: st->hole_y += st->draw_size; break;
415          case LEFT: st->hole_x--; break;
416          case UP: st->hole_y--; break;
417          case RIGHT: st->hole_x += st->draw_size; break;
418          }
419
420        this_delay = st->delay2;
421      }
422
423    return this_delay;
424 }
425
426 static void
427 slidescreen_reshape (Display *dpy, Window window, void *closure, 
428                  unsigned int w, unsigned int h)
429 {
430 }
431
432 static Bool
433 slidescreen_event (Display *dpy, Window window, void *closure, XEvent *event)
434 {
435   return False;
436 }
437
438 static void
439 slidescreen_free (Display *dpy, Window window, void *closure)
440 {
441   struct state *st = (struct state *) closure;
442   XFreeGC (dpy, st->gc);
443   free (st);
444 }
445
446 \f
447
448 static const char *slidescreen_defaults [] = {
449   "*dontClearRoot:              True",
450   "*fpsSolid:                   true",
451
452 #ifdef __sgi    /* really, HAVE_READ_DISPLAY_EXTENSION */
453   "*visualID:                   Best",
454 #endif
455
456   ".background:                 Black",
457   ".foreground:                 #BEBEBE",
458   "*gridSize:                   70",
459   "*pixelIncrement:             10",
460   "*internalBorderWidth:        4",
461   "*delay:                      50000",
462   "*delay2:                     1000000",
463   "*duration:                   120",
464   0
465 };
466
467 static XrmOptionDescRec slidescreen_options [] = {
468   { "-grid-size",       ".gridSize",            XrmoptionSepArg, 0 },
469   { "-ibw",             ".internalBorderWidth", XrmoptionSepArg, 0 },
470   { "-increment",       ".pixelIncrement",      XrmoptionSepArg, 0 },
471   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
472   { "-delay2",          ".delay2",              XrmoptionSepArg, 0 },
473   {"-duration",         ".duration",            XrmoptionSepArg, 0 },
474   { 0, 0, 0, 0 }
475 };
476
477 XSCREENSAVER_MODULE ("SlideScreen", slidescreen)