1 /* twang, twist around screen bits, v1.3
2 * by Dan Bornstein, danfuzz@milk.com
3 * Copyright (c) 2003 Dan Bornstein. All rights reserved.
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that
8 * copyright notice and this permission notice appear in supporting
9 * documentation. No representations are made about the suitability of this
10 * software for any purpose. It is provided "as is" without express or
13 * See the included man page for more details.
17 #include "screenhack.h"
19 #ifdef HAVE_XSHM_EXTENSION
25 /* random float in the range (-1..1) */
26 #define RAND_FLOAT_PM1 \
27 (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000) * 2 - 1)
29 /* random float in the range (0..1) */
30 #define RAND_FLOAT_01 \
31 (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000))
36 int x; /* x coordinate of the center of the tile */
37 int y; /* y coordinate of the center of the tile */
38 FLOAT angle; /* angle of the tile (-pi..pi) */
39 FLOAT zoom; /* log of the zoom of the tile (-1..1) */
40 FLOAT vAngle; /* angular velocity (-pi/4..pi/4) */
41 FLOAT vZoom; /* zoomular velocity (-0.25..0.25) */
50 int delay; /* delay (usec) between iterations */
51 int duration; /* time (sec) before loading new image */
52 int maxColumns; /* the maximum number of columns of tiles */
53 int maxRows; /* the maximum number of rows of tiles */
54 int tileSize; /* the size (width and height) of a tile */
55 int borderWidth; /* the width of the border around each tile */
56 FLOAT eventChance; /* the chance, per iteration, of an interesting event happening */
57 FLOAT friction; /* friction: the fraction (0..1) by which velocity decreased per iteration */
58 FLOAT springiness; /* springiness: the fraction (0..1) of the orientation that turns into velocity towards the center */
59 FLOAT transference; /* transference: the fraction (0..1) of the orientations of orthogonal neighbors that turns into velocity (in the same direction as the orientation) */
60 int windowWidth; /* width and height of the window */
62 Screen *screen; /* the screen to draw on */
63 XImage *sourceImage; /* image source of stuff to draw */
64 XImage *workImage; /* work area image, used when rendering */
65 XImage *backgroundImage; /* image filled with background pixels */
67 GC backgroundGC; /* GC for the background color */
68 GC foregroundGC; /* GC for the foreground color */
69 GC borderGC; /* GC for the border color */
70 unsigned long backgroundPixel; /* background color as a pixel value */
71 unsigned long borderPixel; /* border color as a pixel value */
73 Tile *tiles; /* array of tiles (left->right, top->bottom, row major) */
74 int rows; /* number of rows of tiles */
75 int columns; /* number of columns of tiles */
77 Tile **sortedTiles; /* array of tile pointers, sorted by zoom */
78 int tileCount; /* total number of tiles */
81 async_load_state *img_loader;
83 Bool useShm; /* whether or not to use xshm */
84 #ifdef HAVE_XSHM_EXTENSION
85 XShmSegmentInfo shmInfo;
90 #define TILE_AT(col,row) (&st->tiles[(row) * st->columns + (col)])
92 #define MAX_VANGLE (M_PI / 4.0)
93 #define MAX_VZOOM 0.25
95 #define RAND_ANGLE (RAND_FLOAT_PM1 * M_PI)
96 #define RAND_ZOOM (RAND_FLOAT_PM1)
97 #define RAND_VANGLE (RAND_FLOAT_PM1 * MAX_VANGLE)
98 #define RAND_VZOOM (RAND_FLOAT_PM1 * MAX_VZOOM)
103 * overall setup stuff
106 /* grab the source image */
108 grabImage_start (struct state *st, XWindowAttributes *xwa)
113 XFillRectangle (st->dpy, st->window, st->backgroundGC, 0, 0,
114 st->windowWidth, st->windowHeight);
116 p = XCreatePixmap (st->dpy, st->window,
117 xwa->width, xwa->height, xwa->depth);
118 gc = XCreateGC (st->dpy, st->window, 0, &gcv);
119 XCopyArea (st->dpy, st->window, p, gc, 0, 0,
120 xwa->width, xwa->height, 0, 0);
121 st->backgroundImage =
122 XGetImage (st->dpy, p, 0, 0, st->windowWidth, st->windowHeight,
124 XFreeGC (st->dpy, gc);
125 XFreePixmap (st->dpy, p);
127 st->start_time = time ((time_t *) 0);
128 st->img_loader = load_image_async_simple (0, xwa->screen, st->window,
133 grabImage_done (struct state *st)
135 XWindowAttributes xwa;
136 XGetWindowAttributes (st->dpy, st->window, &xwa);
138 st->start_time = time ((time_t *) 0);
139 if (st->sourceImage) XDestroyImage (st->sourceImage);
140 st->sourceImage = XGetImage (st->dpy, st->window, 0, 0, st->windowWidth, st->windowHeight,
143 if (st->workImage) XDestroyImage (st->workImage);
144 st->workImage = NULL;
146 #ifdef HAVE_XSHM_EXTENSION
149 st->workImage = create_xshm_image (st->dpy, xwa.visual, xwa.depth,
150 ZPixmap, 0, &st->shmInfo,
151 st->windowWidth, st->windowHeight);
155 fprintf (stderr, "create_xshm_image failed\n");
159 if (st->workImage == NULL)
160 #endif /* HAVE_XSHM_EXTENSION */
162 /* just use XSubImage to acquire the right visual, depth, etc;
163 * easier than the other alternatives */
164 st->workImage = XSubImage (st->sourceImage, 0, 0, st->windowWidth, st->windowHeight);
167 /* set up the system */
168 static void setup (struct state *st)
170 XWindowAttributes xgwa;
173 XGetWindowAttributes (st->dpy, st->window, &xgwa);
175 st->screen = xgwa.screen;
176 st->windowWidth = xgwa.width;
177 st->windowHeight = xgwa.height;
179 gcv.line_width = st->borderWidth;
180 gcv.foreground = get_pixel_resource (st->dpy, xgwa.colormap,
181 "borderColor", "BorderColor");
182 st->borderPixel = gcv.foreground;
183 st->borderGC = XCreateGC (st->dpy, st->window, GCForeground | GCLineWidth,
186 gcv.foreground = get_pixel_resource (st->dpy, xgwa.colormap,
187 "background", "Background");
188 st->backgroundPixel = gcv.foreground;
189 st->backgroundGC = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
191 gcv.foreground = get_pixel_resource (st->dpy, xgwa.colormap,
192 "foreground", "Foreground");
193 st->foregroundGC = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
195 grabImage_start (st, &xgwa);
204 /* event: randomize all the angular velocities */
205 static void randomizeAllAngularVelocities (struct state *st)
210 for (r = 0; r < st->rows; r++)
212 for (c = 0; c < st->columns; c++)
214 TILE_AT (c, r)->vAngle = RAND_VANGLE;
219 /* event: randomize all the zoomular velocities */
220 static void randomizeAllZoomularVelocities (struct state *st)
225 for (r = 0; r < st->rows; r++)
227 for (c = 0; c < st->columns; c++)
229 TILE_AT (c, r)->vZoom = RAND_VZOOM;
234 /* event: randomize all the velocities */
235 static void randomizeAllVelocities (struct state *st)
237 randomizeAllAngularVelocities (st);
238 randomizeAllZoomularVelocities (st);
241 /* event: randomize all the angular orientations */
242 static void randomizeAllAngularOrientations (struct state *st)
247 for (r = 0; r < st->rows; r++)
249 for (c = 0; c < st->columns; c++)
251 TILE_AT (c, r)->angle = RAND_ANGLE;
256 /* event: randomize all the zoomular orientations */
257 static void randomizeAllZoomularOrientations (struct state *st)
262 for (r = 0; r < st->rows; r++)
264 for (c = 0; c < st->columns; c++)
266 TILE_AT (c, r)->zoom = RAND_ZOOM;
271 /* event: randomize all the orientations */
272 static void randomizeAllOrientations (struct state *st)
274 randomizeAllAngularOrientations (st);
275 randomizeAllZoomularOrientations (st);
278 /* event: randomize everything */
279 static void randomizeEverything (struct state *st)
281 randomizeAllVelocities (st);
282 randomizeAllOrientations (st);
285 /* event: pick one tile and randomize all its stats */
286 static void randomizeOneTile (struct state *st)
288 int c = RAND_FLOAT_01 * st->columns;
289 int r = RAND_FLOAT_01 * st->rows;
291 Tile *t = TILE_AT (c, r);
292 t->angle = RAND_ANGLE;
294 t->vAngle = RAND_VANGLE;
295 t->vZoom = RAND_VZOOM;
298 /* event: pick one row and randomize everything about each of its tiles */
299 static void randomizeOneRow (struct state *st)
302 int r = RAND_FLOAT_01 * st->rows;
304 for (c = 0; c < st->columns; c++)
306 Tile *t = TILE_AT (c, r);
307 t->angle = RAND_ANGLE;
309 t->vAngle = RAND_VANGLE;
310 t->vZoom = RAND_VZOOM;
314 /* event: pick one column and randomize everything about each of its tiles */
315 static void randomizeOneColumn (struct state *st)
317 int c = RAND_FLOAT_01 * st->columns;
320 for (r = 0; r < st->rows; r++)
322 Tile *t = TILE_AT (c, r);
323 t->angle = RAND_ANGLE;
325 t->vAngle = RAND_VANGLE;
326 t->vZoom = RAND_VZOOM;
330 /* do model event processing */
331 static void modelEvents (struct state *st)
335 if (RAND_FLOAT_01 > st->eventChance)
340 which = RAND_FLOAT_01 * 10;
344 case 0: randomizeAllAngularVelocities (st); break;
345 case 1: randomizeAllZoomularVelocities (st); break;
346 case 2: randomizeAllVelocities (st); break;
347 case 3: randomizeAllAngularOrientations (st); break;
348 case 4: randomizeAllZoomularOrientations (st); break;
349 case 5: randomizeAllOrientations (st); break;
350 case 6: randomizeEverything (st); break;
351 case 7: randomizeOneTile (st); break;
352 case 8: randomizeOneColumn (st); break;
353 case 9: randomizeOneRow (st); break;
357 /* update the model for one iteration */
358 static void updateModel (struct state *st)
363 /* for each tile, decrease its velocities according to the friction,
364 * and increase them based on its current orientation and the orientations
365 * of its orthogonal neighbors */
366 for (r = 0; r < st->rows; r++)
368 for (c = 0; c < st->columns; c++)
370 Tile *t = TILE_AT (c, r);
373 FLOAT va = t->vAngle;
376 va -= t->angle * st->springiness;
377 vz -= t->zoom * st->springiness;
381 Tile *t2 = TILE_AT (c - 1, r);
382 va += (t2->angle - a) * st->transference;
383 vz += (t2->zoom - z) * st->transference;
386 if (c < (st->columns - 1))
388 Tile *t2 = TILE_AT (c + 1, r);
389 va += (t2->angle - a) * st->transference;
390 vz += (t2->zoom - z) * st->transference;
395 Tile *t2 = TILE_AT (c, r - 1);
396 va += (t2->angle - a) * st->transference;
397 vz += (t2->zoom - z) * st->transference;
400 if (r < (st->rows - 1))
402 Tile *t2 = TILE_AT (c, r + 1);
403 va += (t2->angle - a) * st->transference;
404 vz += (t2->zoom - z) * st->transference;
407 va *= (1.0 - st->friction);
408 vz *= (1.0 - st->friction);
410 if (va > MAX_VANGLE) va = MAX_VANGLE;
411 else if (va < -MAX_VANGLE) va = -MAX_VANGLE;
414 if (vz > MAX_VZOOM) vz = MAX_VZOOM;
415 else if (vz < -MAX_VZOOM) vz = -MAX_VZOOM;
420 /* for each tile, update its orientation based on its velocities */
421 for (r = 0; r < st->rows; r++)
423 for (c = 0; c < st->columns; c++)
425 Tile *t = TILE_AT (c, r);
426 FLOAT a = t->angle + t->vAngle;
427 FLOAT z = t->zoom + t->vZoom;
429 if (a > M_PI) a = M_PI;
430 else if (a < -M_PI) a = -M_PI;
433 if (z > 1.0) z = 1.0;
434 else if (z < -1.0) z = -1.0;
440 /* the comparator to us to sort the tiles (used immediately below); it'd
441 * sure be nice if C allowed inner functions (or jeebus-forbid *real
443 static int sortTilesComparator (const void *v1, const void *v2)
445 Tile *t1 = *(Tile **) v1;
446 Tile *t2 = *(Tile **) v2;
448 if (t1->zoom < t2->zoom)
453 if (t1->zoom > t2->zoom)
461 /* sort the tiles in sortedTiles by zoom */
462 static void sortTiles (struct state *st)
464 qsort (st->sortedTiles, st->tileCount, sizeof (Tile *), sortTilesComparator);
467 /* render the given tile */
468 static void renderTile (struct state *st, Tile *t)
470 /* note: the zoom as stored per tile is log-based (centered on 0, with
471 * 0 being no zoom, but the range for zoom-as-drawn is 0.4..2.5,
472 * hence the alteration of t->zoom, below */
479 FLOAT zoom = pow (2.5, t->zoom);
480 FLOAT ang = -t->angle;
481 FLOAT sinAng = sin (ang);
482 FLOAT cosAng = cos (ang);
484 FLOAT innerBorder = (st->tileSize - st->borderWidth) / 2.0;
485 FLOAT outerBorder = innerBorder + st->borderWidth;
487 int maxCoord = outerBorder * zoom * (fabs (sinAng) + fabs (cosAng));
488 int minX = tx - maxCoord;
489 int maxX = tx + maxCoord;
490 int minY = ty - maxCoord;
491 int maxY = ty + maxCoord;
495 if (minX < 0) minX = 0;
496 if (maxX > st->windowWidth) maxX = st->windowWidth;
497 if (minY < 0) minY = 0;
498 if (maxY > st->windowHeight) maxY = st->windowHeight;
503 for (y = minY, prey = y - ty; y < maxY; y++, prey++)
505 FLOAT prex = minX - tx;
506 FLOAT srcx = prex * cosAng - prey * sinAng;
507 FLOAT srcy = prex * sinAng + prey * cosAng;
511 x++, srcx += cosAng, srcy += sinAng)
513 if ((srcx < -innerBorder) || (srcx >= innerBorder) ||
514 (srcy < -innerBorder) || (srcy >= innerBorder))
516 if ((srcx < -outerBorder) || (srcx >= outerBorder) ||
517 (srcy < -outerBorder) || (srcy >= outerBorder))
521 XPutPixel (st->workImage, x, y, st->borderPixel);
526 XGetPixel (st->sourceImage, srcx + tx, srcy + ty);
527 XPutPixel (st->workImage, x, y, p);
533 /* render and display the current model */
534 static void renderFrame (struct state *st)
538 memcpy (st->workImage->data, st->backgroundImage->data,
539 st->workImage->bytes_per_line * st->workImage->height);
543 for (n = 0; n < st->tileCount; n++)
545 renderTile (st, st->sortedTiles[n]);
548 #ifdef HAVE_XSHM_EXTENSION
550 XShmPutImage (st->dpy, st->window, st->backgroundGC, st->workImage, 0, 0, 0, 0,
551 st->windowWidth, st->windowHeight, False);
553 #endif /* HAVE_XSHM_EXTENSION */
554 XPutImage (st->dpy, st->window, st->backgroundGC, st->workImage,
555 0, 0, 0, 0, st->windowWidth, st->windowHeight);
558 /* set up the model */
559 static void setupModel (struct state *st)
564 int leftX; /* x of the center of the top-left tile */
565 int topY; /* y of the center of the top-left tile */
567 if (st->tileSize > (st->windowWidth / 2))
569 st->tileSize = st->windowWidth / 2;
572 if (st->tileSize > (st->windowHeight / 2))
574 st->tileSize = st->windowHeight / 2;
577 st->columns = st->tileSize ? st->windowWidth / st->tileSize : 0;
578 st->rows = st->tileSize ? st->windowHeight / st->tileSize : 0;
580 if ((st->maxColumns != 0) && (st->columns > st->maxColumns))
582 st->columns = st->maxColumns;
585 if ((st->maxRows != 0) && (st->rows > st->maxRows))
587 st->rows = st->maxRows;
590 st->tileCount = st->rows * st->columns;
592 leftX = (st->windowWidth - (st->columns * st->tileSize) + st->tileSize) / 2;
593 topY = (st->windowHeight - (st->rows * st->tileSize) + st->tileSize) / 2;
595 st->tiles = calloc (st->tileCount, sizeof (Tile));
596 st->sortedTiles = calloc (st->tileCount, sizeof (Tile *));
598 for (r = 0; r < st->rows; r++)
600 for (c = 0; c < st->columns; c++)
602 Tile *t = TILE_AT (c, r);
603 t->x = leftX + c * st->tileSize;
604 t->y = topY + r * st->tileSize;
605 st->sortedTiles[c + r * st->columns] = t;
609 randomizeEverything (st);
612 /* do one iteration */
615 twang_draw (Display *dpy, Window window, void *closure)
617 struct state *st = (struct state *) closure;
619 if (st->img_loader) /* still loading */
621 st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
622 if (! st->img_loader) { /* just finished */
628 if (!st->img_loader &&
629 st->start_time + st->duration < time ((time_t *) 0)) {
630 XWindowAttributes xgwa;
631 XGetWindowAttributes (st->dpy, st->window, &xgwa);
632 grabImage_start (st, &xgwa);
644 twang_reshape (Display *dpy, Window window, void *closure,
645 unsigned int w, unsigned int h)
650 twang_event (Display *dpy, Window window, void *closure, XEvent *event)
652 struct state *st = (struct state *) closure;
653 if (screenhack_event_helper (dpy, window, event))
662 twang_free (Display *dpy, Window window, void *closure)
664 struct state *st = (struct state *) closure;
670 /* main and options and stuff */
672 /* initialize the user-specifiable params */
673 static void initParams (struct state *st)
677 st->borderWidth = get_integer_resource (st->dpy, "borderWidth", "Integer");
678 if (st->borderWidth < 0)
680 fprintf (stderr, "error: border width must be at least 0\n");
684 st->delay = get_integer_resource (st->dpy, "delay", "Delay");
687 fprintf (stderr, "error: delay must be at least 0\n");
691 st->duration = get_integer_resource (st->dpy, "duration", "Seconds");
692 if (st->duration < 1) st->duration = 1;
694 st->eventChance = get_float_resource (st->dpy, "eventChance", "Double");
695 if ((st->eventChance < 0.0) || (st->eventChance > 1.0))
697 fprintf (stderr, "error: eventChance must be in the range 0..1\n");
701 st->friction = get_float_resource (st->dpy, "friction", "Double");
702 if ((st->friction < 0.0) || (st->friction > 1.0))
704 fprintf (stderr, "error: friction must be in the range 0..1\n");
708 st->maxColumns = get_integer_resource (st->dpy, "maxColumns", "Integer");
709 if (st->maxColumns < 0)
711 fprintf (stderr, "error: max columns must be at least 0\n");
715 st->maxRows = get_integer_resource (st->dpy, "maxRows", "Integer");
718 fprintf (stderr, "error: max rows must be at least 0\n");
722 st->springiness = get_float_resource (st->dpy, "springiness", "Double");
723 if ((st->springiness < 0.0) || (st->springiness > 1.0))
725 fprintf (stderr, "error: springiness must be in the range 0..1\n");
729 st->tileSize = get_integer_resource (st->dpy, "tileSize", "Integer");
730 if (st->tileSize < 1)
732 fprintf (stderr, "error: tile size must be at least 1\n");
736 st->transference = get_float_resource (st->dpy, "transference", "Double");
737 if ((st->transference < 0.0) || (st->transference > 1.0))
739 fprintf (stderr, "error: transference must be in the range 0..1\n");
743 #ifdef HAVE_XSHM_EXTENSION
744 st->useShm = get_boolean_resource (st->dpy, "useSHM", "Boolean");
754 twang_init (Display *dpy, Window win)
756 struct state *st = (struct state *) calloc (1, sizeof(*st));
768 static const char *twang_defaults [] = {
769 ".background: black",
770 ".foreground: white",
771 "*borderColor: blue",
775 "*eventChance: 0.01",
781 "*transference: 0.025",
782 #ifdef HAVE_XSHM_EXTENSION
788 "*ignoreRotation: True",
789 "*rotateImages: True",
794 static XrmOptionDescRec twang_options [] = {
795 { "-border-color", ".borderColor", XrmoptionSepArg, 0 },
796 { "-border-width", ".borderWidth", XrmoptionSepArg, 0 },
797 { "-delay", ".delay", XrmoptionSepArg, 0 },
798 { "-duration", ".duration", XrmoptionSepArg, 0 },
799 { "-event-chance", ".eventChance", XrmoptionSepArg, 0 },
800 { "-friction", ".friction", XrmoptionSepArg, 0 },
801 { "-max-columns", ".maxColumns", XrmoptionSepArg, 0 },
802 { "-max-rows", ".maxRows", XrmoptionSepArg, 0 },
803 { "-springiness", ".springiness", XrmoptionSepArg, 0 },
804 { "-tile-size", ".tileSize", XrmoptionSepArg, 0 },
805 { "-transference", ".transference", XrmoptionSepArg, 0 },
806 { "-shm", ".useSHM", XrmoptionNoArg, "True" },
807 { "-no-shm", ".useSHM", XrmoptionNoArg, "False" },
812 XSCREENSAVER_MODULE ("Twang", twang)