cb88c10a6cc6ea8bc0420b1418c81dd4d742f885
[xscreensaver] / hacks / imsmap.c
1 /* imsmap, Copyright (c) 1992 Juergen Nickelsen <nickel@cs.tu-berlin.de>
2  * Derived from code by Markus Schirmer, TU Berlin.
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  * Revision History:
13  * 24-aug-92: jwz: hacked.
14  * 17-May-97: jwz: hacked more.
15  */
16
17 #include <stdio.h>
18 #include <math.h>
19 #include <sys/time.h> /* for gettimeofday() */
20
21 #include <X11/Xlib.h>
22 #include <X11/Xutil.h>
23
24 #include "screenhack.h"
25
26 #define NSTEPS 7
27 #define COUNT (1 << NSTEPS)
28 #define CELL(c, r) cell[((unsigned int)(c)) + ((unsigned int) (r)) * xmax]
29
30 static enum imsmap_mode { MODE_H, MODE_S, MODE_V, MODE_RANDOM } mode;
31
32 static GC gc, gc2;
33 static XWindowAttributes xgwa;
34
35 #if defined(sun) && !__STDC__   /* sun cc doesn't know "signed char" */
36 #define signed /**/
37 #endif
38
39 static Colormap cmap;
40 static int ncolors;
41 static XColor *colors;
42 static Bool cycle_p;
43 static int cycle_direction;
44 static Bool extra_krinkly_p;
45
46 static int delay, cycle_delay;
47 static signed char *cell = NULL;
48 static int xmax, ymax;
49 static int iterations;
50
51 static void
52 init_map (Display *dpy, Window window)
53 {
54   unsigned long fg_pixel = 0, bg_pixel = 0;
55   int fg_h, bg_h;
56   double fg_s, fg_v, bg_s, bg_v;
57
58   enum imsmap_mode this_mode;
59   static Bool rv_p;
60     
61   XGCValues gcv;
62
63   XGetWindowAttributes (dpy, window, &xgwa);
64   cmap = xgwa.colormap;
65
66   if (!ncolors)
67     {
68       char *mode_str = get_string_resource ("mode", "Mode");
69       rv_p = get_boolean_resource ("reverseVideo", "ReverseVideo");
70       cycle_p = get_boolean_resource ("cycle", "Cycle");
71       ncolors = get_integer_resource ("ncolors", "Integer");
72       delay = get_integer_resource ("delay", "Integer");
73       cycle_delay = get_integer_resource ("cycleDelay", "Integer");
74       iterations = get_integer_resource ("iterations", "Integer");
75       if (iterations < 0) iterations = 0;
76       else if (iterations > 7) iterations = 7;
77
78       if (ncolors <= 2) ncolors = 0;
79       if (ncolors == 0) mono_p = True;
80       if (ncolors > 255) ncolors = 255;  /* too many look bad */
81
82       fg_pixel = get_pixel_resource ("background", "Background", dpy, cmap);
83       bg_pixel = get_pixel_resource ("foreground", "Foreground", dpy, cmap);
84
85       if (fg_pixel == bg_pixel)
86         {
87           XColor black, white;
88           black.red = black.green = black.blue = 0;
89           white.red = white.green = white.blue = 0xFFFF;
90           black.flags = white.flags = DoRed|DoGreen|DoBlue;
91           XAllocColor(dpy, cmap, &black);
92           XAllocColor(dpy, cmap, &white);
93           if (bg_pixel == black.pixel)
94             fg_pixel = white.pixel;
95           else
96             fg_pixel = black.pixel;
97         }
98
99       if (mono_p) cycle_p = False;
100
101       gcv.foreground = fg_pixel;
102       gcv.background = bg_pixel;
103       gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
104       gcv.foreground = bg_pixel;
105       gc2 = XCreateGC (dpy, window, GCForeground, &gcv);
106
107       if (!mode_str || !strcmp (mode_str, "random"))
108         mode = MODE_RANDOM;
109       else if (!strcmp (mode_str, "h") || !strcmp (mode_str, "hue"))
110         mode = MODE_H;
111       else if (!strcmp (mode_str, "s") || !strcmp (mode_str, "saturation"))
112         mode = MODE_S;
113       else if (!strcmp (mode_str, "v") || !strcmp (mode_str, "value"))
114         mode = MODE_V;
115       else
116         {
117           fprintf (stderr,
118            "%s: mode must be hue, saturation, value, or random, not \"%s\"\n",
119                    progname, mode_str);
120           mode = MODE_RANDOM;
121         }
122     }
123
124   this_mode = mode;
125   if (!mono_p && mode == MODE_RANDOM)
126     switch (random () % 6) {
127     case 0: this_mode = MODE_H; break;
128     case 1: this_mode = MODE_S; break;
129     case 2: this_mode = MODE_V; break;
130     default: break;
131     }
132
133   if (mono_p)
134     extra_krinkly_p = !(random() % 15);
135   else
136     extra_krinkly_p = !(random() % 5);
137
138   if (!mono_p)
139     {
140       double distance, fg_H, bg_H, dh;
141
142     RETRY:
143       fg_h = random() % 360;
144       fg_s = frand(1.0);
145       fg_v = frand(1.0);
146
147       bg_h = fg_h;
148       bg_s = fg_s;
149       bg_v = fg_v;
150
151       switch (this_mode)
152         {
153         case MODE_H:
154           bg_h = random() % 360;
155           if (fg_v < 0.4)
156             goto RETRY;
157           distance = fg_h - bg_h;
158           if (distance < 0)
159             distance = -distance;
160           if (distance > 360)
161             distance = 180 - (distance - 180);
162           if (distance < 30)
163             goto RETRY;
164           break;
165
166         case MODE_S:
167           bg_s = frand(1.0);
168           if (fg_v < 0.4)
169             goto RETRY;
170           distance = fg_s - bg_s;
171           if (distance < 0)
172             distance = -distance;
173           if (distance < 0.2)
174             goto RETRY;
175           break;
176
177         case MODE_V:
178           bg_v = frand(1.0);
179           distance = fg_v - bg_v;
180           if (distance < 0)
181             distance = -distance;
182           if (distance < 0.4)
183             goto RETRY;
184           break;
185
186         default:
187           bg_h = random() % 360;
188           bg_s = frand(1.0);
189           bg_v = frand(1.0);
190
191           fg_H = ((double) fg_h) / 360;
192           bg_H = ((double) bg_h) / 360;
193           dh = fg_H - bg_H;
194           if (dh < 0) dh = -dh;
195           if (dh > 0.5) dh = 0.5 - (dh - 0.5);
196           distance = sqrt ((dh * dh) +
197                            ((fg_s - bg_s) * (fg_s - bg_s)) +
198                            ((fg_v - bg_v) * (fg_v - bg_v)));
199           if (distance < 0.2)
200             goto RETRY;
201         }
202
203       cycle_p = True;
204       if (colors)
205         free_colors (dpy, cmap, colors, ncolors);
206       else
207         colors = (XColor *) malloc (ncolors * sizeof(*colors));
208
209       cycle_direction = (random() & 1 ? 1 : -1);
210
211     RETRY_NON_WRITABLE:
212       {
213         int n = ncolors;
214         make_color_ramp (dpy, cmap,
215                          fg_h, fg_s, fg_v,
216                          bg_h, bg_s, bg_v,
217                          colors, &n,
218                          True, True, cycle_p);
219         if (n == 0 && cycle_p)
220           {
221             cycle_p = False;
222             goto RETRY_NON_WRITABLE;
223           }
224         ncolors = n;
225       }
226
227       if (ncolors <= 0)
228         mono_p = 1;
229     }
230
231   if (mono_p)
232     {
233       static Bool done = False;
234       static XColor c[50];
235           colors = c;
236       cycle_p = False;
237       ncolors = sizeof(c)/sizeof(*c);
238       if (!done)
239         {
240           int i;
241           done = True;
242           colors[0].pixel = fg_pixel;
243           for (i = 1; i < ncolors; i++)
244             colors[i].pixel = bg_pixel;
245         }
246     }
247
248   XSetForeground (dpy, gc, colors[1].pixel);
249   XFillRectangle (dpy, window, gc, 0, 0, xgwa.width, xgwa.height);
250 }
251
252
253 #define HEIGHT_TO_PIXEL(height)                         \
254         ((height) < 0                                   \
255          ? (extra_krinkly_p                             \
256             ? ncolors - ((-(height)) % ncolors)         \
257             : 0)                                        \
258          : ((height) >= ncolors                         \
259             ? (extra_krinkly_p                          \
260                ? (height) % ncolors                     \
261                : ncolors-1)                             \
262             : (height)))
263
264
265 static unsigned int
266 set (unsigned int l,
267      unsigned int c,
268      unsigned int size,
269      int height)
270 {
271   int rang = 1 << (NSTEPS - size);
272   height = height + (random () % rang) - rang / 2;
273   height = HEIGHT_TO_PIXEL(height);
274   CELL (l, c) = height;
275   return colors[height].pixel;
276 }
277
278 static void
279 floyd_steinberg (Display *dpy, Window window)
280 {
281   int x, y, err;
282
283   /* Instead of repeatedly calling XPutPixel(), we make an Image and then
284      send its bits over all at once.  This consumes much less network
285      bandwidth.  The image we create is Wx1 intead of WxH, so that we
286      don't use enormous amounts of memory.
287    */
288   XImage *image =
289     XCreateImage (dpy, xgwa.visual,
290                   1, XYBitmap, 0,               /* depth, format, offset */
291                   (char *) calloc ((xmax + 8) / 8, 1),  /* data */
292                   xmax, 1, 8, 0);               /* w, h, pad, bpl */
293
294   XSetForeground (dpy, gc, colors[0].pixel);
295   XSetBackground (dpy, gc, colors[1].pixel);
296
297   for (y = 0; y < ymax - 1; y++)
298     {
299       for (x = 0; x < xmax - 1; x++)
300         {
301           if (CELL(x, y) < 0)
302             {
303               err = CELL (x, y);
304               XPutPixel (image, x, 0, 1);
305             }
306           else
307             {
308               err = CELL (x, y) - 1;
309               XPutPixel (image, x, 0, 0);
310             }
311           /* distribute error */
312           CELL (x,   y+1) += (int) (((float) err) * 3.0/8.0);
313           CELL (x+1, y)   += (int) (((float) err) * 3.0/8.0);
314           CELL (x+1, y+1) += (int) (((float) err) * 1.0/4.0);
315         }
316       XPutImage (dpy, window, gc, image, 0, 0, 0, y, xmax, 1);
317     }
318   XDestroyImage (image);
319 }
320
321 static void
322 draw (Display *dpy, Window window,
323       int x, int y, unsigned long pixel, int grid_size)
324 {
325   static unsigned int last_pixel, last_valid = 0;
326   if (! (last_valid && pixel == last_pixel))
327     XSetForeground (dpy, gc, pixel);
328   last_valid = 1, last_pixel = pixel;
329   if (grid_size == 1)
330     XDrawPoint (dpy, window, gc, x, y);
331   else
332     XFillRectangle (dpy, window, gc, x, y, grid_size, grid_size);
333 }
334
335
336 static void 
337 draw_map (Display *dpy, Window window)
338 {
339   int xstep, ystep, xnextStep, ynextStep;
340   int x, y, i, x1, x2, y1, y2;
341   unsigned int pixel, qpixels [4];
342
343   int backwards = random() & 1;
344
345   xmax = xgwa.width;
346   ymax = xgwa.height;
347
348   cell = (signed char *) calloc (xmax * ymax, 1);
349   if (cell == NULL)
350     exit (1);
351
352   CELL (0, 0) = 0;
353   xstep = (backwards ? -COUNT : COUNT);
354   ystep = COUNT;
355   for (i = 0; i < iterations; i++)
356     {
357       xnextStep = xstep / 2;
358       ynextStep = ystep / 2;
359       for (x = (backwards ? xmax-1 : 0);
360            (backwards ? x >= 0 : x < xmax);
361            x += xstep)
362         {
363           x1 = x + xnextStep;
364           if (x1 < 0)
365             x1 = xmax-1;
366           else if (x1 >= xmax)
367             x1 = 0;
368
369           x2 = x + xstep;
370           if (x2 < 0)
371             x2 = xmax-1;
372           else if (x2 >= xmax)
373             x2 = 0;
374
375           for (y = 0; y < ymax; y += ystep)
376             {
377               y1 = y + ynextStep;
378               if (y1 < 0)
379                 y1 = ymax-1;
380               else if (y1 >= ymax)
381                 y1 = 0;
382
383               y2 = y + ystep;
384               if (y2 < 0)
385                 y2 = ymax-1;
386               else if (y2 >= ymax)
387                 y2 = 0;
388
389               qpixels [0] = colors [HEIGHT_TO_PIXEL (CELL (x, y))].pixel;
390               qpixels [1] = colors [HEIGHT_TO_PIXEL (CELL (x, y2))].pixel;
391               qpixels [2] = colors [HEIGHT_TO_PIXEL (CELL (x2, y))].pixel;
392               qpixels [3] = colors [HEIGHT_TO_PIXEL (CELL (x2, y2))].pixel;
393
394               pixel = set (x, y1, i,
395                            ((int) CELL (x, y) + (int) CELL (x, y2) + 1) / 2);
396               if (! mono_p &&
397                   (pixel != qpixels[0] || pixel != qpixels[1] || 
398                    pixel != qpixels[2] || pixel != qpixels[3]))
399                 draw (dpy, window, x, y1, pixel, ynextStep);
400
401               pixel = set (x1, y, i,
402                            ((int) CELL (x, y) + (int) CELL (x2, y) + 1) / 2);
403               if (! mono_p &&
404                   (pixel != qpixels[0] || pixel != qpixels[1] || 
405                    pixel != qpixels[2] || pixel != qpixels[3]))
406                 draw (dpy, window, x1, y, pixel, ynextStep);
407
408               pixel = set (x1, y1, i,
409                            ((int) CELL (x, y) + (int) CELL (x, y2) +
410                             (int) CELL (x2, y) + (int) CELL (x2, y2) + 2)
411                            / 4);
412               if (! mono_p &&
413                   (pixel != qpixels[0] || pixel != qpixels[1] || 
414                    pixel != qpixels[2] || pixel != qpixels[3]))
415                 draw (dpy, window, x1, y1, pixel, ynextStep);
416
417
418               if (cycle_p)
419                 {
420                   struct timeval now;
421                   static struct timeval then = { 0, };
422                   unsigned long diff;
423 #ifdef GETTIMEOFDAY_TWO_ARGS
424                   struct timezone tzp;
425                   gettimeofday(&now, &tzp);
426 #else
427                   gettimeofday(&now);
428 #endif
429                   diff = (((now.tv_sec - then.tv_sec)  * 1000000) +
430                           (now.tv_usec - then.tv_usec));
431                   if (diff > cycle_delay)
432                     {
433                       rotate_colors (dpy, cmap, colors, ncolors,
434                                      cycle_direction);
435                       then = now;
436                     }
437                 }
438             }
439         }
440       xstep = xnextStep;
441       ystep = ynextStep;
442       if (!mono_p)
443         XSync (dpy, False);
444       screenhack_handle_events (dpy);
445     }
446   if (mono_p)
447     /* in mono-mode, we do all the drawing at the end */
448     floyd_steinberg (dpy, window);
449   
450   free (cell);
451   XSync (dpy, False);
452 }
453
454
455 char *progclass = "Imsmap";
456
457 char *defaults [] = {
458   ".background: black",
459   ".foreground: black",
460   "*mode:       random",
461   "*ncolors:    50",
462   "*iterations: 7",
463   "*delay:      10",
464   "*cycleDelay: 100000",
465   "*cycle:      true",
466   0
467 };
468
469 XrmOptionDescRec options [] = {
470   { "-ncolors",         ".ncolors",     XrmoptionSepArg, 0 },
471   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
472   { "-cycle-delay",     ".cycleDelay",  XrmoptionSepArg, 0 },
473   { "-mode",            ".mode",        XrmoptionSepArg, 0 },
474   { "-iterations",      ".iterations",  XrmoptionSepArg, 0 },
475   { "-cycle",           ".cycle",       XrmoptionNoArg, "True"  },
476   { "-no-cycle",        ".cycle",       XrmoptionNoArg, "False" },
477   { 0, 0, 0, 0 }
478 };
479
480
481 void
482 screenhack (Display *dpy, Window window)
483 {
484     while (1)
485       {
486         init_map (dpy, window);
487         draw_map (dpy, window);
488         if (delay)
489           {
490             if (cycle_p)
491               {
492                 time_t start = time((time_t) 0);
493                 while (start + delay > time((time_t) 0))
494                   {
495                     rotate_colors (dpy, cmap, colors, ncolors,
496                                    cycle_direction);
497                     if (cycle_delay) usleep(cycle_delay);
498                     screenhack_handle_events (dpy);
499                   }
500               }
501             else
502               {
503                 screenhack_handle_events (dpy);
504                 sleep (delay);
505               }
506           }
507       }
508 }