baf3be43562d6e8c0d1faad58ad6df254ac1d751
[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@jwz.org>
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
41 /* #define DO_STIPPLE */
42
43 struct state {
44   Display *dpy;
45   Window window;
46
47   XColor *colors;
48   int ncolors;
49
50 #ifndef DO_STIPPLE
51   XColor *colors2;
52   int ncolors2;
53 #endif
54
55   int fg_pixel, bg_pixel;
56   GC fg_gc, bg_gc, shadow_gc;
57
58   int curColor;
59   int curBase;  /* color progression */
60   int   shadowWidth;
61   int   elevation;      /* offset of dropshadow */
62   int   sway;   /* time until base color changed */
63   int   timeLeft;       /* until base color used */
64   int   tweak;  /* amount of color variance */
65   int gridSize;
66   int iterations, i, delay;
67   XWindowAttributes xgwa;
68 };
69
70
71 /**
72  * The smallest size for an individual cell.
73  **/
74 #define MINCELLSIZE 16
75
76 /**
77  * The narrowest a rectangle can be.
78  **/
79 #define MINRECTSIZE 6
80
81 /**
82  * Every so often genNewColor() generates a completely random
83  * color. This variable sets how frequently that happens. It's
84  * currently set to happen 1% of the time.
85  *
86  * @see #genNewColor
87  **/
88 #define THRESHOLD 100 /*0.01*/
89
90 static void paint(struct state *st);
91 static int genNewColor(struct state *st);
92 static int genConstrainedColor(struct state *st, int base, int tweak);
93 static int c_tweak(struct state *st, int base, int tweak);
94
95
96 static void *
97 cynosure_init (Display *d, Window w)
98 {
99   struct state *st = (struct state *) calloc (1, sizeof(*st));
100   XGCValues gcv;
101
102   st->dpy = d;
103   st->window = w;
104
105   st->curColor    = 0;
106   st->curBase     = st->curColor;
107   st->shadowWidth = get_integer_resource (st->dpy, "shadowWidth", "Integer");
108   st->elevation   = get_integer_resource (st->dpy, "elevation", "Integer");
109   st->sway        = get_integer_resource (st->dpy, "sway", "Integer");
110   st->tweak       = get_integer_resource (st->dpy, "tweak", "Integer");
111   st->gridSize    = get_integer_resource (st->dpy, "gridSize", "Integer");
112   st->timeLeft    = 0;
113
114   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
115
116   st->ncolors = get_integer_resource (st->dpy, "colors", "Colors");
117   if (st->ncolors < 2) st->ncolors = 2;
118   if (st->ncolors <= 2) mono_p = True;
119
120   if (mono_p)
121     st->colors = 0;
122   else
123     st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
124
125   if (mono_p)
126     ;
127   else {
128     make_smooth_colormap (st->dpy, st->xgwa.visual, st->xgwa.colormap, st->colors, &st->ncolors,
129                           True, 0, True);
130     if (st->ncolors <= 2) {
131       mono_p = True;
132       st->ncolors = 2;
133       if (st->colors) free(st->colors);
134       st->colors = 0;
135     }
136   }
137
138   st->bg_pixel = get_pixel_resource(st->dpy,
139                                 st->xgwa.colormap, "background", "Background");
140   st->fg_pixel = get_pixel_resource(st->dpy,
141                                 st->xgwa.colormap, "foreground", "Foreground");
142
143   gcv.foreground = st->fg_pixel;
144   st->fg_gc = XCreateGC(st->dpy, st->window, GCForeground, &gcv);
145   gcv.foreground = st->bg_pixel;
146   st->bg_gc = XCreateGC(st->dpy, st->window, GCForeground, &gcv);
147
148 #ifdef DO_STIPPLE
149   gcv.fill_style = FillStippled;
150   gcv.stipple = XCreateBitmapFromData(st->dpy, st->window, "\125\252", 8, 2);
151   st->shadow_gc = XCreateGC(st->dpy, st->window, GCForeground|GCFillStyle|GCStipple, &gcv);
152   XFreePixmap(st->dpy, gcv.stipple);
153
154 #else /* !DO_STIPPLE */
155   st->shadow_gc = XCreateGC(st->dpy, st->window, GCForeground, &gcv);
156
157 #  ifdef HAVE_COCOA /* allow non-opaque alpha components in pixel values */
158   jwxyz_XSetAlphaAllowed (st->dpy, st->shadow_gc, True);
159 #  endif
160
161   if (st->colors)
162     {
163       int i;
164       st->ncolors2 = st->ncolors;
165       st->colors2 = (XColor *) malloc(sizeof(*st->colors2) * (st->ncolors2+1));
166
167       for (i = 0; i < st->ncolors2; i++)
168         {
169 #  ifdef HAVE_COCOA
170           /* give a non-opaque alpha to the shadow colors */
171           unsigned long pixel = st->colors[i].pixel;
172           unsigned long amask = BlackPixelOfScreen (st->xgwa.screen);
173           unsigned long a = (0x77777777 & amask);
174           pixel = (pixel & (~amask)) | a;
175           st->colors2[i].pixel = pixel;
176 #  else /* !HAVE_COCOA */
177           int h;
178           double s, v;
179           rgb_to_hsv (st->colors[i].red,
180                       st->colors[i].green,
181                       st->colors[i].blue,
182                       &h, &s, &v);
183           v *= 0.4;
184           hsv_to_rgb (h, s, v,
185                       &st->colors2[i].red,
186                       &st->colors2[i].green,
187                       &st->colors2[i].blue);
188           st->colors2[i].pixel = st->colors[i].pixel;
189           XAllocColor (st->dpy, st->xgwa.colormap, &st->colors2[i]);
190 #  endif /* !HAVE_COCOA */
191         }
192     }
193 # endif /* !DO_STIPPLE */
194
195   st->delay = get_integer_resource (st->dpy, "delay", "Delay");
196   st->iterations = get_integer_resource (st->dpy, "iterations", "Iterations");
197
198   return st;
199 }
200
201 static unsigned long
202 cynosure_draw (Display *dpy, Window window, void *closure)
203 {
204   struct state *st = (struct state *) closure;
205   if (st->iterations > 0 && ++st->i >= st->iterations)
206     {
207       st->i = 0;
208       if (!mono_p)
209         XSetWindowBackground(st->dpy, st->window,
210                              st->colors[random() % st->ncolors].pixel);
211       XClearWindow(st->dpy, st->window);
212     }
213   paint(st);
214
215   return st->delay;
216 }
217
218
219 /**
220  * paint adds a new layer of multicolored rectangles within a grid of
221  * randomly generated size. Each row of rectangles is the same color,
222  * but colors vary slightly from row to row. Each rectangle is placed
223  * within a regularly-sized cell, but each rectangle is sized and
224  * placed randomly within that cell.
225  *
226  * @param g      the Graphics coordinate in which to draw
227  * @see #genNewColor
228  **/
229 static void paint(struct state *st)
230 {
231     int i;
232     int cellsWide, cellsHigh, cellWidth, cellHeight;
233     int width = st->xgwa.width;
234     int height = st->xgwa.height;
235
236     /* How many cells wide the grid is (equal to gridSize +/- (gridSize / 2))
237      */
238     cellsWide  = c_tweak(st, st->gridSize, st->gridSize / 2);
239     /* How many cells high the grid is (equal to gridSize +/- (gridSize / 2))
240      */
241     cellsHigh  = c_tweak(st, st->gridSize, st->gridSize / 2);
242     /* How wide each cell in the grid is */
243     cellWidth  = width  / cellsWide;
244     /* How tall each cell in the grid is */
245     cellHeight = height / cellsHigh;
246
247     /* Ensure that each cell is above a certain minimum size */
248
249     if (cellWidth < MINCELLSIZE) {
250       cellWidth  = MINCELLSIZE;
251       cellsWide  = width / cellWidth;
252     }
253
254     if (cellHeight < MINCELLSIZE) {
255       cellHeight = MINCELLSIZE;
256       cellsHigh  = width / cellWidth;
257     }
258
259     /* fill the grid with randomly-generated cells */
260     for(i = 0; i < cellsHigh; i++) {
261       int j;
262
263       /* Each row is a different color, randomly generated (but constrained) */
264       if (!mono_p)
265         {
266           int c = genNewColor(st);
267           XSetForeground(st->dpy, st->fg_gc, st->colors[c].pixel);
268 # ifndef DO_STIPPLE
269           if (st->colors2)
270             XSetForeground(st->dpy, st->shadow_gc, st->colors2[c].pixel);
271 # endif
272         }
273
274       for(j = 0; j < cellsWide; j++) {
275         int curWidth, curHeight, curX, curY;
276
277         /* Generate a random height for a rectangle and make sure that */
278         /* it's above a certain minimum size */
279         curHeight = random() % (cellHeight - st->shadowWidth);
280         if (curHeight < MINRECTSIZE)
281           curHeight = MINRECTSIZE;
282         /* Generate a random width for a rectangle and make sure that
283            it's above a certain minimum size */
284         curWidth  = random() % (cellWidth  - st->shadowWidth);
285         if (curWidth < MINRECTSIZE)
286           curWidth = MINRECTSIZE;
287         /* Figure out a random place to locate the rectangle within the
288            cell */
289         curY      = (i * cellHeight) + (random() % ((cellHeight - curHeight) -
290                                                     st->shadowWidth));
291         curX      = (j * cellWidth) +  (random() % ((cellWidth  - curWidth) -
292                                                     st->shadowWidth));
293
294         /* Draw the shadow */
295         if (st->elevation > 0)
296           XFillRectangle(st->dpy, st->window, st->shadow_gc,
297                          curX + st->elevation, curY + st->elevation,
298                          curWidth, curHeight);
299
300         /* Draw the edge */
301         if (st->shadowWidth > 0)
302           XFillRectangle(st->dpy, st->window, st->bg_gc,
303                          curX + st->shadowWidth, curY + st->shadowWidth,
304                          curWidth, curHeight);
305
306         XFillRectangle(st->dpy, st->window, st->fg_gc, curX, curY, curWidth, curHeight);
307
308         /* Draw a 1-pixel black border around the rectangle */
309         XDrawRectangle(st->dpy, st->window, st->bg_gc, curX, curY, curWidth, curHeight);
310       }
311
312     }
313 }
314
315
316 /**
317  * genNewColor returns a new color, gradually mutating the colors and
318  * occasionally returning a totally random color, just for variety.
319  *
320  * @return the new color
321  **/
322 static int genNewColor(struct state *st)
323 {
324     /* These lines handle "sway", or the gradual random changing of */
325     /* colors. After genNewColor() has been called a given number of */
326     /* times (specified by a random permutation of the tweak variable), */
327     /* take whatever color has been most recently randomly generated and */
328     /* make it the new base color. */
329     if (st->timeLeft == 0) {
330       st->timeLeft = c_tweak(st, st->sway, st->sway / 3);
331       st->curColor = st->curBase;
332     } else {
333       st->timeLeft--;
334     }
335      
336     /* If a randomly generated number is less than the threshold value,
337        produce a "sport" color value that is completely unrelated to the 
338        current palette. */
339     if (0 == (random() % THRESHOLD)) {
340       return (random() % st->ncolors);
341     } else {
342       st->curBase = genConstrainedColor(st, st->curColor, st->tweak);
343       return st->curBase;
344     }
345
346 }
347
348 /**
349  * genConstrainedColor creates a random new color within a certain
350  * range of an existing color. Right now this works with RGB color
351  * values, but a future version of the program will most likely use HSV
352  * colors, which should generate a more pleasing progression of values.
353  *
354  * @param base  the color on which the new color will be based
355  * @param tweak the amount that the new color can be tweaked
356  * @return a new constrained color
357  * @see #genNewColor
358  **/
359 static int genConstrainedColor(struct state *st, int base, int tweak) 
360 {
361     int i = 1 + (random() % st->tweak);
362     if (random() & 1)
363       i = -i;
364     i = (base + i) % st->ncolors;
365     while (i < 0)
366       i += st->ncolors;
367     return i;
368 }
369
370 /**
371  * Utility function to generate a tweaked color value
372  *
373  * @param  base   the byte value on which the color is based
374  * @param  tweak  the amount the value will be skewed
375  * @see    #tweak
376  * @return the tweaked byte
377  **/
378 static int c_tweak(struct state *st, int base, int tweak) 
379 {
380     int ranTweak = (random() % (2 * tweak));
381     int n = (base + (ranTweak - tweak));
382     if (n < 0) n = -n;
383     return (n < 255 ? n : 255);
384 }
385
386 static void
387 cynosure_reshape (Display *dpy, Window window, void *closure, 
388                  unsigned int w, unsigned int h)
389 {
390   struct state *st = (struct state *) closure;
391   st->xgwa.width = w;
392   st->xgwa.height = h;
393 }
394
395 static Bool
396 cynosure_event (Display *dpy, Window window, void *closure, XEvent *event)
397 {
398   return False;
399 }
400
401 static void
402 cynosure_free (Display *dpy, Window window, void *closure)
403 {
404   struct state *st = (struct state *) closure;
405   free (st);
406 }
407
408
409 static const char *cynosure_defaults [] = {
410   ".background:         black",
411   ".foreground:         white",
412   "*fpsSolid:           true",
413   "*delay:              500000",
414   "*colors:             128",
415   "*iterations:         100",
416   "*shadowWidth:        2",
417   "*elevation:          5",
418   "*sway:               30",
419   "*tweak:              20",
420   "*gridSize:           12",
421   0
422 };
423
424 static XrmOptionDescRec cynosure_options [] = {
425   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
426   { "-ncolors",         ".colors",      XrmoptionSepArg, 0 },
427   { "-iterations",      ".iterations",  XrmoptionSepArg, 0 },
428   { 0, 0, 0, 0 }
429 };
430
431
432 XSCREENSAVER_MODULE ("Cynosure", cynosure)