From http://www.jwz.org/xscreensaver/xscreensaver-5.39.tar.gz
[xscreensaver] / hacks / fuzzyflakes.c
1 /* fuzzyflakes, Copyright (c) 2004
2  *  Barry Dmytro <badcherry@mailc.net>
3  *
4  * ! 2004.06.10 21:05
5  * ! - Added support for resizing
6  * ! - Added a color scheme generation algorithm
7  * !    Thanks to <ZoeB> from #vegans@irc.blitzed.org
8  * ! - Added random color generation
9  * ! - Fixed errors in the xml config file
10  * ! - Cleaned up a few inconsistencies in the code
11  * ! - Changed the default color to #EFBEA5
12  *
13  * ! 2004.05.?? ??:??
14  * ! -original creation
15  *
16  * Permission to use, copy, modify, distribute, and sell this software and its
17  * documentation for any purpose is hereby granted without fee, provided that
18  * the above copyright notice appear in all copies and that both that
19  * copyright notice and this permission notice appear in supporting
20  * documentation.  No representations are made about the suitability of this
21  * software for any purpose.  It is provided "as is" without express or 
22  * implied warranty.
23  */
24
25 #include <math.h>
26 #include "screenhack.h"
27
28 /* I have need of 1/3 and 2/3 constants later on */
29 #define N1_3 0.3333333333
30 #define N2_3 0.6666666666
31
32 typedef struct _flake_var
33 {
34    double              Ticks;
35    double              XPos, YPos;
36    double              TrueX;
37    double              XOffset;
38    double              Angle;
39 } FlakeVariable;
40
41 /* Struct containing the atrributes to our flakes */
42 typedef struct _flake
43 {
44    Display *dpy;
45    Window window;
46
47    int                 Arms;
48    int                 Thickness;
49    int                 BorderThickness;
50    int                 Radius;
51    unsigned long       BordColor;
52    unsigned long       ForeColor;
53    unsigned long       BackColor;
54    Bool                RandomColors;
55    int                 Layers;
56    int                 Density;
57    int                 Delay;
58    int                 FallingSpeed;
59    struct _colors
60    {
61       char               *Fore;
62       char               *Bord;
63       char               *Back;
64    } Colors;
65 /* a dynamic array containing positions of all the flakes */
66    FlakeVariable    ***Flakes;
67    XGCValues           GCValues;
68    unsigned long       GCFlags;
69    GC                  GCVar;
70    XWindowAttributes   XGWA;
71    struct _dbevar
72    {
73       Bool                dbuf;
74       Pixmap              b, ba, bb;
75    } DB;
76 } Flake;
77
78 /*
79  *This gets the pixel resource for a color: #ffffff
80  */
81 static unsigned int
82 FuzzyFlakesColorResource(Flake *flake, char *Color)
83 {
84    XColor              color;
85
86    if (!XParseColor(flake->dpy, flake->XGWA.colormap, Color, &color))
87      {
88         fprintf(stderr, "%s: can't parse color %s", progname, Color);
89         return 0;
90      }
91    if (!XAllocColor(flake->dpy, flake->XGWA.colormap, &color))
92      {
93         fprintf(stderr, "%s: can't allocate color %s", progname, Color);
94         return 0;
95      }
96    return color.pixel;
97 }
98
99 /*
100  * This is a great color matching algorithm that I got from
101  * a friend of mine on #vegans@irc.blitzed.org
102  * She wrote it in PHP and I ported it over to C
103  * her site is http://beautifulfreak.net/
104  */
105 static int
106 FuzzyFlakesColorHelper(Flake *flake)
107 {
108    unsigned int        iR, iG, iB;
109    unsigned int        iR0, iG0, iB0;
110    unsigned int        iR1, iG1, iB1;
111    float               fR, fG, fB;
112    float               Max = 0, Min = 0, Lig, Sat;
113    float               Hue, Hue0, Hue1;
114    float               f1, f2;
115    float               fR0, fG0, fB0;
116    float               fR1, fG1, fB1;
117    float               nR0, nG0, nB0;
118    float               nR1, nG1, nB1;
119    XColor              color;
120
121    /* First convert from hex to dec */
122    /* while splitting up the RGB values */
123    if (!XParseColor(flake->dpy, flake->XGWA.colormap,
124                     flake->Colors.Back, &color))
125      {
126         fprintf(stderr, "%s: can't parse color %s", progname,
127                 flake->Colors.Back);
128         return 1;
129      }
130    iR = color.red   >> 8;
131    iG = color.green >> 8;
132    iB = color.blue  >> 8;
133
134    /* Convert from int to float */
135    fR = iR;
136    fG = iG;
137    fB = iB;
138
139    /* convert from 0-255 to 0-1 */
140    fR = fR / 255;
141    fG = fG / 255;
142    fB = fB / 255;
143
144    /* work out the lightness */
145    if (fR >= fG && fR >= fB)
146       Max = fR;
147    if (fG >= fR && fG >= fB)
148       Max = fG;
149    if (fB >= fR && fB >= fG)
150       Max = fB;
151
152    if (fR <= fG && fR <= fB)
153       Min = fR;
154    if (fG <= fR && fG <= fB)
155       Min = fG;
156    if (fB <= fR && fB <= fG)
157       Min = fB;
158
159    Lig = (Max + Min) / 2;
160
161    /* work out the saturation */
162    if (Max == Min)
163       Sat = 0;
164    else
165      {
166         if (Lig < 0.5)
167            Sat = (Max - Min) / (Max + Min);
168         else
169            Sat = (Max - Min) / (2 - Max - Min);
170      }
171
172    /*
173     * if our satration is too low we won't be
174     * able to see any objects
175     */
176    if (Sat < 0.03)
177      {
178         return 1;
179      }
180
181    /* work out the hue */
182    if (fR == Max)
183       Hue = (fG - fB) / (Max - Min);
184    else if (fG == Max)
185       Hue = 2 + (fB - fR) / (Max - Min);
186    else
187       Hue = 4 + (fR - fG) / (Max - Min);
188
189    Hue = Hue / 6;
190
191    /* fine two equidistant hues */
192    Hue0 = Hue + N1_3;
193    if (Hue0 > 1)
194       Hue0 = Hue0 - 1;
195    Hue1 = Hue0 + N1_3;
196    if (Hue1 > 1)
197       Hue1 = Hue1 - 1;
198
199    /* convert the colors into hex codes */
200    if (Lig < 0.5)
201       f2 = Lig * (1 + Sat);
202    else
203       f2 = (Lig + Sat) - (Lig * Sat);
204
205    f1 = (2 * Lig) - f2;
206
207    fR0 = (Hue0 + 1) / 3;
208    fR1 = (Hue1 + 1) / 3;
209    fG0 = Hue0;
210    fG1 = Hue1;
211    fB0 = (Hue0 - 1) / 3;
212    fB1 = (Hue1 - 1) / 3;
213
214    if (fR0 < 0)
215       fR0 = fR0 + 1;
216    if (fR0 > 1)
217       fR0 = fR0 - 1;
218    if (fG0 < 0)
219       fG0 = fG0 + 1;
220    if (fG0 > 1)
221       fG0 = fG0 - 1;
222    if (fB0 < 0)
223       fB0 = fB0 + 1;
224    if (fB0 > 1)
225       fB0 = fB0 - 1;
226
227    if (fR1 < 0)
228       fR1 = fR1 + 1;
229    if (fR1 > 1)
230       fR1 = fR1 - 1;
231    if (fG1 < 0)
232       fG1 = fG1 + 1;
233    if (fG1 > 1)
234       fG1 = fG1 - 1;
235    if (fB1 < 0)
236       fB1 = fB1 + 1;
237    if (fB1 > 1)
238       fB1 = fB1 - 1;
239
240    if (6 * fR0 < 1)
241       nR0 = f1 + (f2 - f1) * 6 * fR0;
242    else if (2 * fR0 < 1)
243       nR0 = f2;
244    else if (3 * fR0 < 2)
245       nR0 = f1 + (f2 - f1) * (N2_3 - fR0) * 6;
246    else
247       nR0 = f1;
248
249    if (6 * fG0 < 1)
250       nG0 = f1 + (f2 - f1) * 6 * fG0;
251    else if (2 * fG0 < 1)
252       nG0 = f2;
253    else if (3 * fG0 < 2)
254       nG0 = f1 + (f2 - f1) * (N2_3 - fG0) * 6;
255    else
256       nG0 = f1;
257
258    if (6 * fB0 < 1)
259       nB0 = f1 + (f2 - f1) * 6 * fB0;
260    else if (2 * fB0 < 1)
261       nB0 = f2;
262    else if (3 * fB0 < 2)
263       nB0 = f1 + (f2 - f1) * (N2_3 - fB0) * 6;
264    else
265       nB0 = f1;
266
267    if (6 * fR1 < 1)
268       nR1 = f1 + (f2 - f1) * 6 * fR1;
269    else if (2 * fR1 < 1)
270       nR1 = f2;
271    else if (3 * fR1 < 2)
272       nR1 = f1 + (f2 - f1) * (N2_3 - fR1) * 6;
273    else
274       nR1 = f1;
275
276    if (6 * fG1 < 1)
277       nG1 = f1 + (f2 - f1) * 6 * fG1;
278    else if (2 * fG1 < 1)
279       nG1 = f2;
280    else if (3 * fG1 < 2)
281       nG1 = f1 + (f2 - f1) * (N2_3 - fG1) * 6;
282    else
283       nG1 = f1;
284
285    if (6 * fB1 < 1)
286       nB1 = f1 + (f2 - f1) * 6 * fB1;
287    else if (2 * fB1 < 1)
288       nB1 = f2;
289    else if (3 * fB1 < 2)
290       nB1 = f1 + (f2 - f1) * (N2_3 - fB1) * 6;
291    else
292       nB1 = f1;
293
294    /* at last convert them to a hex string */
295    iR0 = nR0 * 255;
296    iG0 = nG0 * 255;
297    iB0 = nB0 * 255;
298
299    iR1 = nR1 * 255;
300    iG1 = nG1 * 255;
301    iB1 = nB1 * 255;
302
303    flake->Colors.Fore = malloc(sizeof(unsigned char) * 8);
304    flake->Colors.Bord = malloc(sizeof(unsigned char) * 8);
305
306    sprintf(flake->Colors.Fore, "#%02X%02X%02X", iR0, iG0, iB0);
307    sprintf(flake->Colors.Bord, "#%02X%02X%02X", iR1, iG1, iB1);
308
309    return 0;
310 }
311
312 static void
313 FuzzyFlakesInit(Flake *flake)
314 {
315    int                 i, j;
316    XWindowAttributes   xgwa;
317
318    XGetWindowAttributes(flake->dpy, flake->window, &xgwa);
319    flake->XGWA = xgwa;
320    flake->DB.b = flake->DB.ba = flake->DB.bb = 0;
321    flake->DB.dbuf = get_boolean_resource(flake->dpy, "doubleBuffer", "Boolean");
322
323 # ifdef HAVE_JWXYZ      /* Don't second-guess Quartz's double-buffering */
324    flake->DB.dbuf = False;
325 # endif
326
327    if (flake->DB.dbuf)
328      {
329         flake->DB.ba =
330            XCreatePixmap(flake->dpy, flake->window, xgwa.width, xgwa.height, xgwa.depth);
331         flake->DB.bb =
332            XCreatePixmap(flake->dpy, flake->window, xgwa.width, xgwa.height, xgwa.depth);
333         flake->DB.b = flake->DB.ba;
334      }
335    else
336      {
337         flake->DB.b = flake->window;
338      }
339
340    flake->Arms = get_integer_resource(flake->dpy, "arms", "Integer");
341    flake->Thickness = get_integer_resource(flake->dpy, "thickness", "Integer");
342    flake->BorderThickness = get_integer_resource(flake->dpy, "bthickness", "Integer");
343    flake->Radius = get_integer_resource(flake->dpy, "radius", "Integer");
344
345    flake->Density = get_integer_resource(flake->dpy, "density", "Integer");
346    flake->Layers = get_integer_resource(flake->dpy, "layers", "Integer");
347    flake->FallingSpeed = get_integer_resource(flake->dpy, "fallingspeed", "Integer");
348    flake->Delay = get_integer_resource(flake->dpy, "delay", "Integer");
349    if (flake->RandomColors == True)
350       flake->RandomColors = get_boolean_resource(flake->dpy, "randomColors", "Boolean");
351
352    if (xgwa.width > 2560) {  /* Retina displays */
353      flake->Thickness *= 2;
354      flake->BorderThickness *= 2;
355      flake->Radius *= 2;
356      flake->FallingSpeed *= 2;
357    }
358
359    if (flake->Delay < 0)
360       flake->Delay = 0;
361
362    if (!flake->Colors.Back)
363      {
364         flake->Colors.Back = get_string_resource(flake->dpy, "color", "Color");
365         if (!FuzzyFlakesColorResource(flake, flake->Colors.Back))
366           {
367              fprintf(stderr, " reverting to random\n");
368              flake->RandomColors = True;
369           }
370
371         if (flake->RandomColors)
372           {
373              if (flake->Colors.Back)
374                 free(flake->Colors.Back);
375              flake->Colors.Back = malloc(sizeof(unsigned char) * 8);
376              sprintf(flake->Colors.Back, "#%X%X%X%X%X%X", random() % 16,
377                      random() % 16, random() % 16, random() % 16, random() % 16,
378                      random() % 16);
379           }
380
381         /*
382          * Here we establish our colormap based on what is in
383          * flake->Colors.Back
384          */
385         if (FuzzyFlakesColorHelper(flake))
386           {
387              fprintf(stderr, " reverting to random\n");
388              if (flake->Colors.Back)
389                 free(flake->Colors.Back);
390              flake->Colors.Back = malloc(sizeof(unsigned char) * 8);
391              sprintf(flake->Colors.Back, "#%X%X%X%X%X%X", random() % 16,
392                      random() % 16, random() % 16, random() % 16, random() % 16,
393                      random() % 16);
394              FuzzyFlakesColorHelper(flake);
395           }
396
397         flake->ForeColor = FuzzyFlakesColorResource(flake, flake->Colors.Fore);
398         flake->BackColor = FuzzyFlakesColorResource(flake, flake->Colors.Back);
399         flake->BordColor = FuzzyFlakesColorResource(flake, flake->Colors.Bord);
400
401         flake->GCValues.foreground = flake->ForeColor;
402         flake->GCValues.background = flake->BackColor;
403         flake->RandomColors = False;
404      }
405
406    flake->GCValues.line_width = flake->Thickness;
407    flake->GCValues.cap_style = CapProjecting;
408    flake->GCValues.join_style = JoinMiter;
409    flake->GCFlags |= (GCLineWidth | GCCapStyle | GCJoinStyle);
410
411    flake->GCVar =
412       XCreateGC(flake->dpy, flake->window, flake->GCFlags,
413                 &flake->GCValues);
414
415    flake->Density = flake->XGWA.width / 200 * flake->Density;
416    flake->Flakes = malloc(sizeof(FlakeVariable **) * flake->Layers);
417    for (i = 1; i <= flake->Layers; i++)
418      {
419         flake->Flakes[i - 1] = malloc(sizeof(FlakeVariable *) * flake->Density);
420         for (j = 0; j < flake->Density; j++)
421           {
422              flake->Flakes[i - 1][j] = malloc(sizeof(FlakeVariable));
423              flake->Flakes[i - 1][j]->XPos = random() % flake->XGWA.width;
424              flake->Flakes[i - 1][j]->YPos = random() % flake->XGWA.height;
425              flake->Flakes[i - 1][j]->Angle = random() % 360 * (M_PI / 180);
426              flake->Flakes[i - 1][j]->Ticks = random() % 360;
427              flake->Flakes[i - 1][j]->XOffset = random() % flake->XGWA.height;
428           }
429      }
430 }
431
432 static void
433 FuzzyFlakesFreeFlake(Flake *flake)
434 {
435    int                 i, j;
436
437    for (i = 1; i <= flake->Layers; i++)
438      {
439         for (j = 0; j < flake->Density; j++)
440           {
441              free(flake->Flakes[i - 1][j]);
442           }
443         free(flake->Flakes[i - 1]);
444      }
445
446    if (flake->DB.bb) XFreePixmap(flake->dpy, flake->DB.bb);
447    if (flake->DB.ba) XFreePixmap(flake->dpy, flake->DB.ba);
448 }
449
450 static void
451 FuzzyFlakesMove(Flake *flake)
452 {
453    int                 i, j;
454
455    for (i = 1; i <= flake->Layers; i++)
456      {
457         for (j = 0; j < flake->Density; j++)
458           {
459              FlakeVariable      *FlakeVar;
460
461              FlakeVar = flake->Flakes[i - 1][j];
462              FlakeVar->Ticks++;
463              FlakeVar->YPos =
464                 FlakeVar->YPos + ((double)flake->FallingSpeed) / 10 / i;
465              FlakeVar->TrueX =
466                 (sin
467                  (FlakeVar->XOffset +
468                   FlakeVar->Ticks * (M_PI / 180) * ((double)flake->FallingSpeed /
469                                                     10))) * 10 + FlakeVar->XPos;
470              FlakeVar->Angle =
471                 FlakeVar->Angle + 0.005 * ((double)flake->FallingSpeed / 10);
472              if (FlakeVar->YPos - flake->Radius > flake->XGWA.height)
473                {
474                   FlakeVar->Ticks = 0;
475                   FlakeVar->YPos = 0 - flake->Radius;
476                }
477           }
478      }
479 }
480
481 static void
482 FuzzyFlakesDrawFlake(Flake *flake, int XPos, int YPos, double AngleOffset, int Layer)
483 {
484    int                 i;
485    double              x, y, Angle, Radius;
486
487    /* calculate the shrink factor debending on which layer we are drawing atm */
488    Radius = (double)(flake->Radius - Layer * 5);
489    /* draw the flake one arm at a time */
490    for (i = 1; i <= flake->Arms; i++)
491      {
492         int                 Diameter;
493
494         Diameter = (flake->BorderThickness * 2 + flake->Thickness) / Layer;
495         /* compute the angle of this arm of the flake */
496         Angle = ((2 * M_PI) / flake->Arms) * i + AngleOffset;
497         /* calculate the x and y dispositions for this arm */
498         y = (int)(sin(Angle) * Radius);
499         x = (int)(cos(Angle) * Radius);
500         /* draw the base for the arm */
501         flake->GCValues.line_width = Diameter;
502         XFreeGC(flake->dpy, flake->GCVar);
503         flake->GCVar =
504            XCreateGC(flake->dpy, flake->DB.b, flake->GCFlags,
505                      &flake->GCValues);
506         XSetForeground(flake->dpy, flake->GCVar, flake->BordColor);
507         XDrawLine(flake->dpy, flake->DB.b, flake->GCVar, XPos, YPos,
508                   XPos + x, YPos + y);
509      }
510    /* draw the flake one arm at a time */
511    for (i = 1; i <= flake->Arms; i++)
512      {
513         /* compute the angle of this arm of the flake */
514         Angle = ((2 * M_PI) / flake->Arms) * i + AngleOffset;
515         /* calculate the x and y dispositions for this arm */
516         y = (int)(sin(Angle) * Radius);
517         x = (int)(cos(Angle) * Radius);
518         /* draw the inside of the arm */
519         flake->GCValues.line_width = flake->Thickness / Layer;
520         XFreeGC(flake->dpy, flake->GCVar);
521         flake->GCVar =
522            XCreateGC(flake->dpy, flake->DB.b, flake->GCFlags,
523                      &flake->GCValues);
524         XSetForeground(flake->dpy, flake->GCVar, flake->ForeColor);
525         XDrawLine(flake->dpy, flake->DB.b, flake->GCVar, XPos, YPos,
526                   XPos + x, YPos + y);
527      }
528 }
529
530 static void
531 FuzzyFlakes(Flake *flake)
532 {
533    int                 i, j;
534
535    FuzzyFlakesMove(flake);
536    XSetForeground(flake->dpy, flake->GCVar, flake->BackColor);
537    XFillRectangle(flake->dpy, flake->DB.b, flake->GCVar, 0, 0,
538                   flake->XGWA.width, flake->XGWA.height);
539    for (i = flake->Layers; i >= 1; i--)
540      {
541         for (j = 0; j < flake->Density; j++)
542           {
543              FuzzyFlakesDrawFlake(flake,
544                                   flake->Flakes[i - 1][j]->TrueX,
545                                   flake->Flakes[i - 1][j]->YPos,
546                                   flake->Flakes[i - 1][j]->Angle, i);
547           }
548      }
549
550 }
551
552 static void *
553 fuzzyflakes_init (Display *dpy, Window window)
554 {
555    Flake *flake = (Flake *) calloc (1, sizeof(*flake));
556    flake->dpy = dpy;
557    flake->window = window;
558
559    /* This is needed even if it is going to be set to false */
560    flake->RandomColors = True;
561
562    /* set up our colors amoung many other things */
563    FuzzyFlakesInit(flake);
564
565    return flake;
566 }
567
568 static unsigned long
569 fuzzyflakes_draw (Display *dpy, Window window, void *closure)
570 {
571   Flake *flake = (Flake *) closure;
572
573   FuzzyFlakes(flake);
574   if (flake->DB.dbuf)
575     {
576       XCopyArea(flake->dpy, flake->DB.b, flake->window,
577                 flake->GCVar, 0, 0, flake->XGWA.width, flake->XGWA.height,
578                 0, 0);
579       flake->DB.b =
580         (flake->DB.b == flake->DB.ba ? flake->DB.bb : flake->DB.ba);
581     }
582
583   return flake->Delay;
584 }
585
586 static void
587 fuzzyflakes_reshape (Display *dpy, Window window, void *closure, 
588                  unsigned int w, unsigned int h)
589 {
590   Flake *flake = (Flake *) closure;
591
592   if (flake->XGWA.width != w || flake->XGWA.height != h)
593     {
594       FuzzyFlakesFreeFlake(flake);
595       FuzzyFlakesInit(flake);
596     }
597 }
598
599 static Bool
600 fuzzyflakes_event (Display *dpy, Window window, void *closure, XEvent *event)
601 {
602   return False;
603 }
604
605 static void
606 fuzzyflakes_free (Display *dpy, Window window, void *closure)
607 {
608   Flake *flake = (Flake *) closure;
609   FuzzyFlakesFreeFlake(flake);
610   free(flake);
611 }
612
613
614 static const char *fuzzyflakes_defaults[] = {
615    "*color:     #efbea5",
616    "*arms:      5",
617    "*thickness: 10",
618    "*bthickness:        3",
619    "*radius:    20",
620    "*layers:    3",
621    "*density:   5",
622    "*fallingspeed:      10",
623    "*delay:     10000",
624    "*doubleBuffer:      True",
625    "*randomColors:      False",
626    0
627 };
628
629 static XrmOptionDescRec fuzzyflakes_options[] = {
630    { "-color", ".color", XrmoptionSepArg, 0},
631    { "-arms", ".arms", XrmoptionSepArg, 0},
632    { "-thickness", ".thickness", XrmoptionSepArg, 0},
633    { "-bthickness", ".bthickness", XrmoptionSepArg, 0},
634    { "-radius", ".radius", XrmoptionSepArg, 0},
635    { "-layers", ".layers", XrmoptionSepArg, 0},
636    { "-density", ".density", XrmoptionSepArg, 0},
637    { "-speed", ".fallingspeed", XrmoptionSepArg, 0},
638    { "-delay", ".delay", XrmoptionSepArg, 0},
639    { "-db", ".doubleBuffer", XrmoptionNoArg, "True"},
640    { "-no-db", ".doubleBuffer", XrmoptionNoArg, "False"},
641    { "-random-colors", ".randomColors", XrmoptionNoArg, "True"},
642    { 0, 0, 0, 0}
643 };
644
645
646 XSCREENSAVER_MODULE ("FuzzyFlakes", fuzzyflakes)