http://ftp.x.org/contrib/applications/xscreensaver-3.09.tar.gz
[xscreensaver] / utils / fade.c
1 /* xscreensaver, Copyright (c) 1992-1998 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 "utils.h"
13
14 #include <sys/time.h> /* for gettimeofday() */
15
16 #ifdef VMS
17 # include "vms-gtod.h"
18 #endif /* VMS */
19
20 #include "visual.h"
21 #include "usleep.h"
22 #include "fade.h"
23
24
25 Colormap
26 copy_colormap (Screen *screen, Visual *visual,
27                Colormap cmap, Colormap into_cmap)
28 {
29   int i;
30   Display *dpy = DisplayOfScreen (screen);
31   Window window = RootWindowOfScreen (screen);
32   int ncolors = CellsOfScreen (screen);
33   XColor *colors = 0;
34
35   /* If this is a colormap on a mono visual, or one with insanely many
36      color cells, bug out. */
37   if (ncolors <= 2 || ncolors > 4096)
38     return 0;
39   /* If this is a non-writable visual, bug out. */
40   if (!has_writable_cells (screen, visual))
41     return 0;
42
43   if (! into_cmap)
44     into_cmap = XCreateColormap (dpy, window, visual, AllocAll);
45   if (! cmap)
46     cmap = DefaultColormapOfScreen (screen);
47
48   colors = (XColor *) calloc(sizeof(XColor), ncolors);
49   for (i = 0; i < ncolors; i++)
50     colors [i].pixel = i;
51   XQueryColors (dpy, cmap, colors, ncolors);
52   XStoreColors (dpy, into_cmap, colors, ncolors);
53   free (colors);
54   return into_cmap;
55 }
56
57
58 void
59 blacken_colormap (Screen *screen, Colormap cmap)
60 {
61   Display *dpy = DisplayOfScreen (screen);
62   int ncolors = CellsOfScreen (screen);
63   XColor *colors;
64   int i;
65   if (ncolors > 4096)
66     return;
67   colors = (XColor *) calloc(sizeof(XColor), ncolors);
68   for (i = 0; i < ncolors; i++)
69     colors[i].pixel = i;
70   XStoreColors (dpy, cmap, colors, ncolors);
71   free (colors);
72 }
73
74
75
76 static void fade_screens_1 (Display *dpy, Colormap *cmaps,
77                             Window *black_windows, int seconds, int ticks,
78                             Bool out_p, Bool clear_windows);
79 #ifdef HAVE_SGI_VC_EXTENSION
80 static int sgi_gamma_fade (Display *dpy,
81                            Window *black_windows, int seconds, int ticks,
82                            Bool out_p, Bool clear_windows);
83 #endif /* HAVE_SGI_VC_EXTENSION */
84
85
86
87 void
88 fade_screens (Display *dpy, Colormap *cmaps, Window *black_windows,
89               int seconds, int ticks,
90               Bool out_p, Bool clear_windows)
91 {
92   int oseconds = seconds;
93   Bool was_in_p = !out_p;
94
95   /* When we're asked to fade in, first fade out, then fade in.
96      That way all the transitions are smooth -- from what's on the
97      screen, to black, to the desktop.
98    */
99   if (was_in_p)
100     {
101       clear_windows = True;
102       out_p = True;
103       seconds /= 3;
104       if (seconds == 0)
105         seconds = 1;
106     }
107
108  AGAIN:
109
110 #ifdef HAVE_SGI_VC_EXTENSION
111   /* First try to do it by fading the gamma in an SGI-specific way... */
112   if (0 != sgi_gamma_fade(dpy, black_windows, seconds, ticks, out_p,
113                           clear_windows))
114 #endif /* HAVE_SGI_VC_EXTENSION */
115     /* Else, do it the old-fashioned way, which (somewhat) loses if
116        there are TrueColor windows visible. */
117     fade_screens_1 (dpy, cmaps, black_windows, seconds, ticks,
118                     out_p, clear_windows);
119
120   /* If we were supposed to be fading in, do so now (we just faded out,
121      so now fade back in.)
122    */
123   if (was_in_p)
124     {
125       was_in_p = False;
126       out_p = False;
127       seconds = oseconds * 2 / 3;
128       if (seconds == 0)
129         seconds = 1;
130       goto AGAIN;
131     }
132 }
133
134
135 /* The business with `cmaps_per_screen' is to fake out the SGI 8-bit video
136    hardware, which is capable of installing multiple (4) colormaps
137    simultaniously.  We have to install multiple copies of the same set of
138    colors in order to fill up all the available slots in the hardware color
139    lookup table, so we install an extra N colormaps per screen to make sure
140    that all screens really go black.
141
142    I'm told that this trick also works with XInside's AcceleratedX when using
143    the Matrox Millenium card (which also allows multiple PseudoColor and
144    TrueColor visuals to co-exist and display properly at the same time.)  
145
146    This trick works ok on the 24-bit Indy video hardware, but doesn't work at
147    all on the O2 24-bit hardware.  I guess the higher-end hardware is too
148    "good" for this to work (dammit.)  So... I figured out the "right" way to
149    do this on SGIs, which is to ramp the monitor's gamma down to 0.  That's
150    what is implemented in sgi_gamma_fade(), so we use that if we can.
151  */
152 static void
153 fade_screens_1 (Display *dpy, Colormap *cmaps, Window *black_windows,
154                 int seconds, int ticks,
155                 Bool out_p, Bool clear_windows)
156 {
157   int i, j, k;
158   int steps = seconds * ticks;
159   long usecs_per_step = (long)(seconds * 1000000) / (long)steps;
160   XEvent dummy_event;
161   int cmaps_per_screen = 5;
162   int nscreens = ScreenCount(dpy);
163   int ncmaps = nscreens * cmaps_per_screen;
164   Colormap *fade_cmaps = 0;
165   Bool installed = False;
166   int total_ncolors;
167   XColor *orig_colors, *current_colors, *screen_colors, *orig_screen_colors;
168   struct timeval then, now;
169 #ifdef GETTIMEOFDAY_TWO_ARGS
170   struct timezone tzp;
171 #endif
172
173   total_ncolors = 0;
174   for (i = 0; i < nscreens; i++)
175     total_ncolors += CellsOfScreen (ScreenOfDisplay(dpy, i));
176
177   orig_colors    = (XColor *) calloc(sizeof(XColor), total_ncolors);
178   current_colors = (XColor *) calloc(sizeof(XColor), total_ncolors);
179
180   /* Get the contents of the colormap we are fading from or to. */
181   screen_colors = orig_colors;
182   for (i = 0; i < nscreens; i++)
183     {
184       int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, i));
185       Colormap cmap = (cmaps ? cmaps[i] : 0);
186       if (!cmap) cmap = DefaultColormap(dpy, i);
187
188       for (j = 0; j < ncolors; j++)
189         screen_colors[j].pixel = j;
190       XQueryColors (dpy, cmap, screen_colors, ncolors);
191
192       screen_colors += ncolors;
193     }
194
195   memcpy (current_colors, orig_colors, total_ncolors * sizeof (XColor));
196
197
198   /* Make the writable colormaps (we keep these around and reuse them.) */
199   if (!fade_cmaps)
200     {
201       fade_cmaps = (Colormap *) calloc(sizeof(Colormap), ncmaps);
202       for (i = 0; i < nscreens; i++)
203         {
204           Visual *v = DefaultVisual(dpy, i);
205           Screen *s = ScreenOfDisplay(dpy, i);
206           if (has_writable_cells (s, v))
207             for (j = 0; j < cmaps_per_screen; j++)
208               fade_cmaps[(i * cmaps_per_screen) + j] =
209                 XCreateColormap (dpy, RootWindowOfScreen (s), v, AllocAll);
210         }
211     }
212
213 #ifdef GETTIMEOFDAY_TWO_ARGS
214   gettimeofday(&then, &tzp);
215 #else
216   gettimeofday(&then);
217 #endif
218
219   /* Iterate by steps of the animation... */
220   for (i = (out_p ? steps : 0);
221        (out_p ? i > 0 : i < steps);
222        (out_p ? i-- : i++))
223     {
224
225       /* For each screen, compute the current value of each color...
226        */
227       orig_screen_colors = orig_colors;
228       screen_colors = current_colors;
229       for (j = 0; j < nscreens; j++)
230         {
231           int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
232           for (k = 0; k < ncolors; k++)
233             {
234               /* This doesn't take into account the relative luminance of the
235                  RGB components (0.299, 0.587, and 0.114 at gamma 2.2) but
236                  the difference is imperceptible for this application... */
237               screen_colors[k].red   = orig_screen_colors[k].red   * i / steps;
238               screen_colors[k].green = orig_screen_colors[k].green * i / steps;
239               screen_colors[k].blue  = orig_screen_colors[k].blue  * i / steps;
240             }
241           screen_colors      += ncolors;
242           orig_screen_colors += ncolors;
243         }
244
245       /* Put the colors into the maps...
246        */
247       screen_colors = current_colors;
248       for (j = 0; j < nscreens; j++)
249         {
250           int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
251           for (k = 0; k < cmaps_per_screen; k++)
252             {
253               Colormap c = fade_cmaps[j * cmaps_per_screen + k];
254               if (c)
255                 XStoreColors (dpy, c, screen_colors, ncolors);
256             }
257           screen_colors += ncolors;
258         }
259
260       /* Put the maps on the screens, and then take the windows off the screen.
261          (only need to do this the first time through the loop.)
262        */
263       if (!installed)
264         {
265           for (j = 0; j < ncmaps; j++)
266             if (fade_cmaps[j])
267               XInstallColormap (dpy, fade_cmaps[j]);
268           installed = True;
269
270           if (black_windows && !out_p)
271             for (j = 0; j < nscreens; j++)
272               if (black_windows[j])
273                 {
274                   XUnmapWindow (dpy, black_windows[j]);
275                   XClearWindow (dpy, black_windows[j]);
276                 }
277         }
278
279       XSync (dpy, False);
280
281       /* If there is user activity, bug out.  (Bug out on keypresses or
282          mouse presses, but not motion, and not release events.  Bugging
283          out on motion made the unfade hack be totally useless, I think.)
284
285          We put the event back so that the calling code can notice it too.
286          It would be better to not remove it at all, but that's harder
287          because Xlib has such a non-design for this kind of crap, and
288          in this application it doesn't matter if the events end up out
289          of order, so in the grand unix tradition we say "fuck it" and
290          do something that mostly works for the time being.
291        */
292       if (XCheckMaskEvent (dpy, (KeyPressMask|ButtonPressMask), &dummy_event))
293         {
294           XPutBackEvent (dpy, &dummy_event);
295           goto DONE;
296         }
297
298 #ifdef GETTIMEOFDAY_TWO_ARGS
299       gettimeofday(&now, &tzp);
300 #else
301       gettimeofday(&now);
302 #endif
303
304       /* If we haven't already used up our alotted time, sleep to avoid
305          changing the colormap too fast. */
306       {
307         long diff = (((now.tv_sec - then.tv_sec) * 1000000) +
308                      now.tv_usec - then.tv_usec);
309         then.tv_sec = now.tv_sec;
310         then.tv_usec = now.tv_usec;
311         if (usecs_per_step > diff)
312           usleep (usecs_per_step - diff);
313       }
314     }
315
316  DONE:
317
318   if (orig_colors)    free (orig_colors);
319   if (current_colors) free (current_colors);
320
321   /* If we've been given windows to raise after blackout, raise them before
322      releasing the colormaps.
323    */
324   if (out_p && black_windows)
325     {
326       for (i = 0; i < nscreens; i++)
327         {
328           if (clear_windows)
329             XClearWindow (dpy, black_windows[i]);
330           XMapRaised (dpy, black_windows[i]);
331         }
332       XSync(dpy, False);
333     }
334
335   /* Now put the target maps back.
336      If we're fading out, use the given cmap (or the default cmap, if none.)
337      If we're fading in, always use the default cmap.
338    */
339   for (i = 0; i < nscreens; i++)
340     {
341       Colormap cmap = (cmaps ? cmaps[i] : 0);
342       if (!cmap || !out_p)
343         cmap = DefaultColormap(dpy, i);
344       XInstallColormap (dpy, cmap);
345     }
346
347   /* The fade (in or out) is complete, so we don't need the black maps on
348      stage any more.
349    */
350   for (i = 0; i < ncmaps; i++)
351     if (fade_cmaps[i])
352       {
353         XUninstallColormap(dpy, fade_cmaps[i]);
354         XFreeColormap(dpy, fade_cmaps[i]);
355         fade_cmaps[i] = 0;
356       }
357   free(fade_cmaps);
358   fade_cmaps = 0;
359 }
360
361
362 #ifdef HAVE_SGI_VC_EXTENSION
363
364 # include <X11/extensions/XSGIvc.h>
365
366 struct screen_gamma_info {
367   int gamma_map;  /* ??? always using 0 */
368   int nred, ngreen, nblue;
369   unsigned short *red1, *green1, *blue1;
370   unsigned short *red2, *green2, *blue2;
371   int gamma_size;
372   int gamma_precision;
373   Bool alpha_p;
374 };
375
376
377 static void whack_gamma(Display *dpy, int screen,
378                         struct screen_gamma_info *info, float ratio);
379
380 static int
381 sgi_gamma_fade (Display *dpy,
382                 Window *black_windows, int seconds, int ticks,
383                 Bool out_p, Bool clear_windows)
384 {
385   int steps = seconds * ticks;
386   long usecs_per_step = (long)(seconds * 1000000) / (long)steps;
387   XEvent dummy_event;
388   int nscreens = ScreenCount(dpy);
389   struct timeval then, now;
390 #ifdef GETTIMEOFDAY_TWO_ARGS
391   struct timezone tzp;
392 #endif
393   int i, screen;
394   int status = -1;
395   struct screen_gamma_info *info = (struct screen_gamma_info *)
396     calloc(nscreens, sizeof(*info));
397
398   /* Get the current gamma maps for all screens.
399      Bug out and return -1 if we can't get them for some screen.
400    */
401   for (screen = 0; screen < nscreens; screen++)
402     {
403       if (!XSGIvcQueryGammaMap(dpy, screen, info[screen].gamma_map,
404                                &info[screen].gamma_size,
405                                &info[screen].gamma_precision,
406                                &info[screen].alpha_p))
407         goto FAIL;
408
409       if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
410                                   XSGIVC_COMPONENT_RED,
411                                   &info[screen].nred, &info[screen].red1))
412         goto FAIL;
413       if (! XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
414                                    XSGIVC_COMPONENT_GREEN,
415                                    &info[screen].ngreen, &info[screen].green1))
416         goto FAIL;
417       if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
418                                   XSGIVC_COMPONENT_BLUE,
419                                   &info[screen].nblue, &info[screen].blue1))
420         goto FAIL;
421
422       if (info[screen].gamma_precision == 8)    /* Scale it up to 16 bits. */
423         {
424           int j;
425           for(j = 0; j < info[screen].nred; j++)
426             info[screen].red1[j]   =
427               ((info[screen].red1[j]   << 8) | info[screen].red1[j]);
428           for(j = 0; j < info[screen].ngreen; j++)
429             info[screen].green1[j] =
430               ((info[screen].green1[j] << 8) | info[screen].green1[j]);
431           for(j = 0; j < info[screen].nblue; j++)
432             info[screen].blue1[j]  =
433               ((info[screen].blue1[j]  << 8) | info[screen].blue1[j]);
434         }
435
436       info[screen].red2   = (unsigned short *)
437         malloc(sizeof(*info[screen].red2)   * (info[screen].nred+1));
438       info[screen].green2 = (unsigned short *)
439         malloc(sizeof(*info[screen].green2) * (info[screen].ngreen+1));
440       info[screen].blue2  = (unsigned short *)
441         malloc(sizeof(*info[screen].blue2)  * (info[screen].nblue+1));
442     }
443
444 #ifdef GETTIMEOFDAY_TWO_ARGS
445   gettimeofday(&then, &tzp);
446 #else
447   gettimeofday(&then);
448 #endif
449
450   /* If we're fading in (from black), then first crank the gamma all the
451      way down to 0, then take the windows off the screen.
452    */
453   if (!out_p)
454     for (screen = 0; screen < nscreens; screen++)
455       {
456         whack_gamma(dpy, screen, &info[screen], 0.0);
457         if (black_windows && black_windows[screen])
458           {
459             XUnmapWindow (dpy, black_windows[screen]);
460             XClearWindow (dpy, black_windows[screen]);
461             XSync(dpy, False);
462           }
463       }
464
465
466   /* Iterate by steps of the animation... */
467   for (i = (out_p ? steps : 0);
468        (out_p ? i > 0 : i < steps);
469        (out_p ? i-- : i++))
470     {
471       for (screen = 0; screen < nscreens; screen++)
472         {
473           whack_gamma(dpy, screen, &info[screen],
474                       (((float)i) / ((float)steps)));
475
476           /* If there is user activity, bug out.  (Bug out on keypresses or
477              mouse presses, but not motion, and not release events.  Bugging
478              out on motion made the unfade hack be totally useless, I think.)
479
480              We put the event back so that the calling code can notice it too.
481              It would be better to not remove it at all, but that's harder
482              because Xlib has such a non-design for this kind of crap, and
483              in this application it doesn't matter if the events end up out
484              of order, so in the grand unix tradition we say "fuck it" and
485              do something that mostly works for the time being.
486            */
487           if (XCheckMaskEvent (dpy, (KeyPressMask|ButtonPressMask),
488                                &dummy_event))
489             {
490               XPutBackEvent (dpy, &dummy_event);
491               goto DONE;
492             }
493
494 #ifdef GETTIMEOFDAY_TWO_ARGS
495           gettimeofday(&now, &tzp);
496 #else
497           gettimeofday(&now);
498 #endif
499
500           /* If we haven't already used up our alotted time, sleep to avoid
501              changing the colormap too fast. */
502           {
503             long diff = (((now.tv_sec - then.tv_sec) * 1000000) +
504                          now.tv_usec - then.tv_usec);
505             then.tv_sec = now.tv_sec;
506             then.tv_usec = now.tv_usec;
507             if (usecs_per_step > diff)
508               usleep (usecs_per_step - diff);
509           }
510         }
511     }
512   
513
514  DONE:
515
516   if (out_p && black_windows)
517     {
518       for (screen = 0; screen < nscreens; screen++)
519         {
520           if (clear_windows)
521             XClearWindow (dpy, black_windows[screen]);
522           XMapRaised (dpy, black_windows[screen]);
523         }
524       XSync(dpy, False);
525     }
526
527   /* I can't explain this; without this delay, we get a flicker.
528      I suppose there's some lossage with stale bits being in the
529      hardware frame buffer or something, and this delay gives it
530      time to flush out.  This sucks! */
531   usleep(100000);  /* 1/10th second */
532
533   for (screen = 0; screen < nscreens; screen++)
534     whack_gamma(dpy, screen, &info[screen], 1.0);
535   XSync(dpy, False);
536
537   status = 0;
538
539  FAIL:
540   for (screen = 0; screen < nscreens; screen++)
541     {
542       if (info[screen].red1)   free (info[screen].red1);
543       if (info[screen].green1) free (info[screen].green1);
544       if (info[screen].blue1)  free (info[screen].blue1);
545       if (info[screen].red2)   free (info[screen].red2);
546       if (info[screen].green2) free (info[screen].green2);
547       if (info[screen].blue2)  free (info[screen].blue2);
548     }
549   free(info);
550
551   return status;
552 }
553
554 static void
555 whack_gamma(Display *dpy, int screen, struct screen_gamma_info *info,
556             float ratio)
557 {
558   int k;
559
560   if (ratio < 0) ratio = 0;
561   if (ratio > 1) ratio = 1;
562   for (k = 0; k < info->gamma_size; k++)
563     {
564       info->red2[k]   = info->red1[k]   * ratio;
565       info->green2[k] = info->green1[k] * ratio;
566       info->blue2[k]  = info->blue1[k]  * ratio;
567     }
568
569   XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nred,
570                            XSGIVC_MComponentRed, info->red2);
571   XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->ngreen,
572                            XSGIVC_MComponentGreen, info->green2);
573   XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nblue,
574                            XSGIVC_MComponentBlue, info->blue2);
575   XSync(dpy, False);
576 }
577
578 #endif /* HAVE_SGI_VC_EXTENSION */
579
580
581
582 \f
583 #if 0
584 #include "screenhack.h"
585
586 char *progclass = "foo";
587 char *defaults [] = {
588   0
589 };
590
591 XrmOptionDescRec options [] = {0};
592 int options_size = 0;
593
594 void
595 screenhack (dpy, w)
596      Display *dpy;
597      Window w;
598 {
599   int seconds = 3;
600   int ticks = 20;
601   int delay = 1;
602
603   while (1)
604     {
605       XSync (dpy, False);
606
607       fprintf(stderr,"out..."); fflush(stderr);
608       fade_screens (dpy, 0, seconds, ticks, True);
609       fprintf(stderr, "done.\n"); fflush(stderr);
610
611       if (delay) sleep (delay);
612
613       fprintf(stderr,"in..."); fflush(stderr);
614       fade_screens (dpy, 0, seconds, ticks, False);
615       fprintf(stderr, "done.\n"); fflush(stderr);
616
617       if (delay) sleep (delay);
618     }
619 }
620
621 #endif