diff options
Diffstat (limited to 'hacks/vfeedback.c')
| -rw-r--r-- | hacks/vfeedback.c | 620 |
1 files changed, 0 insertions, 620 deletions
diff --git a/hacks/vfeedback.c b/hacks/vfeedback.c deleted file mode 100644 index 107bfe5..0000000 --- a/hacks/vfeedback.c +++ /dev/null @@ -1,620 +0,0 @@ -/* vfeedback, Copyright (c) 2018 Jamie Zawinski <jwz@jwz.org> - * - * 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. - * - * Simulates video feedback: pointing a video camera at an NTSC television. - * - * Created: 4-Aug-2018. - * - * TODO: - * - * - Figure out better UI gestures on mobile to pan, zoom and rotate. - * - * - When zoomed in really far, grab_rectangle should decompose pixels - * into RGB phosphor dots. - * - * - Maybe load an image and chroma-key it, letting transparency bleed, - * for that Amiga Genlock, Cabaret Voltaire look. - */ - -#include "screenhack.h" -#include "analogtv.h" - -#include <time.h> - -#undef DEBUG -#undef DARKEN - -#ifdef DEBUG -# include "ximage-loader.h" -# include "images/gen/testcard_bbcf_png.h" -#endif - -struct state { - Display *dpy; - Window window; - XWindowAttributes xgwa; - int w, h; - Pixmap pix; - GC gc; - double start, last_time; - double noise; - double zoom, rot; - double value, svalue, speed, dx, dy, ds, dth; - - struct { double x, y, w, h, th; } rect, orect; - struct { int x, y, s; } specular; - - enum { POWERUP, IDLE, MOVE } state; - analogtv *tv; - analogtv_reception rec; - Bool button_down_p; - int mouse_x, mouse_y; - double mouse_th; - Bool dragmode; - -# ifdef DEBUG - XImage *tcimg; -# endif - -}; - - -static void -twiddle_knobs (struct state *st) -{ - st->rec.ofs = random() % ANALOGTV_SIGNAL_LEN; /* roll picture once */ - st->rec.level = 0.8 + frand(1.0); /* signal strength (low = dark, static) */ - st->tv->color_control = frand(1.0) * RANDSIGN(); - st->tv->contrast_control = 0.4 + frand(1.0); - st->tv->tint_control = frand(360); -} - - -static void -twiddle_camera (struct state *st) -{ -# if 0 - st->rect.x = 0; - st->rect.y = 0; - st->rect.w = 1; - st->rect.h = 1; - st->rect.th = 0; -# else - st->rect.x = frand(0.1) * RANDSIGN(); - st->rect.y = frand(0.1) * RANDSIGN(); - st->rect.w = st->rect.h = 1 + frand(0.4) * RANDSIGN(); - st->rect.th = 0.2 + frand(1.0) * RANDSIGN(); -# endif -} - - -static void * -vfeedback_init (Display *dpy, Window window) -{ - struct state *st = (struct state *) calloc (1, sizeof(*st)); - XGCValues gcv; - - st->dpy = dpy; - st->window = window; - st->tv = analogtv_allocate (st->dpy, st->window); - analogtv_set_defaults (st->tv, ""); - st->tv->need_clear = 1; - st->rec.input = analogtv_input_allocate(); - analogtv_setup_sync (st->rec.input, 1, 0); - st->tv->use_color = 1; - st->tv->powerup = 0; - st->rec.multipath = 0; - twiddle_camera (st); - twiddle_knobs (st); - st->noise = get_float_resource (st->dpy, "noise", "Float"); - st->speed = get_float_resource (st->dpy, "speed", "Float"); - - XGetWindowAttributes (dpy, window, &st->xgwa); - - st->state = POWERUP; - st->value = 0; - - st->w = 640; - st->h = 480; - gcv.foreground = get_pixel_resource (st->dpy, st->xgwa.colormap, - "foreground", "Foreground"); - st->gc = XCreateGC (dpy, st->window, GCForeground, &gcv); - - st->orect = st->rect; - -# ifdef DEBUG - { - int w, h; - Pixmap p; - p = image_data_to_pixmap (dpy, window, - testcard_bbcf_png, sizeof(testcard_bbcf_png), - &w, &h, 0); - st->tcimg = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap); - XFreePixmap (dpy, p); - } -# endif - -# ifndef HAVE_JWXYZ - XSelectInput (dpy, window, - PointerMotionMask | st->xgwa.your_event_mask); -# endif - - return st; -} - - -static double -ease_fn (double r) -{ - return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */ -} - - -static double -ease_ratio (double r) -{ - double ease = 0.5; - if (r <= 0) return 0; - else if (r >= 1) return 1; - else if (r <= ease) return ease * ease_fn (r / ease); - else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease); - else return r; -} - - -static XImage * -grab_rectangle (struct state *st) -{ - XImage *in, *out; - - /* Under XQuartz we can't just do XGetImage on the Window, we have to - go through an intermediate Pixmap first. I don't understand why. - */ - if (! st->pix) - st->pix = XCreatePixmap (st->dpy, st->window, - st->xgwa.width, st->xgwa.height, st->xgwa.depth); - - XCopyArea (st->dpy, st->window, st->pix, st->gc, 0, 0, - st->xgwa.width, st->xgwa.height, 0, 0); - - if (st->specular.s) - { - double p = 0.2; - double r = (st->svalue < p ? st->svalue/p : - st->svalue >= 1-p ? (1-st->svalue)/p : - 1); - double s = st->specular.s * ease_ratio (r * 2); - XFillArc (st->dpy, st->pix, st->gc, - st->specular.x - s/2, - st->specular.y - s/2, - s, s, 0, 360*64); - } - -# ifdef DEBUG - in = st->tcimg; -# else - in = XGetImage (st->dpy, st->pix, - 0, 0, st->xgwa.width, st->xgwa.height, - ~0L, ZPixmap); - /* Could actually use st->tv->image here, except we don't have the - subrectangle being used (overall_top, usewidth, etc.) */ -# endif - - out = XCreateImage (st->dpy, st->xgwa.visual, st->xgwa.depth, - ZPixmap, 0, NULL, - st->w, st->h, 8, 0); - - if (! in) abort(); - if (! out) abort(); - out->data = (char *) calloc (out->height, out->bytes_per_line); - if (! out->data) abort(); - - { - double C = cos (st->rect.th); - double S = sin (st->rect.th); - unsigned long black = BlackPixelOfScreen (st->xgwa.screen); - int ox, oy; - for (oy = 0; oy < out->height; oy++) - { - double doy = (double) oy / out->height; - double diy = st->rect.h * doy + st->rect.y - 0.5; - - float dix_mul = (float) st->rect.w / out->width; - float dix_add = (-0.5 + st->rect.x) * st->rect.w; - float ix_add = (-diy * S + 0.5) * in->width; - float iy_add = ( diy * C + 0.5) * in->height; - float ix_mul = C * in->width; - float iy_mul = S * in->height; - - ix_add += dix_add * ix_mul; - iy_add += dix_add * iy_mul; - ix_mul *= dix_mul; - iy_mul *= dix_mul; - - if (in->bits_per_pixel == 32 && - out->bits_per_pixel == 32) - { - /* Unwrapping XGetPixel and XPutPixel gains us several FPS here */ - uint32_t *out_line = - (uint32_t *) (out->data + out->bytes_per_line * oy); - for (ox = 0; ox < out->width; ox++) - { - float dix = ox; - int ix = dix * ix_mul + ix_add; - int iy = dix * iy_mul + iy_add; - unsigned long p = (ix >= 0 && ix < in->width && - iy >= 0 && iy < in->height - ? ((uint32_t *) - (in->data + in->bytes_per_line * iy))[ix] - : black); -# ifdef HAVE_JWXYZ - p |= black; /* We get 0 instead of BlackPixel... */ -# endif - out_line[ox] = p; - } - } - else - for (ox = 0; ox < out->width; ox++) - { - float dix = ox; - int ix = dix * ix_mul + ix_add; - int iy = dix * iy_mul + iy_add; - unsigned long p = (ix >= 0 && ix < in->width && - iy >= 0 && iy < in->height - ? XGetPixel (in, ix, iy) - : black); -# ifdef HAVE_JWXYZ - p |= black; /* We get 0 instead of BlackPixel... */ -# endif - XPutPixel (out, ox, oy, p); - } - } - } - -# ifndef DEBUG - XDestroyImage (in); -# endif - - return out; -} - - -static double -double_time (void) -{ - struct timeval now; -# ifdef GETTIMEOFDAY_TWO_ARGS - struct timezone tzp; - gettimeofday(&now, &tzp); -# else - gettimeofday(&now); -# endif - - return (now.tv_sec + ((double) now.tv_usec * 0.000001)); -} - - -static unsigned long -vfeedback_draw (Display *dpy, Window window, void *closure) -{ - struct state *st = (struct state *) closure; - const analogtv_reception *rec = &st->rec; - double then = double_time(), now, timedelta; - XImage *img = 0; - - switch (st->state) { - case POWERUP: case IDLE: break; - case MOVE: - st->rect.x = st->orect.x + st->dx * ease_ratio (st->value); - st->rect.y = st->orect.y + st->dy * ease_ratio (st->value); - st->rect.th = st->orect.th + st->dth * ease_ratio (st->value); - st->rect.w = st->orect.w * (1 + (st->ds * ease_ratio (st->value))); - st->rect.h = st->orect.h * (1 + (st->ds * ease_ratio (st->value))); - break; - default: - abort(); - break; - } - - if (! st->button_down_p) - { - st->value += 0.03 * st->speed; - if (st->value > 1 || st->state == POWERUP) - { - st->orect = st->rect; - st->value = 0; - st->dx = st->dy = st->ds = st->dth = 0; - - switch (st->state) { - case POWERUP: - /* Wait until the monitor has warmed up before turning on - the camcorder? */ - /* if (st->tv->powerup > 4.0) */ - st->state = IDLE; - break; - case IDLE: - st->state = MOVE; - if (! (random() % 5)) - st->ds = frand(0.2) * RANDSIGN(); /* zoom */ - if (! (random() % 3)) - st->dth = frand(0.2) * RANDSIGN(); /* rotate */ - if (! (random() % 8)) - st->dx = frand(0.05) * RANDSIGN(), /* pan */ - st->dy = frand(0.05) * RANDSIGN(); - if (! (random() % 2000)) - { - twiddle_knobs (st); - if (! (random() % 10)) - twiddle_camera (st); - } - break; - case MOVE: - st->state = IDLE; - st->value = 0.3; - break; - default: - abort(); - break; - } - } - - /* Draw a specular reflection somewhere on the screen, to mix it up - with a little noise from environmental light. - */ - if (st->specular.s) - { - st->svalue += 0.01 * st->speed; - if (st->svalue > 1) - { - st->svalue = 0; - st->specular.s = 0; - } - } - else if (! (random() % 300)) - { -# if 1 - /* Center on the monitor's screen, depth 1 */ - int cx = st->xgwa.width / 2; - int cy = st->xgwa.height / 2; -# else - /* Center on the monitor's screen, depth 0 -- but this clips. */ - int cx = (st->rect.x + st->rect.w / 2) * st->xgwa.width; - int cy = (st->rect.y + st->rect.h / 2) * st->xgwa.height; -# endif - int ww = 4 + (st->rect.h * st->xgwa.height) / 12; - st->specular.x = cx + (random() % ww) * RANDSIGN(); - st->specular.y = cy + (random() % ww) * RANDSIGN(); - st->specular.s = ww * (0.8 + frand(0.4)); - st->svalue = 0; - } - } - - if (st->last_time == 0) - st->start = then; - - if (st->state != POWERUP) - { - img = grab_rectangle (st); - analogtv_load_ximage (st->tv, st->rec.input, img, 0, 0, 0, 0, 0); - } - - analogtv_reception_update (&st->rec); - analogtv_draw (st->tv, st->noise, &rec, 1); - if (img) - XDestroyImage (img); - - now = double_time(); - timedelta = (1 / 29.97) - (now - then); - - st->tv->powerup = then - st->start; - st->last_time = then; - - return timedelta > 0 ? timedelta * 1000000 : 0; -} - - -static void -vfeedback_reshape (Display *dpy, Window window, void *closure, - unsigned int w, unsigned int h) -{ - struct state *st = (struct state *) closure; - analogtv_reconfigure (st->tv); - XGetWindowAttributes (dpy, window, &st->xgwa); - - if (st->pix) - { - XFreePixmap (dpy, st->pix); - st->pix = 0; - } -} - - -static Bool -vfeedback_event (Display *dpy, Window window, void *closure, XEvent *event) -{ - struct state *st = (struct state *) closure; - double i = 0.02; - - /* Pan with left button and no modifier keys. - Rotate with other buttons, or left-with-modifiers. - */ - if (event->xany.type == ButtonPress && - (event->xbutton.button == Button1 || - event->xbutton.button == Button2 || - event->xbutton.button == Button3)) - { - st->button_down_p = True; - st->mouse_x = event->xbutton.x; - st->mouse_y = event->xbutton.y; - st->mouse_th = st->rect.th; - st->dragmode = (event->xbutton.button == Button1 && - !event->xbutton.state); - return True; - } - else if (event->xany.type == ButtonRelease && - (event->xbutton.button == Button1 || - event->xbutton.button == Button2 || - event->xbutton.button == Button3)) - { - st->button_down_p = False; - return True; - } - else if (event->xany.type == MotionNotify && st->button_down_p) - { - if (st->dragmode) - { - double dx = st->mouse_x - event->xmotion.x; - double dy = st->mouse_y - event->xmotion.y; - st->rect.x += dx / st->xgwa.width * st->rect.w; - st->rect.y += dy / st->xgwa.height * st->rect.h; - st->mouse_x = event->xmotion.x; - st->mouse_y = event->xmotion.y; - } - else - { - /* Angle between center and initial click */ - double a1 = -atan2 (st->mouse_y - st->xgwa.height / 2, - st->mouse_x - st->xgwa.width / 2); - /* Angle between center and drag position */ - double a2 = -atan2 (event->xmotion.y - st->xgwa.height / 2, - event->xmotion.x - st->xgwa.width / 2); - /* Cumulatively rotate by difference between them */ - st->rect.th = a2 - a1 + st->mouse_th; - } - goto OK; - } - - /* Zoom with mouse wheel */ - - else if (event->xany.type == ButtonPress && - (event->xbutton.button == Button4 || - event->xbutton.button == Button6)) - { - i = 1-i; - goto ZZ; - } - else if (event->xany.type == ButtonPress && - (event->xbutton.button == Button5 || - event->xbutton.button == Button7)) - { - i = 1+i; - goto ZZ; - } - else if (event->type == KeyPress) - { - KeySym keysym; - char c = 0; - XLookupString (&event->xkey, &c, 1, &keysym, 0); - switch (keysym) { - /* pan with arrow keys */ - case XK_Up: st->rect.y += i; goto OK; break; - case XK_Down: st->rect.y -= i; goto OK; break; - case XK_Left: st->rect.x += i; goto OK; break; - case XK_Right: st->rect.x -= i; goto OK; break; - default: break; - } - switch (c) { - - /* rotate with <> */ - case '<': case ',': st->rect.th += i; goto OK; break; - case '>': case '.': st->rect.th -= i; goto OK; break; - - /* zoom with += */ - case '-': case '_': - i = 1+i; - goto ZZ; - case '=': case '+': - i = 1-i; - ZZ: - st->orect = st->rect; - st->rect.w *= i; - st->rect.h *= i; - st->rect.x += (st->orect.w - st->rect.w) / 2; - st->rect.y += (st->orect.h - st->rect.h) / 2; - goto OK; - break; - - /* tv controls with T, C, B, O */ - case 't': st->tv->tint_control += 5; goto OK; break; - case 'T': st->tv->tint_control -= 5; goto OK; break; - case 'c': st->tv->color_control += 0.1; goto OK; break; - case 'C': st->tv->color_control -= 0.1; goto OK; break; - case 'b': st->tv->brightness_control += 0.01; goto OK; break; - case 'B': st->tv->brightness_control -= 0.01; goto OK; break; - case 'o': st->tv->contrast_control += 0.1; goto OK; break; - case 'O': st->tv->contrast_control -= 0.1; goto OK; break; - case 'r': st->rec.level += 0.01; goto OK; break; - case 'R': st->rec.level -= 0.01; goto OK; break; - default: break; - } - goto NOPE; - OK: -# if 0 - fprintf (stderr, " %.6f x %.6f @ %.6f, %.6f %.6f\t", - st->rect.w, st->rect.h, - st->rect.x, st->rect.y, st->rect.th); - fprintf (stderr," T=%.2f C=%.2f B=%.2f O=%.2f R=%.3f\n", - st->tv->tint_control, - st->tv->color_control/* * 100*/, - st->tv->brightness_control/* * 100*/, - st->tv->contrast_control/* * 100*/, - st->rec.level); -# endif - st->value = 0; - st->state = IDLE; - st->orect = st->rect; - return True; - } - - NOPE: - if (screenhack_event_helper (dpy, window, event)) - { - /* SPC or RET re-randomize the TV controls. */ - twiddle_knobs (st); - goto OK; - } - - return False; -} - - -static void -vfeedback_free (Display *dpy, Window window, void *closure) -{ - struct state *st = (struct state *) closure; - analogtv_release (st->tv); - free (st->rec.input); - if (st->pix) - XFreePixmap (dpy, st->pix); - XFreeGC (dpy, st->gc); - free (st); -} - - -static const char *vfeedback_defaults [] = { - - ".foreground: #CCCC44", - ".background: #000000", - "*noise: 0.02", - "*speed: 1.0", - ANALOGTV_DEFAULTS - "*TVBrightness: 1.5", - "*TVContrast: 150", - 0 -}; - -static XrmOptionDescRec vfeedback_options [] = { - { "-noise", ".noise", XrmoptionSepArg, 0 }, - { "-speed", ".speed", XrmoptionSepArg, 0 }, - ANALOGTV_OPTIONS - { 0, 0, 0, 0 } -}; - -XSCREENSAVER_MODULE ("VFeedback", vfeedback) |
