From http://www.jwz.org/xscreensaver/xscreensaver-5.40.tar.gz
[xscreensaver] / hacks / filmleader.c
1 /* filmleader, Copyright (c) 2018 Jamie Zawinski <jwz@jwz.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  * Simulate an SMPTE Universal Film Leader playing on an analog television.
12  */
13
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif /* HAVE_CONFIG_H */
17
18 #include "xft.h" /* before screenhack.h */
19
20 #include "screenhack.h"
21 #include "analogtv.h"
22
23 #include <time.h>
24
25 #undef countof
26 #define countof(x) (sizeof((x))/sizeof((*x)))
27
28 struct state {
29   Display *dpy;
30   Window window;
31   XWindowAttributes xgwa;
32   int w, h;
33   unsigned long bg, text_color, ring_color, trace_color;
34   XftColor xft_text_color_1, xft_text_color_2;
35
36   XftFont *font, *font2, *font3;
37   XftDraw *xftdraw;
38   Pixmap pix;
39   GC gc;
40   double start, last_time;
41   double value;
42   int stop;
43   double noise;
44   analogtv *tv;
45   analogtv_input *inp;
46   analogtv_reception rec;
47   Bool button_down_p;
48 };
49
50
51 static void *
52 filmleader_init (Display *dpy, Window window)
53 {
54   struct state *st = (struct state *) calloc (1, sizeof(*st));
55   XGCValues gcv;
56
57   st->dpy = dpy;
58   st->window = window;
59   st->tv = analogtv_allocate (st->dpy, st->window);
60   analogtv_set_defaults (st->tv, "");
61   st->tv->need_clear = 1;
62   st->inp = analogtv_input_allocate();
63   analogtv_setup_sync (st->inp, 1, 0);
64   st->rec.input = st->inp;
65   st->rec.level = 2.0;
66   st->tv->use_color = 1;
67   st->rec.level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
68   st->rec.ofs = random() % ANALOGTV_SIGNAL_LEN;
69   st->tv->powerup = 0;
70
71   st->rec.multipath = 0.0;
72   st->tv->color_control += frand(0.3);
73   st->noise = get_float_resource (st->dpy, "noise", "Float");
74   st->value = 18;  /* Leave time for powerup */
75   st->stop = 2 + (random() % 5);
76   XGetWindowAttributes (dpy, window, &st->xgwa);
77
78   /* Let's render it into a 16:9 pixmap, since that's what most screens are
79      these days.  That means the circle will be squashed on 4:3 screens. */
80   {
81     double r = 16/9.0;
82
83 # ifdef HAVE_MOBILE
84     /* analogtv.c always fills whole screen on mobile, so use screen aspect. */
85     r = st->xgwa.width / (double) st->xgwa.height;
86     if (r < 1) r = 1/r;
87 # endif
88
89     st->w = 712;
90     st->h = st->w / r;
91   }
92
93   if (st->xgwa.width < st->xgwa.height)
94     {
95       int swap = st->w;
96       st->w = st->h;
97       st->h = swap;
98     }
99
100   st->pix = XCreatePixmap (dpy, window,
101                            st->w > st->h ? st->w : st->h,
102                            st->w > st->h ? st->w : st->h,
103                            st->xgwa.depth);
104   st->gc = XCreateGC (dpy, st->pix, 0, &gcv);
105
106   st->xftdraw = XftDrawCreate (dpy, st->pix, st->xgwa.visual,
107                                st->xgwa.colormap);
108   st->font = load_xft_font_retry (dpy, screen_number (st->xgwa.screen),
109                                   get_string_resource (dpy, "numberFont",
110                                                        "Font"));
111   st->font2 = load_xft_font_retry (dpy, screen_number (st->xgwa.screen),
112                                    get_string_resource (dpy, "numberFont2",
113                                                         "Font"));
114   st->font3 = load_xft_font_retry (dpy, screen_number (st->xgwa.screen),
115                                    get_string_resource (dpy, "numberFont3",
116                                                         "Font"));
117
118   st->bg = get_pixel_resource (dpy, st->xgwa.colormap,
119                                "textBackground", "Background");
120   st->text_color = get_pixel_resource (dpy, st->xgwa.colormap,
121                                        "textColor", "Foreground");
122   st->ring_color = get_pixel_resource (dpy, st->xgwa.colormap,
123                                        "ringColor", "Foreground");
124   st->trace_color = get_pixel_resource (dpy, st->xgwa.colormap,
125                                         "traceColor", "Foreground");
126
127   XftColorAllocName (dpy, st->xgwa.visual, st->xgwa.colormap,
128                      get_string_resource (dpy, "textColor", "Foreground"),
129                      &st->xft_text_color_1);
130   XftColorAllocName (dpy, st->xgwa.visual, st->xgwa.colormap,
131                      get_string_resource (dpy, "textBackground", "Background"),
132                      &st->xft_text_color_2);
133
134   return st;
135 }
136
137
138 static double
139 double_time (void)
140 {
141   struct timeval now;
142 # ifdef GETTIMEOFDAY_TWO_ARGS
143   struct timezone tzp;
144   gettimeofday(&now, &tzp);
145 # else
146   gettimeofday(&now);
147 # endif
148
149   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
150 }
151
152
153 static unsigned long
154 filmleader_draw (Display *dpy, Window window, void *closure)
155 {
156   struct state *st = (struct state *) closure;
157   const analogtv_reception *rec = &st->rec;
158   double then = double_time(), now, timedelta;
159   XImage *img;
160   int i, x, y, w2, h2;
161   XGlyphInfo extents;
162   int lbearing, rbearing, ascent, descent;
163   char s[20];
164   double r = 1 - (st->value - (int) st->value);
165   int ivalue = st->value;
166   XftFont *xftfont;
167   XftColor *xftcolor;
168
169   /* You may ask, why use Xft for this instead of the much simpler XDrawString?
170      Well, for some reason, XLoadQueryFont is giving me horribly-scaled bitmap
171      fonts, but Xft works properly. So perhaps in This Modern World, if one
172      expects large fonts to work, one must use Xft instead of Xlib?
173
174      Everything is terrible.
175    */
176
177   const struct { double t; int k, f; const char * const s[4]; } blurbs[] = {
178     {  9.1, 3, 1, { "PICTURE", "  START", 0, 0 }},
179     { 10.0, 2, 1, { "    16", "SOUND", "START", 0 }},
180     { 10.5, 2, 1, { "    32", "SOUND", "START", 0 }},
181     { 11.6, 2, 0, { "PICTURE", "COMPANY", "SERIES", 0 }},
182     { 11.7, 2, 0, { "XSCRNSAVER", 0, 0, 0 }},
183     { 11.9, 2, 0, { "REEL No.", "PROD No.", "PLAY DATE", 0 }},
184     { 12.2, 0, 0, { "    SMPTE     ", "UNIVERSAL", "   LEADER", 0 }},
185     { 12.3, 0, 1, { "X           ", "X", "X", "X" }},
186     { 12.4, 0, 0, { "    SMPTE     ", "UNIVERSAL", "   LEADER", 0 }},
187     { 12.5, 3, 1, { "PICTURE", 0, 0, 0 }},
188     { 12.7, 3, 1, { "HEAD", 0, 0, 0 }},
189     { 12.8, 2, 1, { "OOOO", 0, "ASPECT", "TYPE OF" }},
190     { 12.9, 2, 0, { "SOUND", 0, "RATIO", 0 }},
191     { 13.2, 1, 1, { "                  ", "PICTURE", 0, 0 }},
192     { 13.3, 1, 0, { "REEL No.      ", "COLOR", 0, 0 }},
193     { 13.4, 1, 0, { "LENGTH        ", 0, 0, "ROLL" }},
194     { 13.5, 1, 0, { "SUBJECT", 0, 0, 0 }},
195     { 13.9, 1, 1, { "     \342\206\221", "SPLICE", " HERE", 0 }},
196   };
197
198   for (i = 0; i < countof(blurbs); i++)
199     {
200       if (st->value >= blurbs[i].t && st->value <= blurbs[i].t + 1/15.0)
201         {
202           int line_height;
203           int j;
204           xftfont = (blurbs[i].f == 1 ? st->font2 :
205                      blurbs[i].f == 2 ? st->font : st->font3);
206
207           XSetForeground (dpy, st->gc, 
208                           blurbs[i].k == 3 ? st->bg : st->text_color);
209           XFillRectangle (dpy, st->pix, st->gc, 0, 0, st->w, st->h);
210           XSetForeground (dpy, st->gc,
211                           blurbs[i].k == 3 ? st->text_color : st->bg);
212           xftcolor = (blurbs[i].k == 3 ? 
213                       &st->xft_text_color_1 : &st->xft_text_color_2);
214
215           /* The height of a string of spaces is 0... */
216           XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) "My", 2, &extents);
217           line_height = extents.height;
218
219           XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *)
220                               blurbs[i].s[0], strlen(blurbs[i].s[0]),
221                               &extents);
222           lbearing = -extents.x;
223           rbearing = extents.width - extents.x;
224           ascent   = extents.y;
225           descent  = extents.height - extents.y;
226
227           x = (st->w - rbearing) / 2;
228           y = st->h * 0.1 + ascent;
229
230           for (j = 0; j < countof(blurbs[i].s); j++)
231             {
232               if (blurbs[i].s[j])
233                 XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y,
234                                    (FcChar8 *) blurbs[i].s[j],
235                                    strlen(blurbs[i].s[j]));
236
237               y += line_height * 1.5;
238
239               if (blurbs[i].s[j])
240                 {
241                   XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *)
242                                       blurbs[i].s[0], strlen(blurbs[i].s[j]),
243                                       &extents);
244                   lbearing = -extents.x;
245                   rbearing = extents.width - extents.x;
246                   ascent   = extents.y;
247                   descent  = extents.height - extents.y;
248                 }
249             }
250
251           if (blurbs[i].k == 2)  /* Rotate clockwise and flip */
252             {
253               int wh = st->w < st->h ? st->w : st->h;
254               XImage *img1 = XGetImage (dpy, st->pix,
255                                         (st->w - wh) / 2,
256                                         (st->h - wh) / 2,
257                                         wh, wh, ~0L, ZPixmap);
258               XImage *img2 = XCreateImage (dpy, st->xgwa.visual,
259                                            st->xgwa.depth, ZPixmap, 0, 0,
260                                            wh, wh, 32, 0);
261               img2->data = malloc (img2->bytes_per_line * img2->height);
262               for (y = 0; y < wh; y++)
263                 for (x = 0; x < wh; x++)
264                   XPutPixel (img2, y, x, XGetPixel (img1, x, y));
265               XSetForeground (dpy, st->gc, 
266                               blurbs[i].k == 3 ? st->bg : st->text_color);
267               XFillRectangle (dpy, st->pix, st->gc, 0, 0, st->w, st->h);
268               XPutImage (dpy, st->pix, st->gc, img2,
269                          0, 0,
270                          (st->w - wh) / 2,
271                          (st->h - wh) / 2,
272                          wh, wh);
273               XDestroyImage (img1);
274               XDestroyImage (img2);
275             }
276           else if (blurbs[i].k == 1)  /* Flip vertically */
277             {
278               XImage *img1 = XGetImage (dpy, st->pix, 0, 0,
279                                         st->w, st->h, ~0L, ZPixmap);
280               XImage *img2 = XCreateImage (dpy, st->xgwa.visual,
281                                            st->xgwa.depth, ZPixmap, 0, 0,
282                                            st->w, st->h, 32, 0);
283               img2->data = malloc (img2->bytes_per_line * img2->height);
284               for (y = 0; y < img2->height; y++)
285                 for (x = 0; x < img2->width; x++)
286                   XPutPixel (img2, x, img2->height-y-1,
287                              XGetPixel (img1, x, y));
288               XPutImage (dpy, st->pix, st->gc, img2, 0, 0, 0, 0, st->w, st->h);
289               XDestroyImage (img1);
290               XDestroyImage (img2);
291             }
292
293           goto DONE;
294         }
295     }
296
297   if (st->value < 2.0 || st->value >= 9.0)      /* Black screen */
298     {
299       XSetForeground (dpy, st->gc, st->text_color);
300       XFillRectangle (dpy, st->pix, st->gc, 0, 0, st->w, st->h);
301       goto DONE;
302     }
303
304   XSetForeground (dpy, st->gc, st->bg);
305   XFillRectangle (dpy, st->pix, st->gc, 0, 0, st->w, st->h);
306
307   if (r > 1/30.0)                               /* Sweep line and background */
308     {
309       x = st->w/2 + st->w * cos (M_PI * 2 * r - M_PI/2);
310       y = st->h/2 + st->h * sin (M_PI * 2 * r - M_PI/2);
311
312       XSetForeground (dpy, st->gc, st->trace_color);
313       XFillArc (dpy, st->pix, st->gc,
314                 -st->w, -st->h, st->w*3, st->h*3,
315                 90*64,
316                 90*64 - ((r + 0.25) * 360*64));
317
318       XSetForeground (dpy, st->gc, st->text_color);
319       XSetLineAttributes (dpy, st->gc, 1, LineSolid, CapRound, JoinRound);
320       XDrawLine (dpy, st->pix, st->gc, st->w/2, st->h/2, x, y);
321   
322       XSetForeground (dpy, st->gc, st->text_color);
323       XSetLineAttributes (dpy, st->gc, 2, LineSolid, CapRound, JoinRound);
324       XDrawLine (dpy, st->pix, st->gc, st->w/2, 0, st->w/2, st->h);
325       XDrawLine (dpy, st->pix, st->gc, 0, st->h/2, st->w, st->h/2);
326     }
327
328   /* Big number */
329
330   s[0] = (char) (ivalue + '0');
331   xftfont = st->font;
332   xftcolor = &st->xft_text_color_1;
333   XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) s, 1, &extents);
334   lbearing = -extents.x;
335   rbearing = extents.width - extents.x;
336   ascent   = extents.y;
337   descent  = extents.height - extents.y;
338
339   x = (st->w - (rbearing + lbearing)) / 2;
340   y = (st->h + (ascent - descent)) / 2;
341   XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y, (FcChar8 *) s, 1);
342
343   /* Annotations on 7 and 4 */
344
345   if ((st->value >= 7.75 && st->value <= 7.85) ||
346       (st->value >= 4.00 && st->value <= 4.25))
347     {
348       XSetForeground (dpy, st->gc, st->ring_color);
349       xftcolor = &st->xft_text_color_2;
350       xftfont = st->font2;
351
352       s[0] = (ivalue == 4 ? 'C' : 'M');
353       s[1] = 0;
354
355       XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) s, strlen(s), &extents);
356       lbearing = -extents.x;
357       rbearing = extents.width - extents.x;
358       ascent   = extents.y;
359       descent  = extents.height - extents.y;
360
361       x = st->w * 0.1;
362       y = st->h * 0.1 + ascent;
363       XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y,
364                          (FcChar8 *) s, strlen(s));
365       x = st->w * 0.9 - (rbearing + lbearing);
366       XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y,
367                          (FcChar8 *) s, strlen(s));
368
369       s[0] = (ivalue == 4 ? 'F' : '3');
370       s[1] = (ivalue == 4 ? 0   : '5');
371       s[2] = 0;
372
373       XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) s, strlen(s), &extents);
374       lbearing = -extents.x;
375       rbearing = extents.width - extents.x;
376       ascent   = extents.y;
377       descent  = extents.height - extents.y;
378
379       x = st->w * 0.1;
380       y = st->h * 0.95;
381       XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y, 
382                          (FcChar8 *) s, strlen(s));
383       x = st->w * 0.9 - (rbearing + lbearing);
384       XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y,
385                          (FcChar8 *) s, strlen(s));
386     }
387
388   if (r > 1/30.0)                               /* Two rings around number */
389     {
390       double r2 = st->w / (double) st->h;
391       double ss = 1;
392
393       if (st->xgwa.width < st->xgwa.height)
394         ss = 0.5;
395
396       XSetForeground (dpy, st->gc, st->ring_color);
397       XSetLineAttributes (dpy, st->gc, st->w * 0.025,
398                           LineSolid, CapRound, JoinRound);
399
400       w2 = st->w * 0.8 * ss / r2;
401       h2 = st->h * 0.8 * ss;
402       x = (st->w - w2) / 2;
403       y = (st->h - h2) / 2;
404       XDrawArc (dpy, st->pix, st->gc, x, y, w2, h2, 0, 360*64);
405
406       w2 = w2 * 0.8;
407       h2 = h2 * 0.8;
408       x = (st->w - w2) / 2;
409       y = (st->h - h2) / 2;
410       XDrawArc (dpy, st->pix, st->gc, x, y, w2, h2, 0, 360*64);
411     }
412
413  DONE:
414
415   img = XGetImage (dpy, st->pix, 0, 0, st->w, st->h, ~0L, ZPixmap);
416
417   analogtv_load_ximage (st->tv, st->rec.input, img, 0, 0, 0, 0, 0);
418   analogtv_reception_update (&st->rec);
419   analogtv_draw (st->tv, st->noise, &rec, 1);
420
421   XDestroyImage (img);
422
423   now = double_time();
424   timedelta = (1 / 29.97) - (now - then);
425
426   if (! st->button_down_p)
427     {
428       if (st->last_time == 0)
429         st->start = then;
430       else
431         st->value -= then - st->last_time;
432
433       if (st->value <= 0 ||
434           (r > 0.9 && st->value <= st->stop))
435         {
436           st->value = (random() % 20) ? 8.9 : 15;
437           st->stop = ((random() % 50) ? 2 : 1) + (random() % 5);
438
439           if (st->value > 9)    /* Spin the knobs again */
440             {
441               st->rec.level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
442               st->rec.ofs = random() % ANALOGTV_SIGNAL_LEN;
443               st->tv->color_control += frand(0.3) - 0.15;
444             }
445         }
446     }
447
448   st->tv->powerup = then - st->start;
449   st->last_time = then;
450
451   return timedelta > 0 ? timedelta * 1000000 : 0;
452 }
453
454
455 static void
456 filmleader_reshape (Display *dpy, Window window, void *closure, 
457                     unsigned int w, unsigned int h)
458 {
459   struct state *st = (struct state *) closure;
460   analogtv_reconfigure (st->tv);
461   XGetWindowAttributes (dpy, window, &st->xgwa);
462
463   if ((st->w > st->h) != (st->xgwa.width > st->xgwa.height))
464     {
465       int swap = st->w;
466       st->w = st->h;
467       st->h = swap;
468     }
469 }
470
471
472 static Bool
473 filmleader_event (Display *dpy, Window window, void *closure, XEvent *event)
474 {
475   struct state *st = (struct state *) closure;
476   if (event->xany.type == ButtonPress)
477     {
478       st->button_down_p = True;
479       return True;
480     }
481   else if (event->xany.type == ButtonRelease)
482     {
483       st->button_down_p = False;
484       return True;
485     }
486   else if (screenhack_event_helper (dpy, window, event))
487     {
488       st->value = 15;
489       st->rec.level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
490       st->rec.ofs = random() % ANALOGTV_SIGNAL_LEN;
491       st->tv->color_control += frand(0.3) - 0.15;
492       return True;
493     }
494   else if (event->xany.type == KeyPress)
495     {
496       KeySym keysym;
497       char c = 0;
498       XLookupString (&event->xkey, &c, 1, &keysym, 0);
499       if (c >= '2' && c <= '8')
500         {
501           st->value = (c - '0') + (st->value - (int) st->value);
502           return True;
503         }
504     }
505
506   return False;
507 }
508
509
510 static void
511 filmleader_free (Display *dpy, Window window, void *closure)
512 {
513   struct state *st = (struct state *) closure;
514   analogtv_release (st->tv);
515   XftDrawDestroy (st->xftdraw);
516   XftColorFree(dpy, st->xgwa.visual, st->xgwa.colormap, &st->xft_text_color_1);
517   XftColorFree(dpy, st->xgwa.visual, st->xgwa.colormap, &st->xft_text_color_2);
518   XFreePixmap (dpy, st->pix);
519   XFreeGC (dpy, st->gc);
520   free (st);
521 }
522
523
524 static const char *filmleader_defaults [] = {
525
526   ".background:  #000000",
527
528 # ifdef HAVE_MOBILE
529
530   "*textBackground: #444488",  /* Need much higher contrast for some reason */
531   "*textColor:      #000033",
532   "*ringColor:      #DDDDFF",
533   "*traceColor:     #222244",
534
535 # else /* X11 or Cocoa */
536
537   "*textBackground: #9999DD",
538   "*textColor:      #000015",
539   "*ringColor:      #DDDDFF",
540   "*traceColor:     #555577",
541
542 # endif
543
544 # ifdef USE_IPHONE
545
546   "*numberFont:  Helvetica Bold 120",
547   "*numberFont2: Helvetica 36",
548   "*numberFont3: Helvetica 28",
549
550 # else /* X11, Cocoa or Android */
551
552   "*numberFont:  -*-helvetica-bold-r-*-*-*-1700-*-*-*-*-*-*",
553   "*numberFont2: -*-helvetica-medium-r-*-*-*-500-*-*-*-*-*-*",
554   "*numberFont3: -*-helvetica-medium-r-*-*-*-360-*-*-*-*-*-*",
555
556 # endif
557
558
559   "*noise:       0.04",
560   ANALOGTV_DEFAULTS
561   "*geometry: 1280x720",
562   0
563 };
564
565 static XrmOptionDescRec filmleader_options [] = {
566   { "-noise",           ".noise",     XrmoptionSepArg, 0 },
567   ANALOGTV_OPTIONS
568   { 0, 0, 0, 0 }
569 };
570
571 XSCREENSAVER_MODULE ("FilmLeader", filmleader)