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"
18 #include <X11/Xutil.h>
20 #ifdef HAVE_XSHM_EXTENSION
26 /* random float in the range (-1..1) */
27 #define RAND_FLOAT_PM1 \
28 (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000) * 2 - 1)
30 /* random float in the range (0..1) */
31 #define RAND_FLOAT_01 \
32 (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000))
36 /* parameters that are user configurable */
38 /* whether or not to use xshm */
39 #ifdef HAVE_XSHM_EXTENSION
43 /* delay (usec) between iterations */
46 /* the maximum number of columns of tiles */
47 static int maxColumns;
49 /* the maximum number of rows of tiles */
52 /* the size (width and height) of a tile */
55 /* the width of the border around each tile */
56 static int borderWidth;
58 /* the chance, per iteration, of an interesting event happening */
59 static FLOAT eventChance;
61 /* friction: the fraction (0..1) by which velocity decreased per iteration */
62 static FLOAT friction;
64 /* springiness: the fraction (0..1) of the orientation that turns into
65 * velocity towards the center */
66 static FLOAT springiness;
68 /* transference: the fraction (0..1) of the orientations of orthogonal
69 * neighbors that turns into velocity (in the same direction as the
71 static FLOAT transference;
75 /* non-user-modifiable immutable definitions */
77 /* width and height of the window */
78 static int windowWidth;
79 static int windowHeight;
81 static Display *display; /* the display to draw on */
82 static Window window; /* the window to draw on */
83 static Screen *screen; /* the screen to draw on */
85 static XImage *sourceImage; /* image source of stuff to draw */
86 static XImage *workImage; /* work area image, used when rendering */
87 static XImage *backgroundImage; /* image filled with background pixels */
89 static GC backgroundGC; /* GC for the background color */
90 static GC foregroundGC; /* GC for the foreground color */
91 static GC borderGC; /* GC for the border color */
92 unsigned long backgroundPixel; /* background color as a pixel value */
93 unsigned long borderPixel; /* border color as a pixel value */
95 #ifdef HAVE_XSHM_EXTENSION
96 XShmSegmentInfo shmInfo;
105 int x; /* x coordinate of the center of the tile */
106 int y; /* y coordinate of the center of the tile */
107 FLOAT angle; /* angle of the tile (-pi..pi) */
108 FLOAT zoom; /* log of the zoom of the tile (-1..1) */
109 FLOAT vAngle; /* angular velocity (-pi/4..pi/4) */
110 FLOAT vZoom; /* zoomular velocity (-0.25..0.25) */
114 static Tile *tiles; /* array of tiles (left->right, top->bottom, row major) */
115 static int rows; /* number of rows of tiles */
116 static int columns; /* number of columns of tiles */
118 static Tile **sortedTiles; /* array of tile pointers, sorted by zoom */
119 static int tileCount; /* total number of tiles */
121 #define TILE_AT(col,row) (&tiles[(row) * columns + (col)])
123 #define MAX_VANGLE (M_PI / 4.0)
124 #define MAX_VZOOM 0.25
126 #define RAND_ANGLE (RAND_FLOAT_PM1 * M_PI)
127 #define RAND_ZOOM (RAND_FLOAT_PM1)
128 #define RAND_VANGLE (RAND_FLOAT_PM1 * MAX_VANGLE)
129 #define RAND_VZOOM (RAND_FLOAT_PM1 * MAX_VZOOM)
134 * overall setup stuff
137 /* grab the source image */
138 static void grabImage (XWindowAttributes *xwa)
140 XFillRectangle (display, window, backgroundGC, 0, 0,
141 windowWidth, windowHeight);
143 XGetImage (display, window, 0, 0, windowWidth, windowHeight,
146 load_random_image (screen, window, window);
147 sourceImage = XGetImage (display, window, 0, 0, windowWidth, windowHeight,
150 #ifdef HAVE_XSHM_EXTENSION
154 workImage = create_xshm_image (display, xwa->visual, xwa->depth,
155 ZPixmap, 0, &shmInfo,
156 windowWidth, windowHeight);
160 fprintf (stderr, "create_xshm_image failed\n");
164 if (workImage == NULL)
165 #endif /* HAVE_XSHM_EXTENSION */
167 /* just use XSubImage to acquire the right visual, depth, etc;
168 * easier than the other alternatives */
169 workImage = XSubImage (sourceImage, 0, 0, windowWidth, windowHeight);
172 /* set up the system */
173 static void setup (void)
175 XWindowAttributes xgwa;
178 XGetWindowAttributes (display, window, &xgwa);
180 screen = xgwa.screen;
181 windowWidth = xgwa.width;
182 windowHeight = xgwa.height;
184 gcv.line_width = borderWidth;
185 gcv.foreground = get_pixel_resource ("borderColor", "BorderColor",
186 display, xgwa.colormap);
187 borderPixel = gcv.foreground;
188 borderGC = XCreateGC (display, window, GCForeground | GCLineWidth,
191 gcv.foreground = get_pixel_resource ("background", "Background",
192 display, xgwa.colormap);
193 backgroundPixel = gcv.foreground;
194 backgroundGC = XCreateGC (display, window, GCForeground, &gcv);
196 gcv.foreground = get_pixel_resource ("foreground", "Foreground",
197 display, xgwa.colormap);
198 foregroundGC = XCreateGC (display, window, GCForeground, &gcv);
209 /* event: randomize all the angular velocities */
210 static void randomizeAllAngularVelocities (void)
215 for (r = 0; r < rows; r++)
217 for (c = 0; c < columns; c++)
219 TILE_AT (c, r)->vAngle = RAND_VANGLE;
224 /* event: randomize all the zoomular velocities */
225 static void randomizeAllZoomularVelocities (void)
230 for (r = 0; r < rows; r++)
232 for (c = 0; c < columns; c++)
234 TILE_AT (c, r)->vZoom = RAND_VZOOM;
239 /* event: randomize all the velocities */
240 static void randomizeAllVelocities (void)
242 randomizeAllAngularVelocities ();
243 randomizeAllZoomularVelocities ();
246 /* event: randomize all the angular orientations */
247 static void randomizeAllAngularOrientations (void)
252 for (r = 0; r < rows; r++)
254 for (c = 0; c < columns; c++)
256 TILE_AT (c, r)->angle = RAND_ANGLE;
261 /* event: randomize all the zoomular orientations */
262 static void randomizeAllZoomularOrientations (void)
267 for (r = 0; r < rows; r++)
269 for (c = 0; c < columns; c++)
271 TILE_AT (c, r)->zoom = RAND_ZOOM;
276 /* event: randomize all the orientations */
277 static void randomizeAllOrientations (void)
279 randomizeAllAngularOrientations ();
280 randomizeAllZoomularOrientations ();
283 /* event: randomize everything */
284 static void randomizeEverything (void)
286 randomizeAllVelocities ();
287 randomizeAllOrientations ();
290 /* event: pick one tile and randomize all its stats */
291 static void randomizeOneTile (void)
293 int c = RAND_FLOAT_01 * columns;
294 int r = RAND_FLOAT_01 * rows;
296 Tile *t = TILE_AT (c, r);
297 t->angle = RAND_ANGLE;
299 t->vAngle = RAND_VANGLE;
300 t->vZoom = RAND_VZOOM;
303 /* event: pick one row and randomize everything about each of its tiles */
304 static void randomizeOneRow (void)
307 int r = RAND_FLOAT_01 * rows;
309 for (c = 0; c < columns; c++)
311 Tile *t = TILE_AT (c, r);
312 t->angle = RAND_ANGLE;
314 t->vAngle = RAND_VANGLE;
315 t->vZoom = RAND_VZOOM;
319 /* event: pick one column and randomize everything about each of its tiles */
320 static void randomizeOneColumn (void)
322 int c = RAND_FLOAT_01 * columns;
325 for (r = 0; r < rows; r++)
327 Tile *t = TILE_AT (c, r);
328 t->angle = RAND_ANGLE;
330 t->vAngle = RAND_VANGLE;
331 t->vZoom = RAND_VZOOM;
335 /* do model event processing */
336 static void modelEvents (void)
340 if (RAND_FLOAT_01 > eventChance)
345 which = RAND_FLOAT_01 * 10;
349 case 0: randomizeAllAngularVelocities (); break;
350 case 1: randomizeAllZoomularVelocities (); break;
351 case 2: randomizeAllVelocities (); break;
352 case 3: randomizeAllAngularOrientations (); break;
353 case 4: randomizeAllZoomularOrientations (); break;
354 case 5: randomizeAllOrientations (); break;
355 case 6: randomizeEverything (); break;
356 case 7: randomizeOneTile (); break;
357 case 8: randomizeOneColumn (); break;
358 case 9: randomizeOneRow (); break;
362 /* update the model for one iteration */
363 static void updateModel (void)
368 /* for each tile, decrease its velocities according to the friction,
369 * and increase them based on its current orientation and the orientations
370 * of its orthogonal neighbors */
371 for (r = 0; r < rows; r++)
373 for (c = 0; c < columns; c++)
375 Tile *t = TILE_AT (c, r);
378 FLOAT va = t->vAngle;
381 va -= t->angle * springiness;
382 vz -= t->zoom * springiness;
386 Tile *t2 = TILE_AT (c - 1, r);
387 va += (t2->angle - a) * transference;
388 vz += (t2->zoom - z) * transference;
391 if (c < (columns - 1))
393 Tile *t2 = TILE_AT (c + 1, r);
394 va += (t2->angle - a) * transference;
395 vz += (t2->zoom - z) * transference;
400 Tile *t2 = TILE_AT (c, r - 1);
401 va += (t2->angle - a) * transference;
402 vz += (t2->zoom - z) * transference;
407 Tile *t2 = TILE_AT (c, r + 1);
408 va += (t2->angle - a) * transference;
409 vz += (t2->zoom - z) * transference;
412 va *= (1.0 - friction);
413 vz *= (1.0 - friction);
415 if (va > MAX_VANGLE) va = MAX_VANGLE;
416 else if (va < -MAX_VANGLE) va = -MAX_VANGLE;
419 if (vz > MAX_VZOOM) vz = MAX_VZOOM;
420 else if (vz < -MAX_VZOOM) vz = -MAX_VZOOM;
425 /* for each tile, update its orientation based on its velocities */
426 for (r = 0; r < rows; r++)
428 for (c = 0; c < columns; c++)
430 Tile *t = TILE_AT (c, r);
431 FLOAT a = t->angle + t->vAngle;
432 FLOAT z = t->zoom + t->vZoom;
434 if (a > M_PI) a = M_PI;
435 else if (a < -M_PI) a = -M_PI;
438 if (z > 1.0) z = 1.0;
439 else if (z < -1.0) z = -1.0;
445 /* the comparator to us to sort the tiles (used immediately below); it'd
446 * sure be nice if C allowed inner functions (or jeebus-forbid *real
448 static int sortTilesComparator (const void *v1, const void *v2)
450 Tile *t1 = *(Tile **) v1;
451 Tile *t2 = *(Tile **) v2;
453 if (t1->zoom < t2->zoom)
458 if (t1->zoom > t2->zoom)
466 /* sort the tiles in sortedTiles by zoom */
467 static void sortTiles (void)
469 qsort (sortedTiles, tileCount, sizeof (Tile *), sortTilesComparator);
472 /* render the given tile */
473 static void renderTile (Tile *t)
475 /* note: the zoom as stored per tile is log-based (centered on 0, with
476 * 0 being no zoom, but the range for zoom-as-drawn is 0.4..2.5,
477 * hence the alteration of t->zoom, below */
484 FLOAT zoom = pow (2.5, t->zoom);
485 FLOAT ang = -t->angle;
486 FLOAT sinAng = sin (ang);
487 FLOAT cosAng = cos (ang);
489 FLOAT innerBorder = (tileSize - borderWidth) / 2.0;
490 FLOAT outerBorder = innerBorder + borderWidth;
492 int maxCoord = outerBorder * zoom * (fabs (sinAng) + fabs (cosAng));
493 int minX = tx - maxCoord;
494 int maxX = tx + maxCoord;
495 int minY = ty - maxCoord;
496 int maxY = ty + maxCoord;
500 if (minX < 0) minX = 0;
501 if (maxX > windowWidth) maxX = windowWidth;
502 if (minY < 0) minY = 0;
503 if (maxY > windowHeight) maxY = windowHeight;
508 for (y = minY, prey = y - ty; y < maxY; y++, prey++)
510 FLOAT prex = minX - tx;
511 FLOAT srcx = prex * cosAng - prey * sinAng;
512 FLOAT srcy = prex * sinAng + prey * cosAng;
516 x++, srcx += cosAng, srcy += sinAng)
518 if ((srcx < -innerBorder) || (srcx >= innerBorder) ||
519 (srcy < -innerBorder) || (srcy >= innerBorder))
521 if ((srcx < -outerBorder) || (srcx >= outerBorder) ||
522 (srcy < -outerBorder) || (srcy >= outerBorder))
526 XPutPixel (workImage, x, y, borderPixel);
531 XGetPixel (sourceImage, srcx + tx, srcy + ty);
532 XPutPixel (workImage, x, y, p);
538 /* render and display the current model */
539 static void renderFrame (void)
543 memcpy (workImage->data, backgroundImage->data,
544 workImage->bytes_per_line * workImage->height);
548 for (n = 0; n < tileCount; n++)
550 renderTile (sortedTiles[n]);
553 #ifdef HAVE_XSHM_EXTENSION
555 XShmPutImage (display, window, backgroundGC, workImage, 0, 0, 0, 0,
556 windowWidth, windowHeight, False);
558 #endif /* HAVE_XSHM_EXTENSION */
559 XPutImage (display, window, backgroundGC, workImage,
560 0, 0, 0, 0, windowWidth, windowHeight);
563 /* set up the model */
564 static void setupModel (void)
569 int leftX; /* x of the center of the top-left tile */
570 int topY; /* y of the center of the top-left tile */
572 if (tileSize > (windowWidth / 2))
574 tileSize = windowWidth / 2;
577 if (tileSize > (windowHeight / 2))
579 tileSize = windowHeight / 2;
582 columns = windowWidth / tileSize;
583 rows = windowHeight / tileSize;
585 if ((maxColumns != 0) && (columns > maxColumns))
587 columns = maxColumns;
590 if ((maxRows != 0) && (rows > maxRows))
595 tileCount = rows * columns;
597 leftX = (windowWidth - (columns * tileSize) + tileSize) / 2;
598 topY = (windowHeight - (rows * tileSize) + tileSize) / 2;
600 tiles = calloc (tileCount, sizeof (Tile));
601 sortedTiles = calloc (tileCount, sizeof (Tile *));
603 for (r = 0; r < rows; r++)
605 for (c = 0; c < columns; c++)
607 Tile *t = TILE_AT (c, r);
608 t->x = leftX + c * tileSize;
609 t->y = topY + r * tileSize;
610 sortedTiles[c + r * columns] = t;
614 randomizeEverything ();
617 /* do one iteration */
618 static void oneIteration (void)
627 /* main and options and stuff */
629 char *progclass = "Twang";
631 char *defaults [] = {
632 ".background: black",
633 ".foreground: white",
634 "*borderColor: blue",
637 "*eventChance: 0.01",
643 "*transference: 0.025",
644 #ifdef HAVE_XSHM_EXTENSION
650 XrmOptionDescRec options [] = {
651 { "-border-color", ".borderColor", XrmoptionSepArg, 0 },
652 { "-border-width", ".borderWidth", XrmoptionSepArg, 0 },
653 { "-delay", ".delay", XrmoptionSepArg, 0 },
654 { "-event-chance", ".eventChance", XrmoptionSepArg, 0 },
655 { "-friction", ".friction", XrmoptionSepArg, 0 },
656 { "-max-columns", ".maxColumns", XrmoptionSepArg, 0 },
657 { "-max-rows", ".maxRows", XrmoptionSepArg, 0 },
658 { "-springiness", ".springiness", XrmoptionSepArg, 0 },
659 { "-tile-size", ".tileSize", XrmoptionSepArg, 0 },
660 { "-transference", ".transference", XrmoptionSepArg, 0 },
661 #ifdef HAVE_XSHM_EXTENSION
662 { "-shm", ".useSHM", XrmoptionNoArg, "True" },
663 { "-no-shm", ".useSHM", XrmoptionNoArg, "False" },
668 /* initialize the user-specifiable params */
669 static void initParams (void)
673 borderWidth = get_integer_resource ("borderWidth", "Integer");
676 fprintf (stderr, "error: border width must be at least 0\n");
680 delay = get_integer_resource ("delay", "Delay");
683 fprintf (stderr, "error: delay must be at least 0\n");
687 eventChance = get_float_resource ("eventChance", "Double");
688 if ((eventChance < 0.0) || (eventChance > 1.0))
690 fprintf (stderr, "error: eventChance must be in the range 0..1\n");
694 friction = get_float_resource ("friction", "Double");
695 if ((friction < 0.0) || (friction > 1.0))
697 fprintf (stderr, "error: friction must be in the range 0..1\n");
701 maxColumns = get_integer_resource ("maxColumns", "Integer");
704 fprintf (stderr, "error: max columns must be at least 0\n");
708 maxRows = get_integer_resource ("maxRows", "Integer");
711 fprintf (stderr, "error: max rows must be at least 0\n");
715 springiness = get_float_resource ("springiness", "Double");
716 if ((springiness < 0.0) || (springiness > 1.0))
718 fprintf (stderr, "error: springiness must be in the range 0..1\n");
722 tileSize = get_integer_resource ("tileSize", "Integer");
725 fprintf (stderr, "error: tile size must be at least 1\n");
729 transference = get_float_resource ("transference", "Double");
730 if ((transference < 0.0) || (transference > 1.0))
732 fprintf (stderr, "error: transference must be in the range 0..1\n");
736 #ifdef HAVE_XSHM_EXTENSION
737 useShm = get_boolean_resource ("useSHM", "Boolean");
747 void screenhack (Display *dpy, Window win)
760 screenhack_handle_events (dpy);