X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Ffilmleader.c;fp=hacks%2Ffilmleader.c;h=ef021e1a7ecbec84f6063b19163e1a846eaee5e2;hp=0000000000000000000000000000000000000000;hb=c85f503f5793839a6be4c818332aca4a96927bb2;hpb=78add6e627ee5f10e1fa6f3852602ea5066eee5a diff --git a/hacks/filmleader.c b/hacks/filmleader.c new file mode 100644 index 00000000..ef021e1a --- /dev/null +++ b/hacks/filmleader.c @@ -0,0 +1,571 @@ +/* filmleader, Copyright (c) 2018 Jamie Zawinski + * + * 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. + * + * Simulate an SMPTE Universal Film Leader playing on an analog television. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "xft.h" /* before screenhack.h */ + +#include "screenhack.h" +#include "analogtv.h" + +#include + +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) + +struct state { + Display *dpy; + Window window; + XWindowAttributes xgwa; + int w, h; + unsigned long bg, text_color, ring_color, trace_color; + XftColor xft_text_color_1, xft_text_color_2; + + XftFont *font, *font2, *font3; + XftDraw *xftdraw; + Pixmap pix; + GC gc; + double start, last_time; + double value; + int stop; + double noise; + analogtv *tv; + analogtv_input *inp; + analogtv_reception rec; + Bool button_down_p; +}; + + +static void * +filmleader_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->inp = analogtv_input_allocate(); + analogtv_setup_sync (st->inp, 1, 0); + st->rec.input = st->inp; + st->rec.level = 2.0; + st->tv->use_color = 1; + st->rec.level = pow(frand(1.0), 3.0) * 2.0 + 0.05; + st->rec.ofs = random() % ANALOGTV_SIGNAL_LEN; + st->tv->powerup = 0; + + st->rec.multipath = 0.0; + st->tv->color_control += frand(0.3); + st->noise = get_float_resource (st->dpy, "noise", "Float"); + st->value = 18; /* Leave time for powerup */ + st->stop = 2 + (random() % 5); + XGetWindowAttributes (dpy, window, &st->xgwa); + + /* Let's render it into a 16:9 pixmap, since that's what most screens are + these days. That means the circle will be squashed on 4:3 screens. */ + { + double r = 16/9.0; + +# ifdef HAVE_MOBILE + /* analogtv.c always fills whole screen on mobile, so use screen aspect. */ + r = st->xgwa.width / (double) st->xgwa.height; + if (r < 1) r = 1/r; +# endif + + st->w = 712; + st->h = st->w / r; + } + + if (st->xgwa.width < st->xgwa.height) + { + int swap = st->w; + st->w = st->h; + st->h = swap; + } + + st->pix = XCreatePixmap (dpy, window, + st->w > st->h ? st->w : st->h, + st->w > st->h ? st->w : st->h, + st->xgwa.depth); + st->gc = XCreateGC (dpy, st->pix, 0, &gcv); + + st->xftdraw = XftDrawCreate (dpy, st->pix, st->xgwa.visual, + st->xgwa.colormap); + st->font = load_xft_font_retry (dpy, screen_number (st->xgwa.screen), + get_string_resource (dpy, "numberFont", + "Font")); + st->font2 = load_xft_font_retry (dpy, screen_number (st->xgwa.screen), + get_string_resource (dpy, "numberFont2", + "Font")); + st->font3 = load_xft_font_retry (dpy, screen_number (st->xgwa.screen), + get_string_resource (dpy, "numberFont3", + "Font")); + + st->bg = get_pixel_resource (dpy, st->xgwa.colormap, + "textBackground", "Background"); + st->text_color = get_pixel_resource (dpy, st->xgwa.colormap, + "textColor", "Foreground"); + st->ring_color = get_pixel_resource (dpy, st->xgwa.colormap, + "ringColor", "Foreground"); + st->trace_color = get_pixel_resource (dpy, st->xgwa.colormap, + "traceColor", "Foreground"); + + XftColorAllocName (dpy, st->xgwa.visual, st->xgwa.colormap, + get_string_resource (dpy, "textColor", "Foreground"), + &st->xft_text_color_1); + XftColorAllocName (dpy, st->xgwa.visual, st->xgwa.colormap, + get_string_resource (dpy, "textBackground", "Background"), + &st->xft_text_color_2); + + return st; +} + + +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 +filmleader_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; + int i, x, y, w2, h2; + XGlyphInfo extents; + int lbearing, rbearing, ascent, descent; + char s[20]; + double r = 1 - (st->value - (int) st->value); + int ivalue = st->value; + XftFont *xftfont; + XftColor *xftcolor; + + /* You may ask, why use Xft for this instead of the much simpler XDrawString? + Well, for some reason, XLoadQueryFont is giving me horribly-scaled bitmap + fonts, but Xft works properly. So perhaps in This Modern World, if one + expects large fonts to work, one must use Xft instead of Xlib? + + Everything is terrible. + */ + + const struct { double t; int k, f; const char * const s[4]; } blurbs[] = { + { 9.1, 3, 1, { "PICTURE", " START", 0, 0 }}, + { 10.0, 2, 1, { " 16", "SOUND", "START", 0 }}, + { 10.5, 2, 1, { " 32", "SOUND", "START", 0 }}, + { 11.6, 2, 0, { "PICTURE", "COMPANY", "SERIES", 0 }}, + { 11.7, 2, 0, { "XSCRNSAVER", 0, 0, 0 }}, + { 11.9, 2, 0, { "REEL No.", "PROD No.", "PLAY DATE", 0 }}, + { 12.2, 0, 0, { " SMPTE ", "UNIVERSAL", " LEADER", 0 }}, + { 12.3, 0, 1, { "X ", "X", "X", "X" }}, + { 12.4, 0, 0, { " SMPTE ", "UNIVERSAL", " LEADER", 0 }}, + { 12.5, 3, 1, { "PICTURE", 0, 0, 0 }}, + { 12.7, 3, 1, { "HEAD", 0, 0, 0 }}, + { 12.8, 2, 1, { "OOOO", 0, "ASPECT", "TYPE OF" }}, + { 12.9, 2, 0, { "SOUND", 0, "RATIO", 0 }}, + { 13.2, 1, 1, { " ", "PICTURE", 0, 0 }}, + { 13.3, 1, 0, { "REEL No. ", "COLOR", 0, 0 }}, + { 13.4, 1, 0, { "LENGTH ", 0, 0, "ROLL" }}, + { 13.5, 1, 0, { "SUBJECT", 0, 0, 0 }}, + { 13.9, 1, 1, { " \342\206\221", "SPLICE", " HERE", 0 }}, + }; + + for (i = 0; i < countof(blurbs); i++) + { + if (st->value >= blurbs[i].t && st->value <= blurbs[i].t + 1/15.0) + { + int line_height; + int j; + xftfont = (blurbs[i].f == 1 ? st->font2 : + blurbs[i].f == 2 ? st->font : st->font3); + + XSetForeground (dpy, st->gc, + blurbs[i].k == 3 ? st->bg : st->text_color); + XFillRectangle (dpy, st->pix, st->gc, 0, 0, st->w, st->h); + XSetForeground (dpy, st->gc, + blurbs[i].k == 3 ? st->text_color : st->bg); + xftcolor = (blurbs[i].k == 3 ? + &st->xft_text_color_1 : &st->xft_text_color_2); + + /* The height of a string of spaces is 0... */ + XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) "My", 2, &extents); + line_height = extents.height; + + XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) + blurbs[i].s[0], strlen(blurbs[i].s[0]), + &extents); + lbearing = -extents.x; + rbearing = extents.width - extents.x; + ascent = extents.y; + descent = extents.height - extents.y; + + x = (st->w - rbearing) / 2; + y = st->h * 0.1 + ascent; + + for (j = 0; j < countof(blurbs[i].s); j++) + { + if (blurbs[i].s[j]) + XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y, + (FcChar8 *) blurbs[i].s[j], + strlen(blurbs[i].s[j])); + + y += line_height * 1.5; + + if (blurbs[i].s[j]) + { + XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) + blurbs[i].s[0], strlen(blurbs[i].s[j]), + &extents); + lbearing = -extents.x; + rbearing = extents.width - extents.x; + ascent = extents.y; + descent = extents.height - extents.y; + } + } + + if (blurbs[i].k == 2) /* Rotate clockwise and flip */ + { + int wh = st->w < st->h ? st->w : st->h; + XImage *img1 = XGetImage (dpy, st->pix, + (st->w - wh) / 2, + (st->h - wh) / 2, + wh, wh, ~0L, ZPixmap); + XImage *img2 = XCreateImage (dpy, st->xgwa.visual, + st->xgwa.depth, ZPixmap, 0, 0, + wh, wh, 32, 0); + img2->data = malloc (img2->bytes_per_line * img2->height); + for (y = 0; y < wh; y++) + for (x = 0; x < wh; x++) + XPutPixel (img2, y, x, XGetPixel (img1, x, y)); + XSetForeground (dpy, st->gc, + blurbs[i].k == 3 ? st->bg : st->text_color); + XFillRectangle (dpy, st->pix, st->gc, 0, 0, st->w, st->h); + XPutImage (dpy, st->pix, st->gc, img2, + 0, 0, + (st->w - wh) / 2, + (st->h - wh) / 2, + wh, wh); + XDestroyImage (img1); + XDestroyImage (img2); + } + else if (blurbs[i].k == 1) /* Flip vertically */ + { + XImage *img1 = XGetImage (dpy, st->pix, 0, 0, + st->w, st->h, ~0L, ZPixmap); + XImage *img2 = XCreateImage (dpy, st->xgwa.visual, + st->xgwa.depth, ZPixmap, 0, 0, + st->w, st->h, 32, 0); + img2->data = malloc (img2->bytes_per_line * img2->height); + for (y = 0; y < img2->height; y++) + for (x = 0; x < img2->width; x++) + XPutPixel (img2, x, img2->height-y-1, + XGetPixel (img1, x, y)); + XPutImage (dpy, st->pix, st->gc, img2, 0, 0, 0, 0, st->w, st->h); + XDestroyImage (img1); + XDestroyImage (img2); + } + + goto DONE; + } + } + + if (st->value < 2.0 || st->value >= 9.0) /* Black screen */ + { + XSetForeground (dpy, st->gc, st->text_color); + XFillRectangle (dpy, st->pix, st->gc, 0, 0, st->w, st->h); + goto DONE; + } + + XSetForeground (dpy, st->gc, st->bg); + XFillRectangle (dpy, st->pix, st->gc, 0, 0, st->w, st->h); + + if (r > 1/30.0) /* Sweep line and background */ + { + x = st->w/2 + st->w * cos (M_PI * 2 * r - M_PI/2); + y = st->h/2 + st->h * sin (M_PI * 2 * r - M_PI/2); + + XSetForeground (dpy, st->gc, st->trace_color); + XFillArc (dpy, st->pix, st->gc, + -st->w, -st->h, st->w*3, st->h*3, + 90*64, + 90*64 - ((r + 0.25) * 360*64)); + + XSetForeground (dpy, st->gc, st->text_color); + XSetLineAttributes (dpy, st->gc, 1, LineSolid, CapRound, JoinRound); + XDrawLine (dpy, st->pix, st->gc, st->w/2, st->h/2, x, y); + + XSetForeground (dpy, st->gc, st->text_color); + XSetLineAttributes (dpy, st->gc, 2, LineSolid, CapRound, JoinRound); + XDrawLine (dpy, st->pix, st->gc, st->w/2, 0, st->w/2, st->h); + XDrawLine (dpy, st->pix, st->gc, 0, st->h/2, st->w, st->h/2); + } + + /* Big number */ + + s[0] = (char) (ivalue + '0'); + xftfont = st->font; + xftcolor = &st->xft_text_color_1; + XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) s, 1, &extents); + lbearing = -extents.x; + rbearing = extents.width - extents.x; + ascent = extents.y; + descent = extents.height - extents.y; + + x = (st->w - (rbearing + lbearing)) / 2; + y = (st->h + (ascent - descent)) / 2; + XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y, (FcChar8 *) s, 1); + + /* Annotations on 7 and 4 */ + + if ((st->value >= 7.75 && st->value <= 7.85) || + (st->value >= 4.00 && st->value <= 4.25)) + { + XSetForeground (dpy, st->gc, st->ring_color); + xftcolor = &st->xft_text_color_2; + xftfont = st->font2; + + s[0] = (ivalue == 4 ? 'C' : 'M'); + s[1] = 0; + + XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) s, strlen(s), &extents); + lbearing = -extents.x; + rbearing = extents.width - extents.x; + ascent = extents.y; + descent = extents.height - extents.y; + + x = st->w * 0.1; + y = st->h * 0.1 + ascent; + XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y, + (FcChar8 *) s, strlen(s)); + x = st->w * 0.9 - (rbearing + lbearing); + XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y, + (FcChar8 *) s, strlen(s)); + + s[0] = (ivalue == 4 ? 'F' : '3'); + s[1] = (ivalue == 4 ? 0 : '5'); + s[2] = 0; + + XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) s, strlen(s), &extents); + lbearing = -extents.x; + rbearing = extents.width - extents.x; + ascent = extents.y; + descent = extents.height - extents.y; + + x = st->w * 0.1; + y = st->h * 0.95; + XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y, + (FcChar8 *) s, strlen(s)); + x = st->w * 0.9 - (rbearing + lbearing); + XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y, + (FcChar8 *) s, strlen(s)); + } + + if (r > 1/30.0) /* Two rings around number */ + { + double r2 = st->w / (double) st->h; + double ss = 1; + + if (st->xgwa.width < st->xgwa.height) + ss = 0.5; + + XSetForeground (dpy, st->gc, st->ring_color); + XSetLineAttributes (dpy, st->gc, st->w * 0.025, + LineSolid, CapRound, JoinRound); + + w2 = st->w * 0.8 * ss / r2; + h2 = st->h * 0.8 * ss; + x = (st->w - w2) / 2; + y = (st->h - h2) / 2; + XDrawArc (dpy, st->pix, st->gc, x, y, w2, h2, 0, 360*64); + + w2 = w2 * 0.8; + h2 = h2 * 0.8; + x = (st->w - w2) / 2; + y = (st->h - h2) / 2; + XDrawArc (dpy, st->pix, st->gc, x, y, w2, h2, 0, 360*64); + } + + DONE: + + img = XGetImage (dpy, st->pix, 0, 0, st->w, st->h, ~0L, ZPixmap); + + 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); + + XDestroyImage (img); + + now = double_time(); + timedelta = (1 / 29.97) - (now - then); + + if (! st->button_down_p) + { + if (st->last_time == 0) + st->start = then; + else + st->value -= then - st->last_time; + + if (st->value <= 0 || + (r > 0.9 && st->value <= st->stop)) + { + st->value = (random() % 20) ? 8.9 : 15; + st->stop = ((random() % 50) ? 2 : 1) + (random() % 5); + + if (st->value > 9) /* Spin the knobs again */ + { + st->rec.level = pow(frand(1.0), 3.0) * 2.0 + 0.05; + st->rec.ofs = random() % ANALOGTV_SIGNAL_LEN; + st->tv->color_control += frand(0.3) - 0.15; + } + } + } + + st->tv->powerup = then - st->start; + st->last_time = then; + + return timedelta > 0 ? timedelta * 1000000 : 0; +} + + +static void +filmleader_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->w > st->h) != (st->xgwa.width > st->xgwa.height)) + { + int swap = st->w; + st->w = st->h; + st->h = swap; + } +} + + +static Bool +filmleader_event (Display *dpy, Window window, void *closure, XEvent *event) +{ + struct state *st = (struct state *) closure; + if (event->xany.type == ButtonPress) + { + st->button_down_p = True; + return True; + } + else if (event->xany.type == ButtonRelease) + { + st->button_down_p = False; + return True; + } + else if (screenhack_event_helper (dpy, window, event)) + { + st->value = 15; + st->rec.level = pow(frand(1.0), 3.0) * 2.0 + 0.05; + st->rec.ofs = random() % ANALOGTV_SIGNAL_LEN; + st->tv->color_control += frand(0.3) - 0.15; + return True; + } + else if (event->xany.type == KeyPress) + { + KeySym keysym; + char c = 0; + XLookupString (&event->xkey, &c, 1, &keysym, 0); + if (c >= '2' && c <= '8') + { + st->value = (c - '0') + (st->value - (int) st->value); + return True; + } + } + + return False; +} + + +static void +filmleader_free (Display *dpy, Window window, void *closure) +{ + struct state *st = (struct state *) closure; + analogtv_release (st->tv); + XftDrawDestroy (st->xftdraw); + XftColorFree(dpy, st->xgwa.visual, st->xgwa.colormap, &st->xft_text_color_1); + XftColorFree(dpy, st->xgwa.visual, st->xgwa.colormap, &st->xft_text_color_2); + XFreePixmap (dpy, st->pix); + XFreeGC (dpy, st->gc); + free (st); +} + + +static const char *filmleader_defaults [] = { + + ".background: #000000", + +# ifdef HAVE_MOBILE + + "*textBackground: #444488", /* Need much higher contrast for some reason */ + "*textColor: #000033", + "*ringColor: #DDDDFF", + "*traceColor: #222244", + +# else /* X11 or Cocoa */ + + "*textBackground: #9999DD", + "*textColor: #000015", + "*ringColor: #DDDDFF", + "*traceColor: #555577", + +# endif + +# ifdef USE_IPHONE + + "*numberFont: Helvetica Bold 120", + "*numberFont2: Helvetica 36", + "*numberFont3: Helvetica 28", + +# else /* X11, Cocoa or Android */ + + "*numberFont: -*-helvetica-bold-r-*-*-*-1700-*-*-*-*-*-*", + "*numberFont2: -*-helvetica-medium-r-*-*-*-500-*-*-*-*-*-*", + "*numberFont3: -*-helvetica-medium-r-*-*-*-360-*-*-*-*-*-*", + +# endif + + + "*noise: 0.04", + ANALOGTV_DEFAULTS + "*geometry: 1280x720", + 0 +}; + +static XrmOptionDescRec filmleader_options [] = { + { "-noise", ".noise", XrmoptionSepArg, 0 }, + ANALOGTV_OPTIONS + { 0, 0, 0, 0 } +}; + +XSCREENSAVER_MODULE ("FilmLeader", filmleader)