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