summaryrefslogtreecommitdiffstats
path: root/hacks/vfeedback.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/vfeedback.c')
-rw-r--r--hacks/vfeedback.c620
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)