6a2be2df46d0eb0a292a964a44f9deac43cdb13d
[xscreensaver] / hacks / cynosure.c
1 /* cynosure --- draw some rectangles
2  *
3  * 01-aug-96: written in Java by ozymandias G desiderata <ogd@organic.com>
4  * 25-dec-97: ported to C and XScreenSaver by Jamie Zawinski <jwz@netscape.com>
5  *
6  * Original version:
7  *   http://www.organic.com/staff/ogd/java/cynosure.html
8  *   http://www.organic.com/staff/ogd/java/source/cynosure/Cynosure-java.txt
9  *
10  * Original comments and copyright:
11  *
12  *   Cynosure.java
13  *   A Java implementation of Stephen Linhart's Cynosure screen-saver as a
14  *   drop-in class.
15  *
16  *   Header: /home/ogd/lib/cvs/aoaioxxysz/graphics/Cynosure.java,v 1.2 1996/08/02 02:41:21 ogd Exp 
17  *
18  *   ozymandias G desiderata <ogd@organic.com>
19  *   Thu Aug  1 1996
20  *
21  *   COPYRIGHT NOTICE
22  *
23  *   Copyright 1996 ozymandias G desiderata. Title, ownership rights, and
24  *   intellectual property rights in and to this software remain with
25  *   ozymandias G desiderata. This software may be copied, modified,
26  *   or used as long as this copyright is retained. Use this code at your
27  *   own risk.
28  *
29  *   Revision: 1.2 
30  *
31  *   Log: Cynosure.java,v 
32  *   Revision 1.2  1996/08/02 02:41:21  ogd
33  *   Added a few more comments, fixed messed-up header.
34  *
35  *   Revision 1.1.1.1  1996/08/02 02:30:45  ogd
36  *   First version
37  */
38
39 #include "screenhack.h"
40 static Display *dpy;
41 static Window window;
42 static XColor *colors;
43 static int ncolors;
44 static int fg_pixel, bg_pixel;
45 static GC fg_gc, bg_gc, shadow_gc;
46
47 static void paint(void);
48 static int genNewColor(void);
49 static int genConstrainedColor(int base, int tweak);
50 static int c_tweak(int base, int tweak);
51
52 /**
53  * The current color that is being tweaked to create the
54  * rectangles.
55  **/
56 static int curColor;
57
58 /**
59  * A variable used for the progression of the colors (yes, I know
60  * that's a lame explanation, but if your read the source, it should
61  * become obvious what I'm doing with this variable).
62  **/
63 static int curBase;
64
65 /**
66  * The width of the right and bottom edges of the rectangles.
67  **/
68 static int   shadowWidth;
69
70 /* The offset of the dropshadow beneath the rectangles. */
71 static int   elevation;
72
73 /**
74  * The approximate amount of time that will elapse before the base
75  * color is permanently changed.
76  *
77  * @see #tweak
78  **/
79 static int   sway;
80
81 /**
82  * The counter of time left until the base color value used. This class
83  * variable is necessary because Java doesn't support static method
84  * variables (grr grr).
85  **/
86 static int   timeLeft;
87
88 /**
89  * The amount by which the color of the polygons drawn will vary.
90  *
91  * @see #sway;
92  **/
93 static int   tweak;
94
95 /**
96  * The smallest size for an individual cell.
97  **/
98 #define MINCELLSIZE 16
99
100 /**
101  * The narrowest a rectangle can be.
102  **/
103 #define MINRECTSIZE 6
104
105 /**
106  * The size of the grid that the rectangles are placed within.
107  **/
108 static int gridSize;
109
110 /**
111  * Every so often genNewColor() generates a completely random
112  * color. This variable sets how frequently that happens. It's
113  * currently set to happen 1% of the time.
114  *
115  * @see #genNewColor
116  **/
117 #define THRESHOLD 100 /*0.01*/
118
119
120 char *progclass = "Cynosure";
121 char *defaults [] = {
122   "Cynosure.background: black",         /* to placate SGI */
123   "*delay:              500000",
124   "*colors:             128",
125   "*iterations:         100",
126   "*shadowWidth:        2",
127   "*elevation:          5",
128   "*sway:               30",
129   "*tweak:              20",
130   "*gridSize:           12",
131   0
132 };
133
134 XrmOptionDescRec options [] = {
135   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
136   { "-ncolors",         ".colors",      XrmoptionSepArg, 0 },
137   { "-iterations",      ".iterations",  XrmoptionSepArg, 0 },
138   { 0, 0, 0, 0 }
139 };
140
141
142 void screenhack(Display *d, Window w) 
143 {
144   XWindowAttributes xgwa;
145   XGCValues gcv;
146   int delay;
147   int i, iterations;
148
149   dpy = d;
150   window = w;
151
152   curColor    = 0;
153   curBase     = curColor;
154   shadowWidth = get_integer_resource ("shadowWidth", "Integer");
155   elevation   = get_integer_resource ("elevation", "Integer");
156   sway        = get_integer_resource ("sway", "Integer");
157   tweak       = get_integer_resource ("tweak", "Integer");
158   gridSize    = get_integer_resource ("gridSize", "Integer");
159   timeLeft    = 0;
160
161   XGetWindowAttributes (dpy, window, &xgwa);
162
163   ncolors = get_integer_resource ("colors", "Colors");
164   if (ncolors < 2) ncolors = 2;
165   if (ncolors <= 2) mono_p = True;
166
167   if (mono_p)
168     colors = 0;
169   else
170     colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
171
172   if (mono_p)
173     ;
174   else {
175     make_smooth_colormap (dpy, xgwa.visual, xgwa.colormap, colors, &ncolors,
176                           True, 0, True);
177     if (ncolors <= 2) {
178       mono_p = True;
179       ncolors = 2;
180       if (colors) free(colors);
181       colors = 0;
182     }
183   }
184
185   bg_pixel = get_pixel_resource("background", "Background", dpy,
186                                 xgwa.colormap);
187   fg_pixel = get_pixel_resource("foreground", "Foreground", dpy,
188                                 xgwa.colormap);
189
190   gcv.foreground = fg_pixel;
191   fg_gc = XCreateGC(dpy, window, GCForeground, &gcv);
192   gcv.foreground = bg_pixel;
193   bg_gc = XCreateGC(dpy, window, GCForeground, &gcv);
194
195   gcv.fill_style = FillStippled;
196   gcv.stipple = XCreateBitmapFromData(dpy, window, "\125\252", 8, 2);
197   shadow_gc = XCreateGC(dpy, window, GCForeground|GCFillStyle|GCStipple, &gcv);
198   XFreePixmap(dpy, gcv.stipple);
199
200   delay = get_integer_resource ("delay", "Delay");
201   iterations = get_integer_resource ("iterations", "Iterations");
202
203   while (1)
204     {
205       if (iterations > 0 && ++i >= iterations)
206         {
207           i = 0;
208           if (!mono_p)
209             XSetWindowBackground(dpy, window,
210                                  colors[random() % ncolors].pixel);
211           XClearWindow(dpy, window);
212         }
213       paint();
214       XSync(dpy, False);
215       if (delay)
216         usleep(delay);
217     }
218 }
219
220 /**
221  * paint adds a new layer of multicolored rectangles within a grid of
222  * randomly generated size. Each row of rectangles is the same color,
223  * but colors vary slightly from row to row. Each rectangle is placed
224  * within a regularly-sized cell, but each rectangle is sized and
225  * placed randomly within that cell.
226  *
227  * @param g      the Graphics coordinate in which to draw
228  * @see #genNewColor
229  **/
230 static void paint(void)
231 {
232     int i;
233     int cellsWide, cellsHigh, cellWidth, cellHeight;
234     static int width, height;
235     static int size_check = 1;
236
237     if (--size_check <= 0)
238       {
239         XWindowAttributes xgwa;
240         XGetWindowAttributes (dpy, window, &xgwa);
241         width = xgwa.width;
242         height = xgwa.height;
243         size_check = 1000;
244       }
245
246     /* How many cells wide the grid is (equal to gridSize +/- (gridSize / 2))
247      */
248     cellsWide  = c_tweak(gridSize, gridSize / 2);
249     /* How many cells high the grid is (equal to gridSize +/- (gridSize / 2))
250      */
251     cellsHigh  = c_tweak(gridSize, gridSize / 2);
252     /* How wide each cell in the grid is */
253     cellWidth  = width  / cellsWide;
254     /* How tall each cell in the grid is */
255     cellHeight = height / cellsHigh;
256
257     /* Ensure that each cell is above a certain minimum size */
258
259     if (cellWidth < MINCELLSIZE) {
260       cellWidth  = MINCELLSIZE;
261       cellsWide  = width / cellWidth;
262     }
263
264     if (cellHeight < MINCELLSIZE) {
265       cellHeight = MINCELLSIZE;
266       cellsHigh  = width / cellWidth;
267     }
268
269     /* fill the grid with randomly-generated cells */
270     for(i = 0; i < cellsHigh; i++) {
271       int j;
272
273       /* Each row is a different color, randomly generated (but constrained) */
274       if (!mono_p)
275         {
276           int c = genNewColor();
277           XSetForeground(dpy, fg_gc, colors[c].pixel);
278         }
279
280       for(j = 0; j < cellsWide; j++) {
281         int curWidth, curHeight, curX, curY;
282
283         /* Generate a random height for a rectangle and make sure that */
284         /* it's above a certain minimum size */
285         curHeight = random() % (cellHeight - shadowWidth);
286         if (curHeight < MINRECTSIZE)
287           curHeight = MINRECTSIZE;
288         /* Generate a random width for a rectangle and make sure that
289            it's above a certain minimum size */
290         curWidth  = random() % (cellWidth  - shadowWidth);
291         if (curWidth < MINRECTSIZE)
292           curWidth = MINRECTSIZE;
293         /* Figure out a random place to locate the rectangle within the
294            cell */
295         curY      = (i * cellHeight) + (random() % ((cellHeight - curHeight) -
296                                                     shadowWidth));
297         curX      = (j * cellWidth) +  (random() % ((cellWidth  - curWidth) -
298                                                     shadowWidth));
299
300         /* Draw the shadow */
301         if (elevation > 0)
302           XFillRectangle(dpy, window, shadow_gc,
303                          curX + elevation, curY + elevation,
304                          curWidth, curHeight);
305
306         /* Draw the edge */
307         if (shadowWidth > 0)
308           XFillRectangle(dpy, window, bg_gc,
309                          curX + shadowWidth, curY + shadowWidth,
310                          curWidth, curHeight);
311
312         XFillRectangle(dpy, window, fg_gc, curX, curY, curWidth, curHeight);
313
314         /* Draw a 1-pixel black border around the rectangle */
315         XDrawRectangle(dpy, window, bg_gc, curX, curY, curWidth, curHeight);
316       }
317
318     }
319 }
320
321
322 /**
323  * genNewColor returns a new color, gradually mutating the colors and
324  * occasionally returning a totally random color, just for variety.
325  *
326  * @return the new color
327  **/
328 static int genNewColor(void)
329 {
330     /* These lines handle "sway", or the gradual random changing of */
331     /* colors. After genNewColor() has been called a given number of */
332     /* times (specified by a random permutation of the tweak variable), */
333     /* take whatever color has been most recently randomly generated and */
334     /* make it the new base color. */
335     if (timeLeft == 0) {
336       timeLeft = c_tweak(sway, sway / 3);
337       curColor = curBase;
338     } else {
339       timeLeft--;
340     }
341      
342     /* If a randomly generated number is less than the threshold value,
343        produce a "sport" color value that is completely unrelated to the 
344        current palette. */
345     if (0 == (random() % THRESHOLD)) {
346       return (random() % ncolors);
347     } else {
348       curBase = genConstrainedColor(curColor, tweak);
349       return curBase;
350     }
351
352 }
353
354 /**
355  * genConstrainedColor creates a random new color within a certain
356  * range of an existing color. Right now this works with RGB color
357  * values, but a future version of the program will most likely use HSV
358  * colors, which should generate a more pleasing progression of values.
359  *
360  * @param base  the color on which the new color will be based
361  * @param tweak the amount that the new color can be tweaked
362  * @return a new constrained color
363  * @see #genNewColor
364  **/
365 static int genConstrainedColor(int base, int tweak) 
366 {
367     int i = 1 + (random() % tweak);
368     if (random() & 1)
369       i = -i;
370     i = (base + i) % ncolors;
371     while (i < 0)
372       i += ncolors;
373     return i;
374 }
375
376 /**
377  * Utility function to generate a tweaked color value
378  *
379  * @param  base   the byte value on which the color is based
380  * @param  tweak  the amount the value will be skewed
381  * @see    #tweak
382  * @return the tweaked byte
383  **/
384 static int c_tweak(int base, int tweak) 
385 {
386     int ranTweak = (random() % (2 * tweak));
387     int n = (base + (ranTweak - tweak));
388     if (n < 0) n = -n;
389     return (n < 255 ? n : 255);
390 }