From http://www.jwz.org/xscreensaver/xscreensaver-5.27.tar.gz
[xscreensaver] / utils / erase.c
1 /* erase.c: Erase the screen in various more or less interesting ways.
2  * Copyright (c) 1997-2008 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  *
12  * Portions (c) 1997 by Johannes Keukelaar <johannes@nada.kth.se>:
13  *   Permission to use in any way granted. Provided "as is" without expressed
14  *   or implied warranty. NO WARRANTY, NO EXPRESSION OF SUITABILITY FOR ANY
15  *   PURPOSE. (I.e.: Use in any way, but at your own risk!)
16  */
17
18 #include "utils.h"
19 #include "yarandom.h"
20 #include "usleep.h"
21 #include "resources.h"
22 #include "erase.h"
23 #include <sys/time.h> /* for gettimeofday() */
24
25 extern char *progname;
26
27 #undef countof
28 #define countof(x) (sizeof(x)/sizeof(*(x)))
29
30 typedef void (*Eraser) (eraser_state *);
31
32 struct eraser_state {
33   Display *dpy;
34   Window window;
35   GC fg_gc, bg_gc;
36   int width, height;
37   Eraser fn;
38
39   double start_time, stop_time;
40   double ratio, prev_ratio;
41
42   /* data for random_lines, venetian, random_squares */
43   Bool horiz_p;
44   Bool flip_p;
45   int nlines, *lines;
46
47   /* data for triple_wipe, quad_wipe */
48   Bool flip_x, flip_y;
49
50   /* data for circle_wipe, three_circle_wipe */
51   int start;
52
53   /* data for random_squares */
54   int cols;
55
56 };
57
58
59 static double
60 double_time (void)
61 {
62   struct timeval now;
63 # ifdef GETTIMEOFDAY_TWO_ARGS
64   struct timezone tzp;
65   gettimeofday(&now, &tzp);
66 # else
67   gettimeofday(&now);
68 # endif
69
70   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
71 }
72
73
74 static void
75 random_lines (eraser_state *st)
76 {
77   int i;
78
79   if (! st->lines)      /* first time */
80     {
81       st->horiz_p = (random() & 1);
82       st->nlines = (st->horiz_p ? st->height : st->width);
83       st->lines = (int *) calloc (st->nlines, sizeof(*st->lines));
84
85       for (i = 0; i < st->nlines; i++)  /* every line */
86         st->lines[i] = i;
87
88       for (i = 0; i < st->nlines; i++)  /* shuffle */
89         {
90           int t, r;
91           t = st->lines[i];
92           r = random() % st->nlines;
93           st->lines[i] = st->lines[r];
94           st->lines[r] = t;
95         }
96     }
97
98   for (i = st->nlines * st->prev_ratio;
99        i < st->nlines * st->ratio;
100        i++)
101     {
102       if (st->horiz_p)
103         XDrawLine (st->dpy, st->window, st->bg_gc, 
104                    0, st->lines[i], st->width, st->lines[i]);
105       else
106         XDrawLine (st->dpy, st->window, st->bg_gc, 
107                    st->lines[i], 0, st->lines[i], st->height);
108     }
109
110   if (st->ratio >= 1.0)
111     {
112       free (st->lines);
113       st->lines = 0;
114     }
115 }
116
117
118 static void
119 venetian (eraser_state *st)
120 {
121   int i;
122   if (st->ratio == 0.0)
123     {
124       int j = 0;
125       st->horiz_p = (random() & 1);
126       st->flip_p = (random() & 1);
127       st->nlines = (st->horiz_p ? st->height : st->width);
128       st->lines = (int *) calloc (st->nlines, sizeof(*st->lines));
129
130       for (i = 0; i < st->nlines * 2; i++)
131         {
132           int line = ((i / 16) * 16) - ((i % 16) * 15);
133           if (line >= 0 && line < st->nlines)
134             st->lines[j++] = (st->flip_p ? st->nlines - line : line);
135         }
136     }
137
138   
139   for (i = st->nlines * st->prev_ratio;
140        i < st->nlines * st->ratio;
141        i++)
142     {
143       if (st->horiz_p)
144         XDrawLine (st->dpy, st->window, st->bg_gc, 
145                    0, st->lines[i], st->width, st->lines[i]);
146       else
147         XDrawLine (st->dpy, st->window, st->bg_gc, 
148                    st->lines[i], 0, st->lines[i], st->height);
149     }
150
151   if (st->ratio >= 1.0)
152     {
153       free (st->lines);
154       st->lines = 0;
155     }
156 }
157
158
159 static void
160 triple_wipe (eraser_state *st)
161 {
162   int i;
163   if (st->ratio == 0.0)
164     {
165       st->flip_x = random() & 1;
166       st->flip_y = random() & 1;
167       st->nlines = st->width + (st->height / 2);
168       st->lines = (int *) calloc (st->nlines, sizeof(*st->lines));
169
170       for (i = 0; i < st->width / 2; i++)
171         st->lines[i] = i * 2 + st->height;
172       for (i = 0; i < st->height / 2; i++)
173         st->lines[i + st->width / 2] = i*2;
174       for (i = 0; i < st->width / 2; i++)
175         st->lines[i + st->width / 2 + st->height / 2] = 
176           st->width - i * 2 - (st->width % 2 ? 0 : 1) + st->height;
177     }
178
179   for (i = st->nlines * st->prev_ratio;
180        i < st->nlines * st->ratio;
181        i++)
182     {
183       int x, y, x2, y2;
184
185       if (st->lines[i] < st->height)
186         x = 0, y = st->lines[i], x2 = st->width, y2 = y;
187       else
188         x = st->lines[i]-st->height, y = 0, x2 = x, y2 = st->height;
189
190       if (st->flip_x)
191         x = st->width - x, x2 = st->width - x2;
192       if (st->flip_y)
193         y = st->height - y, y2 = st->height - y2;
194
195       XDrawLine (st->dpy, st->window, st->bg_gc, x, y, x2, y2);
196     }
197
198   if (st->ratio >= 1.0)
199     {
200       free (st->lines);
201       st->lines = 0;
202     }
203 }
204
205
206 static void
207 quad_wipe (eraser_state *st)
208 {
209   int i;
210   if (st->ratio == 0.0)
211     {
212       st->flip_x = random() & 1;
213       st->flip_y = random() & 1;
214       st->nlines = st->width + st->height;
215       st->lines = (int *) calloc (st->nlines, sizeof(*st->lines));
216
217       for (i = 0; i < st->nlines/4; i++)
218         {
219           st->lines[i*4]   = i*2;
220           st->lines[i*4+1] = st->height - i*2 - (st->height % 2 ? 0 : 1);
221           st->lines[i*4+2] = st->height + i*2;
222           st->lines[i*4+3] = st->height + st->width - i*2
223             - (st->width % 2 ? 0 : 1);
224         }
225     }
226
227   for (i = st->nlines * st->prev_ratio;
228        i < st->nlines * st->ratio;
229        i++)
230     {
231       int x, y, x2, y2;
232       if (st->lines[i] < st->height)
233         x = 0, y = st->lines[i], x2 = st->width, y2 = y;
234       else
235         x = st->lines[i] - st->height, y = 0, x2 = x, y2 = st->height;
236
237       if (st->flip_x)
238         x = st->width-x, x2 = st->width-x2;
239       if (st->flip_y)
240         y = st->height-y, y2 = st->height-y2;
241
242       XDrawLine (st->dpy, st->window, st->bg_gc, x, y, x2, y2);
243     }
244
245   if (st->ratio >= 1.0)
246     {
247       free (st->lines);
248       st->lines = 0;
249     }
250 }
251
252
253 static void
254 circle_wipe (eraser_state *st)
255 {
256   int rad = (st->width > st->height ? st->width : st->height);
257   int max = 360 * 64;
258   int th, oth;
259
260   if (st->ratio == 0.0)
261     {
262       st->flip_p = random() & 1;
263       st->start = random() % max;
264     }
265
266   th  = max * st->ratio;
267   oth = max * st->prev_ratio;
268   if (st->flip_p)
269     {
270       th  = max - th;
271       oth = max - oth;
272     }
273   XFillArc (st->dpy, st->window, st->bg_gc,
274             (st->width  / 2) - rad,
275             (st->height / 2) - rad, 
276             rad*2, rad*2,
277             (st->start + oth) % max,
278             th-oth);
279 }
280
281
282 static void
283 three_circle_wipe (eraser_state *st)
284 {
285   int rad = (st->width > st->height ? st->width : st->height);
286   int max = 360 * 64;
287   int th, oth;
288   int i;
289
290   if (st->ratio == 0.0)
291     st->start = random() % max;
292
293   th  = max/6 * st->ratio;
294   oth = max/6 * st->prev_ratio;
295
296   for (i = 0; i < 3; i++)
297     {
298       int off = i * max / 3;
299       XFillArc (st->dpy, st->window, st->bg_gc,
300                 (st->width  / 2) - rad,
301                 (st->height / 2) - rad, 
302                 rad*2, rad*2,
303                 (st->start + off + oth) % max,
304                 th-oth);
305       XFillArc (st->dpy, st->window, st->bg_gc,
306                 (st->width  / 2) - rad,
307                 (st->height / 2) - rad, 
308                 rad*2, rad*2,
309                 (st->start + off - oth) % max,
310                 oth-th);
311     }
312 }
313
314
315 static void
316 squaretate (eraser_state *st)
317 {
318   int max = ((st->width > st->height ? st->width : st->height) * 2);
319   XPoint points [3];
320   int i = max * st->ratio;
321
322   if (st->ratio == 0.0)
323     st->flip_p = random() & 1;
324     
325 # define DRAW()                                         \
326   if (st->flip_p) {                                     \
327     points[0].x = st->width - points[0].x;              \
328     points[1].x = st->width - points[1].x;              \
329     points[2].x = st->width - points[2].x;              \
330   }                                                     \
331   XFillPolygon (st->dpy, st->window, st->bg_gc,         \
332                 points, 3, Convex, CoordModeOrigin)
333
334   points[0].x = 0;
335   points[0].y = 0;
336   points[1].x = st->width;
337   points[1].y = 0;
338   points[2].x = 0;
339   points[2].y = points[0].y + ((i * st->height) / max);
340   DRAW();
341
342   points[0].x = 0;
343   points[0].y = 0;
344   points[1].x = 0;
345   points[1].y = st->height;
346   points[2].x = ((i * st->width) / max);
347   points[2].y = st->height;
348   DRAW();
349
350   points[0].x = st->width;
351   points[0].y = st->height;
352   points[1].x = 0;
353   points[1].y = st->height;
354   points[2].x = st->width;
355   points[2].y = st->height - ((i * st->height) / max);
356   DRAW();
357
358   points[0].x = st->width;
359   points[0].y = st->height;
360   points[1].x = st->width;
361   points[1].y = 0;
362   points[2].x = st->width - ((i * st->width) / max);
363   points[2].y = 0;
364   DRAW();
365 # undef DRAW
366 }
367
368
369 static void
370 fizzle (eraser_state *st)
371 {
372   XPoint *points;
373   int chunk = 20000;
374   int npoints = st->width * st->height * 4;
375   npoints *= (st->ratio - st->prev_ratio);
376
377   points = (XPoint *) malloc (chunk * sizeof(*points));
378   if (! points) return;
379
380   while (npoints > 0)
381     {
382       int remain = (chunk > npoints ? npoints : chunk);
383       int i;
384       for (i = 0; i < remain; i++)
385         {
386           int r = random();
387           points[i].x = r % st->width;
388           points[i].y = (r >> 16) % st->height;
389         }
390       XDrawPoints (st->dpy, st->window, st->bg_gc, 
391                    points, remain, CoordModeOrigin);
392       npoints -= remain;
393     }
394   free (points);
395 }
396
397
398 static void
399 spiral (eraser_state *st)
400 {
401   int max_radius = (st->width > st->height ? st->width : st->height) * 0.7;
402   int loops = 10;
403   float max_th = M_PI * 2 * loops;
404   int i;
405   int steps = 360 * loops / 4;
406   float off;
407
408   if (st->ratio == 0.0)
409     {
410       st->flip_p = random() & 1;
411       st->start = random() % 360;
412     }
413
414   off = st->start * M_PI / 180;
415
416   for (i = steps * st->prev_ratio;
417        i < steps * st->ratio;
418        i++)
419     {
420       float th1 = i     * max_th / steps;
421       float th2 = (i+1) * max_th / steps;
422       int   r1  = i     * max_radius / steps;
423       int   r2  = (i+1) * max_radius / steps;
424       XPoint points[3];
425
426       if (st->flip_p)
427         {
428           th1 = max_th - th1;
429           th2 = max_th - th2;
430         }
431
432       points[0].x = st->width  / 2;
433       points[0].y = st->height / 2;
434       points[1].x = points[0].x + r1 * cos (off + th1);
435       points[1].y = points[0].y + r1 * sin (off + th1);
436       points[2].x = points[0].x + r2 * cos (off + th2);
437       points[2].y = points[0].y + r2 * sin (off + th2);
438 /*  XFillRectangle(st->dpy, st->window, st->fg_gc,0,0,st->width, st->height);*/
439       XFillPolygon (st->dpy, st->window, st->bg_gc,
440                     points, 3, Convex, CoordModeOrigin);
441     }
442 }
443
444
445 static void
446 random_squares (eraser_state *st)
447 {
448   int i, size, rows;
449
450   if (st->ratio == 0.0)
451     {
452       st->cols = 10 + random() % 30;
453       size = st->width / st->cols;
454       rows = (size ? (st->height / size) : 0) + 1;
455       st->nlines = st->cols * rows;
456       st->lines = (int *) calloc (st->nlines, sizeof(*st->lines));
457
458       for (i = 0; i < st->nlines; i++)  /* every square */
459         st->lines[i] = i;
460
461       for (i = 0; i < st->nlines; i++)  /* shuffle */
462         {
463           int t, r;
464           t = st->lines[i];
465           r = random() % st->nlines;
466           st->lines[i] = st->lines[r];
467           st->lines[r] = t;
468         }
469     }
470
471   size = st->width / st->cols;
472   rows = (size ? (st->height / size) : 0) + 1;
473
474   for (i = st->nlines * st->prev_ratio;
475        i < st->nlines * st->ratio;
476        i++)
477     {
478       int x = st->lines[i] % st->cols;
479       int y = st->lines[i] / st->cols;
480       XFillRectangle (st->dpy, st->window, st->bg_gc,
481                       st->width  * x / st->cols,
482                       st->height * y / rows,
483                       size+1, size+1);
484     }
485
486   if (st->ratio >= 1.0)
487     {
488       free (st->lines);
489       st->lines = 0;
490     }
491 }
492
493
494 /* I first saw something like this, albeit in reverse, in an early Tetris
495    implementation for the Mac.
496     -- Torbjörn Andersson <torbjorn@dev.eurotime.se>
497  */
498 static void
499 slide_lines (eraser_state *st)
500 {
501   int max = st->width * 1.1;
502   int nlines = 40;
503   int h = st->height / nlines;
504   int y, step;
505   int tick = 0;
506
507   if (h < 10)
508     h = 10;
509
510   step = (max * st->ratio) - (max * st->prev_ratio);
511   if (step <= 0)
512     step = 1;
513
514   for (y = 0; y < st->height; y += h)
515     {
516       if (st->width <= step)
517         ;
518       else if (tick & 1)
519         {
520           XCopyArea (st->dpy, st->window, st->window, st->fg_gc,
521                      0, y, st->width-step, h, step, y);
522           XFillRectangle (st->dpy, st->window, st->bg_gc, 
523                           0, y, step, h);
524         }
525       else
526         {
527           XCopyArea (st->dpy, st->window, st->window, st->fg_gc,
528                      step, y, st->width-step, h, 0, y);
529           XFillRectangle (st->dpy, st->window, st->bg_gc, 
530                           st->width-step, y, step, h);
531         }
532
533       tick++;
534     }
535 }
536
537
538 /* from Frederick Roeber <roeber@xigo.com> */
539 static void
540 losira (eraser_state *st)
541 {
542   double mode1 = 0.55;
543   double mode2 = mode1 + 0.30;
544   double mode3 = 1.0;
545   int radius = 10;
546
547   if (st->ratio < mode1)                /* squeeze from the sides */
548     {
549       double ratio = st->ratio / mode1;
550       double prev_ratio = st->prev_ratio / mode1;
551       int max = st->width / 2;
552       int step = (max * ratio) - (max * prev_ratio);
553
554       if (step <= 0)
555         step = 1;
556
557       /* pull from left */
558       XCopyArea (st->dpy, st->window, st->window, st->fg_gc,
559                  0, 0, max - step, st->height, step, 0);
560       XFillRectangle (st->dpy, st->window, st->bg_gc, 
561                       0, 0, max * ratio, st->height);
562
563       /* pull from right */
564       XCopyArea (st->dpy, st->window, st->window, st->fg_gc,
565                  max+step, 0, max - step, st->height, max, 0);
566       XFillRectangle (st->dpy, st->window, st->bg_gc, 
567                       max + max*(1-ratio), 0, max, st->height);
568
569       /* expand white from center */
570       XFillRectangle (st->dpy, st->window, st->fg_gc,
571                       max - (radius * ratio), 0,
572                       radius * ratio * 2, st->height);
573     }
574   else if (st->ratio < mode2)           /* squeeze from the top/bottom */
575     {
576       double ratio = (st->ratio - mode1) / (mode2 - mode1);
577       int max = st->height / 2;
578
579       /* fill middle */
580       XFillRectangle (st->dpy, st->window, st->fg_gc,
581                       st->width/2 - radius,
582                       max * ratio,
583                       radius*2, st->height * (1 - ratio));
584
585       /* fill left and right */
586       XFillRectangle (st->dpy, st->window, st->bg_gc, 
587                       0, 0, st->width/2 - radius, st->height);
588       XFillRectangle (st->dpy, st->window, st->bg_gc, 
589                       st->width/2 + radius, 0, st->width/2, st->height);
590
591       /* fill top and bottom */
592       XFillRectangle (st->dpy, st->window, st->bg_gc,
593                       0, 0, st->width, max * ratio);
594       XFillRectangle (st->dpy, st->window, st->bg_gc,
595                       0, st->height - (max * ratio),
596                       st->width, max);
597
598       /* cap top */
599       XFillArc (st->dpy, st->window, st->fg_gc,
600                 st->width/2 - radius,
601                 max * ratio - radius,
602                 radius*2, radius*2,
603                 0, 180*64);
604
605       /* cap bottom */
606       XFillArc (st->dpy, st->window, st->fg_gc,
607                 st->width/2 - radius,
608                 st->height - (max * ratio + radius),
609                 radius*2, radius*2,
610                 180*64, 180*64);
611     }
612   else                                  /* starburst */
613     {
614       double ratio = (st->ratio - mode2) / (mode3 - mode2);
615       double r2 = ratio * radius * 4;
616       XArc arc[9];
617       int i;
618       int angle = 360*64/countof(arc);
619
620       for (i = 0; i < countof(arc); i++)
621         {
622           double th;
623           arc[i].angle1 = angle * i;
624           arc[i].angle2 = angle;
625           arc[i].width  = radius*2 * (1 + ratio);
626           arc[i].height = radius*2 * (1 + ratio);
627           arc[i].x = st->width  / 2 - radius;
628           arc[i].y = st->height / 2 - radius;
629
630           th = ((arc[i].angle1 + (arc[i].angle2 / 2)) / 64.0 / 180 * M_PI);
631
632           arc[i].x += r2 * cos (th);
633           arc[i].y -= r2 * sin (th);
634         }
635
636       XFillRectangle (st->dpy, st->window, st->bg_gc, 
637                       0, 0, st->width, st->height);
638       XFillArcs (st->dpy, st->window, st->fg_gc, arc, countof(arc));
639     }
640 }
641
642
643 static Eraser erasers[] = {
644   random_lines,
645   venetian,
646   triple_wipe,
647   quad_wipe,
648   circle_wipe,
649   three_circle_wipe,
650   squaretate,
651   fizzle,
652   spiral,
653   random_squares,
654   slide_lines,
655   losira,
656 };
657
658
659 static eraser_state *
660 eraser_init (Display *dpy, Window window)
661 {
662   eraser_state *st = (eraser_state *) calloc (1, sizeof(*st));
663   XWindowAttributes xgwa;
664   XGCValues gcv;
665   unsigned long fg, bg;
666   double duration;
667   int which;
668   char *s;
669
670   st->dpy = dpy;
671   st->window = window;
672
673   XGetWindowAttributes (dpy, window, &xgwa);
674   st->width = xgwa.width;
675   st->height = xgwa.height;
676
677   bg = get_pixel_resource (dpy, xgwa.colormap, "background", "Background");
678   fg = get_pixel_resource (dpy, xgwa.colormap, "foreground", "Foreground");
679
680   gcv.foreground = fg;
681   gcv.background = bg;
682   st->fg_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
683   gcv.foreground = bg;
684   gcv.background = fg;
685   st->bg_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
686
687 # ifdef HAVE_COCOA
688   /* Pretty much all of these leave turds if AA is on. */
689   jwxyz_XSetAntiAliasing (st->dpy, st->fg_gc, False);
690   jwxyz_XSetAntiAliasing (st->dpy, st->bg_gc, False);
691 # endif
692
693   s = get_string_resource (dpy, "eraseMode", "Integer");
694   if (!s || !*s)
695     which = -1;
696   else
697     which = get_integer_resource(dpy, "eraseMode", "Integer");
698
699   if (which < 0 || which >= countof(erasers))
700     which = random() % countof(erasers);
701   st->fn = erasers[which];
702
703   duration = get_float_resource (dpy, "eraseSeconds", "Float");
704   if (duration < 0.1 || duration > 10)
705     duration = 1;
706
707   st->start_time = double_time();
708   st->stop_time = st->start_time + duration;
709
710   XSync (st->dpy, False);
711
712   return st;
713 }
714
715
716 static Bool
717 eraser_draw (eraser_state *st, Bool first_p)
718 {
719   double now = (first_p ? st->start_time : double_time());
720   double duration = st->stop_time - st->start_time;
721
722   st->prev_ratio = st->ratio;
723   st->ratio = (now - st->start_time) / duration;
724
725   if (st->ratio > 1.0)
726     st->ratio = 1.0;
727
728   st->fn (st);
729   XSync (st->dpy, False);
730
731   return (st->ratio < 1.0);
732 }
733
734 static void
735 eraser_free (eraser_state *st)
736 {
737   XClearWindow (st->dpy, st->window);
738   XFreeGC (st->dpy, st->fg_gc);
739   XFreeGC (st->dpy, st->bg_gc);
740   free (st);
741 }
742
743 eraser_state *
744 erase_window (Display *dpy, Window window, eraser_state *st)
745 {
746   Bool first_p = False;
747   if (! st)
748     {
749       first_p = True;
750       st = eraser_init (dpy, window);
751     }
752   if (! eraser_draw (st, first_p)) 
753     {
754       eraser_free (st);
755       st = 0;
756     }
757   return st;
758 }