/* ripples, Copyright (c) 1999 Ian McConnell * * 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. */ /* * "Water" ripples that can cross and interfere with each other. * * I can't remember where I got this idea from, but it's been around for a * while in various demos. Some inspiration from * water.txt by Tom Hammersley,tomh@globalnet.co.uk * * Options * -delay usleep every iteration * -rate Add one drop every "rate" iterations * -box Add big square splash every "box" iters (not very good) * -water Ripples on a grabbed background image * -foreground Interpolate ripples between these two colors * -background * -oily Psychedelic colours like man * -stir Add a regular pattern of drops * -fluidity Between 0 and 16. 16 = big drops * -light Hack to add lighting effect * * Code mainly hacked from xflame and decayscreen. */ /* Version history: * 13 Oct 1999: Initial hack * 30 Oct 1999: Speeded up graphics with dirty buffer. Returned to using * putpixel for greater portability * Added a variety of methods for splashing screen. * 31 Oct 1999: Added in lighting hack * 13 Nov 1999: Speed up tweaks * Adjust "light" for different bits per colour (-water only) * 09 Oct 2016: Updated for new xshm.c * */ #include #include "screenhack.h" typedef enum {ripple_drop, ripple_blob, ripple_box, ripple_stir} ripple_mode; #include "xshm.h" #define TABLE 256 struct state { Display *dpy; Window window; GC gc; Visual *visual; XImage *orig_map, *buffer_map; int ctab[256]; Colormap colormap; Screen *screen; int ncolors; int light; int width, height; /* ripple size */ int bigwidth, bigheight; /* screen size */ Bool transparent; short *bufferA, *bufferB, *temp; char *dirty_buffer; double cos_tab[TABLE]; Bool grayscale_p; unsigned long rmask; /* This builds on the warp effect by adding */ unsigned long gmask; /* in a lighting effect: brighten pixels by an */ unsigned long bmask; /* amount corresponding to the vertical gradient */ int rshift; int gshift; int bshift; double stir_ang; int draw_toggle; int draw_count; int iterations, delay, rate, box, oily, stir, fluidity; int duration; time_t start_time; void (*draw_transparent) (struct state *st, short *src); async_load_state *img_loader; XShmSegmentInfo shm_info; }; /* Distribution of drops: many little ones and a few big ones. */ static const double drop_dist[] = {0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.2, 0.6}; /* How hard to hit the water */ #define SPLASH 512 #undef MIN #define MIN(x, y) ((x) < (y) ? (x) : (y)) #undef MAX #define MAX(x, y) ((x) > (y) ? (x) : (y)) #undef DIRTY #define DIRTY 3 /* dirty >= 2, 1 = restore original pixel, 0 = leave alone */ /* From fortune(6) */ /* -- really weird C code to count the number of bits in a word */ #define BITCOUNT(x) (((BX_(x)+(BX_(x)>>4)) & 0x0F0F0F0F) % 255) #define BX_(x) ((x) - (((x)>>1)&0x77777777) \ - (((x)>>2)&0x33333333) \ - (((x)>>3)&0x11111111)) static unsigned long grayscale(struct state *st, unsigned long color); /* ------------------------------------------- */ static int map_color(struct state *st, int grey) { /* Clip it */ grey = st->ncolors * abs(grey) / (SPLASH/4); if (grey > st->ncolors) grey = st->ncolors; /* Display it */ return st->ctab[grey]; } static void draw_ripple(struct state *st, short *src) { int across, down; char *dirty = st->dirty_buffer; for (down = 0; down < st->height - 1; down++, src += 1, dirty += 1) for (across = 0; across < st->width - 1; across++, src++, dirty++) { int v1, v2, v3, v4; v1 = (int)*src; v2 = (int)*(src + 1); v3 = (int)*(src + st->width); v4 = (int)*(src + st->width + 1); if ((v1 == 0 && v2 == 0 && v3 == 0 && v4 == 0)) { if (*dirty > 0) (*dirty)--; } else *dirty = DIRTY; if (*dirty > 0) { int dx; if (st->light > 0) { dx = ((v3 - v1) + (v4 - v2)) << st->light; /* light from top */ } else dx = 0; XPutPixel(st->buffer_map,(across<<1), (down<<1), map_color(st, dx + v1)); XPutPixel(st->buffer_map,(across<<1)+1,(down<<1), map_color(st, dx + ((v1 + v2) >> 1))); XPutPixel(st->buffer_map,(across<<1), (down<<1)+1,map_color(st, dx + ((v1 + v3) >> 1))); XPutPixel(st->buffer_map,(across<<1)+1,(down<<1)+1,map_color(st, dx + ((v1 + v4) >> 1))); } } } /* ------------------------------------------- */ /* Uses the horizontal gradient as an offset to create a warp effect */ static void draw_transparent_vanilla(struct state *st, short *src) { int across, down, pixel; char *dirty = st->dirty_buffer; pixel = 0; for (down = 0; down < st->height - 2; down++, pixel += 2) for (across = 0; across < st->width-2; across++, pixel++) { int gradx, grady, gradx1, grady1; int x0, x1, x2, y1, y2; x0 = src[pixel]; x1 = src[pixel + 1]; x2 = src[pixel + 2]; y1 = src[pixel + st->width]; y2 = src[pixel + 2*st->width]; gradx = (x1 - x0); grady = (y1 - x0); gradx1= (x2 - x1); grady1= (y2 - y1); gradx1 = 1 + (gradx + gradx1) / 2; grady1 = 1 + (grady + grady1) / 2; if ((2*across+MIN(gradx,gradx1) < 0) || (2*across+MAX(gradx,gradx1) >= st->bigwidth)) { gradx = 0; gradx1= 1; } if ((2*down+MIN(grady,grady1) < 0) || (2*down+MAX(grady,grady1) >= st->bigheight)) { grady = 0; grady1 = 1; } if ((gradx == 0 && gradx1 == 1 && grady == 0 && grady1 == 1)) { if (dirty[pixel] > 0) dirty[pixel]--; } else dirty[pixel] = DIRTY; if (dirty[pixel] > 0) { XPutPixel(st->buffer_map, (across<<1), (down<<1), grayscale(st, XGetPixel(st->orig_map, (across<<1) + gradx, (down<<1) + grady))); XPutPixel(st->buffer_map, (across<<1)+1,(down<<1), grayscale(st, XGetPixel(st->orig_map, (across<<1) + gradx1,(down<<1) + grady))); XPutPixel(st->buffer_map, (across<<1), (down<<1)+1, grayscale(st, XGetPixel(st->orig_map, (across<<1) + gradx, (down<<1) + grady1))); XPutPixel(st->buffer_map, (across<<1)+1,(down<<1)+1, grayscale(st, XGetPixel(st->orig_map, (across<<1) + gradx1,(down<<1) + grady1))); } } } /* ------------------------------------------- */ static void set_mask(unsigned long *mask, int *shift) { unsigned long color = *mask; *shift = 0; while (color != 0 && (color & 1) == 0) { (*shift)++; color >>= 1; } *mask = color; } static unsigned long cadd(unsigned long color, int dx, unsigned long mask, int shift) { int x; color >>= shift; x = (color & mask); x += dx; if (x < 0) x = 0; else if (x > (int)mask) x = mask; color = x; return color << shift; } static unsigned long bright(struct state *st, int dx, unsigned long color) { return (cadd(color, dx, st->rmask, st->rshift) | cadd(color, dx, st->gmask, st->gshift) | cadd(color, dx, st->bmask, st->bshift)); } static unsigned long grayscale(struct state *st, unsigned long color) { int red; int green; int blue; int total; int gray_r; int gray_g; int gray_b; if (!st->grayscale_p) return color; if (!st->transparent) return color; if ((st->rmask == 0) || (st->gmask == 0) || (st->bmask == 0)) return color; red = ((color >> st->rshift) & st->rmask); green = ((color >> st->gshift) & st->gmask); blue = ((color >> st->bshift) & st->bmask); total = red * st->gmask * st->bmask + green * st->rmask * st->bmask + blue * st->rmask * st->gmask; gray_r = total / (3 * st->gmask * st->bmask); if (gray_r < 0) gray_r = 0; if (gray_r > st->rmask) gray_r = st->rmask; gray_g = total / (3 * st->rmask * st->bmask); if (gray_g < 0) gray_g = 0; if (gray_g > st->gmask) gray_g = st->gmask; gray_b = total / (3 * st->rmask * st->gmask); if (gray_b < 0) gray_b = 0; if (gray_b > st->bmask) gray_b = st->bmask; return ((unsigned long) ((gray_r << st->rshift) | (gray_g << st->gshift) | (gray_b << st->bshift))); } static void draw_transparent_light(struct state *st, short *src) { int across, down, pixel; char *dirty = st->dirty_buffer; pixel = 0; for (down = 0; down < st->height - 2; down++, pixel += 2) for (across = 0; across < st->width-2; across++, pixel++) { int gradx, grady, gradx1, grady1; int x0, x1, x2, y1, y2; x0 = src[pixel]; x1 = src[pixel + 1]; x2 = src[pixel + 2]; y1 = src[pixel + st->width]; y2 = src[pixel + 2*st->width]; gradx = (x1 - x0); grady = (y1 - x0); gradx1= (x2 - x1); grady1= (y2 - y1); gradx1 = 1 + (gradx + gradx1) / 2; grady1 = 1 + (grady + grady1) / 2; if ((2*across+MIN(gradx,gradx1) < 0) || (2*across+MAX(gradx,gradx1) >= st->bigwidth)) { gradx = 0; gradx1= 1; } if ((2*down+MIN(grady,grady1) < 0) || (2*down+MAX(grady,grady1) >= st->bigheight)) { grady = 0; grady1 = 1; } if ((gradx == 0 && gradx1 == 1 && grady == 0 && grady1 == 1)) { if (dirty[pixel] > 0) dirty[pixel]--; } else dirty[pixel] = DIRTY; if (dirty[pixel] > 0) { int dx; /* light from top */ if (4-st->light >= 0) dx = (grady + (src[pixel+st->width+1]-x1)) >> (4-st->light); else dx = (grady + (src[pixel+st->width+1]-x1)) << (st->light-4); if (dx != 0) { XPutPixel(st->buffer_map, (across<<1), (down<<1), bright(st, dx, grayscale(st, XGetPixel(st->orig_map, (across<<1) + gradx, (down<<1) + grady)))); XPutPixel(st->buffer_map, (across<<1)+1,(down<<1), bright(st, dx, grayscale(st, XGetPixel(st->orig_map, (across<<1) + gradx1,(down<<1) + grady)))); XPutPixel(st->buffer_map, (across<<1), (down<<1)+1, bright(st, dx, grayscale(st, XGetPixel(st->orig_map, (across<<1) + gradx, (down<<1) + grady1)))); XPutPixel(st->buffer_map, (across<<1)+1,(down<<1)+1, bright(st, dx, grayscale(st, XGetPixel(st->orig_map, (across<<1) + gradx1,(down<<1) + grady1)))); } else { /* Could use XCopyArea, but XPutPixel is faster */ XPutPixel(st->buffer_map, (across<<1), (down<<1), grayscale(st, XGetPixel(st->orig_map, (across<<1) + gradx, (down<<1) + grady))); XPutPixel(st->buffer_map, (across<<1)+1,(down<<1), grayscale(st, XGetPixel(st->orig_map, (across<<1) + gradx1,(down<<1) + grady))); XPutPixel(st->buffer_map, (across<<1), (down<<1)+1, grayscale(st, XGetPixel(st->orig_map, (across<<1) + gradx, (down<<1) + grady1))); XPutPixel(st->buffer_map, (across<<1)+1,(down<<1)+1, grayscale(st, XGetPixel(st->orig_map, (across<<1) + gradx1,(down<<1) + grady1))); } } } } /* ------------------------------------------- */ #if 0 /* Doesn't go any faster and doesn't work at all colour depths */ static void draw_transparent16l(short *src) { int across, down, bigpix, pixel; char *dirty = st->dirty_buffer; unsigned short *buffer, *orig; buffer = (unsigned short *) st->buffer_map->data; orig = (unsigned short *) st->orig_map->data; for (pixel = bigpix = down = 0; down < st->height - 2; down++, pixel += 2, bigpix += st->bigwidth+4) for (across = 0; across < st->width-2; across++, pixel++, bigpix+=2) { int gradx, grady, gradx1, grady1; int x0, x1, x2, y1, y2; x0 = src[pixel]; x1 = src[pixel + 1]; x2 = src[pixel + 2]; y1 = src[pixel + st->width]; y2 = src[pixel + 2*st->width]; gradx = (x1 - x0); grady = (y1 - x0); gradx1= (x2 - x1); grady1= (y2 - y1); gradx1 = 1 + (gradx + gradx1) / 2; grady1 = 1 + (grady + grady1) / 2; if ((2*across+MIN(gradx,gradx1) < 0) || (2*across+MAX(gradx,gradx1) >= st->bigwidth)) { gradx = 0; gradx1= 1; } if ((2*down+MIN(grady,grady1) < 0) || (2*down+MAX(grady,grady1) >= st->bigheight)) { grady = 0; grady1 = 1; } if ((gradx == 0 && gradx1 == 1 && grady == 0 && grady1 == 1)) { if (dirty[pixel] > 0) dirty[pixel]--; } else dirty[pixel] = DIRTY; if (dirty[pixel] > 0) { unsigned short *dest = buffer + bigpix; unsigned short *image = orig + bigpix; int dx; /* light from top */ if (4-st->light >= 0) dx = (grady + (src[pixel+st->width+1]-x1)) >> (4-st->light); else dx = (grady + (src[pixel+st->width+1]-x1)) << (st->light-4); grady *= st->bigwidth; grady1*= st->bigwidth; if (dx != 0) { *dest++ = dobright(dx, *(image + gradx + grady)); *dest = dobright(dx, *(image + gradx1 + grady)); dest += st->bigwidth - 1; *dest++ = dobright(dx, *(image + gradx + grady1)); *dest = dobright(dx, *(image + gradx1 + grady1)); } else { *dest++ = *(image + gradx + grady); *dest = *(image + gradx1 + grady); dest += st->bigwidth - 1; *dest++ = *(image + gradx + grady1); *dest = *(image + gradx1 + grady1); } } } } #endif /* ------------------------------------------- */ static void setup_X(struct state *st) { XWindowAttributes xgwa; int depth; XGetWindowAttributes(st->dpy, st->window, &xgwa); depth = xgwa.depth; st->colormap = xgwa.colormap; st->screen = xgwa.screen; st->bigwidth = xgwa.width; st->bigheight = xgwa.height; st->visual = xgwa.visual; /* This causes buffer_map to be 1 pixel taller and wider than orig_map, which can cause the two XImages to have different bytes-per-line, which causes stair-stepping. So this better not be necessary... -jwz, 23-Nov-01 */ #if 0 /* I'm not entirely sure if I need this */ if (st->bigwidth % 2) st->bigwidth++; if (st->bigheight % 2) st->bigheight++; #endif st->width = st->bigwidth / 2; st->height = st->bigheight / 2; if (st->transparent) { XGCValues gcv; long gcflags; gcv.function = GXcopy; gcv.subwindow_mode = IncludeInferiors; gcflags = GCFunction; if (use_subwindow_mode_p(xgwa.screen, st->window)) /* see grabscreen.c */ gcflags |= GCSubwindowMode; st->gc = XCreateGC(st->dpy, st->window, gcflags, &gcv); st->img_loader = load_image_async_simple (0, xgwa.screen, st->window, st->window, 0, 0); st->start_time = time ((time_t *) 0); } else { XGCValues gcv; st->gc = XCreateGC(st->dpy, st->window, 0, &gcv); st->orig_map = 0; } if (!st->gc) { fprintf(stderr, "XCreateGC failed\n"); exit(1); } st->buffer_map = create_xshm_image(st->dpy, xgwa.visual, depth, ZPixmap, &st->shm_info, st->bigwidth, st->bigheight); } static void DisplayImage(struct state *st) { put_xshm_image(st->dpy, st->window, st->gc, st->buffer_map, 0, 0, 0, 0, st->bigwidth, st->bigheight, &st->shm_info); } /* ------------------------------------------- */ static int cinterp(double a, int bg, int fg) { int result; result = (int)((1-a) * bg + a * fg + 0.5); if (result < 0) result = 0; if (result > 255) result = 255; return result; } /* Interpolate the ripple colours between the background colour and foreground colour */ static void init_linear_colors(struct state *st) { int i, j, red, green, blue, bred, bgreen, bblue; XColor fg, bg; if (st->ncolors < 2 || mono_p) st->ncolors = 2; if (st->ncolors <= 2) mono_p = True; /* Make it possible to set the color of the ripples, Based on work by Raymond Medeiros and jwz. */ fg.pixel = get_pixel_resource(st->dpy, st->colormap, "foreground", "Foreground"); XQueryColor(st->dpy, st->colormap, &fg); red = (fg.red >> 8); green = (fg.green >> 8); blue = (fg.blue >> 8); bg.pixel = get_pixel_resource(st->dpy, st->colormap, "background", "Background"); XQueryColor(st->dpy, st->colormap, &bg); bred = (bg.red >> 8); bgreen = (bg.green >> 8); bblue = (bg.blue >> 8); j = 0; for (i = 0; i < st->ncolors+1; i++) { XColor xcl; double a = (double)i / st->ncolors; int r = cinterp(a, bred, red); int g = cinterp(a, bgreen, green); int b = cinterp(a, bblue, blue); xcl.red = (unsigned short) ((r << 8) | r); xcl.green = (unsigned short) ((g << 8) | g); xcl.blue = (unsigned short) ((b << 8) | b); xcl.flags = DoRed | DoGreen | DoBlue; XAllocColor(st->dpy, st->colormap, &xcl); st->ctab[j++] = (int) xcl.pixel; } } static void init_oily_colors(struct state *st) { XColor *colors = NULL; if (st->ncolors < 2 || mono_p) st->ncolors = 2; if (st->ncolors <= 2) mono_p = True; colors = 0; if (!mono_p) { colors = (XColor *)malloc(sizeof(*colors) * (st->ncolors+1)); make_smooth_colormap(st->screen, st->visual, st->colormap, colors, &st->ncolors, True, /* allocate */ False, /* not writable */ True); /* verbose (complain about failure) */ if (st->ncolors <= 2) { if (colors) free (colors); colors = 0; mono_p = True; } } if (!mono_p) { int i, j = 0; for (i = 0; i < st->ncolors+1; i++) { XAllocColor(st->dpy, st->colormap, colors+i); st->ctab[j++] = (int) colors[i].pixel; } free (colors); } else { st->ncolors = 2; st->ctab[1] = get_pixel_resource(st->dpy, st->colormap, "foreground", "Foreground"); st->ctab[0] = get_pixel_resource(st->dpy, st->colormap, "background", "Background"); } } /* ------------------------------------------- */ static void init_cos_tab(struct state *st) { int i; for (i = 0; i < TABLE; i++) st->cos_tab[i] = cos(i * M_PI/2 / TABLE); } /* Shape of drop to add */ static double sinc(struct state *st, double x) { #if 1 /* cosine hump */ int i; i = (int)(x * TABLE + 0.5); if (i >= TABLE) i = (TABLE-1) - (i-(TABLE-1)); if (i < 0) return 0.; return st->cos_tab[i]; #elif 0 return cos(x * M_PI/2); #else if (fabs(x) < 0.1) return 1 - x*x; else return sin(x) / x; #endif } static void add_circle_drop(struct state *st, int x, int y, int radius, int dheight) { int r, r2, cx, cy; short *buf = (random()&1) ? st->bufferA : st->bufferB; r2 = radius * radius; for (cy = -radius; cy <= radius; cy++) for (cx = -radius; cx <= radius; cx++) { int xx = x+cx; int yy = y+cy; if (xx < 0 || yy < 0 || xx >= st->width || yy >= st->height) {break;} r = cx*cx + cy*cy; if (r > r2) break; buf[xx + yy*st->width] = (short)(dheight * sinc(st, (radius > 0) ? sqrt(r)/radius : 0)); } } static void add_drop(struct state *st, ripple_mode mode, int drop) { int newx, newy, dheight; int radius = MIN(st->width, st->height) / 50; /* Don't put drops too near the edge of the screen or they get stuck */ int border = 8; switch (mode) { default: case ripple_drop: { int x; dheight = 1 + (random() % drop); newx = border + (random() % (st->width - 2*border)); newy = border + (random() % (st->height - 2*border)); x = newy * st->width + newx; st->bufferA[x + 1] = st->bufferA[x] = st->bufferA[x + st->width] = st->bufferA[x + st->width + 1] = st->bufferB[x + 1] = st->bufferB[x] = st->bufferB[x + st->width] = st->bufferB[x + st->width + 1] = dheight; } break; case ripple_blob: { double power; int tmp_i, tmp_j; power = drop_dist[random() % (sizeof(drop_dist)/sizeof(drop_dist[0]))]; /* clumsy */ dheight = (int)(drop * (power + 0.01)); tmp_i = (int)(st->width - 2*border - 2*radius*power); tmp_j = (int)(st->height - 2*border - 2*radius*power); newx = radius + border + ((tmp_i > 0) ? random() % tmp_i : 0); newy = radius + border + ((tmp_j > 0) ? random() % tmp_j : 0); add_circle_drop(st, newx, newy, radius, dheight); } break; /* Adding too many boxes too quickly (-box 1) doesn't give the waves time to disperse and the waves build up (and overflow) */ case ripple_box: { int x; int cx, cy; short *buf = (random()&1) ? st->bufferA : st->bufferB; int tmp_i, tmp_j; radius = (1 + (random() % 5)) * (1 + (random() % 5)); dheight = drop / 128; if (random() & 1) dheight = -dheight; tmp_i = st->width - 2*border - 2*radius; tmp_j = st->height - 2*border - 2*radius; newx = radius + border + ((tmp_i > 0) ? random() % tmp_i : 0); newy = radius + border + ((tmp_j > 0) ? random() % tmp_j : 0); x = newy * st->width + newx; for (cy = -radius; cy <= radius; cy++) for (cx = -radius; cx <= radius; cx++) buf[x + cx + cy*st->width] = (short)(dheight); } break; case ripple_stir: { border += radius; newx = border + (int)((st->width-2*border) * (1+cos(3*st->stir_ang)) / 2); newy = border + (int)((st->height-2*border) * (1+sin(2*st->stir_ang)) / 2); add_circle_drop(st, newx, newy, radius, drop / 10); st->stir_ang += 0.02; if (st->stir_ang > 12*M_PI) st->stir_ang = 0; } break; } } static void init_ripples(struct state *st, int ndrops, int splash) { int i; if (st->bufferA) free (st->bufferA); if (st->bufferB) free (st->bufferB); if (st->temp) free (st->temp); if (st->dirty_buffer) free (st->dirty_buffer); st->bufferA = (short *)calloc(st->width * st->height, sizeof(*st->bufferA)); st->bufferB = (short *)calloc(st->width * st->height, sizeof(*st->bufferB)); st->temp = (short *)calloc(st->width * st->height, sizeof(*st->temp)); st->dirty_buffer = (char *)calloc(st->width * st->height, sizeof(*st->dirty_buffer)); for (i = 0; i < ndrops; i++) add_drop(st, ripple_blob, splash); if (st->transparent) { if (st->grayscale_p) { int across, down; for (down = 0; down < st->bigheight; down++) for (across = 0; across < st->bigwidth; across++) XPutPixel(st->buffer_map, across, down, grayscale(st, XGetPixel(st->orig_map, across, down))); } else { /* There's got to be a better way of doing this XCopyArea? */ memcpy(st->buffer_map->data, st->orig_map->data, st->bigheight * st->buffer_map->bytes_per_line); } } else { int across, down, color; color = map_color(st, 0); /* background colour */ for (down = 0; down < st->bigheight; down++) for (across = 0; across < st->bigwidth; across++) XPutPixel(st->buffer_map,across, down, color); } DisplayImage(st); } /* Explanation from hq_water.zip (Arturo R Montesinos (ARM) arami@fi.upm.es) Differential equation is: u = a ( u + u ) tt xx yy Where a = tension * gravity / surface_density. Approximating second derivatives by central differences: [ u(t+1)-2u(t)+u(t-1) ] / dt = a [ u(x+1)+u(x-1)+u(y+1)+u(y-1)-4u ] / h where dt = time step squared, h = dx*dy = mesh resolution squared. From where u(t+1) may be calculated as: dt | 1 | dt u(t+1) = a -- | 1 0 1 |u - u(t-1) + (2-4a --)u h | 1 | h When a*dt/h = 1/2 last term vanishes, giving: 1 | 1 | u(t+1) = - | 1 0 1 |u - u(t-1) 2 | 1 | (note that u(t-1,x,y) is only used to calculate u(t+1,x,y) so we can use the same array for both t-1 and t+1, needing only two arrays, U[0] and U[1]) Dampening is simulated by subtracting 1/2^n of result. n=4 gives best-looking result n<4 (eg 2 or 3) thicker consistency, waves die out immediately n>4 (eg 8 or 12) more fluid, waves die out slowly */ static void ripple(struct state *st) { int across, down, pixel; short *src, *dest; if (st->draw_toggle == 0) { src = st->bufferA; dest = st->bufferB; st->draw_toggle = 1; } else { src = st->bufferB; dest = st->bufferA; st->draw_toggle = 0; } switch (st->draw_count) { case 0: case 1: pixel = 1 * st->width + 1; for (down = 1; down < st->height - 1; down++, pixel += 2 * 1) for (across = 1; across < st->width - 1; across++, pixel++) { st->temp[pixel] = (((src[pixel - 1] + src[pixel + 1] + src[pixel - st->width] + src[pixel + st->width]) / 2)) - dest[pixel]; } /* Smooth the output */ pixel = 1 * st->width + 1; for (down = 1; down < st->height - 1; down++, pixel += 2 * 1) for (across = 1; across < st->width - 1; across++, pixel++) { if (st->temp[pixel] != 0) { /* Close enough for government work */ int damp = (st->temp[pixel - 1] + st->temp[pixel + 1] + st->temp[pixel - st->width] + st->temp[pixel + st->width] + st->temp[pixel - st->width - 1] + st->temp[pixel - st->width + 1] + st->temp[pixel + st->width - 1] + st->temp[pixel + st->width + 1] + st->temp[pixel]) / 9; dest[pixel] = damp - (damp >> st->fluidity); } else dest[pixel] = 0; } break; case 2: case 3: pixel = 1 * st->width + 1; for (down = 1; down < st->height - 1; down++, pixel += 2 * 1) for (across = 1; across < st->width - 1; across++, pixel++) { int damp = (((src[pixel - 1] + src[pixel + 1] + src[pixel - st->width] + src[pixel + st->width]) / 2)) - dest[pixel]; dest[pixel] = damp - (damp >> st->fluidity); } break; } if (++st->draw_count > 3) st->draw_count = 0; if (st->transparent) st->draw_transparent(st, dest); else draw_ripple(st, dest); } /* ------------------------------------------- */ static void * ripples_init (Display *disp, Window win) { struct state *st = (struct state *) calloc (1, sizeof(*st)); st->dpy = disp; st->window = win; st->iterations = 0; st->delay = get_integer_resource(disp, "delay", "Integer"); st->duration = get_integer_resource (st->dpy, "duration", "Seconds"); st->rate = get_integer_resource(disp, "rate", "Integer"); st->box = get_integer_resource(disp, "box", "Integer"); st->oily = get_boolean_resource(disp, "oily", "Boolean"); st->stir = get_boolean_resource(disp, "stir", "Boolean"); st->fluidity = get_integer_resource(disp, "fluidity", "Integer"); st->transparent = get_boolean_resource(disp, "water", "Boolean"); st->grayscale_p = get_boolean_resource(disp, "grayscale", "Boolean"); st->light = get_integer_resource(disp, "light", "Integer"); if (st->delay < 0) st->delay = 0; if (st->duration < 1) st->duration = 1; if (st->fluidity <= 1) st->fluidity = 1; if (st->fluidity > 16) st->fluidity = 16; /* 16 = sizeof(short) */ if (st->light < 0) st->light = 0; init_cos_tab(st); setup_X(st); st->ncolors = get_integer_resource (disp, "colors", "Colors"); if (0 == st->ncolors) /* English spelling? */ st->ncolors = get_integer_resource (disp, "colours", "Colors"); if (st->ncolors > sizeof(st->ctab)/sizeof(*st->ctab)) st->ncolors = sizeof(st->ctab)/sizeof(*st->ctab); if (st->oily) init_oily_colors(st); else init_linear_colors(st); if (st->transparent && st->light > 0) { int maxbits; st->draw_transparent = draw_transparent_light; visual_rgb_masks (st->screen, st->visual, &st->rmask, &st->gmask, &st->bmask); set_mask(&st->rmask, &st->rshift); set_mask(&st->gmask, &st->gshift); set_mask(&st->bmask, &st->bshift); if (st->rmask == 0) st->draw_transparent = draw_transparent_vanilla; /* Adjust the shift value "light" when we don't have 8 bits per colour */ maxbits = MIN(MIN(BITCOUNT(st->rmask), BITCOUNT(st->gmask)), BITCOUNT(st->bmask)); st->light -= 8-maxbits; if (st->light < 0) st->light = 0; } else { if (st->grayscale_p) { visual_rgb_masks (st->screen, st->visual, &st->rmask, &st->gmask, &st->bmask); set_mask(&st->rmask, &st->rshift); set_mask(&st->gmask, &st->gshift); set_mask(&st->bmask, &st->bshift); } st->draw_transparent = draw_transparent_vanilla; } if (!st->transparent) init_ripples(st, 0, -SPLASH); /* Start off without any drops */ return st; } static unsigned long ripples_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 */ XWindowAttributes xgwa; XGetWindowAttributes(st->dpy, st->window, &xgwa); st->start_time = time ((time_t *) 0); if (st->orig_map) XDestroyImage (st->orig_map); st->orig_map = XGetImage (st->dpy, st->window, 0, 0, xgwa.width, xgwa.height, ~0L, ZPixmap); init_ripples(st, 0, -SPLASH); /* Start off without any drops */ } return st->delay; } if (!st->img_loader && st->start_time + st->duration < time ((time_t *) 0)) { XWindowAttributes xgwa; XGetWindowAttributes(st->dpy, st->window, &xgwa); st->img_loader = load_image_async_simple (0, xgwa.screen, st->window, st->window, 0, 0); st->start_time = time ((time_t *) 0); return st->delay; } if (st->rate > 0 && (st->iterations % st->rate) == 0) add_drop(st, ripple_blob, -SPLASH); if (st->stir) add_drop(st, ripple_stir, -SPLASH); if (st->box > 0 && (random() % st->box) == 0) add_drop(st, ripple_box, -SPLASH); ripple(st); DisplayImage(st); st->iterations++; return st->delay; } static void ripples_reshape (Display *dpy, Window window, void *closure, unsigned int w, unsigned int h) { } static Bool ripples_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 ripples_free (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; if (st->bufferA) free (st->bufferA); if (st->bufferB) free (st->bufferB); if (st->temp) free (st->temp); if (st->dirty_buffer) free (st->dirty_buffer); if (st->orig_map) XDestroyImage (st->orig_map); if (st->buffer_map) destroy_xshm_image (dpy, st->buffer_map, &st->shm_info); XFreeGC (dpy, st->gc); free (st); } static const char *ripples_defaults[] = { ".background: black", ".foreground: #FFAF5F", "*colors: 200", "*dontClearRoot: True", "*delay: 50000", "*duration: 120", "*rate: 5", "*box: 0", "*water: True", "*oily: False", "*stir: False", "*fluidity: 6", "*light: 4", "*grayscale: False", #ifdef HAVE_XSHM_EXTENSION "*useSHM: True", #else "*useSHM: False", #endif #ifdef HAVE_MOBILE "*ignoreRotation: True", "*rotateImages: True", #endif 0 }; static XrmOptionDescRec ripples_options[] = { { "-colors", ".colors", XrmoptionSepArg, 0}, { "-colours", ".colors", XrmoptionSepArg, 0}, {"-delay", ".delay", XrmoptionSepArg, 0}, {"-duration", ".duration", XrmoptionSepArg, 0 }, {"-rate", ".rate", XrmoptionSepArg, 0}, {"-box", ".box", XrmoptionSepArg, 0}, {"-water", ".water", XrmoptionNoArg, "True"}, {"-oily", ".oily", XrmoptionNoArg, "True"}, {"-stir", ".stir", XrmoptionNoArg, "True"}, {"-fluidity", ".fluidity", XrmoptionSepArg, 0}, {"-light", ".light", XrmoptionSepArg, 0}, {"-grayscale", ".grayscale", XrmoptionNoArg, "True"}, {"-shm", ".useSHM", XrmoptionNoArg, "True"}, {"-no-shm", ".useSHM", XrmoptionNoArg, "False"}, {0, 0, 0, 0} }; XSCREENSAVER_MODULE ("Ripples", ripples)