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