7a1a0ffc13c258bad17567ba44150f7e6ee809d3
[xscreensaver] / hacks / shadebobs.c
1 /* shadebobs, Copyright (c) 1999 Shane Smit <blackend@inconnect.com>
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  * Module - "shadebobs.c"
12  *
13  * Description:
14  *  There are two little shading circles (bobs) that zip around the screen.
15  *  one of them shades up towards white, and the other shades down toward
16  *  black.
17  *  This keeps the screen in color balance at a chosen color.
18  *
19  *  Its kinda like 'The Force'
20  *   There is a light side, a dark side, and it keeps the world in balance.
21  *
22  * [05/23/99] - Shane Smit: Creation
23  * [05/26/99] - Shane Smit: Port to C/screenhack for use with XScreenSaver
24  * [06/11/99] - Shane Smit: Stopped trying to rape the palette.
25  * [06/20/99] - jwz: cleaned up ximage handling, gave resoources short names,
26  *                introduced delay, made it restart after N iterations.
27  * [06/21/99] - Shane Smit: Modified default values slightly, color changes
28  *                on cycle, and the extents of the sinus pattern change in
29  *                real-time.
30  * [06/22/99] - Shane Smit: Fixed delay to be fast and use little CPU :).
31  * [09/17/99] - Shane Smit: Made all calculations based on the size of the
32  *                window. Thus, it'll look the same at 100x100 as it does at
33  *                1600x1200 ( Only smaller :).
34  * [04/24/00] - Shane Smit: Revamped entire source code:
35  *                Shade Bob movement is calculated very differently.
36  *                Base color can be any color now.
37  */
38
39
40 #include <math.h>
41 #include "screenhack.h"
42
43 /* #define VERBOSE */
44
45 static const char *shadebobs_defaults [] = {
46   ".background: black",
47   ".foreground: white",
48   "*fpsSolid:   true",
49   "*degrees:  0",       /* default: Automatic degree calculation */
50   "*color:    random",
51   "*count:    4",
52   "*cycles:   10",
53   "*ncolors:  64",    /* changing this doesn't work particularly well */
54   "*delay:    10000",
55 #ifdef HAVE_MOBILE
56   "*ignoreRotation: True",
57 #endif
58   0
59 };
60
61 static XrmOptionDescRec shadebobs_options [] = {
62   { "-degrees", ".degrees", XrmoptionSepArg, 0 },
63   { "-color",   ".color",   XrmoptionSepArg, 0 },
64   { "-ncolors", ".ncolors", XrmoptionSepArg, 0 },
65   { "-count",   ".count",   XrmoptionSepArg, 0 },
66   { "-delay",   ".delay",   XrmoptionSepArg, 0 },
67   { "-cycles",  ".cycles",  XrmoptionSepArg, 0 },
68   { 0, 0, 0, 0 }
69 };
70
71 /* Ahem. Chocolate is a flavor; not a food. Thank you */
72
73
74 typedef struct
75 {
76         signed char *anDeltaMap;
77         double nAngle, nAngleDelta, nAngleInc;
78         double nPosX, nPosY;
79 } SShadeBob;
80
81 struct state {
82   Display *dpy;
83   Window window;
84   unsigned short iDegreeCount;
85   double *anSinTable, *anCosTable;
86   unsigned short iWinWidth, iWinHeight;
87   unsigned short iWinCenterX, iWinCenterY;
88   char *sColor;
89   unsigned char iBobRadius, iBobDiameter; 
90   unsigned char iVelocity;
91
92   unsigned long *aiColorVals;
93   signed short iColorCount;
94   int cycles;
95   XImage *pImage;
96   unsigned char nShadeBobCount, iShadeBob;
97   SShadeBob *aShadeBobs;
98   GC gc;
99   int delay;
100   int draw_i;
101 };
102
103 #define RANDOM() ((int) (random() & 0X7FFFFFFFL))
104
105
106
107 static void ResetShadeBob( struct state *st, SShadeBob *pShadeBob )
108 {
109         pShadeBob->nPosX = RANDOM() % st->iWinWidth;
110         pShadeBob->nPosY = RANDOM() % st->iWinHeight;
111         pShadeBob->nAngle = RANDOM() % st->iDegreeCount;
112         pShadeBob->nAngleDelta = ( RANDOM() % st->iDegreeCount ) - ( st->iDegreeCount / 2.0F );
113         pShadeBob->nAngleInc = pShadeBob->nAngleDelta / 50.0F; 
114         if( pShadeBob->nAngleInc == 0.0F )      
115                 pShadeBob->nAngleInc = ( pShadeBob->nAngleDelta > 0.0F ) ? 0.0001F : -0.0001F;
116 }
117
118
119 static void InitShadeBob( struct state *st, SShadeBob *pShadeBob, Bool bDark )
120 {
121         double nDelta;
122         int iWidth, iHeight;
123
124         if( ( pShadeBob->anDeltaMap = calloc( st->iBobDiameter * st->iBobDiameter, sizeof(char) ) ) == NULL )
125         {
126                 fprintf( stderr, "%s: Could not allocate Delta Map!\n", progname );
127                 return;
128         }
129
130         for( iHeight=-st->iBobRadius; iHeight<st->iBobRadius; iHeight++ )
131                 for( iWidth=-st->iBobRadius; iWidth<st->iBobRadius; iWidth++ )
132                 {
133                         nDelta = 9 - ( ( sqrt( pow( iWidth+0.5, 2 ) + pow( iHeight+0.5, 2 ) ) / st->iBobRadius ) * 8 );
134                         if( nDelta < 0 )  nDelta = 0;
135                         if( bDark ) nDelta = -nDelta;
136                         pShadeBob->anDeltaMap[ ( iWidth + st->iBobRadius ) * st->iBobDiameter + iHeight + st->iBobRadius ] = (char)nDelta;
137                 }
138   
139         ResetShadeBob( st, pShadeBob );
140 }
141
142
143 /* A delta is calculated, and the shadebob turns at an increment.  When the delta
144  * falls to 0, a new delta and increment are calculated. */
145 static void MoveShadeBob( struct state *st, SShadeBob *pShadeBob )
146 {
147         pShadeBob->nAngle          += pShadeBob->nAngleInc;
148         pShadeBob->nAngleDelta -= pShadeBob->nAngleInc;
149
150         /* Since it can happen that nAngle < 0 and nAngle + iDegreeCount >= iDegreeCount
151            on floating point, we set some marginal value.
152         */
153         if( pShadeBob->nAngle + 0.5 >= st->iDegreeCount )       pShadeBob->nAngle -= st->iDegreeCount;
154         else if( pShadeBob->nAngle < -0.5 )             pShadeBob->nAngle += st->iDegreeCount;
155         
156         if( ( pShadeBob->nAngleInc>0.0F  && pShadeBob->nAngleDelta<pShadeBob->nAngleInc ) ||
157             ( pShadeBob->nAngleInc<=0.0F && pShadeBob->nAngleDelta>pShadeBob->nAngleInc ) )
158         {
159                 pShadeBob->nAngleDelta = ( RANDOM() % st->iDegreeCount ) - ( st->iDegreeCount / 2.0F );
160                 pShadeBob->nAngleInc = pShadeBob->nAngleDelta / 50.0F;
161                 if( pShadeBob->nAngleInc == 0.0F )
162                         pShadeBob->nAngleInc = ( pShadeBob->nAngleDelta > 0.0F ) ? 0.0001F : -0.0001F;
163         }
164         
165         pShadeBob->nPosX = ( st->anSinTable[ (int)pShadeBob->nAngle ] * st->iVelocity ) + pShadeBob->nPosX;
166         pShadeBob->nPosY = ( st->anCosTable[ (int)pShadeBob->nAngle ] * st->iVelocity ) + pShadeBob->nPosY;
167
168         /* This wraps it around the screen. */
169         if( pShadeBob->nPosX >= st->iWinWidth ) pShadeBob->nPosX -= st->iWinWidth;
170         else if( pShadeBob->nPosX < 0 )         pShadeBob->nPosX += st->iWinWidth;
171         
172         if( pShadeBob->nPosY >= st->iWinHeight )        pShadeBob->nPosY -= st->iWinHeight;
173         else if( pShadeBob->nPosY < 0 )                 pShadeBob->nPosY += st->iWinHeight;
174 }
175
176
177 static void Execute( struct state *st, SShadeBob *pShadeBob )
178 {
179         unsigned long iColor;
180         short iColorVal;
181         int iPixelX, iPixelY;
182         unsigned int iWidth, iHeight;
183
184         MoveShadeBob( st, pShadeBob );
185         
186         for( iHeight=0; iHeight<st->iBobDiameter; iHeight++ )
187         {
188                 iPixelY = pShadeBob->nPosY + iHeight;
189                 if( iPixelY >= st->iWinHeight ) iPixelY -= st->iWinHeight;
190
191                 for( iWidth=0; iWidth<st->iBobDiameter; iWidth++ )
192                 {
193                         iPixelX = pShadeBob->nPosX + iWidth;
194                         if( iPixelX >= st->iWinWidth )  iPixelX -= st->iWinWidth;
195
196                         iColor = XGetPixel( st->pImage, iPixelX, iPixelY );
197
198                         /*  FIXME: Here is a loop I'd love to take out. */
199                         for( iColorVal=0; iColorVal<st->iColorCount; iColorVal++ )
200                                 if( st->aiColorVals[ iColorVal ] == iColor )
201                                         break;
202
203                         iColorVal += pShadeBob->anDeltaMap[ iWidth * st->iBobDiameter + iHeight ];
204                         if( iColorVal >= st->iColorCount ) iColorVal = st->iColorCount - 1;
205                         if( iColorVal < 0 )                        iColorVal = 0;
206
207                         XPutPixel( st->pImage, iPixelX, iPixelY, st->aiColorVals[ iColorVal ] );
208                 }
209         }
210
211         /* FIXME: if it's next to the top or left sides of screen this will break. However, it's not noticable. */
212         XPutImage( st->dpy, st->window, st->gc, st->pImage,
213              pShadeBob->nPosX, pShadeBob->nPosY, pShadeBob->nPosX, pShadeBob->nPosY, st->iBobDiameter, st->iBobDiameter );
214 }
215
216
217 static void CreateTables( struct state *st, unsigned int nDegrees )
218 {
219         double nRadian;
220         unsigned int iDegree;
221         st->anSinTable = calloc( nDegrees, sizeof(double) );
222         st->anCosTable = calloc( nDegrees, sizeof(double) );
223
224         for( iDegree=0; iDegree<nDegrees; iDegree++ )
225         {
226                 nRadian = ( (double)(2*iDegree) / (double)nDegrees ) * M_PI;
227                 st->anSinTable[ iDegree ] = sin( nRadian );
228                 st->anCosTable[ iDegree ] = cos( nRadian );
229         }
230 }
231
232
233 static unsigned long * SetPalette(struct state *st )
234 {
235         XWindowAttributes XWinAttribs;
236         XColor Color, *aColors;
237         signed short iColor;
238         float nHalfColors;
239         
240         XGetWindowAttributes( st->dpy, st->window, &XWinAttribs );
241         
242         Color.red =   RANDOM() % 0xFFFF;
243         Color.green = RANDOM() % 0xFFFF;
244         Color.blue =  RANDOM() % 0xFFFF;
245
246         if( strcasecmp( st->sColor, "random" ) && !XParseColor( st->dpy, XWinAttribs.colormap, st->sColor, &Color ) )
247                 fprintf( stderr, "%s: color %s not found in database. Choosing to random...\n", progname, st->sColor );
248
249 #ifdef VERBOSE
250         printf( "%s: Base color (RGB): <%d, %d, %d>\n", progname, Color.red, Color.green, Color.blue );
251 #endif  /*  VERBOSE */
252
253         st->iColorCount = get_integer_resource(st->dpy,  "ncolors", "Integer" );
254         if( st->iColorCount <   2 )     st->iColorCount = 2;
255         if( st->iColorCount > 255 )     st->iColorCount = 255;
256
257         aColors    = calloc( st->iColorCount, sizeof(XColor) );
258         st->aiColorVals = calloc( st->iColorCount, sizeof(unsigned long) );
259         
260         for( iColor=0; iColor<st->iColorCount; iColor++ )
261         {
262                 nHalfColors = st->iColorCount / 2.0F;
263                 /* Black -> Base Color */
264                 if( iColor < (st->iColorCount/2) )
265                 {
266                         aColors[ iColor ].red   = ( Color.red   / nHalfColors ) * iColor;
267                         aColors[ iColor ].green = ( Color.green / nHalfColors ) * iColor;
268                         aColors[ iColor ].blue  = ( Color.blue  / nHalfColors ) * iColor;
269                 }
270                 /* Base Color -> White */
271                 else
272                 {
273                         aColors[ iColor ].red   = ( ( ( 0xFFFF - Color.red )   / nHalfColors ) * ( iColor - nHalfColors ) ) + Color.red;
274                         aColors[ iColor ].green = ( ( ( 0xFFFF - Color.green ) / nHalfColors ) * ( iColor - nHalfColors ) ) + Color.green;
275                         aColors[ iColor ].blue  = ( ( ( 0xFFFF - Color.blue )  / nHalfColors ) * ( iColor - nHalfColors ) ) + Color.blue;
276                 }
277
278                 if( !XAllocColor( st->dpy, XWinAttribs.colormap, &aColors[ iColor ] ) )
279                 {
280                         /* start all over with less colors */   
281                         XFreeColors( st->dpy, XWinAttribs.colormap, st->aiColorVals, iColor, 0 );
282                         free( aColors );
283                         free( st->aiColorVals );
284                         st->iColorCount--;
285                         aColors     = calloc( st->iColorCount, sizeof(XColor) );
286                         st->aiColorVals = calloc( st->iColorCount, sizeof(unsigned long) );
287                         iColor = -1;
288                 }
289                 else
290                         st->aiColorVals[ iColor ] = aColors[ iColor ].pixel;
291         }
292
293         free( aColors );
294
295         XSetWindowBackground( st->dpy, st->window, st->aiColorVals[ 0 ] );
296
297         return st->aiColorVals;
298 }
299
300
301 static void Initialize( struct state *st )
302 {
303         XGCValues gcValues;
304         XWindowAttributes XWinAttribs;
305         /*int iBitsPerPixel;*/
306
307         /* Create the Image for drawing */
308         XGetWindowAttributes( st->dpy, st->window, &XWinAttribs );
309
310 #if 0
311   /* Find the preferred bits-per-pixel. (jwz) */
312         {
313                 int i, pfvc = 0;
314                 XPixmapFormatValues *pfv = XListPixmapFormats( st->dpy, &pfvc );
315                 for( i=0; i<pfvc; i++ )
316                         if( pfv[ i ].depth == XWinAttribs.depth )
317                         {
318                                 iBitsPerPixel = pfv[ i ].bits_per_pixel;
319                                 break;
320                         }
321                 if( pfv )
322                         XFree (pfv);
323         }
324 #endif
325
326         /*  Create the GC. */
327         st->gc = XCreateGC( st->dpy, st->window, 0, &gcValues );
328
329         st->pImage = XCreateImage( st->dpy, XWinAttribs.visual, XWinAttribs.depth, ZPixmap, 0, NULL,
330                                                           XWinAttribs.width, XWinAttribs.height, 8 /*BitmapPad( st->dpy )*/, 0 );
331         st->pImage->data = calloc((st->pImage)->bytes_per_line, (st->pImage)->height);
332
333         st->iWinWidth = XWinAttribs.width;
334         st->iWinHeight = XWinAttribs.height;
335
336         /*  These are precalculations used in Execute(). */
337         st->iBobDiameter = ( ( st->iWinWidth < st->iWinHeight ) ? st->iWinWidth : st->iWinHeight ) / 25;
338         st->iBobRadius = st->iBobDiameter / 2;
339 #ifdef VERBOSE
340         printf( "%s: Bob Diameter = %d\n", progname, st->iBobDiameter );
341 #endif
342
343         st->iWinCenterX = ( XWinAttribs.width / 2 ) - st->iBobRadius;
344         st->iWinCenterY = ( XWinAttribs.height / 2 ) - st->iBobRadius;
345
346         st->iVelocity = ( ( st->iWinWidth < st->iWinHeight ) ? st->iWinWidth : st->iWinHeight ) / 150;
347         
348         /*  Create the Sin and Cosine lookup tables. */
349         st->iDegreeCount = get_integer_resource(st->dpy,  "degrees", "Integer" );
350         if(      st->iDegreeCount == 0   ) st->iDegreeCount = ( XWinAttribs.width / 6 ) + 400;
351         else if( st->iDegreeCount < 90   ) st->iDegreeCount = 90;
352         else if( st->iDegreeCount > 5400 ) st->iDegreeCount = 5400;
353         CreateTables( st, st->iDegreeCount );
354 #ifdef VERBOSE
355         printf( "%s: Using a %d degree circle.\n", progname, st->iDegreeCount );
356 #endif /* VERBOSE */
357   
358         /*  Get the base color. */
359         st->sColor = get_string_resource(st->dpy,  "color", "Color" );
360 }
361
362
363 static void *
364 shadebobs_init (Display *dpy, Window window)
365 {
366   struct state *st = (struct state *) calloc (1, sizeof(*st));
367
368 #ifdef VERBOSE
369         time_t nTime = time( NULL );
370         unsigned short iFrame = 0;
371 #endif  /*  VERBOSE */
372
373   st->dpy = dpy;
374   st->window = window;
375
376         st->nShadeBobCount = get_integer_resource(st->dpy,  "count", "Integer" );
377         if( st->nShadeBobCount > 64 ) st->nShadeBobCount = 64;
378         if( st->nShadeBobCount <  1 ) st->nShadeBobCount = 1;
379
380         if( ( st->aShadeBobs = calloc( st->nShadeBobCount, sizeof(SShadeBob) ) ) == NULL )
381         {
382                 fprintf( stderr, "%s: Could not allocate %d ShadeBobs\n", progname, st->nShadeBobCount );
383                 abort();
384         }
385 #ifdef VERBOSE 
386         printf( "%s: Allocated %d ShadeBobs\n", progname, st->nShadeBobCount );
387 #endif  /*  VERBOSE */
388
389         Initialize( st );
390
391         for( st->iShadeBob=0; st->iShadeBob<st->nShadeBobCount; st->iShadeBob++ )
392                 InitShadeBob( st, &st->aShadeBobs[ st->iShadeBob ], st->iShadeBob % 2 );
393
394         st->delay = get_integer_resource(st->dpy,  "delay", "Integer" );
395         st->cycles = get_integer_resource(st->dpy,  "cycles", "Integer" ) * st->iDegreeCount;
396
397         st->draw_i = 99999999;
398         return st;
399 }
400
401 static unsigned long
402 shadebobs_draw (Display *dpy, Window window, void *closure)
403 {
404   struct state *st = (struct state *) closure;
405
406   if( st->draw_i++ >= st->cycles )
407     {
408       XWindowAttributes XWinAttribs;
409       XGetWindowAttributes( st->dpy, st->window, &XWinAttribs );
410
411       st->draw_i = 0;
412 #if 0
413       memset( st->pImage->data, 0, st->pImage->bytes_per_line * st->pImage->height );
414 #else
415       {
416         /* fill the image with the actual value of the black pixel, not 0. */
417         unsigned long black = BlackPixelOfScreen (XWinAttribs.screen);
418         int x, y;
419         for (y = 0; y < XWinAttribs.height; y++)
420           for (x = 0; x < XWinAttribs.width; x++)
421             XPutPixel (st->pImage, x, y, black);
422       }
423 #endif
424
425       for( st->iShadeBob=0; st->iShadeBob<st->nShadeBobCount; st->iShadeBob++ )
426         ResetShadeBob( st, &st->aShadeBobs[ st->iShadeBob ] );
427       XFreeColors( st->dpy, XWinAttribs.colormap, st->aiColorVals, st->iColorCount, 0 );
428       free( st->aiColorVals );
429       st->aiColorVals = SetPalette( st );
430       XClearWindow( st->dpy, st->window );
431     }
432
433   for( st->iShadeBob=0; st->iShadeBob<st->nShadeBobCount; st->iShadeBob++ )
434     Execute( st, &st->aShadeBobs[ st->iShadeBob ] );
435
436   return st->delay;
437 }
438
439 static void
440 shadebobs_reshape (Display *dpy, Window window, void *closure, 
441                  unsigned int w, unsigned int h)
442 {
443 }
444
445 static Bool
446 shadebobs_event (Display *dpy, Window window, void *closure, XEvent *event)
447 {
448   return False;
449 }
450
451 static void
452 shadebobs_free (Display *dpy, Window window, void *closure)
453 {
454   struct state *st = (struct state *) closure;
455         free( st->anSinTable );
456         free( st->anCosTable );
457         /* free( st->pImage->data ); */
458         XDestroyImage( st->pImage );
459         for( st->iShadeBob=0; st->iShadeBob<st->nShadeBobCount; st->iShadeBob++ )
460                 free( st->aShadeBobs[ st->iShadeBob ].anDeltaMap );
461         free( st->aShadeBobs );
462         free( st->aiColorVals );
463 }
464
465
466 XSCREENSAVER_MODULE ("ShadeBobs", shadebobs)
467
468 /* End of Module - "shadebobs.c" */
469
470 /* vim: ts=4
471  */