69085525a029f9ece95185a471c761628e6b7a95
[xscreensaver] / hacks / deco.c
1 /* xscreensaver, Copyright (c) 1997, 1998, 2002, 2006
2  *  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  * Concept snarfed from Michael D. Bayne in
13  * http://www.go2net.com/internet/deep/1997/04/16/body.html
14  *
15  * Changes by Lars Huttar, http://www.huttar.net:
16  * - allow use of golden ratio for dividing rectangles instead of 1/2.
17  * - allow smooth colors instead of random
18  * - added line thickness setting
19  * - added "Mondrian" mode
20  * Other ideas:
21  * - allow recomputing the colormap on each new frame (especially useful
22  *   when ncolors is low)
23  */
24
25 #include "screenhack.h"
26 #include <stdio.h>
27
28 struct state {
29   XColor colors[255];
30   int ncolors;
31   int max_depth;
32   int min_height;
33   int min_width;
34   int line_width;
35   int old_line_width;
36   Bool goldenRatio;
37   Bool mondrian;
38   Bool smoothColors;
39
40   int delay;
41   XWindowAttributes xgwa;
42   GC fgc, bgc;
43   int current_color;
44 };
45
46 /* Golden Ratio
47  *   Suppose you're dividing a rectangle of length A+B
48  *   into two parts, of length A and B respectively. You want the ratio of
49  *   A to B to be the same as the ratio of the whole (A+B) to A. The golden
50  *   ratio (phi) is that ratio. Supposed to be visually pleasing. */
51 #define PHI 1.61803
52 #define PHI1 (1.0/PHI)
53 #define PHI2 (1.0 - PHI1)
54
55 /* copied from make_random_colormap in colors.c */
56 static void
57 make_mondrian_colormap (Display *dpy, Visual *visual, Colormap cmap,
58                       XColor *colors, int *ncolorsP,
59                       Bool allocate_p,
60                       Bool *writable_pP,
61                       Bool verbose_p)
62 {
63   Bool wanted_writable = (allocate_p && writable_pP && *writable_pP);
64   int ncolors = 8;
65   int i;
66   Screen *screen = (dpy ? DefaultScreenOfDisplay(dpy) : 0); /* #### WRONG! */
67
68   if (*ncolorsP <= 0) return;
69
70   /* If this visual doesn't support writable cells, don't bother trying. */
71   if (wanted_writable && !has_writable_cells(screen, visual))
72     *writable_pP = False;
73
74   for (i = 0; i < ncolors; i++)
75   {
76       colors[i].flags = DoRed|DoGreen|DoBlue;
77       colors[i].red = 0;
78       colors[i].green = 0;
79       colors[i].blue = 0;
80
81       switch(i) {
82           case 0: case 1: case 2: case 3: case 7: /* white */
83               colors[i].red = 0xE800;
84               colors[i].green = 0xE800;
85               colors[i].blue  = 0xE800;
86               break;
87           case 4:
88               colors[i].red = 0xCFFF; break; /* red */
89           case 5:
90               colors[i].red = 0x2000;
91               colors[i].blue = 0xCFFF; break; /* blue */
92           case 6:
93               colors[i].red = 0xDFFF; /* yellow */
94               colors[i].green = 0xCFFF; break;
95       }
96   }
97
98   if (!allocate_p)
99     return;
100
101  RETRY_NON_WRITABLE:
102   if (writable_pP && *writable_pP)
103     {
104       unsigned long *pixels = (unsigned long *)
105         malloc(sizeof(*pixels) * (ncolors + 1));
106
107       allocate_writable_colors (dpy, cmap, pixels, &ncolors);
108       if (ncolors > 0)
109         for (i = 0; i < ncolors; i++)
110           colors[i].pixel = pixels[i];
111       free (pixels);
112       if (ncolors > 0)
113         XStoreColors (dpy, cmap, colors, ncolors);
114     }
115   else
116     {
117       for (i = 0; i < ncolors; i++)
118         {
119           XColor color;
120           color = colors[i];
121           if (!XAllocColor (dpy, cmap, &color))
122             break;
123           colors[i].pixel = color.pixel;
124         }
125       ncolors = i;
126     }
127
128   /* If we tried for writable cells and got none, try for non-writable. */
129   if (allocate_p && ncolors == 0 && writable_pP && *writable_pP)
130     {
131       ncolors = *ncolorsP;
132       *writable_pP = False;
133       goto RETRY_NON_WRITABLE;
134     }
135
136 #if 0
137   /* I don't think we need to bother copying or linking to the complain
138      function. */
139   if (verbose_p)
140     complain(*ncolorsP, ncolors, wanted_writable,
141              wanted_writable && *writable_pP);
142 #endif
143
144   *ncolorsP = ncolors;
145 }
146
147 static void
148 mondrian_set_sizes (struct state *st, int w, int h)
149 {
150     if (w > h) {
151         st->line_width = w/50;
152         st->min_height = st->min_width = w/8;
153     } else {
154         st->line_width = h/50;
155         st->min_height = st->min_width = h/8;
156     }
157 }
158  
159 static void
160 deco (Display *dpy, Window window, struct state *st,
161       int x, int y, int w, int h, int depth)
162 {
163   if (((random() % st->max_depth) < depth) || (w < st->min_width) || (h < st->min_height))
164     {
165       if (!mono_p)
166         {
167           if (++st->current_color >= st->ncolors)
168             st->current_color = 0;
169           XSetForeground(dpy, st->bgc, st->colors[st->current_color].pixel);
170         }
171       XFillRectangle (dpy, window, st->bgc, x, y, w, h);
172       XDrawRectangle (dpy, window, st->fgc, x, y, w, h);
173     }
174   else
175     {
176       if ((st->goldenRatio || st->mondrian) ? (w > h) : (random() & 1))
177         { /* Divide the rectangle side-by-side */
178           int wnew = (st->goldenRatio ? (w * (random() & 1 ? PHI1 : PHI2)) : w/2);
179           deco (dpy, window, st, x, y, wnew, h, depth+1);
180           deco (dpy, window, st, x+wnew, y, w-wnew, h, depth+1);
181         }
182       else
183         { /* Divide the rectangle top-to-bottom */
184           int hnew = (st->goldenRatio ? (h * (random() & 1 ? PHI1 : PHI2)) : h/2);
185           deco (dpy, window, st, x, y, w, hnew, depth+1);
186           deco (dpy, window, st, x, y+hnew, w, h-hnew, depth+1);
187         }
188     }
189 }
190
191 static void *
192 deco_init (Display *dpy, Window window)
193 {
194   struct state *st = (struct state *) calloc (1, sizeof(*st));
195   XGCValues gcv;
196
197   st->delay = get_integer_resource (dpy, "delay", "Integer");
198
199   st->smoothColors = get_boolean_resource(dpy, "smoothColors", "Boolean");
200   st->old_line_width = 1;
201
202   st->goldenRatio = get_boolean_resource (dpy, "goldenRatio", "Boolean");
203
204   st->max_depth = get_integer_resource (dpy, "maxDepth", "Integer");
205   if (st->max_depth < 1) st->max_depth = 1;
206   else if (st->max_depth > 1000) st->max_depth = 1000;
207
208   st->min_width = get_integer_resource (dpy, "minWidth", "Integer");
209   if (st->min_width < 2) st->min_width = 2;
210   st->min_height = get_integer_resource (dpy, "minHeight", "Integer");
211   if (st->min_height < 2) st->min_height = 2;
212
213   st->line_width = get_integer_resource (dpy, "lineWidth", "Integer");
214
215   XGetWindowAttributes (dpy, window, &st->xgwa);
216
217   st->ncolors = get_integer_resource (dpy, "ncolors", "Integer");
218
219   gcv.foreground = get_pixel_resource(dpy, st->xgwa.colormap,
220                                       "foreground", "Foreground");
221   st->fgc = XCreateGC (dpy, window, GCForeground, &gcv);
222
223   gcv.foreground = get_pixel_resource(dpy, st->xgwa.colormap,
224                                       "background", "Background");
225   st->bgc = XCreateGC (dpy, window, GCForeground, &gcv);
226
227   if (st->ncolors <= 2)
228     mono_p = True;
229
230   if (!mono_p)
231     {
232       GC tmp = st->fgc;
233       st->fgc = st->bgc;
234       st->bgc = tmp;
235     }
236
237   st->mondrian = get_boolean_resource(dpy, "mondrian", "Boolean");
238   if (st->mondrian) {
239       /* Mondrian, if true, overrides several other options. */
240       mondrian_set_sizes(st, st->xgwa.width, st->xgwa.height);
241
242       /** set up red-yellow-blue-black-white colormap and fgc **/
243       make_mondrian_colormap(dpy, st->xgwa.visual, st->xgwa.colormap,
244                              st->colors, &st->ncolors, True, 0, True);
245
246       /** put white in several cells **/
247       /** set min-height and min-width to about 10% of total w/h **/
248   }
249   else if (st->smoothColors)
250       make_smooth_colormap (dpy, st->xgwa.visual, st->xgwa.colormap,
251                             st->colors, &st->ncolors, True, 0, True);
252   else
253       make_random_colormap (dpy, st->xgwa.visual, st->xgwa.colormap,
254                             st->colors, &st->ncolors, False, True, 0, True);
255
256   gcv.line_width = st->old_line_width = st->line_width;
257   XChangeGC(dpy, st->fgc, GCLineWidth, &gcv);
258
259   return st;
260 }
261
262 static unsigned long
263 deco_draw (Display *dpy, Window window, void *closure)
264 {
265   struct state *st = (struct state *) closure;
266   XFillRectangle (dpy, window, st->bgc, 0, 0, st->xgwa.width, st->xgwa.height);
267   if (st->mondrian) {
268       mondrian_set_sizes(st, st->xgwa.width, st->xgwa.height);
269       if (st->line_width != st->old_line_width) {
270           XSetLineAttributes(dpy, st->fgc, st->line_width,
271                              LineSolid, CapButt, JoinBevel);
272           st->old_line_width = st->line_width;
273       }
274   }
275   deco (dpy, window, st, 0, 0, st->xgwa.width, st->xgwa.height, 0);
276   return 1000000 * st->delay;
277 }
278
279 static void
280 deco_reshape (Display *dpy, Window window, void *closure, 
281                  unsigned int w, unsigned int h)
282 {
283   struct state *st = (struct state *) closure;
284   st->xgwa.width = w;
285   st->xgwa.height = h;
286 }
287
288 static Bool
289 deco_event (Display *dpy, Window window, void *closure, XEvent *event)
290 {
291   return False;
292 }
293
294 static void
295 deco_free (Display *dpy, Window window, void *closure)
296 {
297   struct state *st = (struct state *) closure;
298   free (st);
299 }
300
301 \f
302 static const char *deco_defaults [] = {
303   ".background:         black",
304   ".foreground:         white",
305   "*maxDepth:           12",
306   "*minWidth:           20",
307   "*minHeight:          20",
308   "*lineWidth:          1",
309   "*delay:              5",
310   "*ncolors:            64",
311   "*goldenRatio:        False",
312   "*smoothColors:       False",
313   "*mondrian:           False",
314   0
315 };
316
317 static XrmOptionDescRec deco_options [] = {
318   { "-max-depth",       ".maxDepth",    XrmoptionSepArg, 0 },
319   { "-min-width",       ".minWidth",    XrmoptionSepArg, 0 },
320   { "-min-height",      ".minHeight",   XrmoptionSepArg, 0 },
321   { "-line-width",      ".lineWidth",   XrmoptionSepArg, 0 },
322   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
323   { "-ncolors",         ".ncolors",     XrmoptionSepArg, 0 },
324   { "-golden-ratio",    ".goldenRatio", XrmoptionNoArg, "True" },
325   { "-no-golden-ratio", ".goldenRatio", XrmoptionNoArg, "False" },
326   { "-smooth-colors",   ".smoothColors",XrmoptionNoArg, "True" },
327   { "-no-smooth-colors",".smoothColors",XrmoptionNoArg, "False" },
328   { "-mondrian",        ".mondrian",    XrmoptionNoArg, "True" },
329   { "-no-mondrian",     ".mondrian",    XrmoptionNoArg, "False" },
330   { 0, 0, 0, 0 }
331 };
332
333 XSCREENSAVER_MODULE ("Deco", deco)