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