http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.20.tar.gz
[xscreensaver] / hacks / xanalogtv.c
1 /* xanalogtv, Copyright (c) 2003 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  * It's easy to add some test patterns by compiling in an XPM, but I
23  * can't find any that are clearly freely redistributable.
24  *
25  */
26
27 #include <math.h>
28 #include "screenhack.h"
29 #include "xpm-pixmap.h"
30 #include "analogtv.h"
31 #include <stdio.h>
32 #include <time.h>
33 #include <sys/time.h>
34 #include <X11/Xlib.h>
35 #include <X11/Xutil.h>
36 #include <X11/Intrinsic.h>
37
38 #include "images/logo-50.xpm"
39
40 /* #define DEBUG 1 */
41 /* #define USE_TEST_PATTERNS */
42
43 #define countof(x) (sizeof((x))/sizeof((*x)))
44
45 static analogtv *tv=NULL;
46
47 analogtv_font ugly_font;
48
49 static void
50 update_smpte_colorbars(analogtv_input *input)
51 {
52   int col;
53   int xpos, ypos;
54   int black_ntsc[4];
55
56   /* 
57      SMPTE is the society of motion picture and television engineers, and
58      these are the standard color bars in the US. Following the partial spec
59      at http://broadcastengineering.com/ar/broadcasting_inside_color_bars/
60      These are luma, chroma, and phase numbers for each of the 7 bars.
61   */
62   double top_cb_table[7][3]={
63     {75, 0, 0.0},    /* gray */
64     {69, 31, 167.0}, /* yellow */
65     {56, 44, 283.5}, /* cyan */
66     {48, 41, 240.5}, /* green */
67     {36, 41, 60.5},  /* magenta */
68     {28, 44, 103.5}, /* red */
69     {15, 31, 347.0}  /* blue */
70   };
71   double mid_cb_table[7][3]={
72     {15, 31, 347.0}, /* blue */
73     {7, 0, 0},       /* black */
74     {36, 41, 60.5},  /* magenta */
75     {7, 0, 0},       /* black */
76     {56, 44, 283.5}, /* cyan */
77     {7, 0, 0},       /* black */
78     {75, 0, 0.0}     /* gray */
79   };
80
81   analogtv_lcp_to_ntsc(0.0, 0.0, 0.0, black_ntsc);
82
83   analogtv_setup_sync(input, 1, 0);
84   analogtv_setup_teletext(input);
85
86   for (col=0; col<7; col++) {
87     analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.00, 0.68, 
88                                 top_cb_table[col][0], 
89                                 top_cb_table[col][1], top_cb_table[col][2]);
90     
91     analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.68, 0.75, 
92                                 mid_cb_table[col][0], 
93                                 mid_cb_table[col][1], mid_cb_table[col][2]);
94   }
95
96   analogtv_draw_solid_rel_lcp(input, 0.0, 1.0/6.0,
97                               0.75, 1.00, 7, 40, 303);   /* -I */
98   analogtv_draw_solid_rel_lcp(input, 1.0/6.0, 2.0/6.0,
99                               0.75, 1.00, 100, 0, 0);    /* white */
100   analogtv_draw_solid_rel_lcp(input, 2.0/6.0, 3.0/6.0,
101                               0.75, 1.00, 7, 40, 33);    /* +Q */
102   analogtv_draw_solid_rel_lcp(input, 3.0/6.0, 4.0/6.0,
103                               0.75, 1.00, 7, 0, 0);      /* black */
104   analogtv_draw_solid_rel_lcp(input, 12.0/18.0, 13.0/18.0,
105                               0.75, 1.00, 3, 0, 0);      /* black -4 */
106   analogtv_draw_solid_rel_lcp(input, 13.0/18.0, 14.0/18.0,
107                               0.75, 1.00, 7, 0, 0);      /* black */
108   analogtv_draw_solid_rel_lcp(input, 14.0/18.0, 15.0/18.0,
109                               0.75, 1.00, 11, 0, 0);     /* black +4 */
110   analogtv_draw_solid_rel_lcp(input, 5.0/6.0, 6.0/6.0,
111                               0.75, 1.00, 7, 0, 0);      /* black */
112
113
114   ypos=ANALOGTV_V/5;
115   xpos=ANALOGTV_VIS_START + ANALOGTV_VIS_LEN/2;
116
117   {
118     char localname[256];
119     if (gethostname (localname, sizeof (localname))==0) {
120       localname[sizeof(localname)-1]=0; /* "The returned name is null-
121                                            terminated unless insufficient 
122                                            space is provided" */
123       localname[24]=0; /* limit length */
124
125       analogtv_draw_string_centered(input, &ugly_font, localname,
126                                     xpos, ypos, black_ntsc);
127     }
128   }
129   ypos += ugly_font.char_h*5/2;
130
131   analogtv_draw_xpm(tv, input,
132                     logo_50_xpm, xpos - 100, ypos);
133
134   ypos += 58;
135
136 #if 0
137   analogtv_draw_string_centered(input, &ugly_font, "Please Stand By", xpos, ypos);
138   ypos += ugly_font.char_h*4;
139 #endif
140
141   {
142     char timestamp[256];
143     time_t t = time ((time_t *) 0);
144     struct tm *tm = localtime (&t);
145
146     /* Y2K: It is OK for this to use a 2-digit year because it's simulating a
147        TV display and is purely decorative. */
148     strftime(timestamp, sizeof(timestamp)-1, "%y.%m.%d %H:%M:%S ", tm);
149     analogtv_draw_string_centered(input, &ugly_font, timestamp,
150                                   xpos, ypos, black_ntsc);
151   }
152
153   
154   input->next_update_time += 1.0;
155 }
156
157 #if 0
158 static void
159 draw_color_square(analogtv_input *input)
160 {
161   double xs,ys;
162
163   analogtv_draw_solid_rel_lcp(input, 0.0, 1.0, 0.0, 1.0,
164                               30.0, 0.0, 0.0);
165   
166   for (xs=0.0; xs<0.9999; xs+=1.0/15.0) {
167     analogtv_draw_solid_rel_lcp(input, xs, xs, 0.0, 1.0,
168                                 100.0, 0.0, 0.0);
169   }
170
171   for (ys=0.0; ys<0.9999; ys+=1.0/11.0) {
172     analogtv_draw_solid_rel_lcp(input, 0.0, 1.0, ys, ys,
173                                 100.0, 0.0, 0.0);
174   }
175
176   for (ys=0.0; ys<0.9999; ys+=0.01) {
177     
178     analogtv_draw_solid_rel_lcp(input, 0.0/15, 1.0/15, ys, ys+0.01,
179                                 40.0, 45.0, 103.5*(1.0-ys) + 347.0*ys);
180
181     analogtv_draw_solid_rel_lcp(input, 14.0/15, 15.0/15, ys, ys+0.01,
182                                 40.0, 45.0, 103.5*(ys) + 347.0*(1.0-ys));
183   }
184
185   for (ys=0.0; ys<0.9999; ys+=0.02) {
186     analogtv_draw_solid_rel_lcp(input, 1.0/15, 2.0/15, ys*2.0/11.0+1.0/11.0, 
187                                 (ys+0.01)*2.0/11.0+1.0/11.0,
188                                 100.0*(1.0-ys), 0.0, 0.0);
189   }
190
191
192 }
193 #endif
194
195 char *progclass = "XAnalogTV";
196
197 char *defaults [] = {
198   ".background:      black",
199   ".foreground:      white",
200   "*delay:           5",
201   ANALOGTV_DEFAULTS
202   0,
203 };
204
205 XrmOptionDescRec options [] = {
206   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
207   ANALOGTV_OPTIONS
208   { 0, 0, 0, 0 }
209 };
210
211
212 #ifdef USE_TEST_PATTERNS
213
214 #include "images/earth.xpm"
215
216 char **test_patterns[] = {
217   earth_xpm,
218 };
219
220 #endif
221
222
223 enum {
224   N_CHANNELS=12, /* Channels 2 through 13 on VHF */
225   MAX_MULTICHAN=2
226 }; 
227
228 typedef struct chansetting_s {
229
230   analogtv_reception recs[MAX_MULTICHAN];
231   double noise_level;
232
233   int dur;
234 } chansetting;
235
236 static struct timeval basetime;
237
238 static int
239 getticks(void)
240 {
241   struct timeval tv;
242   gettimeofday(&tv,NULL);
243   return ((tv.tv_sec - basetime.tv_sec)*1000 +
244           (tv.tv_usec - basetime.tv_usec)/1000);
245 }
246
247
248 /* The first time we grab an image, do it the default way.
249    The second and subsequent times, add "-no-desktop" to the command.
250    That way we don't have to watch the window un-map 5+ times in a row.
251    Also, we end up with the desktop on only one channel, and pictures
252    on all the others (or colorbars, if no imageDirectory is set.)
253  */
254 static void
255 hack_resources (void)
256 {
257   static int count = -1;
258   count++;
259
260   if (count == 0)
261     return;
262   else if (count == 1)
263     {
264       char *res = "desktopGrabber";
265       char *val = get_string_resource (res, "DesktopGrabber");
266       char buf1[255];
267       char buf2[255];
268       XrmValue value;
269       sprintf (buf1, "%.100s.%.100s", progclass, res);
270       sprintf (buf2, "%.200s -no-desktop", val);
271       value.addr = buf2;
272       value.size = strlen(buf2);
273       XrmPutResource (&db, buf1, "String", &value);
274     }
275 }
276
277
278 int
279 analogtv_load_random_image(analogtv *it, analogtv_input *input)
280 {
281   Pixmap pixmap;
282   XImage *image=NULL;
283   int width=ANALOGTV_PIC_LEN;
284   int height=width*3/4;
285   int rc;
286
287   pixmap=XCreatePixmap(it->dpy, it->window, width, height, it->visdepth);
288   XSync(it->dpy, False);
289   hack_resources();
290   load_random_image(it->screen, it->window, pixmap, NULL, NULL);
291   image = XGetImage(it->dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap);
292   XFreePixmap(it->dpy, pixmap);
293
294   /* Make sure the window's background is not set to None, and get the
295      grabbed bits (if any) off it as soon as possible. */
296   XSetWindowBackground (it->dpy, it->window,
297                         get_pixel_resource ("background", "Background",
298                                             it->dpy, it->xgwa.colormap));
299   XClearWindow (it->dpy, it->window);
300
301   analogtv_setup_sync(input, 1, (random()%20)==0);
302   rc=analogtv_load_ximage(it, input, image);
303   if (image) XDestroyImage(image);
304   XSync(it->dpy, False);
305   return rc;
306 }
307
308 int
309 analogtv_load_xpm(analogtv *it, analogtv_input *input, char **xpm)
310 {
311   Pixmap pixmap;
312   XImage *image;
313   int width,height;
314   int rc;
315
316   pixmap=xpm_data_to_pixmap (it->dpy, it->window, xpm,
317                              &width, &height, NULL);
318   image = XGetImage(it->dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap);
319   XFreePixmap(it->dpy, pixmap);
320   rc=analogtv_load_ximage(it, input, image);
321   if (image) XDestroyImage(image);
322   XSync(it->dpy, False);
323   return rc;
324 }
325
326 enum { MAX_STATIONS = 6 };
327 static int n_stations;
328 static analogtv_input *stations[MAX_STATIONS];
329
330
331 void add_stations(void)
332 {
333   while (n_stations < MAX_STATIONS) {
334     analogtv_input *input=analogtv_input_allocate();
335     stations[n_stations++]=input;
336
337     if (n_stations==1) {
338       input->updater = update_smpte_colorbars;
339       input->do_teletext=1;
340     }
341 #ifdef USE_TEST_PATTERNS
342     else if (random()%5==0) {
343       j=random()%countof(test_patterns);
344       analogtv_setup_sync(input);
345       analogtv_load_xpm(tv, input, test_patterns[j]);
346       analogtv_setup_teletext(input);
347     }
348 #endif
349     else {
350       analogtv_load_random_image(tv, input);
351       input->do_teletext=1;
352     }
353   }
354 }
355
356
357 void
358 screenhack (Display *dpy, Window window)
359 {
360   int i;
361   int curinputi;
362   int change_ticks;
363   int using_mouse=0;
364   int change_now;
365   chansetting chansettings[N_CHANNELS];
366   chansetting *cs;
367   int last_station=42;
368   int delay = get_integer_resource("delay", "Integer");
369   if (delay < 1) delay = 1;
370
371   analogtv_make_font(dpy, window, &ugly_font, 7, 10, "6x10");
372   
373   tv=analogtv_allocate(dpy, window);
374   tv->event_handler = screenhack_handle_event;
375
376   add_stations();
377
378   analogtv_set_defaults(tv, "");
379   tv->need_clear=1;
380
381   if (random()%4==0) {
382     tv->tint_control += pow(frand(2.0)-1.0, 7) * 180.0;
383   }
384   if (1) {
385     tv->color_control += frand(0.3);
386   }
387
388   for (i=0; i<N_CHANNELS; i++) {
389     memset(&chansettings[i], 0, sizeof(chansetting));
390
391     chansettings[i].noise_level = 0.06;
392     chansettings[i].dur = 1000*delay;
393
394     if (random()%6==0) {
395       chansettings[i].dur=600;
396     }
397     else {
398       int stati;
399       for (stati=0; stati<MAX_MULTICHAN; stati++) {
400         analogtv_reception *rec=&chansettings[i].recs[stati];
401         int station;
402         while (1) {
403           station=random()%n_stations;
404           if (station!=last_station) break;
405           if ((random()%10)==0) break;
406         }
407         last_station=station;
408         rec->input = stations[station];
409         rec->level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
410         rec->ofs=random()%ANALOGTV_SIGNAL_LEN;
411         if (random()%3) {
412           rec->multipath = frand(1.0);
413         } else {
414           rec->multipath=0.0;
415         }
416         if (stati) {
417           /* We only set a frequency error for ghosting stations,
418              because it doesn't matter otherwise */
419           rec->freqerr = (frand(2.0)-1.0) * 3.0;
420         }
421
422         if (rec->level > 0.3) break;
423         if (random()%4) break;
424       }
425     }
426   }
427
428   gettimeofday(&basetime,NULL);
429
430   curinputi=0;
431   cs=&chansettings[curinputi];
432   change_ticks = cs->dur + 1500;
433
434   tv->powerup=0.0;
435   while (1) {
436     int curticks=getticks();
437     double curtime=curticks*0.001;
438
439     change_now=0;
440     if (analogtv_handle_events(tv)) {
441       using_mouse=1;
442       change_now=1;
443     }
444     if (change_now || (!using_mouse && curticks>=change_ticks 
445                        && tv->powerup > 10.0)) {
446       curinputi=(curinputi+1)%N_CHANNELS;
447       cs=&chansettings[curinputi];
448       change_ticks = curticks + cs->dur;
449       /* Set channel change noise flag */
450       tv->channel_change_cycles=200000;
451     }
452
453     for (i=0; i<MAX_MULTICHAN; i++) {
454       analogtv_reception *rec=&cs->recs[i];
455       analogtv_input *inp=rec->input;
456       if (!inp) continue;
457
458       if (inp->updater) {
459         inp->next_update_time = curtime;
460         (inp->updater)(inp);
461       }
462       rec->ofs += rec->freqerr;
463     }
464
465     tv->powerup=curtime;
466
467     analogtv_init_signal(tv, cs->noise_level);
468     for (i=0; i<MAX_MULTICHAN; i++) {
469       analogtv_reception *rec=&cs->recs[i];
470       analogtv_input *inp=rec->input;
471       if (!inp) continue;
472
473       analogtv_reception_update(rec);
474       analogtv_add_signal(tv, rec);
475     }
476     analogtv_draw(tv);
477   }
478
479   XSync(dpy, False);
480   XClearWindow(dpy, window);
481   
482   if (tv) analogtv_release(tv);
483 }
484