From http://www.jwz.org/xscreensaver/xscreensaver-5.40.tar.gz
[xscreensaver] / hacks / xanalogtv.c
1 /* xanalogtv, Copyright (c) 2003-2018 Trevor Blackwell <tlb@tlb.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  *
12  * Simulate test patterns on an analog TV. Concept similar to xteevee
13  * in this distribution, but a totally different implementation based
14  * on the simulation of an analog TV set in utils/analogtv.c. Much
15  * more realistic, but needs more video card bandwidth.
16  *
17  * It flips around through simulated channels 2 through 13. Some show
18  * pictures from your images directory, some show color bars, and some
19  * just have static. Some channels receive two stations simultaneously
20  * so you see a ghostly, misaligned image.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif /* HAVE_CONFIG_H */
26
27 #include <math.h>
28
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif
32
33 #ifndef HAVE_JWXYZ
34 # include <X11/Intrinsic.h> /* for XtDatabase in hack_resources() */
35 #endif
36
37 #include "screenhack.h"
38 #include "ximage-loader.h"
39 #include "analogtv.h"
40
41 #define USE_TEST_PATTERNS
42
43 #include "images/gen/logo-180_png.h"
44
45 #ifdef USE_TEST_PATTERNS
46 # include "images/gen/testcard_rca_png.h"
47 # include "images/gen/testcard_pm5544_png.h"
48 # include "images/gen/testcard_bbcf_png.h"
49 #endif
50
51
52 #define countof(x) (sizeof((x))/sizeof((*x)))
53
54 enum {
55   N_CHANNELS=12, /* Channels 2 through 13 on VHF */
56   MAX_MULTICHAN=2,
57   MAX_STATIONS=6
58 }; 
59
60 typedef struct chansetting_s {
61
62   analogtv_reception recs[MAX_MULTICHAN];
63   double noise_level;
64   Bool image_loaded_p;
65 /*  char *filename;     was only used for diagnostics */
66   int dur;
67 } chansetting;
68
69
70 struct state {
71   Display *dpy;
72   Window window;
73   analogtv *tv;
74   analogtv_font ugly_font;
75   struct timeval basetime;
76
77   int n_stations;
78   analogtv_input *stations[MAX_STATIONS];
79   Bool image_loading_p;
80   XImage *logo, *logo_mask;
81 # ifdef USE_TEST_PATTERNS
82   XImage *test_patterns[MAX_STATIONS];
83 # endif
84
85   int curinputi;
86   int change_ticks;
87   chansetting chansettings[N_CHANNELS];
88   chansetting *cs;
89
90   int change_now;
91   int colorbars_only_p;
92 };
93
94
95 static void
96 update_smpte_colorbars(analogtv_input *input)
97 {
98   struct state *st = (struct state *) input->client_data;
99   int col;
100   int xpos, ypos;
101   int black_ntsc[4];
102
103   /* 
104      SMPTE is the society of motion picture and television engineers, and
105      these are the standard color bars in the US. Following the partial spec
106      at http://broadcastengineering.com/ar/broadcasting_inside_color_bars/
107      These are luma, chroma, and phase numbers for each of the 7 bars.
108   */
109   double top_cb_table[7][3]={
110     {75, 0, 0.0},    /* gray */
111     {69, 31, 167.0}, /* yellow */
112     {56, 44, 283.5}, /* cyan */
113     {48, 41, 240.5}, /* green */
114     {36, 41, 60.5},  /* magenta */
115     {28, 44, 103.5}, /* red */
116     {15, 31, 347.0}  /* blue */
117   };
118   double mid_cb_table[7][3]={
119     {15, 31, 347.0}, /* blue */
120     {7, 0, 0},       /* black */
121     {36, 41, 60.5},  /* magenta */
122     {7, 0, 0},       /* black */
123     {56, 44, 283.5}, /* cyan */
124     {7, 0, 0},       /* black */
125     {75, 0, 0.0}     /* gray */
126   };
127
128   analogtv_lcp_to_ntsc(0.0, 0.0, 0.0, black_ntsc);
129
130   analogtv_setup_sync(input, 1, 0);
131   analogtv_setup_teletext(input);
132
133   for (col=0; col<7; col++) {
134     analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.00, 0.68, 
135                                 top_cb_table[col][0], 
136                                 top_cb_table[col][1], top_cb_table[col][2]);
137     
138     analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.68, 0.75, 
139                                 mid_cb_table[col][0], 
140                                 mid_cb_table[col][1], mid_cb_table[col][2]);
141   }
142
143   analogtv_draw_solid_rel_lcp(input, 0.0, 1.0/6.0,
144                               0.75, 1.00, 7, 40, 303);   /* -I */
145   analogtv_draw_solid_rel_lcp(input, 1.0/6.0, 2.0/6.0,
146                               0.75, 1.00, 100, 0, 0);    /* white */
147   analogtv_draw_solid_rel_lcp(input, 2.0/6.0, 3.0/6.0,
148                               0.75, 1.00, 7, 40, 33);    /* +Q */
149   analogtv_draw_solid_rel_lcp(input, 3.0/6.0, 4.0/6.0,
150                               0.75, 1.00, 7, 0, 0);      /* black */
151   analogtv_draw_solid_rel_lcp(input, 12.0/18.0, 13.0/18.0,
152                               0.75, 1.00, 3, 0, 0);      /* black -4 */
153   analogtv_draw_solid_rel_lcp(input, 13.0/18.0, 14.0/18.0,
154                               0.75, 1.00, 7, 0, 0);      /* black */
155   analogtv_draw_solid_rel_lcp(input, 14.0/18.0, 15.0/18.0,
156                               0.75, 1.00, 11, 0, 0);     /* black +4 */
157   analogtv_draw_solid_rel_lcp(input, 5.0/6.0, 6.0/6.0,
158                               0.75, 1.00, 7, 0, 0);      /* black */
159
160
161   ypos=ANALOGTV_V/5;
162   xpos=ANALOGTV_VIS_START + ANALOGTV_VIS_LEN/2;
163
164   /* if (! st->colorbars_only_p) */
165   {
166     char localname[256];
167     if (gethostname (localname, sizeof (localname))==0) {
168       int L;
169       localname[sizeof(localname)-1]=0; /* "The returned name is null-
170                                            terminated unless insufficient 
171                                            space is provided" */
172       L = strlen(localname);
173       if (L > 6 && !strcmp(".local", localname+L-6))
174         localname[L-6] = 0;
175
176       localname[24]=0; /* limit length */
177
178       analogtv_draw_string_centered(input, &st->ugly_font, localname,
179                                     xpos, ypos, black_ntsc);
180     }
181   }
182   ypos += st->ugly_font.char_h*5/2;
183
184   if (st->logo)
185     {
186       int w2 = st->tv->xgwa.width  * 0.2;
187       int h2 = st->tv->xgwa.height * 0.2;
188       analogtv_load_ximage (st->tv, input, st->logo, st->logo_mask,
189                             (st->tv->xgwa.width - w2) / 2,
190                             st->tv->xgwa.height * 0.28,
191                             w2, h2);
192     }
193
194   ypos += 58;
195
196 #if 0
197   analogtv_draw_string_centered(input, &st->ugly_font,
198                                 "Please Stand By", xpos, ypos);
199   ypos += st->ugly_font.char_h*4;
200 #endif
201
202   /* if (! st->colorbars_only_p) */
203   {
204     char timestamp[256];
205     time_t t = time ((time_t *) 0);
206     struct tm *tm = localtime (&t);
207
208     /* Y2K: It is OK for this to use a 2-digit year because it's simulating a
209        TV display and is purely decorative. */
210     strftime(timestamp, sizeof(timestamp)-1, "%y.%m.%d %H:%M:%S ", tm);
211     analogtv_draw_string_centered(input, &st->ugly_font, timestamp,
212                                   xpos, ypos, black_ntsc);
213   }
214
215   
216   input->next_update_time += 1.0;
217 }
218
219 static int
220 getticks(struct state *st)
221 {
222   struct timeval tv;
223   gettimeofday(&tv,NULL);
224   return ((tv.tv_sec - st->basetime.tv_sec)*1000 +
225           (tv.tv_usec - st->basetime.tv_usec)/1000);
226 }
227
228
229 /* The first time we grab an image, do it the default way.
230    The second and subsequent times, add "-no-desktop" to the command.
231    That way we don't have to watch the window un-map 5+ times in a row.
232    Also, we end up with the desktop on only one channel, and pictures
233    on all the others (or colorbars, if no imageDirectory is set.)
234  */
235 static void
236 hack_resources (Display *dpy)
237 {
238 #ifndef HAVE_JWXYZ
239   static int count = -1;
240   count++;
241
242   if (count == 0)
243     return;
244   else if (count == 1)
245     {
246       XrmDatabase db = XtDatabase (dpy);
247       char *res = "desktopGrabber";
248       char *val = get_string_resource (dpy, res, "DesktopGrabber");
249       char buf1[255];
250       char buf2[255];
251       XrmValue value;
252       sprintf (buf1, "%.100s.%.100s", progname, res);
253       sprintf (buf2, "%.200s -no-desktop", val);
254       value.addr = buf2;
255       value.size = strlen(buf2);
256       XrmPutResource (&db, buf1, "String", &value);
257     }
258 #endif /* HAVE_JWXYZ */
259 }
260
261
262 static void analogtv_load_random_image(struct state *);
263
264
265 static void image_loaded_cb (Screen *screen, Window window, Drawable pixmap,
266                              const char *name, XRectangle *geometry,
267                              void *closure)
268 {
269   /* When an image has just been loaded, store it into the first available
270      channel.  If there are other unloaded channels, then start loading
271      another image.
272   */
273   struct state *st = (struct state *) closure;
274   int i;
275   int this = -1;
276   int next = -1;
277
278   if (!st->image_loading_p) abort();  /* only one at a time... */
279   st->image_loading_p = False;
280
281   for (i = 0; i < MAX_STATIONS; i++) {
282     if (! st->chansettings[i].image_loaded_p) {
283       if (this == -1) this = i;
284       else if (next == -1) next = i;
285     }
286   }
287   if (this == -1) abort();  /* no unloaded stations? */
288
289   /* Load this image into the next channel. */
290   {
291     analogtv_input *input = st->stations[this];
292     int width=ANALOGTV_PIC_LEN;
293     int height=width*3/4;
294     XImage *image = XGetImage (st->dpy, pixmap, 0, 0,
295                                width, height, ~0L, ZPixmap);
296     XFreePixmap(st->dpy, pixmap);
297
298     analogtv_setup_sync(input, 1, (random()%20)==0);
299     analogtv_load_ximage(st->tv, input, image, 0, 0, 0, 0, 0);
300     if (image) XDestroyImage(image);
301     st->chansettings[this].image_loaded_p = True;
302 #if 0
303     if (name) {
304       const char *s = strrchr (name, '/');
305       if (s) s++;
306       else s = name;
307       st->chansettings[this].filename = strdup (s);
308     }
309     fprintf(stderr, "%s: loaded channel %d, %s\n", progname, this, 
310             st->chansettings[this].filename);
311 #endif
312   }
313
314   /* If there are still unloaded stations, fire off another loader. */
315   if (next != -1)
316     analogtv_load_random_image (st);
317 }
318
319
320 /* Queues a single image for loading.  Only load one at a time.
321    The image is done loading when st->img_loader is null and
322    it->loaded_image is a pixmap.
323  */
324 static void
325 analogtv_load_random_image(struct state *st)
326 {
327   int width=ANALOGTV_PIC_LEN;
328   int height=width*3/4;
329   Pixmap p;
330
331   if (st->image_loading_p)  /* a load is already in progress */
332     return;
333
334   st->image_loading_p = True;
335   p = XCreatePixmap(st->dpy, st->window, width, height, st->tv->visdepth);
336   hack_resources(st->dpy);
337   load_image_async (st->tv->xgwa.screen, st->window, p, image_loaded_cb, st);
338 }
339
340
341 static void add_stations(struct state *st)
342 {
343   while (st->n_stations < MAX_STATIONS) {
344     analogtv_input *input=analogtv_input_allocate();
345     st->stations[st->n_stations++]=input;
346     input->client_data = st;
347   }
348 }
349
350
351 static void load_station_images(struct state *st)
352 {
353   int i;
354   for (i = 0; i < MAX_STATIONS; i++) {
355     analogtv_input *input = st->stations[i];
356
357     st->chansettings[i].image_loaded_p = True;
358     if (i == 0 ||   /* station 0 is always colorbars */
359         st->colorbars_only_p) {
360       input->updater = update_smpte_colorbars;
361       input->do_teletext=1;
362     }
363 #ifdef USE_TEST_PATTERNS
364     else if (random()%5==0) {
365       int count = 0, j;
366       for (count = 0; st->test_patterns[count]; count++)
367         ;
368       j=random()%count;
369       analogtv_setup_sync(input, 1, 0);
370       analogtv_load_ximage(st->tv, input, st->test_patterns[j],
371                            0, 0, 0, 0, 0);
372       analogtv_setup_teletext(input);
373     }
374 #endif
375     else {
376       analogtv_load_random_image(st);
377       input->do_teletext=1;
378       st->chansettings[i].image_loaded_p = False;
379     }
380   }
381 }
382
383
384 static void *
385 xanalogtv_init (Display *dpy, Window window)
386 {
387   struct state *st = (struct state *) calloc (1, sizeof(*st));
388   int i;
389   int last_station=42;
390   int delay = get_integer_resource(dpy, "delay", "Integer");
391
392   if (delay < 1) delay = 1;
393
394   analogtv_make_font(dpy, window, &st->ugly_font, 7, 10, "6x10");
395   
396   st->dpy = dpy;
397   st->window = window;
398   st->tv=analogtv_allocate(dpy, window);
399
400   st->colorbars_only_p =
401     get_boolean_resource(dpy, "colorbarsOnly", "ColorbarsOnly");
402
403   /* if (!st->colorbars_only_p) */
404     {
405       int w, h;
406       Pixmap mask = 0;
407       Pixmap p = image_data_to_pixmap (dpy, window,
408                                        logo_180_png, sizeof(logo_180_png),
409                                        &w, &h, &mask);
410       st->logo = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
411       XFreePixmap (dpy, p);
412       if (mask)
413         {
414           st->logo_mask = XGetImage (dpy, mask, 0, 0, w, h, ~0L, ZPixmap);
415           XFreePixmap (dpy, mask);
416         }
417     }
418
419 # ifdef USE_TEST_PATTERNS
420   {
421     int i = 0;
422     int w, h;
423     Pixmap p;
424     p = image_data_to_pixmap (dpy, window,
425                               testcard_rca_png, sizeof(testcard_rca_png),
426                               &w, &h, 0);
427     st->test_patterns[i++] = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
428     XFreePixmap (dpy, p);
429
430     p = image_data_to_pixmap (dpy, window,
431                               testcard_pm5544_png, sizeof(testcard_pm5544_png),
432                               &w, &h, 0);
433     st->test_patterns[i++] = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
434     XFreePixmap (dpy, p);
435
436     p = image_data_to_pixmap (dpy, window,
437                               testcard_bbcf_png, sizeof(testcard_bbcf_png),
438                               &w, &h, 0);
439     st->test_patterns[i++] = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
440     XFreePixmap (dpy, p);
441   }
442 # endif /* USE_TEST_PATTERNS */
443
444
445   add_stations(st);
446
447   analogtv_set_defaults(st->tv, "");
448   st->tv->need_clear=1;
449
450   if (random()%4==0) {
451     st->tv->tint_control += pow(frand(2.0)-1.0, 7) * 180.0;
452   }
453   if (1) {
454     st->tv->color_control += frand(0.3);
455   }
456
457   for (i=0; i<N_CHANNELS; i++) {
458     memset(&st->chansettings[i], 0, sizeof(chansetting));
459
460     st->chansettings[i].noise_level = 0.06;
461     st->chansettings[i].dur = 1000*delay;
462
463     if (random()%6==0) {
464       st->chansettings[i].dur=600;
465     }
466     else {
467       int stati;
468       for (stati=0; stati<MAX_MULTICHAN; stati++) {
469         analogtv_reception *rec=&st->chansettings[i].recs[stati];
470         int station;
471         while (1) {
472           station=random()%st->n_stations;
473           if (station!=last_station) break;
474           if ((random()%10)==0) break;
475         }
476         last_station=station;
477         rec->input = st->stations[station];
478         rec->level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
479         rec->ofs=random()%ANALOGTV_SIGNAL_LEN;
480         if (random()%3) {
481           rec->multipath = frand(1.0);
482         } else {
483           rec->multipath=0.0;
484         }
485         if (stati) {
486           /* We only set a frequency error for ghosting stations,
487              because it doesn't matter otherwise */
488           rec->freqerr = (frand(2.0)-1.0) * 3.0;
489         }
490
491         if (rec->level > 0.3) break;
492         if (random()%4) break;
493       }
494     }
495   }
496
497   gettimeofday(&st->basetime,NULL);
498
499   st->curinputi=0;
500   st->cs = &st->chansettings[st->curinputi];
501   st->change_ticks = st->cs->dur + 1500;
502
503   st->tv->powerup=0.0;
504
505   load_station_images(st);
506
507   return st;
508 }
509
510 static unsigned long
511 xanalogtv_draw (Display *dpy, Window window, void *closure)
512 {
513   struct state *st = (struct state *) closure;
514   int i;
515
516   int curticks=getticks(st);
517   double curtime=curticks*0.001;
518
519   const analogtv_reception *recs[MAX_MULTICHAN];
520   unsigned rec_count = 0;
521
522   int auto_change = curticks >= st->change_ticks && st->tv->powerup > 10.0 ? 1 : 0;
523
524   if (st->change_now || auto_change) {
525     st->curinputi=(st->curinputi+st->change_now+auto_change+N_CHANNELS)%N_CHANNELS;
526     st->change_now = 0;
527     st->cs = &st->chansettings[st->curinputi];
528 #if 0
529     fprintf (stderr, "%s: channel %d, %s\n", progname, st->curinputi,
530              st->cs->filename);
531 #endif
532     st->change_ticks = curticks + st->cs->dur;
533     /* Set channel change noise flag */
534     st->tv->channel_change_cycles=200000;
535   }
536
537   for (i=0; i<MAX_MULTICHAN; i++) {
538     analogtv_reception *rec = &st->cs->recs[i];
539     analogtv_input *inp=rec->input;
540     if (!inp) continue;
541
542     if (inp->updater) {
543       inp->next_update_time = curtime;
544       (inp->updater)(inp);
545     }
546     rec->ofs += rec->freqerr;
547   }
548
549   st->tv->powerup=curtime;
550
551   for (i=0; i<MAX_MULTICHAN; i++) {
552     analogtv_reception *rec = &st->cs->recs[i];
553     if (rec->input) {
554       analogtv_reception_update(rec);
555       recs[rec_count] = rec;
556       ++rec_count;
557     }
558   }
559   analogtv_draw(st->tv, st->cs->noise_level, recs, rec_count);
560
561 #ifdef HAVE_MOBILE
562   return 0;
563 #else
564   return 5000;
565 #endif
566 }
567
568 static void
569 xanalogtv_reshape (Display *dpy, Window window, void *closure, 
570                  unsigned int w, unsigned int h)
571 {
572   struct state *st = (struct state *) closure;
573   analogtv_reconfigure(st->tv);
574 }
575
576 static Bool
577 xanalogtv_event (Display *dpy, Window window, void *closure, XEvent *event)
578 {
579   struct state *st = (struct state *) closure;
580
581   if (event->type == ButtonPress)
582     {
583       unsigned button = event->xbutton.button;
584       st->change_now = button == 2 || button == 3 || button == 5 ? -1 : 1;
585       return True;
586     }
587   else if (event->type == KeyPress)
588     {
589       KeySym keysym;
590       char c = 0;
591       XLookupString (&event->xkey, &c, 1, &keysym, 0);
592       if (c == ' ' || c == '\t' || c == '\r' || c == '\n' ||
593           keysym == XK_Up || keysym == XK_Right || keysym == XK_Prior)
594         {
595           st->change_now = 1;
596           return True;
597         }
598       else if (c == '\b' ||
599                keysym == XK_Down || keysym == XK_Left || keysym == XK_Next)
600         {
601           st->change_now = -1;
602           return True;
603         }
604       else if (screenhack_event_helper (dpy, window, event))
605         goto DEF;
606     }
607   else if (screenhack_event_helper (dpy, window, event))
608     {
609     DEF:
610       st->change_now = ((random() & 1) ? 1 : -1);
611       return True;
612     }
613
614   return False;
615 }
616
617 static void
618 xanalogtv_free (Display *dpy, Window window, void *closure)
619 {
620   struct state *st = (struct state *) closure;
621   analogtv_release(st->tv);
622   if (st->logo) XDestroyImage (st->logo);
623   if (st->logo_mask) XDestroyImage (st->logo_mask);
624 # ifdef USE_TEST_PATTERNS
625   {
626     int i;
627     for (i = 0; i < countof(st->test_patterns); i++)
628       if (st->test_patterns[i]) XDestroyImage (st->test_patterns[i]);
629   }
630 # endif
631   free (st);
632 }
633
634
635 static const char *xanalogtv_defaults [] = {
636   ".background:         black",
637   ".foreground:         white",
638   "*delay:              5",
639   "*grabDesktopImages:  False",   /* HAVE_JWXYZ */
640   "*chooseRandomImages: True",    /* HAVE_JWXYZ */
641   "*colorbarsOnly:      False",
642   ANALOGTV_DEFAULTS
643   0,
644 };
645
646 static XrmOptionDescRec xanalogtv_options [] = {
647   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
648   { "-colorbars-only",  ".colorbarsOnly",       XrmoptionNoArg, "True" },
649   ANALOGTV_OPTIONS
650   { 0, 0, 0, 0 }
651 };
652
653
654 XSCREENSAVER_MODULE ("XAnalogTV", xanalogtv)