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