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