9a4318b7cdce04b3e120891c3b51d5ac7ef8be43
[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->xgwa.screen, st->xgwa.visual, st->xgwa.colormap,
129                           st->colors, &st->ncolors,
130                           True, 0, True);
131     if (st->ncolors <= 2) {
132       mono_p = True;
133       st->ncolors = 2;
134       if (st->colors) free(st->colors);
135       st->colors = 0;
136     }
137   }
138
139   st->bg_pixel = get_pixel_resource(st->dpy,
140                                 st->xgwa.colormap, "background", "Background");
141   st->fg_pixel = get_pixel_resource(st->dpy,
142                                 st->xgwa.colormap, "foreground", "Foreground");
143
144   gcv.foreground = st->fg_pixel;
145   st->fg_gc = XCreateGC(st->dpy, st->window, GCForeground, &gcv);
146   gcv.foreground = st->bg_pixel;
147   st->bg_gc = XCreateGC(st->dpy, st->window, GCForeground, &gcv);
148
149 #ifdef DO_STIPPLE
150   gcv.fill_style = FillStippled;
151   gcv.stipple = XCreateBitmapFromData(st->dpy, st->window, "\125\252", 8, 2);
152   st->shadow_gc = XCreateGC(st->dpy, st->window, GCForeground|GCFillStyle|GCStipple, &gcv);
153   XFreePixmap(st->dpy, gcv.stipple);
154
155 #else /* !DO_STIPPLE */
156   st->shadow_gc = XCreateGC(st->dpy, st->window, GCForeground, &gcv);
157
158 #  ifdef HAVE_JWXYZ /* allow non-opaque alpha components in pixel values */
159   jwxyz_XSetAlphaAllowed (st->dpy, st->shadow_gc, True);
160 #  endif
161
162   if (st->colors)
163     {
164       int i;
165       st->ncolors2 = st->ncolors;
166       st->colors2 = (XColor *) malloc(sizeof(*st->colors2) * (st->ncolors2+1));
167
168       for (i = 0; i < st->ncolors2; i++)
169         {
170 #  ifdef HAVE_JWXYZ
171           /* give a non-opaque alpha to the shadow colors */
172           unsigned long pixel = st->colors[i].pixel;
173           unsigned long amask = BlackPixelOfScreen (st->xgwa.screen);
174           unsigned long a = (0x77777777 & amask);
175           pixel = (pixel & (~amask)) | a;
176           st->colors2[i].pixel = pixel;
177 #  else /* !HAVE_JWXYZ */
178           int h;
179           double s, v;
180           rgb_to_hsv (st->colors[i].red,
181                       st->colors[i].green,
182                       st->colors[i].blue,
183                       &h, &s, &v);
184           v *= 0.4;
185           hsv_to_rgb (h, s, v,
186                       &st->colors2[i].red,
187                       &st->colors2[i].green,
188                       &st->colors2[i].blue);
189           st->colors2[i].pixel = st->colors[i].pixel;
190           XAllocColor (st->dpy, st->xgwa.colormap, &st->colors2[i]);
191 #  endif /* !HAVE_JWXYZ */
192         }
193     }
194 # endif /* !DO_STIPPLE */
195
196   st->delay = get_integer_resource (st->dpy, "delay", "Delay");
197   st->iterations = get_integer_resource (st->dpy, "iterations", "Iterations");
198
199   return st;
200 }
201
202 static unsigned long
203 cynosure_draw (Display *dpy, Window window, void *closure)
204 {
205   struct state *st = (struct state *) closure;
206   if (st->iterations > 0 && ++st->i >= st->iterations)
207     {
208       st->i = 0;
209       if (!mono_p)
210         XSetWindowBackground(st->dpy, st->window,
211                              st->colors[random() % st->ncolors].pixel);
212       XClearWindow(st->dpy, st->window);
213     }
214   paint(st);
215
216   return st->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(struct state *st)
231 {
232     int i;
233     int cellsWide, cellsHigh, cellWidth, cellHeight;
234     int width = st->xgwa.width;
235     int height = st->xgwa.height;
236
237     /* How many cells wide the grid is (equal to gridSize +/- (gridSize / 2))
238      */
239     cellsWide  = c_tweak(st, st->gridSize, st->gridSize / 2);
240     /* How many cells high the grid is (equal to gridSize +/- (gridSize / 2))
241      */
242     cellsHigh  = c_tweak(st, st->gridSize, st->gridSize / 2);
243     /* How wide each cell in the grid is */
244     cellWidth  = width  / cellsWide;
245     /* How tall each cell in the grid is */
246     cellHeight = height / cellsHigh;
247
248     /* Ensure that each cell is above a certain minimum size */
249
250     if (cellWidth < MINCELLSIZE) {
251       cellWidth  = MINCELLSIZE;
252       cellsWide  = width / cellWidth;
253     }
254
255     if (cellHeight < MINCELLSIZE) {
256       cellHeight = MINCELLSIZE;
257       cellsHigh  = width / cellWidth;
258     }
259
260     /* fill the grid with randomly-generated cells */
261     for(i = 0; i < cellsHigh; i++) {
262       int j;
263
264       /* Each row is a different color, randomly generated (but constrained) */
265       if (!mono_p)
266         {
267           int c = genNewColor(st);
268           XSetForeground(st->dpy, st->fg_gc, st->colors[c].pixel);
269 # ifndef DO_STIPPLE
270           if (st->colors2)
271             XSetForeground(st->dpy, st->shadow_gc, st->colors2[c].pixel);
272 # endif
273         }
274
275       for(j = 0; j < cellsWide; j++) {
276         int curWidth, curHeight, curX, curY;
277
278         /* Generate a random height for a rectangle and make sure that */
279         /* it's above a certain minimum size */
280         curHeight = random() % (cellHeight - st->shadowWidth);
281         if (curHeight < MINRECTSIZE)
282           curHeight = MINRECTSIZE;
283         /* Generate a random width for a rectangle and make sure that
284            it's above a certain minimum size */
285         curWidth  = random() % (cellWidth  - st->shadowWidth);
286         if (curWidth < MINRECTSIZE)
287           curWidth = MINRECTSIZE;
288         /* Figure out a random place to locate the rectangle within the
289            cell */
290         curY      = (i * cellHeight) + (random() % ((cellHeight - curHeight) -
291                                                     st->shadowWidth));
292         curX      = (j * cellWidth) +  (random() % ((cellWidth  - curWidth) -
293                                                     st->shadowWidth));
294
295         /* Draw the shadow */
296         if (st->elevation > 0)
297           XFillRectangle(st->dpy, st->window, st->shadow_gc,
298                          curX + st->elevation, curY + st->elevation,
299                          curWidth, curHeight);
300
301         /* Draw the edge */
302         if (st->shadowWidth > 0)
303           XFillRectangle(st->dpy, st->window, st->bg_gc,
304                          curX + st->shadowWidth, curY + st->shadowWidth,
305                          curWidth, curHeight);
306
307         XFillRectangle(st->dpy, st->window, st->fg_gc, curX, curY, curWidth, curHeight);
308
309         /* Draw a 1-pixel black border around the rectangle */
310         XDrawRectangle(st->dpy, st->window, st->bg_gc, curX, curY, curWidth, curHeight);
311       }
312
313     }
314 }
315
316
317 /**
318  * genNewColor returns a new color, gradually mutating the colors and
319  * occasionally returning a totally random color, just for variety.
320  *
321  * @return the new color
322  **/
323 static int genNewColor(struct state *st)
324 {
325     /* These lines handle "sway", or the gradual random changing of */
326     /* colors. After genNewColor() has been called a given number of */
327     /* times (specified by a random permutation of the tweak variable), */
328     /* take whatever color has been most recently randomly generated and */
329     /* make it the new base color. */
330     if (st->timeLeft == 0) {
331       st->timeLeft = c_tweak(st, st->sway, st->sway / 3);
332       st->curColor = st->curBase;
333     } else {
334       st->timeLeft--;
335     }
336      
337     /* If a randomly generated number is less than the threshold value,
338        produce a "sport" color value that is completely unrelated to the 
339        current palette. */
340     if (0 == (random() % THRESHOLD)) {
341       return (random() % st->ncolors);
342     } else {
343       st->curBase = genConstrainedColor(st, st->curColor, st->tweak);
344       return st->curBase;
345     }
346
347 }
348
349 /**
350  * genConstrainedColor creates a random new color within a certain
351  * range of an existing color. Right now this works with RGB color
352  * values, but a future version of the program will most likely use HSV
353  * colors, which should generate a more pleasing progression of values.
354  *
355  * @param base  the color on which the new color will be based
356  * @param tweak the amount that the new color can be tweaked
357  * @return a new constrained color
358  * @see #genNewColor
359  **/
360 static int genConstrainedColor(struct state *st, int base, int tweak) 
361 {
362     int i = 1 + (random() % st->tweak);
363     if (random() & 1)
364       i = -i;
365     i = (base + i) % st->ncolors;
366     while (i < 0)
367       i += st->ncolors;
368     return i;
369 }
370
371 /**
372  * Utility function to generate a tweaked color value
373  *
374  * @param  base   the byte value on which the color is based
375  * @param  tweak  the amount the value will be skewed
376  * @see    #tweak
377  * @return the tweaked byte
378  **/
379 static int c_tweak(struct state *st, int base, int tweak) 
380 {
381     int ranTweak = (random() % (2 * tweak));
382     int n = (base + (ranTweak - tweak));
383     if (n < 0) n = -n;
384     return (n < 255 ? n : 255);
385 }
386
387 static void
388 cynosure_reshape (Display *dpy, Window window, void *closure, 
389                  unsigned int w, unsigned int h)
390 {
391   struct state *st = (struct state *) closure;
392   st->xgwa.width = w;
393   st->xgwa.height = h;
394 }
395
396 static Bool
397 cynosure_event (Display *dpy, Window window, void *closure, XEvent *event)
398 {
399   struct state *st = (struct state *) closure;
400   if (screenhack_event_helper (dpy, window, event))
401     {
402       st->i = st->iterations;
403       return True;
404     }
405   return False;
406 }
407
408 static void
409 cynosure_free (Display *dpy, Window window, void *closure)
410 {
411   struct state *st = (struct state *) closure;
412   free (st);
413 }
414
415
416 static const char *cynosure_defaults [] = {
417   ".background:         black",
418   ".foreground:         white",
419   "*fpsSolid:           true",
420   "*delay:              500000",
421   "*colors:             128",
422   "*iterations:         100",
423   "*shadowWidth:        2",
424   "*elevation:          5",
425   "*sway:               30",
426   "*tweak:              20",
427   "*gridSize:           12",
428 #ifdef HAVE_MOBILE
429   "*ignoreRotation:     True",
430 #endif
431   0
432 };
433
434 static XrmOptionDescRec cynosure_options [] = {
435   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
436   { "-ncolors",         ".colors",      XrmoptionSepArg, 0 },
437   { "-iterations",      ".iterations",  XrmoptionSepArg, 0 },
438   { 0, 0, 0, 0 }
439 };
440
441
442 XSCREENSAVER_MODULE ("Cynosure", cynosure)