d384605add37067ddf55db4ccc62a4b5b945982a
[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 #include <math.h>
40 #include "screenhack.h"
41 #include <X11/Xutil.h>
42
43 /* #define VERBOSE */
44
45 char *progclass = "ShadeBobs";
46
47 char *defaults [] = {
48   "*degrees:  0",       /* default: Automatic degree calculation */
49   "*color:    random",
50   "*count:    4",
51   "*cycles:   10",
52   "*ncolors:  64",    /* changing this doesn't work particularly well */
53   "*delay:    5000",
54   0
55 };
56
57 XrmOptionDescRec options [] = {
58   { "-degrees", ".degrees", XrmoptionSepArg, 0 },
59   { "-color",   ".color",   XrmoptionSepArg, 0 },
60   { "-ncolors", ".ncolors", XrmoptionSepArg, 0 },
61   { "-count",   ".count",   XrmoptionSepArg, 0 },
62   { "-delay",   ".delay",   XrmoptionSepArg, 0 },
63   { "-cycles",  ".cycles",  XrmoptionSepArg, 0 },
64   { 0, 0, 0, 0 }
65 };
66
67 static unsigned short iDegreeCount;
68 static double *anSinTable, *anCosTable;
69 static unsigned short iWinWidth, iWinHeight;
70 static unsigned short iWinCenterX, iWinCenterY;
71 static char *sColor;
72 static unsigned char iBobRadius, iBobDiameter; 
73 static unsigned char iVelocity;
74
75 #define RANDOM() ((int) (random() & 0X7FFFFFFFL))
76
77
78 /* Ahem. Chocolate is a flavor; not a food. Thank you */
79
80
81 typedef struct
82 {
83         signed char *anDeltaMap;
84         double nAngle, nAngleDelta, nAngleInc;
85         double nPosX, nPosY;
86 } SShadeBob;
87
88
89 static void ResetShadeBob( SShadeBob *pShadeBob )
90 {
91         pShadeBob->nPosX = RANDOM() % iWinWidth;
92         pShadeBob->nPosY = RANDOM() % iWinHeight;
93         pShadeBob->nAngle = RANDOM() % iDegreeCount;
94         pShadeBob->nAngleDelta = ( RANDOM() % iDegreeCount ) - ( iDegreeCount / 2.0F );
95         pShadeBob->nAngleInc = pShadeBob->nAngleDelta / 50.0F; 
96         if( pShadeBob->nAngleInc == 0.0F )      
97                 pShadeBob->nAngleInc = ( pShadeBob->nAngleDelta > 0.0F ) ? 0.0001F : -0.0001F;
98 }
99
100
101 static void InitShadeBob( SShadeBob *pShadeBob, Bool bDark )
102 {
103         double nDelta;
104         int iWidth, iHeight;
105
106         if( ( pShadeBob->anDeltaMap = calloc( iBobDiameter * iBobDiameter, sizeof(char) ) ) == NULL )
107         {
108                 fprintf( stderr, "%s: Could not allocate Delta Map!\n", progclass );
109                 return;
110         }
111
112         for( iHeight=-iBobRadius; iHeight<iBobRadius; iHeight++ )
113                 for( iWidth=-iBobRadius; iWidth<iBobRadius; iWidth++ )
114                 {
115                         nDelta = 9 - ( ( sqrt( pow( iWidth+0.5, 2 ) + pow( iHeight+0.5, 2 ) ) / iBobRadius ) * 8 );
116                         if( nDelta < 0 )  nDelta = 0;
117                         if( bDark ) nDelta = -nDelta;
118                         pShadeBob->anDeltaMap[ ( iWidth + iBobRadius ) * iBobDiameter + iHeight + iBobRadius ] = (char)nDelta;
119                 }
120   
121         ResetShadeBob( pShadeBob );
122 }
123
124
125 /* A delta is calculated, and the shadebob turns at an increment.  When the delta
126  * falls to 0, a new delta and increment are calculated. */
127 static void MoveShadeBob( SShadeBob *pShadeBob )
128 {
129         pShadeBob->nAngle          += pShadeBob->nAngleInc;
130         pShadeBob->nAngleDelta -= pShadeBob->nAngleInc;
131
132         if( pShadeBob->nAngle >= iDegreeCount ) pShadeBob->nAngle -= iDegreeCount;
133         else if( pShadeBob->nAngle < 0 )                pShadeBob->nAngle += iDegreeCount;
134         
135         if( ( pShadeBob->nAngleInc>0.0F  && pShadeBob->nAngleDelta<pShadeBob->nAngleInc ) ||
136             ( pShadeBob->nAngleInc<=0.0F && pShadeBob->nAngleDelta>pShadeBob->nAngleInc ) )
137         {
138                 pShadeBob->nAngleDelta = ( RANDOM() % iDegreeCount ) - ( iDegreeCount / 2.0F );
139                 pShadeBob->nAngleInc = pShadeBob->nAngleDelta / 50.0F;
140                 if( pShadeBob->nAngleInc == 0.0F )
141                         pShadeBob->nAngleInc = ( pShadeBob->nAngleDelta > 0.0F ) ? 0.0001F : -0.0001F;
142         }
143         
144         pShadeBob->nPosX = ( anSinTable[ (int)pShadeBob->nAngle ] * iVelocity ) + pShadeBob->nPosX;
145         pShadeBob->nPosY = ( anCosTable[ (int)pShadeBob->nAngle ] * iVelocity ) + pShadeBob->nPosY;
146
147         /* This wraps it around the screen. */
148         if( pShadeBob->nPosX >= iWinWidth )     pShadeBob->nPosX -= iWinWidth;
149         else if( pShadeBob->nPosX < 0 )         pShadeBob->nPosX += iWinWidth;
150         
151         if( pShadeBob->nPosY >= iWinHeight )    pShadeBob->nPosY -= iWinHeight;
152         else if( pShadeBob->nPosY < 0 )                 pShadeBob->nPosY += iWinHeight;
153 }
154
155
156 static void Execute( SShadeBob *pShadeBob, Display *pDisplay,
157                      Window MainWindow,
158                      GC *pGC, XImage *pImage,
159                      signed short iColorCount, unsigned long *aiColorVals )
160 {
161         unsigned long iColor;
162         short iColorVal;
163         int iPixelX, iPixelY;
164         unsigned int iWidth, iHeight;
165
166         MoveShadeBob( pShadeBob );
167         
168         for( iHeight=0; iHeight<iBobDiameter; iHeight++ )
169         {
170                 iPixelY = pShadeBob->nPosY + iHeight;
171                 if( iPixelY >= iWinHeight )     iPixelY -= iWinHeight;
172
173                 for( iWidth=0; iWidth<iBobDiameter; iWidth++ )
174                 {
175                         iPixelX = pShadeBob->nPosX + iWidth;
176                         if( iPixelX >= iWinWidth )      iPixelX -= iWinWidth;
177
178                         iColor = XGetPixel( pImage, iPixelX, iPixelY );
179
180                         /*  FIXME: Here is a loop I'd love to take out. */
181                         for( iColorVal=0; iColorVal<iColorCount; iColorVal++ )
182                                 if( aiColorVals[ iColorVal ] == iColor )
183                                         break;
184
185                         iColorVal += pShadeBob->anDeltaMap[ iWidth * iBobDiameter + iHeight ];
186                         if( iColorVal >= iColorCount ) iColorVal = iColorCount - 1;
187                         if( iColorVal < 0 )                        iColorVal = 0;
188
189                         XPutPixel( pImage, iPixelX, iPixelY, aiColorVals[ iColorVal ] );
190                 }
191         }
192
193         /* FIXME: if it's next to the top or left sides of screen this will break. However, it's not noticable. */
194         XPutImage( pDisplay, MainWindow, *pGC, pImage,
195              pShadeBob->nPosX, pShadeBob->nPosY, pShadeBob->nPosX, pShadeBob->nPosY, iBobDiameter, iBobDiameter );
196         XSync( pDisplay, False );
197 }
198
199
200 static void CreateTables( unsigned int nDegrees )
201 {
202         double nRadian;
203         unsigned int iDegree;
204         anSinTable = calloc( nDegrees, sizeof(double) );
205         anCosTable = calloc( nDegrees, sizeof(double) );
206
207         for( iDegree=0; iDegree<nDegrees; iDegree++ )
208         {
209                 nRadian = ( (double)(2*iDegree) / (double)nDegrees ) * M_PI;
210                 anSinTable[ iDegree ] = sin( nRadian );
211                 anCosTable[ iDegree ] = cos( nRadian );
212         }
213 }
214
215
216 static unsigned long * SetPalette(Display *pDisplay, Window Win, char *sColor, signed short *piColorCount )
217 {
218         XWindowAttributes XWinAttribs;
219         XColor Color, *aColors;
220         unsigned long *aiColorVals;
221         signed short iColor;
222         float nHalfColors;
223         
224         XGetWindowAttributes( pDisplay, Win, &XWinAttribs );
225         
226         Color.red =   RANDOM() % 0xFFFF;
227         Color.green = RANDOM() % 0xFFFF;
228         Color.blue =  RANDOM() % 0xFFFF;
229
230         if( strcasecmp( sColor, "random" ) && !XParseColor( pDisplay, XWinAttribs.colormap, sColor, &Color ) )
231                 fprintf( stderr, "%s: color %s not found in database. Choosing to random...\n", progname, sColor );
232
233 #ifdef VERBOSE
234         printf( "%s: Base color (RGB): <%d, %d, %d>\n", progclass, Color.red, Color.green, Color.blue );
235 #endif  /*  VERBOSE */
236
237         *piColorCount = get_integer_resource( "ncolors", "Integer" );
238         if( *piColorCount <   2 )       *piColorCount = 2;
239         if( *piColorCount > 255 )       *piColorCount = 255;
240
241         aColors    = calloc( *piColorCount, sizeof(XColor) );
242         aiColorVals = calloc( *piColorCount, sizeof(unsigned long) );
243         
244         for( iColor=0; iColor<*piColorCount; iColor++ )
245         {
246                 nHalfColors = *piColorCount / 2.0F;
247                 /* Black -> Base Color */
248                 if( iColor < (*piColorCount/2) )
249                 {
250                         aColors[ iColor ].red   = ( Color.red   / nHalfColors ) * iColor;
251                         aColors[ iColor ].green = ( Color.green / nHalfColors ) * iColor;
252                         aColors[ iColor ].blue  = ( Color.blue  / nHalfColors ) * iColor;
253                 }
254                 /* Base Color -> White */
255                 else
256                 {
257                         aColors[ iColor ].red   = ( ( ( 0xFFFF - Color.red )   / nHalfColors ) * ( iColor - nHalfColors ) ) + Color.red;
258                         aColors[ iColor ].green = ( ( ( 0xFFFF - Color.green ) / nHalfColors ) * ( iColor - nHalfColors ) ) + Color.green;
259                         aColors[ iColor ].blue  = ( ( ( 0xFFFF - Color.blue )  / nHalfColors ) * ( iColor - nHalfColors ) ) + Color.blue;
260                 }
261
262                 if( !XAllocColor( pDisplay, XWinAttribs.colormap, &aColors[ iColor ] ) )
263                 {
264                         /* start all over with less colors */   
265                         XFreeColors( pDisplay, XWinAttribs.colormap, aiColorVals, iColor, 0 );
266                         free( aColors );
267                         free( aiColorVals );
268                         piColorCount--;
269                         aColors     = calloc( *piColorCount, sizeof(XColor) );
270                         aiColorVals = calloc( *piColorCount, sizeof(unsigned long) );
271                         iColor = -1;
272                 }
273                 else
274                         aiColorVals[ iColor ] = aColors[ iColor ].pixel;
275         }
276
277         free( aColors );
278
279         XSetWindowBackground( pDisplay, Win, aiColorVals[ 0 ] );
280
281         return aiColorVals;
282 }
283
284
285 static void Initialize( Display *pDisplay, Window Win, GC *pGC, XImage **ppImage )
286 {
287         XGCValues gcValues;
288         XWindowAttributes XWinAttribs;
289         int iBitsPerPixel;
290
291         /* Create the Image for drawing */
292         XGetWindowAttributes( pDisplay, Win, &XWinAttribs );
293
294         /* Find the preferred bits-per-pixel. (jwz) */
295         {
296                 int i, pfvc = 0;
297                 XPixmapFormatValues *pfv = XListPixmapFormats( pDisplay, &pfvc );
298                 for( i=0; i<pfvc; i++ )
299                         if( pfv[ i ].depth == XWinAttribs.depth )
300                         {
301                                 iBitsPerPixel = pfv[ i ].bits_per_pixel;
302                                 break;
303                         }
304                 if( pfv )
305                         XFree (pfv);
306         }
307
308         /*  Create the GC. */
309         *pGC = XCreateGC( pDisplay, Win, 0, &gcValues );
310
311         *ppImage = XCreateImage( pDisplay, XWinAttribs.visual, XWinAttribs.depth, ZPixmap, 0, NULL,
312                                                           XWinAttribs.width, XWinAttribs.height, BitmapPad( pDisplay ), 0 );
313         (*ppImage)->data = calloc((*ppImage)->bytes_per_line, (*ppImage)->height);
314
315         iWinWidth = XWinAttribs.width;
316         iWinHeight = XWinAttribs.height;
317
318         /*  These are precalculations used in Execute(). */
319         iBobDiameter = ( ( iWinWidth < iWinHeight ) ? iWinWidth : iWinHeight ) / 25;
320         iBobRadius = iBobDiameter / 2;
321 #ifdef VERBOSE
322         printf( "%s: Bob Diameter = %d\n", progclass, iBobDiameter );
323 #endif
324
325         iWinCenterX = ( XWinAttribs.width / 2 ) - iBobRadius;
326         iWinCenterY = ( XWinAttribs.height / 2 ) - iBobRadius;
327
328         iVelocity = ( ( iWinWidth < iWinHeight ) ? iWinWidth : iWinHeight ) / 150;
329         
330         /*  Create the Sin and Cosine lookup tables. */
331         iDegreeCount = get_integer_resource( "degrees", "Integer" );
332         if(      iDegreeCount == 0   ) iDegreeCount = ( XWinAttribs.width / 6 ) + 400;
333         else if( iDegreeCount < 90   ) iDegreeCount = 90;
334         else if( iDegreeCount > 5400 ) iDegreeCount = 5400;
335         CreateTables( iDegreeCount );
336 #ifdef VERBOSE
337         printf( "%s: Using a %d degree circle.\n", progclass );
338 #endif /* VERBOSE */
339   
340         /*  Get the base color. */
341         sColor = get_string_resource( "color", "Color" );
342 }
343
344
345 void screenhack(Display *pDisplay, Window Win )
346 {
347         GC gc;
348         signed short iColorCount = 0;
349         unsigned long *aiColorVals = NULL;
350         XImage *pImage = NULL;
351         unsigned char nShadeBobCount, iShadeBob;
352         SShadeBob *aShadeBobs;
353 #ifdef VERBOSE
354         time_t nTime = time( NULL );
355         unsigned short iFrame = 0;
356 #endif  /*  VERBOSE */
357         int delay, cycles, i;
358
359         nShadeBobCount = get_integer_resource( "count", "Integer" );
360         if( nShadeBobCount > 64 ) nShadeBobCount = 64;
361         if( nShadeBobCount <  1 ) nShadeBobCount = 1;
362
363         if( ( aShadeBobs = calloc( nShadeBobCount, sizeof(SShadeBob) ) ) == NULL )
364         {
365                 fprintf( stderr, "%s: Could not allocate %d ShadeBobs\n", progclass, nShadeBobCount );
366                 return;
367         }
368 #ifdef VERBOSE 
369         printf( "%s: Allocated %d ShadeBobs\n", progclass, nShadeBobCount );
370 #endif  /*  VERBOSE */
371
372         Initialize( pDisplay, Win, &gc, &pImage );
373
374         for( iShadeBob=0; iShadeBob<nShadeBobCount; iShadeBob++ )
375                 InitShadeBob( &aShadeBobs[ iShadeBob ], iShadeBob % 2 );
376
377         delay = get_integer_resource( "delay", "Integer" );
378         cycles = get_integer_resource( "cycles", "Integer" ) * iDegreeCount;
379         i = cycles;
380
381         while( 1 )
382         {
383                 screenhack_handle_events( pDisplay );
384
385                 if( i++ >= cycles )
386                 {
387                         XWindowAttributes XWinAttribs;
388                         XGetWindowAttributes( pDisplay, Win, &XWinAttribs );
389
390                         i = 0;
391                         memset( pImage->data, 0, pImage->bytes_per_line * pImage->height );
392                         for( iShadeBob=0; iShadeBob<nShadeBobCount; iShadeBob++ )
393                                 ResetShadeBob( &aShadeBobs[ iShadeBob ] );
394                         XFreeColors( pDisplay, XWinAttribs.colormap, aiColorVals, iColorCount, 0 );
395                         free( aiColorVals );
396                         aiColorVals = SetPalette( pDisplay, Win, sColor, &iColorCount );
397                         XClearWindow( pDisplay, Win );
398                 }
399
400                 for( iShadeBob=0; iShadeBob<nShadeBobCount; iShadeBob++ )
401                         Execute( &aShadeBobs[ iShadeBob ], pDisplay, Win, &gc, pImage, iColorCount, aiColorVals );
402
403                 if( delay && !(i % 4) )
404                         usleep(delay);
405
406 #ifdef VERBOSE
407                 iFrame++;
408                 if( nTime - time( NULL ) )
409                 {
410                         printf( "%s: %d FPS\n", progclass, iFrame );
411                         nTime = time( NULL );
412                         iFrame = 0;
413                 }
414 #endif  /*  VERBOSE */
415         }
416
417         free( anSinTable );
418         free( anCosTable );
419         free( pImage->data );
420         XDestroyImage( pImage );
421         for( iShadeBob=0; iShadeBob<nShadeBobCount; iShadeBob++ )
422                 free( aShadeBobs[ iShadeBob ].anDeltaMap );
423         free( aShadeBobs );
424         free( aiColorVals );
425 }
426
427
428 /* End of Module - "shadebobs.c" */
429
430 /* vim: ts=4
431  */