5d11097da502fc5c272a9dbe94fad3c98846dd2a
[xscreensaver] / hacks / pedal.c
1 /*
2  * pedal
3  *
4  * Based on a program for some old PDP-11 Graphics Display Processors
5  * at CMU.
6  *
7  * X version by
8  *
9  *  Dale Moore  <Dale.Moore@cs.cmu.edu>
10  *  24-Jun-1994
11  *
12  *  Copyright (c) 1994, by Carnegie Mellon University.  Permission to use,
13  *  copy, modify, distribute, and sell this software and its documentation
14  *  for any purpose is hereby granted without fee, provided fnord that the
15  *  above copyright notice appear in all copies and that both that copyright
16  *  notice and this permission notice appear in supporting documentation.
17  *  No representations are made about the  suitability of fnord this software
18  *  for any purpose.  It is provided "as is" without express or implied
19  *  warranty.
20  */
21
22 #include <math.h>
23 #include <stdlib.h>
24 #include "screenhack.h"
25
26 /* If MAXLINES is too big, we might not be able to get it
27  * to the X server in the 2byte length field. Must be less
28  * than 16k
29  */
30 #define MAXLINES (16 * 1024)
31 #define MAXPOINTS MAXLINES
32 XPoint *points;
33
34 /* 
35  * If the pedal has only this many lines, it must be ugly and we dont
36  * want to see it.
37  */
38 #define MINLINES 7
39
40 static int sizex, sizey;
41 static int delay;
42 static int fadedelay;
43 static int maxlines;
44 static GC gc;
45 static XColor foreground, background;
46 static Colormap cmap;
47
48 static Bool fade_p;
49
50
51 /*
52  * Routine (Macro actually)
53  *   mysin
54  * Description:
55  *   Assume that degrees is .. oh 360... meaning that
56  *   there are 360 degress in a circle.  Then this function
57  *   would return the sin of the angle in degrees.  But lets
58  *   say that they are really big degrees, with 4 big degrees
59  *   the same as one regular degree.  Then this routine
60  *   would be called mysin(t, 90) and would return sin(t degrees * 4)
61  */
62 #define mysin(t, degrees) sin(t * 2 * M_PI / (degrees))
63 #define mycos(t, degrees) cos(t * 2 * M_PI / (degrees))
64
65 /*
66  * Macro:
67  *   rand_range
68  * Description:
69  *   Return a random number between a inclusive  and b exclusive.
70  *    rand (3, 6) returns 3 or 4 or 5, but not 6.
71  */
72 #define rand_range(a, b) (a + random() % (b - a))
73
74
75 static int
76 gcd(int m, int n) /* Greatest Common Divisor (also Greates common factor). */
77 {
78     int r;
79
80     for (;;) {
81         r = m % n;
82         if (r == 0) return (n);
83         m = n;
84         n = r;
85     }
86 }
87
88 static int numlines (int a, int b, int d)
89 /*
90  * Description:
91  *
92  *      Given parameters a and b, how many lines will we have to draw?
93  *
94  * Algorithm:
95  *
96  *      This algorithm assumes that r = sin (theta * a), where we
97  *      evaluate theta on multiples of b.
98  *
99  *      LCM (i, j) = i * j / GCD (i, j);
100  *
101  *      So, at LCM (b, 360) we start over again.  But since we
102  *      got to LCM (b, 360) by steps of b, the number of lines is
103  *      LCM (b, 360) / b.
104  *
105  *      If a is odd, then at 180 we cross over and start the
106  *      negative.  Someone should write up an elegant way of proving
107  *      this.  Why?  Because I'm not convinced of it myself. 
108  *
109  */
110 {
111 #define odd(x) (x & 1)
112 #define even(x) (!odd(x))
113     if ( odd(a) && odd(b) && even(d)) d /= 2;
114     return  (d / gcd (d, b));
115 #undef odd
116 }
117
118 static int
119 compute_pedal(XPoint *points, int maxpoints)
120 /*
121  * Description:
122  *
123  *    Basically, it's combination spirograph and string art.
124  *    Instead of doing lines, we just use a complex polygon,
125  *    and use an even/odd rule for filling in between.
126  *
127  *    The spirograph, in mathematical terms is a polar
128  *    plot of the form r = sin (theta * c);
129  *    The string art of this is that we evaluate that
130  *    function only on certain multiples of theta.  That is
131  *    we let theta advance in some random increment.  And then
132  *    we draw a straight line between those two adjacent points.
133  *
134  *    Eventually, the lines will start repeating themselves
135  *    if we've evaluated theta on some rational portion of the
136  *    whole.
137  *
138  *    The number of lines generated is limited to the
139  *    ratio of the increment we put on theta to the whole.
140  *    If we say that there are 360 degrees in a circle, then we
141  *    will never have more than 360 lines.   
142  *
143  * Return:
144  *
145  *    The number of points.
146  *
147  */
148 {
149     int a, b, d;  /* These describe a unique pedal */
150
151     double r;
152     int theta = 0;
153     XPoint *pp = points;
154     int count;
155     int numpoints;
156
157     /* Just to make sure that this division is not done inside the loop */
158     int h_width = sizex / 2, h_height = sizey / 2 ;
159
160     for (;;) {
161         d = rand_range (MINLINES, maxlines);
162
163         a = rand_range (1, d);
164         b = rand_range (1, d);
165         numpoints = numlines(a, b, d);
166         if (numpoints > MINLINES) break;
167     }
168
169     /* it might be nice to try to move as much sin and cos computing
170      * (or at least the argument computing) out of the loop.
171      */
172     for (count = numpoints; count-- ; )
173     {
174         r = mysin (theta * a, d);
175
176         /* Convert from polar to cartesian coordinates */
177         /* We could round the results, but coercing seems just fine */
178         pp->x = mysin (theta, d) * r * h_width  + h_width;
179         pp->y = mycos (theta, d) * r * h_height + h_height;
180
181         /* Advance index into array */
182         pp++;
183
184         /* Advance theta */
185         theta += b;
186         theta %= d;
187     }
188
189     return(numpoints);
190 }
191
192 static void
193 init_pedal (Display *dpy, Window window)
194 {
195   XGCValues gcv;
196   XWindowAttributes xgwa;
197
198   fade_p = !mono_p;
199
200   delay = get_integer_resource ("delay", "Integer");
201   if (delay < 0) delay = 0;
202
203   fadedelay = get_integer_resource ("fadedelay", "Integer");
204   if (fadedelay < 0) fadedelay = 0;
205
206   maxlines = get_integer_resource ("maxlines", "Integer");
207   if (maxlines < MINLINES) maxlines = MINLINES;
208   else if (maxlines > MAXLINES) maxlines = MAXLINES;
209
210   points = (XPoint *)malloc(sizeof(XPoint) * maxlines);
211
212   XGetWindowAttributes (dpy, window, &xgwa);
213   sizex = xgwa.width;
214   sizey = xgwa.height;
215
216   if ((xgwa.visual->class != GrayScale) && (xgwa.visual->class != PseudoColor))
217     fade_p = False;
218
219   cmap = xgwa.colormap;
220
221   gcv.function = GXcopy;
222   gcv.foreground = get_pixel_resource ("foreground", "Foreground", dpy, cmap);
223   gcv.background = get_pixel_resource ("background", "Background", dpy, cmap);
224   gc = XCreateGC (dpy, window, GCForeground | GCBackground |GCFunction, &gcv);
225
226   if (fade_p)
227   {
228       int status;
229       foreground.pixel = gcv.foreground;
230       XQueryColor (dpy, cmap, &foreground);
231
232       status = XAllocColorCells (
233                         dpy,
234                         cmap,
235                         0,
236                         NULL,
237                         0,
238                         &foreground.pixel,
239                         1);
240       if (status)
241       {
242           XStoreColor ( dpy, cmap, &foreground);
243           XSetForeground (dpy, gc, foreground.pixel);
244
245           background.pixel = gcv.background;
246           XQueryColor (dpy, cmap, &background);
247       }
248       else
249       {
250           /* If we cant allocate a color cell, then just forget the
251            * whole fade business.
252            */
253           fade_p = False;
254       }
255   }
256 }
257
258 static void
259 fade_foreground (Display *dpy, Colormap cmap,
260                  XColor from, XColor to, int steps)
261 /*
262  * This routine assumes that we have a writeable colormap.
263  * That means that the default colormap is not full, and that
264  * the visual class is PseudoColor or GrayScale.
265  */
266 {
267     int i;
268     XColor inbetween;
269     int udelay = fadedelay / (steps + 1);
270
271     inbetween = foreground;
272     for (i = 0; i <= steps; i++ )
273     {
274       inbetween.red   = from.red   + (to.red   - from.red)   * i / steps ;
275       inbetween.green = from.green + (to.green - from.green) * i / steps ;
276       inbetween.blue  = from.blue  + (to.blue  - from.blue)  * i / steps ;
277       XStoreColor (dpy, cmap, &inbetween);
278       /* If we don't sync, these can bunch up */
279       XSync(dpy, 0);
280       usleep(udelay);
281     }
282 }
283
284 static void
285 pedal (Display *dpy, Window window)
286 /*
287  *    Since the XFillPolygon doesn't require that the last
288  *    point == first point, the number of points is the same
289  *    as the number of lines.  We just let XFillPolygon supply
290  *    the line from the last point to the first point.
291  *
292  */
293 {
294    int numpoints;
295
296    numpoints = compute_pedal(points, maxlines);
297
298    /* Fade out, make foreground the same as background */
299    if (fade_p)
300      fade_foreground (dpy, cmap, foreground, background, 32);
301
302     /* Clear the window of previous garbage */
303     XClearWindow (dpy, window);
304
305     XFillPolygon (
306                 dpy,
307                 window,
308                 gc,
309                 points,
310                 numpoints,
311                 Complex,
312                 CoordModeOrigin);
313
314    /* Pick a new foreground color (added by jwz) */
315    if (! mono_p)
316      {
317        XColor color;
318        hsv_to_rgb (random()%360, 1.0, 1.0,
319                    &color.red, &color.green, &color.blue);
320        XSync(dpy, 0);
321        if (fade_p)
322          {
323            foreground.red = color.red;
324            foreground.green = color.green;
325            foreground.blue = color.blue;
326            /* don't do this here -- let fade_foreground() bring it up! */
327            /* XStoreColor (dpy, cmap, &foreground); */
328          }
329        else if (XAllocColor (dpy, cmap, &color))
330          {
331            XSetForeground (dpy, gc, color.pixel);
332            XFreeColors (dpy, cmap, &foreground.pixel, 1, 0);
333            foreground.red = color.red;
334            foreground.green = color.green;
335            foreground.blue = color.blue;
336            foreground.pixel = color.pixel;
337          }
338        XSync(dpy, 0);
339      }
340
341     /* Fade in by bringing the foreground back from background */
342     if (fade_p)
343        fade_foreground (dpy, cmap, background, foreground, 32);
344 }
345
346 \f
347 char *progclass = "Pedal";
348
349 /*
350  * If we are trying to save the screen, the background
351  * should be dark.
352  */
353 char *defaults [] = {
354   "*background:                 black",
355   "*foreground:                 white",
356   "*delay:                      5",
357   "*fadedelay:                  200000",
358   "*maxlines:                   1000",
359   0
360 };
361
362 XrmOptionDescRec options [] = {
363   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
364   { "-fadedelay",       ".fadedelay",           XrmoptionSepArg, 0 },
365   { "-maxlines",        ".maxlines",            XrmoptionSepArg, 0 },
366   { "-foreground",      ".foreground",          XrmoptionSepArg, 0 },
367   { "-background",      ".background",          XrmoptionSepArg, 0 },
368   { 0, 0, 0, 0 }
369 };
370
371 void
372 screenhack (Display *dpy, Window window)
373 {
374     init_pedal (dpy, window);
375     for (;;) {
376         pedal (dpy, window);
377         XSync(dpy, 0);
378         if (delay) sleep (delay);
379     }
380 }