http://ftp.aanet.ru/pub/Linux/X11/apps/xscreensaver-2.31.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, wanted_writable);
564
565   /* If we tried for writable cells and got none, try for non-writable. */
566   if (allocate_p && *ncolorsP == 0 && writable_pP && *writable_pP)
567     {
568       ncolors = *ncolorsP;
569       *writable_pP = False;
570       goto RETRY_NON_WRITABLE;
571     }
572
573   if (verbose_p)
574     complain(*ncolorsP, ncolors, wanted_writable,
575              wanted_writable && *writable_pP);
576
577   *ncolorsP = ncolors;
578 }
579
580
581 void
582 make_random_colormap (Display *dpy, Visual *visual, Colormap cmap,
583                       XColor *colors, int *ncolorsP,
584                       Bool bright_p,
585                       Bool allocate_p,
586                       Bool *writable_pP,
587                       Bool verbose_p)
588 {
589   Bool wanted_writable = (allocate_p && writable_pP && *writable_pP);
590   int ncolors = *ncolorsP;
591   int i;
592   Screen *screen = DefaultScreenOfDisplay(dpy); /* #### WRONG! */
593
594   if (*ncolorsP <= 0) return;
595
596   /* If this visual doesn't support writable cells, don't bother trying. */
597   if (wanted_writable && !has_writable_cells(screen, visual))
598     *writable_pP = False;
599
600   for (i = 0; i < ncolors; i++)
601     {
602       colors[i].flags = DoRed|DoGreen|DoBlue;
603       if (bright_p)
604         {
605           int H = random() % 360;                          /* range 0-360    */
606           double S = ((double) (random()%70) + 30)/100.0;  /* range 30%-100% */
607           double V = ((double) (random()%34) + 66)/100.0;  /* range 66%-100% */
608           hsv_to_rgb (H, S, V,
609                       &colors[i].red, &colors[i].green, &colors[i].blue);
610         }
611       else
612         {
613           colors[i].red   = random() % 0xFFFF;
614           colors[i].green = random() % 0xFFFF;
615           colors[i].blue  = random() % 0xFFFF;
616         }
617     }
618
619   if (!allocate_p)
620     return;
621
622  RETRY_NON_WRITABLE:
623   if (writable_pP && *writable_pP)
624     {
625       unsigned long *pixels = (unsigned long *)
626         malloc(sizeof(*pixels) * (ncolors + 1));
627
628       allocate_writable_colors (dpy, cmap, pixels, &ncolors);
629       if (ncolors > 0)
630         for (i = 0; i < ncolors; i++)
631           colors[i].pixel = pixels[i];
632       free (pixels);
633       if (ncolors > 0)
634         XStoreColors (dpy, cmap, colors, ncolors);
635     }
636   else
637     {
638       for (i = 0; i < ncolors; i++)
639         {
640           XColor color;
641           color = colors[i];
642           if (!XAllocColor (dpy, cmap, &color))
643             break;
644           colors[i].pixel = color.pixel;
645         }
646       ncolors = i;
647     }
648
649   /* If we tried for writable cells and got none, try for non-writable. */
650   if (allocate_p && ncolors == 0 && writable_pP && *writable_pP)
651     {
652       ncolors = *ncolorsP;
653       *writable_pP = False;
654       goto RETRY_NON_WRITABLE;
655     }
656
657   if (verbose_p)
658     complain(*ncolorsP, ncolors, wanted_writable,
659              wanted_writable && *writable_pP);
660
661   *ncolorsP = ncolors;
662 }
663
664
665 void
666 rotate_colors (Display *dpy, Colormap cmap,
667                XColor *colors, int ncolors, int distance)
668 {
669   int i;
670   XColor *colors2 = (XColor *) malloc(sizeof(*colors2) * ncolors);
671   if (ncolors < 2) return;
672   distance = distance % ncolors;
673   for (i = 0; i < ncolors; i++)
674     {
675       int j = i - distance;
676       if (j >= ncolors) j -= ncolors;
677       if (j < 0) j += ncolors;
678       colors2[i] = colors[j];
679       colors2[i].pixel = colors[i].pixel;
680     }
681   XStoreColors (dpy, cmap, colors2, ncolors);
682   XFlush(dpy);
683   memcpy(colors, colors2, sizeof(*colors) * ncolors);
684   free(colors2);
685 }