From http://www.jwz.org/xscreensaver/xscreensaver-5.35.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 (flake->Delay < 0)
353       flake->Delay = 0;
354
355    if (!flake->Colors.Back)
356      {
357         flake->Colors.Back = get_string_resource(flake->dpy, "color", "Color");
358         if (!FuzzyFlakesColorResource(flake, flake->Colors.Back))
359           {
360              fprintf(stderr, " reverting to random\n");
361              flake->RandomColors = True;
362           }
363
364         if (flake->RandomColors)
365           {
366              if (flake->Colors.Back)
367                 free(flake->Colors.Back);
368              flake->Colors.Back = malloc(sizeof(unsigned char) * 8);
369              sprintf(flake->Colors.Back, "#%X%X%X%X%X%X", random() % 16,
370                      random() % 16, random() % 16, random() % 16, random() % 16,
371                      random() % 16);
372           }
373
374         /*
375          * Here we establish our colormap based on what is in
376          * flake->Colors.Back
377          */
378         if (FuzzyFlakesColorHelper(flake))
379           {
380              fprintf(stderr, " reverting to random\n");
381              if (flake->Colors.Back)
382                 free(flake->Colors.Back);
383              flake->Colors.Back = malloc(sizeof(unsigned char) * 8);
384              sprintf(flake->Colors.Back, "#%X%X%X%X%X%X", random() % 16,
385                      random() % 16, random() % 16, random() % 16, random() % 16,
386                      random() % 16);
387              FuzzyFlakesColorHelper(flake);
388           }
389
390         flake->ForeColor = FuzzyFlakesColorResource(flake, flake->Colors.Fore);
391         flake->BackColor = FuzzyFlakesColorResource(flake, flake->Colors.Back);
392         flake->BordColor = FuzzyFlakesColorResource(flake, flake->Colors.Bord);
393
394         flake->GCValues.foreground = flake->ForeColor;
395         flake->GCValues.background = flake->BackColor;
396         flake->RandomColors = False;
397      }
398
399    flake->GCValues.line_width = flake->Thickness;
400    flake->GCValues.cap_style = CapProjecting;
401    flake->GCValues.join_style = JoinMiter;
402    flake->GCFlags |= (GCLineWidth | GCCapStyle | GCJoinStyle);
403
404    flake->GCVar =
405       XCreateGC(flake->dpy, flake->window, flake->GCFlags,
406                 &flake->GCValues);
407
408    flake->Density = flake->XGWA.width / 200 * flake->Density;
409    flake->Flakes = malloc(sizeof(FlakeVariable **) * flake->Layers);
410    for (i = 1; i <= flake->Layers; i++)
411      {
412         flake->Flakes[i - 1] = malloc(sizeof(FlakeVariable *) * flake->Density);
413         for (j = 0; j < flake->Density; j++)
414           {
415              flake->Flakes[i - 1][j] = malloc(sizeof(FlakeVariable));
416              flake->Flakes[i - 1][j]->XPos = random() % flake->XGWA.width;
417              flake->Flakes[i - 1][j]->YPos = random() % flake->XGWA.height;
418              flake->Flakes[i - 1][j]->Angle = random() % 360 * (M_PI / 180);
419              flake->Flakes[i - 1][j]->Ticks = random() % 360;
420              flake->Flakes[i - 1][j]->XOffset = random() % flake->XGWA.height;
421           }
422      }
423 }
424
425 static void
426 FuzzyFlakesFreeFlake(Flake *flake)
427 {
428    int                 i, j;
429
430    for (i = 1; i <= flake->Layers; i++)
431      {
432         for (j = 0; j < flake->Density; j++)
433           {
434              free(flake->Flakes[i - 1][j]);
435           }
436         free(flake->Flakes[i - 1]);
437      }
438
439    if (flake->DB.bb) XFreePixmap(flake->dpy, flake->DB.bb);
440    if (flake->DB.ba) XFreePixmap(flake->dpy, flake->DB.ba);
441 }
442
443 static void
444 FuzzyFlakesMove(Flake *flake)
445 {
446    int                 i, j;
447
448    for (i = 1; i <= flake->Layers; i++)
449      {
450         for (j = 0; j < flake->Density; j++)
451           {
452              FlakeVariable      *FlakeVar;
453
454              FlakeVar = flake->Flakes[i - 1][j];
455              FlakeVar->Ticks++;
456              FlakeVar->YPos =
457                 FlakeVar->YPos + ((double)flake->FallingSpeed) / 10 / i;
458              FlakeVar->TrueX =
459                 (sin
460                  (FlakeVar->XOffset +
461                   FlakeVar->Ticks * (M_PI / 180) * ((double)flake->FallingSpeed /
462                                                     10))) * 10 + FlakeVar->XPos;
463              FlakeVar->Angle =
464                 FlakeVar->Angle + 0.005 * ((double)flake->FallingSpeed / 10);
465              if (FlakeVar->YPos - flake->Radius > flake->XGWA.height)
466                {
467                   FlakeVar->Ticks = 0;
468                   FlakeVar->YPos = 0 - flake->Radius;
469                }
470           }
471      }
472 }
473
474 static void
475 FuzzyFlakesDrawFlake(Flake *flake, int XPos, int YPos, double AngleOffset, int Layer)
476 {
477    int                 i;
478    double              x, y, Angle, Radius;
479
480    /* calculate the shrink factor debending on which layer we are drawing atm */
481    Radius = (double)(flake->Radius - Layer * 5);
482    /* draw the flake one arm at a time */
483    for (i = 1; i <= flake->Arms; i++)
484      {
485         int                 Diameter;
486
487         Diameter = (flake->BorderThickness * 2 + flake->Thickness) / Layer;
488         /* compute the angle of this arm of the flake */
489         Angle = ((2 * M_PI) / flake->Arms) * i + AngleOffset;
490         /* calculate the x and y dispositions for this arm */
491         y = (int)(sin(Angle) * Radius);
492         x = (int)(cos(Angle) * Radius);
493         /* draw the base for the arm */
494         flake->GCValues.line_width = Diameter;
495         XFreeGC(flake->dpy, flake->GCVar);
496         flake->GCVar =
497            XCreateGC(flake->dpy, flake->DB.b, flake->GCFlags,
498                      &flake->GCValues);
499         XSetForeground(flake->dpy, flake->GCVar, flake->BordColor);
500         XDrawLine(flake->dpy, flake->DB.b, flake->GCVar, XPos, YPos,
501                   XPos + x, YPos + y);
502      }
503    /* draw the flake one arm at a time */
504    for (i = 1; i <= flake->Arms; i++)
505      {
506         /* compute the angle of this arm of the flake */
507         Angle = ((2 * M_PI) / flake->Arms) * i + AngleOffset;
508         /* calculate the x and y dispositions for this arm */
509         y = (int)(sin(Angle) * Radius);
510         x = (int)(cos(Angle) * Radius);
511         /* draw the inside of the arm */
512         flake->GCValues.line_width = flake->Thickness / Layer;
513         XFreeGC(flake->dpy, flake->GCVar);
514         flake->GCVar =
515            XCreateGC(flake->dpy, flake->DB.b, flake->GCFlags,
516                      &flake->GCValues);
517         XSetForeground(flake->dpy, flake->GCVar, flake->ForeColor);
518         XDrawLine(flake->dpy, flake->DB.b, flake->GCVar, XPos, YPos,
519                   XPos + x, YPos + y);
520      }
521 }
522
523 static void
524 FuzzyFlakes(Flake *flake)
525 {
526    int                 i, j;
527
528    FuzzyFlakesMove(flake);
529    XSetForeground(flake->dpy, flake->GCVar, flake->BackColor);
530    XFillRectangle(flake->dpy, flake->DB.b, flake->GCVar, 0, 0,
531                   flake->XGWA.width, flake->XGWA.height);
532    for (i = flake->Layers; i >= 1; i--)
533      {
534         for (j = 0; j < flake->Density; j++)
535           {
536              FuzzyFlakesDrawFlake(flake,
537                                   flake->Flakes[i - 1][j]->TrueX,
538                                   flake->Flakes[i - 1][j]->YPos,
539                                   flake->Flakes[i - 1][j]->Angle, i);
540           }
541      }
542
543 }
544
545 static void *
546 fuzzyflakes_init (Display *dpy, Window window)
547 {
548    Flake *flake = (Flake *) calloc (1, sizeof(*flake));
549    flake->dpy = dpy;
550    flake->window = window;
551
552    /* This is needed even if it is going to be set to false */
553    flake->RandomColors = True;
554
555    /* set up our colors amoung many other things */
556    FuzzyFlakesInit(flake);
557
558    return flake;
559 }
560
561 static unsigned long
562 fuzzyflakes_draw (Display *dpy, Window window, void *closure)
563 {
564   Flake *flake = (Flake *) closure;
565
566   FuzzyFlakes(flake);
567   if (flake->DB.dbuf)
568     {
569       XCopyArea(flake->dpy, flake->DB.b, flake->window,
570                 flake->GCVar, 0, 0, flake->XGWA.width, flake->XGWA.height,
571                 0, 0);
572       flake->DB.b =
573         (flake->DB.b == flake->DB.ba ? flake->DB.bb : flake->DB.ba);
574     }
575
576   return flake->Delay;
577 }
578
579 static void
580 fuzzyflakes_reshape (Display *dpy, Window window, void *closure, 
581                  unsigned int w, unsigned int h)
582 {
583   Flake *flake = (Flake *) closure;
584
585   if (flake->XGWA.width != w || flake->XGWA.height != h)
586     {
587       FuzzyFlakesFreeFlake(flake);
588       FuzzyFlakesInit(flake);
589     }
590 }
591
592 static Bool
593 fuzzyflakes_event (Display *dpy, Window window, void *closure, XEvent *event)
594 {
595   return False;
596 }
597
598 static void
599 fuzzyflakes_free (Display *dpy, Window window, void *closure)
600 {
601   Flake *flake = (Flake *) closure;
602   FuzzyFlakesFreeFlake(flake);
603   free(flake);
604 }
605
606
607 static const char *fuzzyflakes_defaults[] = {
608    "*color:     #efbea5",
609    "*arms:      5",
610    "*thickness: 10",
611    "*bthickness:        3",
612    "*radius:    20",
613    "*layers:    3",
614    "*density:   5",
615    "*fallingspeed:      10",
616    "*delay:     10000",
617    "*doubleBuffer:      True",
618    "*randomColors:      False",
619    0
620 };
621
622 static XrmOptionDescRec fuzzyflakes_options[] = {
623    { "-color", ".color", XrmoptionSepArg, 0},
624    { "-arms", ".arms", XrmoptionSepArg, 0},
625    { "-thickness", ".thickness", XrmoptionSepArg, 0},
626    { "-bthickness", ".bthickness", XrmoptionSepArg, 0},
627    { "-radius", ".radius", XrmoptionSepArg, 0},
628    { "-layers", ".layers", XrmoptionSepArg, 0},
629    { "-density", ".density", XrmoptionSepArg, 0},
630    { "-speed", ".fallingspeed", XrmoptionSepArg, 0},
631    { "-delay", ".delay", XrmoptionSepArg, 0},
632    { "-db", ".doubleBuffer", XrmoptionNoArg, "True"},
633    { "-no-db", ".doubleBuffer", XrmoptionNoArg, "False"},
634    { "-random-colors", ".randomColors", XrmoptionNoArg, "True"},
635    { 0, 0, 0, 0}
636 };
637
638
639 XSCREENSAVER_MODULE ("FuzzyFlakes", fuzzyflakes)