ftp://ftp.uni-heidelberg.de/pub/X11/contrib/applications/xscreensaver-2.07.tar.gz
[xscreensaver] / utils / colors.c
1 /* xscreensaver, Copyright (c) 1997 Jamie Zawinski <jwz@netscape.com>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* This file contains some utility routines for randomly picking the colors
13    to hack the screen with.
14  */
15
16 #include "utils.h"
17 #include "hsv.h"
18 #include "yarandom.h"
19 #include "visual.h"
20 #include "colors.h"
21
22 extern char *progname;
23
24 void
25 free_colors(Display *dpy, Colormap cmap, XColor *colors, int ncolors)
26 {
27   int i;
28   unsigned long *pixels = (unsigned long *) malloc(sizeof(*pixels) * ncolors);
29   for (i = 0; i < ncolors; i++)
30     pixels[i] = colors[i].pixel;
31   XFreeColors (dpy, cmap, pixels, ncolors, 0L);
32   free(pixels);
33 }
34
35
36 void
37 allocate_writable_colors (Display *dpy, Colormap cmap,
38                           unsigned long *pixels, int *ncolorsP)
39 {
40   int desired = *ncolorsP;
41   int got = 0;
42   int requested = desired;
43   unsigned long *new_pixels = pixels;
44
45   *ncolorsP = 0;
46   while (got < desired
47          && requested > 0)
48     {
49       if (desired - got > requested)
50         requested = desired - got;
51
52       if (XAllocColorCells (dpy, cmap, False, 0, 0, new_pixels, requested))
53         {
54           /* Got all the pixels we asked for. */
55           new_pixels += requested;
56           got += requested;
57         }
58       else
59         {
60           /* We didn't get all/any of the pixels we asked for.  This time, ask
61              for half as many.  (If we do get all that we ask for, we ask for
62              the same number again next time, so we only do O(log(n)) server
63              roundtrips.)
64           */
65           requested = requested / 2;
66         }
67     }
68   *ncolorsP += got;
69 }
70
71
72
73 void
74 make_color_ramp (Display *dpy, Colormap cmap,
75                  int h1, double s1, double v1,   /* 0-360, 0-1.0, 0-1.0 */
76                  int h2, double s2, double v2,   /* 0-360, 0-1.0, 0-1.0 */
77                  XColor *colors, int *ncolorsP,
78                  Bool closed_p,
79                  Bool allocate_p,
80                  Bool writable_p)
81 {
82   int i;
83   int ncolors = *ncolorsP;
84   double dh, ds, dv;            /* deltas */
85
86  AGAIN:
87
88   memset (colors, 0, (*ncolorsP) * sizeof(*colors));
89
90   if (closed_p)
91     ncolors = (ncolors / 2) + 1;
92
93   /* Note: unlike other routines in this module, this function assumes that
94      if h1 and h2 are more than 180 degrees apart, then the desired direction
95      is always from h1 to h2 (rather than the shorter path.)  make_uniform
96      depends on this.
97    */
98   dh = ((double)h2 - (double)h1) / ncolors;
99   ds = (s2 - s1) / ncolors;
100   dv = (v2 - v1) / ncolors;
101
102   for (i = 0; i < ncolors; i++)
103     {
104       colors[i].flags = DoRed|DoGreen|DoBlue;
105       hsv_to_rgb ((int) (h1 + (i*dh)), (s1 + (i*ds)), (v1 + (i*dv)),
106                   &colors[i].red, &colors[i].green, &colors[i].blue);
107     }
108
109   if (closed_p)
110     for (i = ncolors; i < *ncolorsP; i++)
111       colors[i] = colors[(*ncolorsP)-i];
112
113   if (!allocate_p)
114     return;
115
116   if (writable_p)
117     {
118       unsigned long *pixels = (unsigned long *)
119         malloc(sizeof(*pixels) * ((*ncolorsP) + 1));
120
121       /* allocate_writable_colors() won't do here, because we need exactly this
122          number of cells, or the color sequence we've chosen won't fit. */
123       if (! XAllocColorCells(dpy, cmap, False, 0, 0, pixels, *ncolorsP))
124         {
125           free(pixels);
126           goto FAIL;
127         }
128
129       for (i = 0; i < *ncolorsP; i++)
130         colors[i].pixel = pixels[i];
131       free (pixels);
132
133       XStoreColors (dpy, cmap, colors, *ncolorsP);
134     }
135   else
136     {
137       for (i = 0; i < *ncolorsP; i++)
138         {
139           XColor color;
140           color = colors[i];
141           if (XAllocColor (dpy, cmap, &color))
142             {
143               colors[i].pixel = color.pixel;
144             }
145           else
146             {
147               free_colors (dpy, cmap, colors, i);
148               goto FAIL;
149             }
150         }
151     }
152
153   return;
154
155  FAIL:
156   /* we weren't able to allocate all the colors we wanted;
157      decrease the requested number and try again.
158    */
159   ncolors = (ncolors > 170 ? ncolors - 20 :
160              ncolors > 100 ? ncolors - 10 :
161              ncolors >  75 ? ncolors -  5 :
162              ncolors >  25 ? ncolors -  3 :
163              ncolors >  10 ? ncolors -  2 :
164              ncolors >   2 ? ncolors -  1 :
165              0);
166   *ncolorsP = ncolors;
167   if (ncolors > 0)
168     goto AGAIN;
169 }
170
171
172 #define MAXPOINTS 50    /* yeah, so I'm lazy */
173
174
175 static void
176 make_color_path (Display *dpy, Colormap cmap,
177                  int npoints, int *h, double *s, double *v,
178                  XColor *colors, int *ncolorsP,
179                  Bool allocate_p,
180                  Bool writable_p)
181 {
182   int i, j, k;
183   int total_ncolors = *ncolorsP;
184
185   int ncolors[MAXPOINTS];  /* number of pixels per edge */
186   double dh[MAXPOINTS];    /* distance between pixels, per edge (0 - 360.0) */
187   double ds[MAXPOINTS];    /* distance between pixels, per edge (0 - 1.0) */
188   double dv[MAXPOINTS];    /* distance between pixels, per edge (0 - 1.0) */
189
190   if (npoints == 0)
191     {
192       *ncolorsP = 0;
193       return;
194     }
195   else if (npoints == 2)        /* using make_color_ramp() will be faster */
196     {
197       make_color_ramp (dpy, cmap,
198                        h[0], s[0], v[0], h[1], s[1], v[1],
199                        colors, ncolorsP,
200                        True,  /* closed_p */
201                        allocate_p, writable_p);
202       return;
203     }
204   else if (npoints >= MAXPOINTS)
205     {
206       npoints = MAXPOINTS-1;
207     }
208
209  AGAIN:
210
211   {
212     double DH[MAXPOINTS];       /* Distance between H values in the shortest
213                                    direction around the circle, that is, the
214                                    distance between 10 and 350 is 20.
215                                    (Range is 0 - 360.0.)
216                                 */
217     double edge[MAXPOINTS];     /* lengths of edges in unit HSV space. */
218     double ratio[MAXPOINTS];    /* proportions of the edges (total 1.0) */
219     double circum = 0;
220     double one_point_oh = 0;    /* (debug) */
221
222     for (i = 0; i < npoints; i++)
223       {
224         int j = (i+1) % npoints;
225         double d = ((double) (h[i] - h[j])) / 360;
226         if (d < 0) d = -d;
227         if (d > 0.5) d = 0.5 - (d - 0.5);
228         DH[i] = d;
229       }
230
231     for (i = 0; i < npoints; i++)
232       {
233         int j = (i+1) % npoints;
234         edge[i] = sqrt((DH[i] * DH[j]) +
235                        ((s[j] - s[i]) * (s[j] - s[i])) +
236                        ((v[j] - v[i]) * (v[j] - v[i])));
237         circum += edge[i];
238       }
239
240 #ifdef DEBUG
241     fprintf(stderr, "\ncolors:");
242     for (i=0; i < npoints; i++)
243       fprintf(stderr, " (%d, %.3f, %.3f)", h[i], s[i], v[i]);
244     fprintf(stderr, "\nlengths:");
245     for (i=0; i < npoints; i++)
246       fprintf(stderr, " %.3f", edge[i]);
247 #endif /* DEBUG */
248
249     if (circum < 0.0001)
250       goto FAIL;
251
252     for (i = 0; i < npoints; i++)
253       {
254         ratio[i] = edge[i] / circum;
255         one_point_oh += ratio[i];
256       }
257
258 #ifdef DEBUG
259     fprintf(stderr, "\nratios:");
260     for (i=0; i < npoints; i++)
261       fprintf(stderr, " %.3f", ratio[i]);
262 #endif /* DEBUG */
263
264     if (one_point_oh < 0.99999 || one_point_oh > 1.00001)
265       abort();
266
267     /* space the colors evenly along the circumference -- that means that the
268        number of pixels on a edge is proportional to the length of that edge
269        (relative to the lengths of the other edges.)
270      */
271     for (i = 0; i < npoints; i++)
272       ncolors[i] = total_ncolors * ratio[i];
273
274
275 #ifdef DEBUG
276     fprintf(stderr, "\npixels:");
277     for (i=0; i < npoints; i++)
278       fprintf(stderr, " %d", ncolors[i]);
279     fprintf(stderr, "  (%d)\n", total_ncolors);
280 #endif /* DEBUG */
281
282     for (i = 0; i < npoints; i++)
283       {
284         int j = (i+1) % npoints;
285
286         if (ncolors[i] > 0)
287           {
288             dh[i] = 360 * (DH[i] / ncolors[i]);
289             ds[i] = (s[j] - s[i]) / ncolors[i];
290             dv[i] = (v[j] - v[i]) / ncolors[i];
291           }
292       }
293   }
294
295   memset (colors, 0, (*ncolorsP) * sizeof(*colors));
296
297   k = 0;
298   for (i = 0; i < npoints; i++)
299     {
300       int distance, direction;
301       distance = h[(i+1) % npoints] - h[i];
302       direction = (distance >= 0 ? -1 : 1);
303
304       if (distance > 180)
305         distance = 180 - (distance - 180);
306       else if (distance < -180)
307         distance = -(180 - ((-distance) - 180));
308       else
309         direction = -direction;
310
311 #ifdef DEBUG
312       fprintf (stderr, "point %d: %3d %.2f %.2f\n",
313                i, h[i], s[i], v[i]);
314       fprintf(stderr, "  h[i]=%d  dh[i]=%.2f  ncolors[i]=%d\n",
315               h[i], dh[i], ncolors[i]);
316 #endif /* DEBUG */
317       for (j = 0; j < ncolors[i]; j++, k++)
318         {
319           double hh = (h[i] + (j * dh[i] * direction));
320           if (hh < 0) hh += 360;
321           else if (hh > 360) hh -= 0;
322           colors[k].flags = DoRed|DoGreen|DoBlue;
323           hsv_to_rgb ((int)
324                       hh,
325                       (s[i] + (j * ds[i])),
326                       (v[i] + (j * dv[i])),
327                       &colors[k].red, &colors[k].green, &colors[k].blue);
328 #ifdef DEBUG
329           fprintf (stderr, "point %d+%d: %.2f %.2f %.2f  %04X %04X %04X\n",
330                    i, j,
331                    hh,
332                    (s[i] + (j * ds[i])),
333                    (v[i] + (j * dv[i])),
334                    colors[k].red, colors[k].green, colors[k].blue);
335 #endif /* DEBUG */
336         }
337     }
338
339   /* Floating-point round-off can make us decide to use fewer colors. */
340   if (k < *ncolorsP)
341     {
342       *ncolorsP = k;
343       if (k <= 0)
344         return;
345     }
346
347   if (!allocate_p)
348     return;
349
350   if (writable_p)
351     {
352       unsigned long *pixels = (unsigned long *)
353         malloc(sizeof(*pixels) * ((*ncolorsP) + 1));
354
355       /* allocate_writable_colors() won't do here, because we need exactly this
356          number of cells, or the color sequence we've chosen won't fit. */
357       if (! XAllocColorCells(dpy, cmap, False, 0, 0, pixels, *ncolorsP))
358         {
359           free(pixels);
360           goto FAIL;
361         }
362
363       for (i = 0; i < *ncolorsP; i++)
364         colors[i].pixel = pixels[i];
365       free (pixels);
366
367       XStoreColors (dpy, cmap, colors, *ncolorsP);
368     }
369   else
370     {
371       for (i = 0; i < *ncolorsP; i++)
372         {
373           XColor color;
374           color = colors[i];
375           if (XAllocColor (dpy, cmap, &color))
376             {
377               colors[i].pixel = color.pixel;
378             }
379           else
380             {
381               free_colors (dpy, cmap, colors, i);
382               goto FAIL;
383             }
384         }
385     }
386
387   return;
388
389  FAIL:
390   /* we weren't able to allocate all the colors we wanted;
391      decrease the requested number and try again.
392    */
393   total_ncolors = (total_ncolors > 170 ? total_ncolors - 20 :
394                    total_ncolors > 100 ? total_ncolors - 10 :
395                    total_ncolors >  75 ? total_ncolors -  5 :
396                    total_ncolors >  25 ? total_ncolors -  3 :
397                    total_ncolors >  10 ? total_ncolors -  2 :
398                    total_ncolors >   2 ? total_ncolors -  1 :
399                    0);
400   *ncolorsP = total_ncolors;
401   if (total_ncolors > 0)
402     goto AGAIN;
403 }
404
405
406 void
407 make_color_loop (Display *dpy, Colormap cmap,
408                  int h0, double s0, double v0,   /* 0-360, 0-1.0, 0-1.0 */
409                  int h1, double s1, double v1,   /* 0-360, 0-1.0, 0-1.0 */
410                  int h2, double s2, double v2,   /* 0-360, 0-1.0, 0-1.0 */
411                  XColor *colors, int *ncolorsP,
412                  Bool allocate_p,
413                  Bool writable_p)
414 {
415   int h[3];
416   double s[3], v[3];
417   h[0] = h0; h[1] = h1; h[2] = h2;
418   s[0] = s0; s[1] = s1; s[2] = s2;
419   v[0] = v0; v[1] = v1; v[2] = v2;
420   make_color_path(dpy, cmap,
421                   3, h, s, v,
422                   colors, ncolorsP,
423                   allocate_p, writable_p);
424 }
425
426
427 static void
428 complain (int wanted_colors, int got_colors,
429           Bool wanted_writable, Bool got_writable)
430 {
431   if (wanted_writable && !got_writable)
432     fprintf(stderr,
433             "%s: wanted %d writable colors; got %d read-only colors.\n",
434             progname, wanted_colors, got_colors);
435
436   else if (wanted_colors > (got_colors + 10))
437     /* don't bother complaining if we're within ten pixels. */
438     fprintf(stderr, "%s: wanted %d%s colors; got %d.\n",
439             progname, wanted_colors, (got_writable ? " writable" : ""),
440             got_colors);
441 }
442
443
444 void
445 make_smooth_colormap (Display *dpy, Visual *visual, Colormap cmap,
446                       XColor *colors, int *ncolorsP,
447                       Bool allocate_p,
448                       Bool *writable_pP,
449                       Bool verbose_p)
450 {
451   int npoints;
452   int ncolors = *ncolorsP;
453   Bool wanted_writable = (allocate_p && writable_pP && *writable_pP);
454   int i;
455   int h[MAXPOINTS];
456   double s[MAXPOINTS];
457   double v[MAXPOINTS];
458   double total_s = 0;
459   double total_v = 0;
460   Screen *screen = DefaultScreenOfDisplay(dpy); /* #### WRONG! */
461
462   if (*ncolorsP <= 0) return;
463
464   {
465     int n = random() % 20;
466     if      (n <= 5)  npoints = 2;      /* 30% of the time */
467     else if (n <= 15) npoints = 3;      /* 50% of the time */
468     else if (n <= 18) npoints = 4;      /* 15% of the time */
469     else             npoints = 5;       /*  5% of the time */
470   }
471
472  REPICK_ALL_COLORS:
473   for (i = 0; i < npoints; i++)
474     {
475     REPICK_THIS_COLOR:
476       h[i] = random() % 360;
477       s[i] = frand(1.0);
478       v[i] = frand(0.8) + 0.2;
479
480       /* Make sure that no two adjascent colors are *too* close together.
481          If they are, try again.
482        */
483       if (i > 0)
484         {
485           int j = (i+1 == npoints) ? 0 : (i-1);
486           double hi = ((double) h[i]) / 360;
487           double hj = ((double) h[j]) / 360;
488           double dh = hj - hi;
489           double distance;
490           if (dh < 0) dh = -dh;
491           if (dh > 0.5) dh = 0.5 - (dh - 0.5);
492           distance = sqrt ((dh * dh) +
493                            ((s[j] - s[i]) * (s[j] - v[i])) +
494                            ((v[j] - v[i]) * (v[j] - v[i])));
495           if (distance < 0.2)
496             goto REPICK_THIS_COLOR;
497         }
498       total_s += s[i];
499       total_v += v[i];
500     }
501
502   /* If the average saturation or intensity are too low, repick the colors,
503      so that we don't end up with a black-and-white or too-dark map.
504    */
505   if (total_s / npoints < 0.2)
506     goto REPICK_ALL_COLORS;
507   if (total_v / npoints < 0.3)
508     goto REPICK_ALL_COLORS;
509
510   /* If this visual doesn't support writable cells, don't bother trying.
511    */
512   if (wanted_writable && !has_writable_cells(screen, visual))
513     *writable_pP = False;
514
515  RETRY_NON_WRITABLE:
516   make_color_path (dpy, cmap, npoints, h, s, v, colors, &ncolors,
517                    allocate_p, (writable_pP && *writable_pP));
518
519   /* If we tried for writable cells and got none, try for non-writable. */
520   if (allocate_p && *ncolorsP == 0 && *writable_pP)
521     {
522       *writable_pP = False;
523       goto RETRY_NON_WRITABLE;
524     }
525
526   if (verbose_p)
527     complain(*ncolorsP, ncolors, wanted_writable,
528              wanted_writable && *writable_pP);
529
530   *ncolorsP = ncolors;
531 }
532
533
534 void
535 make_uniform_colormap (Display *dpy, Visual *visual, Colormap cmap,
536                        XColor *colors, int *ncolorsP,
537                        Bool allocate_p,
538                        Bool *writable_pP,
539                        Bool verbose_p)
540 {
541   int ncolors = *ncolorsP;
542   Bool wanted_writable = (allocate_p && writable_pP && *writable_pP);
543   Screen *screen = DefaultScreenOfDisplay(dpy); /* #### WRONG! */
544
545   double S = ((double) (random() % 34) + 66) / 100.0;   /* range 66%-100% */
546   double V = ((double) (random() % 34) + 66) / 100.0;   /* range 66%-100% */
547
548   if (*ncolorsP <= 0) return;
549
550   /* If this visual doesn't support writable cells, don't bother trying. */
551   if (wanted_writable && !has_writable_cells(screen, visual))
552     *writable_pP = False;
553
554  RETRY_NON_WRITABLE:
555   make_color_ramp(dpy, cmap,
556                   0,   S, V,
557                   359, S, V,
558                   colors, &ncolors,
559                   False, True, wanted_writable);
560
561   /* If we tried for writable cells and got none, try for non-writable. */
562   if (allocate_p && *ncolorsP == 0 && writable_pP && *writable_pP)
563     {
564       ncolors = *ncolorsP;
565       *writable_pP = False;
566       goto RETRY_NON_WRITABLE;
567     }
568
569   if (verbose_p)
570     complain(*ncolorsP, ncolors, wanted_writable,
571              wanted_writable && *writable_pP);
572
573   *ncolorsP = ncolors;
574 }
575
576
577 void
578 make_random_colormap (Display *dpy, Visual *visual, Colormap cmap,
579                       XColor *colors, int *ncolorsP,
580                       Bool bright_p,
581                       Bool allocate_p,
582                       Bool *writable_pP,
583                       Bool verbose_p)
584 {
585   Bool wanted_writable = (allocate_p && writable_pP && *writable_pP);
586   int ncolors = *ncolorsP;
587   int i;
588   Screen *screen = DefaultScreenOfDisplay(dpy); /* #### WRONG! */
589
590   if (*ncolorsP <= 0) return;
591
592   /* If this visual doesn't support writable cells, don't bother trying. */
593   if (wanted_writable && !has_writable_cells(screen, visual))
594     *writable_pP = False;
595
596   for (i = 0; i < ncolors; i++)
597     {
598       colors[i].flags = DoRed|DoGreen|DoBlue;
599       if (bright_p)
600         {
601           int H = random() % 360;                          /* range 0-360    */
602           double S = ((double) (random()%70) + 30)/100.0;  /* range 30%-100% */
603           double V = ((double) (random()%34) + 66)/100.0;  /* range 66%-100% */
604           hsv_to_rgb (H, S, V,
605                       &colors[i].red, &colors[i].green, &colors[i].blue);
606         }
607       else
608         {
609           colors[i].red   = random() % 0xFFFF;
610           colors[i].green = random() % 0xFFFF;
611           colors[i].blue  = random() % 0xFFFF;
612         }
613     }
614
615   if (!allocate_p)
616     return;
617
618  RETRY_NON_WRITABLE:
619   if (writable_pP && *writable_pP)
620     {
621       unsigned long *pixels = (unsigned long *)
622         malloc(sizeof(*pixels) * (ncolors + 1));
623
624       allocate_writable_colors (dpy, cmap, pixels, &ncolors);
625       if (ncolors > 0)
626         for (i = 0; i < ncolors; i++)
627           colors[i].pixel = pixels[i];
628       free (pixels);
629       if (ncolors > 0)
630         XStoreColors (dpy, cmap, colors, ncolors);
631     }
632   else
633     {
634       for (i = 0; i < ncolors; i++)
635         {
636           XColor color;
637           color = colors[i];
638           if (!XAllocColor (dpy, cmap, &color))
639             break;
640           colors[i].pixel = color.pixel;
641         }
642       ncolors = i;
643     }
644
645   /* If we tried for writable cells and got none, try for non-writable. */
646   if (allocate_p && ncolors == 0 && writable_pP && *writable_pP)
647     {
648       ncolors = *ncolorsP;
649       *writable_pP = False;
650       goto RETRY_NON_WRITABLE;
651     }
652
653   if (verbose_p)
654     complain(*ncolorsP, ncolors, wanted_writable,
655              wanted_writable && *writable_pP);
656
657   *ncolorsP = ncolors;
658 }
659
660
661 void
662 rotate_colors (Display *dpy, Colormap cmap,
663                XColor *colors, int ncolors, int distance)
664 {
665   int i;
666   XColor *colors2 = (XColor *) malloc(sizeof(*colors2) * ncolors);
667   if (ncolors < 2) return;
668   distance = distance % ncolors;
669   for (i = 0; i < ncolors; i++)
670     {
671       int j = i - distance;
672       if (j >= ncolors) j -= ncolors;
673       if (j < 0) j += ncolors;
674       colors2[i] = colors[j];
675       colors2[i].pixel = colors[i].pixel;
676     }
677   XStoreColors (dpy, cmap, colors2, ncolors);
678   XFlush(dpy);
679   memcpy(colors, colors2, sizeof(*colors) * ncolors);
680   free(colors2);
681 }