/* twang, twist around screen bits, v1.3 * by Dan Bornstein, danfuzz@milk.com * Copyright (c) 2003 Dan Bornstein. All rights reserved. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. * * See the included man page for more details. */ #include #include "screenhack.h" #include "xshm.h" #define FLOAT double /* random float in the range (-1..1) */ #define RAND_FLOAT_PM1 \ (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000) * 2 - 1) /* random float in the range (0..1) */ #define RAND_FLOAT_01 \ (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000)) typedef struct { int x; /* x coordinate of the center of the tile */ int y; /* y coordinate of the center of the tile */ FLOAT angle; /* angle of the tile (-pi..pi) */ FLOAT zoom; /* log of the zoom of the tile (-1..1) */ FLOAT vAngle; /* angular velocity (-pi/4..pi/4) */ FLOAT vZoom; /* zoomular velocity (-0.25..0.25) */ } Tile; struct state { Display *dpy; Window window; int delay; /* delay (usec) between iterations */ int duration; /* time (sec) before loading new image */ int maxColumns; /* the maximum number of columns of tiles */ int maxRows; /* the maximum number of rows of tiles */ int tileSize; /* the size (width and height) of a tile */ int borderWidth; /* the width of the border around each tile */ FLOAT eventChance; /* the chance, per iteration, of an interesting event happening */ FLOAT friction; /* friction: the fraction (0..1) by which velocity decreased per iteration */ FLOAT springiness; /* springiness: the fraction (0..1) of the orientation that turns into velocity towards the center */ FLOAT transference; /* transference: the fraction (0..1) of the orientations of orthogonal neighbors that turns into velocity (in the same direction as the orientation) */ int windowWidth; /* width and height of the window */ int windowHeight; Screen *screen; /* the screen to draw on */ XImage *sourceImage; /* image source of stuff to draw */ XImage *workImage; /* work area image, used when rendering */ GC backgroundGC; /* GC for the background color */ GC foregroundGC; /* GC for the foreground color */ GC borderGC; /* GC for the border color */ unsigned long backgroundPixel; /* background color as a pixel value */ unsigned long borderPixel; /* border color as a pixel value */ Tile *tiles; /* array of tiles (left->right, top->bottom, row major) */ int rows; /* number of rows of tiles */ int columns; /* number of columns of tiles */ Tile **sortedTiles; /* array of tile pointers, sorted by zoom */ int tileCount; /* total number of tiles */ time_t start_time; async_load_state *img_loader; Pixmap pm; XShmSegmentInfo shmInfo; }; #define TILE_AT(col,row) (&st->tiles[(row) * st->columns + (col)]) #define MAX_VANGLE (M_PI / 4.0) #define MAX_VZOOM 0.25 #define RAND_ANGLE (RAND_FLOAT_PM1 * M_PI) #define RAND_ZOOM (RAND_FLOAT_PM1) #define RAND_VANGLE (RAND_FLOAT_PM1 * MAX_VANGLE) #define RAND_VZOOM (RAND_FLOAT_PM1 * MAX_VZOOM) /* * overall setup stuff */ /* grab the source image */ static void grabImage_start (struct state *st, XWindowAttributes *xwa) { /* On MacOS X11, XGetImage on a Window often gets an inexplicable BadMatch, possibly due to the window manager having occluded something? It seems nondeterministic. Loading the image into a pixmap instead fixes it. */ if (st->pm) XFreePixmap (st->dpy, st->pm); st->pm = XCreatePixmap (st->dpy, st->window, xwa->width, xwa->height, xwa->depth); st->start_time = time ((time_t *) 0); st->img_loader = load_image_async_simple (0, xwa->screen, st->window, st->pm, 0, 0); } static void grabImage_done (struct state *st) { XWindowAttributes xwa; XGetWindowAttributes (st->dpy, st->window, &xwa); st->start_time = time ((time_t *) 0); if (st->sourceImage) XDestroyImage (st->sourceImage); st->sourceImage = XGetImage (st->dpy, st->pm, 0, 0, st->windowWidth, st->windowHeight, ~0L, ZPixmap); if (st->workImage) destroy_xshm_image (st->dpy, st->workImage, &st->shmInfo); st->workImage = create_xshm_image (st->dpy, xwa.visual, xwa.depth, ZPixmap, &st->shmInfo, st->windowWidth, st->windowHeight); } /* set up the system */ static void setup (struct state *st) { XWindowAttributes xgwa; XGCValues gcv; XGetWindowAttributes (st->dpy, st->window, &xgwa); st->screen = xgwa.screen; st->windowWidth = xgwa.width; st->windowHeight = xgwa.height; gcv.line_width = st->borderWidth; gcv.foreground = get_pixel_resource (st->dpy, xgwa.colormap, "borderColor", "BorderColor"); st->borderPixel = gcv.foreground; st->borderGC = XCreateGC (st->dpy, st->window, GCForeground | GCLineWidth, &gcv); gcv.foreground = get_pixel_resource (st->dpy, xgwa.colormap, "background", "Background"); st->backgroundPixel = gcv.foreground; st->backgroundGC = XCreateGC (st->dpy, st->window, GCForeground, &gcv); gcv.foreground = get_pixel_resource (st->dpy, xgwa.colormap, "foreground", "Foreground"); st->foregroundGC = XCreateGC (st->dpy, st->window, GCForeground, &gcv); grabImage_start (st, &xgwa); } /* * the simulation */ /* event: randomize all the angular velocities */ static void randomizeAllAngularVelocities (struct state *st) { int c; int r; for (r = 0; r < st->rows; r++) { for (c = 0; c < st->columns; c++) { TILE_AT (c, r)->vAngle = RAND_VANGLE; } } } /* event: randomize all the zoomular velocities */ static void randomizeAllZoomularVelocities (struct state *st) { int c; int r; for (r = 0; r < st->rows; r++) { for (c = 0; c < st->columns; c++) { TILE_AT (c, r)->vZoom = RAND_VZOOM; } } } /* event: randomize all the velocities */ static void randomizeAllVelocities (struct state *st) { randomizeAllAngularVelocities (st); randomizeAllZoomularVelocities (st); } /* event: randomize all the angular orientations */ static void randomizeAllAngularOrientations (struct state *st) { int c; int r; for (r = 0; r < st->rows; r++) { for (c = 0; c < st->columns; c++) { TILE_AT (c, r)->angle = RAND_ANGLE; } } } /* event: randomize all the zoomular orientations */ static void randomizeAllZoomularOrientations (struct state *st) { int c; int r; for (r = 0; r < st->rows; r++) { for (c = 0; c < st->columns; c++) { TILE_AT (c, r)->zoom = RAND_ZOOM; } } } /* event: randomize all the orientations */ static void randomizeAllOrientations (struct state *st) { randomizeAllAngularOrientations (st); randomizeAllZoomularOrientations (st); } /* event: randomize everything */ static void randomizeEverything (struct state *st) { randomizeAllVelocities (st); randomizeAllOrientations (st); } /* event: pick one tile and randomize all its stats */ static void randomizeOneTile (struct state *st) { int c = RAND_FLOAT_01 * st->columns; int r = RAND_FLOAT_01 * st->rows; Tile *t = TILE_AT (c, r); t->angle = RAND_ANGLE; t->zoom = RAND_ZOOM; t->vAngle = RAND_VANGLE; t->vZoom = RAND_VZOOM; } /* event: pick one row and randomize everything about each of its tiles */ static void randomizeOneRow (struct state *st) { int c; int r = RAND_FLOAT_01 * st->rows; for (c = 0; c < st->columns; c++) { Tile *t = TILE_AT (c, r); t->angle = RAND_ANGLE; t->zoom = RAND_ZOOM; t->vAngle = RAND_VANGLE; t->vZoom = RAND_VZOOM; } } /* event: pick one column and randomize everything about each of its tiles */ static void randomizeOneColumn (struct state *st) { int c = RAND_FLOAT_01 * st->columns; int r; for (r = 0; r < st->rows; r++) { Tile *t = TILE_AT (c, r); t->angle = RAND_ANGLE; t->zoom = RAND_ZOOM; t->vAngle = RAND_VANGLE; t->vZoom = RAND_VZOOM; } } /* do model event processing */ static void modelEvents (struct state *st) { int which; if (RAND_FLOAT_01 > st->eventChance) { return; } which = RAND_FLOAT_01 * 10; switch (which) { case 0: randomizeAllAngularVelocities (st); break; case 1: randomizeAllZoomularVelocities (st); break; case 2: randomizeAllVelocities (st); break; case 3: randomizeAllAngularOrientations (st); break; case 4: randomizeAllZoomularOrientations (st); break; case 5: randomizeAllOrientations (st); break; case 6: randomizeEverything (st); break; case 7: randomizeOneTile (st); break; case 8: randomizeOneColumn (st); break; case 9: randomizeOneRow (st); break; } } /* update the model for one iteration */ static void updateModel (struct state *st) { int r; int c; /* for each tile, decrease its velocities according to the friction, * and increase them based on its current orientation and the orientations * of its orthogonal neighbors */ for (r = 0; r < st->rows; r++) { for (c = 0; c < st->columns; c++) { Tile *t = TILE_AT (c, r); FLOAT a = t->angle; FLOAT z = t->zoom; FLOAT va = t->vAngle; FLOAT vz = t->vZoom; va -= t->angle * st->springiness; vz -= t->zoom * st->springiness; if (c > 0) { Tile *t2 = TILE_AT (c - 1, r); va += (t2->angle - a) * st->transference; vz += (t2->zoom - z) * st->transference; } if (c < (st->columns - 1)) { Tile *t2 = TILE_AT (c + 1, r); va += (t2->angle - a) * st->transference; vz += (t2->zoom - z) * st->transference; } if (r > 0) { Tile *t2 = TILE_AT (c, r - 1); va += (t2->angle - a) * st->transference; vz += (t2->zoom - z) * st->transference; } if (r < (st->rows - 1)) { Tile *t2 = TILE_AT (c, r + 1); va += (t2->angle - a) * st->transference; vz += (t2->zoom - z) * st->transference; } va *= (1.0 - st->friction); vz *= (1.0 - st->friction); if (va > MAX_VANGLE) va = MAX_VANGLE; else if (va < -MAX_VANGLE) va = -MAX_VANGLE; t->vAngle = va; if (vz > MAX_VZOOM) vz = MAX_VZOOM; else if (vz < -MAX_VZOOM) vz = -MAX_VZOOM; t->vZoom = vz; } } /* for each tile, update its orientation based on its velocities */ for (r = 0; r < st->rows; r++) { for (c = 0; c < st->columns; c++) { Tile *t = TILE_AT (c, r); FLOAT a = t->angle + t->vAngle; FLOAT z = t->zoom + t->vZoom; if (a > M_PI) a = M_PI; else if (a < -M_PI) a = -M_PI; t->angle = a; if (z > 1.0) z = 1.0; else if (z < -1.0) z = -1.0; t->zoom = z; } } } /* the comparator to us to sort the tiles (used immediately below); it'd * sure be nice if C allowed inner functions (or jeebus-forbid *real * closures*!) */ static int sortTilesComparator (const void *v1, const void *v2) { Tile *t1 = *(Tile **) v1; Tile *t2 = *(Tile **) v2; if (t1->zoom < t2->zoom) { return -1; } if (t1->zoom > t2->zoom) { return 1; } return 0; } /* sort the tiles in sortedTiles by zoom */ static void sortTiles (struct state *st) { qsort (st->sortedTiles, st->tileCount, sizeof (Tile *), sortTilesComparator); } /* render the given tile */ static void renderTile (struct state *st, Tile *t) { /* note: the zoom as stored per tile is log-based (centered on 0, with * 0 being no zoom, but the range for zoom-as-drawn is 0.4..2.5, * hence the alteration of t->zoom, below */ int x, y; int tx = t->x; int ty = t->y; FLOAT zoom = pow (2.5, t->zoom); FLOAT ang = -t->angle; FLOAT sinAng = sin (ang); FLOAT cosAng = cos (ang); FLOAT innerBorder = (st->tileSize - st->borderWidth) / 2.0; FLOAT outerBorder = innerBorder + st->borderWidth; int maxCoord = outerBorder * zoom * (fabs (sinAng) + fabs (cosAng)); int minX = tx - maxCoord; int maxX = tx + maxCoord; int minY = ty - maxCoord; int maxY = ty + maxCoord; FLOAT prey; if (minX < 0) minX = 0; if (maxX > st->windowWidth) maxX = st->windowWidth; if (minY < 0) minY = 0; if (maxY > st->windowHeight) maxY = st->windowHeight; sinAng /= zoom; cosAng /= zoom; for (y = minY, prey = y - ty; y < maxY; y++, prey++) { FLOAT prex = minX - tx; FLOAT srcx = prex * cosAng - prey * sinAng; FLOAT srcy = prex * sinAng + prey * cosAng; for (x = minX; x < maxX; x++, srcx += cosAng, srcy += sinAng) { if ((srcx < -innerBorder) || (srcx >= innerBorder) || (srcy < -innerBorder) || (srcy >= innerBorder)) { if ((srcx < -outerBorder) || (srcx >= outerBorder) || (srcy < -outerBorder) || (srcy >= outerBorder)) { continue; } XPutPixel (st->workImage, x, y, st->borderPixel); } else { unsigned long p = XGetPixel (st->sourceImage, srcx + tx, srcy + ty); XPutPixel (st->workImage, x, y, p); } } } } /* render and display the current model */ static void renderFrame (struct state *st) { int n; /* This assumes black is zero. */ memset (st->workImage->data, 0, st->workImage->bytes_per_line * st->workImage->height); sortTiles (st); for (n = 0; n < st->tileCount; n++) { renderTile (st, st->sortedTiles[n]); } put_xshm_image (st->dpy, st->window, st->backgroundGC, st->workImage, 0, 0, 0, 0, st->windowWidth, st->windowHeight, &st->shmInfo); } /* set up the model */ static void setupModel (struct state *st) { int c; int r; int leftX; /* x of the center of the top-left tile */ int topY; /* y of the center of the top-left tile */ if (st->tileSize > (st->windowWidth / 2)) { st->tileSize = st->windowWidth / 2; } if (st->tileSize > (st->windowHeight / 2)) { st->tileSize = st->windowHeight / 2; } st->columns = st->tileSize ? st->windowWidth / st->tileSize : 0; st->rows = st->tileSize ? st->windowHeight / st->tileSize : 0; if ((st->maxColumns != 0) && (st->columns > st->maxColumns)) { st->columns = st->maxColumns; } if ((st->maxRows != 0) && (st->rows > st->maxRows)) { st->rows = st->maxRows; } st->tileCount = st->rows * st->columns; leftX = (st->windowWidth - (st->columns * st->tileSize) + st->tileSize) / 2; topY = (st->windowHeight - (st->rows * st->tileSize) + st->tileSize) / 2; if (st->tileCount < 1) st->tileCount = 1; st->tiles = calloc (st->tileCount, sizeof (Tile)); st->sortedTiles = calloc (st->tileCount, sizeof (Tile *)); for (r = 0; r < st->rows; r++) { for (c = 0; c < st->columns; c++) { Tile *t = TILE_AT (c, r); t->x = leftX + c * st->tileSize; t->y = topY + r * st->tileSize; st->sortedTiles[c + r * st->columns] = t; } } randomizeEverything (st); } /* do one iteration */ static unsigned long twang_draw (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; if (st->img_loader) /* still loading */ { st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0); if (! st->img_loader) { /* just finished */ grabImage_done (st); } return st->delay; } if (!st->img_loader && st->start_time + st->duration < time ((time_t *) 0)) { XWindowAttributes xgwa; XGetWindowAttributes (st->dpy, st->window, &xgwa); grabImage_start (st, &xgwa); return st->delay; } modelEvents (st); updateModel (st); renderFrame (st); return st->delay; } static void twang_reshape (Display *dpy, Window window, void *closure, unsigned int w, unsigned int h) { } static Bool twang_event (Display *dpy, Window window, void *closure, XEvent *event) { struct state *st = (struct state *) closure; if (screenhack_event_helper (dpy, window, event)) { st->start_time = 0; return True; } return False; } static void twang_free (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; if (st->pm) XFreePixmap (dpy, st->pm); XFreeGC (dpy, st->backgroundGC); XFreeGC (dpy, st->foregroundGC); XFreeGC (dpy, st->borderGC); if (st->sourceImage) XDestroyImage (st->sourceImage); if (st->workImage) destroy_xshm_image (st->dpy, st->workImage, &st->shmInfo); if (st->tiles) free (st->tiles); if (st->sortedTiles) free (st->sortedTiles); free (st); } /* main and options and stuff */ /* initialize the user-specifiable params */ static void initParams (struct state *st) { int problems = 0; st->borderWidth = get_integer_resource (st->dpy, "borderWidth", "Integer"); if (st->borderWidth < 0) { fprintf (stderr, "error: border width must be at least 0\n"); problems = 1; } st->delay = get_integer_resource (st->dpy, "delay", "Delay"); if (st->delay < 0) { fprintf (stderr, "error: delay must be at least 0\n"); problems = 1; } st->duration = get_integer_resource (st->dpy, "duration", "Seconds"); if (st->duration < 1) st->duration = 1; st->eventChance = get_float_resource (st->dpy, "eventChance", "Double"); if ((st->eventChance < 0.0) || (st->eventChance > 1.0)) { fprintf (stderr, "error: eventChance must be in the range 0..1\n"); problems = 1; } st->friction = get_float_resource (st->dpy, "friction", "Double"); if ((st->friction < 0.0) || (st->friction > 1.0)) { fprintf (stderr, "error: friction must be in the range 0..1\n"); problems = 1; } st->maxColumns = get_integer_resource (st->dpy, "maxColumns", "Integer"); if (st->maxColumns < 0) { fprintf (stderr, "error: max columns must be at least 0\n"); problems = 1; } st->maxRows = get_integer_resource (st->dpy, "maxRows", "Integer"); if (st->maxRows < 0) { fprintf (stderr, "error: max rows must be at least 0\n"); problems = 1; } st->springiness = get_float_resource (st->dpy, "springiness", "Double"); if ((st->springiness < 0.0) || (st->springiness > 1.0)) { fprintf (stderr, "error: springiness must be in the range 0..1\n"); problems = 1; } st->tileSize = get_integer_resource (st->dpy, "tileSize", "Integer"); if (st->tileSize < 1) { fprintf (stderr, "error: tile size must be at least 1\n"); problems = 1; } st->transference = get_float_resource (st->dpy, "transference", "Double"); if ((st->transference < 0.0) || (st->transference > 1.0)) { fprintf (stderr, "error: transference must be in the range 0..1\n"); problems = 1; } if (problems) { exit (1); } } static void * twang_init (Display *dpy, Window win) { struct state *st = (struct state *) calloc (1, sizeof(*st)); st->dpy = dpy; st->window = win; initParams (st); setup (st); setupModel (st); return st; } static const char *twang_defaults [] = { ".background: black", ".foreground: white", ".lowrez: true", "*borderColor: blue", "*borderWidth: 3", "*delay: 10000", "*duration: 120", "*eventChance: 0.01", "*friction: 0.05", "*maxColumns: 0", "*maxRows: 0", "*springiness: 0.1", "*tileSize: 120", "*transference: 0.025", #ifdef HAVE_XSHM_EXTENSION "*useSHM: True", #else "*useSHM: False", #endif #ifdef HAVE_MOBILE "*ignoreRotation: True", "*rotateImages: True", #endif 0 }; static XrmOptionDescRec twang_options [] = { { "-border-color", ".borderColor", XrmoptionSepArg, 0 }, { "-border-width", ".borderWidth", XrmoptionSepArg, 0 }, { "-delay", ".delay", XrmoptionSepArg, 0 }, { "-duration", ".duration", XrmoptionSepArg, 0 }, { "-event-chance", ".eventChance", XrmoptionSepArg, 0 }, { "-friction", ".friction", XrmoptionSepArg, 0 }, { "-max-columns", ".maxColumns", XrmoptionSepArg, 0 }, { "-max-rows", ".maxRows", XrmoptionSepArg, 0 }, { "-springiness", ".springiness", XrmoptionSepArg, 0 }, { "-tile-size", ".tileSize", XrmoptionSepArg, 0 }, { "-transference", ".transference", XrmoptionSepArg, 0 }, { "-shm", ".useSHM", XrmoptionNoArg, "True" }, { "-no-shm", ".useSHM", XrmoptionNoArg, "False" }, { 0, 0, 0, 0 } }; XSCREENSAVER_MODULE ("Twang", twang)