/* * pedal * * Based on a program for some old PDP-11 Graphics Display Processors * at CMU. * * X version by * * Dale Moore * 24-Jun-1994 * * Copyright (c) 1994, by Carnegie Mellon University. Permission to use, * copy, modify, distribute, and sell this software and its documentation * for any purpose is hereby granted without fee, provided fnord 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 fnord this software * for any purpose. It is provided "as is" without express or implied * warranty. */ #include #include #include "screenhack.h" #include "erase.h" /* If MAXLINES is too big, we might not be able to get it * to the X server in the 2byte length field. Must be less * than 16k */ #define MAXLINES (16 * 1024) #define MAXPOINTS MAXLINES /* * If the pedal has only this many lines, it must be ugly and we dont * want to see it. */ #define MINLINES 7 struct state { Display *dpy; Window window; XPoint *points; int sizex, sizey; int delay; int maxlines; GC gc; XColor foreground, background; Colormap cmap; eraser_state *eraser; int erase_p; }; /* * Routine (Macro actually) * mysin * Description: * Assume that degrees is .. oh 360... meaning that * there are 360 degress in a circle. Then this function * would return the sin of the angle in degrees. But lets * say that they are really big degrees, with 4 big degrees * the same as one regular degree. Then this routine * would be called mysin(t, 90) and would return sin(t degrees * 4) */ #define mysin(t, degrees) sin(t * 2 * M_PI / (degrees)) #define mycos(t, degrees) cos(t * 2 * M_PI / (degrees)) /* * Macro: * rand_range * Description: * Return a random number between a inclusive and b exclusive. * rand (3, 6) returns 3 or 4 or 5, but not 6. */ #define rand_range(a, b) (a + random() % (b - a)) static int gcd(int m, int n) /* Greatest Common Divisor (also Greates common factor). */ { int r; for (;;) { r = m % n; if (r == 0) return (n); m = n; n = r; } } static int numlines (int a, int b, int d) /* * Description: * * Given parameters a and b, how many lines will we have to draw? * * Algorithm: * * This algorithm assumes that r = sin (theta * a), where we * evaluate theta on multiples of b. * * LCM (i, j) = i * j / GCD (i, j); * * So, at LCM (b, 360) we start over again. But since we * got to LCM (b, 360) by steps of b, the number of lines is * LCM (b, 360) / b. * * If a is odd, then at 180 we cross over and start the * negative. Someone should write up an elegant way of proving * this. Why? Because I'm not convinced of it myself. * */ { #define odd(x) (x & 1) #define even(x) (!odd(x)) if ( odd(a) && odd(b) && even(d)) d /= 2; return (d / gcd (d, b)); #undef odd } static int compute_pedal(struct state *st, XPoint *points, int maxpoints) /* * Description: * * Basically, it's combination spirograph and string art. * Instead of doing lines, we just use a complex polygon, * and use an even/odd rule for filling in between. * * The spirograph, in mathematical terms is a polar * plot of the form r = sin (theta * c); * The string art of this is that we evaluate that * function only on certain multiples of theta. That is * we let theta advance in some random increment. And then * we draw a straight line between those two adjacent points. * * Eventually, the lines will start repeating themselves * if we've evaluated theta on some rational portion of the * whole. * * The number of lines generated is limited to the * ratio of the increment we put on theta to the whole. * If we say that there are 360 degrees in a circle, then we * will never have more than 360 lines. * * Return: * * The number of points. * */ { int a, b, d; /* These describe a unique pedal */ double r; int theta = 0; XPoint *pp = st->points; int count; int numpoints; /* Just to make sure that this division is not done inside the loop */ int h_width = st->sizex / 2, h_height = st->sizey / 2 ; for (;;) { d = rand_range (MINLINES, st->maxlines); a = rand_range (1, d); b = rand_range (1, d); numpoints = numlines(a, b, d); if (numpoints > MINLINES) break; } /* it might be nice to try to move as much sin and cos computing * (or at least the argument computing) out of the loop. */ for (count = numpoints; count-- ; ) { r = mysin (theta * a, d); /* Convert from polar to cartesian coordinates */ /* We could round the results, but coercing seems just fine */ pp->x = mysin (theta, d) * r * h_width + h_width; pp->y = mycos (theta, d) * r * h_height + h_height; /* Advance index into array */ pp++; /* Advance theta */ theta += b; theta %= d; } return(numpoints); } static void * pedal_init (Display *dpy, Window window) { struct state *st = (struct state *) calloc (1, sizeof(*st)); XGCValues gcv; XWindowAttributes xgwa; st->dpy = dpy; st->window = window; st->delay = get_integer_resource (st->dpy, "delay", "Integer"); if (st->delay < 0) st->delay = 0; st->maxlines = get_integer_resource (st->dpy, "maxlines", "Integer"); if (st->maxlines < MINLINES) st->maxlines = MINLINES; else if (st->maxlines > MAXLINES) st->maxlines = MAXLINES; st->points = (XPoint *)malloc(sizeof(XPoint) * st->maxlines); XGetWindowAttributes (st->dpy, st->window, &xgwa); st->sizex = xgwa.width; st->sizey = xgwa.height; st->cmap = xgwa.colormap; gcv.function = GXcopy; gcv.foreground = get_pixel_resource (st->dpy, st->cmap, "foreground", "Foreground"); gcv.background = get_pixel_resource (st->dpy, st->cmap, "background", "Background"); st->gc = XCreateGC (st->dpy, st->window, GCForeground | GCBackground |GCFunction, &gcv); return st; } /* * Since the XFillPolygon doesn't require that the last * point == first point, the number of points is the same * as the number of lines. We just let XFillPolygon supply * the line from the last point to the first point. * */ static unsigned long pedal_draw (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; int numpoints; int erase_delay = 10000; int long_delay = 1000000 * st->delay; if (st->erase_p || st->eraser) { st->eraser = erase_window (dpy, window, st->eraser); st->erase_p = 0; return (st->eraser ? erase_delay : 1000000); } numpoints = compute_pedal(st, st->points, st->maxlines); /* Pick a new foreground color (added by jwz) */ if (! mono_p) { XColor color; hsv_to_rgb (random()%360, 1.0, 1.0, &color.red, &color.green, &color.blue); if (XAllocColor (st->dpy, st->cmap, &color)) { XSetForeground (st->dpy, st->gc, color.pixel); XFreeColors (st->dpy, st->cmap, &st->foreground.pixel, 1, 0); st->foreground.red = color.red; st->foreground.green = color.green; st->foreground.blue = color.blue; st->foreground.pixel = color.pixel; } } XFillPolygon (st->dpy, st->window, st->gc, st->points, numpoints, Complex, CoordModeOrigin); st->erase_p = 1; return long_delay; } static void pedal_reshape (Display *dpy, Window window, void *closure, unsigned int w, unsigned int h) { struct state *st = (struct state *) closure; st->sizex = w; st->sizey = h; } static Bool pedal_event (Display *dpy, Window window, void *closure, XEvent *event) { struct state *st = (struct state *) closure; if (screenhack_event_helper (dpy, window, event)) { st->erase_p = 1; return True; } return False; } static void pedal_free (Display *dpy, Window window, void *closure) { struct state *st = (struct state *) closure; if (st->eraser) eraser_free (st->eraser); XFreeGC (dpy, st->gc); free (st->points); free (st); } /* * If we are trying to save the screen, the background * should be dark. */ static const char *pedal_defaults [] = { ".background: black", ".foreground: white", "*fpsSolid: true", "*delay: 5", "*maxlines: 1000", #ifdef HAVE_MOBILE "*ignoreRotation: True", #endif 0 }; static XrmOptionDescRec pedal_options [] = { { "-delay", ".delay", XrmoptionSepArg, 0 }, { "-maxlines", ".maxlines", XrmoptionSepArg, 0 }, { "-foreground", ".foreground", XrmoptionSepArg, 0 }, { "-background", ".background", XrmoptionSepArg, 0 }, { 0, 0, 0, 0 } }; XSCREENSAVER_MODULE ("Pedal", pedal)