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