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