diff options
author | Simon Rettberg | 2018-10-16 10:08:48 +0200 |
---|---|---|
committer | Simon Rettberg | 2018-10-16 10:08:48 +0200 |
commit | d3a98cf6cbc3bd0b9efc570f58e8812c03931c18 (patch) | |
tree | cbddf8e50f35a9c6e878a5bfe3c6d625d99e12ba /hacks/xanalogtv.c | |
download | xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.gz xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.xz xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.zip |
Original 5.40
Diffstat (limited to 'hacks/xanalogtv.c')
-rw-r--r-- | hacks/xanalogtv.c | 654 |
1 files changed, 654 insertions, 0 deletions
diff --git a/hacks/xanalogtv.c b/hacks/xanalogtv.c new file mode 100644 index 0000000..80cc7d9 --- /dev/null +++ b/hacks/xanalogtv.c @@ -0,0 +1,654 @@ +/* xanalogtv, Copyright (c) 2003-2018 Trevor Blackwell <tlb@tlb.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. + * + * + * Simulate test patterns on an analog TV. Concept similar to xteevee + * in this distribution, but a totally different implementation based + * on the simulation of an analog TV set in utils/analogtv.c. Much + * more realistic, but needs more video card bandwidth. + * + * It flips around through simulated channels 2 through 13. Some show + * pictures from your images directory, some show color bars, and some + * just have static. Some channels receive two stations simultaneously + * so you see a ghostly, misaligned image. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include <math.h> + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#ifndef HAVE_JWXYZ +# include <X11/Intrinsic.h> /* for XtDatabase in hack_resources() */ +#endif + +#include "screenhack.h" +#include "ximage-loader.h" +#include "analogtv.h" + +#define USE_TEST_PATTERNS + +#include "images/gen/logo-180_png.h" + +#ifdef USE_TEST_PATTERNS +# include "images/gen/testcard_rca_png.h" +# include "images/gen/testcard_pm5544_png.h" +# include "images/gen/testcard_bbcf_png.h" +#endif + + +#define countof(x) (sizeof((x))/sizeof((*x))) + +enum { + N_CHANNELS=12, /* Channels 2 through 13 on VHF */ + MAX_MULTICHAN=2, + MAX_STATIONS=6 +}; + +typedef struct chansetting_s { + + analogtv_reception recs[MAX_MULTICHAN]; + double noise_level; + Bool image_loaded_p; +/* char *filename; was only used for diagnostics */ + int dur; +} chansetting; + + +struct state { + Display *dpy; + Window window; + analogtv *tv; + analogtv_font ugly_font; + struct timeval basetime; + + int n_stations; + analogtv_input *stations[MAX_STATIONS]; + Bool image_loading_p; + XImage *logo, *logo_mask; +# ifdef USE_TEST_PATTERNS + XImage *test_patterns[MAX_STATIONS]; +# endif + + int curinputi; + int change_ticks; + chansetting chansettings[N_CHANNELS]; + chansetting *cs; + + int change_now; + int colorbars_only_p; +}; + + +static void +update_smpte_colorbars(analogtv_input *input) +{ + struct state *st = (struct state *) input->client_data; + int col; + int xpos, ypos; + int black_ntsc[4]; + + /* + SMPTE is the society of motion picture and television engineers, and + these are the standard color bars in the US. Following the partial spec + at http://broadcastengineering.com/ar/broadcasting_inside_color_bars/ + These are luma, chroma, and phase numbers for each of the 7 bars. + */ + double top_cb_table[7][3]={ + {75, 0, 0.0}, /* gray */ + {69, 31, 167.0}, /* yellow */ + {56, 44, 283.5}, /* cyan */ + {48, 41, 240.5}, /* green */ + {36, 41, 60.5}, /* magenta */ + {28, 44, 103.5}, /* red */ + {15, 31, 347.0} /* blue */ + }; + double mid_cb_table[7][3]={ + {15, 31, 347.0}, /* blue */ + {7, 0, 0}, /* black */ + {36, 41, 60.5}, /* magenta */ + {7, 0, 0}, /* black */ + {56, 44, 283.5}, /* cyan */ + {7, 0, 0}, /* black */ + {75, 0, 0.0} /* gray */ + }; + + analogtv_lcp_to_ntsc(0.0, 0.0, 0.0, black_ntsc); + + analogtv_setup_sync(input, 1, 0); + analogtv_setup_teletext(input); + + for (col=0; col<7; col++) { + analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.00, 0.68, + top_cb_table[col][0], + top_cb_table[col][1], top_cb_table[col][2]); + + analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.68, 0.75, + mid_cb_table[col][0], + mid_cb_table[col][1], mid_cb_table[col][2]); + } + + analogtv_draw_solid_rel_lcp(input, 0.0, 1.0/6.0, + 0.75, 1.00, 7, 40, 303); /* -I */ + analogtv_draw_solid_rel_lcp(input, 1.0/6.0, 2.0/6.0, + 0.75, 1.00, 100, 0, 0); /* white */ + analogtv_draw_solid_rel_lcp(input, 2.0/6.0, 3.0/6.0, + 0.75, 1.00, 7, 40, 33); /* +Q */ + analogtv_draw_solid_rel_lcp(input, 3.0/6.0, 4.0/6.0, + 0.75, 1.00, 7, 0, 0); /* black */ + analogtv_draw_solid_rel_lcp(input, 12.0/18.0, 13.0/18.0, + 0.75, 1.00, 3, 0, 0); /* black -4 */ + analogtv_draw_solid_rel_lcp(input, 13.0/18.0, 14.0/18.0, + 0.75, 1.00, 7, 0, 0); /* black */ + analogtv_draw_solid_rel_lcp(input, 14.0/18.0, 15.0/18.0, + 0.75, 1.00, 11, 0, 0); /* black +4 */ + analogtv_draw_solid_rel_lcp(input, 5.0/6.0, 6.0/6.0, + 0.75, 1.00, 7, 0, 0); /* black */ + + + ypos=ANALOGTV_V/5; + xpos=ANALOGTV_VIS_START + ANALOGTV_VIS_LEN/2; + + /* if (! st->colorbars_only_p) */ + { + char localname[256]; + if (gethostname (localname, sizeof (localname))==0) { + int L; + localname[sizeof(localname)-1]=0; /* "The returned name is null- + terminated unless insufficient + space is provided" */ + L = strlen(localname); + if (L > 6 && !strcmp(".local", localname+L-6)) + localname[L-6] = 0; + + localname[24]=0; /* limit length */ + + analogtv_draw_string_centered(input, &st->ugly_font, localname, + xpos, ypos, black_ntsc); + } + } + ypos += st->ugly_font.char_h*5/2; + + if (st->logo) + { + int w2 = st->tv->xgwa.width * 0.2; + int h2 = st->tv->xgwa.height * 0.2; + analogtv_load_ximage (st->tv, input, st->logo, st->logo_mask, + (st->tv->xgwa.width - w2) / 2, + st->tv->xgwa.height * 0.28, + w2, h2); + } + + ypos += 58; + +#if 0 + analogtv_draw_string_centered(input, &st->ugly_font, + "Please Stand By", xpos, ypos); + ypos += st->ugly_font.char_h*4; +#endif + + /* if (! st->colorbars_only_p) */ + { + char timestamp[256]; + time_t t = time ((time_t *) 0); + struct tm *tm = localtime (&t); + + /* Y2K: It is OK for this to use a 2-digit year because it's simulating a + TV display and is purely decorative. */ + strftime(timestamp, sizeof(timestamp)-1, "%y.%m.%d %H:%M:%S ", tm); + analogtv_draw_string_centered(input, &st->ugly_font, timestamp, + xpos, ypos, black_ntsc); + } + + + input->next_update_time += 1.0; +} + +static int +getticks(struct state *st) +{ + struct timeval tv; + gettimeofday(&tv,NULL); + return ((tv.tv_sec - st->basetime.tv_sec)*1000 + + (tv.tv_usec - st->basetime.tv_usec)/1000); +} + + +/* The first time we grab an image, do it the default way. + The second and subsequent times, add "-no-desktop" to the command. + That way we don't have to watch the window un-map 5+ times in a row. + Also, we end up with the desktop on only one channel, and pictures + on all the others (or colorbars, if no imageDirectory is set.) + */ +static void +hack_resources (Display *dpy) +{ +#ifndef HAVE_JWXYZ + static int count = -1; + count++; + + if (count == 0) + return; + else if (count == 1) + { + XrmDatabase db = XtDatabase (dpy); + char *res = "desktopGrabber"; + char *val = get_string_resource (dpy, res, "DesktopGrabber"); + char buf1[255]; + char buf2[255]; + XrmValue value; + sprintf (buf1, "%.100s.%.100s", progname, res); + sprintf (buf2, "%.200s -no-desktop", val); + value.addr = buf2; + value.size = strlen(buf2); + XrmPutResource (&db, buf1, "String", &value); + } +#endif /* HAVE_JWXYZ */ +} + + +static void analogtv_load_random_image(struct state *); + + +static void image_loaded_cb (Screen *screen, Window window, Drawable pixmap, + const char *name, XRectangle *geometry, + void *closure) +{ + /* When an image has just been loaded, store it into the first available + channel. If there are other unloaded channels, then start loading + another image. + */ + struct state *st = (struct state *) closure; + int i; + int this = -1; + int next = -1; + + if (!st->image_loading_p) abort(); /* only one at a time... */ + st->image_loading_p = False; + + for (i = 0; i < MAX_STATIONS; i++) { + if (! st->chansettings[i].image_loaded_p) { + if (this == -1) this = i; + else if (next == -1) next = i; + } + } + if (this == -1) abort(); /* no unloaded stations? */ + + /* Load this image into the next channel. */ + { + analogtv_input *input = st->stations[this]; + int width=ANALOGTV_PIC_LEN; + int height=width*3/4; + XImage *image = XGetImage (st->dpy, pixmap, 0, 0, + width, height, ~0L, ZPixmap); + XFreePixmap(st->dpy, pixmap); + + analogtv_setup_sync(input, 1, (random()%20)==0); + analogtv_load_ximage(st->tv, input, image, 0, 0, 0, 0, 0); + if (image) XDestroyImage(image); + st->chansettings[this].image_loaded_p = True; +#if 0 + if (name) { + const char *s = strrchr (name, '/'); + if (s) s++; + else s = name; + st->chansettings[this].filename = strdup (s); + } + fprintf(stderr, "%s: loaded channel %d, %s\n", progname, this, + st->chansettings[this].filename); +#endif + } + + /* If there are still unloaded stations, fire off another loader. */ + if (next != -1) + analogtv_load_random_image (st); +} + + +/* Queues a single image for loading. Only load one at a time. + The image is done loading when st->img_loader is null and + it->loaded_image is a pixmap. + */ +static void +analogtv_load_random_image(struct state *st) +{ + int width=ANALOGTV_PIC_LEN; + int height=width*3/4; + Pixmap p; + + if (st->image_loading_p) /* a load is already in progress */ + return; + + st->image_loading_p = True; + p = XCreatePixmap(st->dpy, st->window, width, height, st->tv->visdepth); + hack_resources(st->dpy); + load_image_async (st->tv->xgwa.screen, st->window, p, image_loaded_cb, st); +} + + +static void add_stations(struct state *st) +{ + while (st->n_stations < MAX_STATIONS) { + analogtv_input *input=analogtv_input_allocate(); + st->stations[st->n_stations++]=input; + input->client_data = st; + } +} + + +static void load_station_images(struct state *st) +{ + int i; + for (i = 0; i < MAX_STATIONS; i++) { + analogtv_input *input = st->stations[i]; + + st->chansettings[i].image_loaded_p = True; + if (i == 0 || /* station 0 is always colorbars */ + st->colorbars_only_p) { + input->updater = update_smpte_colorbars; + input->do_teletext=1; + } +#ifdef USE_TEST_PATTERNS + else if (random()%5==0) { + int count = 0, j; + for (count = 0; st->test_patterns[count]; count++) + ; + j=random()%count; + analogtv_setup_sync(input, 1, 0); + analogtv_load_ximage(st->tv, input, st->test_patterns[j], + 0, 0, 0, 0, 0); + analogtv_setup_teletext(input); + } +#endif + else { + analogtv_load_random_image(st); + input->do_teletext=1; + st->chansettings[i].image_loaded_p = False; + } + } +} + + +static void * +xanalogtv_init (Display *dpy, Window window) +{ + struct state *st = (struct state *) calloc (1, sizeof(*st)); + int i; + int last_station=42; + int delay = get_integer_resource(dpy, "delay", "Integer"); + + if (delay < 1) delay = 1; + + analogtv_make_font(dpy, window, &st->ugly_font, 7, 10, "6x10"); + + st->dpy = dpy; + st->window = window; + st->tv=analogtv_allocate(dpy, window); + + st->colorbars_only_p = + get_boolean_resource(dpy, "colorbarsOnly", "ColorbarsOnly"); + + /* if (!st->colorbars_only_p) */ + { + int w, h; + Pixmap mask = 0; + Pixmap p = image_data_to_pixmap (dpy, window, + logo_180_png, sizeof(logo_180_png), + &w, &h, &mask); + st->logo = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap); + XFreePixmap (dpy, p); + if (mask) + { + st->logo_mask = XGetImage (dpy, mask, 0, 0, w, h, ~0L, ZPixmap); + XFreePixmap (dpy, mask); + } + } + +# ifdef USE_TEST_PATTERNS + { + int i = 0; + int w, h; + Pixmap p; + p = image_data_to_pixmap (dpy, window, + testcard_rca_png, sizeof(testcard_rca_png), + &w, &h, 0); + st->test_patterns[i++] = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap); + XFreePixmap (dpy, p); + + p = image_data_to_pixmap (dpy, window, + testcard_pm5544_png, sizeof(testcard_pm5544_png), + &w, &h, 0); + st->test_patterns[i++] = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap); + XFreePixmap (dpy, p); + + p = image_data_to_pixmap (dpy, window, + testcard_bbcf_png, sizeof(testcard_bbcf_png), + &w, &h, 0); + st->test_patterns[i++] = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap); + XFreePixmap (dpy, p); + } +# endif /* USE_TEST_PATTERNS */ + + + add_stations(st); + + analogtv_set_defaults(st->tv, ""); + st->tv->need_clear=1; + + if (random()%4==0) { + st->tv->tint_control += pow(frand(2.0)-1.0, 7) * 180.0; + } + if (1) { + st->tv->color_control += frand(0.3); + } + + for (i=0; i<N_CHANNELS; i++) { + memset(&st->chansettings[i], 0, sizeof(chansetting)); + + st->chansettings[i].noise_level = 0.06; + st->chansettings[i].dur = 1000*delay; + + if (random()%6==0) { + st->chansettings[i].dur=600; + } + else { + int stati; + for (stati=0; stati<MAX_MULTICHAN; stati++) { + analogtv_reception *rec=&st->chansettings[i].recs[stati]; + int station; + while (1) { + station=random()%st->n_stations; + if (station!=last_station) break; + if ((random()%10)==0) break; + } + last_station=station; + rec->input = st->stations[station]; + rec->level = pow(frand(1.0), 3.0) * 2.0 + 0.05; + rec->ofs=random()%ANALOGTV_SIGNAL_LEN; + if (random()%3) { + rec->multipath = frand(1.0); + } else { + rec->multipath=0.0; + } + if (stati) { + /* We only set a frequency error for ghosting stations, + because it doesn't matter otherwise */ + rec->freqerr = (frand(2.0)-1.0) * 3.0; + } + + if (rec->level > 0.3) break; + if (random()%4) break; + } + } + } + + gettimeofday(&st->basetime,NULL); + + st->curinputi=0; + st->cs = &st->chansettings[st->curinputi]; + st->change_ticks = st->cs->dur + 1500; + + st->tv->powerup=0.0; + + load_station_images(st); + + return st; +} + +static unsigned long +xanalogtv_draw (Display *dpy, Window window, void *closure) +{ + struct state *st = (struct state *) closure; + int i; + + int curticks=getticks(st); + double curtime=curticks*0.001; + + const analogtv_reception *recs[MAX_MULTICHAN]; + unsigned rec_count = 0; + + int auto_change = curticks >= st->change_ticks && st->tv->powerup > 10.0 ? 1 : 0; + + if (st->change_now || auto_change) { + st->curinputi=(st->curinputi+st->change_now+auto_change+N_CHANNELS)%N_CHANNELS; + st->change_now = 0; + st->cs = &st->chansettings[st->curinputi]; +#if 0 + fprintf (stderr, "%s: channel %d, %s\n", progname, st->curinputi, + st->cs->filename); +#endif + st->change_ticks = curticks + st->cs->dur; + /* Set channel change noise flag */ + st->tv->channel_change_cycles=200000; + } + + for (i=0; i<MAX_MULTICHAN; i++) { + analogtv_reception *rec = &st->cs->recs[i]; + analogtv_input *inp=rec->input; + if (!inp) continue; + + if (inp->updater) { + inp->next_update_time = curtime; + (inp->updater)(inp); + } + rec->ofs += rec->freqerr; + } + + st->tv->powerup=curtime; + + for (i=0; i<MAX_MULTICHAN; i++) { + analogtv_reception *rec = &st->cs->recs[i]; + if (rec->input) { + analogtv_reception_update(rec); + recs[rec_count] = rec; + ++rec_count; + } + } + analogtv_draw(st->tv, st->cs->noise_level, recs, rec_count); + +#ifdef HAVE_MOBILE + return 0; +#else + return 5000; +#endif +} + +static void +xanalogtv_reshape (Display *dpy, Window window, void *closure, + unsigned int w, unsigned int h) +{ + struct state *st = (struct state *) closure; + analogtv_reconfigure(st->tv); +} + +static Bool +xanalogtv_event (Display *dpy, Window window, void *closure, XEvent *event) +{ + struct state *st = (struct state *) closure; + + if (event->type == ButtonPress) + { + unsigned button = event->xbutton.button; + st->change_now = button == 2 || button == 3 || button == 5 ? -1 : 1; + return True; + } + else if (event->type == KeyPress) + { + KeySym keysym; + char c = 0; + XLookupString (&event->xkey, &c, 1, &keysym, 0); + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || + keysym == XK_Up || keysym == XK_Right || keysym == XK_Prior) + { + st->change_now = 1; + return True; + } + else if (c == '\b' || + keysym == XK_Down || keysym == XK_Left || keysym == XK_Next) + { + st->change_now = -1; + return True; + } + else if (screenhack_event_helper (dpy, window, event)) + goto DEF; + } + else if (screenhack_event_helper (dpy, window, event)) + { + DEF: + st->change_now = ((random() & 1) ? 1 : -1); + return True; + } + + return False; +} + +static void +xanalogtv_free (Display *dpy, Window window, void *closure) +{ + struct state *st = (struct state *) closure; + analogtv_release(st->tv); + if (st->logo) XDestroyImage (st->logo); + if (st->logo_mask) XDestroyImage (st->logo_mask); +# ifdef USE_TEST_PATTERNS + { + int i; + for (i = 0; i < countof(st->test_patterns); i++) + if (st->test_patterns[i]) XDestroyImage (st->test_patterns[i]); + } +# endif + free (st); +} + + +static const char *xanalogtv_defaults [] = { + ".background: black", + ".foreground: white", + "*delay: 5", + "*grabDesktopImages: False", /* HAVE_JWXYZ */ + "*chooseRandomImages: True", /* HAVE_JWXYZ */ + "*colorbarsOnly: False", + ANALOGTV_DEFAULTS + 0, +}; + +static XrmOptionDescRec xanalogtv_options [] = { + { "-delay", ".delay", XrmoptionSepArg, 0 }, + { "-colorbars-only", ".colorbarsOnly", XrmoptionNoArg, "True" }, + ANALOGTV_OPTIONS + { 0, 0, 0, 0 } +}; + + +XSCREENSAVER_MODULE ("XAnalogTV", xanalogtv) |