feefd69d7d2db06355536ff9ced2fea7680e3d1c
[xscreensaver] / utils / erase.c
1 /* erase.c: Erase the screen in various more or less interesting ways.
2  * Copyright (c) 1997-2001, 2006 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 = (st->height / size) + 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 = (st->height / size) + 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 (tick & 1)
517         {
518           XCopyArea (st->dpy, st->window, st->window, st->fg_gc,
519                      0, y, st->width-step, h, step, y);
520           XFillRectangle (st->dpy, st->window, st->bg_gc, 
521                           0, y, step, h);
522         }
523       else
524         {
525           XCopyArea (st->dpy, st->window, st->window, st->fg_gc,
526                      step, y, st->width-step, h, 0, y);
527           XFillRectangle (st->dpy, st->window, st->bg_gc, 
528                           st->width-step, y, step, h);
529         }
530
531       tick++;
532     }
533 }
534
535
536 /* from Frederick Roeber <roeber@xigo.com> */
537 static void
538 losira (eraser_state *st)
539 {
540   double mode1 = 0.55;
541   double mode2 = mode1 + 0.30;
542   double mode3 = 1.0;
543   int radius = 10;
544
545   if (st->ratio < mode1)                /* squeeze from the sides */
546     {
547       double ratio = st->ratio / mode1;
548       double prev_ratio = st->prev_ratio / mode1;
549       int max = st->width / 2;
550       int step = (max * ratio) - (max * prev_ratio);
551
552       if (step <= 0)
553         step = 1;
554
555       /* pull from left */
556       XCopyArea (st->dpy, st->window, st->window, st->fg_gc,
557                  0, 0, max - step, st->height, step, 0);
558       XFillRectangle (st->dpy, st->window, st->bg_gc, 
559                       0, 0, max * ratio, st->height);
560
561       /* pull from right */
562       XCopyArea (st->dpy, st->window, st->window, st->fg_gc,
563                  max+step, 0, max - step, st->height, max, 0);
564       XFillRectangle (st->dpy, st->window, st->bg_gc, 
565                       max + max*(1-ratio), 0, max, st->height);
566
567       /* expand white from center */
568       XFillRectangle (st->dpy, st->window, st->fg_gc,
569                       max - (radius * ratio), 0,
570                       radius * ratio * 2, st->height);
571     }
572   else if (st->ratio < mode2)           /* squeeze from the top/bottom */
573     {
574       double ratio = (st->ratio - mode1) / (mode2 - mode1);
575       double prev_ratio = (st->prev_ratio - mode1) / (mode2 - mode1);
576       int max = st->height / 2;
577       int step = (max * ratio) - (max * prev_ratio);
578
579       if (step <= 0)
580         step = 1;
581
582       /* fill middle */
583       XFillRectangle (st->dpy, st->window, st->fg_gc,
584                       st->width/2 - radius,
585                       max * ratio,
586                       radius*2, st->height * (1 - ratio));
587
588       /* fill left and right */
589       XFillRectangle (st->dpy, st->window, st->bg_gc, 
590                       0, 0, st->width/2 - radius, st->height);
591       XFillRectangle (st->dpy, st->window, st->bg_gc, 
592                       st->width/2 + radius, 0, st->width/2, st->height);
593
594       /* fill top and bottom */
595       XFillRectangle (st->dpy, st->window, st->bg_gc,
596                       0, 0, st->width, max * ratio);
597       XFillRectangle (st->dpy, st->window, st->bg_gc,
598                       0, st->height - (max * ratio),
599                       st->width, max);
600
601       /* cap top */
602       XFillArc (st->dpy, st->window, st->fg_gc,
603                 st->width/2 - radius,
604                 max * ratio - radius,
605                 radius*2, radius*2,
606                 0, 180*64);
607
608       /* cap bottom */
609       XFillArc (st->dpy, st->window, st->fg_gc,
610                 st->width/2 - radius,
611                 st->height - (max * ratio + radius),
612                 radius*2, radius*2,
613                 180*64, 180*64);
614     }
615   else                                  /* starburst */
616     {
617       double ratio = (st->ratio - mode2) / (mode3 - mode2);
618       double r2 = ratio * radius * 4;
619       XArc arc[9];
620       int i;
621       int angle = 360*64/countof(arc);
622
623       for (i = 0; i < countof(arc); i++)
624         {
625           double th;
626           arc[i].angle1 = angle * i;
627           arc[i].angle2 = angle;
628           arc[i].width  = radius*2 * (1 + ratio);
629           arc[i].height = radius*2 * (1 + ratio);
630           arc[i].x = st->width  / 2 - radius;
631           arc[i].y = st->height / 2 - radius;
632
633           th = ((arc[i].angle1 + (arc[i].angle2 / 2)) / 64.0 / 180 * M_PI);
634
635           arc[i].x += r2 * cos (th);
636           arc[i].y -= r2 * sin (th);
637         }
638
639       XFillRectangle (st->dpy, st->window, st->bg_gc, 
640                       0, 0, st->width, st->height);
641       XFillArcs (st->dpy, st->window, st->fg_gc, arc, countof(arc));
642     }
643 }
644
645
646 static Eraser erasers[] = {
647   random_lines,
648   venetian,
649   triple_wipe,
650   quad_wipe,
651   circle_wipe,
652   three_circle_wipe,
653   squaretate,
654   fizzle,
655   spiral,
656   random_squares,
657   slide_lines,
658   losira,
659 };
660
661
662 static eraser_state *
663 eraser_init (Display *dpy, Window window)
664 {
665   eraser_state *st = (eraser_state *) calloc (1, sizeof(*st));
666   XWindowAttributes xgwa;
667   XGCValues gcv;
668   unsigned long fg, bg;
669   double duration;
670   int which;
671   char *s;
672
673   st->dpy = dpy;
674   st->window = window;
675
676   XGetWindowAttributes (dpy, window, &xgwa);
677   st->width = xgwa.width;
678   st->height = xgwa.height;
679
680   bg = get_pixel_resource (dpy, xgwa.colormap, "background", "Background");
681   fg = get_pixel_resource (dpy, xgwa.colormap, "foreground", "Foreground");
682
683   gcv.foreground = fg;
684   gcv.background = bg;
685   st->fg_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
686   gcv.foreground = bg;
687   gcv.background = fg;
688   st->bg_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
689
690 # ifdef HAVE_COCOA
691   /* Pretty much all of these leave turds if AA is on. */
692   jwxyz_XSetAntiAliasing (st->dpy, st->fg_gc, False);
693   jwxyz_XSetAntiAliasing (st->dpy, st->bg_gc, False);
694 # endif
695
696   s = get_string_resource (dpy, "eraseMode", "Integer");
697   if (!s || !*s)
698     which = -1;
699   else
700     which = get_integer_resource(dpy, "eraseMode", "Integer");
701
702   if (which < 0 || which >= countof(erasers))
703     which = random() % countof(erasers);
704   st->fn = erasers[which];
705
706   duration = get_float_resource (dpy, "eraseSeconds", "Float");
707   if (duration < 0.1 || duration > 10)
708     duration = 1;
709
710   st->start_time = double_time();
711   st->stop_time = st->start_time + duration;
712
713   XSync (st->dpy, False);
714
715   return st;
716 }
717
718
719 static Bool
720 eraser_draw (eraser_state *st, Bool first_p)
721 {
722   double now = (first_p ? st->start_time : double_time());
723   double duration = st->stop_time - st->start_time;
724
725   st->prev_ratio = st->ratio;
726   st->ratio = (now - st->start_time) / duration;
727
728   if (st->ratio > 1.0)
729     st->ratio = 1.0;
730
731   st->fn (st);
732   XSync (st->dpy, False);
733
734   return (st->ratio < 1.0);
735 }
736
737 static void
738 eraser_free (eraser_state *st)
739 {
740   XClearWindow (st->dpy, st->window);
741   XFreeGC (st->dpy, st->fg_gc);
742   XFreeGC (st->dpy, st->bg_gc);
743   free (st);
744 }
745
746 eraser_state *
747 erase_window (Display *dpy, Window window, eraser_state *st)
748 {
749   Bool first_p = False;
750   if (! st)
751     {
752       first_p = True;
753       st = eraser_init (dpy, window);
754     }
755   if (! eraser_draw (st, first_p)) 
756     {
757       eraser_free (st);
758       st = 0;
759     }
760   return st;
761 }